dslx

DSL generation meta-framework for MoonBit

dsl
parser
codegen
moonbit
moon add f4ah6o/dslx@0.1.2
Download zip
Author
Version
0.1.2
License
Apache-2.0
Last updated
2 months ago
Downloads
42

Dependencies

README

#DSLX

dslx.mbt is a MoonBit meta-framework for describing DSLs with one compact builder surface. It keeps public API names to four characters so coding agents can generate calls consistently.

///|
test {
let spec = @dslx.dslx("sql")
.node(
@dslx.node("Expr").case(
@dslx.case("Lit").fldx(@dslx.fldx("text", "String")),
),
)
.rule(
@dslx.rule("expr").body(@dslx.altx([@dslx.tokn("name"), @dslx.litx("*")])),
)
.rule(
@dslx.rule("decl").body(
@dslx.seqx([@dslx.litx("let"), @dslx.refx("expr")]),
),
)
inspect(@dslx.name(spec), content="sql")
inspect(@dslx.mods(@dslx.emit(spec)).length(), content="4")
let parsed = @dslx.runx(spec, "decl", "let user")
inspect(parsed.done, content="true")
inspect(parsed.posx, content="8")
match parsed.tree {
Some(tree) => inspect(tree.kids[1].kind, content="refx:expr")
None => inspect("none", content="tree")
}
}

MoonBit uses test as syntax, so the test DSL entrypoint is exported as tstx.

Proj is the small project layer for grouping related specs, examples, and extra virtual files. pack is deterministic and only returns virtual files; it never writes to disk.

///|
test {
let prj = @dslx.proj("family")
.file(@dslx.file("README.md", "# family\n"))
.exmp(@dslx.file("examples/fwd.dsl", "schema User id:int\n"))
.dslx(@dslx.fwds())
.dslx(@dslx.wflw())
let files = @dslx.pack(prj)
inspect(files[0].path, content="README.md")
inspect(files[1].path, content="examples/fwd.dsl")
inspect(files[2].path, content="Fwd/astx.mbt")
inspect(@dslx.vprj(prj).length(), content="0")
}

fspc, runp, and chex make the project layer useful without unpacking the dslx array manually. runp routes parsing through the named spec and rule. chex checks an example file and returns parser diagnostics only when it fails.

///|
test {
let file = @dslx.file("examples/fwd.dsl", "schema User id:int\n")
let prj = @dslx.proj("family").exmp(file).dslx(@dslx.fwds())
inspect(@dslx.fspc(prj, "Fwd") is Some(_), content="true")
inspect(@dslx.runp(prj, "Fwd", "sche", file.text).done, content="true")
inspect(@dslx.chex(prj, "Fwd", "sche", file).length(), content="0")
inspect(
@dslx.runp(prj, "Missing", "sche", file.text).digs[0].text,
content="unknown project spec",
)
}

#Public API

The main objects are Dslx, Spec, Proj, Node, Rule, Tree, and Diag. The public traits are Pars, Vald, Prnt, and Emit.

The builder surface includes:

  • core: dslx, spec, proj, name
  • AST: node, case, fldx, tree, span
  • grammar: rule, body, seqx, altx, many, optx, litx, tokn, rgxx, refx
  • Pratt parser: prat, pref, infx, post, atom
  • parser: pars, runx, runp
  • validation and diagnostics: vald, vspc, chec, warn, fail, hint, diag
  • printing: prnt, grup, line, join, nest, brkx
  • codegen: emit, file, mods, path
  • project: pack, vprj, fspc, chex, methods dslx, exmp, file
  • examples: fwds, wflw, domn, n8nx, rout, parm, bind, sqlx, slct, from, wher, ordr, limt, html, elem, attr, text, chid, mach, stat, tran, evnt, method init, tstx, givn, when

The code generator returns virtual files only. It does not write to disk.

#Core v1 Contracts

Pout is the parser result. done is true only when parsing succeeded and all non-whitespace input was consumed, tree contains the parse tree on success, posx is the final byte offset after trailing whitespace is skipped, and digs contains diagnostics.

pars(expr, text) parses a single expression tree. It does not resolve refx, so named rule references should be executed through runx(spec, rule, text). pars is strict: it skips whitespace before tokens and expressions, accepts trailing whitespace, and fails if any non-whitespace input remains after a successful expression parse. Leftover input returns done=false, tree=None, and a fail("unexpected input") diagnostic whose span is span("", leftover_pos, leftover_pos).

