nandi/clonimpublic Fork 0
86373e5
Commits
Clone
git clone https://git.rickub.com/nandi/clonim.git
git clone ssh://git@rickub.com/nandi/clonim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Persistent vectors (32-way trie) and HAMT maps/sets

Vectors and maps were copy-on-write seqs: every assoc/conj rebuilt the
whole collection, and set conj re-scanned for duplicates on top of that,
so building an n-element set was O(n^3).

Replace both with the real structures:

- PVec: Clojure's PersistentVector — a 32-way trie with a tail buffer.
  conj/assoc/nth are O(log32 n) and copy a handful of 32-wide nodes.
- PMap: a HAMT, used for both maps and sets (a set maps each key to
  itself). Collisions fall back to a linear bucket once the 32-bit hash
  runs out of index bits.
- hashValue, consistent with equals: ints and floats that compare equal
  hash alike, lists and vectors hash alike, sets and maps hash by xor.

Maps and sets keep insertion order — each entry carries an `ord` stamp
and iteration sorts by it — so printing, keys and vals stay deterministic
the way Clojure's small array-maps are.

Value grows `xs`/`vec`/`m` fields in place of `items`/`pairs`; `items`,
`pairs` and a new `count` are procs over the new representation, so the
compiler and the parts of core that only read collections are unchanged.

n = 8000, -d:release: vector conj 489ms -> 29ms, map assoc 448ms -> 64ms,
set conj 526607ms -> 102ms, map get 3031ms -> 50ms. fib(30) is unchanged.

