///|
priv struct Parser {
  source : String
  tokens : Array[Token]
  len : Int
  mut pos : Int
  // PKL-107: running byte offset into `source`. Maintained incrementally
  // by `bump` / direct `pos` writes so `current_offset` is O(1) instead
  // of summing all preceding token texts on every call (which previously
  // made parser diagnostics-with-positions an O(N) extra cost per emit).
  mut byte_offset : Int
  builder : @cst.GreenNodeBuilder
  diagnostics : Array[Diagnostic]
  unsupported_syntax : Array[UnsupportedSyntax]
  // PKL-128d: annotations consumed by `skip_member_header` accumulate
  // here until the following declaration parser drains them. Bindings
  // / properties that don't capture annotations simply leave the list
  // in place; the next `skip_member_header` resets it before parsing
  // the next member's annotation prefix.
  pending_annotations : Array[Annotation]
  // PKL-117: modifier keywords (`abstract`, `open`, `external`, etc.)
  // captured by `skip_member_header` for the next declaration. Drained
  // by the decl parsers that care (`parse_class_decl` /
  // `parse_function_decl` consult the `abstract` flag); cleared
  // unconditionally by the next `skip_member_header` so a modifier on
  // a binding doesn't leak into the following class declaration.
  pending_modifiers : Array[String]
}

///|
priv struct ParsedModuleDecl {
  module_name : String?
  module_relation : ModuleRelation?
  // PKL-128d: annotations preceding the `module` / `amends` /
  // `extends` keyword. Drained from `pending_annotations` at the
  // start of `parse_module_decl`.
  annotations : Array[Annotation]
}

///|
fn Parser::new(source : String) -> Parser {
  let tokens = tokenize(source)
  Parser::{
    source,
    tokens,
    len: tokens.length(),
    pos: 0,
    byte_offset: 0,
    builder: @cst.GreenNodeBuilder::new(),
    diagnostics: validate_parser_syntax(source, tokens),
    unsupported_syntax: [],
    pending_annotations: [],
    pending_modifiers: [],
  }
}

///|
fn Parser::peek(self : Parser) -> Token {
  if self.pos >= self.len {
    Token::new(eof(), "")
  } else {
    self.tokens[self.pos]
  }
}

///|
/// Cheap peek that avoids materialising a Token struct copy for the
/// common case (`self.pos` inside bounds). Called from hot dispatch
/// loops in the parser where the Token's text is not needed.
fn Parser::peek_kind(self : Parser) -> @cst.SyntaxKind {
  if self.pos >= self.len {
    eof()
  } else {
    self.tokens[self.pos].kind()
  }
}

///|
fn Parser::at(self : Parser, kind : @cst.SyntaxKind) -> Bool {
  self.peek_kind() == kind
}

///|
/// Compare the peeked token's text against a literal. Short-circuits on
/// text length so the common case of mismatched-length keywords (e.g.
/// `at_text("class")` when the peek is `function`) is rejected without
/// touching the underlying character data.
fn Parser::at_text(self : Parser, text : String) -> Bool {
  if self.pos >= self.len {
    return false
  }
  let tok_text = self.tokens[self.pos].text()
  if tok_text.length() != text.length() {
    return false
  }
  tok_text == text
}

///|
fn is_modifier_text(text : String) -> Bool {
  match text {
    "abstract" | "external" | "open" | "hidden" | "fixed" | "const" => true
    _ => false
  }
}

///|
fn is_decl_text(text : String) -> Bool {
  match text {
    "class" | "function" | "typealias" | "amends" | "extends" => true
    _ => false
  }
}

///|
fn Parser::peek_non_whitespace_kind(
  self : Parser,
  start : Int,
) -> @cst.SyntaxKind {
  let mut i = start
  while i < self.len &&
        (
          self.tokens[i].kind() == whitespace() ||
          self.tokens[i].kind() == comment()
        ) {
    i += 1
  }
  if i >= self.len {
    eof()
  } else {
    self.tokens[i].kind()
  }
}

///|
fn Parser::peek_non_trivia_kind(self : Parser, start : Int) -> @cst.SyntaxKind {
  let mut i = start
  while i < self.len && is_trivia(self.tokens[i].kind()) {
    i += 1
  }
  if i >= self.len {
    eof()
  } else {
    self.tokens[i].kind()
  }
}

