Concrete lambda-calculus implementation of dowdiness/loom.
Serves as the reference grammar — any other language plugs in the same
way.
Two responsibilities: grammar description and Term/DOT visualization.
// ── Grammar ───────────────────────────────────────────────────────────────────
pub let lambda_grammar : @loom.Grammar[@token.Token, @syntax.SyntaxKind, @ast.Term]
pub let lambda_grammar_no_threshold : @loom.Grammar[...] // reuse size threshold disabled
// ── High-level parsing ────────────────────────────────────────────────────────
pub fn parse(String) -> @ast.Term raise
pub fn parse_cst(String) -> (@seam.CstNode, @core.DiagnosticSet) raise @core.LexError
pub fn new_imperative_parser(String) -> @incremental.ImperativeParser[@ast.Term]
// ── Visualization ─────────────────────────────────────────────────────────────
pub fn term_to_dot(@ast.Term) -> StringFor the full signature list, see pkg.generated.mbti.
lambda_grammar is the single integration surface. Pass it to the
@loom factories to get an ImperativeParser or the
unified reactive Parser[@ast.Term]:
///|
test "grammar example: imperative parser" {
let imp = @loom.new_imperative_parser("42", lambda_grammar)
let term = imp.parse().ast
inspect(@ast.print_term(term), content="42")
}
///|
test "grammar example: reactive parser + set_source" {
let parser = @loom.new_parser("1 + 2", lambda_grammar)
parser.set_source("42")
inspect(@ast.print_term(parser.ast().read_or_abort()), content="42")
}@loom.Grammar[T, K, Ast] is the description any language provides —
spec, lex, and fold_node. The factories erase T/K internally, so
consumers only see Ast. Grammar authors never write vtable wiring
(ImperativeLanguage) by hand.
Lambda's @lexer.lex helper wraps the step lexer in a total LexResult
boundary. Invalid steps become error tokens plus structured lexer diagnostics;
strict tokenize remains available for low-level tests and batch consumers.
See @loom's Quick Start for the
full consumer-side flow, including apply_edit.
term_to_dot converts an @ast.Term to a Graphviz DOT string by
delegating to @viz.to_dot.
MoonBit requires that you own either the trait or the type to implement
it. @viz.DotNode is foreign (defined in loom/viz) and @ast.Term is
foreign (defined in ast/), so lambda cannot impl DotNode for Term
directly.
The fix: a private newtype wrapper in this package:
priv struct TermDotNode {
id : Int
term : @ast.Term
child_nodes : Array[TermDotNode]
resolution : Resolution?
}
impl @viz.DotNode for TermDotNode with ...TermDotNode is local, so the impl is legal. term_to_dot wraps and
unwraps transparently — callers always work with plain @ast.Term.
The same pattern applies whenever you need to bridge two foreign
packages. See viz/README.md in the loom
package for the DotNode trait contract.
Grammar expansion plans and CRDT exploration live in ROADMAP.md alongside this README.
@loomQuick Start — consumer-side flow includingapply_edit- Architecture overview — layer diagram and design principles
examples/json— step-based total lexing +block_reparse_specexamples/markdown— mode-aware lexing viaModeLexer