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.
65 lines
2.7 KiB
Markdown
65 lines
2.7 KiB
Markdown
# 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
|
|
|
|
1. **Type representation.** Resolve `Ast.texpr` into a real type. Needs the
|
|
declared-type environment `Parse.declared_types` already computes — that
|
|
pre-pass exists and should be reused rather than rebuilt.
|
|
2. **Top-level environment.** Two passes, because top-level names are
|
|
order-independent (`plan.org`, Modules): collect all signatures, then check
|
|
bodies.
|
|
3. **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)` with
|
|
`Some`/`None`, `Unwrap (Usome, _)` as early-return-None, `while`, `return`,
|
|
`set` on the five places, `match` on `Option`, and the milestone-2 primitive
|
|
list in `plan.org`.
|
|
4. **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-form` became a symbol named `'skip-form`
|
|
- `dotimes`/`defer`/`some` fell through to `Call`, 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`.
|