From 6e37a270d76fc0670911439189358c982f1d608a Mon Sep 17 00:00:00 2001 From: Yogthos Date: Mon, 1 Jun 2026 23:41:43 -0400 Subject: [PATCH] fix: bind-put sentinel for nil values in Janet tables Janet's (put table key nil) silently drops the key. This caused nil-valued fn/let/loop/macro parameters to be invisible to resolve-sym, making them fall through to global symbol resolution. Added bind-put helper that uses :jolt/nil sentinel for nil values, and resolve-sym unwraps it back to nil. --- .clj-kondo/.cache/v1/clj/user.transit.json | 2 +- src/jolt/evaluator.janet | 23 +++++---- trace-dv.janet | 57 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 trace-dv.janet diff --git a/.clj-kondo/.cache/v1/clj/user.transit.json b/.clj-kondo/.cache/v1/clj/user.transit.json index d392a72..cf99466 100644 --- a/.clj-kondo/.cache/v1/clj/user.transit.json +++ b/.clj-kondo/.cache/v1/clj/user.transit.json @@ -1 +1 @@ -["^ ","~$eval-list",["^ ","~:fixed-arities",["~#set",[3]],"~:private",true,"~:ns","~$user","~:name","^0","~:type","~:fn","~:col",1,"~:top-ns",null,"~:row",196],"~$resolve-var",["^ ","^;",57,"^9",1,"^1",["^2",[3]],"^6","^<","^4","^5","^:",null,"^7","^8"],"~$syntax-quote*",["^ ","^1",["^2",[3]],"^3",true,"^4","^5","^6","^=","^7","^8","^9",1,"^:",null,"^;",22],"~$new-v",["^ ","^;",436,"^9",18,"^6","^>","^4","^5","^:",null],"~$ctor",["^ ","^;",510,"^9",17,"~:varargs-min-arity",0,"^6","^?","^4","^5","^:",null,"^7","^8"],"~$v",["^ ","^;",252,"^9",20,"^6","~$v","^4","^5","^:",null],"~$resolve-sym",["^ ","^1",["^2",[3]],"^3",true,"^4","^5","^6","^A","^7","^8","^9",1,"^:",null,"^;",95],"~$first-form",["^ ","^;",198,"^9",3,"^6","^B","^4","^5","^:",null,"^7","~:any"],"~$run-finally",["^ ","^;",406,"^9",13,"^1",["^2",[1]],"^6","^D","^4","^5","^:",null,"^7","^8"],"~$eval-require",["^ ","^1",["^2",[2]],"^3",true,"^4","^5","^6","^E","^7","^8","^9",1,"^:",null,"~:arities",["^ ","~i2",["^ ","~:ret","~:nil","~:arglist-str","[ctx spec]"]],"^;",74],"~$alias-sym",["^ ","^;",85,"^9",15,"^6","^J","^4","^5","^:",null],"~$name",["^ ","^;",200,"^9",3,"^6","^K","^4","^5","^:",null,"^7",["^2",[["^ ","~:tag","^H","^;",202,"^9",15,"~:end-row",202,"~:end-col",18],["^ ","~:call",["^ ","~:filename","/Users/yogthos/src/jolt/src/jolt/evaluator.janet","^7","^O","~:lang","~:clj","~:base-lang","^R","~:resolved-ns","^5","^4","^5","^6","^B","~:arity",1],"^;",201,"^9",15,"^M",201,"^N",33]]]],"^P","/Users/yogthos/src/jolt/src/jolt/evaluator.janet","~$macro-fn",["^ ","^1",["^2",[]],"^4","^5","^6","^V","^7","^8","^9",18,"^:",null,"^@",0,"^;",236],"~$val",["^ ","^;",351,"^9",21,"^6","^W","^4","^5","^:",null],"~$parse-arg-names",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^X","^7","^8","^9",1,"^:",null,"^F",["^ ","~i1",["^ ","^G",["^ ","^7","~:map","~:val",["^ ","~:fixed",["^ ","^;",182,"^M",584,"^9",11,"^N",13]]],"^I","[args-form]"]],"^;",136],"~$special-symbol?",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^10","^7","^8","^9",1,"^:",null,"^F",["^ ","~i1",["^ ","^G",["^2",["~:boolean"]],"^I","[name]"]],"^;",10],"~$sym-name?",["^ ","^1",["^2",[2]],"^3",true,"^4","^5","^6","^12","^7","^8","^9",1,"^:",null,"^;",6],"~$inner",["^ ","^;",160,"^9",17,"^6","^13","^4","^5","^:",null],"~$sym-name-str",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^14","^7","^8","^9",1,"^:",null,"^;",70],"~$unwrap-meta-name",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^15","^7","^8","^9",1,"^:",null,"^;",185]] \ No newline at end of file +["^ ","~$eval-list",["^ ","~:fixed-arities",["~#set",[3]],"~:private",true,"~:ns","~$user","~:name","^0","~:type","~:fn","~:col",1,"~:top-ns",null,"~:row",203],"~$resolve-var",["^ ","^;",57,"^9",1,"^1",["^2",[3]],"^6","^<","^4","^5","^:",null,"^7","^8"],"~$syntax-quote*",["^ ","^1",["^2",[3]],"^3",true,"^4","^5","^6","^=","^7","^8","^9",1,"^:",null,"^;",22],"~$new-v",["^ ","^;",441,"^9",18,"^6","^>","^4","^5","^:",null],"~$ctor",["^ ","^;",515,"^9",17,"~:varargs-min-arity",0,"^6","^?","^4","^5","^:",null,"^7","^8"],"~$v",["^ ","^;",259,"^9",20,"^6","~$v","^4","^5","^:",null],"~$resolve-sym",["^ ","^1",["^2",[3]],"^3",true,"^4","^5","^6","^A","^7","^8","^9",1,"^:",null,"^;",101],"~$first-form",["^ ","^;",205,"^9",3,"^6","^B","^4","^5","^:",null,"^7","~:any"],"~$run-finally",["^ ","^;",411,"^9",13,"^1",["^2",[1]],"^6","^D","^4","^5","^:",null,"^7","^8"],"~$eval-require",["^ ","^1",["^2",[2]],"^3",true,"^4","^5","^6","^E","^7","^8","^9",1,"^:",null,"~:arities",["^ ","~i2",["^ ","~:ret","~:nil","~:arglist-str","[ctx spec]"]],"^;",74],"~$bind-put",["^ ","^1",["^2",[3]],"^3",true,"^4","^5","^6","^J","^7","^8","^9",1,"^:",null,"^;",95],"~$alias-sym",["^ ","^;",85,"^9",15,"^6","^K","^4","^5","^:",null],"~$name",["^ ","^;",207,"^9",3,"^6","^L","^4","^5","^:",null,"^7",["^2",[["^ ","~:call",["^ ","~:filename","/Users/yogthos/src/jolt/src/jolt/evaluator.janet","^7","^M","~:lang","~:clj","~:base-lang","^P","~:resolved-ns","^5","^4","^5","^6","^B","~:arity",1],"^;",208,"^9",15,"~:end-row",208,"~:end-col",33],["^ ","~:tag","^H","^;",209,"^9",15,"^T",209,"^U",18]]]],"^N","/Users/yogthos/src/jolt/src/jolt/evaluator.janet","~$macro-fn",["^ ","^1",["^2",[]],"^4","^5","^6","^W","^7","^8","^9",18,"^:",null,"^@",0,"^;",243],"~$val",["^ ","^;",356,"^9",21,"^6","^X","^4","^5","^:",null],"~$parse-arg-names",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^Y","^7","^8","^9",1,"^:",null,"^F",["^ ","~i1",["^ ","^G",["^ ","^7","~:map","~:val",["^ ","~:fixed",["^ ","^;",189,"^T",589,"^9",11,"^U",13]]],"^I","[args-form]"]],"^;",143],"~$special-symbol?",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^11","^7","^8","^9",1,"^:",null,"^F",["^ ","~i1",["^ ","^G",["^2",["~:boolean"]],"^I","[name]"]],"^;",10],"~$sym-name?",["^ ","^1",["^2",[2]],"^3",true,"^4","^5","^6","^13","^7","^8","^9",1,"^:",null,"^;",6],"~$inner",["^ ","^;",167,"^9",17,"^6","^14","^4","^5","^:",null],"~$sym-name-str",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^15","^7","^8","^9",1,"^:",null,"^;",70],"~$unwrap-meta-name",["^ ","^1",["^2",[1]],"^3",true,"^4","^5","^6","^16","^7","^8","^9",1,"^:",null,"^;",192]] \ No newline at end of file diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index f864176..6242aa7 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -92,6 +92,12 @@ (ns-import current-ns alias ns-name))) nil)) +(defn- bind-put + "Put a value into bindings. Uses :jolt/nil sentinel for nil values + because Janet's (put table key nil) silently drops the key." + [bindings key value] + (put bindings key (if (nil? value) :jolt/nil value))) + (defn- resolve-sym [ctx bindings sym-s] (let [name (sym-s :name) ns (sym-s :ns)] @@ -104,7 +110,8 @@ (if v (var-get v) (error (string "Unable to resolve symbol: " ns "/" name)))))) # Use :jolt/not-found sentinel to distinguish nil binding from absent binding (let [local (get bindings name :jolt/not-found)] - (if (not= local :jolt/not-found) local + (if (not= local :jolt/not-found) + (if (= local :jolt/nil) nil local) (let [current-ns (ctx-current-ns ctx) ns (ctx-find-ns ctx current-ns) v (ns-find ns name)] (if v (var-get v) # Check clojure.core as auto-referred fallback @@ -239,7 +246,7 @@ (put new-bindings "&env" @{}) # implicit &env for macro bodies (table — nil-safe) (var i 0) (each a fixed-names - (put new-bindings a (macro-args i)) + (bind-put new-bindings a (macro-args i)) (++ i)) (when rest-name (put new-bindings rest-name (tuple/slice macro-args i))) @@ -300,7 +307,7 @@ (table/setproto fn-bindings bindings) (var i 0) (each arg-name fixed-names - (put fn-bindings arg-name (fn-args i)) + (bind-put fn-bindings arg-name (fn-args i)) (++ i)) (when rest-name (put fn-bindings rest-name (tuple/slice fn-args i))) @@ -328,7 +335,7 @@ (table/setproto fn-bindings bindings) (var i 0) (each arg-name fixed-names - (put fn-bindings arg-name (fn-args i)) + (bind-put fn-bindings arg-name (fn-args i)) (++ i)) (when rest-name (put fn-bindings rest-name (tuple/slice fn-args i))) @@ -346,10 +353,8 @@ (let [len (length bind-vec)] (while (< i len) (let [sym (bind-vec i)] - # Pre-register with nil to prevent proto fallthrough to global scope - (put new-bindings (sym :name) nil) (def val (eval-form ctx new-bindings (bind-vec (+ i 1)))) - (put new-bindings (sym :name) val) + (bind-put new-bindings (sym :name) val) (+= i 2)))) (var result nil) (each body-form body @@ -370,7 +375,7 @@ (table/setproto loop-bindings bindings) (var j 0) (each sn sym-names - (put loop-bindings sn (args j)) + (bind-put loop-bindings sn (args j)) (++ j)) (put loop-bindings :jolt/loop-fn loop-fn) (var result nil) @@ -486,7 +491,7 @@ (table/setproto new-bindings bindings) (var i 0) (each a arg-names - (put new-bindings a (args i)) + (bind-put new-bindings a (args i)) (++ i)) (var result nil) (each bf body diff --git a/trace-dv.janet b/trace-dv.janet new file mode 100644 index 0000000..04d9cf4 --- /dev/null +++ b/trace-dv.janet @@ -0,0 +1,57 @@ +(use ./src/jolt/evaluator) +(use ./src/jolt/types) +(use ./src/jolt/reader) +(use ./src/jolt/api) + +# Override resolve-sym with tracing +(def old-resolve-sym @{:private true}) + +# Monkey-patch eval-list to add tracing +(var orig-eval-list nil) +# Just add a direct trace in fn* + +(def ctx (init)) +(each fp ["/Users/yogthos/src/sci/src/sci/impl/macros.cljc" + "/Users/yogthos/src/sci/src/sci/impl/protocols.cljc" + "/Users/yogthos/src/sci/src/sci/impl/types.cljc" + "/Users/yogthos/src/sci/src/sci/impl/unrestrict.cljc"] + (def src (slurp fp)) + (var s src) + (while (> (length (string/trim s)) 0) + (def [f r] (parse-next s)) (set s r) + (if (not (nil? f)) (protect (eval-form ctx @{} f))))) + +(def vs (slurp "/Users/yogthos/src/sci/src/sci/impl/vars.cljc")) +(var s vs) (var c 0) +(while (and (> (length (string/trim s)) 0) (< c 27)) + (def [f r] (parse-next s)) (set s r) (++ c) + (if (not (nil? f)) (protect (eval-form ctx @{} f)))) + +(def ls (slurp "/Users/yogthos/src/sci/src/sci/lang.cljc")) +(set s ls) +(while (> (length (string/trim s)) 0) + (def [f r] (parse-next s)) (set s r) + (if (not (nil? f)) (protect (eval-form ctx @{} f)))) + +# Load utils up to form 35 +(def us (slurp "/Users/yogthos/src/sci/src/sci/impl/utils.cljc")) +(set s us) (set c 0) +(while (and (> (length (string/trim s)) 0) (< c 35)) + (def [f r] (parse-next s)) (set s r) (++ c) + (if (not (nil? f)) + (let [pr (protect (eval-form ctx @{} f))] + (if (not (pr 0)) + (printf "utils[%d]: %q\n" c (pr 1)))))) + +# Now inspect the dynamic-var fn in detail +(def ns (ctx-find-ns ctx "sci.impl.utils")) +(def dv (ns-find ns "dynamic-var")) +(def dv-fn (var-get dv)) + +# Evaluate (dynamic-var 'foo) manually +(def quoted-foo @[{:jolt/type :symbol :ns nil :name "quote"} {:jolt/type :symbol :ns nil :name "foo"}]) +(def dv-call @[{:jolt/type :symbol :ns nil :name "dynamic-var"} quoted-foo]) + +(printf "Calling dynamic-var('foo):\n") +(def r1 (protect (eval-form ctx @{} dv-call))) +(printf " result: %q\n" (if (r1 0) "OK" (r1 1)))