#moonast
The catalogue

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.

PLATE I

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

pub fn bool(Bool) -> Code #

A boolean literal.

inspect(@quote.bool(true).to_string(), content="true")
pub fn expr(StringView) -> Template #

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")
pub fn ident(StringView) -> Code raise Refused #

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")
pub fn int(Int32) -> Code #

An integer literal.

inspect(@quote.int(42).to_string(), content="42")
pub fn items(StringView) -> Template #

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")
pub fn join(Array[Code]) -> Code raise Refused #

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",
)
pub fn string(StringView) -> Code #

A string literal holding text; quoting and escaping happen when it is printed.

inspect(@quote.string("坐标").to_string(), content="\"坐标\"")
pub fn ty(StringView) -> Template #

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

pub struct Code #

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.

pub fn Code::items(Self) -> @list.List[@syntax.Impl]? #

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")
pub fn Code::output(Self, &Logger) -> Unit #

Writes the value to a logger, as <code>Show</code> defines it.

pub fn Code::to_string(Self) -> String #

The value as a string, as <code>Show</code> defines it.

pub struct Template #

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.

pub fn Template::render(Self) -> Code raise Refused #

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")
pub fn Template::set(Self, String, Code) -> Self #

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",
)
pub fn Template::splice(Self, String, Array[Code]) -> Self #

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

pub(all) suberror Refused { #

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) — ident was given something that is not an identifier.
  • Kind(expected~ : String) — join was given a code that is not declarations.
pub fn Refused::output(Self, &Logger) -> Unit #

Writes the value to a logger, as <code>Show</code> defines it.

pub fn Refused::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

pub fn Refused::to_string(Self) -> String #

The value as a string, as <code>Show</code> defines it.

PLATE II

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

pub fn all(@list.List[@attribute.Attribute], StringView) -> Array[@attribute.Attribute] #

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")
pub fn find(@list.List[@attribute.Attribute], StringView) -> @attribute.Attribute? #

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")
pub fn malformed(@list.List[@attribute.Attribute], StringView) -> Array[@attribute.Attribute] #

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")
pub fn name_of(@attribute.Attribute) -> String? #

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")
pub fn under(@list.List[@attribute.Attribute], StringView) -> Array[@attribute.Attribute] #

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

pub struct Args { #

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
pub fn Args::bool(Self, StringView) -> Bool? #

The boolean given for key.

pub fn Args::get(Self, StringView) -> Array[Value] #

The values given for key, in order; empty when it was not given.

pub fn Args::has(Self, StringView) -> Bool #

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")
pub fn Args::str(Self, StringView) -> String? #

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")
pub fn Args::strs(Self, StringView) -> Array[String] #

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")
pub(all) enum Kind { #

What a parameter accepts.

  • Str
  • Bool
  • Ident
  • Type
pub fn Kind::equal(Self, Self) -> Bool #

Whether two values are equal, as <code>Eq</code> defines it.

pub fn Kind::not_equal(Self, Self) -> Bool #

Whether two values differ, as <code>Eq</code> defines it.

pub fn Kind::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

pub struct Spec #

The parameters one annotation takes. Built by chaining:

Spec::new("app.get").positional("path", Str).optional("summary", Str).many("tags", Str)
pub fn Spec::bind(Self, @attribute.Attribute) -> Args raise Refused #

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")
pub fn Spec::many(Self, String, Kind) -> Self #

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.

pub fn Spec::new(StringView) -> Self #
pub fn Spec::optional(Self, String, Kind) -> Self #

A parameter that may be left out.

pub fn Spec::positional(Self, String, Kind, optional? : Bool) -> Self #

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.

pub fn Spec::required(Self, String, Kind) -> Self #

A required parameter given by name.

pub(all) enum Value { #

One argument, as its kind reads it.

  • Str(String)
  • Bool(Bool)
  • Ident(String)
  • Type(String)
pub fn Value::equal(Self, Self) -> Bool #

Whether two values are equal, as <code>Eq</code> defines it.

pub fn Value::not_equal(Self, Self) -> Bool #

Whether two values differ, as <code>Eq</code> defines it.

pub fn Value::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

Errors

pub(all) suberror Refused { #

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.
pub fn Refused::output(Self, &Logger) -> Unit #

Writes the value to a logger, as <code>Show</code> defines it.

pub fn Refused::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

pub fn Refused::to_string(Self) -> String #

The value as a string, as <code>Show</code> defines it.

PLATE III

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

pub fn read(String, String) -> Array[Decl] raise Refused #

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")
pub fn render(&Generator, Array[(String, String)]) -> String raise #

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

pub(all) enum Decl { #

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)
pub fn Decl::attrs(Self) -> @list.List[@attribute.Attribute] #

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")
pub fn Decl::raises(Self) -> String #

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")
pub fn Decl::raw(Self) -> @syntax.Impl #

The official syntax tree behind the view.

pub(all) struct Field { #

A field of a struct, with its type as written in the source.

  • name : String
  • ty : String
  • mutable : Bool
  • attrs : @list.List[@attribute.Attribute]
pub(all) struct Param { #

A function parameter: _ has no name, and an unannotated one no type.

  • name : String?
  • ty : String?
  • pass : Pass
pub fn Param::declared(Self) -> String? #

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?",
)
pub fn Param::passed(Self) -> String? #

A parameter as a wrapper passes it on: a, a~, a?. None when it has no name.

pub(all) enum Pass { #

How a function parameter is passed.

  • Positional
  • Labelled
  • Optional
  • Defaulted
pub fn Pass::equal(Self, Self) -> Bool #

Whether two values are equal, as <code>Eq</code> defines it.

pub fn Pass::not_equal(Self, Self) -> Bool #

Whether two values differ, as <code>Eq</code> defines it.

pub fn Pass::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

Traits

pub(open) trait Generator { #

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.

fn generate(Self, Array[Decl]) -> @quote.Code raise #

Every declaration of the inputs, in one call: the code to write for all of them.

fn name(Self) -> String #

Short and stable: it goes in the header of every file it writes.

Errors

pub(all) suberror Refused { #

Why a generator's inputs were not read.

  • Syntax(file~ : String, line~ : Int32, column~ : Int32, msg~ : String) — An input file is not MoonBit.
pub fn Refused::output(Self, &Logger) -> Unit #

Writes the value to a logger, as <code>Show</code> defines it.

pub fn Refused::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

pub fn Refused::to_string(Self) -> String #

The value as a string, as <code>Show</code> defines it.

PLATE IV

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

pub async fn run(&@gen.Generator, ArrayView[String]) -> Unit #

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

pub(all) suberror Refused { #

Why a generator run stopped.

  • Usage(msg~ : String) — The command line is not [-C <dir>] <inputs...> -o <output> [--check].
  • Stale(output~ : String) — --check found the output out of date with its inputs.
  • Generator(msg~ : String) — The generator refused what it read, in its own words.
pub fn Refused::output(Self, &Logger) -> Unit #

Writes the value to a logger, as <code>Show</code> defines it.

pub fn Refused::to_repr(Self) -> @debug.Repr #

The value as a <code>Repr</code>, as <code>Debug</code> defines it.

pub fn Refused::to_string(Self) -> String #

The value as a string, as <code>Show</code> defines it.