// Copyright (C) 2025 International Digital Economy Academy
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see .
///|
async fn println_to_stderr(msg : String) -> Unit noraise {
@stdio.stderr.write(msg + "\n") catch {
_ => ()
}
}
///|
#cfg(target="wasm")
fn runtime_exit(code : Int) -> Unit = "wasi_snapshot_preview1" "proc_exit"
///|
#cfg(target="native")
extern "c" fn runtime_exit(code : Int) -> Unit = "exit"
///|
fn[T] exit(code : Int) -> T {
runtime_exit(code)
panic()
}
///|
async fn write_text_file(file : String, content : String) -> Unit {
@fs.write_file(file, content, create_mode=CreateOrTruncate)
}
///|
priv suberror CliError {
UnsupportedMode(String)
UnsupportedInputMode(String)
}
///|
priv struct CliOptions {
input_file : String
output_file : String
output_map_file : String?
mode : @driver.Mode
input_mode : @driver.InputMode
external_tokens : Bool
no_comments : Bool
no_std : Bool
table : Bool
compress : Bool
token_payload_rewrite : @driver.TokenPayloadRewrite
force_int_position : Bool
print_as_mly_without_actions : Bool
}
///|
fn cli_command() -> @argparse.Command {
Command(
"moonyacc",
version="moonyacc 0.1.0",
disable_version_flag=true,
flags=[
FlagArg(
"external-tokens",
long="external-tokens",
about="Use external tokens",
),
FlagArg(
"no-comments",
long="no-comments",
about="Do not include comments in the output",
),
FlagArg("no-std", long="no-std", about="Do not include standard library"),
FlagArg("table", long="table", about="Use table engine"),
FlagArg(
"compress-table",
long="compress-table",
about="Use table engine with compression",
),
FlagArg(
"version",
short='v',
long="version",
action=Version,
about="Show version",
),
FlagArg(
"print-as-mly-without-actions",
long="print-as-mly-without-actions",
about="Print as mly without actions",
),
FlagArg(
"force-token-json-payload",
long="force-token-json-payload",
about="Force token to use JSON payload",
),
FlagArg(
"force-token-no-payload",
long="force-token-no-payload",
about="Force token to not use payload",
),
FlagArg(
"force-int-position",
long="force-int-position",
about="Overwrite the position type to int",
),
],
options=[
OptionArg(
"output-file",
short='o',
long="output-file",
about="Output file",
),
OptionArg(
"output-map-file",
long="output-map-file",
about="Output source map file, if not specified, it will be the output file with .map.json extension",
),
OptionArg(
"mode",
long="mode",
about="Specify mode (default, json-cst, only-tokens)",
),
OptionArg(
"input-mode",
long="input-mode",
about="Specify input mode (array, pull). Default is array",
),
],
positionals=[
PositionArg("input-file", num_args=@argparse.ValueRange::single()),
],
)
}
///|
fn string_value(
matches : @argparse.Matches,
name : String,
default : String,
) -> String {
match matches.values.get(name) {
Some(values) => values[0]
None => default
}
}
///|
fn string_option(matches : @argparse.Matches, name : String) -> String? {
match matches.values.get(name) {
Some(values) => Some(values[0])
None => None
}
}
///|
fn flag_value(matches : @argparse.Matches, name : String) -> Bool {
matches.flags.get(name).unwrap_or(false)
}
///|
fn parse_mode(mode_sym : String) -> @driver.Mode raise CliError {
match mode_sym {
"default" => Default
"json-cst" => JsonCst
"only-tokens" => OnlyTokens
_ => raise UnsupportedMode(mode_sym)
}
}
///|
fn parse_input_mode(
input_mode_sym : String,
) -> @driver.InputMode raise CliError {
match input_mode_sym {
"array" => Array
"pull" => Pull
_ => raise UnsupportedInputMode(input_mode_sym)
}
}
///|
fn parse_token_payload_rewrite(
argv : ArrayView[String],
) -> @driver.TokenPayloadRewrite {
let mut rewrite : @driver.TokenPayloadRewrite = NoRewrite
for arg in argv {
match arg {
"--force-token-json-payload" => rewrite = JsonPayload
"--force-token-no-payload" => rewrite = NoPayload
_ => ()
}
}
rewrite
}
///|
fn parse_cli_args(argv : ArrayView[String]) -> CliOptions raise {
let matches = cli_command().parse(argv~, env={})
let compress = flag_value(matches, "compress-table")
{
input_file: string_value(matches, "input-file", ""),
output_file: string_value(matches, "output-file", ""),
output_map_file: string_option(matches, "output-map-file"),
mode: parse_mode(string_value(matches, "mode", "default")),
input_mode: parse_input_mode(string_value(matches, "input-mode", "array")),
external_tokens: flag_value(matches, "external-tokens"),
no_comments: flag_value(matches, "no-comments"),
no_std: flag_value(matches, "no-std"),
table: compress || flag_value(matches, "table"),
compress,
token_payload_rewrite: parse_token_payload_rewrite(argv),
force_int_position: flag_value(matches, "force-int-position"),
print_as_mly_without_actions: flag_value(
matches, "print-as-mly-without-actions",
),
}
}
///|
async fn main {
let argv = @env.args()
let options = parse_cli_args(if argv.length() > 1 { argv[1:] } else { [] }) catch {
CliError::UnsupportedMode(mode_sym) => {
println_to_stderr("Unsupported mode: \{mode_sym}")
exit(1)
}
CliError::UnsupportedInputMode(input_mode_sym) => {
println_to_stderr("Unsupported input mode: \{input_mode_sym}")
exit(1)
}
err => {
println_to_stderr(err.to_string())
exit(1)
}
}
let parser_spec_src = @fs.read_file(options.input_file).text() catch {
err => abort(Show::to_string(err))
}
if options.print_as_mly_without_actions {
let out = StringBuilder::new()
@driver.print(parser_spec_src, filename=options.input_file, out~)
exit(0)
}
let source_map = @codegen.SourceMap::new()
let output = @driver.compile(
parser_spec_src,
mode=options.mode,
source_map_builder=source_map,
input_mode=options.input_mode,
filename=options.input_file,
external_tokens=options.external_tokens,
no_comments=options.no_comments,
no_std=options.no_std,
token_payload_rewrite=options.token_payload_rewrite,
force_int_position=options.force_int_position,
generator=if options.table {
if options.compress {
@gen_mbt_table.compress_generator
} else {
@gen_mbt_table.generator
}
} else {
@gen_mbt.generator
},
)
if options.output_file == "" {
@stdio.stdout.write(output + "\n")
match options.output_map_file {
Some(file) =>
try! write_text_file(file, source_map.to_json().stringify(indent=2))
None => ()
}
} else {
try! ({
write_text_file(options.output_file, output)
write_text_file(
options.output_map_file.unwrap_or("\{options.output_file}.map.json"),
source_map.to_json().stringify(indent=2),
)
})
}
}