///|
/// Parser entrypoints for moonpython (see parser_*.mbt for implementation).
///

///|
pub fn parse(source : String) -> Result[Module, ParseError] {
  let lex_source = source.to_string()
  match scan_source_lexical_errors(lex_source) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let mismatch_hint = first_bracket_mismatch_line(source.to_string())
  let chars = source.to_array()
  let mut i = 0
  let mut line = 1
  let mut line_start = 0
  let mut quote : Char? = None
  let mut escape = false
  let bracket_stack : Array[(Char, Int32, Int32, Int32)] = []
  let mut unclosed_open : (Char, Int32, Int32, Int32)? = None
  while i < chars.length() {
    let c = chars[i]
    match quote {
      Some(q) => {
        if escape {
          escape = false
          i += 1
          continue
        }
        if c == '\\' {
          escape = true
          i += 1
          continue
        }
        if c == q {
          quote = None
        }
        i += 1
        continue
      }
      None => ()
    }
    if c == '\n' {
      line += 1
      i += 1
      line_start = i
      continue
    }
    if c == '#' {
      while i < chars.length() && chars[i] != '\n' {
        i += 1
      }
      continue
    }
    if c == '\'' || c == '"' {
      // Skip over triple-quoted strings so bracket mismatch detection doesn't
      // accidentally inspect parentheses/brackets inside docstrings.
      if i + 2 < chars.length() && chars[i + 1] == c && chars[i + 2] == c {
        let q = c
        i += 3
        while i < chars.length() {
          if chars[i] == '\n' {
            line += 1
            i += 1
            line_start = i
            continue
          }
          if i + 2 < chars.length() &&
            chars[i] == q &&
            chars[i + 1] == q &&
            chars[i + 2] == q {
            i += 3
            break
          }
          i += 1
        }
        continue
      }
      quote = Some(c)
      escape = false
      i += 1
      continue
    }
    if c == '(' {
      let column = i - line_start + 1
      bracket_stack.push((c, i, line, column))
      i += 1
      continue
    }
    if c == '[' {
      let column = i - line_start + 1
      bracket_stack.push((c, i, line, column))
      i += 1
      continue
    }
    if c == '{' {
      let column = i - line_start + 1
      bracket_stack.push((c, i, line, column))
      i += 1
      continue
    }
    if c == ')' {
      if bracket_stack.length() == 0 {
        let token = {
          kind: Eof,
          start: i,
          end: i + 1,
          line,
          column: i - line_start + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "unmatched ')'".to_string(),
            token,
          ),
        )
      }
      let open = bracket_stack[bracket_stack.length() - 1]
      let _ = bracket_stack.pop()
      if open.0 != '(' {
        let token = {
          kind: Eof,
          start: i,
          end: i + 1,
          line,
          column: i - line_start + 1,
        }
        let message = "closing " +
          bracket_name(c) +
          " '" +
          c.to_string() +
          "' does not match opening " +
          bracket_name(open.0) +
          " '" +
          open.0.to_string() +
          "'"
        return Err(make_parse_error(ParseErrorKind::Syntax, message, token))
      }
      i += 1
      continue
    }
    if c == ']' {
      if bracket_stack.length() == 0 {
        let token = {
          kind: Eof,
          start: i,
          end: i + 1,
          line,
          column: i - line_start + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "unmatched ']'".to_string(),
            token,
          ),
        )
      }
      let open = bracket_stack[bracket_stack.length() - 1]
      let _ = bracket_stack.pop()
      if open.0 != '[' {
        let token = {
          kind: Eof,
          start: i,
          end: i + 1,
          line,
          column: i - line_start + 1,
        }
        let message = "closing " +
          bracket_name(c) +
          " '" +
          c.to_string() +
          "' does not match opening " +
          bracket_name(open.0) +
          " '" +
          open.0.to_string() +
          "'"
        return Err(make_parse_error(ParseErrorKind::Syntax, message, token))
      }
      i += 1
      continue
    }
    if c == '}' {
      if bracket_stack.length() == 0 {
        let token = {
          kind: Eof,
          start: i,
          end: i + 1,
          line,
          column: i - line_start + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "unmatched '}'".to_string(),
            token,
          ),
        )
      }
      let open = bracket_stack[bracket_stack.length() - 1]
      let _ = bracket_stack.pop()
      if open.0 != '{' {
        let token = {
          kind: Eof,
          start: i,
          end: i + 1,
          line,
          column: i - line_start + 1,
        }
        let message = "closing " +
          bracket_name(c) +
          " '" +
          c.to_string() +
          "' does not match opening " +
          bracket_name(open.0) +
          " '" +
          open.0.to_string() +
          "'"
        return Err(make_parse_error(ParseErrorKind::Syntax, message, token))
      }
      i += 1
      continue
    }
    i += 1
  }
  if bracket_stack.length() > 0 {
    unclosed_open = Some(bracket_stack[bracket_stack.length() - 1])
  }
  let parser = LineParser::new(source)
  match parser.parse_block(0) {
    Ok(body) =>
      match unclosed_open {
        Some(open) => {
          let token = {
            kind: Eof,
            start: open.1,
            end: open.1 + 1,
            line: open.2,
            column: open.3,
          }
          let message = "'" + open.0.to_string() + "' was never closed"
          Err(make_parse_error(ParseErrorKind::Syntax, message, token))
        }
        None =>
          match stmts_first_invalid_control_flow_line(body, false, 0) {
            Some(line) => Err(parser.invalid_syntax_at_line(line))
            None => Ok(Module::{ body, })
          }
      }
    Err(err) =>
      match err.kind {
        ParseErrorKind::Indentation => Err(err)
        _ => {
          let mut final_err = err
          match unclosed_open {
            Some(open) =>
              match final_err.kind {
                ParseErrorKind::Syntax =>
                  if final_err.span.line == open.2 {
                    let token = {
                      kind: Eof,
                      start: open.1,
                      end: open.1 + 1,
                      line: open.2,
                      column: open.3,
                    }
                    let message = "'" +
                      open.0.to_string() +
                      "' was never closed"
                    final_err = make_parse_error(
                      ParseErrorKind::Syntax,
                      message,
                      token,
                    )
                  }
                _ => ()
              }
            None => ()
          }
          match mismatch_hint {
            Some(mismatch_line) =>
              if final_err.span.line == 1 && mismatch_line > 1 {
                Err(ParseError::{
                  kind: final_err.kind,
                  message: final_err.message,
                  span: Span::{
                    start: final_err.span.start,
                    end: final_err.span.end,
                    line: mismatch_line,
                    column: 1,
                  },
                })
              } else {
                Err(final_err)
              }
            None => Err(final_err)
          }
        }
      }
  }
}

