///|
priv struct GeneratedFile {
name : String
content : String
}
///|
fn generated_header(kind : String) -> String {
"// Generated by nanocake generate (\{kind}). DO NOT EDIT.\n\n"
}
///|
fn generated_source(header : String, body : String) -> String {
let clean = body
.split("\n")
.map(line => line.trim_end().to_owned())
.join("\n")
.trim_end()
.to_owned()
header + clean + "\n"
}
///|
fn generated_package() -> String {
let text =
#|import {
#| "YumeXi/nanocake/pass",
#| "YumeXi/nanocake/unparser/sexp",
#|}
#|
#|formatter(ignore: [ "ast.mbt", "codec.mbt", "pass.mbt" ])
#|
#|warnings = "-unused_value-unused_type_declaration-unused_constructor-unused_field-missing_priv-struct_never_constructed-unreachable_code"
#|
text
}
///|
fn build_generated_files(
options : GenerateOptions,
) -> Array[GeneratedFile] raise {
let language = @language.Language::from_spec(
name=options.language,
path=options.spec,
mod=options.module_name,
pkg=options.pkg,
)
let ast = generated_source(
generated_header("typed AST"),
@fmt.impls_to_string(language.gen()),
)
let codec = generated_source(
generated_header("S-expression codecs"),
@gen.gen_source(language),
)
let passes = generated_source(
generated_header("pass APIs"),
@fmt.impls_to_string(@pass.gen_surface(language)) +
@fmt.impls_to_string(@pass.gen_semantics(language)) +
@fmt.impls_to_string(@pass.gen_rewrite(language)),
)
[
{ name: "ast.mbt", content: ast },
{ name: "codec.mbt", content: codec },
{ name: "pass.mbt", content: passes },
{ name: "moon.pkg", content: generated_package() },
]
}
///|
fn join_path(dir : String, name : String) -> String {
if dir.has_suffix("/") {
dir + name
} else {
dir + "/" + name
}
}
///|
fn ensure_dir(path : String) -> Unit raise {
if @fs.path_exists(path) {
return
}
match path.rev_find("/") {
Some(index) => {
let parent = path[:index].to_owned()
if parent != "" {
ensure_dir(parent)
}
}
None => ()
}
@fs.create_dir(path)
}
///|
fn file_matches(path : String, expected : String) -> Bool raise {
@fs.path_exists(path) &&
normalize_newlines(@fs.read_file_to_string(path)) ==
normalize_newlines(expected)
}
///|
fn normalize_newlines(text : String) -> String {
text.replace_all(old="\r\n", new="\n")
}
///|
fn write_generated_files(options : GenerateOptions) -> Array[String] raise {
let files = build_generated_files(options)
let changed : Array[String] = []
if !options.check {
ensure_dir(options.out_dir)
}
for file in files {
let path = join_path(options.out_dir, file.name)
if !file_matches(path, file.content) {
changed.push(path)
if !options.check {
@fs.write_string_to_file(path, file.content)
}
}
}
changed
}