Second stage of the milestone-2 frontend. calc-me.flan (12 decls) and sand.flan (20 decls) both parse end to end, and both are test deps so a regression fails `dune test` rather than surfacing at the CLI. Three silent-misparse bugs fixed along the way -- all cases that read cleanly and meant something else: - dotimes/defer/some/try/fn fell through to Call, discarding their binding and control-flow meaning. Now special forms. Forms from later milestones (handler-bind, restart-case, loop/recur, defmacro, signal, with-allocator, errdefer, await) are rejected outright rather than parsed as calls. - (Some 1) in first body position was read as a return type, because (Option f64) and (Some 1) are identical s-expressions and the heuristic was capitalisation. Now decided by the set of names actually declared as types, collected in a pre-pass -- exact, and order-independent so a type declared below its user still resolves. - Array literals in value position were rejected outright. Also adds NEXT.md with the handoff for the checker.
2.7 KiB
Where this is
Milestone 2 of the build sequence in plan.org: run calc-me.flan on the
interpreter. Two of four stages exist.
reader ✅ → parse ✅ → check ⬜ → interpret ⬜
| File | What it does |
|---|---|
lib/loc.ml |
source locations + Loc.Error, the frontend's one exception |
lib/form.ml |
reader output: Sym Kw Int Float Str Byte List Vec Map |
lib/reader.ml |
hand-written S-expression reader, no menhir/ocamllex |
lib/ast.ml |
AST: texpr, expr, place, pattern, decl |
lib/parse.ml |
forms → AST; special forms, desugaring, declarations |
bin/main.ml |
flan read <file> and flan parse <file> |
test/test_flan.ml |
110+ assertions; calc-me.flan and sand.flan are deps |
dune build && dune test is green. flan parse calc-me.flan and
flan parse sand.flan both succeed.
Next: lib/types.ml and the checker
- Type representation. Resolve
Ast.texprinto a real type. Needs the declared-type environmentParse.declared_typesalready computes — that pre-pass exists and should be reused rather than rebuilt. - Top-level environment. Two passes, because top-level names are
order-independent (
plan.org, Modules): collect all signatures, then check bodies. - Check
calc-me.flan. It needs:i32/u8/f64/bool, structs,[u8]slices,(Ptr T)with one level of auto-deref on.field,(Option T)withSome/None,Unwrap (Usome, _)as early-return-None,while,return,seton the five places,matchonOption, and the milestone-2 primitive list inplan.org. - Then the interpreter over the typed IR.
Watch for
The two bugs found so far were both silent misparses — code that read fine and meant something else:
'skip-formbecame a symbol named'skip-formdotimes/defer/somefell through toCall, discarding their binding and control-flow meaning(Some 1)in first body position was eaten as a return type
The rule that catches this class: anything that binds a name, alters control
flow, or is not yet implemented must be recognised explicitly and rejected if
unsupported — never allowed to fall through to a generic case. parse.ml
rejects handler-bind, restart-case, loop/recur, defmacro, signal,
with-allocator, errdefer and await for exactly this reason. Keep doing that
in the checker.
Open decisions that touch the checker
None block milestone 2. plan.org tags each open decision with the milestone it
is due by; #1 (host language) is now settled as OCaml.
Untracked on purpose
old-ocaml/ — the pre-rewrite menhir/ocamllex frontend, kept as reference and
excluded from the build by the root dune file. Its contents are also in git
history at 2c232dd.