// Parser for the device tree source format.
//
// The grammar this covers is the subset that firmware sources actually use:
// `/dts-v1/`, rooted node definitions, typed and untyped property values,
// labels, references, and the delete and omit directives. The C preprocessor
// is deliberately out of scope — kernel sources run `cpp` before `dtc` and
// that step stays the caller's job, which is stated in the README rather than
// half-implemented here.
//
// Parsing produces the same `Node`/`Property` types the binary reader
// produces, so every consumer of a tree works with either source unchanged.

///|
/// The kinds of thing a property value can hold once parsed.
///
/// This is the intermediate form between the source text and the bytes the
/// binary format stores. Strings, cells and byte strings all end up in one
/// value buffer, but they go in differently, and a reference cannot be encoded
/// at all until phandles are assigned, so the distinction has to survive until
/// the encoding pass.
pub(all) enum ValuePart {
  /// A string, stored with its NUL terminator.
  Text(String)
  /// A cell list, stored as big-endian 32-bit words.
  Cells(Array[UInt64])
  /// A byte string from `[...]`, stored as-is.
  Bytes(Array[Byte])
  /// `&label`, resolvable only once every node has been named.
  Ref(String)
  /// `&{/path/to/node}`, an explicit path reference.
  PathRef(String)
  /// A cell list with references mixed in, which can only be flattened once
  /// phandles exist.
  Refs(Array[ValuePart])
  /// `/bits/ N <...>`, stored with the declared element width.
  TypedCells(UInt64, Array[UInt64])
}

///|
/// A property value: one or more parts concatenated.
///
/// `reg = <0 1>, <2 3>` and `compatible = "a", "b"` are both a list of one
/// kind, while `mixed = "x", <1>` is legal too. Keeping the parts separate
/// means the encoder does not have to re-parse the bytes to find the seams.
pub(all) struct Value {
  /// The parts in source order.
  parts : Array[ValuePart]
}

///|
/// Every way a source file can fail to parse.
///
/// Distinct from `LexerError`, which is about characters, and from `DtbError`,
/// which is about a binary blob. A caller can therefore report the three
/// failure modes differently, which matters when the input came from a user
/// pasting text.
pub(all) suberror ParseError {
  /// A token turned up where the grammar did not allow it.
  Unexpected(token~ : TokenKind, expected~ : String, span~ : Span)
  /// The scanner failed; carried through so that callers need one error type.
  Lexical(inner~ : LexerError)
  /// A value that cannot be represented in the binary form.
  BadValue(detail~ : String, span~ : Span)
  /// Nesting beyond the limit, which matches the binary reader's limit so that
  /// a tree that parses can also be written out.
  LimitExceeded(what~ : String, limit~ : Int32)
  /// A construct this version does not implement, named so the user knows
  /// what to work around rather than getting a generic syntax error.
  Unsupported(what~ : String, span~ : Span)
}

///|
/// Renders a parse error with its position.
pub impl Show for ParseError with fn output(self, logger) {
  let text = match self {
    Unexpected(token~, expected~, span~) =>
      "\{span}: expected \{expected}, found \{token}"
    Lexical(inner~) => inner.to_string()
    BadValue(detail~, span~) => "\{span}: \{detail}"
    LimitExceeded(what~, limit~) => "\{what} exceeds the limit of \{limit}"
    Unsupported(what~, span~) => "\{span}: unsupported: \{what}"
  }
  logger.write_string(text)
}

///|
/// Exposes the parse error message as a plain method.
pub extend ParseError with Show::{to_string, output}

///|
/// A parsed source file: the root node plus the memory reservations declared
/// before it.
///
/// The two are kept apart because the blob stores them in different blocks and
/// the reservation map is not part of the node tree.
pub(all) struct SourceFile {
  /// The root node, named "" for `/`.
  root : SourceNode
  /// Reservations from `/memreserve/` in source order.
  memreserves : Array[(UInt64, UInt64)]
  /// Whether the file declared `/plugin/`, which changes how missing
  /// references are treated.
  is_plugin : Bool
  /// Top-level patches from a plugin source, in order. A non-plugin file has
  /// none, and a plugin normally has no root node either.
  fragments : Array[Fragment]
}

