flan/lib/ast.ml
Joseph Ferano 943561e765 A map entry is not a place
spec-memory.md drops (set (get m k) v) from the assignable forms: a map has an
upsert of its own, put, which either inserts or replaces, so there is no store
into a lookup - and an absent entry has no location to store into anyway.

The compiler still parsed it into an Ast.Pkey and refused it downstream as
unimplemented, milestone 6, which is the wrong reason for something that is
never arriving. The place form is gone from ast, tast, load, check and emit,
and the parser refuses the shape where it is written, with the reason and a
pointer to put.
2026-09-11 12:23:10 +07:00

137 lines
5.9 KiB
OCaml

(** The AST: syntax with special forms recognised, before typing.
Sugar is gone by this point. [when], [unless], [cond] and [and]/[or] are
desugared into [If] and [Do]; they are compiler special forms until macros
arrive at milestone 5, so there is nothing to preserve for a macroexpander
to see yet.
Types here are *surface* type expressions, not resolved types. [Ptr] and
[Option] are still just names; the checker resolves them. *)
(* ── Type expressions ──────────────────────────────────────────────── *)
type texpr = { t : texpr_kind; tloc : Loc.t }
and texpr_kind =
| Tname of string (* i32 bool Cursor string *)
| Tslice of texpr (* [u8] ptr+len *)
| Tarray of len * texpr (* [4 f32] [rows [cols u32]] *)
| Tmap of texpr * texpr (* {string i32} *)
| Tapp of string * texpr list (* (Ptr Cursor) (Option f64) *)
| Tfn of texpr list * texpr (* (Fn [a a] bool) *)
(* An array length is an integer or a compile-time constant's name. *)
and len =
| Lint of int64
| Lname of string
(* ── Expressions ───────────────────────────────────────────────────── *)
type expr = { e : expr_kind; loc : Loc.t }
and expr_kind =
| Int of int64
| Float of float
| Byte of int
| Str of string
| Kw of string (* :space — coerced at typed call sites *)
| Quote of string (* 'skip-form — restart names *)
| Var of string
| Do of expr list
| Let of binding list * expr list
| If of expr * expr * expr option
| While of expr * expr list
| Return of expr option
| Set of place * expr
| Field of expr * string (* (.pos c) — auto-derefs one level *)
| Call of expr * expr list
| Match of expr * arm list
| Struct of string * (string * expr) list (* (Cursor {:src s}) *)
| Arr of expr list (* [0xE6B800FF ...] — a fixed array value *)
(* These bind names or alter control flow, so none of them can be a call. *)
| Fn of string list * expr list (* (fn [x y] ...) — non-escaping *)
| Dotimes of string * expr * expr list (* (dotimes [i n] ...) *)
| Defer of expr list (* runs on scope exit *)
| Unwrap of unwrap * expr (* (some x) / (try x) *)
(* (handler-bind [(Type [c] body ...) ...] body ...) — spec-conditions.md.
A clause binds a name for the condition, so this cannot be a call. *)
| HandlerBind of hclause list * expr list
| Signal of sigkind * expr (* (signal c) / (error c) *)
(* (restart-case body (name [] body ...) ...) and (invoke-restart 'name).
Both alter control flow, so neither can be a call. *)
| RestartCase of expr * rclause list
| InvokeRestart of string
(* Two ways to signal, because they are two different things — §1 and §2.
[signal] returns Unit whatever it finds; [error] has type Never and, with
nothing transferring, the program stops. *)
and sigkind = Ssignal | Serror
and hclause = { hty : texpr; hname : string; hbody : expr list; hloc : Loc.t }
and rclause = { rname : string; rbody : expr list; rloc : Loc.t }
(* Two unwrap operators, because they are two different things — plan.org. *)
and unwrap = Usome | Utry
and binding = { bname : string; bty : texpr option; bval : expr; bloc : Loc.t }
(* The fixed list of assignable forms — spec-memory.md. Not setf. *)
and place =
| Pvar of string
| Pfield of expr * string (* (set (.hp e) v) *)
| Pindex of expr * expr list (* (set (at grid r c) v) *)
| Pderef of expr (* (set (deref p) v) *)
and arm = { pat : pattern; body : expr list; aloc : Loc.t }
and pattern =
| Pctor of string * string list (* (Some e) (Rect w h) None *)
| Pwild (* _ :else *)
(* ── Declarations ──────────────────────────────────────────────────── *)
type field = { fname : string; fty : texpr; floc : Loc.t }
type fn = {
name : string;
params : field list;
ret : texpr option; (* None means Unit *)
fbody : expr list;
nloc : Loc.t;
}
type decl = { d : decl_kind; dloc : Loc.t }
and decl_kind =
| Package of string
| Import of string * string (* alias, path *)
| Defalias of string * texpr
| Defstruct of string * field list
| Defunion of string * variant list
| Defn of fn
(* No body, so no [defn]: a foreign function, and the string is the C symbol
it is actually called by (plan.org, Types — [declare] is kept only where
there is no body). *)
| Declare of fn * string
(* Inline name/value pairs, as everywhere else. The members are what a
keyword at a call site resolves against. *)
| Defenum of string * (string * int64) list
(* value is optional: ZII. `uninit` opts out and is recorded as Uninit. *)
| Defvar of string * texpr option * init
| Defconst of string * texpr option * expr
and variant = { vname : string; vfields : field list; vloc : Loc.t }
and init = Zeroed | Uninit | Init of expr
(* Every top-level name a declaration introduces, whatever kind it is. There is
one top-level namespace, so this is both the set [Load] renames on an import
and the set [Check] refuses to see twice — one definition, so the two cannot
drift apart. *)
let declared_name (d : decl) =
match d.d with
| Defenum (n, _) | Defalias (n, _) | Defstruct (n, _) | Defunion (n, _)
| Defvar (n, _, _) | Defconst (n, _, _) -> Some n
| Declare (fn, _) | Defn fn -> Some fn.name
| Package _ | Import _ -> None