///|
/// Result of `compile`: a parser or generated source, per `Options::output`.
pub(all) enum Compiled[T] {
  Parser(@vm.Parser[T])
  Source(String)
}

///|
/// Collects the bytecode program produced by the generate passes.
pub fn[T] program_of(
  grammar : @ast.Grammar,
  session : Session[T],
  options : Options[T],
) -> @bytecode.Program raise {
  guard grammar.literals is Some(literals) &&
    grammar.classes is Some(classes) &&
    grammar.expectations is Some(expectations) &&
    grammar.functions is Some(functions) else {
    raise CompilerError("The grammar has not been compiled to bytecode.")
  }
  let rules = []
  for rule in grammar.rules {
    guard rule.bytecode is Some(bytecode) else {
      raise CompilerError("Rule \"\{rule.name}\" has no bytecode.")
    }
    rules.push(@bytecode.RuleCode::{ name: rule.name, bytecode, })
  }
  {
    opcodes: session.opcodes,
    rules,
    literals,
    classes,
    expectations,
    functions,
    initializer: grammar.initializer.map(i => i.code),
    allowed_start_rules: options.start_rules(grammar),
    cache: options.cache,
    trace: options.trace,
    features: options.runtime_features(),
  }
}

///|
/// The default backend: interprets the grammar's bytecode, implementing code
/// blocks with `Options::actions` / `Options::initializer`.
pub fn[T] interpreter_backend(
  grammar : @ast.Grammar,
  session : Session[T],
  options : Options[T],
) -> @vm.Parser[T] raise {
  @vm.Parser::from_program(
    program_of(grammar, session, options),
    actions=options.actions,
    initializer?=options.initializer,
  )
}

///|
/// Runs the session's passes over `grammar` and produces the output selected
/// by `options.output` (`peg.compiler.compile`).
///
/// Raises `GrammarError` / `CompilerError` for semantic errors.
pub fn[T] compile(
  grammar : @ast.Grammar,
  session : Session[T],
  options : Options[T],
) -> Compiled[T] raise {
  // Resolve defaults on a copy, leaving the caller's options untouched.
  let options = options.copy()
  options.allowed_start_rules = Some(options.start_rules(grammar))
  // Each stage is looked up when it starts, so a pass may replace the
  // passes of later stages.
  for pass in session.passes.check.copy() {
    (pass.run)(grammar, session, options)
  }
  for pass in session.passes.transform.copy() {
    (pass.run)(grammar, session, options)
  }
  for pass in session.passes.generate.copy() {
    (pass.run)(grammar, session, options)
  }
  match options.output {
    Parser => Parser((session.backend)(grammar, session, options))
    Source =>
      match grammar.code {
        Some(code) => Source(code)
        None => raise CompilerError("No source code was generated.")
      }
  }
}