runx looks up the start rule by name, then resolves refx("name") against the spec rules. It uses the same strict final-consumption rule as pars. Missing start rules or missing references produce fail diagnostics with spans. Successful refx parses keep a wrapper tree named refx:<name> around the referenced rule tree.

Generated pars.mbt rule functions rebuild a private generated spec and call runx(generated_spec(), "<rule>", text). This means generated parser entrypoints resolve refx exactly like hand-written runx calls while still returning virtual files only.

rgxx intentionally supports only explicit scanner patterns in Core v1: [0-9]+ and [a-zA-Z_][a-zA-Z0-9_]*. Unsupported patterns fail validation and fail parsing instead of being treated as literal prefixes.

Parse diagnostics include span information for literal, token, rgxx, refx, start-rule, and refx cycle failures. When there is no wider source range, the parser uses a zero-width span at the current byte offset.

Parser combinator diagnostics are conservative: altx discards failed earlier branches once a later branch succeeds, many stops on the first failed child parse without surfacing that child diagnostic, and optx succeeds with an empty tree when its child fails.

vspc validates empty names, generated-name readiness, duplicate node/case/field/rule names, undefined refx, unsupported rgxx, and empty seqx/altx. Generated-name readiness rejects spec, node, case, field, and rule names that are not MoonBit identifiers: the first byte must be an ASCII letter or _, later bytes must be ASCII letters, digits, or _, and reserved words are not accepted. Diagnostics remain Diag values with kind set to fail or warn plus short hints where useful.

pack(proj) keeps file order stable: explicit project files, example files, then generated files for each Dslx in insertion order. Generated files are namespaced as <SpecName>/astx.mbt, <SpecName>/pars.mbt, <SpecName>/vald.mbt, and <SpecName>/prnt.mbt, which lets multiple DSL specs coexist in one virtual project package. vprj combines spec validation with duplicate spec-name and duplicate virtual-path diagnostics.

fspc(proj, name) returns the matching Dslx by spec name. runp(proj, spec, rule, text) is the project-level parser entrypoint; it returns the same Pout as runx when the spec exists and returns fail("unknown project spec") when it does not. chex(proj, spec, rule, file) parses file.text through runp and returns an empty diagnostic array for successful examples.

#
Emit

pub trait Emit {
fn emit(Self) -> Array[File]
}

#
Pars

pub trait Pars {
fn pars(Self, String) -> Pout
}

#
Prnt

pub trait Prnt {
fn prnt(Self) -> Docx
}

#
Vald

pub trait Vald {
fn vald(Self, Tree) -> Array[Diag]
}

#
Case

pub(all) struct Case {
name : String
flds : Array[Fldx]
} derive(Eq,
Debug
)

#
Case::fldx

fn Case::fldx(self : Case, fld : Fldx) -> Case

#
Diag

pub(all) struct Diag {
kind : String
text : String
span : Span?
hint : String?
} derive(Eq,
Debug
)

#
Diag::hint

fn Diag::hint(self : Diag, hint : String) -> Diag

#
Diag::span

fn Diag::span(self : Diag, span : Span) -> Diag

#
Docx

pub(all) enum Docx {
Text(String)
Line
Join(Array[Docx])
Grup(Docx)
Nest(Int, Docx)
Brkx
} derive(Eq,
Debug
)

#
Dslx

pub(all) struct Dslx {
spec : Spec
} derive(Eq,
Debug
)

#
Dslx::diag

fn Dslx::diag(self : Dslx, item : Diag) -> Dslx

#
Dslx::emit

fn Dslx::emit(self : Dslx) -> Emix

#
Dslx::node

fn Dslx::node(self : Dslx, item : Node) -> Dslx

#
Dslx::rule

fn Dslx::rule(self : Dslx, item : Rule) -> Dslx

#
Emix

pub(all) struct Emix {
spec : Spec
mods : Array[File]
} derive(Eq,
Debug
)

#
Expr

pub(all) enum Expr {
Seqx(Array[Expr])
Altx(Array[Expr])
Many(Expr)
Optx(Expr)
Litx(String)
Tokn(String)
Rgxx(String)
Refx(String)
Atom
} derive(Eq,
Debug
)

#
File

pub(all) struct File {
path : String
text : String
} derive(Eq,
Debug
)

#
Fldx

pub(all) struct Fldx {
name : String
typx : String
} derive(Eq,
Debug
)

#
Html

pub(all) struct Html {
name : String
atrs : Array[(String, String)]
text : String?
chid : Array[Html]
} derive(Eq,
Debug
)

#
Html::attr

