///|
/// A compiler pass: inspects or rewrites the grammar, reporting problems
/// through the session. `name` identifies built-in passes (e.g.
/// `"reportUndefinedRules"`), which lets plugins find and replace them.
pub(all) struct Pass[T] {
  name : String
  run : (@ast.Grammar, Session[T], Options[T]) -> Unit raise
}

///|
/// The ordered pass stages `compile` runs.
pub(all) struct Passes[T] {
  mut check : Array[Pass[T]]
  mut transform : Array[Pass[T]]
  mut generate : Array[Pass[T]]
}

///|
/// Compiler configuration handed to plugins (`peg.compiler.Session`).
///
/// Every component can be replaced: the grammar `parser`, the `passes`, the
/// `opcodes` numbering used by bytecode generation, the `warn` / `error`
/// reporters, and the `backend` that turns a compiled grammar into a parser
/// (the counterpart of PEG.js' `vm`).
pub(all) struct Session[T] {
  mut parser : (String, ParserOptions) -> @ast.Grammar raise
  mut passes : Passes[T]
  mut opcodes : @bytecode.Opcodes
  mut warn : (String, @runtime.Location?) -> Unit
  mut error : (String, @runtime.Location?) -> Unit raise
  mut backend : (@ast.Grammar, Session[T], Options[T]) -> @vm.Parser[T] raise
}

///|
/// Raises `GrammarError` when a location is given, `CompilerError`
/// otherwise (`session.fatal`).
pub fn fatal(message : String, location? : @runtime.Location) -> Unit raise {
  match location {
    Some(location) => raise GrammarError(message~, location~)
    None => raise CompilerError(message)
  }
}

///|
pub fn[T] Session::new(
  parser~ : (String, ParserOptions) -> @ast.Grammar raise,
  passes? : Passes[T] = default_passes(),
  opcodes? : @bytecode.Opcodes = @bytecode.default_opcodes,
  warn? : (String, @runtime.Location?) -> Unit = (_, _) => (),
  error? : (String, @runtime.Location?) -> Unit raise = (m, l) => {
    fatal(m, location?=l)
  },
  backend? : (@ast.Grammar, Session[T], Options[T]) -> @vm.Parser[T] raise = interpreter_backend,
) -> Session[T] {
  { parser, passes, opcodes, warn, error, backend, }
}

///|
pub fn[T] Session::parse(
  self : Session[T],
  input : String,
  options : ParserOptions,
) -> @ast.Grammar raise {
  (self.parser)(input, options)
}

///|
pub fn[T] Session::report_error(
  self : Session[T],
  message : String,
  location? : @runtime.Location,
) -> Unit raise {
  (self.error)(message, location)
}

///|
pub fn[T] Session::report_warning(
  self : Session[T],
  message : String,
  location? : @runtime.Location,
) -> Unit {
  (self.warn)(message, location)
}

///|
/// The standard passes, in PEG.js order.
pub fn[T] default_passes() -> Passes[T] {
  {
    check: [
      { name: "reportUndefinedRules", run: report_undefined_rules, },
      { name: "reportDuplicateRules", run: report_duplicate_rules, },
      { name: "reportUnusedRules", run: report_unused_rules, },
      { name: "reportDuplicateLabels", run: report_duplicate_labels, },
      { name: "reportInfiniteRecursion", run: report_infinite_recursion, },
      { name: "reportInfiniteRepetition", run: report_infinite_repetition, },
      { name: "reportIncorrectPlucking", run: report_incorrect_plucking, },
    ],
    transform: [{ name: "removeProxyRules", run: remove_proxy_rules, }],
    generate: [
      { name: "calcReportFailures", run: calc_report_failures, },
      { name: "inferenceMatchResult", run: inference_match_result, },
      { name: "generateBytecode", run: generate_bytecode, },
    ],
  }
}