fix: unified recursive destructuring (nested seq/map, :strs/:syms, :or, :as, fn-params); proper defn docstring/attr-map handling

This commit is contained in:
Yogthos 2026-06-04 14:31:24 -04:00
parent 92682187a8
commit cbc610c665
5 changed files with 221 additions and 76 deletions

View file

@ -4,3 +4,4 @@
{"id":"int-2eef7613","kind":"field_change","created_at":"2026-06-04T18:06:02.544998Z","actor":"Yogthos","issue_id":"jolt-wec","extra":{"field":"status","new_value":"closed","old_value":"open"}}
{"id":"int-a7573343","kind":"field_change","created_at":"2026-06-04T18:09:14.608401Z","actor":"Yogthos","issue_id":"jolt-alz","extra":{"field":"status","new_value":"closed","old_value":"open"}}
{"id":"int-40cd8348","kind":"field_change","created_at":"2026-06-04T18:09:15.510649Z","actor":"Yogthos","issue_id":"jolt-nte","extra":{"field":"status","new_value":"closed","old_value":"open"}}
{"id":"int-57f4d2b3","kind":"field_change","created_at":"2026-06-04T18:31:23.83434Z","actor":"Yogthos","issue_id":"jolt-9sb","extra":{"field":"status","new_value":"closed","old_value":"open"}}

View file

