///|
/// Class and function definition parsing for line blocks.

///|
fn parse_class_stmt(
  parser : LineParser,
  stmts : Array[Stmt],
  pending_decorators : Array[Expr],
  pending_decorator_token : Token?,
  line_text : String,
  line_index : Int32,
  current_indent : Int32,
  next_index : Int32,
) -> Result[(Bool, Array[Expr], Token?), ParseError] {
  if !line_text.has_prefix("class ") {
    return Ok((false, pending_decorators, pending_decorator_token))
  }
  let decorators = pending_decorators
  let next_pending : Array[Expr] = []
  let next_token : Token? = None
  let colon_pos = find_top_level_colon(line_text)
  if colon_pos is None {
    let token = {
      kind: Eof,
      start: parser.offsets[parser.index] + current_indent,
      end: parser.offsets[parser.index] + current_indent + 1,
      line: parser.index + 1,
      column: 1,
    }
    return Err(
      make_parse_error(
        ParseErrorKind::Syntax,
        "invalid syntax".to_string(),
        token,
      ),
    )
  }
  let header = substring(line_text, 6, colon_pos.unwrap())
    .trim(chars=" \t\n\r")
    .to_owned()
  let paren_span = find_top_level_paren_span(header)
  let name_part = match paren_span {
    Some(span) => substring(header, 0, span.0).trim(chars=" \t\n\r").to_owned()
    None => header
  }
  let name_token = {
    kind: Eof,
    start: parser.offsets[parser.index] + current_indent + 6,
    end: parser.offsets[parser.index] + current_indent + 7,
    line: parser.index + 1,
    column: current_indent + 7,
  }
  let (name, type_params) = match
    parse_type_param_header(name_part, name_token) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let bases : Array[Expr] = []
  let keywords : Array[(String, Expr)] = []
  if paren_span is Some(span) {
    let open_pos = span.0
    let close_pos = span.1
    let tail = substring(header, close_pos + 1, header.length())
      .trim(chars=" \t\n\r")
      .to_owned()
    if tail.length() > 0 {
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "invalid syntax".to_string(),
          name_token,
        ),
      )
    }
    let bases_text = substring(header, open_pos + 1, close_pos)
      .trim(chars=" \t\n\r")
      .to_owned()
    if bases_text.length() > 0 {
      let base_parts = split_top_level_commas(bases_text)
      for part in base_parts {
        let expr_text = part.trim(chars=" \t\n\r").to_owned()
        if expr_text.length() == 0 {
          continue
        }
        if expr_text.has_prefix("**") {
          let inner = substring(expr_text, 2, expr_text.length())
            .trim(chars=" \t\n\r")
            .to_owned()
          if inner.length() == 0 {
            return Err(
              make_parse_error(
                ParseErrorKind::Syntax,
                "invalid syntax".to_string(),
                name_token,
              ),
            )
          }
          match parse_expr(inner) {
            Ok(expr) => keywords.push(("**", expr))
            Err(_) =>
              return Err(
                make_parse_error(
                  ParseErrorKind::Syntax,
                  "invalid syntax".to_string(),
                  name_token,
                ),
              )
          }
          continue
        }
        if expr_text.has_prefix("*") {
          let inner = substring(expr_text, 1, expr_text.length())
            .trim(chars=" \t\n\r")
            .to_owned()
          if inner.length() == 0 {
            return Err(
              make_parse_error(
                ParseErrorKind::Syntax,
                "invalid syntax".to_string(),
                name_token,
              ),
            )
          }
          match parse_expr(inner) {
            Ok(expr) => bases.push(Expr::Starred(value=expr))
            Err(_) =>
              return Err(
                make_parse_error(
                  ParseErrorKind::Syntax,
                  "invalid syntax".to_string(),
                  name_token,
                ),
              )
          }
          continue
        }
        if has_bare_assign(expr_text) {
          let eq_pos = find_char(expr_text, '=')
          if eq_pos is Some(eq_idx) {
            let key = substring(expr_text, 0, eq_idx)
              .trim(chars=" \t\n\r")
              .to_owned()
            let value_text = substring(
                expr_text,
                eq_idx + 1,
                expr_text.length(),
              )
              .trim(chars=" \t\n\r")
              .to_owned()
            if key.length() == 0 || value_text.length() == 0 {
              let token = {
                kind: Eof,
                start: parser.offsets[parser.index] + current_indent + 6,
                end: parser.offsets[parser.index] + current_indent + 7,
                line: parser.index + 1,
                column: 1,
              }
              return Err(
                make_parse_error(
                  ParseErrorKind::Syntax,
                  "invalid syntax".to_string(),
                  token,
                ),
              )
            }
            match parse_expr(value_text) {
              Ok(expr) => keywords.push((key, expr))
              Err(_) => {
                let token = {
                  kind: Eof,
                  start: parser.offsets[parser.index] + current_indent + 6,
                  end: parser.offsets[parser.index] + current_indent + 7,
                  line: parser.index + 1,
                  column: 1,
                }
                return Err(
                  make_parse_error(
                    ParseErrorKind::Syntax,
                    "invalid syntax".to_string(),
                    token,
                  ),
                )
              }
            }
            continue
          } else {
            continue
          }
        }
        match parse_expr(expr_text) {
          Ok(expr) => bases.push(expr)
          Err(_) => {
            let token = {
              kind: Eof,
              start: parser.offsets[parser.index] + current_indent + 6,
              end: parser.offsets[parser.index] + current_indent + 7,
              line: parser.index + 1,
              column: 1,
            }
            return Err(
              make_parse_error(
                ParseErrorKind::Syntax,
                "invalid syntax".to_string(),
                token,
              ),
            )
          }
        }
      }
    }
  }
  if name.length() == 0 {
    let token = {
      kind: Eof,
      start: parser.offsets[parser.index] + current_indent,
      end: parser.offsets[parser.index] + current_indent + 1,
      line: parser.index + 1,
      column: 1,
    }
    return Err(
      make_parse_error(
        ParseErrorKind::Syntax,
        "invalid syntax".to_string(),
        token,
      ),
    )
  }
  if is_async_await_reserved_name(name) {
    return Err(parser.invalid_syntax_at_line(parser.index + 1))
  }
  let body_text = strip_inline_comment(
      substring(line_text, colon_pos.unwrap() + 1, line_text.length()).to_string(),
    )
    .trim(chars=" \t")
    .to_owned()
  if body_text.length() > 0 {
    let body : Array[Stmt] = []
    let segments = split_statements(body_text)
    for segment_pair in segments {
      let segment = segment_pair.0
      let offset = segment_pair.1
      if segment.length() == 0 {
        continue
      }
      let base_offset = parser.offsets[parser.index] +
        current_indent +
        colon_pos.unwrap() +
        1 +
        offset
      let parsed = match
        parse_simple_statement(
          segment,
          parser.index,
          parser.offsets[parser.index],
          base_offset,
          parser.async_depth > 0,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      for stmt in parsed {
        body.push(stmt)
      }
    }
    if class_scope_error_line(body, parser.func_depth > 0) is Some(line) {
      return Err(parser.invalid_syntax_at_line(line))
    }
    match stmts_first_illegal_await_line(body) {
      Some(line) => return Err(parser.invalid_syntax_at_line(line))
      None => ()
    }
    stmts.push(
      wrap_stmt_for_line(
        parser.offsets,
        line_index,
        current_indent,
        Stmt::Class(name~, decorators~, type_params~, bases~, keywords~, body~),
      ),
    )
    parser.index = next_index
    return Ok((true, next_pending, next_token))
  }
  parser.index = next_index
  let next_indent = parser.peek_next_indent()
  if next_indent <= current_indent {
    let missing_line = parser.clamp_line_index(
      parser.peek_next_significant_index().unwrap_or(parser.index),
    )
    let token = {
      kind: Eof,
      start: parser.offsets[missing_line],
      end: parser.offsets[missing_line] + 1,
      line: missing_line + 1,
      column: 1,
    }
    return Err(
      make_parse_error(
        ParseErrorKind::Indentation,
        "expected indented block".to_string(),
        token,
      ),
    )
  }
  let body = match parser.parse_block(next_indent) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if class_scope_error_line(body, parser.func_depth > 0) is Some(line) {
    return Err(parser.invalid_syntax_at_line(line))
  }
  match stmts_first_illegal_await_line(body) {
    Some(line) => return Err(parser.invalid_syntax_at_line(line))
    None => ()
  }
  stmts.push(
    wrap_stmt_for_line(
      parser.offsets,
      line_index,
      current_indent,
      Stmt::Class(name~, decorators~, type_params~, bases~, keywords~, body~),
    ),
  )
  Ok((true, next_pending, next_token))
}