fn Html::attr(self : Html, name : String, value : String) -> Html

#
Html::chid

fn Html::chid(self : Html, item : Html) -> Html

#
Html::elem

fn Html::elem(self : Html, name : String) -> Html

#
Html::text

fn Html::text(self : Html, value : String) -> Html

#
Mach

pub(all) struct Mach {
stat : Array[String]
tran : Array[String]
evnt : Array[String]
init : String?
} derive(Eq,
Debug
)

#
Mach::evnt

fn Mach::evnt(self : Mach, name : String) -> Mach

#
Mach::init

fn Mach::init(self : Mach, name : String) -> Mach

#
Mach::stat

fn Mach::stat(self : Mach, name : String) -> Mach

#
Mach::tran

fn Mach::tran(self : Mach, name : String) -> Mach

#
Node

pub(all) struct Node {
name : String
casx : Array[Case]
} derive(Eq,
Debug
)

#
Node::case

fn Node::case(self : Node, item : Case) -> Node

#
Oper

pub(all) struct Oper {
kind : String
text : String
prec : Int
} derive(Eq,
Debug
)

#
Pout

pub(all) struct Pout {
done : Bool
tree : Tree?
posx : Int
digs : Array[Diag]
} derive(Eq,
Debug
)

#
Prat

pub(all) struct Prat {
opsx : Array[Oper]
atom : Expr
} derive(Eq,
Debug
)

#
Prat::atom

fn Prat::atom(self : Prat, expr : Expr) -> Prat

#
Prat::infx

fn Prat::infx(self : Prat, text : String, prec : Int) -> Prat

#
Prat::post

fn Prat::post(self : Prat, text : String, prec : Int) -> Prat

#
Prat::pref

fn Prat::pref(self : Prat, text : String, prec : Int) -> Prat

#
Proj

pub(all) struct Proj {
name : String
dslx : Array[Dslx]
exmp : Array[File]
mods : Array[File]
} derive(Eq,
Debug
)

#
Proj::dslx

fn Proj::dslx(self : Proj, item : Dslx) -> Proj

#
Proj::exmp

fn Proj::exmp(self : Proj, item : File) -> Proj

#
Proj::file

fn Proj::file(self : Proj, item : File) -> Proj

#
Rout

pub(all) struct Rout {
segs : Array[String]
} derive(Eq,
Debug
)

#
Rout::bind

fn Rout::bind(self : Rout, name : String) -> Rout

#
Rout::litx

fn Rout::litx(self : Rout, text : String) -> Rout

#
Rout::parm

fn Rout::parm(self : Rout, name : String) -> Rout

#
Rule

pub(all) struct Rule {
name : String
expr : Expr
} derive(Eq,
Debug
)

#
Rule::body

fn Rule::body(self : Rule, expr : Expr) -> Rule

#
Span

pub(all) struct Span {
file : String
from : Int
upto : Int
} derive(Eq,
Debug
)

#
Spec

pub(all) struct Spec {
name : String
nods : Array[Node]
ruls : Array[Rule]
digs : Array[Diag]
} derive(Eq,
Debug
)

#
Sqlx

pub(all) struct Sqlx {
cols : Array[String]
from : Array[String]
wher : String?
join : Array[String]
grup : Array[String]
ordr : Array[String]
limt : Int?
} derive(Eq,
Debug
)

#
Sqlx::from

fn Sqlx::from(self : Sqlx, tbls : Array[String]) -> Sqlx

#
Sqlx::grup

fn Sqlx::grup(self : Sqlx, col : String) -> Sqlx

#
Sqlx::join

fn Sqlx::join(self : Sqlx, tbl : String) -> Sqlx

#
Sqlx::limt

fn Sqlx::limt(self : Sqlx, limt : Int) -> Sqlx

#
Sqlx::ordr

fn Sqlx::ordr(self : Sqlx, col : String) -> Sqlx

#
Sqlx::slct

fn Sqlx::slct(self : Sqlx, cols : Array[String]) -> Sqlx

#
Sqlx::wher

fn Sqlx::wher(self : Sqlx, cond : String) -> Sqlx

#
Tree

pub(all) struct Tree {
kind : String
text : String
kids : Array[Tree]
span : Span?
} derive(Eq,
Debug
)

#
Tstx

pub(all) struct Tstx {
name : String
givn : Array[String]
when : Array[String]
chec : Array[String]
} derive(Eq,
Debug
)

#
Tstx::chec

fn Tstx::chec(self : Tstx, text : String) -> Tstx

#
Tstx::givn