///|
/// PKL-148s: `...x` / `...?x` spread member detection. Apple Pkl's
/// `...` is three consecutive `.` tokens (no trivia between them); a
/// trailing `?` lifts it to the nullable form. The dispatch in each
/// of `parse_object_body_members`, `parse_listing_body`, and
/// `parse_mapping_body` consults these helpers before falling through
/// to `skip_unknown_member` so the spread isn't silently dropped.
fn Parser::at_triple_dot(self : Parser) -> Bool {
  self.pos + 2 < self.len &&
  self.tokens[self.pos].kind() == dot() &&
  self.tokens[self.pos + 1].kind() == dot() &&
  self.tokens[self.pos + 2].kind() == dot()
}

///|
/// Consume the leading `...` (and optional `?`) of a spread member.
/// Returns the parsed payload expression. The caller decides which
/// container shape to push the spread into.
fn Parser::parse_spread_payload(self : Parser) -> Expr {
  ignore(self.bump())
  ignore(self.bump())
  ignore(self.bump())
  if self.at(question()) {
    ignore(self.bump())
  }
  self.skip_whitespace()
  self.parse_expr()
}

///|
fn Parser::at_property_decl(self : Parser) -> Bool {
  if !self.at(identifier()) {
    return false
  }
  let next = self.peek_non_whitespace_kind(self.pos + 1)
  next == eq() || next == colon() || next == lbrace()
}

///|
fn Parser::at_property_decl_from(self : Parser, start : Int) -> Bool {
  if start >= self.len || self.tokens[start].kind() != identifier() {
    return false
  }
  let next = self.peek_non_whitespace_kind(start + 1)
  next == eq() || next == colon() || next == lbrace()
}

///|
fn Parser::at_module_member_boundary_from(self : Parser, start : Int) -> Bool {
  let mut i = start
  while i < self.len && is_trivia(self.tokens[i].kind()) {
    i += 1
  }
  if i >= self.len {
    return true
  }
  let kind = self.tokens[i].kind()
  let text = self.tokens[i].text()
  kind == eof() ||
  kind == rbrace() ||
  kind == import_kw() ||
  kind == module_kw() ||
  kind == let_kw() ||
  kind == local_kw() ||
  is_decl_text(text) ||
  is_modifier_text(text) ||
  self.at_property_decl_from(i)
}

///|
fn Parser::paren_expr_looks_unsupported(self : Parser) -> Bool {
  if !self.at(lparen()) {
    return false
  }
  let mut depth = 0
  let mut brackets = 0
  let mut angles = 0
  let mut i = self.pos
  while i < self.len {
    let kind = self.tokens[i].kind()
    if kind == lparen() {
      depth += 1
    } else if kind == rparen() {
      depth -= 1
      if depth == 0 {
        return false
      }
    } else if kind == lbracket() {
      brackets += 1
    } else if kind == rbracket() {
      if brackets > 0 {
        brackets -= 1
      }
    } else if brackets == 0 && kind == lt() {
      angles += 1
    } else if brackets == 0 && kind == gt() {
      if angles > 0 {
        angles -= 1
      }
    } else if depth == 1 &&
      brackets == 0 &&
      angles == 0 &&
      (kind == comma() || kind == colon()) {
      return true
    }
    i += 1
  }
  false
}

///|
fn Parser::function_literal_at_current(self : Parser) -> Bool {
  if !self.at(lparen()) {
    return false
  }
  let first = self.peek_non_trivia_kind(self.pos + 1)
  if first != rparen() && first != identifier() {
    return false
  }
  let mut depth = 0
  let mut i = self.pos
  while i < self.len {
    let kind = self.tokens[i].kind()
    if kind == lparen() {
      depth += 1
    } else if kind == rparen() {
      depth -= 1
      if depth == 0 {
        return self.lambda_arrow_after_parameter_list(i + 1)
      }
    }
    i += 1
  }
  false
}

///|
fn Parser::lambda_arrow_after_parameter_list(
  self : Parser,
  start : Int,
) -> Bool {
  let mut i = start
  while i < self.len && is_trivia(self.tokens[i].kind()) {
    i += 1
  }
  if i >= self.len {
    return false
  }
  if self.tokens[i].kind() == arrow() {
    return true
  }
  if self.tokens[i].kind() != colon() {
    return false
  }
  i += 1
  let mut parens = 0
  let mut brackets = 0
  let mut angles = 0
  while i < self.len {
    let kind = self.tokens[i].kind()
    if parens == 0 && brackets == 0 && angles == 0 {
      if kind == arrow() {
        return true
      }
      if kind == eq() ||
        kind == lbrace() ||
        kind == rbrace() ||
        is_separator(kind) {
        return false
      }
    }
    if kind == lparen() {
      parens += 1
    } else if kind == rparen() {
      if parens == 0 {
        return false
      }
      parens -= 1
    } else if kind == lbracket() {
      brackets += 1
    } else if kind == rbracket() {
      if brackets == 0 {
        return false
      }
      brackets -= 1
    } else if parens == 0 && brackets == 0 && kind == lt() {
      angles += 1
    } else if parens == 0 && brackets == 0 && kind == gt() {
      if angles == 0 {
        return false
      }
      angles -= 1
    }
    i += 1
  }
  false
}

