Merge pull request #92 from jolt-lang/fix-507-p3c
fix: map literal evaluation order; land the local-callee call inline
This commit is contained in:
commit
0cfb5a982e
6 changed files with 110 additions and 22 deletions
|
|
@ -330,6 +330,22 @@
|
|||
body (if (symbol? d-expr) body ['let [d d-expr] body])]
|
||||
(wrap body))))
|
||||
(direct-call? ctx fnode) (tuple (emit ctx fnode) ;args)
|
||||
# Local callee (closure param, let-bound fn, defn self-name): inline the
|
||||
# function check so the overwhelmingly-common function case is a direct
|
||||
# janet call with no variadic arg-tuple packing — jolt-call only handles
|
||||
# the IFn-collection leftovers (jank's dynamic_call removal, jolt-507).
|
||||
# The callee is rebound to a reserved _fp$ symbol first: a raw jolt local
|
||||
# name in janet CALL-HEAD position resolves against janet's macro table
|
||||
# before the lexical upvalue, so a local named like a janet core macro
|
||||
# (clojure.core/repeat's self-name vs janet's repeat macro) would expand
|
||||
# as that macro. Argument positions (the old jolt-call shape, the rebind
|
||||
# here) never consult the macro table, so the rebind is safe.
|
||||
(= :local (fnode :op))
|
||||
(let [fsym (jsym)]
|
||||
['let [fsym (emit ctx fnode)]
|
||||
['if ['function? fsym]
|
||||
(tuple fsym ;args)
|
||||
(tuple jolt-call fsym ;args)]])
|
||||
(tuple jolt-call (emit ctx fnode) ;args)))
|
||||
|
||||
(defn- emit-vector [ctx node]
|
||||
|
|
|
|||
|
|
@ -169,7 +169,17 @@
|
|||
(tuple/slice r))
|
||||
|
||||
# Map builder: parts are alternating k v (no splicing in map syntax-quote).
|
||||
(defn core-sqmap [& parts] (kvs->map (array ;parts)))
|
||||
(defn core-sqmap [& parts]
|
||||
# A syntax-quoted map template is Clojure's array-map case: construction
|
||||
# order is source order and must survive into the built map, which usually
|
||||
# becomes a FORM whose entries the evaluator walks (jolt-p3c). Same
|
||||
# carriers as the reader: struct prototype / phm field.
|
||||
(def kvs (array ;parts))
|
||||
(def m (kvs->map kvs))
|
||||
(cond
|
||||
(struct? m) (struct/with-proto (struct :jolt/kv-order (tuple/slice kvs)) ;kvs)
|
||||
(table? m) (do (put m :jolt/kv-order (tuple/slice kvs)) m)
|
||||
m))
|
||||
|
||||
# Set builder: like core-sqvec but yields a set, so `#{~@a} splices into a set.
|
||||
(defn core-sqset [& parts]
|
||||
|
|
|
|||
|
|
@ -261,9 +261,15 @@
|
|||
(make-phs ;result))
|
||||
(and (struct? form) (get form :jolt/type)) form
|
||||
(struct? form)
|
||||
(do (var kvs @[]) (each k (keys form)
|
||||
(array/push kvs (syntax-quote* ctx bindings k gsmap))
|
||||
(array/push kvs (syntax-quote* ctx bindings (get form k) gsmap))) (struct ;kvs))
|
||||
(do (var kvs @[])
|
||||
(def order (form-kv-order form))
|
||||
(if order
|
||||
(each x order (array/push kvs (syntax-quote* ctx bindings x gsmap)))
|
||||
(each k (keys form)
|
||||
(array/push kvs (syntax-quote* ctx bindings k gsmap))
|
||||
(array/push kvs (syntax-quote* ctx bindings (get form k) gsmap))))
|
||||
# keep carrying source order through nested syntax-quote (jolt-p3c)
|
||||
(struct/with-proto (struct :jolt/kv-order (tuple/slice kvs)) ;kvs))
|
||||
form))
|
||||
|
||||
# Syntax-quote LOWERING: instead of evaluating a `(...) form to a value (what
|
||||
|
|
@ -306,9 +312,12 @@
|
|||
@[(sqsym* "quote") form]
|
||||
(struct? form)
|
||||
(do (var parts @[(sqsym* "__sqmap")])
|
||||
(each k (keys form)
|
||||
(array/push parts (syntax-quote-lower ctx k gsmap))
|
||||
(array/push parts (syntax-quote-lower ctx (get form k) gsmap)))
|
||||
(def order (form-kv-order form))
|
||||
(if order
|
||||
(each x order (array/push parts (syntax-quote-lower ctx x gsmap)))
|
||||
(each k (keys form)
|
||||
(array/push parts (syntax-quote-lower ctx k gsmap))
|
||||
(array/push parts (syntax-quote-lower ctx (get form k) gsmap))))
|
||||
parts)
|
||||
@[(sqsym* "quote") form])))
|
||||
|
||||
|
|
@ -2353,19 +2362,26 @@
|
|||
(error (string "No reader function for tag " tag))))
|
||||
(if (get form :jolt/type)
|
||||
(error (string "Unexpected tagged form: " (form :jolt/type)))
|
||||
# plain map literal: evaluate keys and values
|
||||
(let [kvs @[]]
|
||||
(each k (keys form)
|
||||
(array/push kvs (eval-form ctx bindings k))
|
||||
(array/push kvs (eval-form ctx bindings (get form k))))
|
||||
# plain map literal: evaluate keys and values in SOURCE order when
|
||||
# the reader order rides along (jolt-p3c), hash order otherwise
|
||||
(let [kvs @[]
|
||||
order (form-kv-order form)]
|
||||
(if order
|
||||
(each x order (array/push kvs (eval-form ctx bindings x)))
|
||||
(each k (keys form)
|
||||
(array/push kvs (eval-form ctx bindings k))
|
||||
(array/push kvs (eval-form ctx bindings (get form k)))))
|
||||
(build-eval-map kvs))))))))
|
||||
# A phm map-literal FORM (reader emits one for {:a nil} etc., which a struct
|
||||
# would have dropped): evaluate its key/value forms and rebuild, preserving nil.
|
||||
(phm? form)
|
||||
(let [kvs @[]]
|
||||
(each e (phm-entries form)
|
||||
(array/push kvs (eval-form ctx bindings (in e 0)))
|
||||
(array/push kvs (eval-form ctx bindings (in e 1))))
|
||||
(let [kvs @[]
|
||||
order (form-kv-order form)]
|
||||
(if order
|
||||
(each x order (array/push kvs (eval-form ctx bindings x)))
|
||||
(each e (phm-entries form)
|
||||
(array/push kvs (eval-form ctx bindings (in e 0)))
|
||||
(array/push kvs (eval-form ctx bindings (in e 1)))))
|
||||
(build-eval-map kvs))
|
||||
(array? form)
|
||||
(if (= 0 (length form))
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
(use ./evaluator)
|
||||
(use ./core)
|
||||
(import ./phm :as phm)
|
||||
(import ./reader :as rdr)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Form introspection
|
||||
|
|
@ -54,7 +55,18 @@
|
|||
(defn h-elements [form] (make-vec form))
|
||||
(defn h-vector-items [form] (make-vec form))
|
||||
(defn h-map-pairs [form]
|
||||
(if (phm/phm? form)
|
||||
# reader forms carry source order (jolt-p3c) — Clojure evaluates literal
|
||||
# entries left to right; hash order only for constructed maps
|
||||
(def order (rdr/form-kv-order form))
|
||||
(cond
|
||||
order
|
||||
(do (def out @[])
|
||||
(var i 0)
|
||||
(while (< i (length order))
|
||||
(array/push out (make-vec [(in order i) (in order (+ i 1))]))
|
||||
(+= i 2))
|
||||
(make-vec out))
|
||||
(phm/phm? form)
|
||||
(make-vec (map (fn [e] (make-vec [(in e 0) (in e 1)])) (phm/phm-entries form)))
|
||||
(make-vec (map (fn [k] (make-vec [k (get form k)])) (keys form)))))
|
||||
(defn h-set-items [form] (make-vec (form :value)))
|
||||
|
|
|
|||
|
|
@ -305,7 +305,24 @@
|
|||
(defn- reader-map [kvs]
|
||||
(var has-nil false) (var i 0)
|
||||
(while (< i (length kvs)) (when (nil? (in kvs i)) (set has-nil true) (break)) (++ i))
|
||||
(if has-nil (phm/make-phm kvs) (struct ;kvs)))
|
||||
# Source order rides along out-of-band (jolt-p3c): struct iteration is hash
|
||||
# order, but Clojure evaluates literal entries left to right. A struct
|
||||
# PROTOTYPE carries it without changing the form's map behavior (keys/kvs/
|
||||
# length ignore protos; jolt-equal? compares maps structurally); the phm rep
|
||||
# (nil key/value present) gets a plain extra field.
|
||||
(if has-nil
|
||||
(let [m (phm/make-phm kvs)]
|
||||
(put m :jolt/kv-order (tuple/slice kvs))
|
||||
m)
|
||||
(struct/with-proto (struct :jolt/kv-order (tuple/slice kvs)) ;kvs)))
|
||||
|
||||
(defn form-kv-order
|
||||
"Source-ordered [k v k v ...] tuple of a map FORM (nil for maps built at
|
||||
runtime, which carry no reader order)."
|
||||
[form]
|
||||
(cond
|
||||
(struct? form) (get (struct/getproto form) :jolt/kv-order)
|
||||
(table? form) (get form :jolt/kv-order)))
|
||||
|
||||
(defn read-map [s pos]
|
||||
# pos is at opening brace
|
||||
|
|
|
|||
|
|
@ -35,10 +35,16 @@
|
|||
["collection key" ":v" "(get {[1 2] :v} [1 2])"]
|
||||
["collection value-equal key" ":v" "(get {[1 2] :v} (vector 1 2))"]
|
||||
["computed key" "1" "(get {(keyword \"a\") 1} :a)"]
|
||||
# NOTE: entry ORDER is reader-hash order today, not source order (Clojure
|
||||
# evaluates array-map literals left-to-right) — pinned loosely; see jolt-p3c
|
||||
["values evaluate exactly once each" "[1 2 3]"
|
||||
"(do (def log (atom [])) {:a (swap! log conj 1) :b (swap! log conj 2) :c (swap! log conj 3)} (vec (sort (deref log))))"]
|
||||
# jolt-p3c: literal entries evaluate left to right in SOURCE order (the
|
||||
# reader carries [k v ...] order on a struct prototype / phm field)
|
||||
["values evaluate in source order" "[1 2 3]"
|
||||
"(do (def log (atom [])) {:a (swap! log conj 1) :b (swap! log conj 2) :c (swap! log conj 3)} (deref log))"]
|
||||
["keys evaluate before their values, pairwise" "[:k1 :v1 :k2 :v2]"
|
||||
"(do (def log (atom [])) {(do (swap! log conj :k1) :a) (do (swap! log conj :v1) 1) (do (swap! log conj :k2) :b) (do (swap! log conj :v2) 2)} (deref log))"]
|
||||
["source order with a nil value (phm form)" "[1 2 3]"
|
||||
"(do (def log (atom [])) {:a (do (swap! log conj 1) nil) :b (swap! log conj 2) :c (swap! log conj 3)} (deref log))"]
|
||||
["source order through syntax-quote" "[1 2]"
|
||||
"(do (def log (atom [])) (defmacro m-p3c [] `{:a ~(list 'swap! 'log 'conj 1) :b ~(list 'swap! 'log 'conj 2)}) (m-p3c) (deref log))"]
|
||||
["count" "3" "(count {:a 1 :b 2 :c 3})"]
|
||||
["equality with phm" "true" "(= {:a 1 :b 2} (assoc {:a 1} :b 2))"]
|
||||
["keys work after assoc" "2" "(:b (assoc {:a 1 :b 2} :c 3))"]
|
||||
|
|
@ -55,3 +61,14 @@
|
|||
["PI" "true" "(< 3.14 clojure.math/PI 3.15)"]
|
||||
["require + alias" "5" "(do (require '[clojure.math :as m]) (long (m/hypot 3 4)))"]
|
||||
["as a value" "[1 2]" "(mapv (comp long clojure.math/sqrt) [1 4])"])
|
||||
|
||||
# jolt-507: a jolt local in janet CALL-HEAD position must not resolve as a
|
||||
# janet core macro of the same name (repeat/seq/loop are janet macros). The
|
||||
# emitter rebinds local callees to reserved _fp$ symbols.
|
||||
(defspec "calls / locals named like janet macros"
|
||||
["local fn named repeat" "[1 1]" "(let [repeat (fn [x] [x x])] (repeat 1))"]
|
||||
["local fn named seq" ":end" "((fn seq [n] (if (pos? n) (seq (dec n)) :end)) 2)"]
|
||||
["local fn named loop2" "[2 1]" "(let [with (fn [a b] [b a])] (with 1 2))"]
|
||||
["overlay repeat self-call (regression)" "(quote (0 0 0))" "(take 3 (repeat 0))"]
|
||||
["closure param called" "42" "((fn [f] (f 41)) inc)"]
|
||||
["param holding a keyword (IFn leftover)" "1" "((fn [f] (f {:a 1})) :a)"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue