(** Forms → AST. Recognises special forms, desugars sugar, reports malformed syntax with the location of the offending form. Everything not recognised here is a call, which is how a Lisp should work: [at], [length], [push], [abort] and the rest are ordinary functions resolved by the checker, not syntax. *) open Form let fail (f : Form.t) fmt = Loc.fail f.loc fmt let sym (f : Form.t) = match f.v with | Sym s -> s | _ -> fail f "expected a name, found %s" (Form.to_string f) (* Names for the temporaries this file mints — the value is bound once and everything that needs it reads *that*, so a destructuring pattern over a call calls it once and a short-circuit operand is evaluated once. [~] is a delimiter in the reader, so no symbol anyone can write contains one: these cannot collide with a source name and a source name cannot shadow one. Reset per program so the names, and therefore the slot numbering downstream, are the same every run. The purpose is part of the name because these names are shown: the inspector lists a frame's locals by name, and a short-circuit temp called [destructure~3] is a plain lie about where it came from. One counter across all purposes, so a name is still unique whatever minted it. *) let temps = ref 0 let fresh_temp what = incr temps; Printf.sprintf "%s~%d" what !temps (* The one parameter every macro is compiled with, whatever its author wrote as a parameter list: the slice of forms at the call site, which the bindings [macro_body] generates read out of. A [~] in it for the same reason [fresh_temp] puts one there — the reader cannot produce the character in a symbol, so nothing an author writes collides with it. *) let macro_args = "macro~args" (* Destructuring binds in [let] and nowhere else. Every other binding position — a [defn] parameter, a [defstruct] field, an [fn] parameter, a [dotimes] counter, a [match] arm's binds — takes a plain name, and a pattern written there is refused here rather than falling out of [sym] as "expected a name". A parameter is the one worth saying why about: it is a name/type pair, and a pattern has no name to pair the type with, so supporting it means a pattern inside [Ast.field] — a record [Load] and [Shim] both build and read, and neither is this file's to change. *) let no_pattern (f : Form.t) = match f.v with | Map _ | Vec _ -> fail f "%s is a destructuring pattern, and this position takes a plain name. \ Take the value under a name and destructure it in the body" (Form.to_string f) | _ -> () (* ── Type expressions ──────────────────────────────────────────────── *) let rec texpr (f : Form.t) : Ast.texpr = let mk t = { Ast.t; tloc = f.loc } in match f.v with (* Unit is spelled [()], ML's spelling. It is the honest name, and it cannot collide with anything: an empty call is not a valid expression, so [()] has no reading in value position to be confused with. Internally it stays [Tname "Unit"] -- the resolver, the shim and the emitter all speak that name, and diagnostics still print it. *) | List [] -> mk (Ast.Tname "Unit") (* One spelling. Two accepted spellings is how two spellings become permanent, and the refusal names the new one -- the same rule the colon-to-dot change followed. [Tname "Unit"] still exists below this point: it is what [()] parses to, and what the resolver, the shim and the emitter go on speaking. *) | Sym "Unit" -> fail f "unit is written (), not Unit" | Sym s -> mk (Ast.Tname s) | Vec [ elem ] -> mk (Ast.Tslice (texpr elem)) | Vec [ n; elem ] -> mk (Ast.Tarray (len n, texpr elem)) | Vec _ -> fail f "a type in brackets is [T] for a slice or [n T] for a fixed array" (* Braces are not a type. [{K V}] used to spell [(Map K V)] and the two resolved to the same thing; the brace spelling is withdrawn, and the refusal names the surviving one rather than letting the form fall through to "expected a type". Two reasons, and the second is the one that decided it. The brace's value meaning and its type meaning do not correspond the way the bracket's do: [[1 2 3]] is a value whose type is [[3 i32]], but [{.x 1 .y 0}] is a value whose type is a *name*, and a map value is built by [map-new] with no braces anywhere. And dropping it reserves [{}] in type position for anonymous struct types, [{.x f32 .y f32}], which is a likelier thing to want than a second spelling of a type that already has one. It also settles the one syntax question generics had: a defn's constraint map, [{:where (ordered? $t)}], sits immediately after the return type, and with braces gone from type position there is nothing for it to be confused with. *) | Map _ -> fail f "a map type is written (Map K V), not in braces" | List ({ v = Sym "Fn"; _ } :: rest) -> (match rest with | [ { v = Vec params; _ }; ret ] -> mk (Ast.Tfn (List.map texpr params, texpr ret)) | _ -> fail f "a function type is (Fn [T ...] R)") | List ({ v = Sym name; _ } :: args) when args <> [] -> mk (Ast.Tapp (name, List.map texpr args)) | _ -> fail f "expected a type, found %s" (Form.to_string f) and len (f : Form.t) : Ast.len = match f.v with | Int n -> Ast.Lint n | Sym s -> Ast.Lname s | _ -> fail f "an array length is an integer or a constant's name" (* Inline name/type pairs: [x i32 y f32] — as in let, defstruct and defn. *) let rec fields (f : Form.t) (items : Form.t list) : Ast.field list = match items with | [] -> [] | name :: ty :: rest -> no_pattern name; { Ast.fname = sym name; fty = texpr ty; floc = name.loc } :: fields f rest | [ odd ] -> Loc.fail odd.loc "field %s has no type — these come in name/type pairs" (Form.to_string odd) (* A [defn]'s parameter vector, left undecided — the long argument is beside the [defn] case. A bare symbol could be either half of a pair and is carried as one; everything else is a type by its shape alone, and is resolved now so that a malformed type is still reported at the character that is wrong. [fields] applies [no_pattern] to the name half of each pair, and this cannot: which half a slot is has not been decided. A map is the one shape that can be settled here anyway — braces are not a type in any position ([texpr] refuses them), so a map in this vector is a destructuring pattern and nothing else, and it gets the sentence that says so rather than a complaint about map type syntax. A bracket cannot be settled the same way, because [[a b]] is a pattern in a name slot and a slice type in a type slot; one written in a name slot comes back from [Check] as "a parameter's name was expected here", which is true and is as close as this can get. *) and pitems (items : Form.t list) : Ast.pitem list = List.map (fun (it : Form.t) -> match it.v with | Sym s -> Ast.Pname (s, it.loc) | Map _ -> no_pattern it; assert false | _ -> Ast.Ptype (texpr it)) items (* A generic's or a method's parameter vector. Every slot is a bare name and every parameter is [dyn], so the types are written out here rather than left for [Check.pair_params] to decide: the pairing exists because a [defn]'s vector is ambiguous until every type name is known, and this one never is. A parameter named after a type is therefore fine here, where in a [defn] it would be refused. *) and dyn_params which (items : Form.t list) : Ast.field list = List.map (fun (it : Form.t) -> match it.v with | Sym s -> { Ast.fname = s; fty = { Ast.t = Ast.Tname "dyn"; tloc = it.loc }; floc = it.loc } | _ -> fail it "a %s's parameter is a bare name, and found %s. Every parameter \ here is dyn, so there is no type to write" which (Form.to_string it)) items (* A [defmethod]'s dispatch value. Literals only: the value is compared against what the dispatch answered at run time, and the method is declared under a name built from it at compile time, so it has to be something both passes can read off the source. A computed one — Clojure allows any value a method is registered under, because registration there is a run-time call — is not available and the message says so. *) and dispatch (f : Form.t) : Ast.dispatch = match f.v with | Kw "else" -> Ast.Delse | Sym "true" -> Ast.Dbool true | Sym "false" -> Ast.Dbool false | Sym s -> Ast.Dclass s | Kw k -> Ast.Dkw k | Str s -> Ast.Dstr s | Int i -> Ast.Dint i | _ -> fail f "a method's dispatch value is a class's name, a keyword, a string, an \ integer, true, false, or :else for the one that answers when no other \ does — and found %s. It is written out, not computed" (Form.to_string f) (* ── The constraint map at the head of a defn body ────────────────────── [(defn sort [s [$t]] () {:where (ordered? $t)} body ...)]. Clojure's [{:pre [...] :post [...]}] is the precedent and the reason it is a map rather than a bare keyword: it leaves room for further keys without new syntax. **The one syntax question it had, and how it stopped being one.** [{K V}] used to be a legal *return type* spelling for [(Map K V)], which put two braces in a row meaning different things — [(defn f [xs [$t]] {string i32} {:where ...} body)]. The brace spelling has since been withdrawn from type position entirely ([texpr] above), so the slot after the return type can be nothing but this. The leading keyword is checked, but is no longer what tells a constraint map from anything else braces can mean in the position a body starts in — dyn maps changed what else is possible there. [{.x 1}] is still read as a struct literal's field list and never as a constraint map, but [expr] below says so, with its own message; this function no longer sees that shape at all. What DOES reach here besides [:where]: a keyword-keyed map with a different key, an empty map, or a map keyed on something that is neither a keyword nor a [.field] symbol, and only when the body has more after it — with nothing after, that map is the whole single-form body, a real dyn value like [(defn f [] dyn {:a 1})] or [(defn f [] dyn {})], and this function leaves it alone. *) let constraints (body : Form.t list) : Ast.pred list * Form.t list = match body with (* A map literal opening on [:where] is always a constraint map, even with nothing after it: a where-clause with no body past it is what a moved closing paren produces, and the whole point of naming the key here is to catch that as a constraint-map error ("a where predicate is ...", or whatever [keys] finds wrong with it) rather than let a stray [(ordered? $t)] surface later as "unknown function ordered?" from inside what was meant as a predicate. Any OTHER map literal — keyed on a different keyword, keyed on something that is not a keyword at all, or holding no keys at all — is read as a constraint map only when something follows it in the body: unlike [:where], nothing about the map alone says it is a mistake and not an ordinary map literal until [rest] says whether it is one body form among several (discarded, so worth flagging as the typo it almost always is) or the function's entire single-form body ([(defn f [] dyn {:a 1})], where the map is not discarded, it IS the answer, evaluated for both its side effects and its value like any other body form). Note that "discarded" here is about the map's *result*, not about whether evaluating it can do anything: {:a (println "hi")} still prints, same as any expression statement whose value nothing uses — this arm's business is a value going unused, not silence. An empty map, [{}], has no key to check and gets its own message rather than running [keys] on nothing and saying nothing. One shape is excluded on purpose: a map opening on a [.field] symbol, [{.x 1}], is a struct field list with no struct name in front of it, and [expr] below already gives that its own message, "a bare map is not an expression; write (Type {.field v})" — the one this file had before any of the above existed. Catching it here first would bury that dedicated diagnostic under "a constraint map is keyword/value pairs", which is true but not what is wrong with it. *) | ({ Form.v = Form.Map kvs; loc } as m) :: rest when (match kvs with | { Form.v = Form.Kw "where"; _ } :: _ -> true | { Form.v = Form.Sym s; _ } :: _ when String.length s > 1 && s.[0] = '.' -> false | _ -> rest <> []) -> if kvs = [] then Loc.fail loc "an empty map literal here is discarded — did you mean {:where ...}? \ Write (do {} ...) if the empty map is deliberate" else let pred (p : Form.t) = match p.Form.v with (* [$t] at a predicate, not bare [t]: the clause talks about the variable the signature *bound*, and writing it the way the signature wrote it is the one spelling that cannot be read as a concrete type that happens to share the name. *) | Form.List [ { Form.v = Form.Sym name; _ }; { Form.v = Form.Sym v; loc = vloc } ] when String.length v > 1 && v.[0] = '$' -> ignore vloc; { Ast.pname = name; pvar = String.sub v 1 (String.length v - 1); ploc = p.Form.loc } | _ -> Loc.fail p.Form.loc "a where predicate is (name? $t), one predicate about one type \ variable — found %s" (Form.to_string p) in let rec keys = function | [] -> [] | { Form.v = Form.Kw "where"; _ } :: v :: rest -> (match v.Form.v with (* A vector, because two predicates on one variable is the ordinary case — [{:where [(ordered? $t) (copyable? $t)]}] is what a comparing generic that also reads its parameter twice needs. One predicate on its own is accepted unwrapped, which is the same sugar [:pre] does not have and is worth the line it costs. *) | Form.Vec ps -> List.map pred ps | _ -> [ pred v ]) @ keys rest | { Form.v = Form.Kw k; loc } :: _ :: rest -> Loc.fail loc "%s is not a key a defn's constraint map takes; :where is the only \ one" (":" ^ k) |> fun () -> keys rest | odd :: _ -> Loc.fail odd.Form.loc "a constraint map is keyword/value pairs — found %s" (Form.to_string odd) in if List.length kvs mod 2 <> 0 then Loc.fail m.Form.loc "a constraint map is keyword/value pairs, and this \ one has an odd number of forms"; (keys kvs, rest) | _ -> ([], body) (* ── Expressions ───────────────────────────────────────────────────── *) let rec expr (f : Form.t) : Ast.expr = let mk e = { Ast.e; loc = f.loc } in match f.v with | Int i -> mk (Ast.Int i) | Float x -> mk (Ast.Float x) | Byte b -> mk (Ast.Byte b) | Str s -> mk (Ast.Str s) | Kw k -> mk (Ast.Kw k) | Sym s -> mk (Ast.Var s) (* In value position brackets are a fixed-array literal; in type position they are a slice or array type. Position disambiguates, as with {}. *) | Vec items -> mk (Ast.Arr (List.map expr items)) (* Braces in value position are two literals told apart by their first form. A [.field] symbol says struct; anything else, the empty braces included, is a dyn map literal: {:a 1 :b s}, keys and values alternating, each an ordinary expression. A struct field list with no type in front of it is [Ast.Bare]. The fields alone do not name a type, but the *position* often does — a defn's return type, a typed parameter, a field of an enclosing literal — and only the checker can see that. So this parses and [Check] decides: it reads the type off the expectation where there is one, and refuses by name where there is not. The refusal used to stand right here, which is why [(defn f [] Cell {.row r .col c})] could not work at all. *) | Map (({ v = Sym s; _ } :: _) as kvs) when String.length s > 1 && s.[0] = '.' -> mk (Ast.Bare (struct_fields f kvs)) | Map items -> mk (Ast.MapLit (None, map_pairs f items)) | List [] -> fail f "() is not an expression" | List (head :: args) -> form f mk head args and form f mk (head : Form.t) (args : Form.t list) : Ast.expr = match head.v with (* ── quote ─────────────────────────────────────────────────────── *) | Sym "quote" -> (match args with | [ { v = Sym s; _ } ] -> mk (Ast.Quote s) | _ -> fail f "quote takes one symbol") (* ── sequencing and binding ────────────────────────────────────── *) | Sym "do" -> mk (Ast.Do (List.map expr args)) | Sym "let" -> (match args with | { v = Vec bs; _ } :: body -> mk (Ast.Let (bindings f bs, body_of body)) | _ -> fail f "let is (let [name value ...] body ...)") (* ── conditionals ──────────────────────────────────────────────── *) | Sym "if" -> (match args with | [ c; t ] -> mk (Ast.If (expr c, expr t, None)) | [ c; t; e ] -> mk (Ast.If (expr c, expr t, Some (expr e))) | _ -> fail f "if is (if test then) or (if test then else)") (* Sugar, desugared here: special forms until macros land at milestone 5. *) (* An empty body is allowed, and becomes the same [Ast.Do []] that [(do)] already means. There was never a reason for the restriction: [(when test)] is a guard whose consequent has not been written yet, which is a state a program passes through while it is being written, and refusing it buys nothing. A [(when)] with no test at all is still refused, because there is no expression to test. *) | Sym "when" -> (match args with | c :: body -> mk (Ast.If (expr c, { Ast.e = Ast.Do (body_of body); loc = f.loc }, None)) | _ -> fail f "when is (when test body ...)") | Sym "cond" -> cond f args (* Short-circuiting, so they cannot be ordinary calls. *) | Sym "and" -> shortcircuit f args ~is_and:true | Sym "or" -> shortcircuit f args ~is_and:false (* ── loops ─────────────────────────────────────────────────────── *) (* An optional label comes first: [(while :outer (< i n) ...)]. A keyword in the head position is unambiguous because a loop condition is never one and a [dotimes] binding vector is never one either, so [label] peels it off whatever follows the form's name. *) | Sym "while" -> (match label args with | lbl, c :: body -> mk (Ast.While (lbl, expr c, body_of body)) | _, [] -> fail f "while is (while test body ...), or (while :label test body ...)") | Sym "until" -> (match label args with | lbl, c :: body -> let neg = { Ast.e = Ast.Call ({ Ast.e = Ast.Var "not"; loc = head.loc }, [ expr c ]); loc = f.loc } in mk (Ast.While (lbl, neg, body_of body)) | _, [] -> fail f "until is (until test body ...), or (until :label test body ...)") (* Break and continue. Not a goto: the label names one of the loops this form is lexically inside, and the checker resolves it against exactly those, so control can only leave a loop it is already in — the same restriction Odin's labelled break has. Bare, each means the innermost loop. *) | Sym "break" -> (match label args with | lbl, [] -> mk (Ast.Break lbl) | _ -> fail f "break is (break) or (break :label)") | Sym "continue" -> (match label args with | lbl, [] -> mk (Ast.Continue lbl) | _ -> fail f "continue is (continue) or (continue :label)") (* ── control ───────────────────────────────────────────────────── *) | Sym "return" -> (match args with | [] -> mk (Ast.Return None) | [ v ] -> mk (Ast.Return (Some (expr v))) | _ -> fail f "return takes at most one value") | Sym "set" -> (match args with | [ target; value ] -> mk (Ast.Set (place target, expr value)) | _ -> fail f "set is (set place value)") (* ── (array 4 rl/Vector2) ─────────────────────────────────────────── A zeroed fixed array, told its count and its element type. The type spelling [4 rl/Vector2] is unchanged and still works everywhere a type is expected; what it cannot do is appear in a [let] binding, which has no type slot, because there the brackets are an array *literal* of two elements and the second of them is a name nothing declares. So the count and the type arrive as plain arguments and Parse assembles the type itself. [(zeroed)] keeps its own job — the empty value of whatever the destination wants — and this is the one that is told. *) | Sym "array" -> (match args with | [ n; t ] -> mk (Ast.ArrayOf { Ast.t = Ast.Tarray (len n, texpr t); tloc = f.loc }) | _ -> fail f "array is (array COUNT TYPE), as in (array 4 rl/Vector2) — a zeroed \ fixed array of COUNT of them") (* ── (array-fill [r c] v) and (array-gen [r c] f) ────────────────── The two value-producing array constructors, and the reason they are recognised here rather than reaching Check as ordinary calls: the dimensions are in brackets, and a bracket in expression position is an array literal. [(array-fill [rows cols] 255)] handed through as a call would arrive with an [Arr] of two [Var]s as its first argument, which where [rows] and [cols] are defconsts is a perfectly good two-element array of integers — the wrong reading, and a silent one. Read here, the brackets are [len]s: the same integer-or-constant's-name the [n T] type spelling takes, refused by [len] when they are anything else. *) | Sym (("array-fill" | "array-gen") as which) -> let usage () = fail f "%s is (%s [n ...] %s) — the dimensions in brackets, each an integer \ or a compile-time constant's name, and %s" which which (if which = "array-fill" then "value" else "f") (if which = "array-fill" then "the value every element takes" else "a function taking one i32 index per dimension") in (match args with | [ { v = Vec (_ :: _ as ds); _ }; v ] -> let ds = List.map len ds in let v = expr v in mk (if which = "array-fill" then Ast.ArrayFill (ds, v) else Ast.ArrayGen (ds, v)) | _ -> usage ()) | Sym "match" -> (match args with | scrutinee :: rest -> mk (Ast.Match (expr scrutinee, arms f rest)) | [] -> fail f "match is (match value pattern body ...)") (* ── binding and control: never a call ─────────────────────────── *) (* A form that binds a name or alters control flow cannot fall through to Call — it would parse cleanly and mean the wrong thing, silently. *) (* An empty body is allowed here too, and means the same as it does in [when] and in a [defn]: the body is [Do []] and the function answers unit. A [defn] can only have one when its declared return type is (), because there is a type written down to contradict — [Check] refuses "returns i32 but has no body". An [fn] declares nothing, so an empty body is not in conflict with anything: it makes the answer type unit rather than failing to produce a value of some other one. *) | Sym "fn" -> (match args with | { v = Vec ps; _ } :: body -> List.iter no_pattern ps; mk (Ast.Fn (List.map sym ps, body_of body)) | _ -> fail f "fn is (fn [param ...] body ...)") (* One, two or three bounds. The stop is always the last one written, so the shorter forms are the longer one with its defaults left off: [start] is 0 and [step] is 1. *) | Sym "dotimes" -> (match label args with | lbl, ({ v = Vec (n :: ((_ :: _) as bs)); _ } :: body) when List.length bs <= 3 -> no_pattern n; let b = match List.map expr bs with | [ stop ] -> { Ast.dstart = None; dstop = stop; dstep = None } | [ start; stop ] -> { Ast.dstart = Some start; dstop = stop; dstep = None } | [ start; stop; step ] -> { Ast.dstart = Some start; dstop = stop; dstep = Some step } | _ -> assert false in mk (Ast.Dotimes (lbl, sym n, b, body_of body)) | _ -> fail f "dotimes is (dotimes [name stop] body ...), \ (dotimes [name start stop] body ...) or \ (dotimes [name start stop step] body ...) — stop is exclusive, and a \ negative step counts down") (* [(loop [x 0 acc 1] body ...)]. No label: [break] and [continue] may not leave a loop — a loop answers with the value of its body, and a jump out of one has no value to give — so there is nothing here for a label to name. A leading keyword is caught here rather than left to [bindings], which would complain that [:outer] has no value. *) | Sym "loop" -> (match args with | { v = Kw k; _ } :: _ -> fail f ":%s — loop takes no label; break and continue may not leave a loop" k | { v = Vec bs; _ } :: body -> mk (Ast.Loop (loop_bindings f bs, body_of body)) | _ -> fail f "loop is (loop [name value ...] body ...)") (* Rebind and jump to the top. Its arguments are checked against the loop's names in order, so the count is the binding vector's count. *) | Sym "recur" -> mk (Ast.Recur (List.map expr args)) | Sym "defer" -> (match args with | [] -> fail f "defer is (defer body ...)" | body -> mk (Ast.Defer (body_of body))) | Sym "some" -> (match args with | [ v ] -> mk (Ast.Unwrap (Ast.Usome, expr v)) | _ -> fail f "some is (some option-value)") | Sym "try" -> (match args with | [ v ] -> mk (Ast.Unwrap (Ast.Utry, expr v)) | _ -> fail f "try is (try result-value)") (* (signal c) : Unit, always. When every applicable handler returns normally the signalling function simply carries on, and with no handler at all it is a no-op — spec-conditions.md §1 and §2. *) (* And (error c) : Never — §2's diverging variant. Same lookup, but a handler that returns normally does not answer it: with nothing transferring the program stops. *) | Sym (("signal" | "error") as how) -> let kind = if how = "signal" then Ast.Ssignal else Ast.Serror in (match args with | [ c ] -> mk (Ast.Signal (kind, expr c)) | _ -> fail f "%s is (%s condition)" how how) (* (handler-bind [(Type [c] body ...) ...] body ...) A clause names a condition type, binds the condition, and runs for effect; matching is by type, since there is no condition hierarchy. *) | Sym "handler-bind" -> let clauses, body = match args with | { v = Vec clauses; _ } :: body when body <> [] -> (clauses, body) | _ -> fail f "handler-bind is (handler-bind [(Type [name] body ...) ...] body ...)" in let clause (c : Form.t) = match c.Form.v with | Form.List (ty :: { v = Form.Vec [ { v = Form.Sym n; _ } ]; _ } :: cbody) when cbody <> [] -> { Ast.hty = texpr ty; hname = n; hbody = List.map expr cbody; hloc = c.Form.loc } | _ -> fail c "a handler-bind clause is (Type [name] body ...)" in mk (Ast.HandlerBind (List.map clause clauses, body_of body)) (* (handler-case BODY [(Type [c] body ...) ...]) — spec-conditions.md, the unwinding half of the pair. The body comes first and the clauses after it, which is the opposite of handler-bind's order and is deliberate: a handler-bind is read as something established *around* a body, and a handler-case is read as a body with answers hung off the end of it. A clause is spelled exactly as handler-bind spells one, because it names the same thing — a condition type and a name to bind it to. At least one clause, since a handler-case with none would be its body and nothing else. *) | Sym "handler-case" -> let body, clauses = match args with | [ body; { v = Vec clauses; _ } ] when clauses <> [] -> (body, clauses) | _ -> fail f "handler-case is (handler-case body [(Type [name] body ...) ...]) \ with at least one clause" in let clause (c : Form.t) = match c.Form.v with | Form.List (ty :: { v = Form.Vec [ { v = Form.Sym n; _ } ]; _ } :: cbody) when cbody <> [] -> { Ast.hty = texpr ty; hname = n; hbody = List.map expr cbody; hloc = c.Form.loc } | _ -> fail c "a handler-case clause is (Type [name] body ...)" in mk (Ast.HandlerCase (expr body, List.map clause clauses)) (* (restart-case BODY (name [p T ...] BODY-1) ...) — spec-conditions.md §3. The body and every clause have the same type, which is the form's. A clause's parameters are inline name/type pairs, like any other binding form; what fills them in is the [invoke-restart] that chose the clause. *) | Sym "restart-case" -> let body, clauses = match args with | body :: clauses when clauses <> [] -> (body, clauses) | _ -> fail f "restart-case is (restart-case body (name [p T] body ...) ...)" in let clause (c : Form.t) = match c.Form.v with | Form.List ({ v = Form.Sym n; _ } :: { v = Form.Vec ps; _ } :: cbody) when cbody <> [] -> { Ast.rname = n; rparams = fields c ps; rbody = List.map expr cbody; rloc = c.Form.loc } | _ -> fail c "a restart-case clause is (name [p T] body ...)" in mk (Ast.RestartCase (expr body, List.map clause clauses)) (* (invoke-restart 'name arg ...) : Never. The name is a quoted symbol — that is what the reader's quote is for — and it is resolved on the restart stack at run time, since restarts are dynamically scoped. The arguments fill in the clause's parameters, and how many there are and what they are is settled at run time too, against the frame the name found (§3). *) | Sym "invoke-restart" -> (match args with | { v = Form.List [ { v = Form.Sym "quote"; _ }; { v = Form.Sym n; _ } ]; _ } :: rest -> mk (Ast.InvokeRestart (n, List.map expr rest)) | _ -> fail f "invoke-restart takes a quoted restart name and then its arguments, \ as in (invoke-restart 'use-value 42)") (* ── macros ────────────────────────────────────────────────────── *) (* The reader now produces these three, so they arrive here as ordinary heads and would fall through to Call — coming back from the checker as "unknown name quasiquote", which says nothing about what is actually missing. *) (* [Expand.quasiquote] runs over every form on the way into [program] and [decl], so a quasiquote is gone before this file looks at it and this arm cannot be reached by anything that came through either. It is kept as the backstop for the path that did not: a form built by hand and handed straight to [expr]. *) | Sym "quasiquote" -> fail f "a quasiquote reached the parser undesugared, which means this form \ did not come through Parse.program or Parse.decl" (* Not a milestone, a mistake: these two mean nothing anywhere else, and the reader cannot tell, because it does not track where it is. *) | Sym "unquote" -> fail f "~x means nothing outside a quasiquote" | Sym "unquote-splicing" -> fail f "~@x means nothing outside a quasiquote, and splices only into a \ list or a vector" (* A declaration is not an expression, and this arm says so for all of them rather than for [defmacro] alone. It used to be that one, because it was the only head anyone typed by mistake; now that [Parse.expr] expands, a macro can *produce* one, and the head this dispatches on is the only place that sees it — the walk recurses, so a [defn] nested inside what a macro answered is caught with the same message as one typed at the top. A quasiquoted declaration is deliberately not caught: after desugaring, the name in ``(defn ...)`` is a string inside a [Form.Sym] argument and not a head, which is the same property that makes a quasiquoted macro call output rather than a dependency. Building a declaration as a value is what a macro is for. *) | Sym ("defmacro" | "defn" | "def" | "defonce" | "defconst" | "defstruct" | "defdata" | "defunion" | "defclass" | "defgeneric" | "defmulti" | "defmethod" | "defenum" | "defalias" | "import" as name) -> fail f "%s defines a name at the top level, so it cannot be used as an \ expression here" name (* Recognised, deliberately unimplemented. Rejected rather than left to fall through to Call, where they would parse and mean nothing. *) (* [find-restart] and [compute-restarts] are §4's two ways to look at the restart stack without committing to one. *) | Sym ("find-restart" | "compute-restarts" | "errdefer" | "await" as name) -> fail f "%s is not implemented yet" name (* ── field access: (.pos c) ────────────────────────────────────── *) | Sym s when String.length s > 1 && s.[0] = '.' -> let field = String.sub s 1 (String.length s - 1) in (match args with | [ target ] -> mk (Ast.Field (expr target, field)) | _ -> fail f "field access is (.%s value)" field) (* ── struct literal: (Cursor {.src s .pos 0}) ───────────────────── *) (* Only braces whose first form is a [.field] symbol — or the empty braces, which have always meant the zero-initialised struct here. A map literal as an argument, (f {:a 1}), keeps its head as an ordinary call. *) | Sym name when args <> [] && is_struct_map (List.hd args) -> (match args with | [ { v = Map kvs; _ } ] -> mk (Ast.Struct (name, struct_fields f kvs)) | _ -> fail f "a struct literal is (%s {.field value ...})" name) (* ── anything else is a call ────────────────────────────────────── *) | _ -> mk (Ast.Call (expr head, List.map expr args)) and is_struct_map (f : Form.t) = match f.v with | Map [] -> true | Map ({ v = Sym s; _ } :: _) -> String.length s > 1 && s.[0] = '.' | _ -> false (* A map literal's braces hold key/value pairs, each an expression. *) and map_pairs f (items : Form.t list) : (Ast.expr * Ast.expr) list = let rec go = function | [] -> [] | k :: v :: rest -> (expr k, expr v) :: go rest | [ odd ] -> Loc.fail odd.Form.loc "a map literal is key/value pairs, and this one has an odd number of \ forms — found %s with no value" (Form.to_string odd) in ignore f; go items and body_of (items : Form.t list) : Ast.expr list = List.map expr items (* A loop label, or a [break]'s target: a leading keyword, peeled off. Nothing else in any of these positions is a keyword — a loop condition is not, a [dotimes] binding vector is not, and [break] takes nothing else at all — so one function serves all four forms and no form has to say which arguments it has counted. *) and label (items : Form.t list) : string option * Form.t list = match items with | { v = Kw k; _ } :: rest -> (Some k, rest) | _ -> (None, items) (* A loop's binding vector. Pairs like [let]'s, but plain names only: a destructuring pattern expands to several bindings from one form, and then [recur]'s argument count would no longer match what is written here. *) and loop_bindings f (items : Form.t list) : (string * Ast.expr) list = let rec go = function | [] -> [] | name :: value :: rest -> no_pattern name; (sym name, expr value) :: go rest | [ odd ] -> Loc.fail odd.loc "binding %s has no value — loop takes name/value pairs" (Form.to_string odd) in let bs = go items in List.iter (fun (n, _) -> if List.length (List.filter (fun (m, _) -> m = n) bs) > 1 then fail f "%s is bound twice in this loop" n) bs; bs and bindings f (items : Form.t list) : Ast.binding list = (* [name value ...] and [name Type value ...] both read; a type is a form that is not a value position — disambiguated by pair vs triple is ambiguous, so let requires (let [name value]) and types are inferred. Annotated locals are not needed by any acceptance program. *) let rec go = function | [] -> [] | pat :: value :: rest -> let bs = destructure pat (expr value) in no_duplicates pat bs; bs @ go rest | [ odd ] -> Loc.fail odd.loc "binding %s has no value — let takes name/value pairs" (Form.to_string odd) in (* [(let [x i32 5] ...)] is the first thing anyone arriving from a typed language writes, and [let] has no annotation slot: the [i32] is read as [x]'s value and the [5] is left with no name, so the refusal was "binding 5 has no value", which reads as if the writer had miscounted. Only checked when the count is odd — that is, only on the path that was about to refuse anyway — so a binding vector that parses is never examined for this. The test for "this names a type" is syntactic, because nothing is resolved at parse time: a primitive's name, or a capitalised one, which is the convention the whole corpus keeps and the only two spellings somebody writes an annotation with. *) let annotation () = let type_shaped (x : Form.t) = match x.Form.v with | Form.Sym n -> List.mem n Types.primitive_names || (n <> "" && n.[0] = Char.uppercase_ascii n.[0] && n.[0] <> Char.lowercase_ascii n.[0]) | _ -> false in let rec scan i = function | a :: b :: rest -> if i mod 2 = 1 && type_shaped a then Some (a, b) else scan (i + 1) (b :: rest) | _ -> None in scan 0 items in if items = [] then Loc.fail f.loc "let needs at least one binding" else begin if List.length items mod 2 = 1 then (match annotation () with | Some (t, v) -> Loc.failk "parse/let-type-annotation" t.Form.loc "a let binding takes no type annotation, so %s here is read as the \ value and %s is left with no name. Write the pair alone — the \ type is inferred from the value" (Form.to_string t) (Form.to_string v) | None -> ()); go items end (* ── Destructuring ─────────────────────────────────────────────────── *) (* The temporary every pattern binds its value to before anything reads it, so that the value is evaluated once however many names come out of it. Returning the reference as well as the binding is what makes the two impossible to separate by accident. *) and temp (p : Form.t) (v : Ast.expr) : Ast.expr * Ast.binding = let t = fresh_temp "destructure" in ({ Ast.e = Ast.Var t; loc = p.loc }, { Ast.bname = t; bty = None; bval = v; bloc = p.loc }) (* Clojure's destructuring, desugared here into the bindings and field accesses the language already has. [Ast.binding] carries a name and nothing else, and deliberately so: nothing downstream — not [Load]'s renaming, not [Check], not any backend — learns that a pattern exists. The same reason [dotimes] is a [Let] plus a [While]. The one thing this cannot decide is whether an array pattern's arity matches the value's, because that is a type and there are none here. [destructure~nth] carries the question to [Check], which answers it and emits an ordinary [at]. A binding is a pattern only when it is written in brackets or braces; a bare name is what it always was. *) and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list = match p.v with | Sym name -> [ { Ast.bname = name; bty = None; bval = v; bloc = p.loc } ] (* The value goes into a temporary first, so it is evaluated once however many names the pattern binds, and so that [(let [{:keys [p]} p] ...)] reads the old [p] rather than the one it is in the middle of rebinding. *) | Map items -> let t, bind = temp p v in bind :: dmap p t items | Vec items -> let t, bind = temp p v in bind :: dvec p t items | _ -> fail p "expected a name or a destructuring pattern, found %s — a pattern is \ {:keys [x y]} over a struct or [a b] over a fixed array" (Form.to_string p) (* {:keys [x y]}, {.x .y} and {inner .field}, over a struct. Clojure's map destructuring with Flan's structs standing in for its maps: [:keys] is the common case and the pair form is what nests, since a [:keys] entry is a name and never a pattern. Everything else Clojure puts in this position — [:as], [:or], [:strs], [:syms] — is refused by name where it is written. [{.x .y}] is [:keys]'s other spelling and the shortest one: a lone [.field] with no pattern before it binds a local of the field's own name. It is what [:keys] would have been if the language had only ever had structs — a struct's fields are typed and known, so naming one is naming the binding — and it puts the field syntax in the place the rest of the language spells a field. [:keys] stays, because a dyn map's keys are not field names and that is the form they will keep. This arm comes before the pair arm and has to: a lone [.x] is a [Sym], and [destructure] takes any [Sym] as a name, so before this existed [{.x .y}] parsed as the pair "bind a local called [.x] to field [y]" and the program failed later with "unknown name x" — a mis-parse rather than a refusal. An odd number of them hit the [has no .field] arm instead. So the dot in head position did have a meaning here, and this replaces it with the one that was wanted. [:keys] keeps its colon while [.field] takes the dot, and the split is the point rather than an inconsistency: [.field] names a field of the struct, [:keys] names no field at all — it is an instruction to the compiler that happens to sit in the same brace. Keeping them apart leaves the dot meaning exactly one thing, "this names a field", which is the whole reason the colon was given up here. *) and dmap (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list = let ex loc e : Ast.expr = { Ast.e; loc } in let field loc name = ex loc (Ast.Field (t, name)) in let dotted s = String.length s > 1 && s.[0] = '.' in let rec go = function | [] -> [] (* The shorthand. Checked first, so a [.field] in head position is never read as a name to bind. *) | ({ v = Sym s; _ } as fform) :: rest when dotted s -> let name = String.sub s 1 (String.length s - 1) in { Ast.bname = name; bty = None; bval = field fform.loc name; bloc = fform.loc } :: go rest | { v = Kw "keys"; _ } :: names :: rest -> let ns = match names.v with | Vec ns -> ns | _ -> Loc.fail names.loc ":keys takes a bracketed list of field names, found %s" (Form.to_string names) in let rec each = function | [] -> [] | (n : Form.t) :: more -> let name = match n.v with | Sym s -> s | _ -> Loc.fail n.loc ":keys binds field names, and %s is not one — a nested pattern \ is written {%s .field}" (Form.to_string n) (Form.to_string n) in { Ast.bname = name; bty = None; bval = field n.loc name; bloc = n.loc } :: each more in each ns @ go rest | ({ v = Kw k; _ } as bad) :: _ :: rest -> ignore rest; Loc.fail bad.loc ":%s is not implemented in a destructuring pattern — a struct pattern \ is {:keys [x y]} or {name .field}, and nothing else" k | pat :: ({ v = Sym s; _ } as fform) :: rest when String.length s > 1 && s.[0] = '.' -> destructure pat (field fform.loc (String.sub s 1 (String.length s - 1))) @ go rest | pat :: ({ v = Kw fld; _ } as bad) :: _ -> ignore pat; Loc.fail bad.loc "a field label is written .%s, not :%s — the colon is for keys, and a \ struct pattern binds {name .%s}" fld fld fld | pat :: other :: _ -> Loc.fail other.loc "expected .field after %s, found %s — a struct pattern binds \ {name .field}" (Form.to_string pat) (Form.to_string other) | [ odd ] -> Loc.fail odd.loc "%s has no .field — a struct pattern comes in pairs" (Form.to_string odd) in if items = [] then fail p "an empty struct pattern {} binds nothing — write the names it should bind" else go items (* [a b] and [a b & rest], over a fixed array. Not over a slice: see [Check]. *) and dvec (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list = let ex loc e : Ast.expr = { Ast.e; loc } in let var loc n = ex loc (Ast.Var n) in let rec split acc = function | [] -> (List.rev acc, None) | ({ v = Sym "&"; _ } as amp) :: rest -> (match rest with | [ r ] -> (List.rev acc, Some r) | [] -> Loc.fail amp.loc "& needs a name after it, as in [a b & rest]" | _ :: extra :: _ -> Loc.fail extra.loc "& takes one name and it is the last thing in the pattern") | x :: rest -> split (x :: acc) rest in let elems, rest = split [] items in let n = List.length elems in (match elems, rest with | [], None -> fail p "an empty array pattern [] binds nothing — write the names it should bind" | [], Some r -> Loc.fail r.loc "[& %s] binds the whole value — write %s on its own instead of a pattern" (Form.to_string r) (Form.to_string r) | _ -> ()); (* With a [& rest] the pattern says "at least this many"; without one it says "exactly this many". [Check] is where the array's length is known, so the count and which of the two it means travel there as arguments. *) let exact = if rest = None then 1L else 0L in let nth i = ex p.loc (Ast.Call (var p.loc "destructure~nth", [ t; ex p.loc (Ast.Int (Int64.of_int i)); ex p.loc (Ast.Int (Int64.of_int n)); ex p.loc (Ast.Int exact) ])) in let rec each i = function | [] -> [] | e :: more -> destructure e (nth i) @ each (i + 1) more in let rest_binding = match rest with | None -> [] | Some r -> (* An ordinary (slice t n (length t)): the tail of the temporary, which is a local and outlives the body that reads it. Nothing new. *) let name = match r.v with | Sym s -> s | _ -> Loc.fail r.loc "& binds one name for the tail, and %s is not one — the tail is a \ slice, so it cannot be destructured further" (Form.to_string r) in [ { Ast.bname = name; bty = None; bloc = r.loc; bval = ex r.loc (Ast.Call (var r.loc "slice", [ t; ex r.loc (Ast.Int (Int64.of_int n)); ex r.loc (Ast.Call (var r.loc "length", [ t ])) ])) } ] in each 0 elems @ rest_binding (* One pattern binding the same name twice is a mistake, not a shadowing: the second would win and the first would bind nothing. Across a let's bindings it *is* shadowing and stays legal, so this looks at one pattern at a time. *) and no_duplicates (p : Form.t) (bs : Ast.binding list) = let rec go seen = function | [] -> () | (b : Ast.binding) :: rest -> if String.contains b.Ast.bname '~' then go seen rest else if List.mem b.Ast.bname seen then Loc.fail b.Ast.bloc "this pattern binds %s twice" b.Ast.bname else go (b.Ast.bname :: seen) rest in ignore p; go [] bs (* A field label is a dot, never a colon. The delimiter is what disambiguates: [(.x v)] is a call and therefore an access, [{.x 1.0}] is a brace form and therefore a construction. The colon is left for keys — map keys and enum members — so the two never share a spelling. *) and struct_fields f (items : Form.t list) : (string * Ast.expr) list = let rec go = function | [] -> [] | { v = Sym s; _ } :: value :: rest when String.length s > 1 && s.[0] = '.' -> (String.sub s 1 (String.length s - 1), expr value) :: go rest | ({ v = Kw k; _ } as bad) :: _ :: _ -> Loc.fail bad.loc "a field label is written .%s, not :%s — the colon is for keys, and a \ struct value is (Type {.%s value ...})" k k k | other :: _ :: _ -> Loc.fail other.loc "expected .field, found %s" (Form.to_string other) | [ odd ] -> Loc.fail odd.loc "field %s has no value" (Form.to_string odd) in ignore f; go items and cond f (args : Form.t list) : Ast.expr = let rec go = function | [] -> { Ast.e = Ast.Do []; loc = f.loc } (* no clause matched: Unit *) | { v = Kw "else"; _ } :: body :: _ -> expr body | test :: body :: rest -> { Ast.e = Ast.If (expr test, expr body, Some (go rest)); loc = f.loc } | [ odd ] -> Loc.fail odd.loc "cond clause %s has no body" (Form.to_string odd) in if args = [] then Loc.fail f.loc "cond needs at least one clause" else go args (* Every test here is an [if]'s condition, so a dyn operand is truthy-tested (check.ml's check_truthy) exactly the way a bare [if]'s is, for both [and] and [or]. The *answer* is the operand that decided the form, which is Clojure's rule and needs the operand a second time: [(or a b)] is [(let [t a] (if t t b))] and [(and a b)] is [(let [t a] (if t b t))]. The temp is what makes that a single evaluation — writing the operand itself into the arm, as [(if a a b)] would, evaluates it twice. Both used to answer a bare bool sentinel on the deciding path instead. [and]'s "false" sentinel sat in the else arm, so check_if typed the real branch first and boxed the sentinel to match: an all-truthy [and] did hand back its last operand, but a falsey one answered [false] where Clojure answers the falsey operand itself — (and 1 nil) said false, not nil. [or]'s "true" sentinel sat in the then arm, the one check_if types first, so the sentinel decided the whole expression's type and a later non-bool dyn answer hit the strict bool boundary instead of surviving as itself: (or nil "x") trapped rather than answering "x", exactly the canonical (or x default) idiom Clojure is reached for. The locs are the operand's own, not the whole form's, because the temp's [Var] node is what lands in the [if] condition and check_truthy reports the condition's loc when a typed operand is not a bool. Pointing that at [f.loc] would blame the enclosing (and ...) for whichever operand is actually wrong. What answering the operand costs, for both forms alike: the two arms are now both real values, so mixing a dyn operand with a typed bool one makes check_if unify them, and the then arm decides. A non-bool dyn value on the losing side then meets the strict bool boundary at run time — (or false (box "s")) and (and (box nil) some-bool) both trap, verified on this tree. Each form used to be safe in exactly one of those directions, because the sentinel it answered was a bool literal that boxed to fit whatever the real branch was; neither is now, and they are at least symmetric about it. Making bool and dyn arms join as dyn is a check_if question, noted in FIX.org under item 7 and not decided here. One known wart, measured rather than guessed, and left alone deliberately. In a want-free position — [(println (and true true (vec-new i32)))] — the caret lands on the *second* [true] and not on the vec: the last operand is the then arm, check_if types the then arm first, and the mismatch is therefore reported against the else arm, which is the previous operand's temp. An operand anywhere but last is a condition instead, so check_truthy blames it at its own loc and the caret is right; [or] is right everywhere, because there the chain and not the sentinel sits in the else arm. Giving the else arm's [Var] node the *last* operand's loc moves the caret onto the vec and makes the sentence read backwards — "expected (Vec i32), found bool" under a caret on the thing that is the (Vec i32) — so it is not an improvement; answering a bool literal again would revert the paragraph above; and inverting the condition to move the last operand into the else arm buys a [not] per operand and worse locs than it fixes. What would actually fix it is check_if preferring the arm that is not a compiler temp when it reports, which is check.ml's call. Written up in FIX.org. *) and shortcircuit f (args : Form.t list) ~is_and : Ast.expr = let mk e = { Ast.e; loc = f.loc } in let rec go = function | [] -> mk (Ast.Var (if is_and then "true" else "false")) | [ last ] -> expr last | x :: rest -> let ex = expr x in let t = fresh_temp (if is_and then "and" else "or") in let tvar = { Ast.e = Ast.Var t; loc = ex.Ast.loc } in let bind = { Ast.bname = t; bty = None; bval = ex; bloc = ex.Ast.loc } in let rest = go rest in let body = if is_and then mk (Ast.If (tvar, rest, Some tvar)) else mk (Ast.If (tvar, tvar, Some rest)) in mk (Ast.Let ([ bind ], [ body ])) in go args and place (f : Form.t) : Ast.place = match f.v with | Sym s -> Ast.Pvar s | List ({ v = Sym s; _ } :: args) when String.length s > 1 && s.[0] = '.' -> let field = String.sub s 1 (String.length s - 1) in (match args with | [ target ] -> Ast.Pfield (expr target, field) | _ -> fail f "field place is (.%s value)" field) | List ({ v = Sym "at"; _ } :: target :: idx) when idx <> [] -> Ast.Pindex (expr target, List.map expr idx) (* Not a place. spec-memory.md gives a map an upsert of its own — [put] either inserts or replaces — so there is no store into a lookup, and an entry that is absent has no location to store into. Refused here rather than parsed into a place form the language does not have. *) | List ({ v = Sym "get"; _ } :: _) -> fail f "(get m k) is not a place — a map is written with (put m k v)" | List [ { v = Sym "deref"; _ }; p ] -> Ast.Pderef (expr p) | _ -> fail f "%s is not assignable. set takes a name, (.field x), (at a i ...), \ or (deref p)" (Form.to_string f) and arms f (items : Form.t list) : Ast.arm list = let rec go = function | [] -> [] | p :: body :: rest -> { Ast.pat = pattern p; body = [ expr body ]; aloc = p.loc } :: go rest | [ odd ] -> Loc.fail odd.loc "match arm %s has no body" (Form.to_string odd) in if items = [] then Loc.fail f.loc "match needs at least one arm" else go items and pattern (f : Form.t) : Ast.pattern = match f.v with | Sym "_" -> Ast.Pwild | Kw "else" -> Ast.Pwild | Sym ctor -> Ast.Pctor (ctor, []) (* An enum member, which is the one other thing [match] could plausibly be over: an enum is an i32 at run time, so the arms would be a chain of [=] and the members are all known, which is exhaustiveness [cond] cannot give. What stops it is not the lowering, it is that a keyword pattern needs a case in [Ast.pattern] — and [lib/load.ml] matches that type exhaustively, so the variant cannot be added from here. Refused by name rather than spelled as a constructor it is not. *) | Kw member -> fail f ":%s is not implemented as a pattern — use cond with (= k :%s)" member member | List ({ v = Sym ctor; _ } :: binds) -> List.iter no_pattern binds; Ast.Pctor (ctor, List.map sym binds) | _ -> fail f "expected a pattern, found %s" (Form.to_string f) (* ── The third element of a defonce or a def ─────────────────────────── [(defonce x i32)] declares a zeroed static and [(defonce score 0)] declares a dyn global holding 0, and which one a form is is decided by whether the third element is a type. [def] takes exactly the same spellings — the two forms differ only in what a re-run does. The author's rule, 2026-09-20: "if it's 3 atoms then it's dyn", and "dispatch the if it's a type do the right thing". Most forms are settled by their shape alone and are settled here: [0], a string, a map, [[1 2 3]] and [(f "x")] are not types by any reading, so the global is dyn and its initialiser is the expression; [[4 u32]], [()] and [(Fn [i32] i32)] are types by any reading and keep exactly the meaning they have today. Note which side the bracket falls on: [[n T]] stays a fixed array, so [(defonce rows [4 u32])] is the zeroed grid it always was, and a *vector literal* of two names is not reachable in this position. Two shapes are left over, and they are the ones a name decides rather than a shape: a bare symbol, which is a type name or a value's name, and [(head arg ...)] with every argument type-shaped, which is [(Vec i32)] or a call. Both readings are built and carried — the [texpr] in the [Defvar] and the [Ast.Ambiguous] expression beside it — and [Check.collect] picks the type reading whenever the form is a type. Nothing here resolves a name, because at parse time there are none. [texpr] is called under a handler on purpose: "does this parse as a type" is the question, and its refusals are how it answers no. It builds an AST and touches nothing else, so there is nothing to undo when it raises. *) let defvar3 (f : Form.t) : Ast.texpr * Ast.init = let as_type () = match texpr f with t -> Some t | exception Loc.Error _ -> None in let dyn = { Ast.t = Ast.Tname "dyn"; tloc = f.loc } in match f.v with | Sym _ -> (match as_type () with (* [Sym "Unit"] is the one symbol [texpr] refuses outright — unit is spelled [()] — and the refusal is about the spelling of a type, so it stays the error it is rather than becoming a read of a variable nobody can have declared. *) | None -> (texpr f, Ast.Zeroed) | Some t -> (t, Ast.Ambiguous (expr f))) | List ({ v = Sym _; _ } :: _ :: _) -> (match as_type () with | Some ({ Ast.t = Ast.Tapp _; _ } as t) -> (t, Ast.Ambiguous (expr f)) (* [(Fn [i32] i32)] and anything else [texpr] reads as a type without going through [Tapp] has no call reading to be confused with. *) | Some t -> (t, Ast.Zeroed) | None -> (dyn, Ast.Init (expr f))) | _ -> (match as_type () with | Some t -> (t, Ast.Zeroed) | None -> (dyn, Ast.Init (expr f))) (* ── Declarations ──────────────────────────────────────────────────── *) let rec decl (f : Form.t) : Ast.decl = let mk d = { Ast.d; dloc = f.loc } in match f.v with | List ({ v = Sym "package"; _ } :: args) -> (match args with | [ n ] -> mk (Ast.Package (sym n)) | _ -> fail f "package is (package name)") | List ({ v = Sym "import"; _ } :: args) -> (match args with | [ alias; { v = Str path; _ } ] -> mk (Ast.Import (sym alias, path)) | _ -> fail f "import is (import alias \"collection:path\")") | List ({ v = Sym "defalias"; _ } :: args) -> (match args with | [ n; t ] -> mk (Ast.Defalias (sym n, texpr t)) | _ -> fail f "defalias is (defalias Name Type)") | List ({ v = Sym "defstruct"; _ } :: args) -> (match args with | [ n; { v = Vec fs; _ } ] -> mk (Ast.Defstruct (sym n, fields f fs)) | _ -> fail f "defstruct is (defstruct Name [field Type ...])") | List ({ v = Sym "defdata"; _ } :: args) -> (match args with | [ n; { v = Vec vs; _ } ] -> mk (Ast.Defdata (sym n, List.map variant vs)) | _ -> fail f "defdata is (defdata Name [(Case [field Type ...]) ...])") (* C's union: one storage, as many ways of reading it as there are members. It carries a field list and not a case list, which is the whole surface difference from [defdata] — there is no tag, so there is nothing to name a case with. The tagged sum was spelled [defunion] until this form wanted the name, and a file written before the rename is the hazard this arm exists for. It is not an alias and it is not a near-miss: the old text would *parse* under the new meaning. [(defunion U [A B])] is two bare symbols, which is exactly the shape of one member [A] of type [B], and it would have gone on compiling as an untagged union of one member — the silent misparse the [defn] case above was rewritten to make impossible, with no diagnostic anywhere and nothing in the source that looks wrong. So the name slots are read before anything is built. A member name is lowercase and a case name is capitalised, and a case *with* fields is a list where a member name would be; either one means the text in hand is a tagged sum wearing the old spelling, and it is refused by name. A file that really did mean an untagged union whose first member is capitalised is refused too, and it is the right trade: that is not a thing anyone has written, and being told to rename a member is nothing beside being given the wrong type in silence. *) | List ({ v = Sym "defunion"; _ } :: args) -> (match args with | [ n; { v = Vec ms; _ } ] -> List.iteri (fun i (m : Form.t) -> let looks_tagged = i mod 2 = 0 && (match m.v with | List _ -> true | Sym s -> s <> "" && s.[0] = Char.uppercase_ascii s.[0] | _ -> false) in if looks_tagged then Loc.failk "parse/defunion-renamed" f.loc "defunion is C's untagged union, written (defunion Name \ [member Type ...]). This reads as a tagged sum — write \ (defdata Name [(Case [field Type ...]) ...])") ms; mk (Ast.Defunion (sym n, fields f ms)) | _ -> fail f "defunion is (defunion Name [member Type ...])") (* The slot after the parameters is unconditionally the return type. It used to be optional, and the parser decided return-type-versus-body by looking the symbol up in a set of the file's type names -- sound only because one top-level namespace means a name cannot be both a type and a value, and brittle because the set had to be complete. It was wrong twice in one day, the second time parsing [(defn f [] (Rune {.code 65}) (bar))] as a function *returning* a Rune with a one-form body, silently, in every file in the language. A silent misparse is the worst failure class available, and macros now generate definitions, which widens it. Mandatory removes the guess: nothing is consulted, [()] is what a function that returns nothing writes, and a mistyped type is a mistyped type -- [(defn f [] f65 0.0)] reaches the resolver's near-miss check and comes back as *did you mean f64*, where it used to come back as an unknown name. The return slot stays mandatory now that parameters may be left unannotated, and it is worth saying why the two do not move together. Dynamic-by-default means a *parameter* with no type is [dyn]; the return type could have been given the same rule, and was not, because the ambiguity there has no syntactic resolution at all. [(defn f [] (Rune {.code 65}) (bar))] is the case above: a capitalised head in a list is a type application and also a struct literal -- see [Struct] in [expr] -- and no rule separates them, so an optional return slot is a coin toss between a type and the first form of a body. A parameter vector has no such case: every slot in it is a name or a type and never an expression. So [dyn] is written out in the return position, which costs one token and keeps a decision this file paid for twice in one day. ── The parameter vector ────────────────────────────────────────────── [(defn f [x y])] is one parameter [x] of type [y], or two parameters [x] and [y] of type [dyn], and which one it is depends on whether [y] names a type. That is the lookup this comment's first half says was removed for being brittle, and it is being asked for again -- so it is not done here. The vector is carried undecided, as [Ast.pitem]s, and paired in [Check], where the set of type names is complete. The move is not cosmetic. What the old rule got wrong was consulting a set that was not finished being built: it ran per-file, at parse time, before macros had generated their definitions, and macros generating definitions is exactly what widened the failure. By the time [Check] pairs the vector, every file is loaded, every macro has expanded and every C header has been imported, so the set is not a guess about what might be a type -- it is the types. That is strictly more than the parser could ever know, and it is the whole of the argument for the placement. What deferring does not buy is immunity. The set is complete at a point in time and not across time: [(defn f [x y] ...)] is two dyn parameters until somebody writes [(defstruct y ...)] or imports a header that declares one, and then it is one parameter of type [y], with no edit to [f]. The signature changes under it. That residual is real, it is the dictated rule's and not this file's, and [Session.compatible] is where it is felt -- a redefinition that changes a signature is refused there, and this is a way for a signature to change with nothing redefined. *) | List ({ v = Sym "defn"; _ } :: args) -> (match args with | n :: { v = Vec ps; _ } :: ret :: body -> (* The slot's own failure, because the thing found there is almost always the old spelling: a body whose first form was a call, written when the slot could be left out. [texpr]'s "expected a type" alone would be true and unhelpful. *) let rty = try texpr ret with | Loc.Error { Loc.dloc = inner; dmsg = msg; _ } -> (* The slot, not whatever inside it [texpr] happened to give up on: for [(defn f [x i32] (+ x 1))] that was the [1], three forms deep, where the mistake is that the whole form is in the return slot. What [texpr] said keeps its own span as a note, because it is still the reason. *) if inner.Loc.line = ret.Form.loc.Loc.line && inner.Loc.col = ret.Form.loc.Loc.col then (* [texpr] gave up on the slot form itself, so what it said is already about the right thing — [unit is written (), not Unit] leads, and the slot's own clause follows it. *) Loc.failk "parse/return-type-expected" inner "%s — this is the return type, which every defn states, and a \ function that returns nothing writes ()" msg else Loc.failk "parse/return-type-expected" ret.Form.loc ~notes:[ Loc.note inner msg ] "the return type goes here, and this is %s — every defn states \ one, and a function that returns nothing writes ()" (Form.to_string ret) in let fwhere, body = constraints body in mk (Ast.Defn { Ast.name = sym n; params = []; praw = Some (pitems ps); ret = Some rty; fwhere; fbody = body_of body; nloc = n.loc }) | _ -> fail f "defn is (defn name [param Type ...] ReturnType body ...). The return \ type is not optional; a function that returns nothing writes ()") (* ── The dyn side's classes and generic functions ────────────────── Four forms, all of them shorthand: nothing below [Classes.expand] knows they exist, and what it writes in their place is ordinary [defn]s. The parsing here is only the shape check — which slots are present, and what kind of thing is in each — because everything that needs the other declarations to answer (is that a class? is there a generic by that name? has this dispatch value a method already?) is the expansion's. Every parameter of a generic and of a method is [dyn], written or not, so a parameter vector here takes bare names and nothing else. That is what keeps these off [defn]'s undecided-pairing path: a [defn]'s vector cannot be read until every type name is known, and one that may hold only names can be read here. *) | List ({ v = Sym "defclass"; _ } :: args) -> (match args with | [ n; { v = Vec slots; _ } ] -> mk (Ast.Defclass (sym n, List.map (fun (s : Form.t) -> match s.v with | Sym name -> (name, s.loc) | _ -> fail s "a class slot is a name — its value is dyn, so there \ is no type to write. Read one with (get p :%s)" (Form.to_string s)) slots)) | _ -> fail f "defclass is (defclass Name [slot ...])") | List ({ v = Sym ("defgeneric" | "defmulti" as which); _ } :: args) -> let generic = String.equal which "defgeneric" in let usage = if generic then "defgeneric is (defgeneric name [param ...] ReturnType). It has no \ body: its dispatch value is the class of its first argument, which \ is what makes it the class-dispatching half of the pair. Write \ defmulti for a dispatch value of your own" else "defmulti is (defmulti name [param ...] ReturnType body ...), and the \ body is the dispatch: it answers the value the methods are keyed by" in (match args with | n :: { v = Vec ps; _ } :: ret :: body when if generic then body = [] else body <> [] -> mk ((if generic then (fun fn -> Ast.Defgeneric fn) else fun fn -> Ast.Defmulti fn) { Ast.name = sym n; params = dyn_params which ps; praw = None; ret = Some (texpr ret); fwhere = []; fbody = body_of body; nloc = n.loc }) | _ -> fail f "%s" usage) | List ({ v = Sym "defmethod"; _ } :: args) -> (match args with | n :: key :: { v = Vec ps; _ } :: body when body <> [] -> let gen = sym n and k = dispatch key in mk (Ast.Defmethod { Ast.mgen = gen; mkey = k; mkloc = key.loc; (* The name is the declaration's, not a symbol anything emits: no function is ever written under it. *) mfn = { Ast.name = gen ^ "@" ^ Ast.dispatch_text k; params = dyn_params "defmethod" ps; praw = None; ret = None; fwhere = []; fbody = body_of body; nloc = n.loc } }) | _ -> fail f "defmethod is (defmethod generic dispatch [param ...] body ...). \ There is no return type: the generic states it once, for every \ method written for it") | List ({ v = Sym ("declare" | "declare-c" as which); _ } :: args) -> (* (declare name [param Type ...] ReturnType? "c_symbol"). The C symbol is last and is always written: a foreign name is not derivable from a Flan one, and guessing it would fail at link time rather than here. [declare-c] is the same shape and a different claim about the symbol. [declare]'s signature IS the C signature, already flattened by whoever wrote the C; [declare-c]'s is the *library's* — structs by value — and [Shim] generates the flattening. The two cannot be one form, because (declare f [p string] ...) already means the symbol takes ptr+len and (declare-c f [p string] ...) means it takes a NUL-terminated char *. *) let mkd fn csym = if String.equal which "declare-c" then Ast.DeclareC (fn, csym) else Ast.Declare (fn, csym) in let usage = Printf.sprintf "%s is (%s name [param Type ...] ReturnType? \"c_symbol\")" which which in (match List.rev args with | { v = Str csym; _ } :: rest -> (match List.rev rest with | [ n; { v = Form.Vec ps; _ } ] -> mk (mkd { Ast.name = sym n; params = fields f ps; praw = None; ret = None; fwhere = []; fbody = []; nloc = n.loc } csym) | [ n; { v = Form.Vec ps; _ }; r ] -> mk (mkd { Ast.name = sym n; params = fields f ps; praw = None; ret = Some (texpr r); fwhere = []; fbody = []; nloc = n.loc } csym) | _ -> fail f "%s" usage) | _ -> fail f "%s" usage) (* A member's value is optional and autoincrements, which is C's rule and is here for C's reason: the enums this language writes are as often a transcription of a header as they are original, and every value written by hand is a value that can be wrong. [0 1 2 3 4] typed out is fine until a member is inserted in the middle, and then the renumbering is a manual edit of every line below it. Which values were *written* does not survive this form. [Ast.Defenum] holds resolved numbers, so by the time [Check] sees an enum the difference between a number someone chose and one autoincrement produced is gone -- and there are no per-member locations in the AST to point at either. That is why the collision rule below is enforced here and not in the checker beside the duplicate-*name* rule: this one is a question about the source text, and the parser is the last pass that can still answer it. *) | List ({ v = Sym "defenum"; _ } :: args) -> (match args with | [ n; { v = Form.Vec ms; _ } ] -> let ename = sym n in (* An enum member is an [i32] at run time. [Shim] lowers the type to int32_t for C's benefit and [Check] builds every member as a [Tast.Int (v, I32)] -- but the reader hands this pass an [int64], so a value the type cannot hold arrives here looking perfectly ordinary. Left alone it is truncated by the x86 backend and malformed in the LLVM IR, and worse than either, it defeats the duplicate-value rule below: that rule compares [int64]s, so in [(defenum E [A 0 B 4294967296])] the two values differ and the scan passes, while at run time both members are 0 and a [match] on one is unreachable through the other. The one rule written to catch two names for one number waves through the case it exists for. So every value is checked against its run-time type here -- refused if it does not fit, never truncated to fit -- the moment it is resolved and before any of that reasoning runs, and the scan below therefore compares the numbers the program will actually have. The bounds are the signed 32-bit ones and they are spelled out rather than borrowed. [Check.in_range] is the function that does exactly this for every ordinary literal, and it is the right one -- but [Check] is built above this module and reading its output, so there is no call this file could make. If one of the two changes, change the other. *) let fits m loc ~explicit (v : int64) = if Int64.compare v (-2147483648L) >= 0 && Int64.compare v 2147483647L <= 0 then v else if explicit then Loc.failk "parse/enum-value-out-of-range" loc "the member %s of %s is %Ld, which does not fit i32 — an enum's \ members run from -2147483648 to 2147483647. Give %s a value in \ that range, or use a defconst" m ename v m else Loc.failk "parse/enum-value-out-of-range" loc "the member %s of %s has no value of its own, so it \ autoincrements to %Ld, which does not fit i32 — an enum's \ members run from -2147483648 to 2147483647. Write %s's value \ out, or lower the member above it" m ename v m in (* Each member becomes its name, its value, whether that value was written, and where the name is. The last two exist only so the refusals here and below can be made; neither reaches the AST. *) let rec members next = function | [] -> [] | { v = Form.Sym m; loc } :: { v = Form.Int k; _ } :: rest -> (* The [let] is load-bearing rather than tidiness. OCaml leaves the evaluation order of [::]'s two operands unspecified and in practice takes the tail first, so an inlined [fits ... k] would run *after* the recursive call -- and for [(defenum E [A 9223372036854775807 B])] that recursive call is [Int64.add max_int 1L], which wraps quietly to min_int and would have the refusal name B and a number written nowhere in the source. Binding first refuses A, whose value is the one actually wrong, and in doing so makes the wrap unreachable: [k] is inside i32 by the time it is incremented, so the sum cannot overflow. *) let k = fits m loc ~explicit:true k in (m, k, true, loc) :: members (Int64.add k 1L) rest | { v = Form.Sym m; loc } :: rest -> let next = fits m loc ~explicit:false next in (m, next, false, loc) :: members (Int64.add next 1L) rest | bad :: _ -> fail bad "an enum member is a name, optionally followed by an integer, \ found %s" (Form.to_string bad) in let ms = members 0L ms in (* A duplicate value that was written is an alias and is meant: a [Count] or a [Last] pointing at a value another member already holds is how C spells the end of a range, and refusing it would refuse a real idiom. A duplicate that autoincrement walked into is nobody's decision. It happens when a member above is renumbered or one is inserted, and the result is two names for one number with nothing in the source saying so -- silently, and the program still compiles, and one of the two is now unreachable through a [match] on the other. That silence is the same failure class as a silent misparse, so the implicit member is refused; writing its value out is both the fix and the way to say the alias was intended. Every member is resolved before any of this runs, because the member an autoincrement collides with is as often below it as above: in [(defenum E [A B 0])] it is [A], the implicit one, that has to be refused, and a left-to-right check would never see [B] coming. *) let indexed = List.mapi (fun i (m, v, explicit, loc) -> (i, m, v, explicit, loc)) ms in List.iter (fun (i, m, v, explicit, loc) -> if not explicit then match List.find_opt (fun (j, _, ov, _, _) -> j <> i && Int64.equal ov v) indexed with | None -> () | Some (_, other, _, _, oloc) -> Loc.failk "parse/enum-autoincrement-collision" loc ~notes: [ Loc.note oloc (Printf.sprintf "%s has the value %Ld" other v) ] "%s has no value of its own, so it autoincrements to %Ld, \ which is the value %s already has. Give %s its value \ explicitly if the two are meant to be one number under two \ names, or a value no other member holds" m v other m) indexed; mk (Ast.Defenum (ename, List.map (fun (m, v, _, _) -> (m, v)) ms)) | _ -> fail f "defenum is (defenum Name [member value? ...]). A member with no \ value takes the previous member's plus one, and the first takes 0") (* The two forms share one arm because they share everything but what a daemon re-run does: [defonce] initialises once and keeps its value, [def] runs its initialiser on every re-run. The author's rule (FIX.org, 2026-09-20): def, defonce and defconst decide what a re-run does, not the daemon. *) | List ({ v = Sym ("defonce" | "def" as form); _ } :: args) -> let kind = if String.equal form "def" then Ast.Every else Ast.Once in (match args with | [ n; t ] -> let ty, init = defvar3 t in mk (Ast.Defvar (sym n, Some ty, init, kind)) | [ n; t; { v = Sym "uninit"; _ } ] -> mk (Ast.Defvar (sym n, Some (texpr t), Ast.Uninit, kind)) | [ n; t; v ] -> mk (Ast.Defvar (sym n, Some (texpr t), Ast.Init (expr v), kind)) | _ -> fail f "%s is (%s name Type value?) or (%s name value) — a third element \ that is not a type is the value of a dyn global" form form form) (* [defvar] is caught by name rather than left to fall through to "unknown function", because two forms answer it and a did-you-mean over one name could only ever offer one of them. It says there is no defvar, not that defvar was renamed. The reader has this compiler and nothing else: a rename is a fact about our history, and what they need is the name that exists and what it does. *) | List ({ v = Sym "defvar"; _ } :: args) -> (* The rest of the form is echoed back inside the two spellings, so the answer is a line that can be pasted. That only holds for a form long enough to make a valid one: the shortest defonce is [(defonce name value)], so an old form with fewer than two arguments has nothing to paste and echoing it would answer [(defvar x)] with [(defonce x)] — a malformed old form given a malformed new one, which is the standing rule against a suggestion that does not compile. The shapes alone then, which is what there is to say about a form that named too little. *) (match args with | [] | [ _ ] -> Loc.failk "parse/defvar-renamed" f.loc "there is no defvar. Did you mean defonce? \ (defonce name Type value?) initialises once and keeps its value; \ (def name Type value?) re-initialises on every re-run" | _ -> let rest = String.concat " " (List.map Form.to_string args) in Loc.failk "parse/defvar-renamed" f.loc "there is no defvar. Did you mean defonce? (defonce %s) initialises \ once and keeps its value; (def %s) re-initialises on every re-run" rest rest) | List ({ v = Sym "defconst"; _ } :: args) -> (match args with | [ n; v ] -> mk (Ast.Defconst (sym n, None, expr v)) | [ n; t; v ] -> mk (Ast.Defconst (sym n, Some (texpr t), expr v)) | _ -> fail f "defconst is (defconst name Type? value)") (* A macro is an ordinary function, and this is where it becomes one: [(defmacro m [a b & body] ...)] is [(defn m [macro~args [Form]] Form (let [a (at macro~args 0) b (at macro~args 1) body (form-rest macro~args 2)] ...))]. There is no [Ast.Defmacro] and there is not going to be one -- a macro has the type [[Form] -> Form], it is compiled by the same backend as everything else, and the only thing that makes it a macro is that [Expand] calls it at compile time instead of the program calling it at run time. So the declared type is what it always was: one parameter, the slice of the argument forms. What changed is that the parameter is the compiler's now and the author writes a real list against it — positional names, a [ ] pattern wherever an argument is a vector, and [&] for the tail — which opens the body as bindings over that slice. [Expand]'s [msig] is the reading of the list, and [Macro] checks a *call* against the same reading before expanding it, which is where arity and shape are refused with the call's own location. The one breaking change in this: [[args]] used to bind the whole argument list and now binds the first argument, because one grammar that means one thing everywhere is worth more than a legacy spelling. The whole list is [[& args]], and every macro in the tree was migrated to it. *) | List ({ v = Sym "defmacro"; _ } :: args) -> (match args with | n :: ({ v = Form.Vec _; _ } as ps) :: body when body <> [] -> let sg = Expand.params_of ps in let form_t = { Ast.t = Ast.Tname "Form"; tloc = f.loc } in mk (Ast.Defn { Ast.name = sym n; (* A name the reader cannot produce -- [~] opens an unquote, so no symbol read out of a source file holds one -- which is what keeps the compiler's own parameter out of the way of every name the author might bind. Same trick as [gensym]. *) params = [ { Ast.fname = macro_args; fty = { Ast.t = Ast.Tslice form_t; tloc = ps.loc }; floc = ps.loc } ]; (* Written out, not deferred: a macro takes [[Form]] and returns a [Form], and neither half of that is the user's to leave off. *) praw = None; ret = Some form_t; fwhere = []; fbody = macro_body sg body; nloc = n.loc }) | _ -> fail f "defmacro is (defmacro name [param ...] body ...)") (* Only reachable from the single-declaration entry point below: a file's forms go through [splice] first, and a [do] there is its items. Said by name because the two paths differ and the difference is not the author's fault to guess at. *) | List ({ v = Sym "do"; _ } :: _) -> fail f "a top-level (do ...) is several declarations, and this position takes \ exactly one" | List ({ v = Sym s; _ } :: _) -> fail f "unknown top-level form (%s ...)" s | _ -> fail f "expected a top-level declaration, found %s" (Form.to_string f) (* The bindings a macro body opens with, one per name its parameter list binds, in the order they are written. Built as [Form]s and handed to [expr] rather than assembled as [Ast] directly: the extraction is [(at ...)] and [(form-rest ...)] over a slice and [(let ...)] around the body, which is ordinary Flan and already has a parser. Nothing downstream learns that a macro had a parameter list, exactly as nothing downstream learns that a [let] had a pattern. The extraction is unchecked on purpose. [Macro] has already run [Expand.check_call] over this call by the time the body runs, so an [(at macro~args 2)] here is an index that was counted, and a [(form-vec-items ...)] is a form already known to be a [Form.Vec]. Checking twice would mean a second set of sentences, said from inside an expansion where the location is the call site's stamp rather than the call. *) and macro_body (sg : Expand.msig) (body : Form.t list) : Ast.expr list = let loc0 = sg.Expand.src.Form.loc in let s loc n : Form.t = Form.make (Form.Sym n) loc in let call loc xs : Form.t = Form.make (Form.List xs) loc in let idx loc i : Form.t = Form.make (Form.Int (Int64.of_int i)) loc in let nth loc src i = call loc [ s loc "at"; src; idx loc i ] in let tail loc src i = call loc [ s loc "form-rest"; src; idx loc i ] in let out = ref [] in let add n v = out := (n, v) :: !out in let rec go (p : Expand.pat) (src : Form.t) = match p with | Expand.Pname (n, loc) -> add (s loc n) src | Expand.Pvec (ps, rest, pf) -> let loc = pf.Form.loc in (* The elements of the vector, bound once: every name under this pattern reads that one slice rather than unwrapping the form again. *) let t = fresh_temp "macro" in add (s loc t) (call loc [ s loc "form-vec-items"; src ]); List.iteri (fun i q -> go q (nth loc (s loc t) i)) ps; (match rest with | None -> () | Some (r, rl) -> add (s rl r) (tail rl (s loc t) (List.length ps))) in let av = s loc0 macro_args in List.iteri (fun i p -> go p (nth loc0 av i)) sg.Expand.ps; (match sg.Expand.rest with | None -> () | Some (r, rl) -> add (s rl r) (tail rl av (List.length sg.Expand.ps))); match List.rev !out with (* [(defmacro m [] ...)] binds nothing, and [(let [] ...)] is refused a few hundred lines up. The body is the body. *) | [] -> body_of body | bs -> let items = List.concat_map (fun (n, v) -> [ n; v ]) bs in body_of [ call loc0 (s loc0 "let" :: Form.make (Form.Vec items) loc0 :: body) ] and variant (f : Form.t) : Ast.variant = match f.v with | Sym n -> { Ast.vname = n; vfields = []; vloc = f.loc } | List [ { v = Sym n; _ }; { v = Vec fs; _ } ] -> { Ast.vname = n; vfields = fields f fs; vloc = f.loc } | List [ { v = Sym n; _ } ] -> { Ast.vname = n; vfields = []; vloc = f.loc } | _ -> fail f "a data type case is Name or (Name [field Type ...])" (* Macro expansion, which runs over [Form] and therefore before anything in this file. It cannot be called directly: expanding a macro means compiling it and dlopening it, so the expander sits above [Check] and [Build] and this module sits below them. [Macro] fills this in, and lib/dune passes -linkall so that it always has -- an executable that links the library gets the installation whether or not it names the module. The default is the identity because [Macro] is what knows which names are macros; with nothing installed, a call to one arrives at the checker as an unknown name, which is wrong but not silent. *) let expander : (Form.t list -> Form.t list) ref = ref (fun fs -> fs) (* The defmacros an import brought in: already qualified under the alias the package was imported as, and already quasiquote-desugared, so they are in exactly the shape [Macro] hands its own round-0 set. A ref, for the same reason [expander] is one — [Load] sits below [Macro] and above this file, so there is no call it could make instead — and set rather than passed because the parse that needs them is not always the parse that resolved them: [Session.eval] parses one [defn] for C-c C-c, long after the import that supplied the macro it calls. [Load.program] sets it around the parse it drives and restores it afterwards; the session sets it around the whole of an evaluation. Empty is the ordinary case and costs nothing. *) let imported_macros : Form.t list ref = ref [] (* What those macros are allowed to *call*, and it is the same list the importing program gets: the package's declarations, qualified under the alias, as [Load] already built them. A macro module is compiled from the prelude plus the [defmacro]s, so until now the header's rule held without anything enforcing it — a macro body could call prelude functions and other macros and nothing else. A package macro that called one of its own package's functions was renamed to [alias/fn] by [Load.rename_form], reached the checker with nothing of that name declared, and was refused as a call into an imported package. That refusal was the machinery missing a piece rather than a rule. The rename says the intent plainly: what a package's macro answers with, and what its body calls, is spelled the way the importer spells it. So the declarations travel beside the macros and go into the module with them. [Macro.compile] prunes them to what the macros actually reach, so a package whose macros are pure quasiquote — raylib's five [with-*] — pays nothing and links nothing new. An [Ast.decl list] and not forms, because [Load] has already done the qualifying over the Ast and a second renamer over [Form] would be that work written twice, in the file where the two copies could disagree silently. *) let imported_decls : Ast.decl list ref = ref [] let with_imported ?(decls = []) (ms : Form.t list) (f : unit -> 'a) : 'a = let saved = !imported_macros in let saved_decls = !imported_decls in imported_macros := ms; imported_decls := decls; Fun.protect ~finally:(fun () -> imported_macros := saved; imported_decls := saved_decls) f (* Two entry points and not one function with a flag, and the reason is the daemon. [Loc.Errors] is a second exception, and the handlers in the session and in the daemon name only [Loc.Error] — so a list reaching them would be an unhandled exception and a dead session, which is the one thing the whole dev loop exists to prevent. A flag on the function the session already calls would put that one label away from happening. A separate name cannot: the session's call site has to be edited by someone for its behaviour to change. [keep_going] asks for every bad declaration in the file rather than the first. The resync point is a top-level form, and it is the only honest one here: the reader already found where each declaration ends, so skipping a bad one costs nothing and cannot lose its place. Inside a declaration there is no such landmark, so one bad [defn] is one error. *) (* ── One call, several declarations ──────────────────────────────── Expansion is form-for-form: [Macro.expand_form] answers one [Form.t] per input and the loop below turns each into one [Ast.decl]. Every macro written until now expands to an *expression* — [unless], [into], raylib's [with-*] — so one-for-one was the whole of what was needed. A type provider is the first thing that is not. [(defedn Tileset "t.edn")] has to produce the struct *and* the reader over it, and a nested map in the data means a struct per nesting level: three declarations and more from one form. There is no arrangement of one-for-one that reaches that. So a [do] at the top level is its items, in place. It is the sequencing spelling the language already has, it is Clojure's answer to exactly this, and it is only ever reachable by a macro: nobody writes [(do (defn ...))] in a file, and the message below still says so for anyone who tries and wrote it wrong. Recursive, because a macro that splices what another macro answered has a [do] inside a [do] and the nesting is not the author's to flatten by hand. It is spliced *after* expansion and before the declaration walk, so what is spliced is already fully expanded — a [do] holding a call to another macro settled before it got here. *) let rec splice (f : Form.t) : Form.t list = match f.Form.v with | Form.List ({ Form.v = Form.Sym "do"; _ } :: items) -> List.concat_map splice items | _ -> [ f ] let parse_forms ~keep_going (forms : Form.t list) : Ast.decl list = (* Quasiquote first and always, because it is pure and needs nothing loaded: it is what turns a macro body into ordinary code, and the prelude's own macros have to parse in a process that has not built a macro module yet. Then expansion, which may need one. *) let forms = !expander (List.map Expand.quasiquote forms) in let forms = List.concat_map splice forms in temps := 0; let s = Loc.sink ~on:keep_going in let decls = List.filter_map (fun f -> Loc.caught s (fun () -> decl f)) forms in Loc.finish s; decls (** One file, stopping at the first declaration it cannot parse. Raises [Loc.Error], never [Loc.Errors]. *) let program (forms : Form.t list) : Ast.decl list = parse_forms ~keep_going:false forms (** One file, reporting every declaration it cannot parse. Raises [Loc.Errors] when there was more than nothing wrong, so only a caller prepared for a list should be calling it. *) let program_all (forms : Form.t list) : Ast.decl list = parse_forms ~keep_going:true forms (* The head of the form that was expanded, which is the macro's name wherever there was a macro. "%d of them" had no antecedent once the message was read cold; this says what expanded. *) let expanded_head (f : Form.t) = match f.Form.v with | Form.List ({ v = Form.Sym h; _ } :: _) -> h | _ -> Form.to_string f (* Single-declaration entry point, for tests and the REPL. *) let decl (f : Form.t) : Ast.decl = temps := 0; match !expander [ Expand.quasiquote f ] with | [ f ] -> decl f | fs -> (* One declaration in, one out. A macro at the top level would break that, and there is no top-level macro call: [decl] dispatches on the head and a macro name is not one of the heads it knows. *) Loc.failk "parse/expansion-arity" f.loc "expanding %s produced %d declarations, and one was expected here — a \ top-level form is one declaration. Nothing joins several into one" (expanded_head f) (List.length fs) (* Single-expression entry point: C-x C-e, and the tests that parse one expression. It expands, which [Parse.expr] above does not and never did — so a bare [(unless c a b)] typed at the REPL was an unknown name, the prelude's macros included. That is the whole of the change here: the wrap is the one [decl] has, applied to the other entry point. [Expand.quasiquote] first for [parse_forms]'s reason — it is pure, it needs nothing loaded, and the arm in [form] that refuses an undesugared quasiquote is the backstop for the path that skips this, not for this one. What an expression that expands to a declaration does is decided in [form]: [defn] and its siblings are refused by name, wherever in the expansion they appear. Nothing here has to look for them. [temps] is deliberately not reset. [decl] resets it because a declaration is a fresh top level; an expression is evaluated into a session that has been handing out temporaries all along, and restarting the counter would hand out a name the frame beside it is already using. *) let expr (f : Form.t) : Ast.expr = match !expander [ Expand.quasiquote f ] with | [ f ] -> expr f | fs -> (* One expression in, one out. [Macro.program] is a [List.map], so it cannot answer with anything else — this is here because the invariant is worth stating where it is relied on, not because it has been seen. *) Loc.failk "parse/expansion-arity" f.loc "expanding %s produced %d forms, and an expression is one — wrap them \ in (do ...) if they are meant to run in order" (expanded_head f) (List.length fs)