///|
fn Parser::add_error(self : Parser, message : String) -> Unit {
  // PKL-107: anchor parse errors at the parser's current byte offset
  // so the CLI can project them onto a line:column pair. `end` falls
  // back to `start` because the failing token text isn't always
  // bumped by the time `add_error` fires; that's good enough for
  // pointing at the right line, and refinement can come later.
  let start = self.byte_offset
  self.diagnostics.push({ message, start, end: start })
}

///|
fn Parser::current_offset(self : Parser) -> Int {
  self.byte_offset
}

///|
fn Parser::source_slice(self : Parser, start : Int, end : Int) -> String {
  if end <= start || start < 0 || end > self.source.length() {
    ""
  } else {
    String::unsafe_substring(self.source, start~, end~)
  }
}

///|
fn Parser::record_unsupported_syntax(
  self : Parser,
  start : Int,
  end : Int,
  kind : String,
) -> Unit {
  self.unsupported_syntax.push(UnsupportedSyntax::{
    start,
    end,
    text: self.source_slice(start, end),
    kind,
  })
}

///|
fn Parser::parse_empty_unsupported_expr(self : Parser) -> Expr {
  let offset = self.current_offset()
  self.builder.start_node(unsupported_expr())
  self.builder.finish_node()
  self.record_unsupported_syntax(offset, offset, kind_name(unsupported_expr()))
  UnsupportedExpr
}

///|
fn Parser::bump(self : Parser) -> Token {
  let tok = self.peek()
  self.builder.token(tok.kind(), tok.text())
  self.pos += 1
  self.byte_offset += tok.text().length()
  tok
}

///|
fn Parser::skip_whitespace(self : Parser) -> Unit {
  while self.pos < self.len {
    match self.tokens[self.pos].kind().raw() {
      2 | 4 => ignore(self.bump())
      _ => return
    }
  }
}

///|
fn Parser::skip_trivia(self : Parser) -> Unit {
  while self.pos < self.len {
    match self.tokens[self.pos].kind().raw() {
      2 | 3 | 4 => ignore(self.bump())
      _ => return
    }
  }
}

///|
/// PKL-148au: skip trivia (whitespace + newline + comment) plus
/// semicolons. Used by the binary-expression continuation peek so
/// `1\n + 2\n / 3` and `1;; + 2; / 3` keep parsing as a single
/// chain (Apple Pkl treats semicolons / newlines as whitespace
/// inside an expression).
fn Parser::skip_trivia_and_semicolons(self : Parser) -> Unit {
  while self.pos < self.len {
    match self.tokens[self.pos].kind().raw() {
      2 | 3 | 4 | 42 => ignore(self.bump())
      _ => return
    }
  }
}

///|
/// PKL-148au: peek past whitespace + newline + comment + semicolon
/// from `start`, returning the next significant token index. Sibling
/// to `skip_trivia_from`. The binary-expression continuation logic
/// uses this to decide whether the upcoming operator should re-enter
/// the chain after a soft separator.
fn Parser::skip_trivia_and_semicolons_from(self : Parser, start : Int) -> Int {
  let mut i = start
  while i < self.len {
    match self.tokens[i].kind().raw() {
      2 | 3 | 4 | 42 => i += 1
      _ => return i
    }
  }
  i
}

///|
fn Parser::consume_separators(self : Parser) -> Unit {
  while true {
    self.skip_whitespace()
    if self.pos < self.len && is_separator(self.tokens[self.pos].kind()) {
      ignore(self.bump())
    } else {
      return
    }
  }
}