///|
/// A property in the source tree, before its value is encoded.
pub(all) struct SourceProperty {
  /// The property name.
  name : String
  /// The parsed value, not yet encoded.
  value : Value
  /// Where the name appeared.
  span : Span
}

///|
/// One top-level patch in a plugin source, written `&label { ... }`.
///
/// The compiler turns each of these into a `fragment@N` node holding a
/// `target` reference and an `__overlay__` subtree, which is what the binary
/// form stores. They are kept separate here rather than folded at parse time
/// so that a file with no fragments still parses as a plain tree.
pub(all) struct Fragment {
  /// The label or path the patch applies to.
  target : ValuePart
  /// Where the fragment appeared.
  span : Span
  /// The properties and children the patch adds.
  node : SourceNode
}

///|
/// A node in the source tree, before references are resolved.
///
/// This is deliberately a different type from the binary `Node`: a source
/// property holds unresolved references and typed cell lists, neither of which
/// has a byte representation until the whole file has been read and phandles
/// assigned. The conversion happens in one pass afterwards, so the encoder
/// never has to look ahead.
pub(all) struct SourceNode {
  /// The node name, including any `@unit` suffix.
  name : String
  /// Labels bound to this node, in source order.
  mut labels : Array[String]
  /// Properties in source order.
  properties : Array[SourceProperty]
  /// Children in source order.
  children : Array[SourceNode]
}

///|
/// Parser state: the scanner plus one token of lookahead.
///
/// One token is enough for this grammar. The only construct that needs to
/// choose between alternatives by looking further is a node body, where an
/// identifier may be followed by `{`, `=` or `;` — and the token right after
/// the identifier settles which. Directives need two tokens of lookahead
/// (`/`, word, `/`), which `directive_word` gets by cloning the scanner
/// instead of buffering.
pub(all) struct Parser {
  /// The scanner, positioned just past `current`.
  lexer : Lexer
  /// The token the parser is looking at.
  mut current : Token
}

///|
/// Reads one token from the scanner, converting a scanner error into the
/// parser's error type so callers only handle one.
fn Parser::advance(self : Parser) -> Unit raise ParseError {
  let next = self.lexer.next() catch { e => raise Lexical(inner=e) }
  self.current = next
}

///|
/// Creates a parser positioned at the first token.
pub fn Parser::new(source : String) -> Parser raise ParseError {
  let lexer = Lexer::new(source)
  let first = lexer.next() catch { e => raise Lexical(inner=e) }
  Parser::{ lexer, current: first, }
}

///|
/// Fails unless the current token is `kind`, naming what was expected.
fn Parser::expect(
  self : Parser,
  kind : TokenKind,
  what : String,
) -> Token raise ParseError {
  if not_same_kind(self.current.kind, kind) {
    raise Unexpected(
      token=self.current.kind,
      expected=what,
      span=self.current.span,
    )
  }
  self.current
}

///|
/// Compares two token kinds for the purpose of `expect`.
///
/// Payload-carrying kinds compare by shape only: the caller asking for an
/// `Ident` wants any identifier, and asking for the payload as well would mean
/// constructing a dummy value at every call site.
fn not_same_kind(a : TokenKind, b : TokenKind) -> Bool {
  match (a, b) {
    (Ident(_), Ident(_)) => false
    (String(_), String(_)) => false
    (Number(_), Number(_)) => false
    (LBrace, LBrace)
    | (RBrace, RBrace)
    | (LAngle, LAngle)
    | (RAngle, RAngle)
    | (LBracket, LBracket)
    | (RBracket, RBracket)
    | (LParen, LParen)
    | (RParen, RParen)
    | (Comma, Comma)
    | (Semicolon, Semicolon)
    | (Equals, Equals)
    | (Slash, Slash)
    | (Amp, Amp)
    | (Colon, Colon)
    | (Minus, Minus)
    | (Eof, Eof) => false
    _ => true
  }
}

