// The wap front end: source in, Wax AST or wasm out.

///|
/// Everything that can be wrong with a wap program.
///
/// wap's own reports and Wax's are kept apart because they are different types
/// with different renderers, not because a caller is expected to care which
/// stage found the problem.
pub(all) struct Diagnostics {
  wap : Array[@er.Report]
  wax : Array[@diagnostic.Diagnostic]
}

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

///|
/// A parsed and lowered module, before Wax has checked it.
pub struct Module {
  fields : @ast.LocModule
  sources : @er.Sources
  diagnostics : Array[@er.Report]
}

///|
/// The Wax module fields.
///
/// This is the seam an embedder uses: build these from wap source, add fields
/// built with `marianoguerra/wax/ast/build`, and compile the lot. No stage of
/// that produces or consumes source text.
pub fn Module::fields(self : Self) -> @ast.LocModule {
  self.fields
}

///|
/// The source registry the reports point into.
pub fn Module::sources(self : Self) -> @er.Sources {
  self.sources
}

///|
/// Problems found while parsing and lowering.
pub fn Module::diagnostics(self : Self) -> Array[@er.Report] {
  self.diagnostics
}

///|
/// Parse and lower wap source to the Wax AST.
///
/// A parse error is fatal and comes back as a one-element error list, because
/// there is no tree to lower. A lowering error is not: the module comes back
/// with whatever could be lowered, so one bad line does not hide the rest.
pub fn to_wax(
  src : String,
  fname? : String = "input.wap",
) -> Result[Module, Array[@er.Report]] {
  let parsed = @parse.parse_module(src, fname~) catch {
    @parse.ParseError(r) => return Err([r])
  }
  let lowered = @lower.lower_module(
    parsed.module_(),
    text=src,
    fname~,
    src=parsed.source(),
  )
  Ok({
    fields: lowered.fields(),
    sources: parsed.sources(),
    diagnostics: lowered.diagnostics(),
  })
}

///|
/// Compile wap source to wasm.
pub fn compile_string(
  src : String,
  fname? : String = "input.wap",
  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 wap source to the WebAssembly text format.
pub fn compile_string_to_wat(
  src : String,
  fname? : String = "input.wap",
  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())
  }
}

///|
/// Parse, lower and type check, stopping before the emitters.
pub fn check_string(
  src : String,
  fname? : String = "input.wap",
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[(@compile.Checked, Module), Diagnostics] {
  let m = match to_wax(src, fname~) {
    Err(rs) => return Err({ wap: rs, wax: [], })
    Ok(m) => m
  }
  if m.diagnostics.length() > 0 {
    return Err({ wap: 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({ wap: [], wax: reports, })
  } else {
    Ok((checked, m))
  }
}

///|
/// Parse and lower a whole program: the entry module and everything it
/// imports, found through the loader.
///
/// The loader is the only thing that knows where source lives, which is why
/// this works when there are no files -- an editor's buffers, a generated map,
/// a zip. `@resolve.MapLoader` is the one for sources already in memory.
pub fn program_to_wax(
  path : String,
  src : String,
  loader : &@resolve.Loader,
) -> Result[Module, Array[@er.Report]] {
  let program = @resolve.resolve_source(path, src, loader) catch {
    @resolve.ResolveError(r) => return Err([r])
  }
  let lowered = @lower.lower_program(program.units())
  Ok({
    fields: lowered.fields(),
    sources: program.sources(),
    diagnostics: lowered.diagnostics(),
  })
}

///|
/// Compile a whole program to wasm.
pub fn compile_program(
  path : String,
  src : String,
  loader : &@resolve.Loader,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[Bytes, Diagnostics] raise @compile.CompileError {
  match check_program(path, src, loader, features?, policy?) {
    Err(d) => Err(d)
    Ok((checked, _)) => Ok(checked.to_bytes())
  }
}

///|
/// Compile a whole program to the WebAssembly text format.
pub fn compile_program_to_wat(
  path : String,
  src : String,
  loader : &@resolve.Loader,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[String, Diagnostics] raise @compile.CompileError {
  match check_program(path, src, loader, features?, policy?) {
    Err(d) => Err(d)
    Ok((checked, _)) => Ok(checked.to_wat())
  }
}

///|
/// Resolve, lower and type check a program, stopping before the emitters.
pub fn check_program(
  path : String,
  src : String,
  loader : &@resolve.Loader,
  features? : @feature.Set,
  policy? : @warning.Policy,
) -> Result[(@compile.Checked, Module), Diagnostics] {
  let m = match program_to_wax(path, src, loader) {
    Err(rs) => return Err({ wap: rs, wax: [], })
    Ok(m) => m
  }
  if m.diagnostics.length() > 0 {
    return Err({ wap: 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({ wap: [], wax: reports, })
  } else {
    Ok((checked, m))
  }
}