amistozy/sml is an S-expression-based ML-like language implemented in
MoonBit.
The package is both a small runnable language and a compact example of how to build a parser, a surface language, a lowering pass, and an evaluator in MoonBit.
The implementation is organized as a simple pipeline:
- parse source text into
SExpr - parse
SExprintoSurfaceExpr - lower
SurfaceExprintoCoreExpr - evaluate
CoreExprintoValue
The public API exposes each stage, so you can inspect intermediate forms as well as run complete programs.
parse_sexp(source) -> SExprparse_surface(source) -> SurfaceExprparse_surface_expr(expr) -> SurfaceExprdesugar(expr) -> CoreExprlower(source) -> CoreExpreval_core(expr) -> Valuerun(source) -> Valuerun_to_string(source) -> String
The following program is a version of Knuth's "Man or Boy" test:
///|
test "Man or Boy test" {
let program =
#|(letrec (a k x1 x2 x3 x4 x5)
#| (let m (ref k))
#| (letrec (b)
#| (%= m (- _ 1))
#| (a (! m) b x1 x2 x3 x4))
#| (if (<= k 0) (+ (x4) (x5)) (b)))
#|
#|(a 10 (do 1) (do -1) (do -1) (do 1) (do 0))
assert_eq(@sml.run_to_string(program), "-67")
}42
true
false
"hello"(if (> 3 2) "yes" "no")((fn (x y z) (+ x y z)) 4 5 3)Functions are non-curried: one call supplies the full parameter list.
begin is the explicit block form. A block evaluates its forms in order and
returns the value of the last expression. If the final form is a declaration,
the block evaluates to ().
(begin
(let x 1)
(say x)
(let y (+ x 1))
(+ x y))Blocks are also used inside function bodies and declaration right-hand sides.
do is sugar for a zero-argument function:
(let next
(do
(let x 1)
(+ x 2)))
(next)This is equivalent to:
(let next
(fn ()
(let x 1)
(+ x 2)))
(next)Sequential declarations:
(let x 10)
(let y (+ x 5))
(+ x y)Parallel declarations:
(let& (x 1) (y 2))
(+ x y)let& evaluates all right-hand sides in the original environment, so sibling
bindings cannot refer to one another.
Recursive function declarations:
(letrec (fact n)
(if (= n 0)
1
(* n (fact (- n 1)))))
(fact 5)Mutually recursive function declarations:
(letrec&
((even n)
(if (= n 0) true (odd (- n 1))))
((odd n)
(if (= n 0) false (even (- n 1)))))
(even 10)_ means "ignore this binding" when it appears in binding position:
(let _ 42)
((fn (x _ z) (+ x z)) 1 999 2)Ignored bindings are not inserted into the runtime environment.
In expression position, _ is a placeholder for a single implicit argument.
The smallest if or function call that directly contains _ is rewritten into
a one-argument function.
(+ _ 1)expands to:
(fn (__arg) (+ __arg 1))Repeated placeholders within the same shorthand expression all refer to the same argument:
(+ _ _)expands to:
(fn (__arg) (+ __arg __arg))Nested shorthand is expanded bottom-up. For example:
(+ 1 (* _ 2))becomes:
(+ 1 (fn (__arg) (* __arg 2)))A bare _ is not a valid complete expression.
The runtime currently provides:
- Arithmetic:
+,-,*,/ - Concatenation:
++ - Comparison:
=,<,>,<=,>= - Boolean:
not - References:
ref,!,:=,%= - Output:
say
+ performs integer addition:
(+ 1 2 3)++ concatenates values by converting each argument to text first:
(++ "foo" "bar" "baz")
(++ "age: " 18)say accepts zero or more arguments and prints them separated by a single
space.
(let name "Alice")
(let age 18)
(say name "is" age "years old.")References are mutable cells:
(let r (ref 1))
(! r)
(:= r 2)
(! r)%= updates a reference by applying a unary function to its current value:
(let r (ref 42))
(%= r (+ _ 1))
(! r)The repository includes a CLI in cmd/main with subcommands for each pipeline
stage.
Evaluate an inline program:
moon run cmd/main -- run -e "(+ 1 (* 2 3) 4)"Evaluate a program from a file:
moon run cmd/main -- run play.smlInspect the parsed S-expression tree:
moon run cmd/main -- sexp -e "(+ 1 2)"Inspect the surface AST:
moon run cmd/main -- surface -e "(let x 1) (+ x 2)"Inspect the lowered core AST:
moon run cmd/main -- core -e "(let x 1) (+ x 2)"Each subcommand accepts exactly one input source:
-e <expr>reads source from the command line<path>reads source from a file
Update generated package interfaces:
moon infoFormat the project:
moon fmtRun tests:
moon testVS Code syntax highlighting lives in vscode-extension/.
- Comments start with
;and continue to the end of the line - String literals support
\n,\r,\t,\", and\\ - Errors are reported as
SmlError, with parse, surface, and runtime variants