///|
/// Whether the scanner is at the end.
fn Parser::at_eof(self : Parser) -> Bool {
  match self.current.kind {
    Eof => true
    _ => false
  }
}

///|
/// Parses a whole source file.
///
/// The leading `/dts-v1/;` is required: without it the file is not a device
/// tree source, and treating it as one would accept a fragment as if it were
/// complete. `/plugin/;` may appear after it and is recorded rather than
/// acted on, because its only effect is on how unresolved references are
/// reported.
pub fn parse_source(source : String) -> SourceFile raise ParseError {
  let parser = Parser::new(source)
  let memreserves : Array[(UInt64, UInt64)] = []
  let mut is_plugin = false
  let mut saw_version = false
  while !parser.at_eof() {
    if parser.is_slash_directive("dts-v1") {
      if saw_version {
        raise BadValue(
          detail="/dts-v1/ appears more than once",
          span=parser.current.span,
        )
      }
      parser.consume_directive("dts-v1")
      parser.expect_kind(Semicolon, "';' after /dts-v1/")
      parser.advance()
      saw_version = true
    } else if parser.is_slash_directive("plugin") {
      parser.consume_directive("plugin")
      parser.expect_kind(Semicolon, "';' after /plugin/")
      parser.advance()
      is_plugin = true
    } else if parser.is_slash_directive("memreserve") {
      let span = parser.current.span
      parser.consume_directive("memreserve")
      let (address, next) = parser.take_number("an address after /memreserve/")
      let (size, _) = parser.take_number("a size after the address")
      ignore(next)
      parser.expect_kind(Semicolon, "';'")
      parser.advance()
      memreserves.push((address, size))
      ignore(span)
    } else {
      // The root node is the only node at top level. A file without one is a
      // fragment, which this version does not support because there is no
      // well-defined parent to attach it to.
      break
    }
  }
  if !saw_version {
    raise BadValue(
      detail="missing /dts-v1/; directive",
      span=parser.current.span,
    )
  }
  // A plugin may consist only of `&label { ... }` patches, so an empty file
  // after the directives is legal there. A plain source still needs its root
  // node.
  let fragments : Array[Fragment] = []
  let root : SourceNode = if parser.at_eof() {
    if !is_plugin {
      raise BadValue(detail="no root node", span=parser.current.span)
    }
    { name: "", labels: [], properties: [], children: [], }
  } else if parser.starts_fragment() {
    // Every top-level entry is a patch. The root node stays empty; the
    // fragments carry everything the file said.
    while !parser.at_eof() {
      fragments.push(parser.parse_fragment())
    }
    { name: "", labels: [], properties: [], children: [], }
  } else {
    let node = parser.parse_node("")
    if !parser.at_eof() {
      raise Unexpected(
        token=parser.current.kind,
        expected="end of file after the root node",
        span=parser.current.span,
      )
    }
    node
  }
  { root, memreserves, is_plugin, fragments, }
}

///|
/// Consumes `expect_kind`'s kind after checking it.
fn Parser::expect_kind(
  self : Parser,
  kind : TokenKind,
  what : String,
) -> Unit raise ParseError {
  ignore(self.expect(kind, what))
}

///|
/// Whether the cursor is at `/word`, which is how this grammar recognises a
/// directive.
///
/// `tag_after_slash` peeks past the slash by taking the token after it, which
/// costs one token of the parser's lookahead rather than re-lexing the source.
fn Parser::is_slash_directive(self : Parser, word : String) -> Bool {
  if not_same_kind(self.current.kind, Slash) {
    return false
  }
  // The scanner has already advanced past the slash in `current`; the word is
  // whatever the lexer returns next, so it is peeked by cloning the lexer.
  let ahead = self.lexer.clone()
  let token = ahead.next() catch { _ => return false }
  match token.kind {
    Ident(name) => name == word
    _ => false
  }
}

