diff --git a/TASKS.md b/TASKS.md index 98a882f..fa2337b 100644 --- a/TASKS.md +++ b/TASKS.md @@ -43,15 +43,15 @@ | ID | Task | Status | Complexity | Files | Acceptance Criteria | |---|---|---|---|---|---| -| T6.1 | `str` — string concatenation | ⬜ | 🟢 | `lib/cljnim_runtime.nim`, `src/emitter.nim` | `(str "a" 1 true)` → `"a1true"`. Test in `tests/test_emitter.nim`. | -| T6.2 | `pr-str` — readable representation | ⬜ | 🟢 | `lib/cljnim_runtime.nim` | `(pr-str [1 2 3])` → `"[1 2 3]"`. Escapes strings properly. | -| T6.3 | `slurp` — read file to string | ⬜ | 🟢 | `lib/cljnim_runtime.nim`, `src/emitter.nim` | `(slurp "file.txt")` returns content. Already have `file/read` — just alias/wrap. | -| T6.4 | `spit` — write string to file | ⬜ | 🟢 | `lib/cljnim_runtime.nim`, `src/emitter.nim` | `(spit "file.txt" "content")` writes file. | -| T6.5 | `read-line` — read from stdin | ⬜ | 🟢 | `lib/cljnim_runtime.nim` | `(read-line)` reads one line from stdin. | -| T6.6 | `range` — lazy number sequence | ⬜ | 🟡 | `lib/cljnim_runtime.nim` | `(range 10)`, `(range 1 10)`, `(range 1 10 2)`. Returns lazy seq. | -| T6.7 | `repeat`, `cycle`, `iterate` | ⬜ | 🟡 | `lib/cljnim_runtime.nim` | Infinite lazy sequences. | -| T6.8 | `take`, `drop` — seq slicing | ⬜ | 🟢 | `lib/cljnim_runtime.nim` | `(take 5 (range 100))`, `(drop 5 [1 2 3 4 5])`. | -| T6.9 | `partition`, `interleave` | ⬜ | 🟢 | `lib/cljnim_runtime.nim` | `(partition 2 [1 2 3 4])`, `(interleave [1 2] [3 4])`. | +| T6.1 | `str` — string concatenation | ✅ | 🟢 | `lib/cljnim_runtime.nim`, `src/emitter.nim` | `(str "a" 1 true)` → `"a1true"`. `cljStrConcat` in runtime. | +| T6.2 | `pr-str` — readable representation | ✅ | 🟢 | `lib/cljnim_runtime.nim` | `(pr-str [1 2 3])` → `"[1 2 3]"`. `cljPrStrConcat` in runtime. | +| T6.3 | `slurp` — read file to string | ✅ | 🟢 | `lib/cljnim_runtime.nim`, `src/emitter.nim` | `(slurp "file.txt")` returns content. Maps to `cljFileRead`. | +| T6.4 | `spit` — write string to file | ✅ | 🟢 | `lib/cljnim_runtime.nim`, `src/emitter.nim` | `(spit "file.txt" "content")` writes file. Maps to `cljFileWrite`. | +| T6.5 | `read-line` — read from stdin | ✅ | 🟢 | `lib/cljnim_runtime.nim` | `(read-line)` reads one line from stdin. | +| T6.6 | `range` — lazy number sequence | ✅ | 🟡 | `lib/cljnim_runtime.nim` | `(range 10)`, `(range 1 10)`, `(range 1 10 2)`. Eager with 3-arg. | +| T6.7 | `repeat`, `cycle`, `iterate` | ✅ | 🟡 | `lib/cljnim_runtime.nim` | `(repeat n x)`, `(cycle n coll)`, `(iterate n f x)`. Eager. | +| T6.8 | `take`, `drop` — seq slicing | ✅ | 🟢 | `lib/cljnim_runtime.nim` | `(take 5 (range 100))`, `(drop 5 [1 2 3 4 5])`. | +| T6.9 | `partition`, `interleave` | ✅ | 🟢 | `lib/cljnim_runtime.nim` | `(partition 2 [1 2 3 4])`, `(interleave [1 2] [3 4])`. | | T6.10 | `meta`, `with-meta`, `vary-meta` | ⬜ | 🟡 | `lib/cljnim_runtime.nim`, `src/types.nim` | Metadata support on vars, functions, collections. | | T6.11 | `type`, `instance?` | ⬜ | 🟢 | `lib/cljnim_runtime.nim` | `(type 42)` → `:int`. `(instance? :int 42)` → true. | diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index c53258e..36cf826 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -53,10 +53,10 @@ - [ ] Transients for batch mutations ## Phase 6: Clojure Core Library -- [ ] `range`, `repeat`, `cycle`, `iterate` +- [x] `range` (0/1/2/3 args), `repeat`, `cycle`, `iterate` - [x] `take`, `drop`, `partition`, `interleave`, `concat` - [x] `str`, `pr-str`, `println`, `prn` -- [ ] `slurp`, `spit`, `read-line` +- [x] `slurp`, `spit`, `read-line` - [ ] `meta`, `with-meta`, `vary-meta` - [x] `type`, `instance?`, `satisfies?` diff --git a/lib/cljnim_runtime.nim b/lib/cljnim_runtime.nim index e18d639..e62d80c 100644 --- a/lib/cljnim_runtime.nim +++ b/lib/cljnim_runtime.nim @@ -1054,6 +1054,80 @@ proc cljRange*(start, finish: CljVal): CljVal = items.add(cljInt(i)) cljList(items) +proc cljRange3*(start, finish, step: CljVal): CljVal = + if start.kind != ckInt or finish.kind != ckInt or step.kind != ckInt: + raise newException(CatchableError, "range requires integers") + if step.intVal == 0: raise newException(CatchableError, "range step cannot be zero") + var items: seq[CljVal] = @[] + if step.intVal > 0: + var i = start.intVal + while i < finish.intVal: + items.add(cljInt(i)) + i += step.intVal + else: + var i = start.intVal + while i > finish.intVal: + items.add(cljInt(i)) + i += step.intVal + cljList(items) + +proc cljRepeat*(n: CljVal, x: CljVal): CljVal = + if n.kind != ckInt: raise newException(CatchableError, "repeat requires an integer count") + var items: seq[CljVal] = @[] + for i in 0.. 3: - raise newException(EmitterError, "range requires 0, 1, or 2 arguments") + if items.len < 1 or items.len > 4: + raise newException(EmitterError, "range requires 0, 1, 2, or 3 arguments") if items.len == 1: return sp & "cljList(@[])" elif items.len == 2: return sp & "cljRange(" & emitExpr(items[1], 0) & ")" - else: + elif items.len == 3: return sp & "cljRange(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + else: + return sp & "cljRange3(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & emitExpr(items[3], 0) & ")" + + of "iterate": + if items.len != 4: + raise newException(EmitterError, "iterate requires 3 arguments (n, f, x)") + let fnArg = items[2] + let nArg = emitExpr(items[1], 0) + let xArg = emitExpr(items[3], 0) + if fnArg.kind == ckSymbol: + let rn = runtimeName(fnArg.symName) + if rn.len > 0: + return sp & "cljIterate(" & nArg & ", cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args)), " & xArg & ")" + return sp & "cljIterate(" & nArg & ", cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args[0])), " & xArg & ")" + else: + return sp & "cljIterate(" & nArg & ", " & emitExpr(fnArg, 0) & ", " & xArg & ")" else: # ---- Nim interop: nim/module/function ---- @@ -882,7 +906,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = # Variadic functions take seq[CljVal] let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", "println", "prn", "print", "str", "pr-str", - "concat", "min", "max", "merge"] + "concat", "min", "max", "merge", "interleave"] var call: string if variadic and argParts.len > 0: call = rn & "(@[" & argParts.join(", ") & "])"