feat: add fullscreen TUI and project updates

- New TUI screens: Main Menu, Compile, Run, REPL, AI Generator, AI Settings, Help
- AI configuration persisted in ~/.config/cljnim/config.json
- Added illwill dependency for terminal UI
- Updated experiments, examples, docs, and core modules
This commit is contained in:
2026-05-09 01:53:23 +03:00
parent 2bc64eca22
commit ba0b300917
42 changed files with 3042 additions and 153 deletions
-11
View File
@@ -99,17 +99,6 @@ proc pushLeaf[T](node: PVecNode[T], index: int, shift: int, val: seq[T]): PVecNo
result.children[childIdx] = child
proc newPath[T](shift: int, leaf: PVecNode[T]): PVecNode[T] =
# Create a path of empty internal nodes from shift down to leaf level
if shift == 0:
return leaf
var node = newInternalNode[T](@[leaf])
var level = BRANCHING_BITS
while level < shift:
node = newInternalNode[T](@[node])
level += BRANCHING_BITS
return node
proc pvecConj*[T](v: PersistentVector[T], val: T): PersistentVector[T] =
result = v
result.count += 1
+39 -39
View File
@@ -13,7 +13,7 @@ type
CljVal* = ref CljValObj
CljValObj = object
meta*: CljVal # metadata attached to this value
meta*: CljVal
case kind*: CljKind
of ckNil: discard
of ckBool: boolVal*: bool
@@ -29,9 +29,9 @@ type
of ckFn: fnProc*: proc(args: seq[CljVal]): CljVal
of ckAtom: atomVal*: CljVal
of ckTransient:
transKind*: CljKind # ckVector or ckMap — what this transient was created from
transVec*: seq[CljVal] # mutable vector builder
transPairs*: seq[(CljVal, CljVal)] # mutable map builder
transKind*: CljKind
transVec*: seq[CljVal]
transPairs*: seq[(CljVal, CljVal)]
of ckAgent:
agentVal*: CljVal
agentLock*: Lock
@@ -601,7 +601,7 @@ proc cljDistinct*(coll: CljVal): CljVal =
proc cljFlatten*(coll: CljVal): CljVal =
if coll.isNil: return cljList(@[])
var result: seq[CljVal] = @[]
var flat: seq[CljVal] = @[]
proc flattenHelper(v: CljVal) =
if v.isNil: return
case v.kind
@@ -612,9 +612,9 @@ proc cljFlatten*(coll: CljVal): CljVal =
for item in v.vecData.items:
flattenHelper(item)
else:
result.add(v)
flat.add(v)
flattenHelper(coll)
cljList(result)
cljList(flat)
proc cljPartition*(n: int, coll: CljVal): CljVal =
if coll.isNil: return cljList(@[])
@@ -715,20 +715,20 @@ proc cljVals*(m: CljVal): CljVal =
proc cljSelectKeys*(m: CljVal, keys: seq[CljVal]): CljVal =
if m.isNil or m.kind != ckMap: return cljMap(@[], @[])
var result = newPersistentMap[CljVal, CljVal]()
var res = newPersistentMap[CljVal, CljVal]()
for key in keys:
let v = pmapGet(m.mapData, key, cljNil(), hash(key), cljEq)
if not cljIsNil(v):
result = pmapAssoc(result, key, v, hash(key), cljEq)
CljVal(kind: ckMap, mapData: result)
res = pmapAssoc(res, key, v, hash(key), cljEq)
CljVal(kind: ckMap, mapData: res)
proc cljMerge*(args: seq[CljVal]): CljVal =
var result = newPersistentMap[CljVal, CljVal]()
var res = newPersistentMap[CljVal, CljVal]()
for m in args:
if not m.isNil and m.kind == ckMap:
for (k, v) in pmapItems(m.mapData):
result = pmapAssoc(result, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: result)
res = pmapAssoc(res, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: res)
# ---- Higher-order functions ----
@@ -973,25 +973,25 @@ proc cljMeta*(v: CljVal): CljVal =
proc cljWithMeta*(v: CljVal, m: CljVal): CljVal =
if v.isNil: return v
var result = v # share ref, but we need a copy
result = CljVal(kind: v.kind, meta: m)
var copy = v # share ref, but we need a copy
copy = CljVal(kind: v.kind, meta: m)
case v.kind
of ckNil: discard
of ckBool: result.boolVal = v.boolVal
of ckInt: result.intVal = v.intVal
of ckFloat: result.floatVal = v.floatVal
of ckString: result.strVal = v.strVal
of ckKeyword: result.kwName = v.kwName
of ckSymbol: result.symName = v.symName
of ckList: result.listItems = v.listItems
of ckVector: result.vecData = v.vecData
of ckMap: result.mapData = v.mapData
of ckSet: result.setData = v.setData
of ckFn: result.fnProc = v.fnProc
of ckAtom: result.atomVal = v.atomVal
of ckTransient: result.transKind = v.transKind; result.transVec = v.transVec; result.transPairs = v.transPairs
of ckAgent: result.agentVal = v.agentVal; initLock(result.agentLock)
return result
of ckBool: copy.boolVal = v.boolVal
of ckInt: copy.intVal = v.intVal
of ckFloat: copy.floatVal = v.floatVal
of ckString: copy.strVal = v.strVal
of ckKeyword: copy.kwName = v.kwName
of ckSymbol: copy.symName = v.symName
of ckList: copy.listItems = v.listItems
of ckVector: copy.vecData = v.vecData
of ckMap: copy.mapData = v.mapData
of ckSet: copy.setData = v.setData
of ckFn: copy.fnProc = v.fnProc
of ckAtom: copy.atomVal = v.atomVal
of ckTransient: copy.transKind = v.transKind; copy.transVec = v.transVec; copy.transPairs = v.transPairs
of ckAgent: copy.agentVal = v.agentVal; initLock(copy.agentLock)
return copy
proc cljAtom*(v: CljVal): CljVal =
CljVal(kind: ckAtom, atomVal: v)
@@ -1173,22 +1173,22 @@ proc cljInto*(to: CljVal, src: CljVal): CljVal =
else: to
of ckMap:
if src.kind == ckMap:
var result = to.mapData
var res = to.mapData
for (k, v) in pmapItems(src.mapData):
result = pmapAssoc(result, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: result)
res = pmapAssoc(res, k, v, hash(k), cljEq)
CljVal(kind: ckMap, mapData: res)
elif src.kind == ckVector:
var result = to.mapData
var res = to.mapData
for pair in src.vecData.items:
if pair.kind == ckVector and pair.vecData.count == 2:
result = pmapAssoc(result, pvecNth(pair.vecData, 0), pvecNth(pair.vecData, 1), hash(pvecNth(pair.vecData, 0)), cljEq)
CljVal(kind: ckMap, mapData: result)
res = pmapAssoc(res, pvecNth(pair.vecData, 0), pvecNth(pair.vecData, 1), hash(pvecNth(pair.vecData, 0)), cljEq)
CljVal(kind: ckMap, mapData: res)
elif src.kind == ckList:
var result = to.mapData
var res = to.mapData
for pair in src.listItems:
if pair.kind == ckVector and pair.vecData.count == 2:
result = pmapAssoc(result, pvecNth(pair.vecData, 0), pvecNth(pair.vecData, 1), hash(pvecNth(pair.vecData, 0)), cljEq)
CljVal(kind: ckMap, mapData: result)
res = pmapAssoc(res, pvecNth(pair.vecData, 0), pvecNth(pair.vecData, 1), hash(pvecNth(pair.vecData, 0)), cljEq)
CljVal(kind: ckMap, mapData: res)
else: to
else: to
+113
View File
@@ -0,0 +1,113 @@
# Minimal Clojure runtime for JavaScript target
# No C FFI, no threads, no processes — just plain Nim/JS
type
CljKind* = enum
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
ckList, ckVector, ckMap, ckSet, ckFn, ckAtom, ckTransient, ckAgent
CljVal* = object
case kind*: CljKind
of ckNil: discard
of ckBool: boolVal*: bool
of ckInt: intVal*: int64
of ckFloat: floatVal*: float64
of ckString: strVal*: string
of ckKeyword: kwName*: string
of ckSymbol: symName*: string
of ckList, ckVector: items*: seq[CljVal]
of ckMap:
mapKeys*: seq[CljVal]
mapVals*: seq[CljVal]
of ckSet: setItems*: seq[CljVal]
of ckFn: fnBody*: seq[CljVal]
of ckAtom: atomVal*: CljVal
of ckTransient: transVec*: seq[CljVal]
of ckAgent: agentVal*: CljVal
proc cljNil*(): CljVal = CljVal(kind: ckNil)
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
proc cljList*(items: seq[CljVal]): CljVal = CljVal(kind: ckList, items: items)
proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, items: items)
proc cljMap*(keys: seq[CljVal], vals: seq[CljVal]): CljVal =
CljVal(kind: ckMap, mapKeys: keys, mapVals: vals)
# ---- Arithmetic ----
proc cljAdd*(args: seq[CljVal]): CljVal =
var sum: int64 = 0
var hasFloat = false
var fsum: float64 = 0.0
for a in args:
if a.kind == ckInt: sum += a.intVal
elif a.kind == ckFloat: hasFloat = true; fsum += a.floatVal
if hasFloat: return cljFloat(fsum + sum.float64)
return cljInt(sum)
proc cljMul*(args: seq[CljVal]): CljVal =
var product: int64 = 1
var hasFloat = false
var fprod: float64 = 1.0
for a in args:
if a.kind == ckInt: product *= a.intVal
elif a.kind == ckFloat: hasFloat = true; fprod *= a.floatVal
if hasFloat: return cljFloat(fprod * product.float64)
return cljInt(product)
proc cljSub*(args: seq[CljVal]): CljVal =
if args.len == 0: return cljInt(0)
var sum = args[0].intVal.float64
for i in 1..<args.len:
sum -= args[i].intVal.float64
return cljInt(sum.int64)
# ---- String ops ----
proc cljStrConcat*(args: seq[CljVal]): CljVal =
var s = ""
for a in args:
case a.kind
of ckString: s.add(a.strVal)
of ckInt: s.add($a.intVal)
of ckFloat: s.add($a.floatVal)
of ckBool: s.add($a.boolVal)
of ckKeyword: s.add(":" & a.kwName)
of ckNil: s.add("nil")
else: discard
return cljString(s)
# ---- Predicates ----
proc cljIsTruthy*(v: CljVal): bool =
if v.kind == ckNil: return false
if v.kind == ckBool: return v.boolVal
return true
proc cljNumEq*(args: seq[CljVal]): CljVal =
if args.len < 2: return cljBool(true)
for i in 1..<args.len:
let a = args[i-1]
let b = args[i]
var eq = false
if a.kind == ckInt and b.kind == ckInt: eq = a.intVal == b.intVal
elif a.kind == ckFloat and b.kind == ckFloat: eq = a.floatVal == b.floatVal
elif a.kind == ckInt and b.kind == ckFloat: eq = a.intVal.float64 == b.floatVal
elif a.kind == ckFloat and b.kind == ckInt: eq = a.floatVal == b.intVal.float64
else: return cljBool(false)
if not eq: return cljBool(false)
return cljBool(true)
# ---- Equality ----
proc cljEq*(a, b: CljVal): bool =
if a.kind != b.kind: return false
case a.kind
of ckNil: return true
of ckBool: return a.boolVal == b.boolVal
of ckInt: return a.intVal == b.intVal
of ckFloat: return a.floatVal == b.floatVal
of ckString: return a.strVal == b.strVal
of ckKeyword: return a.kwName == b.kwName
of ckSymbol: return a.symName == b.symName
else: return false