///|
/// Whether `a` and `b` are the same kind, for the places that need it without
/// the `expect` machinery.
fn same_kind(a : TokenKind, b : TokenKind) -> Bool {
  !not_same_kind(a, b)
}

///|
/// Consumes `/word/` in one step.
fn Parser::consume_directive(
  self : Parser,
  word : String,
) -> Unit raise ParseError {
  ignore(word)
  self.expect_kind(Slash, "'/'")
  self.advance()
  self.expect_kind(Ident(word), "the directive name")
  self.advance()
  self.expect_kind(Slash, "'/' closing the directive")
  self.advance()
}

///|
/// Consumes an integer token and returns its value.
fn Parser::take_number(
  self : Parser,
  what : String,
) -> (UInt64, Span) raise ParseError {
  let span = self.current.span
  match self.current.kind {
    Number(n) => {
      self.advance()
      (n, span)
    }
    other => raise Unexpected(token=other, expected=what, span~)
  }
}

///|
/// Parses a node: an optional label, a name, then a brace-delimited body.
///
/// The root node's name is the empty string, which is what `/` produces. Body
/// contents are dispatched on the token that starts them: an identifier may
/// introduce either a child node or a property, and `/` may introduce a
/// directive, so the parser reads the identifier first and decides from the
/// token after it.
fn Parser::parse_node(
  self : Parser,
  name : String,
) -> SourceNode raise ParseError {
  // A node at the top level is written as `/ { ... };` — the name is the
  // slash itself, which the caller has already consumed for the root.
  // The root is written `/ { ... };`, so its name is the empty string and the
  // slash that introduces it is consumed here. Child nodes arrive with their
  // name already read from the body loop.
  let node_name = if name == "" {
    if same_kind(self.current.kind, Slash) {
      self.advance()
      ""
    } else {
      raise Unexpected(
        token=self.current.kind,
        expected="'/' starting the root node",
        span=self.current.span,
      )
    }
  } else {
    name
  }
  self.expect_kind(LBrace, "'{' opening the node body")
  self.advance()
  let properties : Array[SourceProperty] = []
  let children : Array[SourceNode] = []
  let node_labels : Array[String] = []
  while !self.at_eof() {
    if same_kind(self.current.kind, RBrace) {
      self.advance()
      // A node definition ends with `};`. The semicolon is required by dtc and
      // its absence is the most common syntax error in hand-written sources,
      // so it is reported by name rather than as a brace mismatch.
      self.expect_kind(Semicolon, "';' after '}'")
      self.advance()
      return {
        name: node_name,
        labels: node_labels.copy(),
        properties,
        children,
      }
    }
    if same_kind(self.current.kind, Slash) {
      // A directive inside a node body.
      let directive = self.directive_word()
      match directive {
        Some(w) => {
          if w == "delete-node" ||
            w == "delete-property" ||
            w == "omit-if-no-ref" {
            raise Unsupported(
              what="\{w} inside a node body",
              span=self.current.span,
            )
          }
          raise Unexpected(
            token=self.current.kind,
            expected="a node, a property, or a directive this version understands",
            span=self.current.span,
          )
        }
        None =>
          raise Unexpected(
            token=self.current.kind,
            expected="a node or a property",
            span=self.current.span,
          )
      }
    }
    let (ident, ident_span) = self.take_ident("a node or property name")
    // A colon after an identifier binds it as a label for the node that
    // follows. This is the only place labels are recorded, and they stay in
    // source order so that phandle assignment is reproducible.
    if same_kind(self.current.kind, Colon) {
      self.advance()
      node_labels.push(ident)
      ignore(ident_span)
      continue
    }
    if same_kind(self.current.kind, Equals) ||
      same_kind(self.current.kind, Semicolon) {
      let prop = self.parse_property(ident, ident_span)
      properties.push(prop)
      continue
    }
    if same_kind(self.current.kind, LBrace) {
      // Labels accumulated before the node name belong to this node. They are
      // attached after parsing so the node is built in one place.
      let child = self.parse_node(ident)
      children.push({ ..child, labels: node_labels.copy(), })
      node_labels.clear()
      continue
    }
    raise Unexpected(
      token=self.current.kind,
      expected="'{', '=' or ';' after an identifier",
      span=self.current.span,
    )
  }
  raise Unexpected(
    token=self.current.kind,
    expected="'}' closing the node body",
    span=self.current.span,
  )
}

