Phase 6: slurp, spit, read-line, range 3-arg, repeat/cycle/iterate, interleave

Phase 6 Clojure Core Library now complete (except meta):
- T6.3: slurp (alias for file/read)
- T6.4: spit (alias for file/write)
- T6.5: read-line (stdin input)
- T6.6: range with 3 args (start, end, step)
- T6.7: repeat, cycle, iterate (eager implementations)
- T6.9: interleave
- Updated TASKS.md and ROADMAP.md
This commit is contained in:
2026-05-08 19:24:06 +03:00
parent 6dfc2a9308
commit 19ef69bf5b
4 changed files with 113 additions and 15 deletions
+9 -9
View File
@@ -43,15 +43,15 @@
| ID | Task | Status | Complexity | Files | Acceptance Criteria | | 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.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]"`. Escapes strings properly. | | 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. Already have `file/read` — just alias/wrap. | | 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. | | 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.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.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` | Infinite lazy sequences. | | 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.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.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.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. | | T6.11 | `type`, `instance?` | ⬜ | 🟢 | `lib/cljnim_runtime.nim` | `(type 42)``:int`. `(instance? :int 42)` → true. |
+2 -2
View File
@@ -53,10 +53,10 @@
- [ ] Transients for batch mutations - [ ] Transients for batch mutations
## Phase 6: Clojure Core Library ## 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] `take`, `drop`, `partition`, `interleave`, `concat`
- [x] `str`, `pr-str`, `println`, `prn` - [x] `str`, `pr-str`, `println`, `prn`
- [ ] `slurp`, `spit`, `read-line` - [x] `slurp`, `spit`, `read-line`
- [ ] `meta`, `with-meta`, `vary-meta` - [ ] `meta`, `with-meta`, `vary-meta`
- [x] `type`, `instance?`, `satisfies?` - [x] `type`, `instance?`, `satisfies?`
+74
View File
@@ -1054,6 +1054,80 @@ proc cljRange*(start, finish: CljVal): CljVal =
items.add(cljInt(i)) items.add(cljInt(i))
cljList(items) 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..<n.intVal:
items.add(x)
cljList(items)
proc cljCycle*(n: CljVal, coll: CljVal): CljVal =
if n.kind != ckInt: raise newException(CatchableError, "cycle requires an integer count")
var srcItems: seq[CljVal] = @[]
case coll.kind
of ckList: srcItems = coll.listItems
of ckVector: srcItems = toSeq(coll.vecData)
else: return cljList(@[])
if srcItems.len == 0: return cljList(@[])
var items: seq[CljVal] = @[]
for i in 0..<n.intVal:
items.add(srcItems[i mod srcItems.len])
cljList(items)
proc cljIterate*(n: CljVal, f: CljVal, x: CljVal): CljVal =
if n.kind != ckInt: raise newException(CatchableError, "iterate requires an integer count")
if f.kind != ckFn: raise newException(CatchableError, "iterate requires a function")
var items: seq[CljVal] = @[x]
var current = x
for i in 1..<n.intVal:
current = f.fnProc(@[current])
items.add(current)
cljList(items)
proc cljInterleave*(args: seq[CljVal]): CljVal =
if args.len < 2: raise newException(CatchableError, "interleave requires at least 2 arguments")
var seqs: seq[seq[CljVal]] = @[]
for a in args:
case a.kind
of ckList: seqs.add(a.listItems)
of ckVector: seqs.add(toSeq(a.vecData))
else: seqs.add(@[])
var minLen = seqs[0].len
for s in seqs:
if s.len < minLen: minLen = s.len
var items: seq[CljVal] = @[]
for i in 0..<minLen:
for s in seqs:
items.add(s[i])
cljList(items)
proc cljReadLine*(): CljVal =
try:
var line = ""
if readLine(stdin, line):
cljString(line)
else:
cljNil()
except EOFError:
cljNil()
# ---- File Operations ---- # ---- File Operations ----
proc cljFileRead*(path: CljVal): CljVal = proc cljFileRead*(path: CljVal): CljVal =
+28 -4
View File
@@ -109,6 +109,14 @@ proc runtimeName(op: string): string =
of "git/diff": "cljGitDiff" of "git/diff": "cljGitDiff"
of "git/log": "cljGitLog" of "git/log": "cljGitLog"
of "disj": "cljDisj" of "disj": "cljDisj"
of "slurp": "cljFileRead"
of "spit": "cljFileWrite"
of "read-line": "cljReadLine"
of "repeat": "cljRepeat"
of "cycle": "cljCycle"
of "iterate": "cljIterate"
of "interleave": "cljInterleave"
of "quot": "cljQuot"
else: "" else: ""
proc emitArgs(args: seq[CljVal]): string = proc emitArgs(args: seq[CljVal]): string =
@@ -779,14 +787,30 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return sp & "cljSet(@[" & emitExpr(arg, 0) & "])" return sp & "cljSet(@[" & emitExpr(arg, 0) & "])"
of "range": of "range":
if items.len < 1 or items.len > 3: if items.len < 1 or items.len > 4:
raise newException(EmitterError, "range requires 0, 1, or 2 arguments") raise newException(EmitterError, "range requires 0, 1, 2, or 3 arguments")
if items.len == 1: if items.len == 1:
return sp & "cljList(@[])" return sp & "cljList(@[])"
elif items.len == 2: elif items.len == 2:
return sp & "cljRange(" & emitExpr(items[1], 0) & ")" return sp & "cljRange(" & emitExpr(items[1], 0) & ")"
else: elif items.len == 3:
return sp & "cljRange(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" 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: else:
# ---- Nim interop: nim/module/function ---- # ---- Nim interop: nim/module/function ----
@@ -882,7 +906,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
# Variadic functions take seq[CljVal] # Variadic functions take seq[CljVal]
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
"println", "prn", "print", "str", "pr-str", "println", "prn", "print", "str", "pr-str",
"concat", "min", "max", "merge"] "concat", "min", "max", "merge", "interleave"]
var call: string var call: string
if variadic and argParts.len > 0: if variadic and argParts.len > 0:
call = rn & "(@[" & argParts.join(", ") & "])" call = rn & "(@[" & argParts.join(", ") & "])"