diff --git a/docs/spec/coverage.md b/docs/spec/coverage.md index 3f919c2..b7fa36f 100644 --- a/docs/spec/coverage.md +++ b/docs/spec/coverage.md @@ -3,14 +3,14 @@ Generated 2026-06-10 by `tools/spec_coverage.py` — do not edit by hand. Surface: **694** clojure.core vars (ClojureDocs export; 648 with -community examples). jolt interns 534 of them. +community examples). jolt interns 550 of them. | Status | Count | Meaning | |---|---|---| -| implemented+tested | 380 | in jolt and exercised by spec/conformance | -| implemented-untested | 154 | in jolt, no direct test — spec entries will add them | +| implemented+tested | 397 | in jolt and exercised by spec/conformance | +| implemented-untested | 153 | in jolt, no direct test — spec entries will add them | | resolvable-not-interned | 22 | works in code but invisible to ns introspection (conformance finding) | -| missing-portable | 35 | portable semantics, jolt lacks it — implementation gap | +| missing-portable | 19 | portable semantics, jolt lacks it — implementation gap | | special-form | 13 | specified in §3, not a library var | | dynamic-var | 29 | classification needed: portable default vs host-dependent | | agents-taps | 22 | out of scope pending concurrency design note | @@ -103,7 +103,7 @@ UNVERIFIED field; that column will be added as entries land. | `agent-errors` | agents-taps | | | `aget` | implemented+tested | ✓ | | `alength` | implemented+tested | ✓ | -| `alias` | missing-portable | ✓ | +| `alias` | implemented+tested | ✓ | | `all-ns` | implemented-untested | ✓ | | `alter` | stm-refs | ✓ | | `alter-meta!` | implemented+tested | ✓ | @@ -390,7 +390,7 @@ UNVERIFIED field; that column will be added as entries land. | `long-array` | implemented-untested | ✓ | | `longs` | implemented-untested | ✓ | | `loop` | implemented+tested | ✓ | -| `macroexpand` | missing-portable | ✓ | +| `macroexpand` | implemented+tested | ✓ | | `macroexpand-1` | implemented+tested | ✓ | | `make-array` | implemented-untested | ✓ | | `make-hierarchy` | implemented+tested | ✓ | @@ -423,7 +423,7 @@ UNVERIFIED field; that column will be added as entries land. | `neg-int?` | implemented+tested | ✓ | | `neg?` | implemented+tested | ✓ | | `new` | special-form | ✓ | -| `newline` | missing-portable | ✓ | +| `newline` | implemented+tested | ✓ | | `next` | implemented+tested | ✓ | | `nfirst` | implemented-untested | ✓ | | `nil?` | implemented+tested | ✓ | @@ -434,15 +434,15 @@ UNVERIFIED field; that column will be added as entries land. | `not-every?` | implemented+tested | ✓ | | `not=` | implemented+tested | ✓ | | `ns` | implemented+tested | ✓ | -| `ns-aliases` | implemented-untested | ✓ | +| `ns-aliases` | implemented+tested | ✓ | | `ns-imports` | implemented-untested | ✓ | | `ns-interns` | implemented-untested | ✓ | | `ns-map` | implemented-untested | ✓ | | `ns-name` | implemented+tested | ✓ | -| `ns-publics` | missing-portable | ✓ | +| `ns-publics` | implemented+tested | ✓ | | `ns-refers` | missing-portable | ✓ | | `ns-resolve` | implemented+tested | ✓ | -| `ns-unalias` | missing-portable | ✓ | +| `ns-unalias` | implemented+tested | ✓ | | `ns-unmap` | implemented-untested | ✓ | | `nth` | implemented+tested | ✓ | | `nthnext` | implemented+tested | ✓ | @@ -454,16 +454,16 @@ UNVERIFIED field; that column will be added as entries land. | `odd?` | implemented+tested | ✓ | | `or` | implemented+tested | ✓ | | `parents` | implemented+tested | ✓ | -| `parse-boolean` | missing-portable | ✓ | -| `parse-double` | missing-portable | ✓ | -| `parse-long` | missing-portable | ✓ | +| `parse-boolean` | implemented+tested | ✓ | +| `parse-double` | implemented+tested | ✓ | +| `parse-long` | implemented+tested | ✓ | | `parse-uuid` | implemented+tested | ✓ | | `partial` | implemented+tested | ✓ | | `partition` | implemented+tested | ✓ | | `partition-all` | implemented+tested | ✓ | | `partition-by` | implemented+tested | ✓ | -| `partitionv` | missing-portable | | -| `partitionv-all` | missing-portable | | +| `partitionv` | implemented+tested | | +| `partitionv-all` | implemented+tested | | | `pcalls` | jvm-specific | ✓ | | `peek` | implemented+tested | ✓ | | `persistent!` | implemented+tested | ✓ | @@ -611,7 +611,7 @@ UNVERIFIED field; that column will be added as entries land. | `spit` | implemented-untested | ✓ | | `split-at` | implemented+tested | ✓ | | `split-with` | implemented+tested | ✓ | -| `splitv-at` | missing-portable | | +| `splitv-at` | implemented+tested | | | `str` | implemented+tested | ✓ | | `stream-into!` | jvm-specific | | | `stream-reduce!` | jvm-specific | | @@ -640,7 +640,7 @@ UNVERIFIED field; that column will be added as entries land. | `the-ns` | implemented+tested | ✓ | | `thread-bound?` | missing-portable | ✓ | | `throw` | special-form | ✓ | -| `time` | missing-portable | ✓ | +| `time` | implemented+tested | ✓ | | `to-array` | implemented-untested | ✓ | | `to-array-2d` | implemented+tested | ✓ | | `trampoline` | implemented+tested | ✓ | @@ -678,9 +678,9 @@ UNVERIFIED field; that column will be added as entries land. | `unsigned-bit-shift-right` | implemented-untested | ✓ | | `update` | implemented+tested | ✓ | | `update-in` | implemented+tested | ✓ | -| `update-keys` | missing-portable | ✓ | +| `update-keys` | implemented+tested | ✓ | | `update-proxy` | implemented-untested | ✓ | -| `update-vals` | missing-portable | ✓ | +| `update-vals` | implemented+tested | ✓ | | `uri?` | implemented-untested | ✓ | | `use` | implemented+tested | ✓ | | `uuid?` | implemented+tested | ✓ | @@ -714,8 +714,8 @@ UNVERIFIED field; that column will be added as entries land. | `with-open` | missing-portable | ✓ | | `with-out-str` | implemented+tested | ✓ | | `with-precision` | missing-portable | ✓ | -| `with-redefs` | missing-portable | ✓ | -| `with-redefs-fn` | missing-portable | ✓ | +| `with-redefs` | implemented+tested | ✓ | +| `with-redefs-fn` | implemented+tested | ✓ | | `xml-seq` | implemented-untested | ✓ | | `zero?` | implemented+tested | ✓ | | `zipmap` | implemented+tested | ✓ | diff --git a/jolt-core/clojure/core/20-coll.clj b/jolt-core/clojure/core/20-coll.clj index 88698ab..4b69d10 100644 --- a/jolt-core/clojure/core/20-coll.clj +++ b/jolt-core/clojure/core/20-coll.clj @@ -287,6 +287,46 @@ (defn tagged-literal? [x] (= (get x :jolt/type) :jolt/tagged-literal)) (defn record? [x] (some? (get x :jolt/deftype))) (defn uuid? [x] (= (get x :jolt/type) :jolt/uuid)) + +;; Clojure 1.11 map transformers. PHM base so transformed keys canonicalize +;; (collisions: last entry in seq order wins, matching the reference). +(defn update-keys [m f] + (reduce-kv (fn [acc k v] (assoc acc (f k) v)) (hash-map) m)) + +(defn update-vals [m f] + (reduce-kv (fn [acc k v] (assoc acc k (f v))) (hash-map) m)) + +;; Vector-returning partition variants (1.11): lazy seqs OF vectors. +(defn partitionv + ([n coll] (map vec (partition n coll))) + ([n step coll] (map vec (partition n step coll))) + ([n step pad coll] (map vec (partition n step pad coll)))) + +(defn partitionv-all + ([n coll] (map vec (partition-all n coll))) + ([n step coll] (map vec (partition-all n step coll)))) + +;; First part a vector, rest a seq — matching the reference implementation. +(defn splitv-at [n coll] + [(vec (take n coll)) (drop n coll)]) + +;; with-redefs-fn: temporarily set each var's root to the mapped value, run +;; the thunk, restore the saved roots even on throw. The with-redefs macro +;; (30-macros) builds the {var val} map from names. +(defn with-redefs-fn [binding-map func] + (let [vars (vec (keys binding-map)) + saved (mapv var-get vars)] + (doseq [v vars] (var-set v (get binding-map v))) + (try + (func) + (finally + ;; loop/recur, not dotimes: dotimes is a 30-macros macro and this tier + ;; compiles before it exists (a forward ref would resolve to the macro + ;; fn at runtime and mis-apply it). + (loop [i 0] + (when (< i (count vars)) + (var-set (nth vars i) (nth saved i)) + (recur (inc i)))))))) ;; Jolt has no chunked seqs (Phase 5 territory), so this is always false. (defn chunked-seq? [x] false) diff --git a/jolt-core/clojure/core/30-macros.clj b/jolt-core/clojure/core/30-macros.clj index 4c8d3f9..01029a1 100644 --- a/jolt-core/clojure/core/30-macros.clj +++ b/jolt-core/clojure/core/30-macros.clj @@ -57,6 +57,22 @@ ;; defonce: define name only if it isn't already bound to a non-nil root; ;; returns the existing var untouched otherwise (matching the prior arm). +;; time: evaluate expr, print the elapsed wall-clock, return the value. +;; current-time-ms is the host's monotonic clock. +(defmacro time [expr] + `(let [start# (current-time-ms) + ret# ~expr] + (println (str "Elapsed time: " (- (current-time-ms) start#) " msecs")) + ret#)) + +;; with-redefs: temporary root rebinding, restored on exit (incl. throw). +;; Builds (hash-map (var n1) v1 ...) — a call form, since map-literal forms +;; can't carry call forms as keys. +(defmacro with-redefs [bindings & body] + (let [pairs (reduce (fn [acc p] (conj (conj acc `(var ~(first p))) (second p))) + [] (partition 2 bindings))] + `(with-redefs-fn (hash-map ~@pairs) (fn [] ~@body)))) + (defmacro defonce [name expr] `(let [v# (resolve (quote ~name))] (if (and v# (some? (var-get v#))) diff --git a/src/jolt/backend.janet b/src/jolt/backend.janet index 071c4a4..f9372cb 100644 --- a/src/jolt/backend.janet +++ b/src/jolt/backend.janet @@ -351,15 +351,24 @@ # jolt.analyzer), and the interpreter rebinds current-ns to a fn's defining ns # while it runs — so h/current-ns must read this instead of ctx-current-ns. (put (ctx :env) :compile-ns (ctx-current-ns ctx)) + (def saved-ns (ctx-current-ns ctx)) (def av (ns-find (ctx-find-ns ctx "jolt.analyzer") "analyze")) # Pre-kernel bootstrap: ensure-analyzer is gated until the kernel tier loads # (see api/load-core-overlay!), so a compile request from an earlier tier (e.g. # 00-syntax's destructure defn) finds no analyzer. That fallback is DESIGNED — # route it through the sanctioned punt channel rather than crashing on a nil var. - (unless av (error "jolt/uncompilable: analyzer not built (pre-kernel bootstrap)")) - (def r ((var-get av) ctx form)) + (unless av + (put (ctx :env) :compile-ns nil) + (error "jolt/uncompilable: analyzer not built (pre-kernel bootstrap)")) + # The analyzer runs INTERPRETED; the interpreter rebinds current-ns to a fn's + # defining ns (jolt.analyzer) while it runs and only restores on normal return. + # A punt THROWS out of those frames, leaking jolt.analyzer as current-ns (and + # :compile-ns stayed set) — the fallback interpretation then resolves user vars + # against the wrong ns. Restore both on every exit. + (def r (protect ((var-get av) ctx form))) (put (ctx :env) :compile-ns nil) - r) + (ctx-set-current-ns ctx saved-ns) + (if (r 0) (r 1) (error (r 1)))) # The analyzer's deliberate punt signal — (uncompilable why) throws the string # "jolt/uncompilable: ". Anything else escaping the compile step is an diff --git a/src/jolt/core.janet b/src/jolt/core.janet index b03696a..c66075c 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -1409,12 +1409,15 @@ # (core/20-coll.clj). (defn core-partition - "(partition n coll) or (partition n step coll). Only complete partitions of - size n are kept (use partition-all to keep the trailing remainder)." + "(partition n coll), (partition n step coll), or (partition n step pad coll). + Only complete partitions of size n are kept; with pad, the final partial + partition is padded from pad (possibly to fewer than n if pad runs out)." [n & rest] - (let [has-step (> (length rest) 1) - step (if has-step (first rest) n) - coll (if has-step (in rest 1) (first rest))] + (let [argc (length rest) + step (if (>= argc 2) (first rest) n) + pad (if (>= argc 3) (in rest 1) nil) + has-pad (>= argc 3) + coll (case argc 1 (first rest) 2 (in rest 1) 3 (in rest 2))] # Option A: always lazy. (defn pstep [c] (fn [] @@ -1425,9 +1428,16 @@ (array/push part (core-first cur)) (set cur (core-rest cur)) (++ i)) - (if (= i n) + (cond + (= i n) (let [next-cur (if (= step n) cur (lazy-from (core-drop (- step n) cur)))] @[(tuple/slice (tuple ;part)) (pstep next-cur)]) + # partial final partition: pad it (last partition, then stop) + (and has-pad (> i 0)) + (do + (each x (realize-for-iteration pad) + (when (< (length part) n) (array/push part x))) + @[(tuple/slice (tuple ;part)) (fn [] nil)]) nil))))) (make-lazy-seq (pstep (lazy-from coll))))) @@ -1775,6 +1785,44 @@ (prin "\n") nil) +(defn core-newline [] (prin "\n") nil) + +# Clojure 1.11 string->scalar parsers: nil on malformed input, throw on a +# non-string. Validation is strict (scan-number alone accepts 0x10 etc.). +(defn- parse-arg-str [s who] + (if (or (string? s) (buffer? s)) (string s) + (error (string who " requires a string, got " (type s))))) + +(defn core-parse-long [s] + (def str* (parse-arg-str s "parse-long")) + (def n (length str*)) + (def start (if (and (> n 0) (or (= 43 (in str* 0)) (= 45 (in str* 0)))) 1 0)) + (if (and (> n start) + (do (var ok true) + (for i start n (when (or (< (in str* i) 48) (> (in str* i) 57)) (set ok false))) + ok)) + (scan-number str*) + nil)) + +(defn core-parse-double [s] + (def str* (parse-arg-str s "parse-double")) + # strict float shape: [+-] digits [. digits] [eE [+-] digits] — at least one + # digit overall; "Infinity"/"-Infinity"/"NaN" accepted like the reference. + (cond + (= str* "Infinity") math/inf + (= str* "-Infinity") (- math/inf) + (= str* "NaN") math/nan + (do + (def pat (peg/compile ~(sequence (opt (set "+-")) (choice (sequence (some :d) (opt (sequence "." (any :d)))) (sequence "." (some :d))) (opt (sequence (set "eE") (opt (set "+-")) (some :d))) -1))) + (if (peg/match pat str*) (scan-number str*) nil)))) + +(defn core-parse-boolean [s] + (def str* (parse-arg-str s "parse-boolean")) + (case str* "true" true "false" false nil)) + +# Host time source for the `time` macro (monotonic, milliseconds). +(defn core-current-time-ms [] (* 1000 (os/clock :monotonic))) + # Capture *out*: run thunk with Janet's :out dynamic bound to a buffer, so all # print/println/pr/prn output (which go through `prin` -> (dyn :out)) is collected # and returned as a string. The with-out-str macro (overlay) wraps a body thunk. @@ -2942,6 +2990,11 @@ "hash-unordered-coll" core-hash-unordered-coll "prefers" core-prefers "random-uuid" core-random-uuid + "parse-long" core-parse-long + "parse-double" core-parse-double + "parse-boolean" core-parse-boolean + "newline" core-newline + "current-time-ms" core-current-time-ms "parse-uuid" core-parse-uuid "interpose" core-interpose "mapcat" core-mapcat diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 147c8b9..c9f98e0 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -784,13 +784,14 @@ (defn require-impl "(require '[ns :as a :refer [...]] ...) — load + alias/refer each spec. A fn, so the args (quoted specs) arrive evaluated. Varargs (Clojure-compatible); each spec - is a vector. eval-require does the namespace load + alias/refer into current-ns." + is a vector [ns & opts] or a bare ns symbol (treated as [ns])." [ctx & specs] (each spec specs (let [s (if (pvec? spec) (pv->array spec) spec)] - (if (and (indexed? s) (> (length s) 0)) - (eval-require ctx s) - (error "require expects a vector spec")))) + (cond + (and (indexed? s) (> (length s) 0)) (eval-require ctx s) + (and (struct? s) (= :symbol (s :jolt/type))) (eval-require ctx @[s]) + (error "require expects a vector spec or a namespace symbol")))) nil) (defn in-ns-impl @@ -1102,15 +1103,47 @@ # Reader / expansion as plain fns: read-string parses one form; macroexpand-1 # expands a (quoted, already-evaluated) call form once via its macro var. (ns-intern core "read-string" (fn [s] (parse-string s))) - (ns-intern core "macroexpand-1" + (def expand-1 (fn [the-form] + (if (and (array? the-form) (> (length the-form) 0) + (struct? (first the-form)) (= :symbol ((first the-form) :jolt/type))) + (let [v (resolve-var ctx @{} (first the-form))] + (if (and v (var-macro? v)) + (apply (var-get v) (tuple/slice the-form 1)) + the-form)) + the-form))) + (ns-intern core "macroexpand-1" expand-1) + # macroexpand: expand repeatedly until the head is no longer a macro (the + # form's SUBFORMS are not expanded, matching Clojure). + (ns-intern core "macroexpand" (fn [the-form] - (if (and (array? the-form) (> (length the-form) 0) - (struct? (first the-form)) (= :symbol ((first the-form) :jolt/type))) - (let [v (resolve-var ctx @{} (first the-form))] - (if (and v (var-macro? v)) - (apply (var-get v) (tuple/slice the-form 1)) - the-form)) - the-form))) + (var cur the-form) + (var nxt (expand-1 cur)) + (while (not= cur nxt) (set cur nxt) (set nxt (expand-1 cur))) + cur)) + # alias/ns-unalias: alias bookkeeping is currently split (require :as writes + # the string-keyed :imports table that resolution reads; :aliases is the + # introspection table ns-aliases reads) — write/remove BOTH until unified. + (ns-intern core "alias" + (fn [alias-sym ns-sym] + (def cur (ctx-find-ns ctx (ctx-current-ns ctx))) + (ns-import cur (alias-sym :name) (ns-sym :name)) + (ns-add-alias cur alias-sym (ctx-find-ns ctx (ns-sym :name))) + nil)) + (ns-intern core "ns-unalias" + (fn [ns-d alias-sym] + (def ns (ns-or-current ns-d)) + (put (ns :imports) (alias-sym :name) nil) + (put (ns :aliases) alias-sym nil) + nil)) + # ns-publics: {symbol -> var} (jolt has no private vars, so publics = interns). + # Keys are symbol structs (value-hashed), matching Clojure's symbol keys. + (ns-intern core "ns-publics" + (fn [&opt ns-d] + (def ns (ns-or-current ns-d)) + (var m (make-phm)) + (loop [[nm v] :pairs (ns :mappings)] + (set m (phm-assoc m {:jolt/type :symbol :ns nil :name nm} v))) + m)) core) # Dispatch a special form by its string name. diff --git a/test/integration/clojure-test-suite-test.janet b/test/integration/clojure-test-suite-test.janet index 5891f1f..3ab4e20 100644 --- a/test/integration/clojure-test-suite-test.janet +++ b/test/integration/clojure-test-suite-test.janet @@ -43,9 +43,9 @@ # Raised 4004 -> 4034 / clean 66 -> 67 porting partition-all + repeatedly to the # overlay, which required fixing two leniencies (a char is not callable; take # validates its count) — correct beyond those fns, so the suite rose broadly. -(def baseline-pass 4081) +(def baseline-pass 4324) # A file is "clean" when it ran with zero failures AND zero errors. -(def baseline-clean-files 72) +(def baseline-clean-files 78) # Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so # this normally only fires on genuinely-infinite-sequence hangs. It's an env var # (JOLT_SUITE_TIMEOUT) so CI — whose runners are slower than a dev machine — can diff --git a/test/integration/conformance-test.janet b/test/integration/conformance-test.janet index c573795..be3b345 100644 --- a/test/integration/conformance-test.janet +++ b/test/integration/conformance-test.janet @@ -177,6 +177,22 @@ ["parse-uuid case =" "true" "(= (parse-uuid \"b6883c0a-0342-4007-9966-bc2dfa6b109e\") (parse-uuid \"B6883C0A-0342-4007-9966-BC2DFA6B109E\"))"] ["parse-uuid bad nil" "nil" "(parse-uuid \"df0993\")"] ["uuid as map key" ":v" "(get {(parse-uuid \"b6883c0a-0342-4007-9966-bc2dfa6b109e\") :v} (parse-uuid \"b6883c0a-0342-4007-9966-bc2dfa6b109e\"))"] + + ### ---- 1.11 additions + ns fns (spec 35-var batch A) ---- + ["parse-long" "42" "(parse-long \"42\")"] + ["parse-long bad" "nil" "(parse-long \"4.2\")"] + ["parse-double" "1500.0" "(parse-double \"1.5e3\")"] + ["parse-boolean" "true" "(parse-boolean \"true\")"] + ["update-keys" "{\"a\" 1}" "(update-keys {:a 1} name)"] + ["update-vals" "{:a 2}" "(update-vals {:a 1} inc)"] + ["partitionv pad" "[[1 2] [3 :p]]" "(partitionv 2 2 [:p] [1 2 3])"] + ["partition pad" "[[0 1 2 3] [4 5 6 7] [8 9 :a]]" "(partition 4 4 [:a] (range 10))"] + ["splitv-at" "[[1 2] [3 4]]" "(splitv-at 2 [1 2 3 4])"] + ["with-redefs" "[42 1]" "(do (defn cwr [] 1) [(with-redefs [cwr (fn [] 42)] (cwr)) (cwr)])"] + ["time returns value" "3" "(time (+ 1 2))"] + ["macroexpand" "true" "(= (quote if) (first (macroexpand (quote (when-not false 1)))))"] + ["require bare symbol" "\"a,b\"" "(do (require (quote clojure.string)) (clojure.string/join \",\" [\"a\" \"b\"]))"] + ["ns-publics lookup" "true" "(do (def cnp 7) (some? (get (ns-publics (quote user)) (quote cnp))))"] ["macroexpand-1 when" "2" "(count (rest (macroexpand-1 (quote (when true 1)))))"] ### ---- HIGH: aliased namespace calls ---- diff --git a/test/spec/macros-spec.janet b/test/spec/macros-spec.janet index e21f5c0..e8d9c60 100644 --- a/test/spec/macros-spec.janet +++ b/test/spec/macros-spec.janet @@ -72,3 +72,16 @@ ["binding rebinds" "99" "(do (def ^:dynamic *bx* 10) (binding [*bx* 99] *bx*))"] ["binding restores" "10" "(do (def ^:dynamic *by* 10) (binding [*by* 99] *by*) *by*)"] ["binding seen by fn" "7" "(do (def ^:dynamic *bz* 0) (defn rdz [] *bz*) (binding [*bz* 7] (rdz)))"]) + +# time returns the body value (the timing line goes to *out*); with-redefs +# temporarily rebinds var roots and restores on exit (even on throw); +# macroexpand expands repeatedly until the head is no longer a macro. +(defspec "macros / time, with-redefs, macroexpand" + ["time returns value" "3" "(time (+ 1 2))"] + ["with-redefs rebinds" "42" "(do (defn wr-f [] 1) (with-redefs [wr-f (fn [] 42)] (wr-f)))"] + ["with-redefs restores" "1" "(do (defn wr-g [] 1) (with-redefs [wr-g (fn [] 42)]) (wr-g))"] + ["with-redefs restores on throw" "1" + "(do (defn wr-h [] 1) (try (with-redefs [wr-h (fn [] 42)] (throw (ex-info \"x\" {}))) (catch :default e nil)) (wr-h))"] + ["with-redefs-fn" "42" "(do (defn wr-i [] 1) (with-redefs-fn {(var wr-i) (fn [] 42)} (fn [] (wr-i))))"] + ["macroexpand full" "true" "(let [e (macroexpand (quote (when-not false 1)))] (= (quote if) (first e)))"] + ["macroexpand non-macro" "[1 2]" "(macroexpand (quote [1 2]))"]) diff --git a/test/spec/maps-spec.janet b/test/spec/maps-spec.janet index afc9fd7..4b39baa 100644 --- a/test/spec/maps-spec.janet +++ b/test/spec/maps-spec.janet @@ -144,3 +144,14 @@ ["dissoc keeps nil" "true" "(contains? (dissoc {:a nil :b 1} :b) :a)"] ["reduce-kv sees nil" "true" "(= #{:a :b} (reduce-kv (fn [acc k v] (conj acc k)) #{} {:a nil :b 2}))"] ["nil-free stays fast" "true" "(= {:a 1 :b 2} {:b 2 :a 1})"]) + +# Clojure 1.11 map transformers. +(defspec "map / update-keys & update-vals (1.11)" + ["update-keys" "{\"a\" 1, \"b\" 2}" "(update-keys {:a 1 :b 2} name)"] + ["update-keys empty" "{}" "(update-keys {} inc)"] + ["update-keys nil" "{}" "(update-keys nil str)"] + ["update-keys collide last wins" "1" "(count (update-keys {:a 1 :b 2} (fn [_] :k)))"] + ["update-vals" "{:a 2, :b 3}" "(update-vals {:a 1 :b 2} inc)"] + ["update-vals empty" "{}" "(update-vals {} inc)"] + ["update-vals nil" "{}" "(update-vals nil inc)"] + ["update-vals keeps keys" "[:a :b]" "(sort (keys (update-vals {:a 1 :b 2} inc)))"]) diff --git a/test/spec/namespaces-spec.janet b/test/spec/namespaces-spec.janet index f69ebc9..adf8437 100644 --- a/test/spec/namespaces-spec.janet +++ b/test/spec/namespaces-spec.janet @@ -35,3 +35,10 @@ ["require clojure.walk" "{:a 2}" "(do (require '[clojure.walk :as w]) (w/postwalk (fn [x] (if (number? x) (inc x) x)) {:a 1}))"] ["walk keywordize-keys" "{:a 1}" "(do (require '[clojure.walk :as w]) (w/keywordize-keys {\"a\" 1}))"] ["walk stringify-keys" "true" "(do (require '[clojure.walk :as w]) (= {\"a\" 1} (w/stringify-keys {:a 1})))"]) + +(defspec "namespaces / alias, ns-unalias, ns-publics" + ["alias + use" "\"1,2\"" "(do (require (quote clojure.string)) (alias (quote st) (quote clojure.string)) (st/join \",\" [1 2]))"] + ["ns-unalias removes" "true" + "(do (require (quote clojure.string)) (alias (quote st2) (quote clojure.string)) (ns-unalias (quote user) (quote st2)) (nil? (get (ns-aliases (quote user)) (quote st2))))"] + ["ns-publics has var" "true" "(do (def npv 1) (some? (get (ns-publics (quote user)) (quote npv))))"] + ["newline returns nil" "nil" "(newline)"]) diff --git a/test/spec/numbers-spec.janet b/test/spec/numbers-spec.janet index 9ed49a7..016c0a2 100644 --- a/test/spec/numbers-spec.janet +++ b/test/spec/numbers-spec.janet @@ -134,3 +134,26 @@ ["rand n in [0,n)" "true" "(let [r (rand 10)] (and (>= r 0) (< r 10)))"] ["rand-nth member" "true" "(contains? #{:a :b :c} (rand-nth [:a :b :c]))"] ["rand-nth single" ":x" "(rand-nth [:x])"]) + +# Clojure 1.11 string->scalar parsers: nil on malformed, throw on non-string. +(defspec "numbers / parse fns (1.11)" + ["parse-long" "42" "(parse-long \"42\")"] + ["parse-long negative" "-7" "(parse-long \"-7\")"] + ["parse-long plus" "7" "(parse-long \"+7\")"] + ["parse-long float nil" "nil" "(parse-long \"1.5\")"] + ["parse-long hex nil" "nil" "(parse-long \"0x10\")"] + ["parse-long empty nil" "nil" "(parse-long \"\")"] + ["parse-long junk nil" "nil" "(parse-long \"12ab\")"] + ["parse-long throws" :throws "(parse-long 42)"] + ["parse-double" "1.5" "(parse-double \"1.5\")"] + ["parse-double int" "4.0" "(parse-double \"4\")"] + ["parse-double sci" "1500.0" "(parse-double \"1.5e3\")"] + ["parse-double neg" "-0.5" "(parse-double \"-0.5\")"] + ["parse-double junk" "nil" "(parse-double \"abc\")"] + ["parse-double trail" "nil" "(parse-double \"1.5x\")"] + ["parse-double throws" :throws "(parse-double :k)"] + ["parse-boolean true" "true" "(parse-boolean \"true\")"] + ["parse-boolean false" "false" "(parse-boolean \"false\")"] + ["parse-boolean case" "nil" "(parse-boolean \"True\")"] + ["parse-boolean junk" "nil" "(parse-boolean \"yes\")"] + ["parse-boolean throws" :throws "(parse-boolean true)"]) diff --git a/test/spec/sequences-spec.janet b/test/spec/sequences-spec.janet index c016906..4d136fa 100644 --- a/test/spec/sequences-spec.janet +++ b/test/spec/sequences-spec.janet @@ -261,3 +261,15 @@ ["dedupe consecutive" "[1 2 3 1]" "(dedupe [1 1 2 2 3 1 1])"] ["dedupe empty" "[]" "(dedupe [])"] ["dedupe no dups" "[1 2 3]" "(dedupe [1 2 3])"]) + +# Clojure 1.11 vector-returning partition/split variants. +(defspec "seq / partitionv & splitv-at (1.11)" + ["partitionv" "[[1 2] [3 4]]" "(partitionv 2 [1 2 3 4 5])"] + ["partitionv elems are vectors" "true" "(every? vector? (partitionv 2 [1 2 3 4]))"] + ["partitionv step" "[[1 2] [3 4]]" "(partitionv 2 2 [1 2 3 4 5])"] + ["partitionv pad" "[[1 2] [3 :p]]" "(partitionv 2 2 [:p] [1 2 3])"] + ["partitionv-all" "[[1 2] [3]]" "(partitionv-all 2 [1 2 3])"] + ["partitionv-all vectors" "true" "(every? vector? (partitionv-all 2 [1 2 3]))"] + ["splitv-at" "[[1 2] [3 4]]" "(splitv-at 2 [1 2 3 4])"] + ["splitv-at first is vector" "true" "(vector? (first (splitv-at 2 [1 2 3])))"] + ["splitv-at past end" "[[1 2] []]" "(splitv-at 5 [1 2])"])