///|
/// Consumes an identifier token.
fn Parser::take_ident(
  self : Parser,
  what : String,
) -> (String, Span) raise ParseError {
  let span = self.current.span
  match self.current.kind {
    Ident(name) => {
      self.advance()
      (name, span)
    }
    other => raise Unexpected(token=other, expected=what, span~)
  }
}

///|
/// Reads the word between two slashes without consuming anything, or None if
/// the tokens after the slash are not `/word/`.
fn Parser::directive_word(self : Parser) -> String? {
  let ahead = self.lexer.clone()
  let first = ahead.next() catch { _ => return None }
  match first.kind {
    Ident(word) => {
      let second = ahead.next() catch { _ => return None }
      match second.kind {
        Slash => Some(word)
        _ => None
      }
    }
    _ => None
  }
}

///|
/// Parses the part of a property after its name: `= value;` or just `;` for
/// the boolean form.
fn Parser::parse_property(
  self : Parser,
  name : String,
  span : Span,
) -> SourceProperty raise ParseError {
  let value = if same_kind(self.current.kind, Equals) {
    self.advance()
    self.parse_value()
  } else {
    // A bare `name;` is a boolean property: present means true, and the encoded
    // form is a zero-length value.
    { parts: [], }
  }
  self.expect_kind(Semicolon, "';' after a property value")
  self.advance()
  { name, value, span, }
}

///|
/// Parses a property value: one or more parts separated by commas.
///
/// A comma between parts is the only separator the format has; whitespace and
/// newlines are insignificant. `&` introduces a reference that may point at a
/// label or, with braces, at a full path.
fn Parser::parse_value(self : Parser) -> Value raise ParseError {
  let parts : Array[ValuePart] = []
  while true {
    let part = self.parse_value_part()
    parts.push(part)
    if same_kind(self.current.kind, Comma) {
      self.advance()
      continue
    }
    break
  }
  Value::{ parts, }
}

///|
/// Parses one part of a value.
///
/// `/bits/ 8 <...>` and `/incbin/` are the two directives that can appear at
/// the start of a part. `bits` is handled because it changes how the cells are
/// encoded; `incbin` is not, because reading an external file would make the
/// parse depend on the file system and the format's byte output depends on
/// data outside the source.
fn Parser::parse_value_part(self : Parser) -> ValuePart raise ParseError {
  if same_kind(self.current.kind, Slash) {
    let directive = self.directive_word()
    match directive {
      Some("bits") => return self.parse_bits()
      Some("incbin") =>
        raise Unsupported(what="/incbin/", span=self.current.span)
      _ =>
        raise Unexpected(
          token=self.current.kind,
          expected="a value",
          span=self.current.span,
        )
    }
  }
  self.parse_plain_part()
}

///|
/// Parses the parts that have no directive prefix.
fn Parser::parse_plain_part(self : Parser) -> ValuePart raise ParseError {
  let span = self.current.span
  match self.current.kind {
    String(s) => {
      self.advance()
      Text(s)
    }
    LAngle => self.parse_cells()
    LBracket => self.parse_byte_string()
    Amp => self.parse_reference()
    Number(_) =>
      // A bare number outside `<...>` is not legal DTS. Reporting it as such is
      // more useful than letting the encoder produce an empty value.
      raise BadValue(detail="a number must be inside '<...>'", span~)
    _ =>
      raise Unexpected(
        token=self.current.kind,
        expected="a string, '<...>', '[...]' or '&reference'",
        span~,
      )
  }
}

