///|
struct Token {
  text : String
  line : Int
  column : Int
} derive(Eq, Debug)

///|
struct Statement {
  tokens : Array[Token]
  indented : Bool
  line : Int
} derive(Debug)

///|
struct LexResult {
  statements : Array[Statement]
  diagnostics : Array[Diagnostic]
} derive(Debug)

///|
fn is_space(c : Char) -> Bool {
  c == ' ' || c == '\t' || c == '\r'
}

///|
fn flush_token(
  tokens : Array[Token],
  builder : StringBuilder,
  active : Bool,
  line : Int,
  column : Int,
) -> Unit {
  if active {
    tokens.push({ text: builder.to_string(), line, column, })
  }
}

///|
/// Lex RFC 1035 master-file statements, retaining locations and inherited-owner indentation.
fn lex_zone(input : String) -> LexResult {
  let chars = input.to_array()
  let statements : Array[Statement] = []
  let diagnostics : Array[Diagnostic] = []
  let mut tokens : Array[Token] = []
  let mut builder = StringBuilder()
  let mut active = false
  let mut quoted = false
  let mut escaped = false
  let mut comment = false
  let mut depth = 0
  let mut line = 1
  let mut column = 1
  let mut token_line = 1
  let mut token_column = 1
  let mut statement_line = 1
  let mut indented = false
  let mut at_line_start = true
  for c in chars {
    if c == '\n' {
      if escaped {
        builder.write_char(c)
        escaped = false
      } else if quoted {
        diagnostics.push(
          diagnostic(
            "Z001", "error", "unterminated quoted text", token_line, token_column,
          ),
        )
        quoted = false
        flush_token(tokens, builder, active, token_line, token_column)
        builder = StringBuilder()
        active = false
      } else {
        flush_token(tokens, builder, active, token_line, token_column)
        builder = StringBuilder()
        active = false
      }
      comment = false
      if depth == 0 && tokens.length() > 0 {
        statements.push({ tokens, indented, line: statement_line, })
        tokens = []
      }
      line += 1
      column = 1
      at_line_start = true
      if depth == 0 {
        statement_line = line
        indented = false
      }
      continue
    }
    if comment {
      column += 1
      continue
    }
    if at_line_start {
      if is_space(c) {
        if depth == 0 && tokens.length() == 0 {
          indented = true
        }
      } else {
        at_line_start = false
      }
    }
    if escaped {
      builder.write_char(c)
      escaped = false
      column += 1
      continue
    }
    if c == '\\' {
      if !active {
        token_line = line
        token_column = column
        active = true
      }
      builder.write_char(c)
      escaped = true
      column += 1
      continue
    }
    if c == '"' {
      if !active {
        token_line = line
        token_column = column
        active = true
      }
      quoted = !quoted
      builder.write_char(c)
      column += 1
      continue
    }
    if !quoted && c == ';' {
      flush_token(tokens, builder, active, token_line, token_column)
      builder = StringBuilder()
      active = false
      comment = true
      column += 1
      continue
    }
    if !quoted && (c == '(' || c == ')') {
      flush_token(tokens, builder, active, token_line, token_column)
      builder = StringBuilder()
      active = false
      if c == '(' {
        depth += 1
      } else if depth == 0 {
        diagnostics.push(
          diagnostic(
            "Z002", "error", "unmatched closing parenthesis", line, column,
          ),
        )
      } else {
        depth -= 1
      }
      column += 1
      continue
    }
    if !quoted && is_space(c) {
      flush_token(tokens, builder, active, token_line, token_column)
      builder = StringBuilder()
      active = false
      column += 1
      continue
    }
    if !active {
      token_line = line
      token_column = column
      active = true
    }
    builder.write_char(c)
    column += 1
  }
  if escaped {
    diagnostics.push(
      diagnostic("Z003", "error", "unfinished escape", token_line, token_column),
    )
  }
  if quoted {
    diagnostics.push(
      diagnostic(
        "Z001", "error", "unterminated quoted text", token_line, token_column,
      ),
    )
  }
  if depth > 0 {
    diagnostics.push(
      diagnostic("Z004", "error", "unclosed parenthesis", statement_line, 1),
    )
  }
  flush_token(tokens, builder, active, token_line, token_column)
  if tokens.length() > 0 {
    statements.push({ tokens, indented, line: statement_line, })
  }
  { statements, diagnostics, }
}