fn Tstx::givn(self : Tstx, text : String) -> Tstx

#
Tstx::when

fn Tstx::when(self : Tstx, text : String) -> Tstx

#
Vctx

pub(all) struct Vctx {
tree : Tree
digs : Array[Diag]
} derive(Eq,
Debug
)

#
Vctx::chec

fn Vctx::chec(self : Vctx, ok : Bool, msg : String) -> Vctx

#
Vctx::fail

fn Vctx::fail(self : Vctx, msg : String) -> Vctx

#
Vctx::warn

fn Vctx::warn(self : Vctx, msg : String) -> Vctx

#
altx

fn altx(items : Array[Expr]) -> Expr

#
atom

fn atom() -> Expr

#
attr

fn attr(name : String, value : String) -> (String, String)

#
bind

fn bind(text : String) -> String

#
body

fn body(rule : Rule, expr : Expr) -> Rule

#
brkx

fn brkx() -> Docx

#
case

fn case(name : String) -> Case

#
chec

fn chec(v : Vctx, ok : Bool, msg : String) -> Vctx

#
chex

fn chex(value : Proj, spec : String, rule : String, file : File) -> Array[Diag]

#
chid

fn chid(item : Html) -> Html

#
diag

fn diag(kind : String, text : String) -> Diag

#
domn

fn domn() -> Dslx

#
dslx

fn dslx(name : String) -> Dslx

#
elem

fn elem(name : String) -> String

#
emit

fn emit(value : Dslx) -> Emix

#
evnt

fn evnt(name : String) -> String

#
fail

fn fail(text : String) -> Diag

#
file

fn file(path : String, text : String) -> File

#
fldx

fn fldx(name : String, typx : String) -> Fldx

#
from

fn from(tbls : Array[String]) -> Array[String]

#
fspc

fn fspc(value : Proj, name : String) -> Dslx?

#
fwds

fn fwds() -> Dslx

#
givn

fn givn(text : String) -> String

#
grup

fn grup(doc : Docx) -> Docx

#
hint

fn hint(text : String) -> String

#
html

fn html() -> Html

#
join

fn join(items : Array[Docx]) -> Docx

#
limt

fn limt(n : Int) -> Int

#
line

fn line() -> Docx

#
litx

fn litx(text : String) -> Expr

#
mach

fn mach() -> Mach

#
many

fn many(item : Expr) -> Expr

#
mods

fn mods(value : Emix) -> Array[File]

#
n8nx

fn n8nx() -> Dslx

#
name

fn name(value : Dslx) -> String

#
nest

fn nest(size : Int, doc : Docx) -> Docx

#
node

fn node(name : String) -> Node

#
optx

fn optx(item : Expr) -> Expr

#
ordr

fn ordr(col : String) -> String

#
pack

fn pack(value : Proj) -> Array[File]

#
parm

fn parm(text : String) -> String

#
pars

fn pars(expr : Expr, text : String) -> Pout

#
path

fn path(text : String) -> String

#
prat

fn prat() -> Prat

#
prnt

fn prnt(doc : Docx) -> String

#
proj

fn proj(name : String) -> Proj

#
refx

fn refx(text : String) -> Expr

#
rgxx

fn rgxx(text : String) -> Expr

#
rout

fn rout() -> Rout

#
rule

fn rule(name : String) -> Rule

#
runp

fn runp(value : Proj, spec : String, rule : String, text : String) -> Pout

#
runx

fn runx(spec : Dslx, rule : String, text : String) -> Pout

#
seqx

fn seqx(items : Array[Expr]) -> Expr

#
slct

fn slct(cols : Array[String]) -> Array[String]

#
span

fn span(file : String, from : Int, upto : Int) -> Span

#
spec

fn spec(name : String) -> Spec

#
sqlx

fn sqlx() -> Sqlx

#
stat

fn stat(name : String) -> String

#
text

fn text(value : String) -> Docx

#
tokn

fn tokn(text : String) -> Expr

#
tran

fn tran(name : String) -> String

#
tree

fn tree(kind : String, text : String, kids? : Array[Tree], span? : Span) -> Tree

#
tstx

fn tstx(name : String) -> Tstx

#
vald

fn vald(tree : Tree) -> Vctx

#
vprj

fn vprj(value : Proj) -> Array[Diag]

#
vspc

fn vspc(value : Dslx) -> Array[Diag]

#
warn

fn warn(text : String) -> Diag

#
wflw

fn wflw() -> Dslx

#
when

fn when(text : String) -> String

#
wher

fn wher(cond : String) -> String

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io