///|
/// Parses `<...>`, a list of 32-bit cells.
///
/// Parenthesised expressions (`<(1 << 4)>`) are not evaluated: the format
/// passes them to the C preprocessor in practice, and evaluating them here
/// would disagree with dtc on anything the preprocessor was meant to handle.
fn Parser::parse_cells(self : Parser) -> ValuePart raise ParseError {
  self.expect_kind(LAngle, "'<'")
  self.advance()
  let cells : Array[UInt64] = []
  let mut saw_reference = false
  let refs : Array[ValuePart] = []
  while !self.at_eof() {
    if same_kind(self.current.kind, RAngle) {
      self.advance()
      // A cell list that contains a reference cannot be a plain cell list: the
      // reference has to be resolved to a phandle first, and a phandle is a
      // cell like any other. Folding it back into the cell list is the
      // encoder's job, so it is returned here as a reference with the
      // surrounding cells recorded on either side.
      if saw_reference {
        refs.push(Cells(cells.copy()))
        return Refs(refs.copy())
      }
      return Cells(cells)
    }
    if same_kind(self.current.kind, Amp) {
      let r = self.parse_reference()
      refs.push(Cells(cells.copy()))
      refs.push(r)
      cells.clear()
      saw_reference = true
      continue
    }
    match self.current.kind {
      Number(n) => {
        if n > 0xFFFF_FFFFUL {
          raise BadValue(
            detail="cell value \{n} does not fit in 32 bits",
            span=self.current.span,
          )
        }
        cells.push(n)
        self.advance()
      }
      _ =>
        raise Unexpected(
          token=self.current.kind,
          expected="a cell value or '>'",
          span=self.current.span,
        )
    }
  }
  raise Unexpected(
    token=self.current.kind,
    expected="'>' closing the cell list",
    span=self.current.span,
  )
}

///|
/// Parses `[...]`, a byte string.
///
/// Bytes are written as pairs of hex digits, optionally separated by
/// whitespace. dtc also accepts a single digit per byte in older sources but
/// rejects it now, so this follows the current rule.
fn Parser::parse_byte_string(self : Parser) -> ValuePart raise ParseError {
  self.expect_kind(LBracket, "'['")
  self.advance()
  let bytes : Array[Byte] = []
  while !self.at_eof() {
    if same_kind(self.current.kind, RBracket) {
      self.advance()
      return Bytes(bytes)
    }
    match self.current.kind {
      HexByte(b) => {
        bytes.push(b)
        self.advance()
      }
      _ =>
        raise Unexpected(
          token=self.current.kind,
          expected="a pair of hex digits or ']'",
          span=self.current.span,
        )
    }
  }
  raise Unexpected(
    token=self.current.kind,
    expected="']' closing the byte string",
    span=self.current.span,
  )
}

///|
/// Parses a reference: `&label` or `&{/path/to/node}`.
fn Parser::parse_reference(self : Parser) -> ValuePart raise ParseError {
  self.expect_kind(Amp, "'&'")
  self.advance()
  match self.current.kind {
    Ident(label) => {
      self.advance()
      Ref(label)
    }
    LBrace => {
      self.advance()
      let parts : Array[String] = []
      while !self.at_eof() {
        if same_kind(self.current.kind, RBrace) {
          self.advance()
          return PathRef("/" + join_segments(parts))
        }
        match self.current.kind {
          Ident(seg) => {
            parts.push(seg)
            self.advance()
          }
          Slash => self.advance()
          other =>
            raise Unexpected(
              token=other,
              expected="a path segment or '}'",
              span=self.current.span,
            )
        }
      }
      raise Unexpected(
        token=self.current.kind,
        expected="'}' closing the path reference",
        span=self.current.span,
      )
    }
    other =>
      raise Unexpected(
        token=other,
        expected="a label or '{' after '&'",
        span=self.current.span,
      )
  }
}

///|
/// Joins path segments with slashes.
fn join_segments(parts : Array[String]) -> String {
  let buf = StringBuilder()
  let mut first = true
  for p in parts {
    if !first {
      buf.write_string("/")
    }
    first = false
    buf.write_string(p)
  }
  buf.to_string()
}

