WAC language tooling for MoonBit
import {
"moonrockz/wac/parser",
"moonrockz/wac/scanner",
"moonrockz/wac/span",
"moonrockz/wac/diagnostic",
"moonrockz/wac/ast",
"moonrockz/wac/cst",
"moonrockz/wac/traversal",
"moonrockz/wac/printer",
"moonrockz/wac/formatter",
} for "test"test "Guide: Parse a WAC document" {
let source = "package example:app; import host: interface { log: func(message: string); }; let app = new example:service { host, ... }; export app.run as \"run\";"
let scanned = @scanner.scan(source)
assert_true(scanned.diagnostics.is_empty())
let parsed = @parser.parse(source)
assert_true(parsed.is_valid())
let syntax : @ast.Tree = parsed.ast
let concrete : @cst.Tree = parsed.cst
assert_true(syntax.validate() && concrete.validate())
assert_eq(concrete.to_source(), source)
let canonical = @printer.print(syntax).unwrap()
assert_true(syntax.equivalent(@parser.parse(canonical).ast))
}test "Guide: Inspect a target world and composition expression structure" {
let source =
#|package example:app targets example:types/app;
#|interface host-api { log: func(message: string); }
#|world app-world { import host: host-api; export run: func(); }
#|import host as "example:host/log@1.0.0": host-api;
#|let provider = new example:provider { host };
#|let app = new example:service { ...provider, "log": host.log, ... };
#|export (app)["run"] as "run";
let parsed = @parser.parse(source)
assert_true(parsed.is_valid())
assert_eq(parsed.cst.to_source(), source)
let kinds = parsed.ast.nodes.map(node => node.data.kind())
assert_true(kinds.contains(World))
assert_true(kinds.contains(SpreadArgument))
assert_true(kinds.contains(FillArgument))
assert_true(kinds.contains(Group))
assert_true(kinds.contains(Access))
assert_eq(kinds.filter(kind => kind == New).length(), 2)
assert_true(parsed.ast.nodes.any(node => node.data is Access(name="run", quoted=true)))
}test "Guide: Render diagnostics as editor line and column values" {
let source = "package demo:app; interface broken { type missing = ; }"
let parsed = @parser.parse(source)
let index = @span.Source::new(source)
let errors : ReadOnlyArray[@diagnostic.Diagnostic] = parsed.diagnostics
assert_false(errors.is_empty())
let locations = errors.map(diagnostic => {
let position = index.position(diagnostic.primary.span.start).unwrap()
"example.wac:\{position.line + 1}:\{position.column + 1}: " + diagnostic.code
})
assert_true(locations[0].has_prefix("example.wac:1:"))
assert_false(parsed.is_valid())
assert_eq(parsed.cst.to_source(), source)
}test "Guide: Index WAC declarations" {
let source = "package example:app; import host: interface {}; let app = new example:service { host }; export app.run;"
let parsed = @parser.parse(source)
let imports = []
let bindings = []
let expressions = []
let _ = @traversal.walk(Ast(parsed.ast), event => {
if event.phase == Enter {
match parsed.ast.nodes[event.view.index].data {
Import(binding~, ..) => imports.push(binding)
Let(name~, expression~) => {
bindings.push(name)
expressions.push(parsed.ast.nodes[expression].data.kind())
}
_ => ()
}
}
Continue
}).unwrap()
assert_eq(imports, ["host"])
assert_eq(bindings, ["app"])
assert_eq(expressions, [Expression])
}test "Guide: Change a type without mutating the parsed tree" {
let parsed = @parser.parse("package demo:app; interface api { type byte = u8; }")
let changed = @traversal.rewrite(parsed.ast, (_view, node) => {
match node.data {
PrimitiveType(name="u8") => Replace(PrimitiveType(name="u64"))
_ => Keep
}
}).unwrap()
assert_true(changed.tree.validate())
assert_true(parsed.ast.nodes.any(node => node.data == PrimitiveType(name="u8")))
assert_false(changed.tree.nodes.any(node => node.data == PrimitiveType(name="u8")))
assert_true(changed.tree.nodes.any(node => node.data == PrimitiveType(name="u64")))
let printed = @printer.print(changed.tree).unwrap()
assert_true(printed.contains("type byte = u64;"))
assert_true(changed.tree.equivalent(@parser.parse(printed).ast))
}test "Guide: Edit a token and format the result" {
let source = "// keep this comment\npackage demo:app; interface api { type byte = u8; }"
let parsed = @parser.parse(source)
let token = parsed.cst.tokens.iter().find_first(token => token.text == "u8").unwrap()
let changed = @traversal.rewrite_cst(parsed.cst, [{ span: token.span, text: "u64" }]).unwrap()
assert_true(changed.is_valid())
assert_eq(changed.cst.to_source(), source.replace(old="u8", new="u64"))
assert_eq(parsed.cst.to_source(), source)
let formatted = @formatter.format_cst(changed.cst).unwrap()
assert_true(formatted.has_prefix("// keep this comment\n"))
assert_eq(@formatter.format(formatted), Ok(formatted))
}test "Guide: Enable only the grammar extension your source needs" {
let source = "package demo:app; type callback = func() -> (value: u8);"
assert_false(@parser.parse(source).is_valid())
let features : @parser.Features = { named_results: true }
let parsed = @parser.parse(source, features~)
assert_true(parsed.is_valid())
assert_eq(parsed.cst.to_source(), source)
let formatted = @formatter.format_cst(parsed.cst, features~).unwrap()
assert_true(@parser.parse(formatted, features~).is_valid())
}test "Guide: Keep invalid input unchanged and report formatting errors" {
let source = "package demo:app; interface broken { type missing = ; } interface later {}"
match @formatter.format(source) {
Err(InvalidSyntax(diagnostics)) => assert_false(diagnostics.is_empty())
_ => fail("expected syntax diagnostics")
}
assert_eq(@parser.parse(source).cst.to_source(), source)
assert_eq(@formatter.format(source, indent_width=-1), Err(InvalidOptions))
}| Task | Guide |
|---|---|
| Editor offsets, Unicode and safe slices | span recipes |
| Diagnostic severity, recovery and related labels | diagnostic recipes |
| Highlighting, comments and malformed tokens | scanner recipes |
| Typed node fields, arenas and structural equality | ast recipes |
| Exact source and concrete subtree ranges | cst recipes |
| Recovery, feature profiles and source diagnostics | parser recipes |
| Readers, prune/stop, cursors, folds, reductions and rewrites | traversal recipes |
| Canonical source and printer errors | printer recipes |
| Comment retention, indentation and formatting errors | formatter recipes |
| Package | Responsibility |
|---|---|
| span | Source text, positions and documented Unicode offset units |
| diagnostic | Stable codes, severity, source labels and recovery details |
| scanner | Lossless tokens, comments and whitespace |
| ast | Immutable typed nodes and child collections |
| cst | Immutable lossless concrete structure, including malformed input |
| parser | Recovering source-to-AST/CST parsing with diagnostics |
| traversal | Explicit-stack walks, folds, reductions, events, cursors and immutable rewrites |
| printer | Stack-safe canonical AST-to-source emission |
| formatter | Stack-safe CST formatting that preserves comments and meaning |
fn version() -> String