///|
/// Where an `@` form is in its own little grammar.
///
/// The names are the reference's. `Initial` through `OpContinue` are the part
/// before the braces, where ordinary shrubbery tokens are read and the mode
/// only decides what may come next; `Open`, `Inside`, `Escape` and `Close` are
/// the braced text, where the scanner is reading characters rather than tokens.
pub(all) enum AtMode {
  /// After `@` with no immediate `{`: read a command term.
  Initial
  /// After the command, reading a parenthesised argument list.
  Args
  /// `@(«...»)`: splice as-is, and take no arguments even if `(` follows.
  NoArgs
  /// Inside an `a.b.c` chain, where no space is allowed between the parts.
  OpContinue
  /// The next characters are the opener and `{`.
  Open
  /// In the braced text.
  Inside
  /// In the braced text, and an escape starts here.
  Escape
  /// At the final `}`.
  Close
} derive(Eq)

///|
/// One `@` form being scanned.
priv struct AtFrame {
  mut mode : AtMode
  /// The characters before `{`: `""` for a plain `@{`, `"|<<"` for `@|<<{`.
  /// The closer is this reversed and flipped, so the text can carry a nested
  /// `@` form of its own without either being confused for the other.
  opener : String
  /// Whether the whole form is a `@//` comment.
  comment : Bool
  /// Bracket nesting while reading the command and arguments.
  openers : Array[String]
  /// Brace nesting inside the text.
  mut depth : Int
}

///|
/// `<` and `>`, `[` and `]`, `(` and `)` swap; everything else is its own
/// mirror. A closer is the opener reversed with each character flipped, so
/// `|<<{` closes with `}>>|`.
fn flip_bracket(c : Char) -> Char {
  match c {
    '<' => '>'
    '>' => '<'
    '[' => ']'
    ']' => '['
    '(' => ')'
    ')' => '('
    _ => c
  }
}

///|
fn opener_to_closer(opener : String) -> String {
  let buf = StringBuilder()
  let chars = []
  for c in opener {
    chars.push(c)
  }
  for i = chars.length() - 1; i >= 0; i = i - 1 {
    buf.write_char(flip_bracket(chars[i]))
  }
  buf.to_string()
}

///|
/// The opener prefix starting at `i`, if one does.
///
/// `{` alone gives `""`. A `|` followed by ASCII symbols or punctuation and
/// then `{` gives that prefix. Anything else gives nothing.
///
/// ASCII-only, deliberately: the prefix has to be typed twice, once as itself
/// and once mirrored, and restricting it keeps the mirroring total.
fn Scanner::peek_at_opener(self : Scanner, i : Int) -> String? {
  match self.at(i) {
    Some('{') => Some("")
    Some('|') => {
      let buf = StringBuilder()
      buf.write_char('|')
      let mut j = i + 1
      while j < self.src.length() {
        match self.at(j) {
          Some('{') => return Some(buf.to_string())
          Some(c) =>
            if c.to_int() < 128 &&
              (@unicode.is_symbolic(c) || @unicode.is_punctuation(c)) {
              buf.write_char(c)
              j = j + 1
            } else {
              return None
            }
          None => return None
        }
      }
      None
    }
    _ => None
  }
}

///|
/// Whether `opener` followed by `c` sits at `i`.
fn Scanner::peek_at_prefixed(
  self : Scanner,
  i : Int,
  opener : String,
  c : Char,
) -> Bool {
  if !self.has(i, opener) {
    return false
  }
  self.at(i + opener.length()) is Some(x) && x == c
}

///|
/// Whether the closer for `opener` sits at `i`.
fn Scanner::peek_at_closer(self : Scanner, i : Int, opener : String) -> Bool {
  if !(self.at(i) is Some('}')) {
    return false
  }
  self.has(i + 1, opener_to_closer(opener))
}

///|
fn Scanner::at_top(self : Scanner) -> AtFrame? {
  if self.at_stack.length() == 0 {
    None
  } else {
    Some(self.at_stack[self.at_stack.length() - 1])
  }
}