Also: a justfile, examples/persistent.clj as a regression test with
recorded output, examples/persistent-bench.clj behind `just bench`, and
a fix for hyphenated .clj filenames (the emitted Nim module name must be
an identifier, so it is now sanitised independently of the binary name).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T16:07:55-07:00 Browse files
86373e5 parent: 980e0ef
modified .gitignore +2 -1
@@ -1,3 +1,4 @@
11 bin/
22 nimcache/
3-*.o
3+.clj-kondo/.cache/
4+.lsp/
@@ -1,3 +1,4 @@
1 bin/1 bin/
2 nimcache/2 nimcache/
3-*.o3+.clj-kondo/.cache/
4+.lsp/
modified README.md +30 -4
@@ -15,13 +15,16 @@ nim c --hints:off -o:bin/clonim src/clonim.nim # build the compiler
1515 ./bin/clonim emit examples/tour.clj # show the generated Nim
1616 ```
1717
18+There is a `justfile` too: `just build`, `just test`, `just bench`,
19+`just run <file>`, `just emit <file>`, `just accept` (re-record expectations).
20+
1821 ## Pipeline
1922
2023 | stage | file | what it does |
2124 |---|---|---|
2225 | reader | `src/reader.nim` | text → data. Forms *are* runtime values (homoiconic), as in Clojure |
2326 | analyzer + codegen | `src/compiler.nim` | expands the macro set to core special forms, emits Nim statements |
24-| runtime | `src/runtime.nim` | the `Value` tagged union, equality, printing, var cells, `call` |
27+| runtime | `src/runtime.nim` | the `Value` tagged union, the persistent vector/map, equality, printing, var cells, `call` |
2528 | core | `src/core.nim` | ~140 `clojure.core` builtins as Nim closures |
2629 | driver | `src/clonim.nim` | shells out to `nim c`, times each phase |
2730
@@ -41,6 +44,28 @@ resolved in the program prelude, so a call site is a pointer deref rather than a
4144 hash lookup, while `def` can still rebind it later. Worth ~20% on call-heavy
4245 code.
4346
47+**Collections share structure.** Vectors are 32-way tries with a tail buffer
48+(Clojure's `PersistentVector`); maps and sets are HAMTs. An `assoc` copies a
49+handful of 32-wide nodes and points at the rest of the old value, so it is
50+O(log₃₂ n) and the original stays valid — which is what makes the persistent
51+part of persistent data structures real rather than a spelling of "copy".
52+
53+Maps and sets also keep insertion order: each entry carries an `ord` stamp and
54+iteration sorts by it, so printing, `keys` and `vals` are deterministic the way
55+Clojure's small array-maps are, without giving up hashed lookup.
56+
57+`examples/persistent-bench.clj` (`just bench`), n = 8000, `-d:release`:
58+
59+| operation | copy-on-write `seq` | trie / HAMT |
60+|---|---:|---:|
61+| 8000 × `conj` onto a vector | 489 ms | 29 ms |
62+| 8000 × `assoc` onto a map | 448 ms | 64 ms |
63+| 8000 × `conj` onto a set | 526 607 ms | 102 ms |
64+| 8000 × `get` from a map | 3031 ms | 50 ms |
65+
66+The set column is the honest shape of the old representation: `conj` rebuilt the
67+whole set and re-scanned it for duplicates, so building one was O(n³).
68+
4469 ## What works
4570
4671 `def` `defn` (multi-arity, varargs, docstrings) `fn` (named, self-recursive)
@@ -50,7 +75,7 @@ code.
5075 Destructuring: sequential `[a b & rest]` and associative `{:keys [x y]}` in `let`.
5176
5277 Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set —
53-with structural equality and Clojure-shaped printing. Atoms, closures, `comp`,
78+persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`,
5479 `partial`, `juxt`, the usual seq library, `clojure.string/*`.
5580
5681 ## What doesn't (yet)
@@ -59,8 +84,9 @@ with structural equality and Clojure-shaped printing. Atoms, closures, `comp`,
5984 macros need the compiler to be able to *evaluate* code at compile time —
6085 the honest fix is to bootstrap clonim in itself, or embed an interpreter.
6186 - **Laziness.** `map`/`filter`/`range` are eager. Infinite seqs will hang.
62-- **Persistent data structures.** Vectors and maps are copy-on-write `seq`s, so
63- `assoc` is O(n), not O(log₃₂ n). This is the first thing to replace.
87+- **Laziness for the seq library.** Most of `core` still materialises a
88+ `seq[Value]` on the way in and out, so even with persistent vectors, `map`
89+ over a big collection allocates twice.
6490 - Protocols/records, namespaces (`ns` is parsed and ignored), refs/agents,
6591 `#()` literals, syntax-quote, transducers, Nim interop.
6692
@@ -15,13 +15,16 @@ nim c --hints:off -o:bin/clonim src/clonim.nim # build the compiler
15 ./bin/clonim emit examples/tour.clj # show the generated Nim15 ./bin/clonim emit examples/tour.clj # show the generated Nim
16 ```16 ```
17 17
18+There is a `justfile` too: `just build`, `just test`, `just bench`,
19+`just run <file>`, `just emit <file>`, `just accept` (re-record expectations).
20+
18 ## Pipeline21 ## Pipeline
19 22
20 | stage | file | what it does |23 | stage | file | what it does |
21 |---|---|---|24 |---|---|---|
22 | reader | `src/reader.nim` | text → data. Forms *are* runtime values (homoiconic), as in Clojure |25 | reader | `src/reader.nim` | text → data. Forms *are* runtime values (homoiconic), as in Clojure |
23 | analyzer + codegen | `src/compiler.nim` | expands the macro set to core special forms, emits Nim statements |26 | analyzer + codegen | `src/compiler.nim` | expands the macro set to core special forms, emits Nim statements |
24-| runtime | `src/runtime.nim` | the `Value` tagged union, equality, printing, var cells, `call` |27+| runtime | `src/runtime.nim` | the `Value` tagged union, the persistent vector/map, equality, printing, var cells, `call` |
25 | core | `src/core.nim` | ~140 `clojure.core` builtins as Nim closures |28 | core | `src/core.nim` | ~140 `clojure.core` builtins as Nim closures |
26 | driver | `src/clonim.nim` | shells out to `nim c`, times each phase |29 | driver | `src/clonim.nim` | shells out to `nim c`, times each phase |
27 30
@@ -41,6 +44,28 @@ resolved in the program prelude, so a call site is a pointer deref rather than a
41 hash lookup, while `def` can still rebind it later. Worth ~20% on call-heavy44 hash lookup, while `def` can still rebind it later. Worth ~20% on call-heavy
42 code.45 code.
43 46
47+**Collections share structure.** Vectors are 32-way tries with a tail buffer
48+(Clojure's `PersistentVector`); maps and sets are HAMTs. An `assoc` copies a
49+handful of 32-wide nodes and points at the rest of the old value, so it is
50+O(log₃₂ n) and the original stays valid — which is what makes the persistent
51+part of persistent data structures real rather than a spelling of "copy".
52+
53+Maps and sets also keep insertion order: each entry carries an `ord` stamp and
54+iteration sorts by it, so printing, `keys` and `vals` are deterministic the way
55+Clojure's small array-maps are, without giving up hashed lookup.
56+
57+`examples/persistent-bench.clj` (`just bench`), n = 8000, `-d:release`:
58+
59+| operation | copy-on-write `seq` | trie / HAMT |
60+|---|---:|---:|
61+| 8000 × `conj` onto a vector | 489 ms | 29 ms |
62+| 8000 × `assoc` onto a map | 448 ms | 64 ms |
63+| 8000 × `conj` onto a set | 526 607 ms | 102 ms |
64+| 8000 × `get` from a map | 3031 ms | 50 ms |
65+
66+The set column is the honest shape of the old representation: `conj` rebuilt the
67+whole set and re-scanned it for duplicates, so building one was O(n³).
68+
44 ## What works69 ## What works
45 70
46 `def` `defn` (multi-arity, varargs, docstrings) `fn` (named, self-recursive)71 `def` `defn` (multi-arity, varargs, docstrings) `fn` (named, self-recursive)
@@ -50,7 +75,7 @@ code.
50 Destructuring: sequential `[a b & rest]` and associative `{:keys [x y]}` in `let`.75 Destructuring: sequential `[a b & rest]` and associative `{:keys [x y]}` in `let`.
51 76
52 Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set —77 Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set —
53-with structural equality and Clojure-shaped printing. Atoms, closures, `comp`,78+persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`,
54 `partial`, `juxt`, the usual seq library, `clojure.string/*`.79 `partial`, `juxt`, the usual seq library, `clojure.string/*`.
55 80
56 ## What doesn't (yet)81 ## What doesn't (yet)
@@ -59,8 +84,9 @@ with structural equality and Clojure-shaped printing. Atoms, closures, `comp`,
59 macros need the compiler to be able to *evaluate* code at compile time —84 macros need the compiler to be able to *evaluate* code at compile time —
60 the honest fix is to bootstrap clonim in itself, or embed an interpreter.85 the honest fix is to bootstrap clonim in itself, or embed an interpreter.
61 - **Laziness.** `map`/`filter`/`range` are eager. Infinite seqs will hang.86 - **Laziness.** `map`/`filter`/`range` are eager. Infinite seqs will hang.
62-- **Persistent data structures.** Vectors and maps are copy-on-write `seq`s, so87+- **Laziness for the seq library.** Most of `core` still materialises a
63- `assoc` is O(n), not O(log₃₂ n). This is the first thing to replace.88+ `seq[Value]` on the way in and out, so even with persistent vectors, `map`
89+ over a big collection allocates twice.
64 - Protocols/records, namespaces (`ns` is parsed and ignored), refs/agents,90 - Protocols/records, namespaces (`ns` is parsed and ignored), refs/agents,
65 `#()` literals, syntax-quote, transducers, Nim interop.91 `#()` literals, syntax-quote, transducers, Nim interop.
66 92
added examples/persistent-bench.clj +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+(def n 20000)
2+(let [t0 (now-ms)
3+ v (reduce conj [] (range n))
4+ t1 (now-ms)
5+ m (reduce (fn [acc i] (assoc acc i i)) {} (range n))
6+ t2 (now-ms)
7+ s (reduce conj #{} (range n))
8+ t3 (now-ms)
9+ g (reduce (fn [acc i] (+ acc (get m i))) 0 (range n))
10+ t4 (now-ms)]
11+ (println "vector conj x" n ":" (- t1 t0) "ms")
12+ (println "map assoc x" n ":" (- t2 t1) "ms")
13+ (println "set conj x" n ":" (- t3 t2) "ms")
14+ (println "map get x" n ":" (- t4 t3) "ms sum" g)
15+ (println "counts" (count v) (count m) (count s)))
new file mode 100644
@@ -0,0 +1,15 @@
1+(def n 20000)
2+(let [t0 (now-ms)
3+ v (reduce conj [] (range n))
4+ t1 (now-ms)
5+ m (reduce (fn [acc i] (assoc acc i i)) {} (range n))
6+ t2 (now-ms)
7+ s (reduce conj #{} (range n))
8+ t3 (now-ms)
9+ g (reduce (fn [acc i] (+ acc (get m i))) 0 (range n))
10+ t4 (now-ms)]
11+ (println "vector conj x" n ":" (- t1 t0) "ms")
12+ (println "map assoc x" n ":" (- t2 t1) "ms")
13+ (println "set conj x" n ":" (- t3 t2) "ms")
14+ (println "map get x" n ":" (- t4 t3) "ms sum" g)
15+ (println "counts" (count v) (count m) (count s)))
added examples/persistent.clj +37 -0
new file mode 100644
@@ -0,0 +1,37 @@
1+;; build a big vector by conj, then read it back
2+(def n 5000)
3+(def v (reduce conj [] (range n)))
4+(println "count" (count v))
5+(println "nth" (nth v 0) (nth v 33) (nth v 1024) (nth v (dec n)))
6+(println "sum" (reduce + 0 v))
7+(def v2 (assoc v 1234 :x))
8+(println "assoc" (nth v2 1234) (nth v 1234) (count v2))
9+(println "last" (last v) (first v))
10+(println "eq" (= v (reduce conj [] (range n))))
11+
12+;; big map
13+(def m (reduce (fn [acc i] (assoc acc i (* i i))) {} (range n)))
14+(println "mcount" (count m) (get m 0) (get m 4999) (get m 5000))
15+(def m2 (dissoc m 100))
16+(println "dissoc" (count m2) (get m2 100) (get m 100))
17+(println "keys-sum" (reduce + 0 (keys m)))
18+(println "vals-sum" (reduce + 0 (vals m)))
19+(println "meq" (= m (reduce (fn [acc i] (assoc acc i (* i i))) {} (range n))))
20+
21+;; sets
22+(def s (set (range n)))
23+(println "scount" (count s) (contains? s 4999) (contains? s 5000))
24+(println "sconj" (count (conj s 5000)) (count (conj s 0)))
25+
26+;; ordering + mixed keys
27+(def om {:b 1 :a 2 "c" 3 4 5 [1 2] 6})
28+(println om)
29+(println (keys om))
30+(println (assoc om :a 99))
31+(println (get om [1 2]) (get om 4) (get om "c"))
32+(println (= {:a 1 :b 2} {:b 2 :a 1}))
33+(println (= #{1 2 3} #{3 2 1}))
34+(println (get {1 :int} 1.0) (get {1.0 :flt} 1))
35+(println (frequencies [1 1 2 3 3 3]))
36+(println (group-by even? (range 10)))
37+(println (update {:a 1} :a inc))
new file mode 100644
@@ -0,0 +1,37 @@
1+;; build a big vector by conj, then read it back
2+(def n 5000)
3+(def v (reduce conj [] (range n)))
4+(println "count" (count v))
5+(println "nth" (nth v 0) (nth v 33) (nth v 1024) (nth v (dec n)))
6+(println "sum" (reduce + 0 v))
7+(def v2 (assoc v 1234 :x))
8+(println "assoc" (nth v2 1234) (nth v 1234) (count v2))
9+(println "last" (last v) (first v))
10+(println "eq" (= v (reduce conj [] (range n))))
11+
12+;; big map
13+(def m (reduce (fn [acc i] (assoc acc i (* i i))) {} (range n)))
14+(println "mcount" (count m) (get m 0) (get m 4999) (get m 5000))
15+(def m2 (dissoc m 100))
16+(println "dissoc" (count m2) (get m2 100) (get m 100))
17+(println "keys-sum" (reduce + 0 (keys m)))
18+(println "vals-sum" (reduce + 0 (vals m)))
19+(println "meq" (= m (reduce (fn [acc i] (assoc acc i (* i i))) {} (range n))))
20+
21+;; sets
22+(def s (set (range n)))
23+(println "scount" (count s) (contains? s 4999) (contains? s 5000))
24+(println "sconj" (count (conj s 5000)) (count (conj s 0)))
25+
26+;; ordering + mixed keys
27+(def om {:b 1 :a 2 "c" 3 4 5 [1 2] 6})
28+(println om)
29+(println (keys om))
30+(println (assoc om :a 99))
31+(println (get om [1 2]) (get om 4) (get om "c"))
32+(println (= {:a 1 :b 2} {:b 2 :a 1}))
33+(println (= #{1 2 3} #{3 2 1}))
34+(println (get {1 :int} 1.0) (get {1.0 :flt} 1))
35+(println (frequencies [1 1 2 3 3 3]))
36+(println (group-by even? (range 10)))
37+(println (update {:a 1} :a inc))
added justfile +46 -0
new file mode 100644
@@ -0,0 +1,46 @@
1+# clonim — a Clojure compiler hosted on Nim
2+
3+bin := "bin/clonim"
4+
5+# Build the compiler
6+build:
7+ nim c --hints:off --warnings:off -o:{{bin}} src/clonim.nim
8+
9+# Build with optimisations on (compiler and, via -d:release, the programs it emits)
10+release:
11+ nim c -d:release --hints:off --warnings:off -o:{{bin}} src/clonim.nim
12+
13+# Run every example against tests/<name>.expected
14+test: build
15+ ./run-tests.sh
16+
17+# Compile and run a .clj file, e.g. `just run examples/tour.clj`
18+run file: build
19+ ./{{bin}} run {{file}}
20+
21+# Emit the generated Nim for a .clj file without compiling it
22+emit file: build
23+ ./{{bin}} emit {{file}}
24+
25+# Compile a .clj file to a native binary
26+compile file: build
27+ ./{{bin}} build {{file}}
28+
29+# Persistent-collection benchmark: conj/assoc/get at scale
30+bench: release
31+ ./{{bin}} run examples/persistent-bench.clj -d
32+
33+# Re-record tests/<name>.expected from current output
34+accept: build
35+ #!/usr/bin/env bash
36+ set -euo pipefail
37+ for f in examples/*.clj; do
38+ name=$(basename "$f" .clj)
39+ [ -f "tests/$name.expected" ] || continue
40+ ./{{bin}} run "$f" > "tests/$name.expected" 2>&1
41+ echo "recorded $name"
42+ done
43+
44+# Remove build output
45+clean:
46+ rm -rf bin nimcache
new file mode 100644
@@ -0,0 +1,46 @@
1+# clonim — a Clojure compiler hosted on Nim
2+
3+bin := "bin/clonim"
4+
5+# Build the compiler
6+build:
7+ nim c --hints:off --warnings:off -o:{{bin}} src/clonim.nim
8+
9+# Build with optimisations on (compiler and, via -d:release, the programs it emits)
10+release:
11+ nim c -d:release --hints:off --warnings:off -o:{{bin}} src/clonim.nim
12+
13+# Run every example against tests/<name>.expected
14+test: build
15+ ./run-tests.sh
16+
17+# Compile and run a .clj file, e.g. `just run examples/tour.clj`
18+run file: build
19+ ./{{bin}} run {{file}}
20+
21+# Emit the generated Nim for a .clj file without compiling it
22+emit file: build
23+ ./{{bin}} emit {{file}}
24+
25+# Compile a .clj file to a native binary
26+compile file: build
27+ ./{{bin}} build {{file}}
28+
29+# Persistent-collection benchmark: conj/assoc/get at scale
30+bench: release
31+ ./{{bin}} run examples/persistent-bench.clj -d
32+
33+# Re-record tests/<name>.expected from current output
34+accept: build
35+ #!/usr/bin/env bash
36+ set -euo pipefail
37+ for f in examples/*.clj; do
38+ name=$(basename "$f" .clj)
39+ [ -f "tests/$name.expected" ] || continue
40+ ./{{bin}} run "$f" > "tests/$name.expected" 2>&1
41+ echo "recorded $name"
42+ done
43+
44+# Remove build output
45+clean:
46+ rm -rf bin nimcache
modified src/clonim.nim +9 -3
@@ -65,14 +65,20 @@ proc main() =
6565 return
6666
6767 let stem = file.splitFile.name
68- let work = getTempDir() / ("clonim-" & stem & "-" & $getCurrentProcessId())
68+ # Nim module names must be identifiers, but .clj filenames are usually
69+ # hyphenated; the binary keeps the original stem, the module doesn't.
70+ var modName = ""
71+ for ch in stem:
72+ modName.add (if ch in {'a'..'z', 'A'..'Z', '0'..'9'}: ch else: '_')
73+ if modName.len == 0 or modName[0] in {'0'..'9'}: modName = "m" & modName
74+ let work = getTempDir() / ("clonim-" & modName & "-" & $getCurrentProcessId())
6975 createDir(work)
7076 defer: removeDir(work)
71- let nimFile = work / (stem & ".nim")
77+ let nimFile = work / (modName & ".nim")
7278 writeFile(nimFile, nimSrc)
7379
7480 if outBin.len == 0:
75- outBin = (if cmd == "build": stem else: work / stem)
81+ outBin = (if cmd == "build": stem else: work / modName)
7682 outBin = outBin.absolutePath
7783
7884 var nimCmd = @["nim", "c", "--hints:off", "--warnings:off",
@@ -65,14 +65,20 @@ proc main() =
65 return65 return
66 66
67 let stem = file.splitFile.name67 let stem = file.splitFile.name
68- let work = getTempDir() / ("clonim-" & stem & "-" & $getCurrentProcessId())68+ # Nim module names must be identifiers, but .clj filenames are usually
69+ # hyphenated; the binary keeps the original stem, the module doesn't.
70+ var modName = ""
71+ for ch in stem:
72+ modName.add (if ch in {'a'..'z', 'A'..'Z', '0'..'9'}: ch else: '_')
73+ if modName.len == 0 or modName[0] in {'0'..'9'}: modName = "m" & modName
74+ let work = getTempDir() / ("clonim-" & modName & "-" & $getCurrentProcessId())
69 createDir(work)75 createDir(work)
70 defer: removeDir(work)76 defer: removeDir(work)
71- let nimFile = work / (stem & ".nim")77+ let nimFile = work / (modName & ".nim")
72 writeFile(nimFile, nimSrc)78 writeFile(nimFile, nimSrc)
73 79
74 if outBin.len == 0:80 if outBin.len == 0:
75- outBin = (if cmd == "build": stem else: work / stem)81+ outBin = (if cmd == "build": stem else: work / modName)
76 outBin = outBin.absolutePath82 outBin = outBin.absolutePath
77 83
78 var nimCmd = @["nim", "c", "--hints:off", "--warnings:off",84 var nimCmd = @["nim", "c", "--hints:off", "--warnings:off",
modified src/core.nim +49 -67
@@ -43,18 +43,16 @@ proc cmpChain(args: seq[Value], ok: proc (c: int): bool): Value =
4343 proc getIn(coll, k, dflt: Value): Value =
4444 if coll.isNil or coll.kind == kNil: return dflt
4545 case coll.kind
46- of kMap:
47- for (kk, vv) in coll.pairs:
48- if equals(kk, k): return vv
49- dflt
50- of kVector, kList:
46+ of kMap: mapGet(coll.m, k, dflt)
47+ of kSet: mapGet(coll.m, k, dflt)
48+ of kVector:
5149 if k.kind != kInt: return dflt
5250 let i = int(k.i)
53- if i < 0 or i >= coll.items.len: dflt else: coll.items[i]
54- of kSet:
55- for x in coll.items:
56- if equals(x, k): return x
57- dflt
51+ if i < 0 or i >= coll.vec.cnt: dflt else: vecNth(coll.vec, i)
52+ of kList:
53+ if k.kind != kInt: return dflt
54+ let i = int(k.i)
55+ if i < 0 or i >= coll.xs.len: dflt else: coll.xs[i]
5856 of kStr:
5957 if k.kind != kInt: return dflt
6058 let i = int(k.i)
@@ -63,39 +61,29 @@ proc getIn(coll, k, dflt: Value): Value =
6361
6462 proc assocOne(coll, k, v: Value): Value =
6563 if coll.isNil or coll.kind == kNil:
66- return Value(kind: kMap, pairs: @[(k, v)])
64+ return mkMapOf(mapAssoc(emptyPMap(), k, v))
6765 case coll.kind
68- of kMap:
69- var ps = coll.pairs
70- for i in 0 ..< ps.len:
71- if equals(ps[i][0], k):
72- ps[i] = (k, v)
73- return Value(kind: kMap, pairs: ps)
74- ps.add (k, v)
75- Value(kind: kMap, pairs: ps)
66+ of kMap: mkMapOf(mapAssoc(coll.m, k, v))
7667 of kVector:
7768 if k.kind != kInt: err("Vector index must be an integer")
78- var xs = coll.items
79- let i = int(k.i)
80- if i == xs.len: xs.add v
81- elif i >= 0 and i < xs.len: xs[i] = v
82- else: err("Index out of bounds: " & $i)
83- mkVector(xs)
69+ mkVec(vecAssoc(coll.vec, int(k.i), v))
8470 else: err("assoc not supported on " & prStr(coll))
8571
8672 proc conjOne(coll, x: Value): Value =
8773 if coll.isNil or coll.kind == kNil: return mkList(@[x])
8874 case coll.kind
89- of kVector: mkVector(coll.items & @[x])
90- of kList: mkList(@[x] & coll.items)
91- of kSet: mkSet(coll.items & @[x])
75+ of kVector: mkVec(vecConj(coll.vec, x))
76+ of kList: mkList(@[x] & coll.xs)
77+ of kSet:
78+ (if mapContains(coll.m, x): coll else: mkSetOf(mapAssoc(coll.m, x, x)))
9279 of kMap:
93- if x.kind in {kVector, kList} and x.items.len == 2:
94- assocOne(coll, x.items[0], x.items[1])
80+ let xs = items(x)
81+ if x.kind in {kVector, kList} and xs.len == 2:
82+ assocOne(coll, xs[0], xs[1])
9583 elif x.kind == kMap:
96- var m = coll
97- for (k, v) in x.pairs: m = assocOne(m, k, v)
98- m
84+ var m = coll.m
85+ for e in mapEntries(x.m): m = mapAssoc(m, e.key, e.val)
86+ mkMapOf(m)
9987 else: err("conj on map needs a pair")
10088 else: err("conj not supported on " & prStr(coll))
10189
@@ -177,21 +165,14 @@ proc registerCore*() =
177165 def "coll?", proc (a: seq[Value]): Value =
178166 mkBool(a[0].kind in {kList, kVector, kMap, kSet})
179167 def "fn?", proc (a: seq[Value]): Value = mkBool(a[0].kind == kFn)
180- def "empty?", proc (a: seq[Value]): Value = mkBool(toSeq(a[0]).len == 0)
168+ def "empty?", proc (a: seq[Value]): Value = mkBool(count(a[0]) == 0)
181169 def "contains?", proc (a: seq[Value]): Value =
182170 let c = a[0]
183171 if c.isNil or c.kind == kNil: return FalseV
184172 case c.kind
185- of kMap:
186- for (k, _) in c.pairs:
187- if equals(k, a[1]): return TrueV
188- FalseV
189- of kSet:
190- for x in c.items:
191- if equals(x, a[1]): return TrueV
192- FalseV
173+ of kMap, kSet: mkBool(mapContains(c.m, a[1]))
193174 of kVector:
194- mkBool(a[1].kind == kInt and a[1].i >= 0 and a[1].i < c.items.len)
175+ mkBool(a[1].kind == kInt and a[1].i >= 0 and a[1].i < c.vec.cnt)
195176 else: FalseV
196177
197178 # ---- strings / IO
@@ -253,11 +234,11 @@ proc registerCore*() =
253234 def "list", proc (a: seq[Value]): Value = mkList(a)
254235 def "vector", proc (a: seq[Value]): Value = mkVector(a)
255236 def "hash-map", proc (a: seq[Value]): Value =
256- var m: Value = Value(kind: kMap, pairs: @[])
237+ var m = emptyPMap()
257238 var i = 0
258239 while i + 1 < a.len:
259- m = assocOne(m, a[i], a[i + 1]); i += 2
260- m
240+ m = mapAssoc(m, a[i], a[i + 1]); i += 2
241+ mkMapOf(m)
261242 def "hash-set", proc (a: seq[Value]): Value = mkSet(a)
262243 def "set", proc (a: seq[Value]): Value = mkSet(toSeq(a[0]))
263244 def "vec", proc (a: seq[Value]): Value = mkVector(toSeq(a[0]))
@@ -266,20 +247,22 @@ proc registerCore*() =
266247 (if s.len == 0: NilV else: mkList(s))
267248 def "count", proc (a: seq[Value]): Value =
268249 if a[0].isNil or a[0].kind == kNil: return mkInt(0)
269- if a[0].kind == kStr: return mkInt(a[0].s.len)
270- if a[0].kind == kMap: return mkInt(a[0].pairs.len)
271- mkInt(toSeq(a[0]).len)
250+ mkInt(count(a[0]))
272251 def "conj", proc (a: seq[Value]): Value =
273252 result = a[0]
274253 for i in 1 ..< a.len: result = conjOne(result, a[i])
275254 def "cons", proc (a: seq[Value]): Value = mkList(@[a[0]] & toSeq(a[1]))
276255 def "first", proc (a: seq[Value]): Value =
256+ if a[0].kind == kVector:
257+ return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, 0))
277258 let s = toSeq(a[0])
278259 (if s.len == 0: NilV else: s[0])
279260 def "second", proc (a: seq[Value]): Value =
280261 let s = toSeq(a[0])
281262 (if s.len < 2: NilV else: s[1])
282263 def "last", proc (a: seq[Value]): Value =
264+ if a[0].kind == kVector:
265+ return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, a[0].vec.cnt - 1))
283266 let s = toSeq(a[0])
284267 (if s.len == 0: NilV else: s[^1])
285268 def "rest", proc (a: seq[Value]): Value =
@@ -289,8 +272,13 @@ proc registerCore*() =
289272 let s = toSeq(a[0])
290273 (if s.len <= 1: NilV else: mkList(s[1 .. ^1]))
291274 def "nth", proc (a: seq[Value]): Value =
292- let s = toSeq(a[0])
293275 let i = int(intOf(a[1]))
276+ if a[0].kind == kVector:
277+ # O(log32 n) straight through the trie, no intermediate seq
278+ if i >= 0 and i < a[0].vec.cnt: return vecNth(a[0].vec, i)
279+ if a.len > 2: return a[2]
280+ err("Index out of bounds: " & $i)
281+ let s = toSeq(a[0])
294282 if i >= 0 and i < s.len: s[i]
295283 elif a.len > 2: a[2]
296284 else: err("Index out of bounds: " & $i)
@@ -307,23 +295,19 @@ proc registerCore*() =
307295 while i + 1 < a.len:
308296 result = assocOne(result, a[i], a[i + 1]); i += 2
309297 def "dissoc", proc (a: seq[Value]): Value =
310- var ps = a[0].pairs
311- for i in 1 ..< a.len:
312- var keep: seq[(Value, Value)] = @[]
313- for (k, v) in ps:
314- if not equals(k, a[i]): keep.add (k, v)
315- ps = keep
316- Value(kind: kMap, pairs: ps)
298+ var m = a[0].m
299+ for i in 1 ..< a.len: m = mapDissoc(m, a[i])
300+ mkMapOf(m)
317301 def "update", proc (a: seq[Value]): Value =
318302 let cur = getIn(a[0], a[1], NilV)
319303 assocOne(a[0], a[1], call(a[2], @[cur] & a[3 .. ^1]))
320304 def "keys", proc (a: seq[Value]): Value =
321305 var r: seq[Value] = @[]
322- for (k, _) in a[0].pairs: r.add k
306+ for e in mapEntries(a[0].m): r.add e.key
323307 (if r.len == 0: NilV else: mkList(r))
324308 def "vals", proc (a: seq[Value]): Value =
325309 var r: seq[Value] = @[]
326- for (_, v) in a[0].pairs: r.add v
310+ for e in mapEntries(a[0].m): r.add e.val
327311 (if r.len == 0: NilV else: mkList(r))
328312 def "reverse", proc (a: seq[Value]): Value =
329313 var s = toSeq(a[0])
@@ -484,18 +468,16 @@ proc registerCore*() =
484468 r.add x
485469 mkList(r)
486470 def "group-by", proc (a: seq[Value]): Value =
487- var m: Value = Value(kind: kMap, pairs: @[])
471+ var m = emptyPMap()
488472 for x in toSeq(a[1]):
489473 let k = call(a[0], @[x])
490- let cur = getIn(m, k, mkVector(@[]))
491- m = assocOne(m, k, conjOne(cur, x))
492- m
474+ m = mapAssoc(m, k, conjOne(mapGet(m, k, mkVector(@[])), x))
475+ mkMapOf(m)
493476 def "frequencies", proc (a: seq[Value]): Value =
494- var m: Value = Value(kind: kMap, pairs: @[])
477+ var m = emptyPMap()
495478 for x in toSeq(a[0]):
496- let cur = getIn(m, x, mkInt(0))
497- m = assocOne(m, x, mkInt(cur.i + 1))
498- m
479+ m = mapAssoc(m, x, mkInt(mapGet(m, x, mkInt(0)).i + 1))
480+ mkMapOf(m)
499481 def "identity", proc (a: seq[Value]): Value = a[0]
500482 def "comp", proc (a: seq[Value]): Value =
501483 let fs = a
@@ -43,18 +43,16 @@ proc cmpChain(args: seq[Value], ok: proc (c: int): bool): Value =
43 proc getIn(coll, k, dflt: Value): Value =43 proc getIn(coll, k, dflt: Value): Value =
44 if coll.isNil or coll.kind == kNil: return dflt44 if coll.isNil or coll.kind == kNil: return dflt
45 case coll.kind45 case coll.kind
46- of kMap:46+ of kMap: mapGet(coll.m, k, dflt)
47- for (kk, vv) in coll.pairs:47+ of kSet: mapGet(coll.m, k, dflt)
48- if equals(kk, k): return vv48+ of kVector:
49- dflt
50- of kVector, kList:
51 if k.kind != kInt: return dflt49 if k.kind != kInt: return dflt
52 let i = int(k.i)50 let i = int(k.i)
53- if i < 0 or i >= coll.items.len: dflt else: coll.items[i]51+ if i < 0 or i >= coll.vec.cnt: dflt else: vecNth(coll.vec, i)
54- of kSet:52+ of kList:
55- for x in coll.items:53+ if k.kind != kInt: return dflt
56- if equals(x, k): return x54+ let i = int(k.i)
57- dflt55+ if i < 0 or i >= coll.xs.len: dflt else: coll.xs[i]
58 of kStr:56 of kStr:
59 if k.kind != kInt: return dflt57 if k.kind != kInt: return dflt
60 let i = int(k.i)58 let i = int(k.i)
@@ -63,39 +61,29 @@ proc getIn(coll, k, dflt: Value): Value =
63 61
64 proc assocOne(coll, k, v: Value): Value =62 proc assocOne(coll, k, v: Value): Value =
65 if coll.isNil or coll.kind == kNil:63 if coll.isNil or coll.kind == kNil:
66- return Value(kind: kMap, pairs: @[(k, v)])64+ return mkMapOf(mapAssoc(emptyPMap(), k, v))
67 case coll.kind65 case coll.kind
68- of kMap:66+ of kMap: mkMapOf(mapAssoc(coll.m, k, v))
69- var ps = coll.pairs
70- for i in 0 ..< ps.len:
71- if equals(ps[i][0], k):
72- ps[i] = (k, v)
73- return Value(kind: kMap, pairs: ps)
74- ps.add (k, v)
75- Value(kind: kMap, pairs: ps)
76 of kVector:67 of kVector:
77 if k.kind != kInt: err("Vector index must be an integer")68 if k.kind != kInt: err("Vector index must be an integer")
78- var xs = coll.items69+ mkVec(vecAssoc(coll.vec, int(k.i), v))
79- let i = int(k.i)
80- if i == xs.len: xs.add v
81- elif i >= 0 and i < xs.len: xs[i] = v
82- else: err("Index out of bounds: " & $i)
83- mkVector(xs)
84 else: err("assoc not supported on " & prStr(coll))70 else: err("assoc not supported on " & prStr(coll))
85 71
86 proc conjOne(coll, x: Value): Value =72 proc conjOne(coll, x: Value): Value =
87 if coll.isNil or coll.kind == kNil: return mkList(@[x])73 if coll.isNil or coll.kind == kNil: return mkList(@[x])
88 case coll.kind74 case coll.kind
89- of kVector: mkVector(coll.items & @[x])75+ of kVector: mkVec(vecConj(coll.vec, x))
90- of kList: mkList(@[x] & coll.items)76+ of kList: mkList(@[x] & coll.xs)
91- of kSet: mkSet(coll.items & @[x])77+ of kSet:
78+ (if mapContains(coll.m, x): coll else: mkSetOf(mapAssoc(coll.m, x, x)))
92 of kMap:79 of kMap:
93- if x.kind in {kVector, kList} and x.items.len == 2:80+ let xs = items(x)
94- assocOne(coll, x.items[0], x.items[1])81+ if x.kind in {kVector, kList} and xs.len == 2:
82+ assocOne(coll, xs[0], xs[1])
95 elif x.kind == kMap:83 elif x.kind == kMap:
96- var m = coll84+ var m = coll.m
97- for (k, v) in x.pairs: m = assocOne(m, k, v)85+ for e in mapEntries(x.m): m = mapAssoc(m, e.key, e.val)
98- m86+ mkMapOf(m)
99 else: err("conj on map needs a pair")87 else: err("conj on map needs a pair")
100 else: err("conj not supported on " & prStr(coll))88 else: err("conj not supported on " & prStr(coll))
101 89
@@ -177,21 +165,14 @@ proc registerCore*() =
177 def "coll?", proc (a: seq[Value]): Value =165 def "coll?", proc (a: seq[Value]): Value =
178 mkBool(a[0].kind in {kList, kVector, kMap, kSet})166 mkBool(a[0].kind in {kList, kVector, kMap, kSet})
179 def "fn?", proc (a: seq[Value]): Value = mkBool(a[0].kind == kFn)167 def "fn?", proc (a: seq[Value]): Value = mkBool(a[0].kind == kFn)
180- def "empty?", proc (a: seq[Value]): Value = mkBool(toSeq(a[0]).len == 0)168+ def "empty?", proc (a: seq[Value]): Value = mkBool(count(a[0]) == 0)
181 def "contains?", proc (a: seq[Value]): Value =169 def "contains?", proc (a: seq[Value]): Value =
182 let c = a[0]170 let c = a[0]
183 if c.isNil or c.kind == kNil: return FalseV171 if c.isNil or c.kind == kNil: return FalseV
184 case c.kind172 case c.kind
185- of kMap:173+ of kMap, kSet: mkBool(mapContains(c.m, a[1]))
186- for (k, _) in c.pairs:
187- if equals(k, a[1]): return TrueV
188- FalseV
189- of kSet:
190- for x in c.items:
191- if equals(x, a[1]): return TrueV
192- FalseV
193 of kVector:174 of kVector:
194- mkBool(a[1].kind == kInt and a[1].i >= 0 and a[1].i < c.items.len)175+ mkBool(a[1].kind == kInt and a[1].i >= 0 and a[1].i < c.vec.cnt)
195 else: FalseV176 else: FalseV
196 177
197 # ---- strings / IO178 # ---- strings / IO
@@ -253,11 +234,11 @@ proc registerCore*() =
253 def "list", proc (a: seq[Value]): Value = mkList(a)234 def "list", proc (a: seq[Value]): Value = mkList(a)
254 def "vector", proc (a: seq[Value]): Value = mkVector(a)235 def "vector", proc (a: seq[Value]): Value = mkVector(a)
255 def "hash-map", proc (a: seq[Value]): Value =236 def "hash-map", proc (a: seq[Value]): Value =
256- var m: Value = Value(kind: kMap, pairs: @[])237+ var m = emptyPMap()
257 var i = 0238 var i = 0
258 while i + 1 < a.len:239 while i + 1 < a.len:
259- m = assocOne(m, a[i], a[i + 1]); i += 2240+ m = mapAssoc(m, a[i], a[i + 1]); i += 2
260- m241+ mkMapOf(m)
261 def "hash-set", proc (a: seq[Value]): Value = mkSet(a)242 def "hash-set", proc (a: seq[Value]): Value = mkSet(a)
262 def "set", proc (a: seq[Value]): Value = mkSet(toSeq(a[0]))243 def "set", proc (a: seq[Value]): Value = mkSet(toSeq(a[0]))
263 def "vec", proc (a: seq[Value]): Value = mkVector(toSeq(a[0]))244 def "vec", proc (a: seq[Value]): Value = mkVector(toSeq(a[0]))
@@ -266,20 +247,22 @@ proc registerCore*() =
266 (if s.len == 0: NilV else: mkList(s))247 (if s.len == 0: NilV else: mkList(s))
267 def "count", proc (a: seq[Value]): Value =248 def "count", proc (a: seq[Value]): Value =
268 if a[0].isNil or a[0].kind == kNil: return mkInt(0)249 if a[0].isNil or a[0].kind == kNil: return mkInt(0)
269- if a[0].kind == kStr: return mkInt(a[0].s.len)250+ mkInt(count(a[0]))
270- if a[0].kind == kMap: return mkInt(a[0].pairs.len)
271- mkInt(toSeq(a[0]).len)
272 def "conj", proc (a: seq[Value]): Value =251 def "conj", proc (a: seq[Value]): Value =
273 result = a[0]252 result = a[0]
274 for i in 1 ..< a.len: result = conjOne(result, a[i])253 for i in 1 ..< a.len: result = conjOne(result, a[i])
275 def "cons", proc (a: seq[Value]): Value = mkList(@[a[0]] & toSeq(a[1]))254 def "cons", proc (a: seq[Value]): Value = mkList(@[a[0]] & toSeq(a[1]))
276 def "first", proc (a: seq[Value]): Value =255 def "first", proc (a: seq[Value]): Value =
256+ if a[0].kind == kVector:
257+ return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, 0))
277 let s = toSeq(a[0])258 let s = toSeq(a[0])
278 (if s.len == 0: NilV else: s[0])259 (if s.len == 0: NilV else: s[0])
279 def "second", proc (a: seq[Value]): Value =260 def "second", proc (a: seq[Value]): Value =
280 let s = toSeq(a[0])261 let s = toSeq(a[0])
281 (if s.len < 2: NilV else: s[1])262 (if s.len < 2: NilV else: s[1])
282 def "last", proc (a: seq[Value]): Value =263 def "last", proc (a: seq[Value]): Value =
264+ if a[0].kind == kVector:
265+ return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, a[0].vec.cnt - 1))
283 let s = toSeq(a[0])266 let s = toSeq(a[0])
284 (if s.len == 0: NilV else: s[^1])267 (if s.len == 0: NilV else: s[^1])
285 def "rest", proc (a: seq[Value]): Value =268 def "rest", proc (a: seq[Value]): Value =
@@ -289,8 +272,13 @@ proc registerCore*() =
289 let s = toSeq(a[0])272 let s = toSeq(a[0])
290 (if s.len <= 1: NilV else: mkList(s[1 .. ^1]))273 (if s.len <= 1: NilV else: mkList(s[1 .. ^1]))
291 def "nth", proc (a: seq[Value]): Value =274 def "nth", proc (a: seq[Value]): Value =
292- let s = toSeq(a[0])
293 let i = int(intOf(a[1]))275 let i = int(intOf(a[1]))
276+ if a[0].kind == kVector:
277+ # O(log32 n) straight through the trie, no intermediate seq
278+ if i >= 0 and i < a[0].vec.cnt: return vecNth(a[0].vec, i)
279+ if a.len > 2: return a[2]
280+ err("Index out of bounds: " & $i)
281+ let s = toSeq(a[0])
294 if i >= 0 and i < s.len: s[i]282 if i >= 0 and i < s.len: s[i]
295 elif a.len > 2: a[2]283 elif a.len > 2: a[2]
296 else: err("Index out of bounds: " & $i)284 else: err("Index out of bounds: " & $i)
@@ -307,23 +295,19 @@ proc registerCore*() =
307 while i + 1 < a.len:295 while i + 1 < a.len:
308 result = assocOne(result, a[i], a[i + 1]); i += 2296 result = assocOne(result, a[i], a[i + 1]); i += 2
309 def "dissoc", proc (a: seq[Value]): Value =297 def "dissoc", proc (a: seq[Value]): Value =
310- var ps = a[0].pairs298+ var m = a[0].m
311- for i in 1 ..< a.len:299+ for i in 1 ..< a.len: m = mapDissoc(m, a[i])
312- var keep: seq[(Value, Value)] = @[]300+ mkMapOf(m)
313- for (k, v) in ps:
314- if not equals(k, a[i]): keep.add (k, v)
315- ps = keep
316- Value(kind: kMap, pairs: ps)
317 def "update", proc (a: seq[Value]): Value =301 def "update", proc (a: seq[Value]): Value =
318 let cur = getIn(a[0], a[1], NilV)302 let cur = getIn(a[0], a[1], NilV)
319 assocOne(a[0], a[1], call(a[2], @[cur] & a[3 .. ^1]))303 assocOne(a[0], a[1], call(a[2], @[cur] & a[3 .. ^1]))
320 def "keys", proc (a: seq[Value]): Value =304 def "keys", proc (a: seq[Value]): Value =
321 var r: seq[Value] = @[]305 var r: seq[Value] = @[]
322- for (k, _) in a[0].pairs: r.add k306+ for e in mapEntries(a[0].m): r.add e.key
323 (if r.len == 0: NilV else: mkList(r))307 (if r.len == 0: NilV else: mkList(r))
324 def "vals", proc (a: seq[Value]): Value =308 def "vals", proc (a: seq[Value]): Value =
325 var r: seq[Value] = @[]309 var r: seq[Value] = @[]
326- for (_, v) in a[0].pairs: r.add v310+ for e in mapEntries(a[0].m): r.add e.val
327 (if r.len == 0: NilV else: mkList(r))311 (if r.len == 0: NilV else: mkList(r))
328 def "reverse", proc (a: seq[Value]): Value =312 def "reverse", proc (a: seq[Value]): Value =
329 var s = toSeq(a[0])313 var s = toSeq(a[0])
@@ -484,18 +468,16 @@ proc registerCore*() =
484 r.add x468 r.add x
485 mkList(r)469 mkList(r)
486 def "group-by", proc (a: seq[Value]): Value =470 def "group-by", proc (a: seq[Value]): Value =
487- var m: Value = Value(kind: kMap, pairs: @[])471+ var m = emptyPMap()
488 for x in toSeq(a[1]):472 for x in toSeq(a[1]):
489 let k = call(a[0], @[x])473 let k = call(a[0], @[x])
490- let cur = getIn(m, k, mkVector(@[]))474+ m = mapAssoc(m, k, conjOne(mapGet(m, k, mkVector(@[])), x))
491- m = assocOne(m, k, conjOne(cur, x))475+ mkMapOf(m)
492- m
493 def "frequencies", proc (a: seq[Value]): Value =476 def "frequencies", proc (a: seq[Value]): Value =
494- var m: Value = Value(kind: kMap, pairs: @[])477+ var m = emptyPMap()
495 for x in toSeq(a[0]):478 for x in toSeq(a[0]):
496- let cur = getIn(m, x, mkInt(0))479+ m = mapAssoc(m, x, mkInt(mapGet(m, x, mkInt(0)).i + 1))
497- m = assocOne(m, x, mkInt(cur.i + 1))480+ mkMapOf(m)
498- m
499 def "identity", proc (a: seq[Value]): Value = a[0]481 def "identity", proc (a: seq[Value]): Value = a[0]
500 def "comp", proc (a: seq[Value]): Value =482 def "comp", proc (a: seq[Value]): Value =
501 let fs = a483 let fs = a
modified src/reader.nim +1 -1
@@ -110,7 +110,7 @@ proc readForm(r: var Reader): Value =
110110 var i = 0
111111 while i < xs.len:
112112 ps.add (xs[i], xs[i + 1]); i += 2
113- return Value(kind: kMap, pairs: ps)
113+ return mkMap(ps)
114114 of ')', ']', '}':
115115 r.readerErr("Unmatched delimiter: " & c)
116116 of '"':
@@ -110,7 +110,7 @@ proc readForm(r: var Reader): Value =
110 var i = 0110 var i = 0
111 while i < xs.len:111 while i < xs.len:
112 ps.add (xs[i], xs[i + 1]); i += 2112 ps.add (xs[i], xs[i + 1]); i += 2
113- return Value(kind: kMap, pairs: ps)113+ return mkMap(ps)
114 of ')', ']', '}':114 of ')', ']', '}':
115 r.readerErr("Unmatched delimiter: " & c)115 r.readerErr("Unmatched delimiter: " & c)
116 of '"':116 of '"':
modified src/runtime.nim +393 -46
@@ -1,11 +1,61 @@
1-## clonim runtime — persistent-ish Clojure values for compiled Nim code.
2-import std/[tables, strutils]
1+## clonim runtime — persistent Clojure values for compiled Nim code.
2+##
3+## Vectors are 32-way tries with a tail buffer (Clojure's PersistentVector);
4+## maps and sets are HAMTs. Both share structure on update, so `assoc`/`conj`
5+## are O(log32 n) and copy a handful of 32-element nodes instead of the whole
6+## collection. Maps and sets additionally remember insertion order — every
7+## entry carries an `ord` stamp and iteration sorts by it — so printing and
8+## `keys`/`vals` stay predictable the way Clojure's small array-maps are.
9+import std/[tables, strutils, hashes, bitops, algorithm]
10+
11+const
12+ Bits = 5
13+ Width = 1 shl Bits # 32
14+ Mask = Width - 1
15+ MaxShift = 30 # beyond this a HAMT runs out of hash bits
316
417 type
518 Kind* = enum
619 kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol,
720 kList, kVector, kMap, kSet, kFn
821
22+ VNode* = ref object
23+ ## A trie node: leaves hold values, internal nodes hold children.
24+ case leaf*: bool
25+ of true: vals*: seq[Value]
26+ of false: kids*: seq[VNode]
27+
28+ PVec* = object
29+ cnt*: int ## total element count
30+ shift*: int ## bit offset of the root level
31+ root*: VNode ## internal node (never nil)
32+ tail*: seq[Value] ## up to 32 trailing elements, not yet in the trie
33+
34+ MEntry* = object
35+ key*, val*: Value
36+ ord*: int ## insertion stamp, for stable iteration order
37+
38+ MSlotKind* = enum msEntry, msNode
39+ MSlot* = object
40+ case sk*: MSlotKind
41+ of msEntry: e*: MEntry
42+ of msNode: node*: MNode
43+
44+ MNode* = ref object
45+ ## Bitmap-indexed node, or — once the hash bits run out — a linear
46+ ## collision bucket.
47+ case collision*: bool
48+ of false:
49+ bitmap*: uint32
50+ slots*: seq[MSlot]
51+ of true:
52+ kvs*: seq[MEntry]
53+
54+ PMap* = object
55+ root*: MNode ## nil when empty
56+ cnt*: int
57+ nextOrd*: int
58+
959 Value* = ref object
1060 case kind*: Kind
1161 of kNil: discard
@@ -13,8 +63,9 @@ type
1363 of kInt: i*: int64
1464 of kFloat: f*: float64
1565 of kStr, kKeyword, kSymbol: s*: string
16- of kList, kVector, kSet: items*: seq[Value]
17- of kMap: pairs*: seq[(Value, Value)]
66+ of kList: xs*: seq[Value]
67+ of kVector: vec*: PVec
68+ of kMap, kSet: m*: PMap
1869 of kFn:
1970 fn*: proc (args: seq[Value]): Value {.closure.}
2071 name*: string
@@ -25,19 +76,337 @@ let NilV* = Value(kind: kNil)
2576 let TrueV* = Value(kind: kBool, b: true)
2677 let FalseV* = Value(kind: kBool, b: false)
2778
79+proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg)
80+
81+proc equals*(a, b: Value): bool
82+proc hashValue*(v: Value): uint32
83+proc prStr*(v: Value): string
84+
85+# ------------------------------------------------------- persistent vector
86+let emptyVNode = VNode(leaf: false, kids: @[])
87+
88+proc emptyPVec*(): PVec = PVec(cnt: 0, shift: Bits, root: emptyVNode, tail: @[])
89+
90+proc tailOff(v: PVec): int =
91+ if v.cnt < Width: 0 else: ((v.cnt - 1) shr Bits) shl Bits
92+
93+proc leafFor(v: PVec, i: int): seq[Value] =
94+ if i >= tailOff(v): return v.tail
95+ var node = v.root
96+ var level = v.shift
97+ while level > 0:
98+ node = node.kids[(i shr level) and Mask]
99+ level -= Bits
100+ node.vals
101+
102+proc vecNth*(v: PVec, i: int): Value =
103+ if i < 0 or i >= v.cnt: err("Index out of bounds: " & $i)
104+ leafFor(v, i)[i and Mask]
105+
106+proc newPath(level: int, node: VNode): VNode =
107+ if level == 0: node
108+ else: VNode(leaf: false, kids: @[newPath(level - Bits, node)])
109+
110+proc pushTail(cnt, level: int, parent, tailNode: VNode): VNode =
111+ let subIdx = ((cnt - 1) shr level) and Mask
112+ var kids = parent.kids
113+ let child =
114+ if level == Bits: tailNode
115+ elif subIdx < kids.len: pushTail(cnt, level - Bits, kids[subIdx], tailNode)
116+ else: newPath(level - Bits, tailNode)
117+ if subIdx < kids.len: kids[subIdx] = child
118+ else: kids.add child
119+ VNode(leaf: false, kids: kids)
120+
121+proc vecConj*(v: PVec, x: Value): PVec =
122+ if v.tail.len < Width:
123+ return PVec(cnt: v.cnt + 1, shift: v.shift, root: v.root, tail: v.tail & x)
124+ let tailNode = VNode(leaf: true, vals: v.tail)
125+ var shift = v.shift
126+ var root: VNode
127+ if (v.cnt shr Bits) > (1 shl v.shift): # root overflow: grow a level
128+ root = VNode(leaf: false, kids: @[v.root, newPath(v.shift, tailNode)])
129+ shift += Bits
130+ else:
131+ root = pushTail(v.cnt, v.shift, v.root, tailNode)
132+ PVec(cnt: v.cnt + 1, shift: shift, root: root, tail: @[x])
133+
134+proc doAssoc(level: int, node: VNode, i: int, x: Value): VNode =
135+ if level == 0:
136+ var vals = node.vals
137+ vals[i and Mask] = x
138+ VNode(leaf: true, vals: vals)
139+ else:
140+ var kids = node.kids
141+ let sub = (i shr level) and Mask
142+ kids[sub] = doAssoc(level - Bits, kids[sub], i, x)
143+ VNode(leaf: false, kids: kids)
144+
145+proc vecAssoc*(v: PVec, i: int, x: Value): PVec =
146+ if i == v.cnt: return vecConj(v, x)
147+ if i < 0 or i > v.cnt: err("Index out of bounds: " & $i)
148+ if i >= tailOff(v):
149+ var tail = v.tail
150+ tail[i - tailOff(v)] = x
151+ return PVec(cnt: v.cnt, shift: v.shift, root: v.root, tail: tail)
152+ PVec(cnt: v.cnt, shift: v.shift, root: doAssoc(v.shift, v.root, i, x),
153+ tail: v.tail)
154+
155+proc vecToSeq*(v: PVec): seq[Value] =
156+ result = newSeqOfCap[Value](v.cnt)
157+ var i = 0
158+ while i < v.cnt:
159+ let leaf = leafFor(v, i)
160+ let base = i and not Mask
161+ for j in 0 ..< leaf.len:
162+ if base + j >= v.cnt: break
163+ result.add leaf[j]
164+ i = base + leaf.len
165+
166+proc toPVec*(xs: seq[Value]): PVec =
167+ result = emptyPVec()
168+ for x in xs: result = vecConj(result, x)
169+
170+# ----------------------------------------------------------------- hashing
171+proc mixHash(a, b: uint32): uint32 =
172+ ## Boost-style combine; cheap and good enough for trie index bits.
173+ a xor (b + 0x9e3779b9'u32 + (a shl 6) + (a shr 2))
174+
175+# ---------------------------------------------------------- persistent map
176+proc bitPos(h: uint32, shift: int): uint32 = 1'u32 shl ((h shr shift) and Mask)
177+proc slotIdx(bitmap, bit: uint32): int = countSetBits(bitmap and (bit - 1))
178+
179+proc emptyPMap*(): PMap = PMap(root: nil, cnt: 0, nextOrd: 0)
180+
181+proc mergeEntries(shift: int, h1: uint32, e1: MEntry,
182+ h2: uint32, e2: MEntry): MNode =
183+ if shift > MaxShift:
184+ return MNode(collision: true, kvs: @[e1, e2])
185+ let b1 = bitPos(h1, shift)
186+ let b2 = bitPos(h2, shift)
187+ if b1 == b2:
188+ MNode(collision: false, bitmap: b1,
189+ slots: @[MSlot(sk: msNode,
190+ node: mergeEntries(shift + Bits, h1, e1, h2, e2))])
191+ elif b1 < b2:
192+ MNode(collision: false, bitmap: b1 or b2,
193+ slots: @[MSlot(sk: msEntry, e: e1), MSlot(sk: msEntry, e: e2)])
194+ else:
195+ MNode(collision: false, bitmap: b1 or b2,
196+ slots: @[MSlot(sk: msEntry, e: e2), MSlot(sk: msEntry, e: e1)])
197+
198+proc nodeAssoc(n: MNode, shift: int, h: uint32, k, v: Value, newOrd: int,
199+ added: var bool): MNode =
200+ if n.collision:
201+ for i in 0 ..< n.kvs.len:
202+ if equals(n.kvs[i].key, k):
203+ var kvs = n.kvs
204+ kvs[i].val = v
205+ return MNode(collision: true, kvs: kvs)
206+ added = true
207+ return MNode(collision: true,
208+ kvs: n.kvs & MEntry(key: k, val: v, ord: newOrd))
209+ let bit = bitPos(h, shift)
210+ let idx = slotIdx(n.bitmap, bit)
211+ var slots = n.slots
212+ if (n.bitmap and bit) != 0:
213+ case slots[idx].sk
214+ of msNode:
215+ slots[idx] = MSlot(sk: msNode,
216+ node: nodeAssoc(slots[idx].node, shift + Bits, h, k, v, newOrd, added))
217+ of msEntry:
218+ let e = slots[idx].e
219+ if equals(e.key, k):
220+ slots[idx] = MSlot(sk: msEntry, e: MEntry(key: k, val: v, ord: e.ord))
221+ else:
222+ added = true
223+ slots[idx] = MSlot(sk: msNode,
224+ node: mergeEntries(shift + Bits, hashValue(e.key), e, h,
225+ MEntry(key: k, val: v, ord: newOrd)))
226+ return MNode(collision: false, bitmap: n.bitmap, slots: slots)
227+ added = true
228+ slots.insert(MSlot(sk: msEntry, e: MEntry(key: k, val: v, ord: newOrd)), idx)
229+ MNode(collision: false, bitmap: n.bitmap or bit, slots: slots)
230+
231+proc mapAssoc*(m: PMap, k, v: Value): PMap =
232+ let h = hashValue(k)
233+ var added = false
234+ if m.root.isNil:
235+ return PMap(root: MNode(collision: false, bitmap: bitPos(h, 0),
236+ slots: @[MSlot(sk: msEntry,
237+ e: MEntry(key: k, val: v, ord: 0))]),
238+ cnt: 1, nextOrd: 1)
239+ let root = nodeAssoc(m.root, 0, h, k, v, m.nextOrd, added)
240+ PMap(root: root, cnt: m.cnt + (if added: 1 else: 0),
241+ nextOrd: m.nextOrd + (if added: 1 else: 0))
242+
243+proc nodeFind(n: MNode, shift: int, h: uint32, k: Value,
244+ found: var bool): Value =
245+ if n.isNil: return NilV
246+ if n.collision:
247+ for e in n.kvs:
248+ if equals(e.key, k):
249+ found = true
250+ return e.val
251+ return NilV
252+ let bit = bitPos(h, shift)
253+ if (n.bitmap and bit) == 0: return NilV
254+ let slot = n.slots[slotIdx(n.bitmap, bit)]
255+ case slot.sk
256+ of msNode: nodeFind(slot.node, shift + Bits, h, k, found)
257+ of msEntry:
258+ if equals(slot.e.key, k):
259+ found = true
260+ slot.e.val
261+ else: NilV
262+
263+proc mapGet*(m: PMap, k: Value, dflt: Value): Value =
264+ var found = false
265+ let v = nodeFind(m.root, 0, hashValue(k), k, found)
266+ if found: v else: dflt
267+
268+proc mapContains*(m: PMap, k: Value): bool =
269+ var found = false
270+ discard nodeFind(m.root, 0, hashValue(k), k, found)
271+ found
272+
273+proc nodeDissoc(n: MNode, shift: int, h: uint32, k: Value,
274+ removed: var bool): MNode =
275+ if n.collision:
276+ var kvs: seq[MEntry] = @[]
277+ for e in n.kvs:
278+ if equals(e.key, k): removed = true
279+ else: kvs.add e
280+ return (if kvs.len == 0: nil else: MNode(collision: true, kvs: kvs))
281+ let bit = bitPos(h, shift)
282+ if (n.bitmap and bit) == 0: return n
283+ let idx = slotIdx(n.bitmap, bit)
284+ var slots = n.slots
285+ case slots[idx].sk
286+ of msNode:
287+ let child = nodeDissoc(slots[idx].node, shift + Bits, h, k, removed)
288+ if child.isNil:
289+ slots.delete(idx)
290+ return (if slots.len == 0: nil
291+ else: MNode(collision: false, bitmap: n.bitmap and not bit,
292+ slots: slots))
293+ slots[idx] = MSlot(sk: msNode, node: child)
294+ MNode(collision: false, bitmap: n.bitmap, slots: slots)
295+ of msEntry:
296+ if not equals(slots[idx].e.key, k): return n
297+ removed = true
298+ slots.delete(idx)
299+ if slots.len == 0: nil
300+ else: MNode(collision: false, bitmap: n.bitmap and not bit, slots: slots)
301+
302+proc mapDissoc*(m: PMap, k: Value): PMap =
303+ if m.root.isNil: return m
304+ var removed = false
305+ let root = nodeDissoc(m.root, 0, hashValue(k), k, removed)
306+ if not removed: return m
307+ PMap(root: root, cnt: m.cnt - 1, nextOrd: m.nextOrd)
308+
309+proc collect(n: MNode, acc: var seq[MEntry]) =
310+ if n.isNil: return
311+ if n.collision:
312+ for e in n.kvs: acc.add e
313+ return
314+ for slot in n.slots:
315+ case slot.sk
316+ of msEntry: acc.add slot.e
317+ of msNode: collect(slot.node, acc)
318+
319+proc mapEntries*(m: PMap): seq[MEntry] =
320+ ## Entries in insertion order.
321+ result = newSeqOfCap[MEntry](m.cnt)
322+ collect(m.root, result)
323+ result.sort(proc (a, b: MEntry): int = cmp(a.ord, b.ord))
324+
325+proc hashValue*(v: Value): uint32 =
326+ if v.isNil: return 0
327+ case v.kind
328+ of kNil: 0'u32
329+ of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32)
330+ of kInt: uint32(hash(v.i))
331+ of kFloat:
332+ # ints and floats compare equal across kinds, so they must hash alike
333+ if v.f == float64(int64(v.f)): uint32(hash(int64(v.f)))
334+ else: uint32(hash(v.f))
335+ of kStr: mixHash(1'u32, uint32(hash(v.s)))
336+ of kKeyword: mixHash(2'u32, uint32(hash(v.s)))
337+ of kSymbol: mixHash(3'u32, uint32(hash(v.s)))
338+ of kList, kVector:
339+ # lists and vectors are `=` when their elements are, so they hash alike
340+ var h = 7'u32
341+ for x in (if v.kind == kList: v.xs else: vecToSeq(v.vec)):
342+ h = mixHash(h, hashValue(x))
343+ h
344+ of kSet:
345+ var h = 0'u32 # xor: independent of iteration order
346+ for e in mapEntries(v.m): h = h xor hashValue(e.key)
347+ h
348+ of kMap:
349+ var h = 0'u32
350+ for e in mapEntries(v.m):
351+ h = h xor mixHash(hashValue(e.key), hashValue(e.val))
352+ h
353+ of kFn: uint32(hash(cast[int](cast[pointer](v))))
354+
355+# ------------------------------------------------------------ constructors
28356 proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV)
29357 proc mkInt*(x: int64): Value = Value(kind: kInt, i: x)
30358 proc mkFloat*(x: float64): Value = Value(kind: kFloat, f: x)
31359 proc mkStr*(x: string): Value = Value(kind: kStr, s: x)
32360 proc mkKeyword*(x: string): Value = Value(kind: kKeyword, s: x)
33361 proc mkSymbol*(x: string): Value = Value(kind: kSymbol, s: x)
34-proc mkList*(xs: seq[Value]): Value = Value(kind: kList, items: xs)
35-proc mkVector*(xs: seq[Value]): Value = Value(kind: kVector, items: xs)
36-proc mkSet*(xs: seq[Value]): Value
362+proc mkList*(xs: seq[Value]): Value = Value(kind: kList, xs: xs)
363+proc mkVector*(xs: seq[Value]): Value = Value(kind: kVector, vec: toPVec(xs))
364+proc mkVec*(v: PVec): Value = Value(kind: kVector, vec: v)
365+proc mkMapOf*(m: PMap): Value = Value(kind: kMap, m: m)
366+proc mkSetOf*(m: PMap): Value = Value(kind: kSet, m: m)
367+
368+proc mkMap*(ps: seq[(Value, Value)]): Value =
369+ var m = emptyPMap()
370+ for (k, v) in ps: m = mapAssoc(m, k, v)
371+ Value(kind: kMap, m: m)
372+
373+proc mkSet*(xs: seq[Value]): Value =
374+ var m = emptyPMap()
375+ for x in xs:
376+ if not mapContains(m, x): m = mapAssoc(m, x, x)
377+ Value(kind: kSet, m: m)
378+
37379 proc mkFn*(name: string, f: proc (args: seq[Value]): Value {.closure.}): Value =
38380 Value(kind: kFn, fn: f, name: name)
39381
40-proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg)
382+# --------------------------------------------------------------- accessors
383+proc items*(v: Value): seq[Value] =
384+ ## Elements of any sequential value, in order. O(n) — prefer `count`/`nth`
385+ ## when you only need one element.
386+ if v.isNil: return @[]
387+ case v.kind
388+ of kList: v.xs
389+ of kVector: vecToSeq(v.vec)
390+ of kSet:
391+ var r = newSeqOfCap[Value](v.m.cnt)
392+ for e in mapEntries(v.m): r.add e.key
393+ r
394+ else: @[]
395+
396+proc pairs*(v: Value): seq[(Value, Value)] =
397+ if v.isNil or v.kind != kMap: return @[]
398+ result = newSeqOfCap[(Value, Value)](v.m.cnt)
399+ for e in mapEntries(v.m): result.add (e.key, e.val)
400+
401+proc count*(v: Value): int =
402+ if v.isNil: return 0
403+ case v.kind
404+ of kNil: 0
405+ of kList: v.xs.len
406+ of kVector: v.vec.cnt
407+ of kMap, kSet: v.m.cnt
408+ of kStr: v.s.len
409+ else: err("Don't know how to count: " & prStr(v))
41410
42411 proc truthy*(v: Value): bool =
43412 if v == nil: return false
@@ -54,9 +423,11 @@ proc equals*(a, b: Value): bool =
54423 if a.kind == kFloat and b.kind == kInt: return a.f == float64(b.i)
55424 # lists and vectors are sequentially equal in Clojure
56425 if a.kind in {kList, kVector} and b.kind in {kList, kVector}:
57- if a.items.len != b.items.len: return false
58- for i in 0 ..< a.items.len:
59- if not equals(a.items[i], b.items[i]): return false
426+ if count(a) != count(b): return false
427+ let xs = items(a)
428+ let ys = items(b)
429+ for i in 0 ..< xs.len:
430+ if not equals(xs[i], ys[i]): return false
60431 return true
61432 if a.kind != b.kind: return false
62433 case a.kind
@@ -66,35 +437,18 @@ proc equals*(a, b: Value): bool =
66437 of kFloat: a.f == b.f
67438 of kStr, kKeyword, kSymbol: a.s == b.s
68439 of kSet:
69- if a.items.len != b.items.len: return false
70- for x in a.items:
71- var found = false
72- for y in b.items:
73- if equals(x, y): found = true; break
74- if not found: return false
440+ if a.m.cnt != b.m.cnt: return false
441+ for e in mapEntries(a.m):
442+ if not mapContains(b.m, e.key): return false
75443 true
76444 of kMap:
77- if a.pairs.len != b.pairs.len: return false
78- for (k, v) in a.pairs:
79- var found = false
80- for (k2, v2) in b.pairs:
81- if equals(k, k2):
82- if not equals(v, v2): return false
83- found = true; break
84- if not found: return false
445+ if a.m.cnt != b.m.cnt: return false
446+ let missing = Value(kind: kKeyword, s: "%clonim-missing")
447+ for e in mapEntries(a.m):
448+ if not equals(e.val, mapGet(b.m, e.key, missing)): return false
85449 true
86450 of kFn: a == b
87451 of kList, kVector: false # handled above
88-
89-proc mkSet*(xs: seq[Value]): Value =
90- var acc: seq[Value] = @[]
91- for x in xs:
92- var dup = false
93- for y in acc:
94- if equals(x, y): dup = true; break
95- if not dup: acc.add x
96- Value(kind: kSet, items: acc)
97-
98452 # ---------------------------------------------------------------- printing
99453 proc escapeStr(s: string): string =
100454 result = "\""
@@ -184,19 +538,13 @@ proc call*(f: Value, args: seq[Value]): Value =
184538 if args.len == 0: err("Wrong number of args to keyword")
185539 let m = args[0]
186540 if m.isNil or m.kind != kMap: return NilV
187- for (k, v) in m.pairs:
188- if equals(k, f): return v
189- (if args.len > 1: args[1] else: NilV)
541+ mapGet(m.m, f, (if args.len > 1: args[1] else: NilV))
190542 of kMap:
191543 if args.len == 0: err("Wrong number of args to map")
192- for (k, v) in f.pairs:
193- if equals(k, args[0]): return v
194- (if args.len > 1: args[1] else: NilV)
544+ mapGet(f.m, args[0], (if args.len > 1: args[1] else: NilV))
195545 of kVector:
196546 if args.len != 1 or args[0].kind != kInt: err("Vector lookup needs one int")
197- let i = int(args[0].i)
198- if i < 0 or i >= f.items.len: err("Index out of bounds: " & $i)
199- f.items[i]
547+ vecNth(f.vec, int(args[0].i))
200548 else: err("Can't call value of kind " & $f.kind & ": " & prStr(f))
201549
202550 proc argAt*(args: seq[Value], i: int): Value =
@@ -222,9 +570,8 @@ proc toSeq*(v: Value): seq[Value] =
222570 r
223571 of kMap:
224572 var r: seq[Value] = @[]
225- for (k, val) in v.pairs: r.add mkVector(@[k, val])
573+ for e in mapEntries(v.m): r.add mkVector(@[e.key, e.val])
226574 r
227575 else: err("Don't know how to create seq from: " & prStr(v))
228576
229-proc mkMap*(ps: seq[(Value, Value)]): Value = Value(kind: kMap, pairs: ps)
230577 let emptyArgs*: seq[Value] = @[]
@@ -1,11 +1,61 @@
1-## clonim runtime — persistent-ish Clojure values for compiled Nim code.1+## clonim runtime — persistent Clojure values for compiled Nim code.
2-import std/[tables, strutils]2+##
3+## Vectors are 32-way tries with a tail buffer (Clojure's PersistentVector);
4+## maps and sets are HAMTs. Both share structure on update, so `assoc`/`conj`
5+## are O(log32 n) and copy a handful of 32-element nodes instead of the whole
6+## collection. Maps and sets additionally remember insertion order — every
7+## entry carries an `ord` stamp and iteration sorts by it — so printing and
8+## `keys`/`vals` stay predictable the way Clojure's small array-maps are.
9+import std/[tables, strutils, hashes, bitops, algorithm]
10+
11+const
12+ Bits = 5
13+ Width = 1 shl Bits # 32
14+ Mask = Width - 1
15+ MaxShift = 30 # beyond this a HAMT runs out of hash bits
3 16
4 type17 type
5 Kind* = enum18 Kind* = enum
6 kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol,19 kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol,
7 kList, kVector, kMap, kSet, kFn20 kList, kVector, kMap, kSet, kFn
8 21
22+ VNode* = ref object
23+ ## A trie node: leaves hold values, internal nodes hold children.
24+ case leaf*: bool
25+ of true: vals*: seq[Value]
26+ of false: kids*: seq[VNode]
27+
28+ PVec* = object
29+ cnt*: int ## total element count
30+ shift*: int ## bit offset of the root level
31+ root*: VNode ## internal node (never nil)
32+ tail*: seq[Value] ## up to 32 trailing elements, not yet in the trie
33+
34+ MEntry* = object
35+ key*, val*: Value
36+ ord*: int ## insertion stamp, for stable iteration order
37+
38+ MSlotKind* = enum msEntry, msNode
39+ MSlot* = object
40+ case sk*: MSlotKind
41+ of msEntry: e*: MEntry
42+ of msNode: node*: MNode
43+
44+ MNode* = ref object
45+ ## Bitmap-indexed node, or — once the hash bits run out — a linear
46+ ## collision bucket.
47+ case collision*: bool
48+ of false:
49+ bitmap*: uint32
50+ slots*: seq[MSlot]
51+ of true:
52+ kvs*: seq[MEntry]
53+
54+ PMap* = object
55+ root*: MNode ## nil when empty
56+ cnt*: int
57+ nextOrd*: int
58+
9 Value* = ref object59 Value* = ref object
10 case kind*: Kind60 case kind*: Kind
11 of kNil: discard61 of kNil: discard
@@ -13,8 +63,9 @@ type
13 of kInt: i*: int6463 of kInt: i*: int64
14 of kFloat: f*: float6464 of kFloat: f*: float64
15 of kStr, kKeyword, kSymbol: s*: string65 of kStr, kKeyword, kSymbol: s*: string
16- of kList, kVector, kSet: items*: seq[Value]66+ of kList: xs*: seq[Value]
17- of kMap: pairs*: seq[(Value, Value)]67+ of kVector: vec*: PVec
68+ of kMap, kSet: m*: PMap
18 of kFn:69 of kFn:
19 fn*: proc (args: seq[Value]): Value {.closure.}70 fn*: proc (args: seq[Value]): Value {.closure.}
20 name*: string71 name*: string
@@ -25,19 +76,337 @@ let NilV* = Value(kind: kNil)
25 let TrueV* = Value(kind: kBool, b: true)76 let TrueV* = Value(kind: kBool, b: true)
26 let FalseV* = Value(kind: kBool, b: false)77 let FalseV* = Value(kind: kBool, b: false)
27 78
79+proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg)
80+
81+proc equals*(a, b: Value): bool
82+proc hashValue*(v: Value): uint32
83+proc prStr*(v: Value): string
84+
85+# ------------------------------------------------------- persistent vector
86+let emptyVNode = VNode(leaf: false, kids: @[])
87+
88+proc emptyPVec*(): PVec = PVec(cnt: 0, shift: Bits, root: emptyVNode, tail: @[])
89+
90+proc tailOff(v: PVec): int =
91+ if v.cnt < Width: 0 else: ((v.cnt - 1) shr Bits) shl Bits
92+
93+proc leafFor(v: PVec, i: int): seq[Value] =
94+ if i >= tailOff(v): return v.tail
95+ var node = v.root
96+ var level = v.shift
97+ while level > 0:
98+ node = node.kids[(i shr level) and Mask]
99+ level -= Bits
100+ node.vals
101+
102+proc vecNth*(v: PVec, i: int): Value =
103+ if i < 0 or i >= v.cnt: err("Index out of bounds: " & $i)
104+ leafFor(v, i)[i and Mask]
105+
106+proc newPath(level: int, node: VNode): VNode =
107+ if level == 0: node
108+ else: VNode(leaf: false, kids: @[newPath(level - Bits, node)])
109+
110+proc pushTail(cnt, level: int, parent, tailNode: VNode): VNode =
111+ let subIdx = ((cnt - 1) shr level) and Mask
112+ var kids = parent.kids
113+ let child =
114+ if level == Bits: tailNode
115+ elif subIdx < kids.len: pushTail(cnt, level - Bits, kids[subIdx], tailNode)
116+ else: newPath(level - Bits, tailNode)
117+ if subIdx < kids.len: kids[subIdx] = child
118+ else: kids.add child
119+ VNode(leaf: false, kids: kids)
120+
121+proc vecConj*(v: PVec, x: Value): PVec =
122+ if v.tail.len < Width:
123+ return PVec(cnt: v.cnt + 1, shift: v.shift, root: v.root, tail: v.tail & x)
124+ let tailNode = VNode(leaf: true, vals: v.tail)
125+ var shift = v.shift
126+ var root: VNode
127+ if (v.cnt shr Bits) > (1 shl v.shift): # root overflow: grow a level
128+ root = VNode(leaf: false, kids: @[v.root, newPath(v.shift, tailNode)])
129+ shift += Bits
130+ else:
131+ root = pushTail(v.cnt, v.shift, v.root, tailNode)
132+ PVec(cnt: v.cnt + 1, shift: shift, root: root, tail: @[x])
133+
134+proc doAssoc(level: int, node: VNode, i: int, x: Value): VNode =
135+ if level == 0:
136+ var vals = node.vals
137+ vals[i and Mask] = x
138+ VNode(leaf: true, vals: vals)
139+ else:
140+ var kids = node.kids
141+ let sub = (i shr level) and Mask
142+ kids[sub] = doAssoc(level - Bits, kids[sub], i, x)
143+ VNode(leaf: false, kids: kids)
144+
145+proc vecAssoc*(v: PVec, i: int, x: Value): PVec =
146+ if i == v.cnt: return vecConj(v, x)
147+ if i < 0 or i > v.cnt: err("Index out of bounds: " & $i)
148+ if i >= tailOff(v):
149+ var tail = v.tail
150+ tail[i - tailOff(v)] = x
151+ return PVec(cnt: v.cnt, shift: v.shift, root: v.root, tail: tail)
152+ PVec(cnt: v.cnt, shift: v.shift, root: doAssoc(v.shift, v.root, i, x),
153+ tail: v.tail)
154+
155+proc vecToSeq*(v: PVec): seq[Value] =
156+ result = newSeqOfCap[Value](v.cnt)
157+ var i = 0
158+ while i < v.cnt:
159+ let leaf = leafFor(v, i)
160+ let base = i and not Mask
161+ for j in 0 ..< leaf.len:
162+ if base + j >= v.cnt: break
163+ result.add leaf[j]
164+ i = base + leaf.len
165+
166+proc toPVec*(xs: seq[Value]): PVec =
167+ result = emptyPVec()
168+ for x in xs: result = vecConj(result, x)
169+
170+# ----------------------------------------------------------------- hashing
171+proc mixHash(a, b: uint32): uint32 =
172+ ## Boost-style combine; cheap and good enough for trie index bits.
173+ a xor (b + 0x9e3779b9'u32 + (a shl 6) + (a shr 2))
174+
175+# ---------------------------------------------------------- persistent map
176+proc bitPos(h: uint32, shift: int): uint32 = 1'u32 shl ((h shr shift) and Mask)
177+proc slotIdx(bitmap, bit: uint32): int = countSetBits(bitmap and (bit - 1))
178+
179+proc emptyPMap*(): PMap = PMap(root: nil, cnt: 0, nextOrd: 0)
180+
181+proc mergeEntries(shift: int, h1: uint32, e1: MEntry,
182+ h2: uint32, e2: MEntry): MNode =
183+ if shift > MaxShift:
184+ return MNode(collision: true, kvs: @[e1, e2])
185+ let b1 = bitPos(h1, shift)
186+ let b2 = bitPos(h2, shift)
187+ if b1 == b2:
188+ MNode(collision: false, bitmap: b1,
189+ slots: @[MSlot(sk: msNode,
190+ node: mergeEntries(shift + Bits, h1, e1, h2, e2))])
191+ elif b1 < b2:
192+ MNode(collision: false, bitmap: b1 or b2,
193+ slots: @[MSlot(sk: msEntry, e: e1), MSlot(sk: msEntry, e: e2)])
194+ else:
195+ MNode(collision: false, bitmap: b1 or b2,
196+ slots: @[MSlot(sk: msEntry, e: e2), MSlot(sk: msEntry, e: e1)])
197+
198+proc nodeAssoc(n: MNode, shift: int, h: uint32, k, v: Value, newOrd: int,
199+ added: var bool): MNode =
200+ if n.collision:
201+ for i in 0 ..< n.kvs.len:
202+ if equals(n.kvs[i].key, k):
203+ var kvs = n.kvs
204+ kvs[i].val = v
205+ return MNode(collision: true, kvs: kvs)
206+ added = true
207+ return MNode(collision: true,
208+ kvs: n.kvs & MEntry(key: k, val: v, ord: newOrd))
209+ let bit = bitPos(h, shift)
210+ let idx = slotIdx(n.bitmap, bit)
211+ var slots = n.slots
212+ if (n.bitmap and bit) != 0:
213+ case slots[idx].sk
214+ of msNode:
215+ slots[idx] = MSlot(sk: msNode,
216+ node: nodeAssoc(slots[idx].node, shift + Bits, h, k, v, newOrd, added))
217+ of msEntry:
218+ let e = slots[idx].e
219+ if equals(e.key, k):
220+ slots[idx] = MSlot(sk: msEntry, e: MEntry(key: k, val: v, ord: e.ord))
221+ else:
222+ added = true
223+ slots[idx] = MSlot(sk: msNode,
224+ node: mergeEntries(shift + Bits, hashValue(e.key), e, h,
225+ MEntry(key: k, val: v, ord: newOrd)))
226+ return MNode(collision: false, bitmap: n.bitmap, slots: slots)
227+ added = true
228+ slots.insert(MSlot(sk: msEntry, e: MEntry(key: k, val: v, ord: newOrd)), idx)
229+ MNode(collision: false, bitmap: n.bitmap or bit, slots: slots)
230+
231+proc mapAssoc*(m: PMap, k, v: Value): PMap =
232+ let h = hashValue(k)
233+ var added = false
234+ if m.root.isNil:
235+ return PMap(root: MNode(collision: false, bitmap: bitPos(h, 0),
236+ slots: @[MSlot(sk: msEntry,
237+ e: MEntry(key: k, val: v, ord: 0))]),
238+ cnt: 1, nextOrd: 1)
239+ let root = nodeAssoc(m.root, 0, h, k, v, m.nextOrd, added)
240+ PMap(root: root, cnt: m.cnt + (if added: 1 else: 0),
241+ nextOrd: m.nextOrd + (if added: 1 else: 0))
242+
243+proc nodeFind(n: MNode, shift: int, h: uint32, k: Value,
244+ found: var bool): Value =
245+ if n.isNil: return NilV
246+ if n.collision:
247+ for e in n.kvs:
248+ if equals(e.key, k):
249+ found = true
250+ return e.val
251+ return NilV
252+ let bit = bitPos(h, shift)
253+ if (n.bitmap and bit) == 0: return NilV
254+ let slot = n.slots[slotIdx(n.bitmap, bit)]
255+ case slot.sk
256+ of msNode: nodeFind(slot.node, shift + Bits, h, k, found)
257+ of msEntry:
258+ if equals(slot.e.key, k):
259+ found = true
260+ slot.e.val
261+ else: NilV
262+
263+proc mapGet*(m: PMap, k: Value, dflt: Value): Value =
264+ var found = false
265+ let v = nodeFind(m.root, 0, hashValue(k), k, found)
266+ if found: v else: dflt
267+
268+proc mapContains*(m: PMap, k: Value): bool =
269+ var found = false
270+ discard nodeFind(m.root, 0, hashValue(k), k, found)
271+ found
272+
273+proc nodeDissoc(n: MNode, shift: int, h: uint32, k: Value,
274+ removed: var bool): MNode =
275+ if n.collision:
276+ var kvs: seq[MEntry] = @[]
277+ for e in n.kvs:
278+ if equals(e.key, k): removed = true
279+ else: kvs.add e
280+ return (if kvs.len == 0: nil else: MNode(collision: true, kvs: kvs))
281+ let bit = bitPos(h, shift)
282+ if (n.bitmap and bit) == 0: return n
283+ let idx = slotIdx(n.bitmap, bit)
284+ var slots = n.slots
285+ case slots[idx].sk
286+ of msNode:
287+ let child = nodeDissoc(slots[idx].node, shift + Bits, h, k, removed)
288+ if child.isNil:
289+ slots.delete(idx)
290+ return (if slots.len == 0: nil
291+ else: MNode(collision: false, bitmap: n.bitmap and not bit,
292+ slots: slots))
293+ slots[idx] = MSlot(sk: msNode, node: child)
294+ MNode(collision: false, bitmap: n.bitmap, slots: slots)
295+ of msEntry:
296+ if not equals(slots[idx].e.key, k): return n
297+ removed = true
298+ slots.delete(idx)
299+ if slots.len == 0: nil
300+ else: MNode(collision: false, bitmap: n.bitmap and not bit, slots: slots)
301+
302+proc mapDissoc*(m: PMap, k: Value): PMap =
303+ if m.root.isNil: return m
304+ var removed = false
305+ let root = nodeDissoc(m.root, 0, hashValue(k), k, removed)
306+ if not removed: return m
307+ PMap(root: root, cnt: m.cnt - 1, nextOrd: m.nextOrd)
308+
309+proc collect(n: MNode, acc: var seq[MEntry]) =
310+ if n.isNil: return
311+ if n.collision:
312+ for e in n.kvs: acc.add e
313+ return
314+ for slot in n.slots:
315+ case slot.sk
316+ of msEntry: acc.add slot.e
317+ of msNode: collect(slot.node, acc)
318+
319+proc mapEntries*(m: PMap): seq[MEntry] =
320+ ## Entries in insertion order.
321+ result = newSeqOfCap[MEntry](m.cnt)
322+ collect(m.root, result)
323+ result.sort(proc (a, b: MEntry): int = cmp(a.ord, b.ord))
324+
325+proc hashValue*(v: Value): uint32 =
326+ if v.isNil: return 0
327+ case v.kind
328+ of kNil: 0'u32
329+ of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32)
330+ of kInt: uint32(hash(v.i))
331+ of kFloat:
332+ # ints and floats compare equal across kinds, so they must hash alike
333+ if v.f == float64(int64(v.f)): uint32(hash(int64(v.f)))
334+ else: uint32(hash(v.f))
335+ of kStr: mixHash(1'u32, uint32(hash(v.s)))
336+ of kKeyword: mixHash(2'u32, uint32(hash(v.s)))
337+ of kSymbol: mixHash(3'u32, uint32(hash(v.s)))
338+ of kList, kVector:
339+ # lists and vectors are `=` when their elements are, so they hash alike
340+ var h = 7'u32
341+ for x in (if v.kind == kList: v.xs else: vecToSeq(v.vec)):
342+ h = mixHash(h, hashValue(x))
343+ h
344+ of kSet:
345+ var h = 0'u32 # xor: independent of iteration order
346+ for e in mapEntries(v.m): h = h xor hashValue(e.key)
347+ h
348+ of kMap:
349+ var h = 0'u32
350+ for e in mapEntries(v.m):
351+ h = h xor mixHash(hashValue(e.key), hashValue(e.val))
352+ h
353+ of kFn: uint32(hash(cast[int](cast[pointer](v))))
354+
355+# ------------------------------------------------------------ constructors
28 proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV)356 proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV)
29 proc mkInt*(x: int64): Value = Value(kind: kInt, i: x)357 proc mkInt*(x: int64): Value = Value(kind: kInt, i: x)
30 proc mkFloat*(x: float64): Value = Value(kind: kFloat, f: x)358 proc mkFloat*(x: float64): Value = Value(kind: kFloat, f: x)
31 proc mkStr*(x: string): Value = Value(kind: kStr, s: x)359 proc mkStr*(x: string): Value = Value(kind: kStr, s: x)
32 proc mkKeyword*(x: string): Value = Value(kind: kKeyword, s: x)360 proc mkKeyword*(x: string): Value = Value(kind: kKeyword, s: x)
33 proc mkSymbol*(x: string): Value = Value(kind: kSymbol, s: x)361 proc mkSymbol*(x: string): Value = Value(kind: kSymbol, s: x)
34-proc mkList*(xs: seq[Value]): Value = Value(kind: kList, items: xs)362+proc mkList*(xs: seq[Value]): Value = Value(kind: kList, xs: xs)
35-proc mkVector*(xs: seq[Value]): Value = Value(kind: kVector, items: xs)363+proc mkVector*(xs: seq[Value]): Value = Value(kind: kVector, vec: toPVec(xs))
36-proc mkSet*(xs: seq[Value]): Value364+proc mkVec*(v: PVec): Value = Value(kind: kVector, vec: v)
365+proc mkMapOf*(m: PMap): Value = Value(kind: kMap, m: m)
366+proc mkSetOf*(m: PMap): Value = Value(kind: kSet, m: m)
367+
368+proc mkMap*(ps: seq[(Value, Value)]): Value =
369+ var m = emptyPMap()
370+ for (k, v) in ps: m = mapAssoc(m, k, v)
371+ Value(kind: kMap, m: m)
372+
373+proc mkSet*(xs: seq[Value]): Value =
374+ var m = emptyPMap()
375+ for x in xs:
376+ if not mapContains(m, x): m = mapAssoc(m, x, x)
377+ Value(kind: kSet, m: m)
378+
37 proc mkFn*(name: string, f: proc (args: seq[Value]): Value {.closure.}): Value =379 proc mkFn*(name: string, f: proc (args: seq[Value]): Value {.closure.}): Value =
38 Value(kind: kFn, fn: f, name: name)380 Value(kind: kFn, fn: f, name: name)
39 381
40-proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg)382+# --------------------------------------------------------------- accessors
383+proc items*(v: Value): seq[Value] =
384+ ## Elements of any sequential value, in order. O(n) — prefer `count`/`nth`
385+ ## when you only need one element.
386+ if v.isNil: return @[]
387+ case v.kind
388+ of kList: v.xs
389+ of kVector: vecToSeq(v.vec)
390+ of kSet:
391+ var r = newSeqOfCap[Value](v.m.cnt)
392+ for e in mapEntries(v.m): r.add e.key
393+ r
394+ else: @[]
395+
396+proc pairs*(v: Value): seq[(Value, Value)] =
397+ if v.isNil or v.kind != kMap: return @[]
398+ result = newSeqOfCap[(Value, Value)](v.m.cnt)
399+ for e in mapEntries(v.m): result.add (e.key, e.val)
400+
401+proc count*(v: Value): int =
402+ if v.isNil: return 0
403+ case v.kind
404+ of kNil: 0
405+ of kList: v.xs.len
406+ of kVector: v.vec.cnt
407+ of kMap, kSet: v.m.cnt
408+ of kStr: v.s.len
409+ else: err("Don't know how to count: " & prStr(v))
41 410
42 proc truthy*(v: Value): bool =411 proc truthy*(v: Value): bool =
43 if v == nil: return false412 if v == nil: return false
@@ -54,9 +423,11 @@ proc equals*(a, b: Value): bool =
54 if a.kind == kFloat and b.kind == kInt: return a.f == float64(b.i)423 if a.kind == kFloat and b.kind == kInt: return a.f == float64(b.i)
55 # lists and vectors are sequentially equal in Clojure424 # lists and vectors are sequentially equal in Clojure
56 if a.kind in {kList, kVector} and b.kind in {kList, kVector}:425 if a.kind in {kList, kVector} and b.kind in {kList, kVector}:
57- if a.items.len != b.items.len: return false426+ if count(a) != count(b): return false
58- for i in 0 ..< a.items.len:427+ let xs = items(a)
59- if not equals(a.items[i], b.items[i]): return false428+ let ys = items(b)
429+ for i in 0 ..< xs.len:
430+ if not equals(xs[i], ys[i]): return false
60 return true431 return true
61 if a.kind != b.kind: return false432 if a.kind != b.kind: return false
62 case a.kind433 case a.kind
@@ -66,35 +437,18 @@ proc equals*(a, b: Value): bool =
66 of kFloat: a.f == b.f437 of kFloat: a.f == b.f
67 of kStr, kKeyword, kSymbol: a.s == b.s438 of kStr, kKeyword, kSymbol: a.s == b.s
68 of kSet:439 of kSet:
69- if a.items.len != b.items.len: return false440+ if a.m.cnt != b.m.cnt: return false
70- for x in a.items:441+ for e in mapEntries(a.m):
71- var found = false442+ if not mapContains(b.m, e.key): return false
72- for y in b.items:
73- if equals(x, y): found = true; break
74- if not found: return false
75 true443 true
76 of kMap:444 of kMap:
77- if a.pairs.len != b.pairs.len: return false445+ if a.m.cnt != b.m.cnt: return false
78- for (k, v) in a.pairs:446+ let missing = Value(kind: kKeyword, s: "%clonim-missing")
79- var found = false447+ for e in mapEntries(a.m):
80- for (k2, v2) in b.pairs:448+ if not equals(e.val, mapGet(b.m, e.key, missing)): return false
81- if equals(k, k2):
82- if not equals(v, v2): return false
83- found = true; break
84- if not found: return false
85 true449 true
86 of kFn: a == b450 of kFn: a == b
87 of kList, kVector: false # handled above451 of kList, kVector: false # handled above
88-
89-proc mkSet*(xs: seq[Value]): Value =
90- var acc: seq[Value] = @[]
91- for x in xs:
92- var dup = false
93- for y in acc:
94- if equals(x, y): dup = true; break
95- if not dup: acc.add x
96- Value(kind: kSet, items: acc)
97-
98 # ---------------------------------------------------------------- printing452 # ---------------------------------------------------------------- printing
99 proc escapeStr(s: string): string =453 proc escapeStr(s: string): string =
100 result = "\""454 result = "\""
@@ -184,19 +538,13 @@ proc call*(f: Value, args: seq[Value]): Value =
184 if args.len == 0: err("Wrong number of args to keyword")538 if args.len == 0: err("Wrong number of args to keyword")
185 let m = args[0]539 let m = args[0]
186 if m.isNil or m.kind != kMap: return NilV540 if m.isNil or m.kind != kMap: return NilV
187- for (k, v) in m.pairs:541+ mapGet(m.m, f, (if args.len > 1: args[1] else: NilV))
188- if equals(k, f): return v
189- (if args.len > 1: args[1] else: NilV)
190 of kMap:542 of kMap:
191 if args.len == 0: err("Wrong number of args to map")543 if args.len == 0: err("Wrong number of args to map")
192- for (k, v) in f.pairs:544+ mapGet(f.m, args[0], (if args.len > 1: args[1] else: NilV))
193- if equals(k, args[0]): return v
194- (if args.len > 1: args[1] else: NilV)
195 of kVector:545 of kVector:
196 if args.len != 1 or args[0].kind != kInt: err("Vector lookup needs one int")546 if args.len != 1 or args[0].kind != kInt: err("Vector lookup needs one int")
197- let i = int(args[0].i)547+ vecNth(f.vec, int(args[0].i))
198- if i < 0 or i >= f.items.len: err("Index out of bounds: " & $i)
199- f.items[i]
200 else: err("Can't call value of kind " & $f.kind & ": " & prStr(f))548 else: err("Can't call value of kind " & $f.kind & ": " & prStr(f))
201 549
202 proc argAt*(args: seq[Value], i: int): Value =550 proc argAt*(args: seq[Value], i: int): Value =
@@ -222,9 +570,8 @@ proc toSeq*(v: Value): seq[Value] =
222 r570 r
223 of kMap:571 of kMap:
224 var r: seq[Value] = @[]572 var r: seq[Value] = @[]
225- for (k, val) in v.pairs: r.add mkVector(@[k, val])573+ for e in mapEntries(v.m): r.add mkVector(@[e.key, e.val])
226 r574 r
227 else: err("Don't know how to create seq from: " & prStr(v))575 else: err("Don't know how to create seq from: " & prStr(v))
228 576
229-proc mkMap*(ps: seq[(Value, Value)]): Value = Value(kind: kMap, pairs: ps)
230 let emptyArgs*: seq[Value] = @[]577 let emptyArgs*: seq[Value] = @[]
added tests/persistent.expected +23 -0
new file mode 100644
@@ -0,0 +1,23 @@
1+count 5000
2+nth 0 33 1024 4999
3+sum 12497500
4+assoc :x 1234 5000
5+last 4999 0
6+eq true
7+mcount 5000 0 24990001 nil
8+dissoc 4999 nil 10000
9+keys-sum 12497500
10+vals-sum 41654167500
11+meq true
12+scount 5000 true false
13+sconj 5001 5000
14+{:b 1, :a 2, c 3, 4 5, [1 2] 6}
15+(:b :a c 4 [1 2])
16+{:b 1, :a 99, c 3, 4 5, [1 2] 6}
17+6 5 3
18+true
19+true
20+:int :flt
21+{1 2, 2 1, 3 3}
22+{true [0 2 4 6 8], false [1 3 5 7 9]}
23+{:a 2}
new file mode 100644
@@ -0,0 +1,23 @@
1+count 5000
2+nth 0 33 1024 4999
3+sum 12497500
4+assoc :x 1234 5000
5+last 4999 0
6+eq true
7+mcount 5000 0 24990001 nil
8+dissoc 4999 nil 10000
9+keys-sum 12497500
10+vals-sum 41654167500
11+meq true
12+scount 5000 true false
13+sconj 5001 5000
14+{:b 1, :a 2, c 3, 4 5, [1 2] 6}
15+(:b :a c 4 [1 2])
16+{:b 1, :a 99, c 3, 4 5, [1 2] 6}
17+6 5 3
18+true
19+true
20+:int :flt
21+{1 2, 2 1, 3 3}
22+{true [0 2 4 6 8], false [1 3 5 7 9]}
23+{:a 2}