///|
fn parse_def_stmt(
  parser : LineParser,
  stmts : Array[Stmt],
  pending_decorators : Array[Expr],
  pending_decorator_token : Token?,
  line_text_in : String,
  line : String,
  line_index : Int32,
  current_indent : Int32,
  next_index : Int32,
) -> Result[(Bool, Array[Expr], Token?), ParseError] {
  let mut line_text = line_text_in
  let mut is_async_def = false
  if line_text.has_prefix("async def ") {
    is_async_def = true
    line_text = "def " + substring(line_text, 10, line_text.length())
  }
  if !line_text.has_prefix("def ") {
    return Ok((false, pending_decorators, pending_decorator_token))
  }
  let decorators = pending_decorators
  let next_pending : Array[Expr] = []
  let next_token : Token? = None
  let header = substring(line_text, 4, line_text.length())
  let paren_span = find_top_level_paren_span(header)
  let close_pos = match paren_span {
    Some(span) => Some(span.1)
    None => None
  }
  let colon_pos = find_top_level_colon(header)
  if paren_span is None || close_pos is None || colon_pos is None {
    let token = {
      kind: Eof,
      start: parser.offsets[parser.index],
      end: parser.offsets[parser.index] + line.length(),
      line: parser.index + 1,
      column: 1,
    }
    return Err(
      make_parse_error(
        ParseErrorKind::Syntax,
        "invalid function definition".to_string(),
        token,
      ),
    )
  }
  let name_part = match paren_span {
    Some(span) => substring(header, 0, span.0).trim(chars=" \t").to_owned()
    None => "".to_string()
  }
  let name_token = {
    kind: Eof,
    start: parser.offsets[parser.index] + current_indent + 4,
    end: parser.offsets[parser.index] + current_indent + 5,
    line: parser.index + 1,
    column: current_indent + 5,
  }
  let (name, type_params) = match
    parse_type_param_header(name_part, name_token) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let name_chars = name.to_array()
  for i = 0; i < name_chars.length(); i = i + 1 {
    let c = name_chars[i]
    if !(is_alnum(c) || c == '_') || (i == 0 && is_digit(c)) {
      let token = {
        kind: Eof,
        start: parser.offsets[parser.index] + current_indent + 4 + i,
        end: parser.offsets[parser.index] + current_indent + 4 + i + 1,
        line: parser.index + 1,
        column: current_indent + 4 + i + 1,
      }
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "invalid syntax".to_string(),
          token,
        ),
      )
    }
  }
  if is_async_await_reserved_name(name) {
    return Err(parser.invalid_syntax_at_line(parser.index + 1))
  }
  let name_end = match paren_span {
    Some(span) => span.0
    None => 0
  }
  let params_text = substring(header, name_end + 1, close_pos.unwrap())
  let params : Array[String] = []
  let defaults : Array[Expr] = []
  let annotations : Array[(String, Expr)] = []
  let mut return_annotation : Expr? = None
  let seen : Array[String] = []
  let mut default_seen = false
  let mut kw_only = false
  let mut seen_posonly_marker = false
  let mut seen_bare_star = false
  let mut bare_star_has_kwonly_name = false
  let params_chars = params_text.to_array()
  let mut param_start = 0
  let mut idx = 0
  let mut quote : Char? = None
  let mut escaped = false
  let mut depth = 0
  let param_base = current_indent + 4 + name_end + 1
  fn is_valid_param_name(text : String) -> Bool {
    if text.length() == 0 {
      return false
    }
    if text == "None" || text == "True" || text == "False" {
      return false
    }
    let chars = text.to_array()
    for i = 0; i < chars.length(); i = i + 1 {
      let c = chars[i]
      if !(is_alnum(c) || c == '_') || (i == 0 && is_digit(c)) {
        return false
      }
    }
    true
  }

  fn consume_param_segment(end_idx : Int32) -> Result[Unit, ParseError] {
    let raw_param = substring(params_text, param_start, end_idx)
    let mut leading = 0
    let raw_chars = raw_param.to_array()
    while leading < raw_chars.length() && raw_chars[leading] == ' ' {
      leading += 1
    }
    let trimmed_param = raw_param.trim(chars=" \t\n\r").to_owned()
    if trimmed_param.length() == 0 {
      return Ok(())
    }
    let mut colon_idx_opt : Int32? = None
    let mut eq_idx_opt : Int32? = None
    let scan_chars = raw_param.to_array()
    let mut scan_depth = 0
    let mut scan_quote : Char? = None
    let mut scan_escape = false
    for j = 0; j < scan_chars.length(); j = j + 1 {
      let c = scan_chars[j]
      match scan_quote {
        Some(q) =>
          if scan_escape {
            scan_escape = false
          } else if c == '\\' {
            scan_escape = true
          } else if c == q {
            scan_quote = None
          }
        None =>
          if c == '\'' || c == '"' {
            scan_quote = Some(c)
            scan_escape = false
          } else if c == '(' || c == '[' || c == '{' {
            scan_depth += 1
          } else if c == ')' || c == ']' || c == '}' {
            if scan_depth > 0 {
              scan_depth -= 1
            }
          } else if c == ':' &&
            scan_depth == 0 &&
            colon_idx_opt is None &&
            eq_idx_opt is None {
            colon_idx_opt = Some(j)
          } else if c == '=' && scan_depth == 0 && eq_idx_opt is None {
            eq_idx_opt = Some(j)
          }
      }
    }
    let name_end_idx = match (colon_idx_opt, eq_idx_opt) {
      (Some(cidx), Some(eidx)) => if cidx < eidx { cidx } else { eidx }
      (Some(cidx), None) => cidx
      (None, Some(eidx)) => eidx
      (None, None) => raw_param.length()
    }
    let mut name = substring(raw_param, 0, name_end_idx)
      .trim(chars=" \t\n\r")
      .to_owned()
    let has_default = eq_idx_opt is Some(_)
    if name == "/" {
      if has_default || kw_only || seen_posonly_marker {
        let token = {
          kind: Eof,
          start: parser.offsets[parser.index] +
          param_base +
          param_start +
          leading,
          end: parser.offsets[parser.index] +
          param_base +
          param_start +
          leading +
          1,
          line: parser.index + 1,
          column: param_base + param_start + leading + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "invalid syntax".to_string(),
            token,
          ),
        )
      }
      seen_posonly_marker = true
      params.push("/")
      return Ok(())
    }
    if name == "*" {
      if seen_bare_star || kw_only {
        let token = {
          kind: Eof,
          start: parser.offsets[parser.index] +
          param_base +
          param_start +
          leading,
          end: parser.offsets[parser.index] +
          param_base +
          param_start +
          leading +
          1,
          line: parser.index + 1,
          column: param_base + param_start + leading + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "invalid syntax".to_string(),
            token,
          ),
        )
      }
      seen_bare_star = true
      kw_only = true
      params.push("*")
      name = ""
    }
    let mut star_count = 0
    while name.has_prefix("*") {
      star_count += 1
      name = substring(name, 1, name.length()).to_string()
      name = name.trim(chars=" \t\n\r").to_owned()
    }
    if star_count > 0 {
      kw_only = true
    }
    if name.length() == 0 {
      return Ok(())
    }
    let param_offset = param_start + leading
    if seen_bare_star && star_count == 2 && !bare_star_has_kwonly_name {
      let token = {
        kind: Eof,
        start: parser.offsets[parser.index] + param_base + param_offset,
        end: parser.offsets[parser.index] + param_base + param_offset + 1,
        line: parser.index + 1,
        column: param_base + param_offset + 1,
      }
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "invalid syntax".to_string(),
          token,
        ),
      )
    }
    if is_async_await_reserved_name(name) {
      return Err(parser.invalid_syntax_at_line(parser.index + 1))
    }
    if !is_valid_param_name(name) {
      let token = {
        kind: Eof,
        start: parser.offsets[parser.index] + param_base + param_offset,
        end: parser.offsets[parser.index] + param_base + param_offset + 1,
        line: parser.index + 1,
        column: param_base + param_offset + 1,
      }
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "invalid syntax".to_string(),
          token,
        ),
      )
    }
    for existing in seen {
      if existing == name {
        let token = {
          kind: Eof,
          start: parser.offsets[parser.index] + param_base + param_offset,
          end: parser.offsets[parser.index] + param_base + param_offset + 1,
          line: parser.index + 1,
          column: param_base + param_offset + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "duplicate argument '" + name + "' in function definition",
            token,
          ),
        )
      }
    }
    if star_count > 2 {
      let token = {
        kind: Eof,
        start: parser.offsets[parser.index] + param_base + param_offset,
        end: parser.offsets[parser.index] + param_base + param_offset + 1,
        line: parser.index + 1,
        column: param_base + param_offset + 1,
      }
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "invalid syntax".to_string(),
          token,
        ),
      )
    }
    if star_count > 0 && has_default {
      let token = {
        kind: Eof,
        start: parser.offsets[parser.index] + param_base + param_offset,
        end: parser.offsets[parser.index] + param_base + param_offset + 1,
        line: parser.index + 1,
        column: param_base + param_offset + 1,
      }
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "invalid syntax".to_string(),
          token,
        ),
      )
    }
    if !kw_only && default_seen && !has_default {
      let token = {
        kind: Eof,
        start: parser.offsets[parser.index] + param_base + param_offset,
        end: parser.offsets[parser.index] + param_base + param_offset + 1,
        line: parser.index + 1,
        column: param_base + param_offset + 1,
      }
      return Err(
        make_parse_error(
          ParseErrorKind::Syntax,
          "non-default argument follows default argument".to_string(),
          token,
        ),
      )
    }
    if has_default {
      if !kw_only {
        default_seen = true
      }
      match eq_idx_opt {
        Some(eq_idx) => {
          let default_text = substring(
              raw_param,
              eq_idx + 1,
              raw_param.length(),
            )
            .trim(chars=" \t\n\r")
            .to_owned()
          let tokens = match
            tokenize_expr(
              default_text,
              parser.index + 1,
              parser.offsets[parser.index] +
              param_base +
              param_start +
              eq_idx +
              1,
              param_base + param_start + eq_idx + 2,
            ) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          let expr = match parse_tokens_expr(tokens) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
          if expr_has_illegal_await(expr, false) {
            return Err(parser.invalid_syntax_at_line(parser.index + 1))
          }
          defaults.push(expr)
        }
        None => ()
      }
    }
    match colon_idx_opt {
      Some(colon_idx) => {
        let ann_end = match eq_idx_opt {
          Some(eq_idx) =>
            if eq_idx > colon_idx {
              eq_idx
            } else {
              raw_param.length()
            }
          None => raw_param.length()
        }
        let ann_raw = substring(raw_param, colon_idx + 1, ann_end).to_string()
        let ann_text = ann_raw.trim(chars=" \t\n\r").to_owned()
        if ann_text.length() == 0 {
          let token = {
            kind: Eof,
            start: parser.offsets[parser.index] +
            param_base +
            param_start +
            colon_idx,
            end: parser.offsets[parser.index] +
            param_base +
            param_start +
            colon_idx +
            1,
            line: parser.index + 1,
            column: param_base + param_start + colon_idx + 1,
          }
          return Err(
            make_parse_error(
              ParseErrorKind::Syntax,
              "invalid syntax".to_string(),
              token,
            ),
          )
        }
        let ann_leading = count_indent(ann_raw)
        let ann_offset = parser.offsets[parser.index] +
          param_base +
          param_start +
          colon_idx +
          1 +
          ann_leading
        let ann_column = param_base +
          param_start +
          colon_idx +
          1 +
          ann_leading +
          1
        let tokens = match
          tokenize_expr(ann_text, parser.index + 1, ann_offset, ann_column) {
          Ok(value) => value
          Err(err) => return Err(err)
        }
        // PEP 646 permits an unpacked annotation on a variadic positional
        // parameter: def f(*args: *Ts). It is not a general expression form.
        let expr = if star_count == 1 &&
          tokens.length() > 0 &&
          tokens[0].kind == Star {
          match parse_tokens_expr(tokens[1:].to_owned()) {
            Ok(value) => Expr::Starred(value~)
            Err(err) => return Err(err)
          }
        } else {
          match parse_tokens_expr(tokens) {
            Ok(value) => value
            Err(err) => return Err(err)
          }
        }
        if expr_has_illegal_await(expr, false) {
          return Err(parser.invalid_syntax_at_line(parser.index + 1))
        }
        annotations.push((name, expr))
      }
      None => ()
    }
    seen.push(name)
    let mut encoded = name
    if star_count == 1 {
      encoded = "*" + name
    } else if star_count == 2 {
      encoded = "**" + name
    } else if has_default {
      encoded = name + "="
    }
    if seen_bare_star && star_count == 0 {
      bare_star_has_kwonly_name = true
    }
    params.push(encoded)
    Ok(())
  }

  while idx < params_chars.length() {
    let c = params_chars[idx]
    match quote {
      Some(q) =>
        if escaped {
          escaped = false
        } else if c == '\\' {
          escaped = true
        } else if c == q {
          quote = None
        }
      None =>
        if c == '\'' || c == '"' {
          quote = Some(c)
        } else if c == '(' || c == '[' || c == '{' {
          depth += 1
        } else if c == ')' || c == ']' || c == '}' {
          if depth > 0 {
            depth -= 1
          }
        } else if c == ',' && depth == 0 {
          let _ = match consume_param_segment(idx) {
            Ok(_) => ()
            Err(err) => return Err(err)
          }
          param_start = idx + 1
        }
    }
    idx += 1
  }
  let _ = match consume_param_segment(params_chars.length()) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if seen_bare_star && !bare_star_has_kwonly_name {
    return Err(parser.invalid_syntax_at_line(parser.index + 1))
  }
  let return_slice = substring(
    header,
    close_pos.unwrap() + 1,
    colon_pos.unwrap(),
  ).to_string()
  match return_slice.find("->") {
    Some(pos) => {
      let raw_ret = substring(return_slice, pos + 2, return_slice.length()).to_string()
      let ret_text = raw_ret.trim(chars=" \t\n\r").to_owned()
      if ret_text.length() == 0 {
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "invalid syntax".to_string(),
            name_token,
          ),
        )
      }
      let ret_leading = count_indent(raw_ret)
      let ret_offset = parser.offsets[parser.index] +
        current_indent +
        4 +
        close_pos.unwrap() +
        1 +
        pos +
        2 +
        ret_leading
      let ret_column = current_indent +
        4 +
        close_pos.unwrap() +
        1 +
        pos +
        2 +
        ret_leading +
        1
      let tokens = match
        tokenize_expr(ret_text, parser.index + 1, ret_offset, ret_column) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let expr = match parse_tokens_expr(tokens) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      return_annotation = Some(expr)
    }
    None => ()
  }
  let body_text = strip_inline_comment(
      substring(header, colon_pos.unwrap() + 1, header.length()).to_string(),
    )
    .trim(chars=" \t")
    .to_owned()
  if !is_async_def && line_has_await_keyword(body_text) {
    let token = {
      kind: Eof,
      start: parser.offsets[parser.index] + current_indent,
      end: parser.offsets[parser.index] + current_indent + 1,
      line: parser.index + 1,
      column: 1,
    }
    return Err(
      make_parse_error(
        ParseErrorKind::Syntax,
        "invalid syntax".to_string(),
        token,
      ),
    )
  }
  if body_text.length() > 0 {
    let body : Array[Stmt] = []
    let segments = split_statements(body_text)
    for segment_pair in segments {
      let segment = segment_pair.0
      let offset = segment_pair.1
      if segment.length() == 0 {
        continue
      }
      let base_offset = parser.offsets[parser.index] +
        current_indent +
        4 +
        colon_pos.unwrap() +
        1 +
        offset
      let parsed = match
        parse_simple_statement(
          segment,
          parser.index,
          parser.offsets[parser.index],
          base_offset,
          parser.async_depth > 0,
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      for stmt in parsed {
        body.push(stmt)
      }
    }
    match scope_decl_after_use_line(body) {
      Some(line) => return Err(parser.invalid_syntax_at_line(line))
      None => ()
    }
    match stmts_first_param_scope_decl_line(body, params) {
      Some(line) => return Err(parser.invalid_syntax_at_line(line))
      None => ()
    }
    for stmt in body {
      match stmt {
        Stmt::WithSpan(span=_, stmt=inner) =>
          match inner {
            Stmt::Global(names) =>
              for name in names {
                for param in params {
                  match normalize_param_name_for_compare(param) {
                    Some(param_name) =>
                      if param_name == name {
                        let token = {
                          kind: Eof,
                          start: parser.offsets[parser.index] +
                          current_indent +
                          4 +
                          colon_pos.unwrap() +
                          2,
                          end: parser.offsets[parser.index] +
                          current_indent +
                          4 +
                          colon_pos.unwrap() +
                          3,
                          line: parser.index + 1,
                          column: current_indent + 4 + colon_pos.unwrap() + 3,
                        }
                        return Err(
                          make_parse_error(
                            ParseErrorKind::Syntax,
                            "name '" + name + "' is parameter and global",
                            token,
                          ),
                        )
                      }
                    None => ()
                  }
                }
              }
            _ => ()
          }
        Stmt::Global(names) =>
          for name in names {
            for param in params {
              match normalize_param_name_for_compare(param) {
                Some(param_name) =>
                  if param_name == name {
                    let token = {
                      kind: Eof,
                      start: parser.offsets[parser.index] +
                      current_indent +
                      4 +
                      colon_pos.unwrap() +
                      2,
                      end: parser.offsets[parser.index] +
                      current_indent +
                      4 +
                      colon_pos.unwrap() +
                      3,
                      line: parser.index + 1,
                      column: current_indent + 4 + colon_pos.unwrap() + 3,
                    }
                    return Err(
                      make_parse_error(
                        ParseErrorKind::Syntax,
                        "name '" + name + "' is parameter and global",
                        token,
                      ),
                    )
                  }
                None => ()
              }
            }
          }
        _ => ()
      }
    }
    if !is_async_def {
      match stmts_first_illegal_await_line(body) {
        Some(line) => return Err(parser.invalid_syntax_at_line(line))
        None => ()
      }
    }
    let mut is_generator = false
    for stmt in body {
      match stmt {
        Stmt::WithSpan(span=_, stmt=inner) =>
          match inner {
            Stmt::Yield(_) | Stmt::YieldFrom(_) => is_generator = true
            _ => ()
          }
        Stmt::Yield(_) | Stmt::YieldFrom(_) => is_generator = true
        _ => ()
      }
    }
    stmts.push(
      wrap_stmt_for_line(
        parser.offsets,
        line_index,
        current_indent,
        Stmt::Function(
          name~,
          decorators~,
          type_params~,
          params~,
          defaults~,
          annotations~,
          return_annotation~,
          body~,
          is_generator~,
          is_async=is_async_def,
        ),
      ),
    )
    parser.index = next_index
    return Ok((true, next_pending, next_token))
  }
  parser.index = next_index
  let body_start_index = parser.index
  let next_indent = parser.peek_next_indent()
  if next_indent <= current_indent {
    let missing_line = parser.clamp_line_index(
      parser.peek_next_significant_index().unwrap_or(parser.index),
    )
    let token = {
      kind: Eof,
      start: parser.offsets[missing_line],
      end: parser.offsets[missing_line] + 1,
      line: missing_line + 1,
      column: 1,
    }
    return Err(
      make_parse_error(
        ParseErrorKind::Indentation,
        "expected indented block".to_string(),
        token,
      ),
    )
  }
  let (body, _) = if is_async_def {
    let mut idx = parser.index
    while idx < parser.lines.length() {
      let body_line = parser.lines[idx]
      let trimmed = body_line.trim(chars=" \t").to_owned()
      if trimmed.length() == 0 || trimmed.has_prefix("#") {
        idx += 1
        continue
      }
      let body_indent = count_indent(body_line)
      if body_indent < next_indent {
        break
      }
      if trimmed.has_prefix("def ") ||
        trimmed.has_prefix("async def ") ||
        trimmed.has_prefix("class ") {
        let def_indent = body_indent
        idx += 1
        while idx < parser.lines.length() {
          let next_line = parser.lines[idx]
          let next_trimmed = next_line.trim(chars=" \t").to_owned()
          if next_trimmed.length() == 0 || next_trimmed.has_prefix("#") {
            idx += 1
            continue
          }
          if count_indent(next_line) <= def_indent {
            break
          }
          idx += 1
        }
        continue
      }
      if trimmed.has_prefix("yield from") {
        let token = {
          kind: Eof,
          start: parser.offsets[idx] + body_indent,
          end: parser.offsets[idx] + body_indent + 1,
          line: idx + 1,
          column: body_indent + 1,
        }
        return Err(
          make_parse_error(
            ParseErrorKind::Syntax,
            "'yield from' inside async function".to_string(),
            token,
          ),
        )
      }
      idx += 1
    }
    let prev_async_depth = parser.async_depth
    parser.async_depth = 1
    parser.func_depth += 1
    let parsed = match parser.parse_block(next_indent) {
      Ok(value) => value
      Err(err) => {
        parser.func_depth -= 1
        parser.async_depth = prev_async_depth
        return Err(err)
      }
    }
    parser.func_depth -= 1
    parser.async_depth = prev_async_depth
    (parsed, false)
  } else {
    let prev_async_depth = parser.async_depth
    parser.async_depth = 0
    parser.func_depth += 1
    let parsed = match parser.parse_block(next_indent) {
      Ok(value) => value
      Err(err) => {
        parser.func_depth -= 1
        parser.async_depth = prev_async_depth
        return Err(err)
      }
    }
    parser.func_depth -= 1
    parser.async_depth = prev_async_depth
    (parsed, false)
  }
  let has_yield_ref : Ref[Bool] = { val: false, }
  let has_yield_from_ref : Ref[Bool] = { val: false, }
  let has_return_value_ref : Ref[Bool] = { val: false, }
  if !is_async_def {
    match stmts_first_illegal_await_line(body) {
      Some(line) => return Err(parser.invalid_syntax_at_line(line))
      None => ()
    }
  }
  fn scan_expr_for_yield(
    expr : Expr,
    has_yield_ref : Ref[Bool],
    has_yield_from_ref : Ref[Bool],
  ) -> Unit {
    match expr {
      Expr::WithSpan(span=_, expr=inner) =>
        scan_expr_for_yield(inner, has_yield_ref, has_yield_from_ref)
      Expr::Yield(value) => {
        has_yield_ref.val = true
        match value {
          Some(inner) =>
            scan_expr_for_yield(inner, has_yield_ref, has_yield_from_ref)
          None => ()
        }
      }
      Expr::YieldFrom(value) => {
        has_yield_ref.val = true
        has_yield_from_ref.val = true
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      }
      Expr::Tuple(items) | Expr::List(items) | Expr::Set(items) =>
        for item in items {
          scan_expr_for_yield(item, has_yield_ref, has_yield_from_ref)
        }
      Expr::Dict(pairs) =>
        for pair in pairs {
          scan_expr_for_yield(pair.0, has_yield_ref, has_yield_from_ref)
          scan_expr_for_yield(pair.1, has_yield_ref, has_yield_from_ref)
        }
      Expr::ListComp(elt~, clauses~, filters~)
      | Expr::GenExp(elt~, clauses~, filters~)
      | Expr::SetComp(elt~, clauses~, filters~) => {
        scan_expr_for_yield(elt, has_yield_ref, has_yield_from_ref)
        for clause in clauses {
          scan_expr_for_yield(clause.iter, has_yield_ref, has_yield_from_ref)
        }
        for filter_expr in filters {
          scan_expr_for_yield(filter_expr, has_yield_ref, has_yield_from_ref)
        }
      }
      Expr::DictComp(key~, value~, clauses~, filters~) => {
        scan_expr_for_yield(key, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
        for clause in clauses {
          scan_expr_for_yield(clause.iter, has_yield_ref, has_yield_from_ref)
        }
        for filter_expr in filters {
          scan_expr_for_yield(filter_expr, has_yield_ref, has_yield_from_ref)
        }
      }
      Expr::Lambda(..) => ()
      Expr::IfExpr(condition~, then_expr~, else_expr~) => {
        scan_expr_for_yield(condition, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(then_expr, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(else_expr, has_yield_ref, has_yield_from_ref)
      }
      Expr::NamedExpr(name=_, value~) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Expr::Await(value) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Expr::Slice(start~, end~, step~) => {
        match start {
          Some(value) =>
            scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
          None => ()
        }
        match end {
          Some(value) =>
            scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
          None => ()
        }
        match step {
          Some(value) =>
            scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
          None => ()
        }
      }
      Expr::Attribute(value~, attr=_) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Expr::Subscript(value~, index~) => {
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(index, has_yield_ref, has_yield_from_ref)
      }
      Expr::Call(callee~, args~) => {
        scan_expr_for_yield(callee, has_yield_ref, has_yield_from_ref)
        for arg in args {
          scan_expr_for_yield(arg, has_yield_ref, has_yield_from_ref)
        }
      }
      Expr::Keyword(name=_, value~) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Expr::Starred(value~) | Expr::DoubleStarred(value~) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Expr::Unary(op=_, expr~) =>
        scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
      Expr::Binary(op=_, left~, right~) => {
        scan_expr_for_yield(left, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(right, has_yield_ref, has_yield_from_ref)
      }
      Expr::BoolOp(op=_, values~) =>
        for value in values {
          scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
        }
      Expr::Compare(left~, ops=_, comparators~) => {
        scan_expr_for_yield(left, has_yield_ref, has_yield_from_ref)
        for value in comparators {
          scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
        }
      }
      _ => ()
    }
  }

  fn scan_target_for_yield(
    target : Target,
    has_yield_ref : Ref[Bool],
    has_yield_from_ref : Ref[Bool],
  ) -> Unit {
    match target {
      Target::Attribute(value~, attr=_) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Target::Subscript(value~, index~) => {
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(index, has_yield_ref, has_yield_from_ref)
      }
      Target::Tuple(items) | Target::List(items) =>
        for item in items {
          scan_target_for_yield(item, has_yield_ref, has_yield_from_ref)
        }
      Target::Starred(inner) =>
        scan_target_for_yield(inner, has_yield_ref, has_yield_from_ref)
      _ => ()
    }
  }

  fn scan_stmt_for_yield(
    stmt : Stmt,
    has_yield_ref : Ref[Bool],
    has_yield_from_ref : Ref[Bool],
    has_return_value_ref : Ref[Bool],
  ) -> Unit {
    match stmt {
      Stmt::WithSpan(span=_, stmt=inner) =>
        scan_stmt_for_yield(
          inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
        )
      Stmt::ExprStmt(expr) =>
        scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
      Stmt::Assign(target~, value~) => {
        scan_target_for_yield(target, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      }
      Stmt::AugAssign(target~, op=_, value~) => {
        scan_target_for_yield(target, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      }
      Stmt::TypeAlias(name=_, type_params=_, value~) =>
        scan_expr_for_yield(value, has_yield_ref, has_yield_from_ref)
      Stmt::Return(value) =>
        match value {
          Some(expr) => {
            has_return_value_ref.val = true
            scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
          }
          None => ()
        }
      Stmt::Yield(value) => {
        has_yield_ref.val = true
        match value {
          Some(expr) =>
            scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
          None => ()
        }
      }
      Stmt::YieldFrom(expr) => {
        has_yield_ref.val = true
        has_yield_from_ref.val = true
        scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
      }
      Stmt::If(condition~, body~, else_body~) => {
        scan_expr_for_yield(condition, has_yield_ref, has_yield_from_ref)
        for inner in body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
        for inner in else_body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
      }
      Stmt::Match(subject~, cases~) => {
        scan_expr_for_yield(subject, has_yield_ref, has_yield_from_ref)
        for case_item in cases {
          let case_guard = case_item.1
          let case_body = case_item.2
          match case_guard {
            Some(expr) =>
              scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
            None => ()
          }
          for inner in case_body {
            scan_stmt_for_yield(
              inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
            )
          }
        }
      }
      Stmt::While(condition~, body~, else_body~) => {
        scan_expr_for_yield(condition, has_yield_ref, has_yield_from_ref)
        for inner in body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
        for inner in else_body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
      }
      Stmt::For(target~, iter~, body~, else_body~)
      | Stmt::AsyncFor(target~, iter~, body~, else_body~) => {
        scan_target_for_yield(target, has_yield_ref, has_yield_from_ref)
        scan_expr_for_yield(iter, has_yield_ref, has_yield_from_ref)
        for inner in body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
        for inner in else_body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
      }
      Stmt::Assert(condition~, message~) => {
        scan_expr_for_yield(condition, has_yield_ref, has_yield_from_ref)
        match message {
          Some(expr) =>
            scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
          None => ()
        }
      }
      Stmt::Raise(exc~, cause~) => {
        match exc {
          Some(expr) =>
            scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
          None => ()
        }
        match cause {
          Some(expr) =>
            scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
          None => ()
        }
      }
      Stmt::Del(targets) =>
        for target in targets {
          scan_target_for_yield(target, has_yield_ref, has_yield_from_ref)
        }
      Stmt::With(context~, target=_, body~)
      | Stmt::AsyncWith(context~, target=_, body~) => {
        scan_expr_for_yield(context, has_yield_ref, has_yield_from_ref)
        for inner in body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
      }
      Stmt::Try(body~, handlers~, else_body~, finally_body~) => {
        for inner in body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
        for handler in handlers {
          match handler.exc {
            Some(expr) =>
              scan_expr_for_yield(expr, has_yield_ref, has_yield_from_ref)
            None => ()
          }
          for inner in handler.body {
            scan_stmt_for_yield(
              inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
            )
          }
        }
        for inner in else_body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
        for inner in finally_body {
          scan_stmt_for_yield(
            inner, has_yield_ref, has_yield_from_ref, has_return_value_ref,
          )
        }
      }
      Stmt::Function(..) | Stmt::Class(..) => ()
      _ => ()
    }
  }

  for stmt in body {
    scan_stmt_for_yield(
      stmt, has_yield_ref, has_yield_from_ref, has_return_value_ref,
    )
  }
  let is_generator = has_yield_ref.val || has_yield_from_ref.val
  if is_async_def {
    if has_yield_from_ref.val {
      let mut idx = body_start_index
      while idx < parser.lines.length() {
        let body_line = parser.lines[idx]
        let trimmed = body_line.trim(chars=" \t").to_owned()
        if trimmed.length() == 0 || trimmed.has_prefix("#") {
          idx += 1
          continue
        }
        let body_indent = count_indent(body_line)
        if body_indent < next_indent {
          break
        }
        if trimmed.has_prefix("def ") ||
          trimmed.has_prefix("async def ") ||
          trimmed.has_prefix("class ") {
          let def_indent = body_indent
          idx += 1
          while idx < parser.lines.length() {
            let next_line = parser.lines[idx]
            let next_trimmed = next_line.trim(chars=" \t").to_owned()
            if next_trimmed.length() == 0 || next_trimmed.has_prefix("#") {
              idx += 1
              continue
            }
            if count_indent(next_line) <= def_indent {
              break
            }
            idx += 1
          }
          continue
        }
        if trimmed.has_prefix("yield from") {
          let token = {
            kind: Eof,
            start: parser.offsets[idx] + body_indent,
            end: parser.offsets[idx] + body_indent + 1,
            line: idx + 1,
            column: body_indent + 1,
          }
          return Err(
            make_parse_error(
              ParseErrorKind::Syntax,
              "'yield from' inside async function".to_string(),
              token,
            ),
          )
        }
        idx += 1
      }
    }
    if is_generator && has_return_value_ref.val {
      let mut idx = body_start_index
      while idx < parser.lines.length() {
        let body_line = parser.lines[idx]
        let trimmed = body_line.trim(chars=" \t").to_owned()
        if trimmed.length() == 0 || trimmed.has_prefix("#") {
          idx += 1
          continue
        }
        let body_indent = count_indent(body_line)
        if body_indent < next_indent {
          break
        }
        if trimmed.has_prefix("def ") ||
          trimmed.has_prefix("async def ") ||
          trimmed.has_prefix("class ") {
          let def_indent = body_indent
          idx += 1
          while idx < parser.lines.length() {
            let next_line = parser.lines[idx]
            let next_trimmed = next_line.trim(chars=" \t").to_owned()
            if next_trimmed.length() == 0 || next_trimmed.has_prefix("#") {
              idx += 1
              continue
            }
            if count_indent(next_line) <= def_indent {
              break
            }
            idx += 1
          }
          continue
        }
        if trimmed.has_prefix("return ") {
          let token = {
            kind: Eof,
            start: parser.offsets[idx] + body_indent,
            end: parser.offsets[idx] + body_indent + 1,
            line: idx + 1,
            column: body_indent + 1,
          }
          return Err(
            make_parse_error(
              ParseErrorKind::Syntax,
              "'return' with value in async generator".to_string(),
              token,
            ),
          )
        }
        idx += 1
      }
    }
  }
  match scope_decl_after_use_line(body) {
    Some(line) => return Err(parser.invalid_syntax_at_line(line))
    None => ()
  }
  match stmts_first_param_scope_decl_line(body, params) {
    Some(line) => return Err(parser.invalid_syntax_at_line(line))
    None => ()
  }
  match stmts_first_function_import_star_line(body) {
    Some(line) => return Err(parser.invalid_syntax_at_line(line))
    None => ()
  }
  for stmt in body {
    match stmt {
      Stmt::Global(names) =>
        for name in names {
          for param in params {
            match normalize_param_name_for_compare(param) {
              Some(param_name) =>
                if param_name == name {
                  let token = {
                    kind: Eof,
                    start: parser.offsets[parser.index - 1] + current_indent + 1,
                    end: parser.offsets[parser.index - 1] + current_indent + 2,
                    line: parser.index,
                    column: current_indent + 2,
                  }
                  return Err(
                    make_parse_error(
                      ParseErrorKind::Syntax,
                      "name '" + name + "' is parameter and global",
                      token,
                    ),
                  )
                }
              None => ()
            }
          }
        }
      _ => ()
    }
  }
  stmts.push(
    wrap_stmt_for_line(
      parser.offsets,
      line_index,
      current_indent,
      Stmt::Function(
        name~,
        decorators~,
        type_params~,
        params~,
        defaults~,
        annotations~,
        return_annotation~,
        body~,
        is_generator~,
        is_async=is_async_def,
      ),
    ),
  )
  Ok((true, next_pending, next_token))
}