flan/NEXT.md
Joseph Ferano c64e91bdfa Add AST and forms->AST parser
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.
2026-09-10 14:56:35 +07:00

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`.