flan/lib/ast.ml
Joseph Ferano f83ca7de6f Two rules the checker was missing
A shift by the operand's own width or more is poison in LLVM, not a wrong
number: (<< 1 32) at -O2 compiled to a bare retq. A literal count out of range
is now rejected in check.ml, and emit.ml masks a computed one to width - 1,
which is what the hardware does and which LLVM folds away for a constant.

There is one top-level namespace, but the environment's tables are per-kind, so
only a function was ever checked for a duplicate. (defn item ...) beside
(defvar item ...) type checked and then died in LLVM as a redefinition of
'@flan.item'; two colliding type declarations were not caught anywhere. One
pass over Ast.declared_name now runs before every other collection pass. That
function lives in ast.ml because Load needs the same set - the names an import
renames - and two copies would drift.
2026-09-10 21:05:08 +07:00

122 lines
5.2 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) *)
(* 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) *)
| Pkey of expr * expr (* (set (get m k) 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