///|
/// `@` at the top of a term, or inside braced text after the escape prefix.
///
/// `@//` is a comment: a braced one when an opener follows, and otherwise a
/// line comment that also swallows the newline and the next line's indentation
/// — without that, commenting out a line of text would leave its line break
/// behind and change the text around it.
fn Scanner::scan_at(self : Scanner, start : @basic.Pos, from : Int) -> Token {
  if self.has(from, "@//") {
    match self.peek_at_opener(from + 3) {
      Some(opener) => {
        self.at_stack.push({
          mode: Open,
          opener,
          comment: true,
          openers: [],
          depth: 0,
        })
        return self.finish(start, from + 3, AtComment)
      }
      None =>
        return self.finish(
          start,
          self.scan_at_line_comment(from + 3),
          AtComment,
        )
    }
  }
  let opener = self.peek_at_opener(from + 1)
  self.at_stack.push({
    mode: if opener is Some(_) {
      Open
    } else {
      Initial
    },
    opener: opener.unwrap_or(""),
    comment: false,
    openers: [],
    depth: 0,
  })
  self.finish(start, from + 1, At)
}

///|
/// A `@//` line comment: to the end of the line, then the newline, then the
/// leading whitespace of the next line.
fn Scanner::scan_at_line_comment(self : Scanner, from : Int) -> Int {
  let mut i = from
  while i < self.src.length() {
    match self.at(i) {
      Some('\n') | Some('\r') => break
      _ => i = self.step(i)
    }
  }
  match self.at(i) {
    Some('\r') => i = if self.has(i, "\r\n") { i + 2 } else { i + 1 }
    Some('\n') => i = i + 1
    _ => return i
  }
  while i < self.src.length() {
    match self.at(i) {
      Some(' ') | Some('\t') => i = i + 1
      _ => break
    }
  }
  i
}

///|
/// Emit the opener token and enter the text.
fn Scanner::at_open(self : Scanner, frame : AtFrame) -> Token {
  let start = self.here()
  let end = self.idx + frame.opener.length() + 1
  frame.mode = Inside
  frame.depth = 0
  self.finish(start, end, AtOpener)
}

///|
/// Emit the closer token and leave the form.
///
/// A second opener may follow immediately — `@x{a}{b}` is one call with two
/// text arguments — so the frame is reused rather than popped when it does.
fn Scanner::at_close(self : Scanner, frame : AtFrame) -> Token {
  let start = self.here()
  let closer = opener_to_closer(frame.opener)
  let end = self.idx + 1 + closer.length()
  match self.peek_at_opener(end) {
    Some(next) if next == frame.opener || frame.opener == "" =>
      // Only a repeat of the same opener continues this form; a different one
      // would be a new form and is left for the next token.
      if next == frame.opener {
        frame.mode = Open
      } else {
        let _ = self.at_stack.pop()
      }
    _ => {
      let _ = self.at_stack.pop()
    }
  }
  self.finish(start, end, AtCloser, partner=Some("}" + closer))
}

///|
/// Emit the escape's `@` and start a nested form.
fn Scanner::at_escape(self : Scanner, frame : AtFrame) -> Token {
  let start = self.here()
  let from = self.idx + frame.opener.length()
  // Back to reading text once the nested form finishes.
  frame.mode = Inside
  if self.has(from, "@//") {
    match self.peek_at_opener(from + 3) {
      Some(opener) => {
        self.at_stack.push({
          mode: Open,
          opener,
          comment: true,
          openers: [],
          depth: 0,
        })
        return self.finish(start, from + 3, AtComment)
      }
      None =>
        return self.finish(start, self.scan_at_line_comment(from + 3), Comment)
    }
  }
  let opener = self.peek_at_opener(from + 1)
  self.at_stack.push({
    mode: if opener is Some(_) {
      Open
    } else {
      Initial
    },
    opener: opener.unwrap_or(""),
    comment: frame.comment,
    openers: [],
    depth: 0,
  })
  self.finish(start, from + 1, At)
}