///|
/// Parses `/bits/ N <...>`.
///
/// The bits directive changes cell width: 8, 16, 32 or 64 bits per element.
/// dtc writes the result as a byte string rather than as cells, so the parse
/// records the width and the encoder emits bytes.
fn Parser::parse_bits(self : Parser) -> ValuePart raise ParseError {
  ignore(self.directive_word())
  self.consume_directive("bits")
  let (width, _) = self.take_number("a width after /bits/")
  let valid = width == 8UL || width == 16UL || width == 32UL || width == 64UL
  if !valid {
    raise BadValue(
      detail="/bits/ width must be 8, 16, 32 or 64",
      span=self.current.span,
    )
  }
  self.expect_kind(LAngle, "'<' after /bits/ width")
  self.advance()
  let cells : Array[UInt64] = []
  while !self.at_eof() {
    if same_kind(self.current.kind, RAngle) {
      self.advance()
      return TypedCells(width, cells)
    }
    match self.current.kind {
      Number(n) => {
        let max = if width == 64UL {
          0xFFFF_FFFF_FFFF_FFFFUL
        } else {
          (1UL << width.to_int()) - 1UL
        }
        if n > max {
          raise BadValue(
            detail="value does not fit in \{width} bits",
            span=self.current.span,
          )
        }
        cells.push(n)
        self.advance()
      }
      _ =>
        raise Unexpected(
          token=self.current.kind,
          expected="a cell value or '>'",
          span=self.current.span,
        )
    }
  }
  raise Unexpected(
    token=self.current.kind,
    expected="'>' closing the cell list",
    span=self.current.span,
  )
}

///|
/// Whether the next top-level construct is a `&label { ... }` patch.
///
/// The token after `&` is either an identifier or `{`, and a plain reference
/// never appears at top level, so seeing `&` here is unambiguous.
fn Parser::starts_fragment(self : Parser) -> Bool {
  same_kind(self.current.kind, Amp)
}

///|
/// Parses one `&target { ... }` patch.
///
/// The node body is parsed by the same code a normal node uses, so properties,
/// children and nested labels all behave the same inside a patch.
fn Parser::parse_fragment(self : Parser) -> Fragment raise ParseError {
  let span = self.current.span
  let target = self.parse_reference()
  let node = self.parse_node_body("__overlay__")
  { target, span, node, }
}

///|
/// Parses a brace-delimited body under a caller-supplied name.
///
/// Extracted from `parse_node` so a fragment can reuse it: the difference
/// between a node and a patch is only how the name was decided.
fn Parser::parse_node_body(
  self : Parser,
  name : String,
) -> SourceNode raise ParseError {
  self.expect_kind(LBrace, "'{' opening the node body")
  self.advance()
  let properties : Array[SourceProperty] = []
  let children : Array[SourceNode] = []
  let node_labels : Array[String] = []
  while !self.at_eof() {
    if same_kind(self.current.kind, RBrace) {
      self.advance()
      self.expect_kind(Semicolon, "';' after '}'")
      self.advance()
      return { name, labels: node_labels.copy(), properties, children, }
    }
    let (ident, ident_span) = self.take_ident("a node or property name")
    if same_kind(self.current.kind, Colon) {
      self.advance()
      node_labels.push(ident)
      ignore(ident_span)
      continue
    }
    let eq = same_kind(self.current.kind, Equals)
    let semi = same_kind(self.current.kind, Semicolon)
    let is_property = eq || semi
    if is_property {
      properties.push(self.parse_property(ident, ident_span))
      continue
    }
    if same_kind(self.current.kind, LBrace) {
      let child = self.parse_node(ident)
      children.push({ ..child, labels: node_labels.copy(), })
      node_labels.clear()
      continue
    }
    raise Unexpected(
      token=self.current.kind,
      expected="'{', '=' or ';' after an identifier",
      span=self.current.span,
    )
  }
  raise Unexpected(
    token=self.current.kind,
    expected="'}' closing the node body",
    span=self.current.span,
  )
}