Three defects, all from lifting every def initialiser, none of which the suite caught: A def typed fresh into a live session came up zero and stayed zero. The image flan_dev_global copies on the allocation is the only value a new global ever gets — the host's .init-globals never calls its initialiser — and both backends chose that image with Tast.const_init, which a def's lifted Call fails by construction. Emit.initial_image reads the constant back out of the lifted body; the x86 twin had the same bug. Changing a global between def and defonce was silently ineffective: the guard lives in the startup function compiled into the host, which a reload cannot republish. Session.compatible refuses both directions and says to restart; editing the value stays allowed. And global/<n> no longer leaks into the signature refusal when a def is retyped — the global loop names the same fact in words a reader can act on. flan check prints def, defonce or defconst off grerun; (defvar) with no arguments names the shapes rather than offering (defonce ); the docs, plan.org, runtime comments and valgrind.supp are swept; BUILT.md states the release-build cost and the uninit caveat.
1982 lines
99 KiB
OCaml
1982 lines
99 KiB
OCaml
(** 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], [len], [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 a pattern binds only in let — 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 — braces in type \
|
|
position are not a type"
|
|
| 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 of \
|
|
a generic function is dyn — there is no type to write, and a \
|
|
method that wanted one could not be reached by a dispatch that \
|
|
does not know types either"
|
|
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 matched at compile time as well as at run \
|
|
time, so it is written out rather than 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 — the body has more after \
|
|
it, and its value going unused is almost always a typo for \
|
|
{: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 ...)")
|
|
|
|
| Sym "dotimes" ->
|
|
(match label args with
|
|
| lbl, ({ v = Vec [ n; count ]; _ } :: body) ->
|
|
no_pattern n;
|
|
mk (Ast.Dotimes (lbl, sym n, expr count, body_of body))
|
|
| _ -> fail f "dotimes is (dotimes [name count] body ...)")
|
|
|
|
(* [(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, \
|
|
because a loop answers with the value of its body; there is nothing \
|
|
for a label to name" 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 is a top-level declaration, not an expression. A quasiquoted one is \
|
|
a value and a macro may answer with it; an evaluated one is not a thing \
|
|
anything can do" 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 (see the build sequence in plan.org)" 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 (len 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 "len", [ 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 — match is over an Option here, \
|
|
and an enum member cannot be one until Ast.pattern can hold a keyword. \
|
|
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
|
|
"the tagged sum is defdata now — (defdata Name [(Case [field \
|
|
Type ...]) ...]) — and defunion is C's untagged union, whose \
|
|
members overlay one storage: (defunion Name [member Type \
|
|
...]). This reads as the tagged one, so it is refused rather \
|
|
than quietly given the other meaning")
|
|
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 and there is \
|
|
no type to write: an instance is a dyn map with a \
|
|
shape tag on it, and (get p :%s) is how a slot is read"
|
|
(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 \
|
|
discriminant is an i32, so its members run from -2147483648 to \
|
|
2147483647. Give %s a value in that range, or a defconst of a \
|
|
wider type if the number itself is what matters"
|
|
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 \
|
|
discriminant is an i32, so its 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)
|
|
|
|
(* The old name of [defonce], refused by name rather than left to fall
|
|
through to "unknown function": every program written before the rename
|
|
spells it, and the message is the migration. *)
|
|
| 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. A form with nothing after the
|
|
keyword has nothing to paste, and echoing it would offer
|
|
[(defonce )] as the fix for [(defvar )] — a malformed old form
|
|
answered with a malformed new one. The names alone then, which is what
|
|
there is to say about a form that named nothing. *)
|
|
(match args with
|
|
| [] ->
|
|
Loc.failk "parse/defvar-renamed" f.loc
|
|
"defvar is now called defonce — the name says what it does: it \
|
|
initialises once and keeps its value across re-runs. It is \
|
|
(defonce name Type value?), or (def name Type value?) if the value \
|
|
should follow the source on every re-run"
|
|
| _ ->
|
|
let rest = String.concat " " (List.map Form.to_string args) in
|
|
Loc.failk "parse/defvar-renamed" f.loc
|
|
"defvar is now called defonce — the name says what it does: it \
|
|
initialises once and keeps its value across re-runs. Write (defonce \
|
|
%s), or (def %s) if the value should follow the source 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 spliced in place, and this \
|
|
is a position that takes exactly one — a macro answering several is a \
|
|
file's form, not an expression's"
|
|
| 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)
|