@ -4,3 +4,12 @@
{"_type":"issue","id":"jolt-x8s","title":"CRITICAL: collections not callable as IFn (vector/map/set)","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:14Z","created_by":"Yogthos","updated_at":"2026-06-04T17:50:54Z","closed_at":"2026-06-04T17:50:54Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-alz","title":"CRITICAL: self-referential lazy-seq/lazy-cat yields only literal prefix","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:13Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:15Z","closed_at":"2026-06-04T18:09:15Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-wec","title":"CRITICAL: multi-collection map returns unrealized thunk","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:12Z","created_by":"Yogthos","updated_at":"2026-06-04T18:06:02Z","closed_at":"2026-06-04T18:06:02Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-gxr","title":"HIGH: aliased namespace calls (require :as) don't resolve (s/join)","status":"open","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:58Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:58Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-4z9","title":"HIGH: dispatch — multimethod :default, protocol-on-record, extend-protocol","status":"open","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:57Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:57Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-40n","title":"HIGH: update/update-in/assoc-in fail on map literals (struct)","status":"open","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:56Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:56Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-ytt","title":"HIGH: str semantics — (str nil)='nil' should be '', (str :b)='b' should be ':b'","status":"open","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:56Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:56Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-9sb","title":"HIGH: destructuring gaps (nested seq, :strs, nested map, fn-param)","status":"closed","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:55Z","created_by":"Yogthos","updated_at":"2026-06-04T18:31:24Z","closed_at":"2026-06-04T18:31:24Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-7dy","title":"MED: compiler path lacks IFn dispatch (collections-as-fn) — only interpreter fixed","status":"open","priority":3,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:10:00Z","created_by":"Yogthos","updated_at":"2026-06-04T18:10:00Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-90c","title":"MED: filter/take-while not lazy — infinite-seq (take N (filter p (range))) loops","status":"open","priority":3,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:10:00Z","created_by":"Yogthos","updated_at":"2026-06-04T18:10:00Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-7n5","title":"MED: REPL display — def/defn should print #'ns/name; lazy-seq printer realizes only head ((0 \u003cfunction\u003e))","status":"open","priority":3,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:59Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-h0n","title":"MED: missing core fns — letfn fnil trampoline doseq cycle partition-all reductions dedupe keep-indexed map-indexed transduce reduce-kv assoc-in peek subvec read-string format","status":"open","priority":3,"issue_type":"feature","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:58Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:58Z","dependency_count":0,"dependent_count":0,"comment_count":0}

View file

@ -1453,43 +1453,39 @@
@[{:jolt/type :symbol :ns nil :name "pop-thread-bindings"}]]]])
(defn- defn->def
"Shared expansion for defn/defn-: (name doc-string? attr-map? params body...)
or (name doc-string? attr-map? ([params] body)... attr-map?) -> (def name (fn* ...))."
[fn-name rest]
(var items (array/slice rest))
# strip optional docstring
(when (and (> (length items) 0) (string? (first items)))
(set items (array/slice items 1)))
# strip optional attr-map (a map literal, i.e. struct/table that isn't a symbol)
(when (and (> (length items) 0)
(let [x (first items)]
(and (or (struct? x) (table? x))
(not (and (struct? x) (= :symbol (get x :jolt/type)))))))
(set items (array/slice items 1)))
(def fn-form @[{:jolt/type :symbol :ns nil :name "fn*"}])
(if (and (> (length items) 0) (array? (first items)) (indexed? (first (first items))))
# multi-arity: each remaining item is an ([params] body...) clause
(each pair items (array/push fn-form pair))
# single-arity: items = [params-vector body...]
(do
(array/push fn-form (first items))
(each b (tuple/slice items 1) (array/push fn-form b))))
@[{:jolt/type :symbol :ns nil :name "def"} fn-name fn-form])
(defn core-defn
"Macro: (defn name [args] body) or (defn name ([args] body)...)
-> (def name (fn* ...) )"
"Macro: (defn name doc-string? attr-map? [args] body...) (or multi-arity)
-> (def name (fn* ...))"
[fn-name & rest]
# Multi-arity if rest starts with list of [args] pairs
(if (and (> (length rest) 0) (array? (first rest)) (indexed? (first (first rest))))
(let [pairs rest]
(def fn-form @[])
(array/push fn-form {:jolt/type :symbol :ns nil :name "fn*"})
(each pair pairs (array/push fn-form pair))
@[{:jolt/type :symbol :ns nil :name "def"} fn-name fn-form])
# Single-arity: (defn name [args] body...)
(let [args-form (first rest)
body (tuple/slice rest 1)]
(def fn-form @[])
(array/push fn-form {:jolt/type :symbol :ns nil :name "fn*"})
(array/push fn-form args-form)
(each b body (array/push fn-form b))
@[{:jolt/type :symbol :ns nil :name "def"} fn-name fn-form])))
(defn->def fn-name rest))
# defn- — same as defn (private not enforced in Jolt)
(defn core-defn- [fn-name & rest]
# Multi-arity if rest starts with list of [args] pairs
(if (and (> (length rest) 0) (array? (first rest)) (indexed? (first (first rest))))
(let [pairs rest]
(def fn-form @[])
(array/push fn-form {:jolt/type :symbol :ns nil :name "fn*"})
(each pair pairs (array/push fn-form pair))
@[{:jolt/type :symbol :ns nil :name "def"} fn-name fn-form])
# Single-arity: (defn- name [args] body...)
(let [args-form (first rest)
body (tuple/slice rest 1)]
(def fn-form @[])
(array/push fn-form {:jolt/type :symbol :ns nil :name "fn*"})
(array/push fn-form args-form)
(each b body (array/push fn-form b))
@[{:jolt/type :symbol :ns nil :name "def"} fn-name fn-form])))
(defn->def fn-name rest))
# Hierarchy stubs for sci bootstrap
(def core-make-hierarchy make-hierarchy)

View file

