API reference
Every public type, function, method, trait and error, one plate per package and
alphabetical within each group. Import one package at a time:
moonbitstack/moonast/quote, /attr, /gen,
/cli.
quotemoonbitstack/moonast/quote
Templates: real MoonBit with $holes. A template is parsed with its holes standing as placeholders, each fill is put where its placeholder stands, and the result is printed by the official formatter.
Values
A boolean literal.
inspect(@quote.bool(true).to_string(), content="true")A template of one expression.
let code = @quote.expr("$a + $b")
.set("a", @quote.int(1))
.set("b", @quote.int(2))
.render()
inspect(code.to_string(), content="1 + 2")A name: a value, a function, a field, a type or a constructor, optionally qualified as @pkg.name. Refused unless it is a well-formed identifier.
inspect(@quote.ident("answer").to_string(), content="answer")
inspect(@quote.ident("@web.App").to_string(), content="@web.App")An integer literal.
inspect(@quote.int(42).to_string(), content="42")A template of top-level declarations.
let code = @quote.items("fn $name() -> $T {\n $value\n}")
.set("name", @quote.ident("answer"))
.set("T", @quote.ident("Int"))
.set("value", @quote.int(42))
.render()
inspect(code.to_string(), content="///|\nfn answer() -> Int {\n 42\n}\n")Several codes of top-level declarations as one, in order.
let a = @quote.items("fn a() -> Unit { () }").render()
let b = @quote.items("fn b() -> Unit { () }").render()
inspect(
@quote.join([a, b]).to_string(),
content="///|\nfn a() -> Unit {\n ()\n}\n\n///|\nfn b() -> Unit {\n ()\n}\n",
)A string literal holding text; quoting and escaping happen when it is printed.
inspect(@quote.string("坐标").to_string(), content="\"坐标\"")A template of one type.
let code = @quote.ty("Array[$T]").set("T", @quote.ident("User")).render()
inspect(code.to_string(), content="Array[User]")Types
A piece of MoonBit: top-level declarations, an expression, a type, a name, or the text of a string literal. Codes come from rendering a template or from the literal constructors here, and fill the holes of other templates.
The official syntax tree behind a code of declarations, for work this package does not cover.
let code = @quote.items("fn a() -> Unit { () }").render()
inspect(code.items() is Some(_), content="true")Writes the value to a logger, as <code>Show</code> defines it.
The value as a string, as <code>Show</code> defines it.
A piece of MoonBit with holes, waiting to be filled and rendered.
A hole is $name; write it in the case the position wants, $T where a type or constructor goes and $name where a value or a name goes. $name* takes a list and expands among statements, array elements or positional arguments. $$ is a literal dollar sign.
Parse the template with its holes standing as placeholders, put each fill where its placeholder stands, and give back the result. Refused when a hole is left empty, a fill names no hole, a fill does not fit where it stands, or the template is not MoonBit.
let text = @quote.items("fn $name() -> Unit { () }").render().to_string() catch {
e => e.to_string()
}
inspect(text, content="hole $name was given nothing")Fill $hole with code.
let t = @quote.items("fn $name() -> Unit { () }").set(
"name",
@quote.ident("answer"),
)
inspect(
t.render().to_string(),
content="///|\nfn answer() -> Unit {\n ()\n}\n",
)Fill $hole* with codes, in order.
let code = @quote.expr("[$items*]")
.splice("items", [@quote.int(1), @quote.int(2), @quote.int(3)])
.render()
inspect(code.to_string(), content="[1, 2, 3]")Errors
Why a template or a code was not accepted. Each variant is named after the thing that was wrong.
- Template(msg~ : String, line~ : Int32, column~ : Int32) — The template is not MoonBit once its holes are in place.
- Unfilled(hole~ : String) — A hole in the template was given nothing.
- Unused(hole~ : String) — Something was given for a hole the template does not have.
- Misfit(hole~ : String, expected~ : String) — A hole was given a code of the wrong kind for where it stands.
- Name(name~ : String) —
identwas given something that is not an identifier. - Kind(expected~ : String) —
joinwas given a code that is not declarations.
Writes the value to a logger, as <code>Show</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
The value as a string, as <code>Show</code> defines it.
attrmoonbitstack/moonast/attr
Annotations as typed arguments. Find #lib.name(...) on a declaration and bind its payload against a spec; every refusal carries the position of the annotation.
Values
Every annotation called name, in source order.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
inspect(@attr.all(attrs, "app.get").length(), content="1")The first annotation called name.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
inspect(@attr.find(attrs, "app.get") is Some(_), content="true")The annotations starting #lib. whose payload the parser could not read, in source order. A key that is a keyword does this: #app.get(type="x"). under, find and all go by the parsed name and pass over them, so a generator asks here first and reports them, rather than ignoring what was written.
let source = "#app.get(type=\"x\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
inspect(@attr.under(attrs, "app").length(), content="0")
inspect(@attr.malformed(attrs, "app").length(), content="1")The full name of an annotation, lib.name for #lib.name(...); None when the parser could not read its payload.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
guard @attr.find(attrs, "app.get") is Some(a) else { abort("annotated") }
inspect(@attr.name_of(a).unwrap_or(""), content="app.get")Every annotation whose name starts with lib., in source order: a generator for #app.get, #app.post and the rest asks once for app.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
inspect(@attr.under(attrs, "app").length(), content="1")Types
An annotation's arguments after Spec::bind: every required one is here and every one has the kind its spec gives.
- name : String
- line : Int32
- column : Int32
The boolean given for key.
The values given for key, in order; empty when it was not given.
Whether key was given.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
guard @attr.find(attrs, "app.get") is Some(a) else { abort("annotated") }
let args = @attr.Spec::new("app.get")
.positional("path", Str)
.many("tag", Str)
.bind(a)
inspect(args.has("tag"), content="true")
inspect(args.has("page"), content="false")The string given for key.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
guard @attr.find(attrs, "app.get") is Some(a) else { abort("annotated") }
let args = @attr.Spec::new("app.get")
.positional("path", Str)
.many("tag", Str)
.bind(a)
inspect(args.str("path").unwrap_or(""), content="/users")Every string given for a repeated key.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
guard @attr.find(attrs, "app.get") is Some(a) else { abort("annotated") }
let args = @attr.Spec::new("app.get")
.positional("path", Str)
.many("tag", Str)
.bind(a)
inspect(args.strs("tag").join(", "), content="users")What a parameter accepts.
- Str
- Bool
- Ident
- Type
Whether two values are equal, as <code>Eq</code> defines it.
Whether two values differ, as <code>Eq</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
The parameters one annotation takes. Built by chaining:
Spec::new("app.get").positional("path", Str).optional("summary", Str).many("tags", Str)The annotation's arguments, checked against the spec.
let source = "#app.get(\"/users\", tag=\"users\")\nfn list_users() -> Unit {}"
let impls = @parser.parse_string(source).0
guard impls is @list.More(impl_, tail=@list.Empty) else {
abort("one declaration")
}
guard impl_ is @syntax.TopFuncDef(fun_decl={ attrs, .. }, ..) else {
abort("a function")
}
guard @attr.find(attrs, "app.get") is Some(a) else { abort("annotated") }
let spec = @attr.Spec::new("app.get").positional("path", Str).many("tag", Str)
let args = spec.bind(a)
inspect(args.str("path").unwrap_or(""), content="/users")
inspect(args.strs("tag").join(", "), content="users")A parameter that may be given any number of times, tags="a", tags="b", which is how a list is written where the grammar has no arrays.
A parameter that may be left out.
A parameter that may be given by position, in the order these are declared, or by name. It is required unless optional says otherwise, which is what lets #orm.table("heroes") and a bare #orm.table both stand.
A required parameter given by name.
One argument, as its kind reads it.
- Str(String)
- Bool(Bool)
- Ident(String)
- Type(String)
Whether two values are equal, as <code>Eq</code> defines it.
Whether two values differ, as <code>Eq</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
Errors
Why an annotation's arguments were not accepted, with where the annotation stands in its file.
- Malformed(raw~ : String, line~ : Int32, column~ : Int32) — The payload is outside the annotation grammar: arrays, numbers, a qualified or generic type cannot be written, and a keyword cannot be a label.
- Missing(key~ : String, line~ : Int32, column~ : Int32) — A required parameter was not given.
- Unknown(key~ : String, line~ : Int32, column~ : Int32) — A parameter the spec does not have.
- Mistyped(key~ : String, expected~ : String, line~ : Int32, column~ : Int32) — A parameter of the wrong kind.
- Repeated(key~ : String, line~ : Int32, column~ : Int32) — A parameter given twice that may only be given once.
Writes the value to a logger, as <code>Show</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
The value as a string, as <code>Show</code> defines it.
genmoonbitstack/moonast/gen
Declarations read from source into plain views, the Generator trait, and render, which gives the file a generator writes. No file access here.
Values
The declarations of one file, in source order. Refused when the file is not MoonBit, with the first problem's position.
let source = "pub struct Point {\n x : Int\n y : Int\n}"
guard @gen.read("models.mbt", source) is [Struct(name~, fields~, ..)] else {
abort("a struct")
}
inspect(name, content="Point")
inspect(fields.map(f => f.name).join(", "), content="x, y")The file a generator writes for inputs: a one-line header naming the generator and the inputs, then the code. The same inputs give the same bytes, so a check can compare them.
Types
A top-level declaration as a generator usually needs it. Types are kept as their source text, which is what a generator splices into new code; raw is the official syntax tree for anything the view leaves out.
- Struct(name~ : String, fields~ : Array[Field], attrs~ : @list.List[@attribute.Attribute], public~ : Bool, file~ : String, raw~ : @syntax.Impl)
- Enum(name~ : String, constructors~ : Array[String], attrs~ : @list.List[@attribute.Attribute], public~ : Bool, file~ : String, raw~ : @syntax.Impl)
- Func(owner~ : String?, name~ : String, params~ : Array[Param], returns~ : String?, is_async~ : Bool, attrs~ : @list.List[@attribute.Attribute], public~ : Bool, file~ : String, raw~ : @syntax.Impl)
- Other(file~ : String, raw~ : @syntax.Impl)
The annotations on a declaration; none for Other.
let source = "#demo.describe\npub struct Point {\n x : Int\n}"
guard @gen.read("models.mbt", source) is [decl] else {
abort("one declaration")
}
inspect(@attr.find(decl.attrs(), "demo.describe") is Some(_), content="true")What a function says after its return type, as source: empty, raise, raise E or raise? E, so a wrapper can say the same. A type with arguments cannot be an error type, so the name is all there is to print. Empty for anything that is not a function.
guard @gen.read("m.mbt", "fn f() -> Unit raise @a.Bad {}") is [decl] else {
abort("a function")
}
inspect(decl.raises(), content="raise @a.Bad")The official syntax tree behind the view.
A field of a struct, with its type as written in the source.
- name : String
- ty : String
- mutable : Bool
- attrs : @list.List[@attribute.Attribute]
A function parameter: _ has no name, and an unannotated one no type.
- name : String?
- ty : String?
- pass : Pass
A parameter as a wrapper's signature declares it: a : Int, a~ : Int, a? : Int. A parameter with a default comes out optional, since the default itself stays with the function being wrapped. None when it has no name or no type, so there is nothing to pass along.
guard @gen.read(
"m.mbt", "fn f(a : Int, b~ : String, c~ : Bool = true) -> Unit {}",
)
is [Func(params~, ..)] else {
abort("a function")
}
inspect(
params.map(p => p.declared().unwrap_or("")).join(", "),
content="a : Int, b~ : String, c? : Bool",
)
inspect(
params.map(p => p.passed().unwrap_or("")).join(", "),
content="a, b~, c?",
)A parameter as a wrapper passes it on: a, a~, a?. None when it has no name.
How a function parameter is passed.
- Positional
- Labelled
- Optional
- Defaulted
Whether two values are equal, as <code>Eq</code> defines it.
Whether two values differ, as <code>Eq</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
Traits
A generator: given every declaration of its input files, the code to write. One call sees all of them, so a derive can answer per declaration and a registrar can gather every annotated function into one table.
Every declaration of the inputs, in one call: the code to write for all of them.
Short and stable: it goes in the header of every file it writes.
Errors
Why a generator's inputs were not read.
- Syntax(file~ : String, line~ : Int32, column~ : Int32, msg~ : String) — An input file is not MoonBit.
Writes the value to a logger, as <code>Show</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
The value as a string, as <code>Show</code> defines it.
climoonbitstack/moonast/cli
The command line of a generator binary: it reads the inputs, has moonast/gen render them, and writes the output or reports that it is stale.
Values
Run generator the way a dev_build rule calls it: read every input, render them, and write the output; with --check, only compare and refuse when the output is stale. args are the arguments after the program name; -C <dir> makes relative paths relative to dir.
Errors
Why a generator run stopped.
- Usage(msg~ : String) — The command line is not
[-C <dir>] <inputs...> -o <output> [--check]. - Stale(output~ : String) —
--checkfound the output out of date with its inputs. - Generator(msg~ : String) — The generator refused what it read, in its own words.
Writes the value to a logger, as <code>Show</code> defines it.
The value as a <code>Repr</code>, as <code>Debug</code> defines it.
The value as a string, as <code>Show</code> defines it.