///|
/// PKL-128d: capture one `@Name(...)` / `@Name { ... }` / `@Name`
/// annotation into the parser's pending list. The name segment may
/// contain dots (`@my.pkg.Custom`); the body delimiters and verbatim
/// body text are recorded so a downstream tool can re-parse the
/// arguments without scanning back to the open token. Returns true
/// if an annotation was consumed.
fn Parser::parse_annotation(self : Parser) -> Bool {
  if !self.at(at_sign()) {
    return false
  }
  ignore(self.bump())
  let name_buf = StringBuilder::new()
  while self.at(identifier()) || self.at(dot()) {
    name_buf.write_string(self.peek().text())
    ignore(self.bump())
  }
  self.skip_whitespace()
  let class_name = name_buf.to_string()
  let (body_kind, body_text) = if self.at(lparen()) {
    let start = self.current_offset()
    self.skip_balanced_group(lparen(), rparen())
    let end = self.current_offset()
    // `skip_balanced_group` advances past the closing delimiter, so
    // the body lives in (start + 1, end - 1).
    (AnnotationBodyKind::ParenBody, self.source_slice(start + 1, end - 1))
  } else if self.at(lbrace()) {
    let start = self.current_offset()
    self.skip_balanced_group(lbrace(), rbrace())
    let end = self.current_offset()
    (AnnotationBodyKind::BraceBody, self.source_slice(start + 1, end - 1))
  } else {
    (AnnotationBodyKind::NoBody, "")
  }
  self.pending_annotations.push({ class_name, body_kind, body_text })
  true
}

///|
fn Parser::skip_member_header(self : Parser) -> Unit {
  // PKL-128d: clear out any annotations a previous header captured
  // but the following decl parser declined to take, so they don't
  // bleed into the next member.
  self.pending_annotations.clear()
  // PKL-117: same lifecycle for modifier keywords — clear before each
  // header pass so a `abstract` consumed for one member doesn't bleed
  // into the next.
  self.pending_modifiers.clear()
  let mut keep_going = true
  while keep_going {
    self.skip_trivia()
    if self.at(at_sign()) {
      ignore(self.parse_annotation())
    } else if self.at(identifier()) && is_modifier_text(self.peek().text()) {
      self.pending_modifiers.push(self.peek().text())
      ignore(self.bump())
    } else {
      keep_going = false
    }
  }
}

///|
/// PKL-128d: drain the pending annotation list and return its
/// contents. Called by decl parsers that want to attach the captured
/// annotations to their AST node; bindings and other consumers that
/// don't need the metadata can ignore it — the next
/// `skip_member_header` will reset the list before parsing the next
/// member.
fn Parser::take_pending_annotations(self : Parser) -> Array[Annotation] {
  let out : Array[Annotation] = []
  for a in self.pending_annotations {
    out.push(a)
  }
  self.pending_annotations.clear()
  out
}

///|
/// PKL-117: drain the pending modifier list and return true when the
/// `abstract` keyword was among them. The bool-returning shape keeps
/// the call site narrow — today the typechecker only cares about
/// `abstract` for inheritance enforcement; richer modifier inspection
/// (`open`, `external`, etc.) can layer in by returning a richer
/// payload later.
fn Parser::take_pending_abstract(self : Parser) -> Bool {
  let mut found = false
  for modifier in self.pending_modifiers {
    if modifier == "abstract" {
      found = true
    }
  }
  self.pending_modifiers.clear()
  found
}

///|
/// PKL-145: drain the pending modifier list and return true when the
/// `hidden` keyword was among them. Parallels `take_pending_abstract`
/// — `parse_class_property_decl` calls this to thread the modifier
/// into the property name via `hidden_member_name`, matching the
/// object-body member path's existing behaviour.
fn Parser::take_pending_hidden(self : Parser) -> Bool {
  let mut found = false
  // Walk in reverse so removals don't shift the iteration window.
  for i = self.pending_modifiers.length() - 1; i >= 0; i = i - 1 {
    if self.pending_modifiers[i] == "hidden" {
      found = true
      let _ = self.pending_modifiers.remove(i)
    }
  }
  found
}

///|
fn Parser::take_pending_const(self : Parser) -> Bool {
  let mut found = false
  for i = self.pending_modifiers.length() - 1; i >= 0; i = i - 1 {
    if self.pending_modifiers[i] == "const" {
      found = true
      let _ = self.pending_modifiers.remove(i)
    }
  }
  found
}

///|
fn Parser::skip_balanced_group(
  self : Parser,
  open : @cst.SyntaxKind,
  close : @cst.SyntaxKind,
) -> Unit {
  if !self.at(open) {
    return
  }
  let mut depth = 0
  while !self.at(eof()) {
    if self.at(open) {
      depth += 1
    } else if self.at(close) {
      depth -= 1
      ignore(self.bump())
      if depth == 0 {
        return
      }
      continue
    }
    ignore(self.bump())
  }
}

