From 0b8564828b8ea73de83082f4b0e3a1c4464162d6 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 03:44:16 +0700 Subject: [PATCH] The temporary and the reference to it come back together, so they cannot drift --- lib/parse.ml | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/parse.ml b/lib/parse.ml index 90fc84a..ae792f0 100644 --- a/lib/parse.ml +++ b/lib/parse.ml @@ -375,6 +375,15 @@ and bindings f (items : Form.t list) : Ast.binding list = (* ── 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 () 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 @@ -393,12 +402,8 @@ and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list = (* 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 = fresh_temp () in - { Ast.bname = t; bty = None; bval = v; bloc = p.loc } :: dmap p t items - | Vec items -> - let t = fresh_temp () in - { Ast.bname = t; bty = None; bval = v; bloc = p.loc } :: dvec p t items + | 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 \ @@ -410,9 +415,9 @@ and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list = 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. *) -and dmap (p : Form.t) (t : string) (items : Form.t list) : Ast.binding list = +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 (ex loc (Ast.Var t), name)) in + let field loc name = ex loc (Ast.Field (t, name)) in let rec go = function | [] -> [] | { v = Kw "keys"; _ } :: names :: rest -> @@ -460,7 +465,7 @@ and dmap (p : Form.t) (t : string) (items : Form.t list) : Ast.binding list = 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 : string) (items : Form.t list) : Ast.binding list = +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 @@ -491,7 +496,7 @@ and dvec (p : Form.t) (t : string) (items : Form.t list) : Ast.binding list = let nth i = ex p.loc (Ast.Call (var p.loc "destructure~nth", - [ var p.loc t; + [ 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) ])) @@ -518,10 +523,9 @@ and dvec (p : Form.t) (t : string) (items : Form.t list) : Ast.binding list = bval = ex r.loc (Ast.Call (var r.loc "slice", - [ var r.loc t; + [ t; ex r.loc (Ast.Int (Int64.of_int n)); - ex r.loc (Ast.Call (var r.loc "len", - [ var r.loc t ])) ])) } ] + ex r.loc (Ast.Call (var r.loc "len", [ t ])) ])) } ] in each 0 elems @ rest_binding