///|
/// The explicit inputs and output target for one generator run.
pub(all) struct GenerationPlan {
input_paths : Array[String]
output_path : String
}
///|
/// A successfully parsed MoonBit source input.
pub(all) struct ParsedSource {
path : String
ast : @syntax.Impls
}
///|
/// Callback-level failure reported by a downstream generator.
pub(all) suberror GenerationFailure {
GenerationFailure(String)
} derive(Debug)
///|
/// Runner-level failure caused by an input source that cannot be parsed.
pub(all) suberror ParseFailure {
ParseFailure(String)
} derive(Debug)
///|
pub fn GenerationFailure::message(self : GenerationFailure) -> String {
match self {
GenerationFailure(message) => message
}
}
///|
pub fn ParseFailure::message(self : ParseFailure) -> String {
match self {
ParseFailure(message) => message
}
}
///|
/// A downstream generator callback that emits into the runner-owned builder.
pub type GenerationCallback = (Array[ParsedSource], StringBuilder) -> Unit raise GenerationFailure
///|
/// Run generation and write the emitted source to the explicit output target.
pub fn generate(
plan : GenerationPlan,
callback : GenerationCallback,
) -> Unit raise {
let generated = generate_to_string(plan, callback)
create_parent_dirs(@path.Path(plan.output_path).dirname().to_string())
@fs.write_string_to_file(plan.output_path, generated)
}
///|
/// Parse the input sources and invoke the callback with a runner-owned builder.
pub fn generate_to_string(
plan : GenerationPlan,
callback : GenerationCallback,
) -> String raise {
if plan.input_paths.is_empty() {
raise ParseFailure::ParseFailure("expected at least one input source")
}
let sources = []
for path in plan.input_paths {
sources.push(parse_source(path))
}
let out = StringBuilder::new()
callback(sources, out)
out.to_string()
}
///|
fn parse_source(path : String) -> ParsedSource raise ParseFailure {
let (ast, reports) = @parser.parse_file(path) catch {
_ =>
raise ParseFailure::ParseFailure("failed to read input source: \{path}")
}
if !reports.is_empty() {
raise ParseFailure::ParseFailure(format_parse_reports(reports))
}
ParsedSource::{ path, ast }
}
///|
fn format_parse_reports(reports : Array[@basic.Report]) -> String {
reports.map(report => report.msg).join("\n")
}
///|
fn create_parent_dirs(path : String) -> Unit raise @fs.IOError {
if path == "" || @fs.path_exists(path) {
return
}
let parent = @path.Path(path).dirname().to_string()
if parent != path {
create_parent_dirs(parent)
}
@fs.create_dir(path)
}