///|
fn Parser::skip_unknown_member(self : Parser) -> Unit {
  let mut parens = 0
  let mut braces = 0
  let mut brackets = 0
  let mut consumed = false
  while !self.at(eof()) {
    if !consumed &&
      (self.at(rbrace()) || self.at(rparen()) || self.at(rbracket())) {
      ignore(self.bump())
      return
    }
    if consumed && parens == 0 && braces == 0 && brackets == 0 {
      if self.at(rbrace()) || self.at(rparen()) || self.at(rbracket()) {
        return
      }
      if is_separator(self.peek_kind()) {
        return
      }
    }
    if self.at(lparen()) {
      parens += 1
    } else if self.at(rparen()) {
      if parens == 0 {
        return
      }
      parens -= 1
    } else if self.at(lbrace()) {
      braces += 1
    } else if self.at(rbrace()) {
      if braces == 0 {
        return
      }
      braces -= 1
    } else if self.at(lbracket()) {
      brackets += 1
    } else if self.at(rbracket()) {
      if brackets == 0 {
        return
      }
      brackets -= 1
    }
    consumed = true
    ignore(self.bump())
  }
}

///|
fn Parser::skip_call_argument_tail(self : Parser) -> Unit {
  let start = self.current_offset()
  let mut parens = 0
  let mut braces = 0
  let mut brackets = 0
  let mut consumed = false
  while !self.at(eof()) {
    if parens == 0 && braces == 0 && brackets == 0 {
      if self.at(comma()) || self.at(rparen()) {
        break
      }
    }
    if self.at(lparen()) {
      parens += 1
    } else if self.at(rparen()) {
      if parens == 0 {
        break
      }
      parens -= 1
    } else if self.at(lbrace()) {
      braces += 1
    } else if self.at(rbrace()) {
      if braces == 0 {
        break
      }
      braces -= 1
    } else if self.at(lbracket()) {
      brackets += 1
    } else if self.at(rbracket()) {
      if brackets == 0 {
        break
      }
      brackets -= 1
    }
    consumed = true
    ignore(self.bump())
  }
  if consumed {
    self.record_unsupported_syntax(
      start,
      self.current_offset(),
      kind_name(unsupported_expr()),
    )
  }
}

///|
fn Parser::expect(
  self : Parser,
  kind : @cst.SyntaxKind,
  expected : String,
) -> Token? {
  self.skip_whitespace()
  if self.at(kind) {
    Some(self.bump())
  } else {
    self.add_error("expected \{expected}, got \{kind_name(self.peek_kind())}")
    self.builder.token(error_kind(), "")
    None
  }
}

///|
fn Parser::expect_text(self : Parser, text : String, expected : String) -> Unit {
  self.skip_whitespace()
  if self.at_text(text) {
    ignore(self.bump())
  } else {
    self.add_error("expected \{expected}, got \{self.peek().text()}")
    self.builder.token(error_kind(), "")
  }
}

