///|
priv suberror CliError {
Io(String)
Parse(@moonmon.MonError, String)
Preprocess(@moonmon.MonPreprocessError, String)
Write(@moonmon.MonWriteError)
RonParse(@ron.RonError, String)
Convert(@mon_ron.MonConvertError)
Usage(String)
}
///|
fn CliError::to_string(self : CliError) -> String {
match self {
Io(message) => "io error: " + message
Parse(error, source) => @moonmon.render_error(source, error)
Preprocess(error, source) => @moonmon.render_preprocess_error(source, error)
Write(error) => @moonmon.render_write_error(error)
RonParse(error, source) => @ron.render_error(source, error)
Convert(error) => "error: " + @mon_ron.MonConvertError::message(error)
Usage(message) => "usage error: " + message
}
}
///|
fn usage() -> String {
(
#|Usage:
#| mon check parse a MON document and report its shape
#| mon compact print the document in canonical compact form
#| mon format print the document with multiline formatting
#| mon expand print the document with its directives expanded
#| mon gen [name [type]]
#| print MoonBit source that holds the document
#| mon to-ron convert the document to RON
#| mon from-ron convert a RON document to MON
#| mon version print the module version
)
}
///|
/// Convert a MON document into RON, with its directives expanded first.
fn to_ron_source(path : String, source : StringView) -> String raise CliError {
let expanded = expand_source(path, source)
let value = @moonmon.parse(expanded) catch {
error => raise Parse(error, expanded)
}
let converted = @mon_ron.to_ron(value) catch { error => raise Convert(error) }
@ron.to_string(converted)
}
///|
/// Convert a RON document into MON.
fn from_ron_source(source : StringView) -> String raise CliError {
let value = @ron.parse(source) catch {
error => raise RonParse(error, source.to_owned())
}
let converted = @mon_ron.from_ron(value) catch {
error => raise Convert(error)
}
@moonmon.to_string(converted) catch {
error => raise Write(error)
}
}
///|
/// Read a file, or `None` when it cannot be read.
fn read_file(path : String) -> String? {
Some(@fs.read_file_to_string(path)) catch {
@fs.IOError(_) => None
}
}
///|
/// Read a document, reporting a failed read as a CLI error.
fn read_document(path : String) -> String raise CliError {
match read_file(path) {
Some(text) => text
None => raise Io("cannot read " + path)
}
}
///|
/// The directory part of a path, used to resolve `#include` paths.
fn base_directory(path : String) -> String {
let mut end = -1
for i = 0; i < path.length(); i = i + 1 {
match path.get_char(i) {
Some(char) => if char == '/' || char == '\\' { end = i }
None => ()
}
}
if end < 0 {
""
} else {
path.unsafe_substring(start=0, end=end + 1)
}
}
///|
/// Resolve an include path against the directory of the root document.
fn resolve_path(base : String, path : String) -> String {
match path.get_char(0) {
Some('/') | Some('\\') => path
_ =>
match path.get_char(1) {
Some(':') => path
_ => base + path
}
}
}
///|
/// The variables and documents of the running environment.
fn environment_for(path : String) -> @moonmon.MonEnvironment {
let base = base_directory(path)
{
lookup: fn(name) { @env.get_env_var(name.to_owned()) },
read: fn(target) { read_file(resolve_path(base, target.to_owned())) },
}
}
///|
/// Parse one document and describe the value it holds.
fn parse_source(path : String, source : StringView) -> String raise CliError {
let value = @moonmon.parse(source) catch {
error => raise Parse(error, source.to_owned())
}
"ok: " + path + " -> " + describe(value)
}
///|
fn expand_source(path : String, source : StringView) -> String raise CliError {
@moonmon.preprocess(source, environment=environment_for(path)) catch {
error => raise Preprocess(error, source.to_owned())
}
}
///|
/// Print MoonBit source for a document, with its directives expanded first.
fn generate_source(
path : String,
source : StringView,
name : String,
type_name : String,
) -> String raise CliError {
let expanded = expand_source(path, source)
let value = @moonmon.parse(expanded) catch {
error => raise Parse(error, expanded)
}
@moonmon.to_moonbit_source(value, name~, type_name~) catch {
error => raise Write(error)
}
}
///|
fn compact_source(source : StringView) -> String raise CliError {
let value = @moonmon.parse(source) catch {
error => raise Parse(error, source.to_owned())
}
@moonmon.to_string(value) catch {
error => raise Write(error)
}
}
///|
fn format_source(source : StringView) -> String raise CliError {
let value = @moonmon.parse(source) catch {
error => raise Parse(error, source.to_owned())
}
@moonmon.to_string_pretty(value) catch {
error => raise Write(error)
}
}
///|
fn describe(value : @moonmon.MonValue) -> String {
match value {
Sequence(values) => "sequence of " + values.length().to_string()
Tuple(values) => "tuple of " + values.length().to_string()
Map(entries) => "map of " + entries.length().to_string()
Fields(fields) => "fields of " + fields.length().to_string()
Record(name, fields) =>
"record " + name + " with " + fields.length().to_string() + " fields"
TupleVariant(name, values) =>
"variant " + name + " with " + values.length().to_string() + " arguments"
Variant(name) => "variant " + name
Range(_) => "range"
_ => value.kind()
}
}
///|
fn run(args : Array[String]) -> Unit raise CliError {
match args {
[] => println(usage())
["--help"] | ["help"] => println(usage())
["version"] => println("moon-mon 0.1.0")
["check", path] => println(parse_source(path, read_document(path)))
["compact", path] => println(compact_source(read_document(path)))
["format", path] => println(format_source(read_document(path)))
["expand", path] => println(expand_source(path, read_document(path)))
["gen", path] =>
println(generate_source(path, read_document(path), "value", ""))
["gen", path, name] =>
println(generate_source(path, read_document(path), name, ""))
["gen", path, name, type_name] =>
println(generate_source(path, read_document(path), name, type_name))
["to-ron", path] => println(to_ron_source(path, read_document(path)))
["from-ron", path] => println(from_ron_source(read_document(path)))
_ => raise Usage("expected a command and one file, and a name for `gen`")
}
}
///|
fn main {
let raw_args = @env.args()
let args : Array[String] = if raw_args.length() == 0 {
[]
} else {
raw_args[1:].to_owned()
}
run(args) catch {
error => {
eprintln(error.to_string())
@sys.exit(1)
}
}
}