// The was front end: shrubbery notation in, Wax AST or wasm out.

///|
/// Everything that can be wrong with a was program.
pub(all) struct Diagnostics {
  was : Array[@er.Report]
  wax : Array[@diagnostic.Diagnostic]
}

///|
/// True when nothing was found.
pub fn Diagnostics::is_empty(self : Self) -> Bool {
  self.was.length() == 0 && self.wax.length() == 0
}

///|
/// Read was source into the Wax AST.
///
/// A grouping error is fatal and comes back as a one-element list, because
/// there is no tree to read. Anything else is not: the module comes back with
/// whatever could be read, so one bad line does not hide the rest.
pub fn to_wax(
  src : String,
  fname? : String = "input.was",
) -> Result[@read.Read, Array[@er.Report]] {
  Ok(@read.read_module(src, fname~)) catch {
    @read.ReadError(r) => Err([r])
  }
}

///|
/// Compile was source to wasm.
pub fn compile_string(
  src : String,
  fname? : String = "input.was",
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[Bytes, Diagnostics] raise @compile.CompileError {
  match check_string(src, fname~, features?, policy?) {
    Err(d) => Err(d)
    Ok(checked) => Ok(checked.to_bytes())
  }
}

///|
/// Compile was source to the WebAssembly text format.
pub fn compile_string_to_wat(
  src : String,
  fname? : String = "input.was",
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[String, Diagnostics] raise @compile.CompileError {
  match check_string(src, fname~, features?, policy?) {
    Err(d) => Err(d)
    Ok(checked) => Ok(checked.to_wat())
  }
}

///|
/// Read and type check, stopping before the emitters.
pub fn check_string(
  src : String,
  fname? : String = "input.was",
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[@compile.Checked, Diagnostics] {
  let m = match to_wax(src, fname~) {
    Err(rs) => return Err({ was: rs, wax: [], })
    Ok(m) => m
  }
  if m.diagnostics().length() > 0 {
    return Err({ was: m.diagnostics(), wax: [], })
  }
  let session = @compile.Session::new(features?, source=src)
  let checked = session.check(m.fields())
  let reports = session.reports(policy?)
  if @compile.rejected(reports) {
    Err({ was: [], wax: reports, })
  } else {
    Ok(checked)
  }
}

///|
/// The Wax module fields a was file denotes.
///
/// This is the seam: the result is indistinguishable from what the Wax parser
/// produces for the equivalent .wax file, which is what makes was a notation
/// for Wax rather than a language above it.
pub fn fields(
  src : String,
  fname? : String = "input.was",
) -> Result[@ast.LocModule, Array[@er.Report]] {
  match to_wax(src, fname~) {
    Err(rs) => Err(rs)
    Ok(m) =>
      if m.diagnostics().length() > 0 {
        Err(m.diagnostics())
      } else {
        Ok(m.fields())
      }
  }
}

///|
/// Print a Wax module as was source.
///
/// This is the other half of the notation: `fields` says what a .was file
/// denotes, and this says that every Wax module can be written as one. The
/// tests close the loop -- read, print, read again, and compare the wasm --
/// which is what makes `wax -f was` and `wap -f was` something other than a
/// hope.
pub fn print_module(fields : @ast.LocModule) -> String {
  @print.module_(fields)
}

///|
/// Reformat was source: read it and print it back.
///
/// The type checker does not run, because a canonical layout is a front-end
/// question and a module that does not check still has one.
pub fn format_string(
  src : String,
  fname? : String = "input.was",
) -> Result[String, Array[@er.Report]] {
  match fields(src, fname~) {
    Err(rs) => Err(rs)
    Ok(f) => Ok(@print.module_(f))
  }
}