@ -271,6 +271,135 @@
(if rest-name (array/push all rest-name))
{:fixed (tuple/slice (tuple ;fixed)) :rest rest-name :all (tuple/slice (tuple ;all))})
# ============================================================
# Destructuring (Clojure-compatible, recursive)
# ============================================================
(defn- parse-params
"Parse a parameter vector into raw patterns: {:fixed [pat...] :rest pat-or-nil}.
Unlike parse-arg-names, patterns are kept intact (not flattened) so they can
be destructured against the corresponding argument."
[args-form]
(var fixed @[])
(var rest-pat nil)
(var i 0)
(while (< i (length args-form))
(let [a (in args-form i)]
(if (and (struct? a) (= :symbol (a :jolt/type)) (= "&" (a :name)))
(do (+= i 1)
(when (< i (length args-form)) (set rest-pat (in args-form i)))
(+= i 1))
(do (array/push fixed a) (+= i 1)))))
{:fixed (tuple/slice (tuple ;fixed)) :rest rest-pat})
(defn- d-realize
"Realize a lazy-seq to an array for positional destructuring; pass others through."
[val]
(if (lazy-seq? val)
(do
(var items @[]) (var cur val) (var go true)
(while go
(let [cell (realize-ls cur)]
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell)))
(set go false)
(do (array/push items (in cell 0))
(let [rt (in cell 1)]
(if (nil? rt) (set go false) (set cur (make-lazy-seq rt))))))))
items)
val))
(defn- d-get
"Look up key k in a map-like value (phm/struct/table/nil)."
[m k]
(cond
(phm? m) (phm-get m k)
(or (struct? m) (table? m)) (get m k)
true nil))
(defn- find-or-default
"Find the :or default expression for binding name nm, or :jolt/none."
[or-map nm]
(var result :jolt/none)
(when or-map
(each k (keys or-map)
(when (and (struct? k) (= :symbol (k :jolt/type)) (= nm (k :name)))
(set result (get or-map k)))))
result)
(var destructure-bind nil)
(set destructure-bind
(fn dbind [ctx bindings pat val]
(cond
# plain symbol
(and (struct? pat) (= :symbol (pat :jolt/type)))
(bind-put bindings (pat :name) val)
# sequential pattern (vector of sub-patterns)
(indexed? pat)
(let [rv (d-realize val)
seqable? (indexed? rv)]
(var di 0) (var vi 0)
(def n (length pat))
(while (< di n)
(let [elem (in pat di)]
(cond
# & rest
(and (struct? elem) (= :symbol (elem :jolt/type)) (= "&" (elem :name)))
(do
# rest binds a seq (jolt list = array), per Clojure semantics
(destructure-bind ctx bindings (in pat (+ di 1))
(if (and seqable? (< vi (length rv)))
(array/slice (if (tuple? rv) (array/slice rv) rv) vi)
@[]))
(set di (+ di 2)))
# :as whole
(= elem :as)
(do
(destructure-bind ctx bindings (in pat (+ di 1)) val)
(set di (+ di 2)))
# positional element
true
(do
(destructure-bind ctx bindings elem
(if (and seqable? (< vi (length rv))) (in rv vi) nil))
(+= di 1) (+= vi 1))))))
# map pattern (struct/table that isn't a symbol)
(or (struct? pat) (table? pat))
(do
(def or-map (get pat :or))
(def as-sym (get pat :as))
(when as-sym (destructure-bind ctx bindings as-sym val))
# :keys (keyword lookup), :strs (string lookup), :syms (symbol lookup)
(each spec [[:keys (fn [nm] (keyword nm))]
[:strs (fn [nm] nm)]
[:syms (fn [nm] {:jolt/type :symbol :ns nil :name nm})]]
(let [kw (in spec 0) keyf (in spec 1) names (get pat kw)]
(when (and names (indexed? names))
(each s names
(let [nm (if (and (struct? s) (= :symbol (s :jolt/type))) (s :name) (string s))
v (d-get val (keyf nm))
v (if (nil? v)
(let [d (find-or-default or-map nm)]
(if (= d :jolt/none) nil (eval-form ctx bindings d)))
v)]
(bind-put bindings nm v))))))
# direct {local-pattern key-expr} entries (local may itself be a
# nested vector/map pattern). Special keys are keywords; skip them.
(each k (keys pat)
(when (not (keyword? k))
(let [key-val (eval-form ctx bindings (get pat k))
v (d-get val key-val)]
(if (and (struct? k) (= :symbol (k :jolt/type)))
# symbol target: apply :or default if missing
(let [nm (k :name)
v (if (nil? v)
(let [d (find-or-default or-map nm)]
(if (= d :jolt/none) nil (eval-form ctx bindings d)))
v)]
(bind-put bindings nm v))
# nested pattern target
(destructure-bind ctx bindings k v))))))
true (error (string "Unsupported destructuring pattern: " (string/format "%q" pat))))))
# Dispatch a special form by its string name.
(defn- unwrap-meta-name
"Recursively unwrap (with-meta sym meta) forms to extract the underlying symbol.
@ -444,20 +573,20 @@
(each pair pairs
(let [args-form (in pair 0)
body (tuple/slice pair 1)
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)
n-fixed (length fixed-names)]
param-info (parse-params args-form)
fixed-pats (param-info :fixed)
rest-pat (param-info :rest)
n-fixed (length fixed-pats)]
(put arities n-fixed
(fn [& fn-args]
(var fn-bindings @{})
(table/setproto fn-bindings bindings)
(var i 0)
(each arg-name fixed-names
(bind-put fn-bindings arg-name (fn-args i))
(each pat fixed-pats
(destructure-bind ctx fn-bindings pat (fn-args i))
(++ i))
(when rest-name
(put fn-bindings rest-name (tuple/slice fn-args i)))
(when rest-pat
(destructure-bind ctx fn-bindings rest-pat (tuple/slice fn-args i)))
(put fn-bindings :jolt/loop-fn self)
# Use defining namespace for symbol resolution
(def saved-ns (ctx-current-ns ctx))
@ -477,20 +606,20 @@
# Single-arity: (fn* [args] body...)
(let [args-form (in form 1)
body (tuple/slice form 2)
arg-info (parse-arg-names args-form)
fixed-names (arg-info :fixed)
rest-name (arg-info :rest)
param-info (parse-params args-form)
fixed-pats (param-info :fixed)
rest-pat (param-info :rest)
defining-ns (ctx-current-ns ctx)]
(var self nil)
(set self (fn [& fn-args]
(var fn-bindings @{})
(table/setproto fn-bindings bindings)
(var i 0)
(each arg-name fixed-names
(bind-put fn-bindings arg-name (fn-args i))
(each pat fixed-pats
(destructure-bind ctx fn-bindings pat (fn-args i))
(++ i))
(when rest-name
(put fn-bindings rest-name (tuple/slice fn-args i)))
(when rest-pat
(destructure-bind ctx fn-bindings rest-pat (tuple/slice fn-args i)))
(put fn-bindings :jolt/loop-fn self)
# Use defining namespace for symbol resolution
(def saved-ns (ctx-current-ns ctx))
@ -510,35 +639,7 @@
(while (< i len)
(let [pat (bind-vec i)]
(def val (eval-form ctx new-bindings (bind-vec (+ i 1))))
# Handle destructuring patterns
(if (struct? pat)
(let [keys-vec (get pat :keys)]
(if (and keys-vec (indexed? keys-vec))
(each k keys-vec
(def kname (if (keyword? k) (string k) (k :name)))
(bind-put new-bindings kname (get val (keyword kname))))
(bind-put new-bindings (pat :name) val)))
(if (indexed? pat)
# Sequential destructuring (vector pattern)
(do
(var di 0)
(while (< di (length pat))
(let [inner-pat (in pat di)]
(if (and (struct? inner-pat) (= :symbol (inner-pat :jolt/type)) (= "&" (inner-pat :name)))
# & rest: next element gets (drop di val)
(do
(+= di 1)
(when (< di (length pat))
(let [rest-pat (in pat di)]
(bind-put new-bindings
(if (struct? rest-pat) (rest-pat :name) rest-pat)
(tuple/slice val di)))))
(if (struct? inner-pat)
(bind-put new-bindings (inner-pat :name) (get val di))
(bind-put new-bindings inner-pat (get val di)))))
(+= di 1)))
# Plain symbol binding
(bind-put new-bindings (pat :name) val)))
(destructure-bind ctx new-bindings pat val)
(+= i 2))))
(var result nil)
(each body-form body

View file

@ -59,6 +59,44 @@
["into map pairs" "{:a 1 :b 2}" "(into {} [[:a 1] [:b 2]])"]
["into map onto map" "{:a 1 :b 2 :c 3}" "(into {:a 1} [[:b 2] [:c 3]])"]
["into list" "(quote (3 2 1))" "(into (list) [1 2 3])"]
### ---- HIGH: destructuring ----
["destr nested seq" "[1 2 3]" "(let [[a [b c]] [1 [2 3]]] [a b c])"]
["destr rest+as" "[1 (quote (2 3)) [1 2 3]]" "(let [[a & r :as all] [1 2 3]] [a r all])"]
["destr map :keys" "[1 2]" "(let [{:keys [a b]} {:a 1 :b 2}] [a b])"]
["destr map :or" "[1 99]" "(let [{:keys [a b] :or {b 99}} {:a 1}] [a b])"]
["destr map :strs" "[1 2]" "(let [{:strs [a b]} {\"a\" 1 \"b\" 2}] [a b])"]
["destr map :as" "[1 {:a 1}]" "(let [{:keys [a] :as m} {:a 1}] [a m])"]
["destr nested map" "5" "(let [{{:keys [x]} :pos} {:pos {:x 5}}] x)"]
["destr fn-param seq" "7" "((fn [[a b]] (+ a b)) [3 4])"]
["destr fn-param map" "3" "((fn [{:keys [a b]}] (+ a b)) {:a 1 :b 2})"]
["destr let map key" "1" "(let [{a :a} {:a 1}] a)"]
### ---- HIGH: update / assoc-in on map literals ----
["update inc" "{:a 2}" "(update {:a 1} :a inc)"]
["update extra args" "{:a 111}" "(update {:a 1} :a + 10 100)"]
["update-in" "{:a {:b 2}}" "(update-in {:a {:b 1}} [:a :b] inc)"]
["assoc-in" "{:a {:b 1 :c 2}}" "(assoc-in {:a {:b 1}} [:a :c] 2)"]
["assoc-in create" "{:a {:b 1}}" "(assoc-in {} [:a :b] 1)"]
["update-in fnil" "{:a {:b 1}}" "(update-in {} [:a :b] (fnil inc 0))"]
["get-in" "1" "(get-in {:a {:b {:c 1}}} [:a :b :c])"]
### ---- HIGH: str semantics ----
["str nil empty" "\"\"" "(str nil)"]
["str concat nil" "\"a1\"" "(str \"a\" 1 nil)"]
["str keyword" "\":b\"" "(str :b)"]
["str symbol" "\"foo\"" "(str (quote foo))"]
["str mixed" "\"a:b1\"" "(str \"a\" :b 1)"]
["str seq" "\"[1 2 3]\"" "(str [1 2 3])"]
### ---- HIGH: dispatch ----
["multimethod" "9" "(do (defmulti area :shape) (defmethod area :sq [s] (* (:s s) (:s s))) (area {:shape :sq :s 3}))"]
["multimethod default" ":def" "(do (defmulti f identity) (defmethod f :default [x] :def) (f 99))"]
["protocol on record" "16" "(do (defprotocol Sh (ar [s])) (defrecord Sq [side] Sh (ar [_] (* side side))) (ar (->Sq 4)))"]
["reify dispatch" "42" "(do (defprotocol P (m [_])) (m (reify P (m [_] 42))))"]
### ---- HIGH: aliased namespace calls ----
["require :as alias" "\"1,2,3\"" "(do (require (quote [clojure.string :as s])) (s/join \",\" [1 2 3]))"]
])
(var pass 0)