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

@ -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