///|
fn Parser::parse(self : Parser) -> ParseResult {
  self.builder.start_node(module_node())
  let mut module_name : String? = None
  let mut module_relation : ModuleRelation? = None
  let mut module_annotations : Array[Annotation] = []
  let imports : Array[ImportDecl] = []
  let declarations : Array[Declaration] = []
  let bindings : Array[Binding] = []
  self.consume_separators()
  let mut body : Expr? = None
  while !self.at(eof()) {
    let hidden_at_module_level = self.consume_module_visibility_modifiers()
    self.skip_member_header()
    if self.at(eof()) {
      break
    }
    // Single peek + kind/text dispatch for the module member.
    // Previously this was a chain of `at_text` calls (each doing a String
    // compare) plus separate `at(kind)` checks; consolidating into one
    // switch removes the redundant peeks and lets the kind check fire
    // first so non-identifier tokens don't pay any string compares.
    let kind = self.peek_kind()
    let text = if kind == identifier() { self.peek().text() } else { "" }
    if kind == import_kw() {
      match self.parse_import_decl() {
        Some(decl) => imports.push(decl)
        None => ()
      }
    } else if kind == module_kw() || text == "amends" || text == "extends" {
      match self.parse_module_decl() {
        Some(decl) => {
          match decl.module_name {
            Some(name) => module_name = Some(name)
            None => ()
          }
          match decl.module_relation {
            Some(relation) => module_relation = Some(relation)
            None => ()
          }
          // PKL-128d: the first explicit `module` header in the file
          // owns the captured annotations. Re-parsing of subsequent
          // `module` keywords (which Apple Pkl rejects anyway) would
          // overwrite, but a single header is the well-formed case.
          module_annotations = decl.annotations
        }
        None => ()
      }
    } else if kind == let_kw() {
      if self.peek_non_trivia_kind(self.pos + 1) == lparen() {
        if body is None {
          body = Some(self.parse_expr())
        } else {
          self.skip_unknown_member()
        }
      } else {
        match self.parse_let_decl() {
          Some(binding) => bindings.push(binding)
          None => ()
        }
      }
    } else if kind == local_kw() {
      // PKL-148m: a `local` modifier in front of a class / function /
      // typealias declaration was silently dropped (the whole member
      // routed through `skip_unknown_member`), so `local class C`,
      // `local function f`, and `local typealias T` never reached the
      // declaration list — references to them later in the module
      // raised `Cannot find property` / silently bypassed the
      // typealias's constraint. Apple Pkl treats `local` here as a
      // scope modifier (the declaration exists inside the module but
      // is not exported); pkl-mbt records the declaration unchanged
      // (the export-suppression side is a separate concern that only
      // matters when another module re-imports this one). Bump past
      // `local` and let the next loop iteration dispatch on the decl
      // keyword.
      let mut decl_i = self.skip_trivia_from(self.pos + 1)
      while decl_i < self.len &&
            self.tokens[decl_i].kind() == identifier() &&
            is_modifier_text(self.tokens[decl_i].text()) {
        decl_i = self.skip_trivia_from(decl_i + 1)
      }
      let next_text = if decl_i < self.len {
        self.tokens[decl_i].text()
      } else {
        ""
      }
      if next_text == "class" ||
        next_text == "function" ||
        next_text == "typealias" {
        ignore(self.bump())
        self.skip_whitespace()
        while self.at(identifier()) && is_modifier_text(self.peek().text()) {
          self.pending_modifiers.push(self.peek().text())
          ignore(self.bump())
          self.skip_whitespace()
        }
        if self.at_text("class") {
          match self.parse_class_decl() {
            Some(decl) => declarations.push(ClassDeclaration(decl))
            None => ()
          }
        } else if self.at_text("function") {
          match self.parse_function_decl() {
            Some(decl) => declarations.push(FunctionDeclaration(decl))
            None => ()
          }
        } else if self.at_text("typealias") {
          match self.parse_typealias_decl() {
            Some(decl) => declarations.push(TypeAliasDeclaration(decl))
            None => ()
          }
        } else {
          self.skip_unknown_member()
        }
      } else if is_decl_text(next_text) {
        self.skip_unknown_member()
      } else {
        match self.parse_local_decl() {
          Some(binding) => bindings.push(binding)
          None => ()
        }
      }
    } else if text == "class" {
      match self.parse_class_decl() {
        Some(decl) => declarations.push(ClassDeclaration(decl))
        None => ()
      }
    } else if text == "typealias" {
      match self.parse_typealias_decl() {
        Some(decl) => declarations.push(TypeAliasDeclaration(decl))
        None => ()
      }
    } else if text == "function" {
      match self.parse_function_decl() {
        Some(decl) => declarations.push(FunctionDeclaration(decl))
        None => ()
      }
    } else if self.at_property_decl() {
      match self.parse_property_decl() {
        Some(binding) =>
          if hidden_at_module_level {
            bindings.push({
              name: hidden_member_name(binding.name),
              type_name: binding.type_name,
              value: binding.value,
              exported: binding.exported,
              is_const: binding.is_const,
              annotations: binding.annotations,
              abstract_slot: binding.abstract_slot,
              sibling_slot: binding.sibling_slot,
            })
          } else {
            bindings.push(binding)
          }
        None => ()
      }
    } else if is_decl_text(text) {
      self.skip_unknown_member()
    } else if body is None {
      body = Some(self.parse_expr())
    } else {
      self.skip_unknown_member()
    }
    self.consume_separators()
  }
  self.builder.finish_node()
  let green = self.builder.finish()
  {
    root: @cst.SyntaxNode::new_root(green),
    program: {
      module_name,
      module_relation,
      imports,
      declarations,
      bindings,
      body,
      module_annotations,
    },
    diagnostics: self.diagnostics,
    unsupported_syntax: self.unsupported_syntax,
  }
}