///|
/// Read braced text up to the next thing that is not text.
///
/// The content is broken at every newline, at every escape and at the closer,
/// which is what lets the parser rebuild lines and strip their shared
/// indentation later. A newline becomes a content token of its own.
///
/// Nested `{` and `}` inside the text are literal and only track depth, so a
/// balanced brace in prose does not end the form.
fn Scanner::at_inside(self : Scanner, frame : AtFrame) -> Token {
  let start = self.here()
  let opener = frame.opener
  let mut i = self.idx
  while i < self.src.length() {
    match self.at(i) {
      Some('\n') | Some('\r') =>
        // A newline alone is its own token; text before it ends here.
        if i == self.idx {
          let e = if self.has(i, "\r\n") { i + 2 } else { i + 1 }
          return self.finish(start, e, AtContent)
        } else {
          return self.finish(start, i, AtContent)
        }
      _ =>
        if self.peek_at_closer(i, opener) {
          if frame.depth == 0 {
            frame.mode = Close
            return self.finish(start, i, AtContent)
          }
          frame.depth = frame.depth - 1
          i = i + 1 + opener_to_closer(opener).length()
        } else if self.peek_at_prefixed(i, opener, '@') {
          frame.mode = Escape
          return self.finish(start, i, AtContent)
        } else if self.peek_at_prefixed(i, opener, '{') {
          frame.depth = frame.depth + 1
          i = i + opener.length() + 1
        } else {
          i = self.step(i)
        }
    }
  }
  // End of input inside the text: hand back what there is and let the parser
  // report the missing closer, which it can do with a span.
  frame.mode = Close
  self.finish(start, i, AtContent)
}

///|
/// Update the command-and-arguments state after one ordinary token.
///
/// This is the half of the `@` grammar that reads shrubbery rather than text:
/// `@f(x)[y]{z}` is one form, and what decides that is whether each bracket
/// closed and whether the very next character continues it. A space anywhere
/// ends the form, which is why every test here is on the immediately following
/// character.
fn Scanner::after_at_token(self : Scanner, frame : AtFrame, t : Token) -> Token {
  let mut token = t
  match t.kind {
    Opener | SExp(_) => {
      if frame.mode is Initial &&
        frame.openers.length() == 1 &&
        frame.openers[0] == "(" &&
        t.text == "\u{AB}" {
        // `@(«...»)` splices its command and takes no arguments.
        frame.mode = NoArgs
      }
      frame.openers.push(if t.kind is SExp(_) { "{" } else { t.text })
    }
    Closer =>
      if frame.openers.length() > 0 &&
        closes(frame.openers[frame.openers.length() - 1], t.text) {
        let _ = frame.openers.pop()
      } else {
        token = { ..t, kind: Fail(InvalidAfterAt), }
      }
    _ => ()
  }
  if frame.openers.length() > 0 {
    return token
  }
  // At a term boundary: decide whether the form continues, and how.
  match self.peek_at_opener(self.idx) {
    Some(op) if !(frame.mode is NoArgs) => {
      let f = self.at_stack[self.at_stack.length() - 1]
      self.at_stack[self.at_stack.length() - 1] = {
        ..f,
        opener: op,
        mode: Open,
      }
      return token
    }
    _ => ()
  }
  if frame.mode is Initial &&
    t.kind is Identifier &&
    self.peek_operator_then_identifier(self.idx) {
    frame.mode = OpContinue
    return token
  }
  if frame.mode is OpContinue {
    frame.mode = Initial
    return token
  }
  if !(frame.mode is Args) && !(frame.mode is NoArgs) {
    match self.at(self.idx) {
      // `[` is taken as an argument list for consistency with S-expression `@`,
      // and rejected by the parser with a message about it. Refusing it here
      // instead would report a stray bracket rather than the mistake.
      Some('(') | Some('[') => {
        frame.mode = Args
        frame.openers.clear()
        return token
      }
      _ => ()
    }
  }
  let _ = self.at_stack.pop()
  token
}

///|
/// Whether an operator and then an identifier follow, with no space between.
///
/// This is what makes `@a.b.c` one command rather than `@a` followed by text.
fn Scanner::peek_operator_then_identifier(self : Scanner, i : Int) -> Bool {
  match self.scan_operator(i) {
    Some(e) => self.scan_identifier(e) is Some(_)
    None => false
  }
}

///|
fn closes(opener : String, closer : String) -> Bool {
  match opener {
    "(" => closer == ")"
    "[" => closer == "]"
    "{" => closer == "}"
    "\u{AB}" => closer == "\u{BB}"
    "'" => closer == "'"
    _ => false
  }
}