// The whole pipeline, in one call each.
//
// Everything here is a composition of packages that can also be used directly:
// `@grammar.parse_string`, `@output.render`, `@compile.Session`. Reach for
// those when you need a stage's intermediate value -- the AST, the trivia
// table, the lowered wasm module. Reach for these when you want the answer.

///|
/// The front end, re-exported.
///
/// `parse_string` stops at the first syntax error; `parse_recover` reports
/// every error it can resynchronize past, at the price of a tree that is partly
/// a guess about what the author meant. Both return a `ParseResult` whose
/// `module_` is `None` only when nothing could be recovered.
pub using @grammar {parse_string, parse_recover, type ParseResult}

///|
/// The formatter, re-exported. Takes the AST and the trivia table `parse_string`
/// returned, and gives back Wax source.
pub using @output {render}

///|
/// The AST-first pipeline, re-exported from `marianoguerra/wax/compile`.
///
/// These names are the reason that package exists separately: a code generator
/// imports it directly and never pays for the parser.
pub using @compile {
  type Session,
  type Checked,
  type CompileError,
  apply_policy,
  rejected,
}

///|
/// Parse, check and compile Wax source to a `.wasm` binary.
///
/// `Err` carries the diagnostics that stopped it -- syntax errors, or whatever
/// the type checker rejected under `policy`. Nothing is returned alongside
/// them: a compile that reports an error produces no bytes at all, rather than
/// bytes derived from a module known to be wrong.
///
/// Warnings never reach the caller here, because a compile reports what stops
/// it producing a module and a style warning does not. Use `Session` directly,
/// or the `check` command's `warn_unused`, to see those.
pub fn compile_string(
  src : String,
  fname? : String,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[Bytes, Array[@diagnostic.Diagnostic]] raise @compile.CompileError {
  match checked(src, fname?, features?, policy?) {
    Err(diagnostics) => Err(diagnostics)
    Ok(c) => Ok(c.to_bytes())
  }
}

///|
/// Parse, check and print Wax source in the WebAssembly text format.
///
/// The same lowering `compile_string` encodes, printed instead of encoded, so
/// the two forms describe the same module by construction. Comments survive:
/// they come from the parse's trivia table and are re-delimited as WAT
/// comments.
pub fn compile_string_to_wat(
  src : String,
  fname? : String,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[String, Array[@diagnostic.Diagnostic]] raise @compile.CompileError {
  let r = parse(src, fname?)
  match checked_of(r, src, features?, policy?) {
    Err(diagnostics) => Err(diagnostics)
    Ok(c) => Ok(c.to_wat(trivia=r.trivia))
  }
}

///|
/// Reformat Wax source.
///
/// The type checker does not run: reformatting is a front-end operation, and a
/// module that does not type-check still has a canonical layout. `Err` therefore
/// only ever holds syntax errors.
pub fn format_string(
  src : String,
  fname? : String,
  theme? : @colors.Theme,
) -> Result[String, Array[@diagnostic.Diagnostic]] {
  let r = parse(src, fname?)
  guard syntax_errors(r).is_empty() else { return Err(syntax_errors(r)) }
  guard r.module_ is Some(m) else { return Err(syntax_errors(r)) }
  Ok(@output.render(m, r.trivia, theme?))
}

///|
/// Parse without recovery, which is what every function here wants: a recovered
/// tree is a guess, and checking or printing a guess reports on instructions
/// nobody wrote.
fn parse(src : String, fname? : String) -> ParseResult {
  @grammar.parse_string(src, fname?)
}

///|
/// The parse's errors, as diagnostics. Warnings and suggestions are dropped:
/// the callers here report only what stopped them.
fn syntax_errors(r : ParseResult) -> Array[@diagnostic.Diagnostic] {
  let out = []
  for e in r.errors {
    if e.severity is Error {
      out.push(@diagnostic.of_report(e))
    }
  }
  out
}

///|
/// Parse and check, or report why not.
fn checked(
  src : String,
  fname? : String,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[Checked, Array[@diagnostic.Diagnostic]] {
  checked_of(parse(src, fname?), src, features?, policy?)
}

///|
/// Check an already-parsed module, or report why not.
///
/// `src` is passed alongside because a parse does not keep the text it read,
/// and a diagnostic needs it to print the snippet under the caret.
fn checked_of(
  r : ParseResult,
  src : String,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[Checked, Array[@diagnostic.Diagnostic]] {
  let errors = syntax_errors(r)
  guard errors.is_empty() else { return Err(errors) }
  guard r.module_ is Some(m) else { return Err(errors) }
  let session = Session::new(features?, source=src)
  let c = session.check(m)
  let reports = session.reports(policy?)
  if rejected(reports) {
    Err(reports)
  } else {
    Ok(c)
  }
}