Comprehensive spec (#19)
* core: fix jolt-265 — syntax-quote fully-qualifies core syms to clojure.core/ Re-attempt of the deferred gap, now unblocked. The earlier uberscript break wasn't qualification itself but an aliased-ref resolution bug it exposed: resolve-var looked up ns-aliases (e.g. g/foo) against the analyzer's REBOUND ctx-current-ns (jolt.analyzer, since the analyzer runs interpreted in its own ns) instead of the namespace being compiled. A user's aliased refs failed mid-compile (g/hello -> nil in the bundled standalone). - resolve-var resolves aliases against :compile-ns when set (same ns h-current-ns uses), falling back to ctx-current-ns — correct for both modes. - sq-symbol qualifies a resolved clojure.core name to clojure.core/<name> (Clojure hygiene); special forms stay bare; unresolved syms -> current ns. Tests updated to the qualified behavior (features-test, reader-forms-spec). * test: comprehensive spec — regex + sorted colls + random/predicate gaps Filling the biggest untested clojure.core areas found in a coverage audit (168 of 506 provided fns had no spec). New + expanded suites: - regex-spec.janet (20): #"…" literals, regex?, re-find/re-matches/re-seq (match/no-match/groups), re-pattern, and clojure.string split/replace with regex (incl $1 backrefs). Whole area was previously unspecced. - sorted-spec.janet (14): sorted-map/sorted-set construction + ordering, sorted?, subseq/rsubseq. Pins the working subset — get/conj/assoc/keys/vals on sorted colls and the by-comparator ctors are not yet first-class (jolt-ti9). - predicates-spec +14: seqable?, integer?, reduced?/unreduced, not-empty. - numbers-spec +5: rand/rand-int/rand-nth invariants. Fix: sorted? was bound to core-sorted-map? so it returned false for sorted-sets; now true for both sorted maps and sets (core-sorted?). Filed: jolt-ti9 (sorted collections incomplete: get/conj/assoc/keys/vals don't operate on the sorted wrapper; sorted-*-by ignore the comparator). Gate green incl full jpm build + jpm test. * core: close surfaced gaps — first-class sorted colls, with-out-str, rand arity, deref reduced jolt-ti9: sorted-map/sorted-set are now first-class across the collection fns — get/assoc/dissoc/conj/contains?/keys/vals/disj and call-as-fn all operate on the wrapper and preserve sort order. The by-comparator constructors (sorted-map-by/ sorted-set-by) now thread the user comparator (numeric or boolean-predicate) through all derived colls. Sorted predicates/ctors/ops moved above core-conj so the collection fns can branch on them; jolt-invoke (interpreter) gets inline branches. jolt-rfw: add with-out-str (binds output to a string buffer) + the macro. jolt-ek3: (rand n) arity and deref-of-reduced (uuid? still deferred). Specs: new io-spec.janet; sorted-spec expanded to pin the now-working map/set ops and by-comparator ordering; predicate/number spec restorations. * remove old doc * core: fix stale comment — by-comparator sorted ctors are implemented --------- Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
afb7d17352
commit
3e9fe8d0fb
9 changed files with 254 additions and 462 deletions
|
|
@ -12,6 +12,11 @@
|
||||||
|
|
||||||
(defmacro comment [& body] nil)
|
(defmacro comment [& body] nil)
|
||||||
|
|
||||||
|
;; with-out-str: capture everything the body prints to *out* and return it as a
|
||||||
|
;; string. __with-out-str (clojure.core) runs the thunk with the output captured.
|
||||||
|
(defmacro with-out-str [& body]
|
||||||
|
`(__with-out-str (fn* [] ~@body)))
|
||||||
|
|
||||||
;; defmulti/defmethod are sugar over defmulti-setup/defmethod-setup (ctx-capturing
|
;; defmulti/defmethod are sugar over defmulti-setup/defmethod-setup (ctx-capturing
|
||||||
;; clojure.core fns) so they compile as plain invokes. name/mm are passed quoted;
|
;; clojure.core fns) so they compile as plain invokes. name/mm are passed quoted;
|
||||||
;; the dispatch fn, options, and dispatch value evaluate normally, and the method
|
;; the dispatch fn, options, and dispatch value evaluate normally, and the method
|
||||||
|
|
|
||||||
439
phase-5.md
439
phase-5.md
|
|
@ -1,439 +0,0 @@
|
||||||
# Phase 5 — True Laziness (jolt-c09)
|
|
||||||
|
|
||||||
Final phase of the `jolt-1j0` clojure.core migration epic. Make jolt's sequence
|
|
||||||
generators and transformers genuinely lazy, so infinite seqs and lazy
|
|
||||||
compositions work and stop hanging the evaluator. This is the deepest and
|
|
||||||
riskiest phase — sub-stage it and gate every step.
|
|
||||||
|
|
||||||
> Issue: `bd show jolt-c09`. Depends on Phase 4 (`jolt-ldf`, done). Blocks nothing
|
|
||||||
> — it's the last phase.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Current state (what already works, what doesn't)
|
|
||||||
|
|
||||||
**The LazySeq machinery exists and is sound.** (`src/jolt/phm.janet`)
|
|
||||||
- A LazySeq is `@{:jolt/type :jolt/lazy-seq :fn thunk :realized false :val nil}`.
|
|
||||||
- A thunk returns `nil` (empty) or a cons cell `[first-val rest-thunk]`.
|
|
||||||
- `realize-ls` forces one cell (memoized via `:realized`), with a `:jolt/pending`
|
|
||||||
sentinel that makes **self-referential** seqs work (`(def ones (lazy-seq (cons 1 ones)))`).
|
|
||||||
- `ls-first` / `ls-rest` / `ls-seq` / `ls-count` walk it. `lazy-seq?` detects it.
|
|
||||||
|
|
||||||
**Already lazy (keep):**
|
|
||||||
- Infinite generators: `(range)`, `(repeat x)`, `(iterate f x)`, `(cycle ...)`,
|
|
||||||
`repeatedly` return LazySeq. Bounded forms (`(range n)`, `(repeat n x)`) are
|
|
||||||
eager tuples/arrays — correct, they're finite.
|
|
||||||
- `map`/`filter` are **hybrid**: lazy when the input is a LazySeq, eager (and
|
|
||||||
representation-preserving) when the input is a concrete collection.
|
|
||||||
- `take`/`drop`/`take-while` pull lazily from a LazySeq input but **return an eager
|
|
||||||
array** (fine for bounded `take`, wrong for the others on infinite tails).
|
|
||||||
- Conformance already covers the working cases (self-ref fib, `iterate`, `count`
|
|
||||||
of `take`, `filter`/`take-while`/`remove` over `(range)`): see
|
|
||||||
`test/integration/conformance-test.janet` lines ~21–143.
|
|
||||||
|
|
||||||
**The gaps (what hangs):**
|
|
||||||
1. **Eager transformers that force their input** even when it's infinite. Confirmed
|
|
||||||
callers of `realize-for-iteration` in their bodies: `remove`, `interpose`,
|
|
||||||
`distinct`, `take-nth`, `map-indexed`, `keep-indexed`, `partition-all`,
|
|
||||||
`partition-by`, `drop-while`. Plus `partition`, `interleave`, `concat`,
|
|
||||||
`dedupe`, `flatten`, `tree-seq`, `mapcat`, `keep`, `sequence` need an
|
|
||||||
infinite-input audit.
|
|
||||||
2. **`map`/`filter` over a *concrete vector* return an eager array**, not a lazy
|
|
||||||
seq. Clojure returns a lazy seq. This is a **representation decision** (§3 Step 6).
|
|
||||||
3. **`realize-for-iteration` is the universal forcing point** (57 call sites). Many
|
|
||||||
are legitimate realization boundaries (`count`, `into`, `reduce`, `vec`, `pr`),
|
|
||||||
but any transformer that calls it on a lazy input loses laziness.
|
|
||||||
4. **Evaluator eager assumptions** — the interpreter/compiler may realize seqs in
|
|
||||||
places (apply arg spreading, `doseq`, destructuring a seq). Audit needed.
|
|
||||||
5. **CPU-bound hangs are uninterruptible.** An infinite realization is a tight
|
|
||||||
Janet loop with no yield points, so `ev/with-deadline` cannot truncate it
|
|
||||||
in-process — it pins the core. This is why the suite runs each file in a
|
|
||||||
**subprocess** (`os/spawn` + 6 s `ev/with-deadline`, then `os/proc-kill`). Phase
|
|
||||||
5 testing must do the same (see §7).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Design principles (the cardinal rules)
|
|
||||||
|
|
||||||
1. **A transformer never forces its input.** It returns a LazySeq whose thunk pulls
|
|
||||||
one element at a time via `core-first`/`core-rest`/`seq-done?`. No
|
|
||||||
`realize-for-iteration` inside a transformer.
|
|
||||||
2. **Force only at realization boundaries.** Exactly the operations that *must* see
|
|
||||||
all elements: `pr`/`print`/`str` rendering, `=`, `count`, `reduce`, `into`,
|
|
||||||
`vec`/`seq`/`doall`, `doseq`, `nth`/`last` (these pull only as far as needed),
|
|
||||||
`apply` (spreads finitely). These are allowed to loop; on a genuinely infinite
|
|
||||||
seq they hang — matching Clojure.
|
|
||||||
3. **One-element-at-a-time, memoized.** Reuse `make-lazy-seq`/`realize-ls`; never
|
|
||||||
re-walk. `realize-ls`'s `:jolt/pending` guard preserves self-reference.
|
|
||||||
4. **Stack safety.** A chain of N lazy wrappers must not consume N stack frames per
|
|
||||||
element. Realize iteratively (a `while` over `realize-ls`), not by deep
|
|
||||||
recursion through `ls-rest`. Watch `concat`/`mapcat`/`lazy-cat` especially.
|
|
||||||
5. **Multi-arity stays correct.** `map`/`mapcat` over multiple colls advance each
|
|
||||||
input one step per output element and stop at the shortest.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Step-by-step implementation
|
|
||||||
|
|
||||||
Order matters: build the helper layer, then convert transformers leaf-first, then
|
|
||||||
fix boundaries, then the evaluator. Gate (§6) after **every** numbered step.
|
|
||||||
|
|
||||||
### Step 0 — Safety net ✓ (commit e2e189a)
|
|
||||||
- Record the baseline: conformance 229×3, clojure-test-suite `baseline-pass=3926`,
|
|
||||||
fixpoint stage1==2==3, self-host, all specs+unit, `lazy-seqs-spec` /
|
|
||||||
`sequences-spec` / `transducers-spec` green. ✓
|
|
||||||
- Build the **infinite-seq harness** first (see §6.2, "Deadlined infinite-seq
|
|
||||||
spec") so every subsequent step is verified against hangs, not just values. ✓
|
|
||||||
→ `test/support/lazy-eval.janet` (subprocess worker) +
|
|
||||||
`test/integration/lazy-infinite-test.janet` (os/spawn + 5s deadline)
|
|
||||||
- Snapshot which clojure-test-suite files currently time out (the ~9). Save the
|
|
||||||
list — it's the acceptance target. ⚠ 9 files recorded but not yet re-verified post-conversion.
|
|
||||||
|
|
||||||
### Step 1 — Lazy combinator layer ✓ (commit e2e189a)
|
|
||||||
Add a small set of internal lazy builders so transformers compose uniformly,
|
|
||||||
rather than each re-implementing the thunk dance:
|
|
||||||
- `lazy-cons val thunk` → a LazySeq cell of `val` + a deferred rest. ✓
|
|
||||||
→ `src/jolt/phm.janet` line 208; registered in core-bindings as `"lazy-cons"`.
|
|
||||||
- `lazy-from coll` → coerce any seqable to a uniform lazy view *without forcing*
|
|
||||||
(vector/list/set/map/string/LazySeq → a LazySeq that pulls element by element).
|
|
||||||
This is the lazy analogue of `realize-for-iteration` and the key primitive: every
|
|
||||||
transformer takes `(lazy-from input)` and walks it with `core-first`/`core-rest`. ✓
|
|
||||||
→ `src/jolt/core.janet` line 1112; registered in core-bindings as `"lazy-from"`.
|
|
||||||
- `seq-done?` already exists — confirm it short-circuits without forcing the tail. ✓
|
|
||||||
- Decide placement: the lazy machinery is host-coupled (Janet thunks) so it stays
|
|
||||||
in `phm.janet`/`core.janet`; transformers that are already in the overlay tiers
|
|
||||||
call these as primitives. ✓
|
|
||||||
|
|
||||||
### Step 2 — Convert the core transformers (leaf-first) ✓ (commits e2e189a, d16e1f4, 97781b3, ff8ffb8)
|
|
||||||
Make each return a LazySeq over `lazy-from input`. Do them in dependency order, one
|
|
||||||
small batch per commit, each gated:
|
|
||||||
- **2a. Single-input maps/filters:** `map` (1-coll) ✓ (already lazy), `filter` ✓ (already lazy),
|
|
||||||
`remove` ✓ (delegates to filter), `keep` ✓, `map-indexed` ✓, `keep-indexed` ✓,
|
|
||||||
`take-while` ✓ (already lazy), `drop-while` ✓, `take-nth` ✓.
|
|
||||||
- **2b. Structural:** `cons` ✓ (already O(1) lazy cell), `rest`/`next` over lazy ✓,
|
|
||||||
`concat` ✓ + zero-arg returns @[], `lazy-cat` ✓ (verify), `mapcat` ✓ (standard
|
|
||||||
`(apply concat (apply map f colls))` + transducer arity. Lazy step-based overlay
|
|
||||||
attempted but **reverted** — compile-mode splice errors when used by defrecord's
|
|
||||||
`~@` syntax-quote. Needs Step 4 apply fix or defrecord rewrite),
|
|
||||||
`cycle` ✓ (already lazy), `interleave` ✓ (lazy multi-arity in overlay),
|
|
||||||
`interpose` ✓.
|
|
||||||
- **2c. Windowing:** `partition` ✓, `partition-all` ✓, `partition-by` ⚠ (still eager),
|
|
||||||
`dedupe` ⚠ (still eager in overlay), `distinct` ✓, `take`/`drop` ⚠ (return
|
|
||||||
eager array, not LazySeq — representation decision, §3 Step 6).
|
|
||||||
- **2d. Multi-input `map`/`mapcat`** over several colls (shortest-stops). ✓
|
|
||||||
→ 9 new tests added to `sequences-spec.janet`, verified against Clojure & CLJS
|
|
||||||
reference implementations. Multi-input `map` already correct; `mapcat` uses
|
|
||||||
the standard overlay impl. No code changes needed.
|
|
||||||
- **2e. Tree/seq:** `tree-seq` ⚠ (kept eager; lazy via mapcat triggers compile-mode
|
|
||||||
splice errors — documented with lazy version in comments), `flatten` ✓ (already
|
|
||||||
correct in overlay), `xml-seq` ✓ (added to overlay, matches Clojure),
|
|
||||||
`line-seq` ✓ (Janet stub — Java-specific API), `sequence` ✓ (Janet stub),
|
|
||||||
`iterator-seq` ✓ (Janet stub — Java-specific API),
|
|
||||||
`enumeration-seq` ✓ (Janet stub — Java-specific API).
|
|
||||||
- For each: a transducer arity may exist (`td-*`) — leave it; only the
|
|
||||||
collection arity changes. ✓
|
|
||||||
|
|
||||||
### Step 3 — Realization boundaries ✔ audit complete (documented in phase-5.md)
|
|
||||||
|
|
||||||
Audit of 56 `realize-for-iteration` call sites in `src/jolt/core.janet` (excludes the definition at line 96). Each site classified below.
|
|
||||||
|
|
||||||
#### Boundary (must force — correct)
|
|
||||||
These functions require seeing all elements by contract.
|
|
||||||
|
|
||||||
| Function | Line(s) | Why |
|
|
||||||
|---|---|---|
|
|
||||||
| `core-sqcat` | 136 | syntax-quote `~@` splicing — must flatten all parts |
|
|
||||||
| `core-sqvec` | 141 | syntax-quote `[~@...]` — must flatten all parts |
|
|
||||||
| `core-every?` | 205 | short-circuits on falsy but must iterate |
|
|
||||||
| `eq-seqable` (part of `=`) | 258 | equality of lazy-seqs: must realize to compare elements |
|
|
||||||
| `core-apply` | 506 | arg spread — forces final collection, matching Clojure |
|
|
||||||
| `core-cons` | 626 | only reached for concrete non-lazy input; lazy already cell-based |
|
|
||||||
| `core-vec` | 650 | builds a vector — must see all elements |
|
|
||||||
| `core-select-keys` | 736 | filters keys from a collection |
|
|
||||||
| `core-zipmap` | 742×2 | needs both key and value collections fully |
|
|
||||||
| `reduce-with-reduced` | 821 | reduce must see all elements (set guard: concrete collections only) |
|
|
||||||
| `core-into` | 847 | consumes entire collection into target |
|
|
||||||
| `core-reduce` (3-arg) | 974 | must see all elements (set guard) |
|
|
||||||
| `core-nth` (concrete) | 1199 | finite pull: must walk to index |
|
|
||||||
| `core-take` (concrete) | 994 | finite prefix pull; could be element-at-a-time, but bounded |
|
|
||||||
| `core-reverse` (concrete) | 1164 | reorder: must see all elements |
|
|
||||||
| `core-sort` | 1212 | sorting: must see all elements |
|
|
||||||
| `core-sort-by` | 1225 | sorting: must see all elements |
|
|
||||||
| `core-set` | 1543 | builds a set — must see all elements |
|
|
||||||
| `core-str-join` | 1670 | rendering: must see all elements |
|
|
||||||
| `pr-render-seq` (in `str-render-one`) | 1626 | rendering lazy-seqs to strings |
|
|
||||||
| `core-shuffle` | 2395 | reorder: must see all elements |
|
|
||||||
| `core-doall` | 2540 | intentional realization — that's its purpose |
|
|
||||||
| `core-dorun` | 2543 | intentional realization — that's its purpose |
|
|
||||||
| `core-rand-nth` | 2558 | O(1) index into realized array |
|
|
||||||
| `core-list*` | 2584 | splices final arg into preceding elements |
|
|
||||||
| `core-transient` | 2631 | builds mutable copy from collection entries |
|
|
||||||
| `core-hash-ordered-coll` | 2738 | hash computation: must see all elements |
|
|
||||||
| `core-hash-unordered-coll` | 2740 | hash computation: must see all elements |
|
|
||||||
| `core-chunk-cons` | 1841 | chunk helper — realizes chunk to concat |
|
|
||||||
| `core-cat` | 1849 | transducer — must eat entire input element |
|
|
||||||
| `core-mapcat` (transducer) | 1134 | transducer arity — internal to reducing fn |
|
|
||||||
|
|
||||||
#### Conditional boundary (forces for concrete, lazy handled separately)
|
|
||||||
These have a `(if (lazy-seq? coll) ...)` guard. The `realize-for-iteration` is only reached for concrete collections. Correct pattern.
|
|
||||||
|
|
||||||
| Function | Line(s) | What happens for lazy input |
|
|
||||||
|---|---|---|
|
|
||||||
| `core-filter` | 951 | lazy branch: `fstep` walks lazily via `ls-first`/`ls-rest` |
|
|
||||||
| `core-take-while` | 1037 | lazy branch: walks until pred fails |
|
|
||||||
| `core-distinct` | 1254 | lazy branch: `dstep` yields one unique at a time |
|
|
||||||
| `core-keep` | 2366 | lazy branch: `kstep` skips nils one element at a time |
|
|
||||||
| `core-keep-indexed` | 1351 | lazy branch: `kstep` with index tracking |
|
|
||||||
| `core-map-indexed` | 1366 | lazy branch: `mstep` pairs idx+val lazily |
|
|
||||||
| `core-take-nth` | 2314 | lazy branch: `tstep` skips N elements at a time |
|
|
||||||
| `core-interpose` | 2340 | lazy branch: `istep` alternates sep + element |
|
|
||||||
| `core-partition-all` | 1324 | lazy branch: `pstep` pulls N elements at a time |
|
|
||||||
| `core-partition` | 1285 | lazy branch: `pstep` with optional step parameter |
|
|
||||||
| `core-drop` | 1013 | lazy branch: walks past N elements lazily |
|
|
||||||
| `core-drop-while` | 1053 | lazy branch: `dwstep` skips past pred-matched elements |
|
|
||||||
| `core-map` (single) | 880 | lazy branch: `mstep` maps one element at a time |
|
|
||||||
|
|
||||||
#### Transformer leak (needs work — still forces)
|
|
||||||
These functions call `realize-for-iteration` unconditionally on their input, breaking laziness. Each has a target Step for resolution.
|
|
||||||
|
|
||||||
| Function | Line(s) | Severity | Target Step |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `core-mapcat` (collection) | 1141 | HIGH | Step 4 — `apply` fix needed to avoid forcing `core-map` result. Currently `(apply concat ...)` forces via `realize-for-iteration`. Lazy overlay exists in `10-seq.clj` but reverted (compile-mode splice errors). |
|
|
||||||
| `core-cycle` | 1372 | MED | Must snapshot input to cycle — would need a lazy cycling buffer. Low priority (cycle of finite coll). |
|
|
||||||
| `core-partition-by` | 1299 | MED | Has no lazy branch yet. Needs Step 2c completion. |
|
|
||||||
| `core-xml-seq` (Janet) | 2464 | LOW | **Overridden** by Clojure overlay `xml-seq` in `20-coll.clj` (uses `tree-seq`). The Janet stub remains for direct Janet-level callers but is rarely hit. Counted in Internal helpers below. |
|
|
||||||
|
|
||||||
#### Interop helpers (context-dependent, keep)
|
|
||||||
Array/byte conversion helpers that naturally force input.
|
|
||||||
|
|
||||||
| Function | Line(s) | Why |
|
|
||||||
|---|---|---|
|
|
||||||
| `make-num-array` | 1769 | (T-array seq) — realizes seq to build native array |
|
|
||||||
| `core-bytes` | 1784 | byte conversion — forces to encode bytes |
|
|
||||||
| `core-into-array` | 1802 | realizes seq to build Java array |
|
|
||||||
| `core-to-array` | 1805 | realizes seq to mutable array |
|
|
||||||
| `core-to-array-2d` | 1807 | realizes 2-level seq to 2d array |
|
|
||||||
|
|
||||||
#### Internal helpers (keep, context-dependent)
|
|
||||||
| Function | Line(s) | Why |
|
|
||||||
|---|---|---|
|
|
||||||
| `core-map` multi-coll init | 894 | Pre-realizes concrete colls only; lazy colls go through step fn |
|
|
||||||
| `core-map` multi-coll step | 919 | On-demand lazy pull: realizes concrete coll only when cursor exhausted |
|
|
||||||
| `sorted-entries` | 2515 | Helper for `subseq`/`rsubseq`; forces sorted-coll items |
|
|
||||||
| `core-xml-seq` (Janet, walk) | 2464 | Interim Janet impl — overridden by Clojure overlay xml-seq in 20-coll.clj |
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
| Category | Count |
|
|
||||||
|---|---|
|
|
||||||
| Boundary (correct) | 31 |
|
|
||||||
| Conditional boundary (lazy branch exists) | 13 |
|
|
||||||
| Transformer leak (needs work) | 3 |
|
|
||||||
| Interop helper (keep) | 5 |
|
|
||||||
| Internal helper (keep) | 4 |
|
|
||||||
| **Total verified** | **56** |
|
|
||||||
| **Leaks remaining** | **3 (mapcat, cycle, partition-by)** |
|
|
||||||
|
|
||||||
Of the 3 leaks:
|
|
||||||
- `mapcat` is the **critical remaining leak** — blocked on Step 4 `apply` fix.
|
|
||||||
- `partition-by` and `cycle` are low-to-medium priority.
|
|
||||||
- `xml-seq` Janet is **overridden** by the Clojure overlay — effectively resolved; counted in Internal helpers.
|
|
||||||
|
|
||||||
### Step 4 — Evaluator / compiler eager assumptions
|
|
||||||
Grep the interpreter (`src/jolt/evaluator.janet`) and back end
|
|
||||||
(`src/jolt/backend.janet`, `compiler.janet`) for places that realize seqs:
|
|
||||||
- `apply` / variadic arg spreading — must finitely spread, not realize an infinite
|
|
||||||
tail beyond the call.
|
|
||||||
- `&`-rest binding in `fn*`/`let*`/`loop*` and `destructure` — a rest param over a
|
|
||||||
lazy seq should stay lazy, not eagerly slurp.
|
|
||||||
- `doseq`/`for` desugaring (they go through `count`/`mapcat` — verify the `for`
|
|
||||||
comprehension stays lazy where Clojure's is).
|
|
||||||
- Any `(each x (realize ...))` in hot paths that assumes finiteness.
|
|
||||||
|
|
||||||
### Step 5 — Laziness-coupled stragglers (the deferred Phase-5 list)
|
|
||||||
From `jolt-c09` notes / MIGRATION.md: `sequence`, `sequential?`, `seqable?`,
|
|
||||||
`realized?`, `line-seq`, `rand-int`, `random-uuid`, `trampoline`, `unreduced`,
|
|
||||||
`ensure-reduced`, the transducer machinery (`cat`, `eduction`, `transduce`,
|
|
||||||
`sequence`, `halt-when`, `dedupe`/`interpose`/`keep` transducer arities). Move the
|
|
||||||
now-lazy ones to the overlay where feasible (Phase-4 style), keeping the
|
|
||||||
`Reduced`/thunk kernels native.
|
|
||||||
|
|
||||||
### Step 6 — Representation decision ✅ Decided: Option A (full Clojure laziness)
|
|
||||||
|
|
||||||
**Decision: Option A.** Lazy transformers always return a LazySeq, even over a
|
|
||||||
concrete vector — matching Clojure: `(seq? (map inc [1 2 3]))` is **true**,
|
|
||||||
`(vector? (map inc [1 2 3]))` is **false**.
|
|
||||||
|
|
||||||
History: an earlier Option A attempt (commit a11535c, reverted) crashed
|
|
||||||
(0/21 lazy-infinite, conformance crash) because flipping `map` to always-lazy
|
|
||||||
without the supporting boundary fixes broke the `lazy-from → seq-done? →
|
|
||||||
ls-first` chain. That measurement led to Option B (hybrid) and Phase 5 being
|
|
||||||
declared complete.
|
|
||||||
|
|
||||||
Option A was then re-attempted **with the boundary fixes the first attempt
|
|
||||||
lacked**, and it works — all gates green (conformance 246×3, lazy-infinite
|
|
||||||
40/40). The fixes that made it viable:
|
|
||||||
- `cons` over a lazy-seq returns a LazySeq, not a raw `@[val thunk]` cell (a
|
|
||||||
cons-of-a-cons no longer leaks the rest-thunk as a list element).
|
|
||||||
- `coll->cells` disambiguates cons cells (mutable arrays) from user vectors
|
|
||||||
whose 2nd element is a function (`[first last]`), and coerces set/map/string/
|
|
||||||
buffer via `core-seq`.
|
|
||||||
- `core-nth`/`core-next`/`core-rest` walk lazy seqs via `seq-done?` (not element
|
|
||||||
truthiness or `length` on the lazy table), so a `false`/`nil` element isn't
|
|
||||||
mistaken for end-of-seq and `rest` never returns nil.
|
|
||||||
- `~@` splice (interpreter `syntax-quote*`) and the test helper `normalize-pvecs`
|
|
||||||
realize lazy-seqs.
|
|
||||||
- Transformers always route concrete input through `lazy-from` + the lazy step
|
|
||||||
machinery (dropping the eager `(if (jvec? coll) (make-vec …))` branch).
|
|
||||||
|
|
||||||
All transformers are lazy in interpret/compile/self-host, using canonical
|
|
||||||
recursive Clojure forms. (jolt-r81 — a compile-mode leak where lazy overlay fns
|
|
||||||
returned the `lazy-seq` macro expansion as data — was root-fixed by moving
|
|
||||||
`lazy-seq`/`lazy-cat` to the 00-syntax tier so they're registered before the
|
|
||||||
seq/coll tiers that use them compile.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3b. Implementation notes (discovered during Phase 5)
|
|
||||||
|
|
||||||
### mapcat + compile mode
|
|
||||||
A lazy step-based `mapcat` (using `cons` + `lazy-seq` + recursive `fn` in the
|
|
||||||
overlay) causes splice errors in self-hosted compilation. The `defrecord` macro
|
|
||||||
in `30-macros.clj` uses `(vec (mapcat …))` inside syntax-quote, and `~@` cannot
|
|
||||||
splice lazy-seqs. Reverted to the standard `(apply concat (apply map f colls))`
|
|
||||||
implementation. Two possible fixes for the future:
|
|
||||||
1. **Fix `apply` to spread lazy-seqs without forcing** (Step 4 proper) — the root cause.
|
|
||||||
2. **Rewrite `defrecord`'s bind-generation to avoid `mapcat`** — replace
|
|
||||||
`(vec (mapcat (fn [f] …) fields))` with an eager `loop` accumulator.
|
|
||||||
|
|
||||||
### tree-seq + compile mode
|
|
||||||
Same root cause as mapcat: lazy `tree-seq` requires `mapcat` for
|
|
||||||
`(when (branch? node) (mapcat walk (children node)))`. Kept eager; lazy version
|
|
||||||
documented in `20-coll.clj` comments. Will switch when mapcat is resolved.
|
|
||||||
|
|
||||||
### pre-existing: protocol-on-record compile-mode failure
|
|
||||||
`(defprotocol P (m [_])) (defrecord R [side] P (m [_] (* side side))) (m (->R 4))`
|
|
||||||
errors with "Unable to resolve symbol: side" in compile mode. This is a pre-existing
|
|
||||||
issue unrelated to Phase 5 changes — `register-method` stores the method body as
|
|
||||||
a raw `fn*` form, and the self-hosted compiler cannot resolve let-bound field
|
|
||||||
access symbols at definition time (bindings only exist at call time).
|
|
||||||
Conformance wraps this in `(= expected (do …))` so it's never triggered; only
|
|
||||||
direct `eval-string` with `:compile? true` hits it. Not blocking — the
|
|
||||||
self-host path (JOLT_SELFHOST=1) and interpret path both pass.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Suggested commit cadence
|
|
||||||
|
|
||||||
One transformer family (a §3 sub-step) per commit. Each commit:
|
|
||||||
1. Convert the fns (overlay or core as appropriate).
|
|
||||||
2. Add infinite-seq spec cases (§6.2) + value cases.
|
|
||||||
3. Run the full gate (§6.1). Commit only if green. Push.
|
|
||||||
|
|
||||||
Mirror the Phase 4 discipline: small, gated, reversible batches.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Risks & gotchas
|
|
||||||
|
|
||||||
- **Uninterruptible hangs:** never probe an infinite case in-process — it pins a
|
|
||||||
core and can't be killed by a deadline. Always go through the subprocess harness.
|
|
||||||
- **Self-reference:** `(def s (lazy-seq (cons 1 s)))` and `lazy-cat` fib rely on
|
|
||||||
`realize-ls`'s `:jolt/pending` guard — don't bypass `realize-ls` with a
|
|
||||||
hand-rolled force.
|
|
||||||
- **Stack overflow** from deep wrapper chains (`concat`/`mapcat`/`iterate` of
|
|
||||||
`iterate`) — realize iteratively.
|
|
||||||
- **Double realization / side effects:** a lazy `map` fn with side effects must run
|
|
||||||
**once per element, in order, only when forced** — assert with a counter (§7).
|
|
||||||
- **Performance:** LazySeq has per-element allocation + thunk-call overhead. Watch
|
|
||||||
`core-bench` (`test/bench/core-bench.janet`) — the eager fast paths exist partly
|
|
||||||
for speed. A heavy suite file slipping past the 6 s deadline = a regression
|
|
||||||
(this already bit Phase 3's macro move).
|
|
||||||
- **Compile/self-host parity:** every behavior must hold in interpret, compile, and
|
|
||||||
self-host (conformance runs all three). Lazy thunks are closures — verify the
|
|
||||||
back end compiles them.
|
|
||||||
- **`chunked` seqs are out of scope** — `chunked-seq?` stays `false`. Don't emulate
|
|
||||||
chunking; one-at-a-time is fine.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Testing strategy
|
|
||||||
|
|
||||||
### 6.1 Per-step gate (every commit) — same as Phase 4
|
|
||||||
```
|
|
||||||
janet test/integration/conformance-test.janet # 229×3 (interpret/compile/self-host)
|
|
||||||
janet test/integration/bootstrap-fixpoint-test.janet # stage1==2==3
|
|
||||||
janet test/integration/self-host-test.janet
|
|
||||||
janet test/integration/sci-bootstrap-test.janet
|
|
||||||
janet test/integration/clojure-test-suite-test.janet # >= baseline (raise as it improves)
|
|
||||||
for f in test/spec/*.janet test/unit/*.janet; do janet "$f"; done
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 Deadlined infinite-seq spec (the Phase-5-specific harness)
|
|
||||||
Build this in Step 0. Plain in-process specs **cannot** test laziness — a wrong
|
|
||||||
answer hangs instead of failing. Mirror `clojure-test-suite-test.janet`'s pattern:
|
|
||||||
- A new `test/integration/lazy-infinite-test.janet` that, for each case, spawns a
|
|
||||||
worker (`os/spawn ["janet" "test/support/lazy-eval.janet" expr]`) and waits under
|
|
||||||
`(ev/with-deadline 5 (os/proc-wait proc))`, killing on timeout.
|
|
||||||
- A timed-out or crashed case = **FAIL** (it should have produced a value).
|
|
||||||
- Cases = the compositions that currently hang. Minimum set:
|
|
||||||
```
|
|
||||||
(nth (map inc (range)) 1000) => 1001
|
|
||||||
(first (filter even? (drop 3 (range)))) => 4
|
|
||||||
(take 3 (remove odd? (range))) => (0 2 4)
|
|
||||||
(take 3 (drop-while #(< % 5) (range))) => (5 6 7)
|
|
||||||
(take 4 (interleave (range) (iterate inc 10)))
|
|
||||||
(take 3 (partition 2 (range))) => ((0 1) (2 3) (4 5))
|
|
||||||
(take 3 (partition-all 2 (range)))
|
|
||||||
(take 3 (map-indexed vector (range)))
|
|
||||||
(take 5 (distinct (cycle [1 2 1 3 1])))
|
|
||||||
(take 3 (mapcat (fn [x] [x x]) (range)))
|
|
||||||
(take 3 (take-nth 2 (range)))
|
|
||||||
(take 3 (interpose :x (range)))
|
|
||||||
(take 3 (map vector (range) (iterate inc 100)))
|
|
||||||
(second (cons :a (range)))
|
|
||||||
```
|
|
||||||
Add one row per transformer converted in Step 2.
|
|
||||||
|
|
||||||
### 6.3 Laziness assertions (side-effect counting)
|
|
||||||
For each lazy transformer, assert it realizes **only what's demanded** — values
|
|
||||||
alone don't prove laziness. Use a counter:
|
|
||||||
```clojure
|
|
||||||
(let [n (atom 0)]
|
|
||||||
(take 3 (map (fn [x] (swap! n inc) x) (range)))
|
|
||||||
@n) ; => 3 (not "hang", not 1000)
|
|
||||||
```
|
|
||||||
Add these to `test/spec/lazy-seqs-spec.janet`. They run in-process safely because
|
|
||||||
they only ever force a bounded prefix.
|
|
||||||
|
|
||||||
### 6.4 Conformance extension
|
|
||||||
Add infinite-composition rows to `conformance-test.janet` (runs ×3 modes) — the
|
|
||||||
subset of §6.2 that returns a small concrete value, e.g.
|
|
||||||
`["lazy compose" "(quote (1 3 5))" "(take 3 (filter odd? (map inc (range))))"]`.
|
|
||||||
These guard interpret/compile/self-host parity.
|
|
||||||
|
|
||||||
### 6.5 Acceptance target — the timed-out suite files
|
|
||||||
The 9 files that currently time out (snapshot in Step 0:
|
|
||||||
`cycle`/`range`/transducers-over-infinite tests) should stop timing out and start
|
|
||||||
contributing passes. Each phase-5 step should monotonically reduce the timed-out
|
|
||||||
count and **raise `baseline-pass`** in `clojure-test-suite-test.janet:35`. Final
|
|
||||||
target: 0 (or near-0) timeouts and a meaningfully higher baseline.
|
|
||||||
|
|
||||||
### 6.6 Regression guards
|
|
||||||
- `core-bench` before/after (back-to-back, load-sensitive) — no large slowdown on
|
|
||||||
the eager-collection paths.
|
|
||||||
- `lazy-seqs-spec`, `sequences-spec`, `transducers-spec` stay green every step.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Done criteria
|
|
||||||
|
|
||||||
- All §6.2 infinite-seq cases return correct values under the deadline (0 hangs). ✅ Done — 21/21
|
|
||||||
- §6.3 laziness counters prove minimal realization for every converted transformer. ✅ Done — 16 counter tests added, all pass
|
|
||||||
- Conformance 229+×3, fixpoint, self-host, sci-bootstrap all green. ✅ Done — 246/246 all three modes (Option A added cases)
|
|
||||||
- clojure-test-suite: the ~9 infinite-seq files no longer time out; `baseline-pass`
|
|
||||||
raised to the new steady-state; no per-file 6 s timeouts introduced. ✅ Done — 3971 pass
|
|
||||||
(up from 3926), 6 timeouts (down from 9), 4628 assertions.
|
|
||||||
- Representation decision (§3 Step 6, option A or B) documented and applied consistently. ✅ **Option A (full laziness)** — re-attempted with boundary fixes the first attempt lacked; all transformers lazy in 3 modes, conformance 246×3, lazy-infinite 40/40. (Earlier Option B was superseded.)
|
|
||||||
- `core-bench` within noise of the Phase-4 baseline. ✅ Captured: TOTAL 2531 ms (fib 131, seq-pipe 97, reduce 414, into-vec 218, map-build 745, map-read 6, str-join 263, hof 657)
|
|
||||||
- `bd close jolt-c09` → closes the `jolt-1j0` epic. ⚠ blocked on above
|
|
||||||
|
|
@ -269,7 +269,7 @@
|
||||||
(defn core-max [& args] (each x args (need-num x "max")) (apply max args))
|
(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))
|
(defn core-min [& args] (each x args (need-num x "min")) (apply min args))
|
||||||
|
|
||||||
(defn core-rand [] (math/random))
|
(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)))
|
(defn core-rand-int [n] (math/floor (* (math/random) n)))
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -375,12 +375,69 @@
|
||||||
(defn- map-value? [x]
|
(defn- map-value? [x]
|
||||||
(or (phm? x) (and (struct? x) (nil? (get x :jolt/type)))))
|
(or (phm? x) (and (struct? x) (nil? (get x :jolt/type)))))
|
||||||
|
|
||||||
|
# --- Sorted collections (sorted-map / sorted-set) -------------------------------
|
||||||
|
# Defined here (before the collection fns) so conj/assoc/get/contains?/keys/vals/
|
||||||
|
# disj can branch on them. A sorted-map is {:jolt/type :jolt/sorted-map :map STRUCT};
|
||||||
|
# a sorted-set is {:jolt/type :jolt/sorted-set :items SORTED-ARRAY}. Keys/elements
|
||||||
|
# are assumed Comparable scalars (the premise of a sorted coll); ops return a fresh
|
||||||
|
# wrapper (persistent — source unchanged). A wrapper may carry an optional :cmp
|
||||||
|
# (set by the by-comparator constructors) that all derived colls propagate.
|
||||||
|
(defn core-sorted-map? [x] (and (table? x) (= :jolt/sorted-map (x :jolt/type))))
|
||||||
|
(defn core-sorted-set? [x] (and (table? x) (= :jolt/sorted-set (x :jolt/type))))
|
||||||
|
(defn core-sorted? [x] (or (core-sorted-map? x) (core-sorted-set? x)))
|
||||||
|
# A sorted coll may carry a :cmp — a Janet 2-arg comparator returning a Clojure
|
||||||
|
# compare result (neg/0/pos). nil means natural order (Janet's < via sort). The
|
||||||
|
# by-comparator constructors install one (built from the user IFn); all derived
|
||||||
|
# colls (assoc/conj/...) propagate it so ordering stays consistent.
|
||||||
|
# A Clojure comparator is either a (neg/0/pos)-returning fn or a boolean predicate
|
||||||
|
# (true => a sorts before b, like <). Reduce both to a strict less-than for sort.
|
||||||
|
(defn- cmp-lt? [cmp a b]
|
||||||
|
(let [r (cmp a b)]
|
||||||
|
(if (boolean? r) r (if (number? r) (< r 0) (truthy? r)))))
|
||||||
|
(defn- sorted-by [cmp arr] (if cmp (sort arr (fn [a b] (cmp-lt? cmp a b))) (sort arr)))
|
||||||
|
(defn sm-make [m &opt cmp] @{:jolt/type :jolt/sorted-map :map m :cmp cmp})
|
||||||
|
(defn ss-make [items &opt cmp] @{:jolt/type :jolt/sorted-set :items items :cmp cmp})
|
||||||
|
(defn core-sorted-map [& kvs]
|
||||||
|
(var m @{}) (var i 0)
|
||||||
|
(while (< i (length kvs)) (put m (kvs i) (kvs (+ i 1))) (+= i 2))
|
||||||
|
(sm-make (table/to-struct m)))
|
||||||
|
(defn core-sorted-set [& xs]
|
||||||
|
(var seen @{}) (each x xs (put seen x true))
|
||||||
|
(ss-make (sorted-by nil (array ;(keys seen)))))
|
||||||
|
(defn sorted-map-keys [sm] (sorted-by (sm :cmp) (array ;(keys (sm :map)))))
|
||||||
|
(defn sorted-map-entries [sm] (let [m (sm :map)] (map (fn [k] [k (get m k)]) (sorted-map-keys sm))))
|
||||||
|
(defn sm-assoc-many [sm kvs]
|
||||||
|
(var m @{}) (each k (keys (sm :map)) (put m k (get (sm :map) k)))
|
||||||
|
(var i 0) (while (< i (length kvs)) (put m (kvs i) (kvs (+ i 1))) (+= i 2))
|
||||||
|
(sm-make (table/to-struct m) (sm :cmp)))
|
||||||
|
(defn sm-dissoc-many [sm ks]
|
||||||
|
(def rm @{}) (each x ks (put rm x true))
|
||||||
|
(var m @{}) (each k (keys (sm :map)) (unless (get rm k) (put m k (get (sm :map) k))))
|
||||||
|
(sm-make (table/to-struct m) (sm :cmp)))
|
||||||
|
(defn ss-contains? [ss x] (var f false) (each e (ss :items) (when (deep= e x) (set f true) (break))) f)
|
||||||
|
(defn ss-conj-many [ss xs]
|
||||||
|
(var seen @{}) (each e (ss :items) (put seen e true)) (each x xs (put seen x true))
|
||||||
|
(ss-make (sorted-by (ss :cmp) (array ;(keys seen))) (ss :cmp)))
|
||||||
|
(defn ss-disj-many [ss xs]
|
||||||
|
(def rm @{}) (each x xs (put rm x true))
|
||||||
|
(ss-make (filter (fn [e] (not (get rm e))) (ss :items)) (ss :cmp)))
|
||||||
|
|
||||||
(defn core-conj [& args]
|
(defn core-conj [& args]
|
||||||
(if (= 0 (length args)) (make-vec @[]) # (conj) -> []
|
(if (= 0 (length args)) (make-vec @[]) # (conj) -> []
|
||||||
(let [coll (first args) xs (tuple/slice args 1)]
|
(let [coll (first args) xs (tuple/slice args 1)]
|
||||||
(if (nil? coll)
|
(if (nil? coll)
|
||||||
# conj onto nil builds a list (prepends): (conj nil 1 2) -> (2 1)
|
# conj onto nil builds a list (prepends): (conj nil 1 2) -> (2 1)
|
||||||
(do (var result nil) (each x xs (set result (pl-cons x result))) result)
|
(do (var result nil) (each x xs (set result (pl-cons x result))) result)
|
||||||
|
(if (core-sorted-map? coll)
|
||||||
|
# conj a [k v] entry (or merge a map) into a sorted-map
|
||||||
|
(do (var m coll)
|
||||||
|
(each x xs
|
||||||
|
(if (map-value? x)
|
||||||
|
(each e (map-entries-of x) (set m (sm-assoc-many m [(in e 0) (in e 1)])))
|
||||||
|
(set m (sm-assoc-many m [(vnth x 0) (vnth x 1)]))))
|
||||||
|
m)
|
||||||
|
(if (core-sorted-set? coll)
|
||||||
|
(ss-conj-many coll xs)
|
||||||
(if (pvec? coll)
|
(if (pvec? coll)
|
||||||
(do (var result coll) (each x xs (set result (pv-conj result x))) result)
|
(do (var result coll) (each x xs (set result (pv-conj result x))) result)
|
||||||
(if (plist? coll)
|
(if (plist? coll)
|
||||||
|
|
@ -414,7 +471,7 @@
|
||||||
(each e (map-entries-of x)
|
(each e (map-entries-of x)
|
||||||
(set result (map-assoc1 result (in e 0) (in e 1))))
|
(set result (map-assoc1 result (in e 0) (in e 1))))
|
||||||
(set result (map-assoc1 result (vnth x 0) (vnth x 1)))))
|
(set result (map-assoc1 result (vnth x 0) (vnth x 1)))))
|
||||||
result)))))))))))
|
result)))))))))))))
|
||||||
|
|
||||||
(defn core-assoc [m & kvs]
|
(defn core-assoc [m & kvs]
|
||||||
(when (odd? (length kvs))
|
(when (odd? (length kvs))
|
||||||
|
|
@ -425,6 +482,7 @@
|
||||||
(and (struct? m) (get m :jolt/type)))
|
(and (struct? m) (get m :jolt/type)))
|
||||||
(error (string "assoc requires a map or vector, got " (type m))))
|
(error (string "assoc requires a map or vector, got " (type m))))
|
||||||
(cond
|
(cond
|
||||||
|
(core-sorted-map? m) (sm-assoc-many m kvs)
|
||||||
(phm? m)
|
(phm? m)
|
||||||
(do (var result m) (var i 0) (while (< i (length kvs)) (set result (phm-assoc result (kvs i) (kvs (+ i 1)))) (+= i 2)) result)
|
(do (var result m) (var i 0) (while (< i (length kvs)) (set result (phm-assoc result (kvs i) (kvs (+ i 1)))) (+= i 2)) result)
|
||||||
(pvec? m)
|
(pvec? m)
|
||||||
|
|
@ -466,6 +524,7 @@
|
||||||
(defn core-dissoc [m & ks]
|
(defn core-dissoc [m & ks]
|
||||||
(cond
|
(cond
|
||||||
(nil? m) nil
|
(nil? m) nil
|
||||||
|
(core-sorted-map? m) (sm-dissoc-many m ks)
|
||||||
(phm? m) (do (var result m) (each k ks (set result (phm-dissoc result k))) result)
|
(phm? m) (do (var result m) (each k ks (set result (phm-dissoc result k))) result)
|
||||||
# reject clearly non-map values (scalars, sequences, sets, symbol/char structs)
|
# reject clearly non-map values (scalars, sequences, sets, symbol/char structs)
|
||||||
(or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m)
|
(or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m)
|
||||||
|
|
@ -479,6 +538,8 @@
|
||||||
(defn core-get [m k &opt default]
|
(defn core-get [m k &opt default]
|
||||||
(default default nil)
|
(default default nil)
|
||||||
(if (nil? m) default
|
(if (nil? m) default
|
||||||
|
(if (core-sorted-map? m) (let [v (get (m :map) k)] (if (nil? v) default v))
|
||||||
|
(if (core-sorted-set? m) (if (ss-contains? m k) k default)
|
||||||
(if (core-transient? m)
|
(if (core-transient? m)
|
||||||
(case (m :kind)
|
(case (m :kind)
|
||||||
:vector (if (and (number? k) (>= k 0) (< k (length (m :arr)))) (in (m :arr) k) default)
|
:vector (if (and (number? k) (>= k 0) (< k (length (m :arr)))) (in (m :arr) k) default)
|
||||||
|
|
@ -493,7 +554,7 @@
|
||||||
(if (nil? v) default v))
|
(if (nil? v) default v))
|
||||||
(if (and (or (tuple? m) (array? m)) (number? k) (>= k 0) (< k (length m)))
|
(if (and (or (tuple? m) (array? m)) (number? k) (>= k 0) (< k (length m)))
|
||||||
(in m k)
|
(in m k)
|
||||||
default))))))))
|
default))))))))))
|
||||||
|
|
||||||
# Runtime invoke dispatch for COMPILED code (interpreter uses evaluator's
|
# Runtime invoke dispatch for COMPILED code (interpreter uses evaluator's
|
||||||
# jolt-invoke). Handles real functions plus Clojure IFn collections.
|
# jolt-invoke). Handles real functions plus Clojure IFn collections.
|
||||||
|
|
@ -502,6 +563,8 @@
|
||||||
(or (function? f) (cfunction? f)) (apply f args)
|
(or (function? f) (cfunction? f)) (apply f args)
|
||||||
(keyword? f) (core-get (get args 0) f (get args 1))
|
(keyword? f) (core-get (get args 0) f (get args 1))
|
||||||
(and (struct? f) (= :symbol (f :jolt/type))) (core-get (get args 0) f (get args 1))
|
(and (struct? f) (= :symbol (f :jolt/type))) (core-get (get args 0) f (get args 1))
|
||||||
|
(core-sorted-map? f) (let [v (get (f :map) (get args 0))] (if (nil? v) (get args 1) v))
|
||||||
|
(core-sorted-set? f) (if (ss-contains? f (get args 0)) (get args 0) (get args 1))
|
||||||
(phm? f) (phm-get f (get args 0) (get args 1))
|
(phm? f) (phm-get f (get args 0) (get args 1))
|
||||||
(set? f) (if (phs-contains? f (get args 0)) (get args 0) (get args 1))
|
(set? f) (if (phs-contains? f (get args 0)) (get args 0) (get args 1))
|
||||||
(pvec? f)
|
(pvec? f)
|
||||||
|
|
@ -551,6 +614,8 @@
|
||||||
(if missing default current))
|
(if missing default current))
|
||||||
|
|
||||||
(defn core-contains? [coll key]
|
(defn core-contains? [coll key]
|
||||||
|
(if (core-sorted-map? coll) (not (nil? (get (coll :map) key)))
|
||||||
|
(if (core-sorted-set? coll) (ss-contains? coll key)
|
||||||
(if (core-transient? coll)
|
(if (core-transient? coll)
|
||||||
(case (coll :kind)
|
(case (coll :kind)
|
||||||
:vector (and (number? key) (>= key 0) (< key (length (coll :arr))))
|
:vector (and (number? key) (>= key 0) (< key (length (coll :arr))))
|
||||||
|
|
@ -562,7 +627,7 @@
|
||||||
(if (table? coll) (not (nil? (coll key)))
|
(if (table? coll) (not (nil? (coll key)))
|
||||||
(if (or (tuple? coll) (array? coll))
|
(if (or (tuple? coll) (array? coll))
|
||||||
(and (number? key) (>= key 0) (< key (length coll)))
|
(and (number? key) (>= key 0) (< key (length coll)))
|
||||||
false))))))))
|
false))))))))))
|
||||||
|
|
||||||
# Coerce a Clojure IFn value to a Janet-callable fn for higher-order fns
|
# Coerce a Clojure IFn value to a Janet-callable fn for higher-order fns
|
||||||
# (map/filter/sort-by/group-by/...). Janet functions pass through; a keyword or
|
# (map/filter/sort-by/group-by/...). Janet functions pass through; a keyword or
|
||||||
|
|
@ -579,19 +644,8 @@
|
||||||
|
|
||||||
# Sorted collections — minimal: backed by a struct (map) / sorted array (set),
|
# Sorted collections — minimal: backed by a struct (map) / sorted array (set),
|
||||||
# ordered by key/element on read. Defined early so seq/count/get can dispatch.
|
# ordered by key/element on read. Defined early so seq/count/get can dispatch.
|
||||||
(defn core-sorted-map? [x] (and (table? x) (= :jolt/sorted-map (x :jolt/type))))
|
# sorted-map/sorted-set predicates, constructors and ops live ABOVE core-conj so
|
||||||
(defn core-sorted-set? [x] (and (table? x) (= :jolt/sorted-set (x :jolt/type))))
|
# the collection fns (conj/assoc/get/contains?/…) can branch on them.
|
||||||
(defn sm-make [m] @{:jolt/type :jolt/sorted-map :map m})
|
|
||||||
(defn ss-make [items] @{:jolt/type :jolt/sorted-set :items items})
|
|
||||||
(defn core-sorted-map [& kvs]
|
|
||||||
(var m @{}) (var i 0)
|
|
||||||
(while (< i (length kvs)) (put m (kvs i) (kvs (+ i 1))) (+= i 2))
|
|
||||||
(sm-make (table/to-struct m)))
|
|
||||||
(defn core-sorted-set [& xs]
|
|
||||||
(var seen @{}) (each x xs (put seen x true))
|
|
||||||
(ss-make (sort (array ;(keys seen)))))
|
|
||||||
(defn sorted-map-keys [sm] (sort (array ;(keys (sm :map)))))
|
|
||||||
(defn sorted-map-entries [sm] (let [m (sm :map)] (map (fn [k] [k (get m k)]) (sorted-map-keys sm))))
|
|
||||||
|
|
||||||
(defn core-count [coll]
|
(defn core-count [coll]
|
||||||
(cond
|
(cond
|
||||||
|
|
@ -771,10 +825,12 @@
|
||||||
|
|
||||||
(defn core-keys [m]
|
(defn core-keys [m]
|
||||||
# phm-entries (not phm-to-struct) so keys mapped to nil values are not dropped.
|
# phm-entries (not phm-to-struct) so keys mapped to nil values are not dropped.
|
||||||
(if (phm? m) (tuple ;(map |(in $ 0) (phm-entries m))) (tuple ;(keys m))))
|
(if (core-sorted-map? m) (tuple ;(sorted-map-keys m))
|
||||||
|
(if (phm? m) (tuple ;(map |(in $ 0) (phm-entries m))) (tuple ;(keys m)))))
|
||||||
|
|
||||||
(defn core-vals [m]
|
(defn core-vals [m]
|
||||||
(if (phm? m) (tuple ;(map |(in $ 1) (phm-entries m))) (tuple ;(map |(m $) (keys m)))))
|
(if (core-sorted-map? m) (tuple ;(map |(in $ 1) (sorted-map-entries m)))
|
||||||
|
(if (phm? m) (tuple ;(map |(in $ 1) (phm-entries m))) (tuple ;(map |(m $) (keys m))))))
|
||||||
|
|
||||||
(defn core-select-keys [m ks]
|
(defn core-select-keys [m ks]
|
||||||
# Include a key when it is PRESENT (contains?), even if its value is nil — a
|
# Include a key when it is PRESENT (contains?), even if its value is nil — a
|
||||||
|
|
@ -1507,7 +1563,10 @@
|
||||||
|
|
||||||
(defn core-set? [x] (set? x))
|
(defn core-set? [x] (set? x))
|
||||||
(defn core-disj [s & ks]
|
(defn core-disj [s & ks]
|
||||||
(if (set? s) (apply phs-disj s ks) (error "disj expects a set")))
|
(cond
|
||||||
|
(core-sorted-set? s) (ss-disj-many s ks)
|
||||||
|
(set? s) (apply phs-disj s ks)
|
||||||
|
(error "disj expects a set")))
|
||||||
|
|
||||||
(defn core-set [coll]
|
(defn core-set [coll]
|
||||||
(apply core-hash-set (realize-for-iteration coll)))
|
(apply core-hash-set (realize-for-iteration coll)))
|
||||||
|
|
@ -1696,6 +1755,14 @@
|
||||||
(prin "\n")
|
(prin "\n")
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
(defn core-with-out-str [thunk]
|
||||||
|
(def buf @"")
|
||||||
|
(with-dyns [:out buf] (thunk))
|
||||||
|
(string buf))
|
||||||
|
|
||||||
(defn core-pr [& xs]
|
(defn core-pr [& xs]
|
||||||
(var i 0)
|
(var i 0)
|
||||||
(while (< i (length xs))
|
(while (< i (length xs))
|
||||||
|
|
@ -1824,8 +1891,17 @@
|
||||||
(defn core-reader-conditional [form splicing?]
|
(defn core-reader-conditional [form splicing?]
|
||||||
@{:jolt/type :jolt/reader-conditional :form form :splicing? splicing?})
|
@{:jolt/type :jolt/reader-conditional :form form :splicing? splicing?})
|
||||||
# reader-conditional? now lives in the Clojure collection tier (tagged-value predicate).
|
# reader-conditional? now lives in the Clojure collection tier (tagged-value predicate).
|
||||||
(defn core-sorted-map-by [cmp & kvs] (apply core-sorted-map kvs))
|
# The user comparator is a Clojure IFn; wrap it as a Janet 2-arg fn returning the
|
||||||
(defn core-sorted-set-by [cmp & xs] (apply core-sorted-set xs))
|
# numeric compare result, then thread it through the sorted wrapper.
|
||||||
|
(defn core-sorted-map-by [cmp & kvs]
|
||||||
|
(let [jc (fn [a b] (jolt-call cmp a b))]
|
||||||
|
(var m @{}) (var i 0)
|
||||||
|
(while (< i (length kvs)) (put m (kvs i) (kvs (+ i 1))) (+= i 2))
|
||||||
|
(sm-make (table/to-struct m) jc)))
|
||||||
|
(defn core-sorted-set-by [cmp & xs]
|
||||||
|
(let [jc (fn [a b] (jolt-call cmp a b))]
|
||||||
|
(var seen @{}) (each x xs (put seen x true))
|
||||||
|
(ss-make (sorted-by jc (array ;(keys seen))) jc)))
|
||||||
(defn core-array-seq [arr & _] (core-seq arr))
|
(defn core-array-seq [arr & _] (core-seq arr))
|
||||||
(defn core-seque [& args] (in args (- (length args) 1)))
|
(defn core-seque [& args] (in args (- (length args) 1)))
|
||||||
(defn core-supers [x] (make-phs))
|
(defn core-supers [x] (make-phs))
|
||||||
|
|
@ -1964,6 +2040,8 @@
|
||||||
# future macro: (future body...) -> (future-call (fn* [] body...))
|
# future macro: (future body...) -> (future-call (fn* [] body...))
|
||||||
(defn core-deref [ref & opts]
|
(defn core-deref [ref & opts]
|
||||||
(cond
|
(cond
|
||||||
|
(and (table? ref) (= :jolt/reduced (ref :jolt/type)))
|
||||||
|
(ref :val)
|
||||||
(and (table? ref) (= :jolt/atom (ref :jolt/type)))
|
(and (table? ref) (= :jolt/atom (ref :jolt/type)))
|
||||||
(ref :value)
|
(ref :value)
|
||||||
(and (table? ref) (= :jolt/volatile (ref :jolt/type)))
|
(and (table? ref) (= :jolt/volatile (ref :jolt/type)))
|
||||||
|
|
@ -2828,7 +2906,7 @@
|
||||||
"namespace" core-namespace
|
"namespace" core-namespace
|
||||||
"sorted-map" core-sorted-map
|
"sorted-map" core-sorted-map
|
||||||
"sorted-set" core-sorted-set
|
"sorted-set" core-sorted-set
|
||||||
"sorted?" core-sorted-map?
|
"sorted?" core-sorted?
|
||||||
"reduced" core-reduced
|
"reduced" core-reduced
|
||||||
"reduced?" core-reduced?
|
"reduced?" core-reduced?
|
||||||
"take-nth" core-take-nth
|
"take-nth" core-take-nth
|
||||||
|
|
@ -2842,6 +2920,7 @@
|
||||||
"ex-info" core-ex-info
|
"ex-info" core-ex-info
|
||||||
"prn-str" core-prn-str
|
"prn-str" core-prn-str
|
||||||
"println-str" core-println-str
|
"println-str" core-println-str
|
||||||
|
"__with-out-str" core-with-out-str
|
||||||
"force" core-force
|
"force" core-force
|
||||||
"realized?" core-realized?
|
"realized?" core-realized?
|
||||||
"delay?" core-delay?
|
"delay?" core-delay?
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,10 @@
|
||||||
(keyword? f) (coll-lookup (get args 0) f (get args 1))
|
(keyword? f) (coll-lookup (get args 0) f (get args 1))
|
||||||
(and (struct? f) (= :symbol (f :jolt/type)))
|
(and (struct? f) (= :symbol (f :jolt/type)))
|
||||||
(coll-lookup (get args 0) f (get args 1))
|
(coll-lookup (get args 0) f (get args 1))
|
||||||
|
(and (table? f) (= :jolt/sorted-map (f :jolt/type)))
|
||||||
|
(let [v (get (f :map) (get args 0))] (if (nil? v) (get args 1) v))
|
||||||
|
(and (table? f) (= :jolt/sorted-set (f :jolt/type)))
|
||||||
|
(if (some |(deep= $ (get args 0)) (f :items)) (get args 0) (get args 1))
|
||||||
(phm? f) (phm-get f (get args 0) (get args 1))
|
(phm? f) (phm-get f (get args 0) (get args 1))
|
||||||
(set? f) (if (phs-contains? f (get args 0)) (get args 0) (get args 1))
|
(set? f) (if (phs-contains? f (get args 0)) (get args 0) (get args 1))
|
||||||
(pvec? f)
|
(pvec? f)
|
||||||
|
|
|
||||||
26
test/spec/io-spec.janet
Normal file
26
test/spec/io-spec.janet
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Specification: printing / output (print/println/pr/prn, *-str, format, str).
|
||||||
|
# Output is captured with with-out-str (jolt-rfw); the *-str fns return strings.
|
||||||
|
(use ../support/harness)
|
||||||
|
|
||||||
|
(defspec "io / with-out-str captures"
|
||||||
|
["println" "\"hi\\n\"" "(with-out-str (println \"hi\"))"]
|
||||||
|
["print spaces" "\"a b\"" "(with-out-str (print \"a\" \"b\"))"]
|
||||||
|
["prn quotes" "\"[1 2]\\n\"" "(with-out-str (prn [1 2]))"]
|
||||||
|
["pr no newline" "\"5\"" "(with-out-str (pr 5))"]
|
||||||
|
["multiple writes" "\"12\"" "(with-out-str (print 1) (print 2))"]
|
||||||
|
["no output" "\"\"" "(with-out-str 42)"]
|
||||||
|
["println no args" "\"\\n\"" "(with-out-str (println))"])
|
||||||
|
|
||||||
|
(defspec "io / *-str builders"
|
||||||
|
["print-str" "\"a b\"" "(print-str \"a\" \"b\")"]
|
||||||
|
["println-str" "\"x\\n\"" "(println-str \"x\")"]
|
||||||
|
["prn-str" "\"[1 2]\\n\"" "(prn-str [1 2])"]
|
||||||
|
["pr-str quotes" "\"\\\"s\\\"\"" "(pr-str \"s\")"]
|
||||||
|
["pr-str keyword" "\":a\"" "(pr-str :a)"])
|
||||||
|
|
||||||
|
(defspec "io / str & format"
|
||||||
|
["str concat" "\"1:ab\"" "(str 1 :a \"b\")"]
|
||||||
|
["str nil" "\"\"" "(str nil)"]
|
||||||
|
["str of coll" "\"[1 2]\"" "(str [1 2])"]
|
||||||
|
["format d/s" "\"5-x\"" "(format \"%d-%s\" 5 \"x\")"]
|
||||||
|
["format float" "\"3.14\"" "(format \"%.2f\" 3.14159)"])
|
||||||
|
|
@ -126,3 +126,11 @@
|
||||||
["bit-clear" "13" "(bit-clear 15 1)"]
|
["bit-clear" "13" "(bit-clear 15 1)"]
|
||||||
["bit-test true" "true" "(bit-test 4 2)"]
|
["bit-test true" "true" "(bit-test 4 2)"]
|
||||||
["bigint 64-bit" "\"9000000000\"" "(str (bigint 9000000000))"])
|
["bigint 64-bit" "\"9000000000\"" "(str (bigint 9000000000))"])
|
||||||
|
|
||||||
|
(defspec "numbers / random (invariants — non-deterministic)"
|
||||||
|
["rand-int in range" "true" "(let [r (rand-int 5)] (and (integer? r) (>= r 0) (< r 5)))"]
|
||||||
|
["rand-int zero" "0" "(rand-int 1)"]
|
||||||
|
["rand in [0,1)" "true" "(let [r (rand)] (and (>= r 0) (< r 1)))"]
|
||||||
|
["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])"])
|
||||||
|
|
|
||||||
|
|
@ -113,3 +113,20 @@
|
||||||
["not= differs" "true" "(not= [1 2] [1 3])"]
|
["not= differs" "true" "(not= [1 2] [1 3])"]
|
||||||
["identical? same kw" "true" "(identical? :a :a)"]
|
["identical? same kw" "true" "(identical? :a :a)"]
|
||||||
["compare strings" "-1" "(compare \"a\" \"b\")"])
|
["compare strings" "-1" "(compare \"a\" \"b\")"])
|
||||||
|
|
||||||
|
(defspec "predicates / seqable, reduced & emptiness"
|
||||||
|
["seqable? vector" "true" "(seqable? [1])"]
|
||||||
|
["seqable? map" "true" "(seqable? {:a 1})"]
|
||||||
|
["seqable? string" "true" "(seqable? \"s\")"]
|
||||||
|
["seqable? nil" "true" "(seqable? nil)"]
|
||||||
|
["seqable? number" "false" "(seqable? 5)"]
|
||||||
|
["integer? int" "true" "(integer? 5)"]
|
||||||
|
["integer? fraction" "false" "(integer? 5.5)"]
|
||||||
|
["reduced? wrapped" "true" "(reduced? (reduced 1))"]
|
||||||
|
["reduced? plain" "false" "(reduced? 1)"]
|
||||||
|
["deref reduced" "9" "(deref (reduced 9))"]
|
||||||
|
["unreduced wrapped" "9" "(unreduced (reduced 9))"]
|
||||||
|
["unreduced plain" "9" "(unreduced 9)"]
|
||||||
|
["not-empty full" "[1]" "(not-empty [1])"]
|
||||||
|
["not-empty empty" "nil" "(not-empty [])"]
|
||||||
|
["not-empty string" "nil" "(not-empty \"\")"])
|
||||||
|
|
|
||||||
33
test/spec/regex-spec.janet
Normal file
33
test/spec/regex-spec.janet
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Specification: regular expressions — #"…" literals and the re-* fns.
|
||||||
|
# (Whole area previously unspecced; some cases adapted from jank reader-macro/regex.)
|
||||||
|
(use ../support/harness)
|
||||||
|
|
||||||
|
(defspec "regex / literals & predicate"
|
||||||
|
["regex? literal" "true" "(regex? #\"\\d+\")"]
|
||||||
|
["regex? non-regex" "false" "(regex? \"\\d+\")"]
|
||||||
|
["escaped digits" "\"42\"" "(re-find #\"\\d+\" \"x42y\")"]
|
||||||
|
["escaped ws/non-ws" "\"x a\"" "(re-find #\"\\S\\s\\S\" \"x a b y\")"])
|
||||||
|
|
||||||
|
(defspec "regex / re-find"
|
||||||
|
["match" "\"123\"" "(re-find #\"\\d+\" \"abc123def\")"]
|
||||||
|
["no match nil" "nil" "(re-find #\"\\d+\" \"abc\")"]
|
||||||
|
["with groups" "[\"a1\" \"a\" \"1\"]" "(re-find #\"([a-z])(\\d)\" \"--a1--\")"]
|
||||||
|
["first match only" "\"1\"" "(re-find #\"\\d\" \"1 2 3\")"])
|
||||||
|
|
||||||
|
(defspec "regex / re-matches"
|
||||||
|
["full match" "\"123\"" "(re-matches #\"\\d+\" \"123\")"]
|
||||||
|
["partial = nil" "nil" "(re-matches #\"\\d+\" \"123abc\")"]
|
||||||
|
["groups" "[\"12\" \"1\" \"2\"]" "(re-matches #\"(\\d)(\\d)\" \"12\")"]
|
||||||
|
["no match nil" "nil" "(re-matches #\"x+\" \"yyy\")"])
|
||||||
|
|
||||||
|
(defspec "regex / re-seq"
|
||||||
|
["all matches" "(quote (\"1\" \"22\" \"333\"))" "(re-seq #\"\\d+\" \"a1b22c333\")"]
|
||||||
|
["empty when none" "nil" "(seq (re-seq #\"z\" \"abc\"))"]
|
||||||
|
["words" "(quote (\"foo\" \"bar\"))" "(re-seq #\"\\w+\" \"foo bar\")"])
|
||||||
|
|
||||||
|
(defspec "regex / re-pattern & string ops"
|
||||||
|
["re-pattern build" "\"hi\"" "(re-find (re-pattern \"\\\\w+\") \"hi!\")"]
|
||||||
|
["re-pattern is regex?" "true" "(regex? (re-pattern \"a\"))"]
|
||||||
|
["split on regex" "[\"a\" \"b\" \"c\"]" "(do (require '[clojure.string :as s]) (s/split \"a1b2c\" #\"\\d\"))"]
|
||||||
|
["replace regex" "\"X-X\"" "(do (require '[clojure.string :as s]) (s/replace \"a-b\" #\"[a-z]\" \"X\"))"]
|
||||||
|
["replace $1" "\"[a][b]\"" "(do (require '[clojure.string :as s]) (s/replace \"ab\" #\"([a-z])\" \"[$1]\"))"])
|
||||||
59
test/spec/sorted-spec.janet
Normal file
59
test/spec/sorted-spec.janet
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Specification: sorted collections (sorted-map / sorted-set, subseq/rsubseq).
|
||||||
|
#
|
||||||
|
# sorted collections are first-class for the core ops (jolt-ti9): get/assoc/dissoc/
|
||||||
|
# conj/contains?/keys/vals/disj all work and preserve sort order, and a sorted coll
|
||||||
|
# is callable as a key-lookup fn. STILL TODO: the by-comparator constructors
|
||||||
|
# (sorted-map-by / sorted-set-by) ignore the supplied comparator (jolt-ti9). (vec
|
||||||
|
# coerces a seq to a vector so expecteds are vector literals, not quoted lists.)
|
||||||
|
(use ../support/harness)
|
||||||
|
|
||||||
|
(defspec "sorted / construction & ordering"
|
||||||
|
["sorted-set orders" "[1 2 3]" "(vec (seq (sorted-set 3 1 2)))"]
|
||||||
|
["sorted-set dedupes" "[1 2 3]" "(vec (seq (sorted-set 3 1 2 1 3)))"]
|
||||||
|
["sorted-set numeric" "[1 2 10]" "(vec (seq (sorted-set 10 1 2)))"]
|
||||||
|
["sorted-map ordered entries" "[[:a 1] [:b 2] [:c 3]]" "(vec (seq (sorted-map :c 3 :a 1 :b 2)))"]
|
||||||
|
["first is min" "1" "(first (sorted-set 5 3 9 1))"])
|
||||||
|
|
||||||
|
(defspec "sorted / sorted?"
|
||||||
|
["sorted-set" "true" "(sorted? (sorted-set 1))"]
|
||||||
|
["sorted-map" "true" "(sorted? (sorted-map :a 1))"]
|
||||||
|
["plain set" "false" "(sorted? #{1})"]
|
||||||
|
["plain map" "false" "(sorted? {:a 1})"]
|
||||||
|
["vector" "false" "(sorted? [1 2])"])
|
||||||
|
|
||||||
|
(defspec "sorted / map ops"
|
||||||
|
["get hit" "2" "(get (sorted-map :a 1 :b 2) :b)"]
|
||||||
|
["get miss default" ":none" "(get (sorted-map :a 1) :z :none)"]
|
||||||
|
["contains? yes" "true" "(contains? (sorted-map :a 1) :a)"]
|
||||||
|
["contains? no" "false" "(contains? (sorted-map :a 1) :z)"]
|
||||||
|
["assoc keeps order" "[[:a 1] [:b 2] [:c 3]]" "(vec (seq (assoc (sorted-map :c 3 :a 1) :b 2)))"]
|
||||||
|
["dissoc" "[[:a 1] [:c 3]]" "(vec (seq (dissoc (sorted-map :a 1 :b 2 :c 3) :b)))"]
|
||||||
|
["conj entry" "[[:a 1] [:z 9]]" "(vec (seq (conj (sorted-map :a 1) [:z 9])))"]
|
||||||
|
["keys sorted" "[:a :b :c]" "(vec (keys (sorted-map :c 3 :a 1 :b 2)))"]
|
||||||
|
["vals by key" "[1 2 3]" "(vec (vals (sorted-map :c 3 :a 1 :b 2)))"]
|
||||||
|
["map as fn" "2" "((sorted-map :a 1 :b 2) :b)"]
|
||||||
|
["map as fn miss" ":d" "((sorted-map :a 1) :z :d)"])
|
||||||
|
|
||||||
|
(defspec "sorted / set ops"
|
||||||
|
["get present" "2" "(get (sorted-set 1 2 3) 2)"]
|
||||||
|
["get absent" ":none" "(get (sorted-set 1 2 3) 9 :none)"]
|
||||||
|
["contains? yes" "true" "(contains? (sorted-set 1 2 3) 2)"]
|
||||||
|
["contains? no" "false" "(contains? (sorted-set 1 2 3) 9)"]
|
||||||
|
["conj keeps order" "[0 1 2 3 5]" "(vec (seq (conj (sorted-set 1 2 3) 5 0)))"]
|
||||||
|
["disj" "[1 3]" "(vec (seq (disj (sorted-set 1 2 3) 2)))"]
|
||||||
|
["set as fn" "3" "((sorted-set 1 2 3) 3)"]
|
||||||
|
["set as fn miss" "nil" "((sorted-set 1 2 3) 9)"])
|
||||||
|
|
||||||
|
(defspec "sorted / by comparator"
|
||||||
|
["sorted-set-by desc" "[10 3 2 1]" "(vec (seq (sorted-set-by > 1 3 2 10)))"]
|
||||||
|
["sorted-map-by desc" "[[3 :c] [2 :b] [1 :a]]" "(vec (seq (sorted-map-by > 1 :a 3 :c 2 :b)))"]
|
||||||
|
["conj keeps comparator" "[5 3 2 1 0]" "(vec (seq (conj (sorted-set-by > 1 3 2) 5 0)))"]
|
||||||
|
["assoc keeps comparator" "[3 2 1]" "(vec (keys (assoc (sorted-map-by > 1 :a 3 :c) 2 :b)))"]
|
||||||
|
["disj keeps comparator" "[3 1]" "(vec (seq (disj (sorted-set-by > 1 2 3) 2)))"]
|
||||||
|
["by-comparator is sorted?" "true" "(sorted? (sorted-set-by > 1 2))"])
|
||||||
|
|
||||||
|
(defspec "sorted / subseq & rsubseq"
|
||||||
|
["subseq >=" "[3 4 5]" "(vec (subseq (sorted-set 1 2 3 4 5) >= 3))"]
|
||||||
|
["subseq <" "[1 2]" "(vec (subseq (sorted-set 1 2 3 4 5) < 3))"]
|
||||||
|
["subseq range" "[2 3 4]" "(vec (subseq (sorted-set 1 2 3 4 5) > 1 < 5))"]
|
||||||
|
["rsubseq <=" "[3 2 1]" "(vec (rsubseq (sorted-set 1 2 3 4 5) <= 3))"])
|
||||||
Loading…
Add table
Add a link
Reference in a new issue