// atdmbt: generate MoonBit code from ATD type definitions.
//
// This runs on the native and wasm backends; the latter is what `moonx`
// uses.
///|
priv suberror Exit {
Exit(Int)
}
///|
let usage : String =
#|Usage: atdmbt [OPTIONS] FILE.atd [FILE.atd ...]
#|
#|Generate MoonBit type definitions and JSON readers and writers from
#|ATD files. For each input file 'foo.atd', the file 'foo.mbt' is created
#|in the current directory, unless an output file is specified with -o.
#|
#|The generated code requires the package "bobzhang/atd_runtime",
#|imported with the alias 'atd_runtime' in the moon.pkg file of the
#|package containing the generated code.
#|
#|Options:
#| -o write to this file (only for a single input file);
#| use '-' for stdout
#| -version print the version and exit
#| -help print this help and exit
///|
async fn run(args : ArrayView[String]) -> Unit {
let inputs = []
let mut output : String? = None
let mut i = 1
while i < args.length() {
match args[i] {
"-o" | "--output" => {
i += 1
if i >= args.length() {
@stdio.stderr.write(
"atdmbt: option '-o' needs an argument.\n" + usage + "\n",
)
raise Exit(2)
}
output = Some(args[i])
}
"-version" | "--version" => {
@stdio.stdout.write(@atd.version + "\n")
raise Exit(0)
}
"-help" | "--help" | "-h" => {
@stdio.stdout.write(usage + "\n")
raise Exit(0)
}
arg =>
if arg.has_prefix("-") {
@stdio.stderr.write(
"atdmbt: unknown option '\{arg}'.\n" + usage + "\n",
)
raise Exit(2)
} else {
inputs.push(arg)
}
}
i += 1
}
if inputs.is_empty() {
@stdio.stderr.write(usage + "\n")
raise Exit(2)
}
if output is Some(_) && inputs.length() > 1 {
@stdio.stderr.write("atdmbt: option '-o' requires a single input file.\n")
raise Exit(2)
}
// Check the destinations, then generate all the code before writing
// anything, so that an error doesn't leave some files updated.
let destinations : Map[String, String] = Map([])
for input in inputs {
let dst = match output {
Some(path) => path
None => @mbtgen.output_file_name(input)
}
match destinations.get(dst) {
Some(other) => {
@stdio.stderr.write(
"atdmbt: '\{other}' and '\{input}' would both be written to '\{dst}'.\n",
)
raise Exit(2)
}
None => destinations[dst] = input
}
}
let results = []
for dst, input in destinations {
let src = @fs.read_file(input).binary()
let basename = match input.rev_find("/") {
Some(i) => input.view(start_offset=i + 1).to_owned()
None => input
}
results.push(
(dst, @mbtgen.generate(src, atd_filename=basename, pos_fname=input)),
)
}
for r in results {
let (dst, code) = r
if dst == "-" {
@stdio.stdout.write(code)
} else {
@fs.write_file(dst, code)
}
}
}
///|
async fn main {
run(@env.args()) catch {
Exit(code) => @sys.exit(code)
@atd.AtdError(msg) => {
@stdio.stderr.write(msg + "\n")
@sys.exit(1)
}
e => {
@stdio.stderr.write("atdmbt: \{e}\n")
@sys.exit(2)
}
}
}