///|
pub fn parse_expr(source : String) -> Result[Expr, ParseError] {
  // `eval()`-style expression parsing: allow multi-line expressions only when
  // they are continued via brackets, backslash-newlines, or f-string braces.
  let mut expr_source = source
  if expr_source.contains("\n") {
    let lines : Array[String] = []
    for part in expr_source.split("\n") {
      lines.push(part.to_owned())
    }
    // Skip leading blank/comment-only lines.
    let mut start_index = 0
    while start_index < lines.length() &&
          strip_inline_comment(lines[start_index]).trim(chars=" \t\r").length() ==
          0 {
      start_index += 1
    }
    if start_index >= lines.length() {
      return Err(
        make_parse_error(ParseErrorKind::Syntax, "invalid syntax".to_string(), {
          kind: Eof,
          start: 0,
          end: 1,
          line: 1,
          column: 1,
        }),
      )
    }
    let raw_line = lines[start_index]
    let mut line_text = raw_line.trim(chars=" \t").to_owned()
    if find_triple_quote(raw_line) is None {
      line_text = strip_inline_comment(line_text).trim(chars=" \t").to_owned()
    }
    let mut next_index = start_index + 1
    match unclosed_triple_quote(raw_line) {
      None => ()
      Some(_) => {
        let mut combined = raw_line
        let mut idx = start_index + 1
        while idx < lines.length() {
          let next_line = lines[idx]
          combined = combined + "\n" + next_line
          idx += 1
          if unclosed_triple_quote(combined) is None {
            next_index = idx
            line_text = combined
            break
          }
        }
        if idx == lines.length() && unclosed_triple_quote(combined) is Some(_) {
          line_text = combined
          next_index = idx
        }
      }
    }
    while has_unclosed_string_line_continuation(line_text) &&
          next_index < lines.length() {
      let next_line = lines[next_index]
      line_text = line_text + "\n" + next_line
      next_index += 1
    }
    while has_trailing_line_continuation(line_text) &&
          next_index < lines.length() {
      line_text = substring(line_text, 0, line_text.length() - 1)
        .trim(chars=" \t")
        .to_owned()
      let next_line = lines[next_index]
      let appended = if find_triple_quote(next_line) is Some(_) {
        next_line
      } else {
        strip_inline_comment(next_line)
      }
      line_text = line_text + "\n" + appended
      next_index += 1
      line_text = line_text.trim(chars=" \t").to_owned()
    }
    match unclosed_triple_quote(line_text) {
      None => ()
      Some(_) => {
        let mut combined = line_text
        let mut idx = next_index
        while idx < lines.length() {
          let next_line = lines[idx]
          combined = combined + "\n" + next_line
          idx += 1
          if unclosed_triple_quote(combined) is None {
            next_index = idx
            line_text = combined
            break
          }
        }
        if idx == lines.length() && unclosed_triple_quote(combined) is Some(_) {
          line_text = combined
          next_index = idx
        }
      }
    }
    let mut balance = bracket_balance(line_text)
    while balance > 0 && next_index < lines.length() {
      let next_line = lines[next_index]
      let appended = if find_triple_quote(next_line) is Some(_) {
        next_line
      } else {
        strip_inline_comment(next_line)
      }
      line_text = line_text + "\n" + appended
      next_index += 1
      balance = bracket_balance(line_text)
    }
    if find_triple_quote(line_text) is None {
      line_text = replace_newlines_with_spaces(line_text)
        .trim(chars=" \t")
        .to_owned()
    }
    // Reject additional non-empty lines that are not part of a continued
    // expression (eval-mode).
    let mut extra_index = next_index
    while extra_index < lines.length() &&
          strip_inline_comment(lines[extra_index]).trim(chars=" \t\r").length() ==
          0 {
      extra_index += 1
    }
    if extra_index < lines.length() {
      return Err(
        make_parse_error(ParseErrorKind::Syntax, "invalid syntax".to_string(), {
          kind: Eof,
          start: 0,
          end: 1,
          line: extra_index + 1,
          column: 1,
        }),
      )
    }
    expr_source = line_text
  }
  let tokens = match tokenize_expr(expr_source, 1, 0, 1) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let expr = match parse_tokens_expr(tokens) {
    Ok(v) => v
    Err(err) => return Err(err)
  }
  if expr_has_async_comprehension(expr) {
    return Err(
      make_parse_error(ParseErrorKind::Syntax, "invalid syntax".to_string(), {
        kind: Eof,
        start: 0,
        end: 1,
        line: 1,
        column: 1,
      }),
    )
  }
  // `eval()` mode: `yield` / `await` are not allowed outside (async) functions.
  if expr_has_yield_or_await(expr) {
    return Err(
      make_parse_error(ParseErrorKind::Syntax, "invalid syntax".to_string(), {
        kind: Eof,
        start: 0,
        end: 1,
        line: 1,
        column: 1,
      }),
    )
  }
  Ok(expr)
}