diff --git a/jolt-core/clojure/core/20-coll.clj b/jolt-core/clojure/core/20-coll.clj index 726f7ec..caafba9 100644 --- a/jolt-core/clojure/core/20-coll.clj +++ b/jolt-core/clojure/core/20-coll.clj @@ -630,3 +630,105 @@ [v] (let [t (:test (meta v))] (if t (do (t) :ok) :no-test))) + +;; --- Phase 2 leaf batch 2 (jolt-ded): canonical Clojure ports ---------------- +;; key/val/find first — merge-with and memoize below use them. + +;; An entry is any 2-element vector in jolt ((seq m) yields tuples, find +;; builds a pvec — both count as entries; Clojure's stricter MapEntry-only +;; key/val has no analog here). +(defn- map-entryish? [e] (and (vector? e) (= 2 (count e)))) +(defn key [e] (if (map-entryish? e) (nth e 0) (throw (ex-info "key requires a map entry" {})))) +(defn val [e] (if (map-entryish? e) (nth e 1) (throw (ex-info "val requires a map entry" {})))) + +;; find was previously missing from jolt entirely. Presence (contains?), not +;; value, decides — so (find {:a nil} :a) is [:a nil]. Works on vectors by index. +(defn find [m k] + (when (contains? m k) [k (get m k)])) + +(defn some? [x] (not (nil? x))) +(defn true? [x] (= true x)) +(defn false? [x] (= false x)) + +;; Presence-preserving: a key with a nil value is kept ((hash-map) base keeps +;; nil values and canonicalizes collection keys). +(defn select-keys [map keyseq] + (reduce (fn [m k] (if (contains? map k) (assoc m k (get map k)) m)) + (hash-map) keyseq)) + +(defn zipmap [keys vals] + (loop [m (hash-map) ks (seq keys) vs (seq vals)] + (if (and ks vs) + (recur (assoc m (first ks) (first vs)) (next ks) (next vs)) + m))) + +;; conj semantics per entry arg (a map merges, a [k v] pair adds); nil args are +;; no-ops; all-nil (or no args) is nil. +(defn merge [& maps] + (when (some identity maps) + (reduce (fn [acc m] (if (nil? m) acc (conj (or acc (hash-map)) m))) + maps))) + +(defn merge-with [f & maps] + (when (some identity maps) + (let [merge-entry (fn [m e] + (let [k (key e) v (val e)] + ;; presence — not nil-of-value — decides combination + (if (contains? m k) + (assoc m k (f (get m k) v)) + (assoc m k v)))) + merge2 (fn [m1 m2] + (reduce merge-entry (or m1 (hash-map)) (seq m2)))] + (reduce merge2 maps)))) + +(defn get-in + ([m ks] (reduce get m ks)) + ([m ks not-found] + ;; a fresh table is its own identity — a present-but-nil step is + ;; distinguished from a missing one + (let [sentinel (hash-map)] + (loop [m m ks (seq ks)] + (if ks + (let [nxt (get m (first ks) sentinel)] + (if (identical? sentinel nxt) + not-found + (recur nxt (next ks)))) + m))))) + +;; find-based, so nil RESULTS are cached too (the old kernel fn re-computed +;; them); args canonicalize as a collection key. +(defn memoize [f] + (let [mem (atom (hash-map))] + (fn [& args] + ;; plain let/if, not if-let: this tier loads before 30-macros defines it + (let [e (find (deref mem) args)] + (if e + (val e) + (let [ret (apply f args)] + (swap! mem assoc args ret) + ret)))))) + +(defn partial + ([f] f) + ([f a] (fn [& args] (apply f a args))) + ([f a b] (fn [& args] (apply f a b args))) + ([f a b c] (fn [& args] (apply f a b c args))) + ([f a b c & more] (fn [& args] (apply f a b c (concat more args))))) + +(defn trampoline + ([f] (let [ret (f)] (if (fn? ret) (trampoline ret) ret))) + ([f & args] (trampoline (fn [] (apply f args))))) + +;; Canonical pairwise max/min: > / < throw on non-numbers, and the NaN +;; behavior is Clojure's by construction. +(defn max + ([x] x) + ([x y] (if (> x y) x y)) + ([x y & more] (reduce max (max x y) more))) + +(defn min + ([x] x) + ([x y] (if (< x y) x y)) + ([x y & more] (reduce min (min x y) more))) + +(defn reverse [coll] (reduce conj (list) coll)) diff --git a/jolt-core/clojure/core/MIGRATION.md b/jolt-core/clojure/core/MIGRATION.md deleted file mode 100644 index 1d36eb8..0000000 --- a/jolt-core/clojure/core/MIGRATION.md +++ /dev/null @@ -1,120 +0,0 @@ -# clojure.core migration worklist (jolt-1j0) - -Tracking the move of clojure.core from native Janet (`src/jolt/core.janet`, -4145 lines / 421 `core-*` fns) into the self-hosted Clojure overlay -(`jolt-core/clojure/core/`). Goal: shrink the Janet seed to `core-renames` + -genuinely host-coupled fns. - -## Phase 0 classification (heuristic — validate per batch) - -| Bucket | Count | Disposition | -|---|---|---| -| SEED (in `compiler/core-renames`) | 73 | stay in Janet (compiler emits `core-X` directly) | -| MACRO (in `core-macro-names`) | 44 | Phase 3 | -| HOST-coupled (atoms/vars/meta/proxy/transient/arrays/futures/ns/io) | 80 | Phase 4 (where feasible) / stay | -| LAZY-coupled | 28 | Phase 5 | -| MOVABLE pure-eager (candidates) | 193 | **Phase 2** | - -Counts are heuristic (name + body markers); the MOVABLE list still has some -host/lazy leakage (e.g. transient `assoc!`/`conj!`, `doall`/`dorun`, -`chunk-*`, `deliver`) to filter out as each batch is actually moved. - -**Key finding:** after removing SEED + HOST, the self-hosted compiler -(`jolt-core/jolt/{ir,analyzer}.clj`) uses **no** additional clojure.core fns -beyond the kernel tier (`second`/`peek`/`subvec`/`mapv`/`update`) plus host -primitives (`atom`/`swap!`/`reset!`). So **Phase 1 (compiler-dep kernel tier) -is essentially already complete** — to verify, not build. - -## Performance baseline (test/bench/core-bench.janet, compile mode, min of 5, ms) - -| bench | ms | -|---|---| -| fib | 128 | -| seq-pipe | 88 | -| reduce | 391 | -| into-vec | 194 | -| map-build | 681 | -| map-read | 6 | -| str-join | 244 | -| hof | 604 | -| **TOTAL** | **2336** | - -Re-run after each phase; watch for regressions as fns move from native Janet to -self-hosted Clojure (interpreted/compiled, slower than native primitives). - -## Per-batch workflow + gate (every migration step) -1. Canonical Clojure def in the overlay tier; remove the Janet `core-X` defn + - its `core-bindings` entry (confirm leaf first: only defn+binding refs). -2. **Add regression tests** for each moved fn — spec cases (test/spec/*-spec.janet, - interpret) and, for any fn whose behavior is subtle or was buggy, a case in the - 3-mode conformance set (test/integration/conformance-test.janet). -3. Gate: conformance ×3 modes · clojure-test-suite ≥ baseline · stage2==stage3 - fixpoint · fib compiled-fast · core-bench A/B under identical load (the - absolute number is load-sensitive — compare batch-vs-prior back to back). - -If a moved fn surfaces a latent bug (e.g. nthrest's nil-vs-() result, the -if-let/when-let else-scope leak), fix it to match Clojure and add a regression -test, rather than preserving the bug. - -## Phase 2 batches landed - -- **Sorted collections (jolt-0lj)** — sorted-map / sorted-map-by / sorted-set / - sorted-set-by / sorted? / sorted-map? / sorted-set? / subseq / rsubseq are - pure Clojure in their own tier (`25-sorted.clj`). Representation: tagged host - table with a comparator-ordered :entries vector and the ops ATTACHED to the - value (:ops map) — the seed dispatch branches call through (coll :ops), so no - module-level hooks and the ops survive forks/AOT images. Host surface grew by - two minimal value primitives: jolt.host/tagged-table and jolt.host/ref-get - (raw field read — plain `get` on a sorted coll is the comparator lookup and - would recurse). GOTCHA for future attached-ops ports: inside the overlay, - NEVER read your own wrapper's fields with `get`. - -- **Leaf batch (jolt-ded)** — complement, fnil (canonical 2/3/4-arity: patch - the first 1-3 args only, unlike the old patch-everything kernel fn), - clojure-version, bigdec, numerator, denominator, supers, munge, test moved - to 20-coll.clj. -- ***in* reader family (jolt-0d9)** — *in*, read-line, read, with-in-str, - line-seq in a new `50-io.clj` tier over two seed seams (__stdin-read-line, - __parse-next). GOTCHAS: a map LITERAL with :jolt/type as a key parses as a - tagged form (don't tag overlay value maps); a leftover seed stub holding the - same name breaks direct-linked overlay self-recursion (line-seq bound to the - stub's root and truncated after one element) — delete the stub first. - -## MOVABLE candidates (Phase 2 worklist, 193) ->Eduction NaN? abs aclone alength ancestors array-map array-seq assoc! associative? bean bigdec bigint biginteger boolean boolean? booleans byte bytes bytes? cat char char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class clojure-version comparator compare-and-set! completing conj! counted? decimal? deliver denominator derive descendants destructure disj disj! dissoc! distinct? doall dorun double? doubles drop-last eduction empty ensure-reduced enumeration-seq ex-cause ex-data ex-info ex-info? ex-message find float? floats force halt-when hash-combine hash-map hash-ordered-coll hash-set hash-unordered-coll ident? ifn? indexed? infinite? inst-ms inst? integer? ints isa? iterator-seq key keyword keyword-identical? list* list? longs macrofy map-entry? memfn munge nat-int? neg-int? not-any? not-every? nthnext nthrest numerator numeric= object? parents persistent! pop pop! pos-int? pr prefers println-str prn-str promise qualified-ident? qualified-keyword? qualified-symbol? rand rand-nth random-sample ratio? rational? rationalize re-groups re-matcher record? reduce-kv reduced reduced? reductions replace replicate resolve reversible? rseq rsubseq run! seq-to-map-for-destructuring seque set set? short shorts shuffle simple-ident? simple-keyword? simple-symbol? some-search sort sort-by sorted-map sorted-map-by sorted-map? sorted-set sorted-set-by sorted-set? special-symbol? split-at split-with str-join str-replace-all str-replace-first str-split subseq supers symbol tagged-literal tagged-literal? take-last test transduce unchecked-add unchecked-byte unchecked-char unchecked-dec unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-int unchecked-multiply unchecked-negate unchecked-remainder-int unchecked-short unchecked-subtract undefined? underive uri? uuid? val vector volatile! volatile? xml-seq - -## HOST-coupled (Phase 4 / stay, 80) -add-watch aget alter-meta! alter-var-root aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short atom atom? avoid-method-too-large boolean-array bounded-count byte-array char-array construct-proxy copy-core-var copy-var delay? deref double-array float-array future-call future-cancel future-cancelled? future-done? future? get-proxy-class get-validator init-proxy int-array intern into-array long-array make-array make-delay meta namespace namespace-munge new-var ns-name object-array pop-thread-bindings prefer-method print-dup print-method print-str proxy-call-with-super proxy-mappings proxy-super push-thread-bindings reader-conditional reader-conditional? remove-watch reset! reset-meta! reset-vals! set-validator! short-array swap! swap-vals! thread-first thread-last to-array to-array-2d transient transient? update-proxy var-dynamic? var-get var-set var? vary-meta vreset! vswap! with-meta - -## LAZY-coupled (Phase 5, 28) -concat cycle dedupe distinct flatten interleave interpose iterate keep keep-indexed line-seq macro-names map-indexed mapcat partition partition-all partition-by rand-int random-uuid realized? repeat repeatedly seqable? sequence sequential? take-nth trampoline tree-seq unreduced - -## Phase 3 (macros) — status & findings - -20 macros moved to the overlay: 19 user-facing in `30-macros.clj`, plus `when` -in a new `00-syntax.clj` tier loaded **before** the kernel (interpreted defmacros, -so the macros exist before any code that uses them compiles). - -Macro-authoring toolkit for jolt (learned the hard way): -- single-template hygiene: auto-gensym `foo#` -- shared explicit fresh symbol: `(symbol (str (gensym)))` — a bare `(gensym)` in a - macro body returns a *Janet* symbol the destructurer rejects -- let-rebinding: splice binding *pairs* into a TEMPLATE vector (`[~a ~b ~@pairs]`), - not a pre-built pvec value — `core-let` wants a tuple form -- build sub-forms via templates, never `cons`/`list` (those make plists the - evaluator can't run as a form) -- Jolt `defmacro` is **single-arity** — use `& rest`/destructuring -- syntax-tier macros may use only special forms + core-renames seed primitives - -**Performance wall (the hot macros stay in Janet for now):** the load-order story -works, but moving the *hot* fundamental control macros (`and`/`or`/`cond`/ -`when-not`) regressed the battery — as interpreted overlay defmacros they expand -slower than native Janet, and since they appear in nearly every form the -cumulative overhead tipped a heavy suite file over the 6 s per-file timeout -(3930 -> 3911, +1 timeout). They are correct (conformance 228×3, all edge cases), -but reverted. Moving `and/or/cond/when-not/case/doseq/declare/cond->/->/->>` -needs a **fast (compiled) macro-expansion path**, not interpreted defmacros. - -Deferred: `defn/defn-/fn/let/loop` (fundamental + same speed concern), the type -machinery (`defrecord/defprotocol/extend-*/reify/proxy/definterface` → Phase 4), -`lazy-seq/lazy-cat` (→ Phase 5). diff --git a/src/jolt/core.janet b/src/jolt/core.janet index a5768a8..2d2170f 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -177,7 +177,7 @@ (defn core-nil? [x] (nil? x)) (defn core-not [x] (if x false true)) -(defn core-some? [x] (not (nil? x))) +# some? / true? / false? now live in the Clojure collection tier. (defn core-string? [x] (string? x)) (defn core-number? [x] (number? x)) (defn core-fn? [x] (or (function? x) (cfunction? x))) @@ -206,8 +206,8 @@ (= :jolt/sorted-map (get x :jolt/type)) (= :jolt/sorted-set (get x :jolt/type)))))) -(defn core-true? [x] (= true x)) -(defn core-false? [x] (= false x)) + + (defn core-identical? [a b] (= a b)) # Strictness helpers: like Clojure, numeric ops reject non-numbers, and the @@ -304,8 +304,9 @@ (let [m (core-rem n d)] (if (or (= m 0) (= (> n 0) (> d 0))) m (+ m d))))) -(defn core-max [& args] (each x args (need-num x "max")) (apply max args)) -(defn core-min [& args] (each x args (need-num x "min")) (apply min args)) +# max / min now live in the Clojure collection tier (canonical pairwise +# >/<, so non-numbers throw and NaN behaves as on the JVM). + (defn core-rand [& n] (let [r (math/random)] (if (empty? n) r (* r (in n 0))))) (defn core-rand-int [n] (math/floor (* (math/random) n))) @@ -466,7 +467,10 @@ (do (var result coll) (each x xs - (if (map-value? x) + (cond + # conj nil onto a map is a no-op (Clojure) + (nil? x) nil + (map-value? x) # conj a map -> merge its entries (each e (map-entries-of x) (set result (phm-assoc result (in e 0) (in e 1)))) @@ -475,7 +479,9 @@ (do (var result coll) (each x xs - (if (map-value? x) + (cond + (nil? x) nil + (map-value? x) (each e (map-entries-of x) (set result (map-assoc1 result (in e 0) (in e 1)))) (set result (map-assoc1 result (vnth x 0) (vnth x 1))))) @@ -604,20 +610,7 @@ (realize-for-iteration t))] (jolt-call f ;fixed ;tail))))) -(defn core-get-in [m ks &opt default] - (default default nil) - (def ks (vview ks)) - # Walk with a fresh sentinel so a PRESENT key whose value is nil is distinguished - # from a missing key: only a genuinely-absent step falls back to default. - (def absent @{}) - (var current m) - (var i 0) - (var missing false) - (while (< i (length ks)) - (let [nxt (core-get current (ks i) absent)] - (if (= nxt absent) (do (set missing true) (break)) (set current nxt))) - (++ i)) - (if missing default current)) +# get-in now lives in the Clojure collection tier (core/20-coll.clj). (defn core-contains? [coll key] (if (core-sorted? coll) (if ((sorted-op coll :contains) coll key) true false) @@ -774,59 +767,9 @@ # ((into #{} [:a :b]) was #{}, jolt-h86) (do (var result to) (each x items (set result (core-conj result x))) result))) -(defn core-merge [& maps] - # Clojure: (when (some identity maps) (reduce conj (or (first maps) {}) (rest maps))) - # - (merge) and (merge nil nil) -> nil; nil args elsewhere are no-ops. - # - later args follow conj semantics (a map merges its entries; a [k v] - # vector/map-entry adds that entry). - (var any false) - (each m maps (when (not (nil? m)) (set any true))) - (if (not any) - nil - (do - (var result (let [f (in maps 0)] (if (nil? f) (struct) f))) - (var i 1) - (while (< i (length maps)) - (let [m (in maps i)] - (cond - (nil? m) nil - (or (phm? m) (struct? m)) - (each e (map-entries-of m) - (set result (core-assoc result (in e 0) (in e 1)))) - # a [k v] pair (map-entry / 2-vector), per conj - (and (or (pvec? m) (tuple? m) (array? m)) - (= 2 (if (pvec? m) (pv-count m) (length m)))) - (set result (core-assoc result (vnth m 0) (vnth m 1))) - # scalars, sets, and wrong-length sequentials can't merge into a map - # (a length-2 vector was handled above; anything else here is bad) - (or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m) - (set? m) (plist? m) (pvec? m) (tuple? m) (array? m) - (and (struct? m) (get m :jolt/type))) - (error (string "Can't merge " (type m) " into a map")) - # other map-like tables (records, sorted-maps, host tables): lenient conj - (set result (core-conj result m)))) - (++ i)) - result))) +# merge now lives in the Clojure collection tier (core/20-coll.clj). -(defn core-merge-with [f & maps] - # Presence — not nil-of-value — decides whether to combine: a key present in the - # accumulator with a nil value still triggers (f existing v), matching Clojure. - (if (= 0 (length maps)) - nil - (do - (var result (first maps)) - (var mi 1) - (while (< mi (length maps)) - (let [m (maps mi)] - (when m - (each e (map-entries-of m) - (let [k (in e 0) v (in e 1)] - (set result - (if (core-contains? result k) - (core-assoc result k (f (core-get result k) v)) - (core-assoc result k v))))))) - (++ mi)) - result))) +# merge-with now lives in the Clojure collection tier (core/20-coll.clj). (defn core-keys [m] # phm-entries (not phm-to-struct) so keys mapped to nil values are not dropped. @@ -837,24 +780,9 @@ (if (core-sorted-map? m) ((sorted-op m :vals) m) (if (phm? m) (tuple ;(map |(in $ 1) (phm-entries m))) (tuple ;(map |(m $) (keys m)))))) -(defn core-select-keys [m ks] - # Include a key when it is PRESENT (contains?), even if its value is nil — a - # struct/table would drop a nil value, so collect entries and build via kvs->map. - (def kvs @[]) - (each k (realize-for-iteration ks) - (when (core-contains? m k) - (array/push kvs k) (array/push kvs (core-get m k)))) - (kvs->map kvs)) +# select-keys now lives in the Clojure collection tier (core/20-coll.clj). -(defn core-zipmap [ks vs] - (let [ks (realize-for-iteration ks) vs (realize-for-iteration vs)] - # collect pairs, then build once — a nil key/value must survive (kvs->map -> phm) - (def kvs @[]) - (var i 0) - (while (and (< i (length ks)) (< i (length vs))) - (array/push kvs (in ks i)) (array/push kvs (in vs i)) - (++ i)) - (kvs->map kvs))) +# zipmap now lives in the Clojure collection tier (core/20-coll.clj). # ============================================================ # Transducers @@ -1304,29 +1232,7 @@ (if ok hit nil))) (make-lazy-seq (step init-cs nil))))) -(defn core-reverse [coll] - (if (nil? coll) @[] - (if (lazy-seq? coll) - (do - (var result @[]) - (var cur coll) - # seq-done?, not (nil? (ls-first)): a nil element must not end the walk. - (while (not (seq-done? cur)) - (array/push result (core-first cur)) - (set cur (core-rest cur))) - (var reversed @[]) - (var i (dec (length result))) - (while (>= i 0) - (array/push reversed (in result i)) - (-- i)) - reversed) - (let [c (realize-for-iteration coll)] - (var result @[]) - (var i (dec (length c))) - (while (>= i 0) - (array/push result (in c i)) - (-- i)) - result)))) +# reverse now lives in the Clojure collection tier ((reduce conj () coll)). (defn core-nth "Return the nth element of a sequential collection. With a not-found arg, return @@ -1445,10 +1351,7 @@ # tier — core/00-kernel.clj.) # subvec lives in the Clojure kernel tier — core/00-kernel.clj. -(defn core-trampoline [f & args] - (var result (apply f args)) - (while (function? result) (set result (result))) - result) +# trampoline now lives in the Clojure collection tier (core/20-coll.clj). (def core-format (fn [fmt & args] (string/format fmt ;args))) @@ -1523,20 +1426,12 @@ (-- i)) (f result)))))) -(defn core-partial [f & args] - (fn [& more] (apply f (array/concat (array/slice args) more)))) +# partial now lives in the Clojure collection tier (canonical arities). # juxt now lives in the Clojure collection tier (core/20-coll.clj). -(defn core-memoize [f] - (var cache @{}) - (fn [& args] - (let [key (tuple ;args)] - (if-let [v (get cache key)] - v - (let [result (apply f args)] - (put cache key result) - result))))) +# memoize now lives in the Clojure collection tier — find-based, so it +# caches nil results too (this kernel fn re-computed them). # ============================================================ # Collection constructors @@ -2616,8 +2511,8 @@ # lists are arrays. So key/val/map-entry? accept a 2-tuple and reject a plain # vector, matching Clojure (where a MapEntry is distinct from a vector). (defn- entry-like? [x] (and (tuple? x) (= 2 (length x)))) -(defn core-key [e] (if (entry-like? e) (in e 0) (error "key requires a map entry"))) -(defn core-val [e] (if (entry-like? e) (in e 1) (error "val requires a map entry"))) +# key / val now live in the Clojure collection tier (core/20-coll.clj), +# along with find (previously missing from jolt entirely). (defn core-map-entry? [x] (entry-like? x)) # Reversible (supports rseq) = vectors and sorted collections. @@ -2818,7 +2713,6 @@ (def- core-bindings "Map of symbol name → function for all core functions." @{"nil?" core-nil? - "some?" core-some? "string?" core-string? "number?" core-number? "fn?" core-fn? @@ -2828,8 +2722,6 @@ "map?" core-map? "seq?" core-seq? "coll?" core-coll? - "true?" core-true? - "false?" core-false? "identical?" core-identical? "zero?" core-zero? "pos?" core-pos? @@ -2856,8 +2748,6 @@ "mod" core-mod "rem" core-rem "quot" core-quot - "max" core-max - "min" core-min "rand" core-rand "rand-int" core-rand-int "=" core-= @@ -2870,10 +2760,8 @@ "assoc" core-assoc "dissoc" core-dissoc "get" core-get - "get-in" core-get-in "contains?" core-contains? "count" core-count - "trampoline" core-trampoline "format" core-format "first" core-first "rest" core-rest @@ -2887,20 +2775,14 @@ "__sqmap" core-sqmap "__sqset" core-sqset "into" core-into - "merge" core-merge - "merge-with" core-merge-with "keys" core-keys "vals" core-vals - "select-keys" core-select-keys "with-meta" core-with-meta - "zipmap" core-zipmap "map" core-map "filter" core-filter "remove" core-remove "reduce" core-reduce "apply" core-apply - "key" core-key - "val" core-val "map-entry?" core-map-entry? "special-symbol?" core-special-symbol? "promise" core-promise @@ -2983,7 +2865,6 @@ "take-while" core-take-while "drop-while" core-drop-while "concat" core-concat - "reverse" core-reverse "nth" core-nth "sort" core-sort "sort-by" core-sort-by @@ -2992,8 +2873,6 @@ "identity" core-identity "constantly" core-constantly "comp" core-comp - "partial" core-partial - "memoize" core-memoize "vector" core-vector "hash-map" core-hash-map "array-map" core-array-map diff --git a/test/spec/functions-spec.janet b/test/spec/functions-spec.janet index b06e4a4..b2d1689 100644 --- a/test/spec/functions-spec.janet +++ b/test/spec/functions-spec.janet @@ -54,3 +54,51 @@ ["munge dashes" "\"a_b\"" "(munge \"a-b\")"] ["munge symbol" "\"x_y\"" "(munge (quote x-y))"] ["test no-test" ":no-test" "(test (quote foo))"]) + +# Phase 2 leaf batch 2 (jolt-ded): canonical ports of key/val/select-keys/ +# zipmap/merge/merge-with/get-in/memoize/partial/trampoline/some?/true?/false?/ +# max/min/reverse, plus find (previously missing entirely). +(defspec "clojure.core / leaf batch 2" + ["key" "1" "(key (first {1 :a}))"] + ["val" ":a" "(val (first {1 :a}))"] + ["key non-entry throws" :throws "(key 5)"] + ["find hit" "[:a 1]" "(find {:a 1} :a)"] + ["find miss" "nil" "(find {:a 1} :b)"] + ["find nil value" "[:a nil]" "(find {:a nil} :a)"] + ["find on vector" "[0 :x]" "(find [:x :y] 0)"] + ["select-keys" "{:a 1}" "(select-keys {:a 1 :b 2} [:a])"] + ["select-keys nil val" "{:a nil}" "(select-keys {:a nil :b 2} [:a])"] + ["select-keys missing" "{}" "(select-keys {:a 1} [:z])"] + ["zipmap" "{:a 1 :b 2}" "(zipmap [:a :b] [1 2])"] + ["zipmap uneven" "{:a 1}" "(zipmap [:a :b] [1])"] + ["zipmap nil val" "{:a nil}" "(zipmap [:a] [nil])"] + ["merge" "{:a 1 :b 2}" "(merge {:a 1} {:b 2})"] + ["merge later wins" "{:a 2}" "(merge {:a 1} {:a 2})"] + ["merge nil arg" "{:a 1}" "(merge {:a 1} nil)"] + ["merge nil first" "{:a 1}" "(merge nil {:a 1})"] + ["merge all nil" "nil" "(merge nil nil)"] + ["merge empty" "nil" "(merge)"] + ["merge entry pair" "{:a 1 :b 2}" "(merge {:a 1} [:b 2])"] + ["merge-with" "{:a 3}" "(merge-with + {:a 1} {:a 2})"] + ["merge-with disjoint" "{:a 1 :b 2}" "(merge-with + {:a 1} {:b 2})"] + ["merge-with nil-val present" "{:a 1}" "(merge-with (fn [a b] (or a b)) {:a nil} {:a 1})"] + ["get-in" "1" "(get-in {:a {:b 1}} [:a :b])"] + ["get-in missing" ":nf" "(get-in {:a 1} [:z :y] :nf)"] + ["get-in nil value present" "nil" "(get-in {:a {:b nil}} [:a :b] :nf)"] + ["get-in empty path" "{:a 1}" "(get-in {:a 1} [])"] + ["memoize" "2" "(do (def c (atom 0)) (def f (memoize (fn [x] (swap! c inc) x))) (f 1) (f 1) (f 2) (deref c))"] + ["memoize caches nil" "1" "(do (def c (atom 0)) (def f (memoize (fn [x] (swap! c inc) nil))) (f 1) (f 1) (deref c))"] + ["partial" "6" "((partial + 1 2) 3)"] + ["partial no extra" "3" "((partial + 1 2))"] + ["partial many fixed" "15" "((partial + 1 2 3 4) 5)"] + ["trampoline" "10" "(trampoline (fn f [n acc] (if (zero? n) acc (fn [] (f (dec n) (+ acc 2))))) 5 0)"] + ["some? true" "true" "(some? 0)"] + ["some? false" "false" "(some? nil)"] + ["true?/false?" "[true false false]" "[(true? true) (true? 1) (false? nil)]"] + ["max" "3" "(max 1 3 2)"] + ["min" "1" "(min 3 1 2)"] + ["max single" "5" "(max 5)"] + ["max non-number throws" :throws "(max 1 :a)"] + ["reverse" "(quote (3 2 1))" "(reverse [1 2 3])"] + ["reverse empty" "()" "(reverse nil)"] + ["conj nil onto map" "{:a 1}" "(conj {:a 1} nil)"])