# Tree-walking interpreter for fast REPL evaluation # Handles common cases without spawning nim c import strutils, sequtils, tables, algorithm, times, deques import types, reader var agentRegistry* = initTable[string, CljVal]() var agentCounter*: int64 = 0 type Channel* = ref object buf: Deque[CljVal] capacity: int # 0 = unbuffered var channelRegistry* = initTable[string, Channel]() var channelCounter*: int64 = 0 type EvalError* = object of CatchableError Env* = ref object bindings*: Table[string, CljVal] parent*: Env BuiltinFn = proc(args: seq[CljVal], env: Env): CljVal EvalResult* = object ok*: bool value*: CljVal error*: string isDef*: bool defName*: string proc newEnv(parent: Env = nil): Env = result = Env() result.bindings = initTable[string, CljVal]() result.parent = parent proc envGet(env: Env, name: string): CljVal = var e = env while e != nil: if name in e.bindings: return e.bindings[name] e = e.parent return nil proc envSet(env: Env, name: string, val: CljVal) = env.bindings[name] = val proc envSetGlobal(env: Env, name: string, val: CljVal) = var e = env while e.parent != nil: e = e.parent e.bindings[name] = val proc cljReprLocal(v: CljVal): string = if v.isNil: return "nil" case v.kind of ckNil: "nil" of ckBool: if v.boolVal: "true" else: "false" of ckInt: $v.intVal of ckFloat: $v.floatVal of ckString: "\"" & v.strVal & "\"" of ckKeyword: ":" & v.kwName of ckSymbol: v.symName of ckList: "(" & v.items.mapIt(cljReprLocal(it)).join(" ") & ")" of ckVector: "[" & v.items.mapIt(cljReprLocal(it)).join(" ") & "]" of ckMap: var parts: seq[string] = @[] for i in 0..= bindings.items.len: return EvalResult(ok: false, error: "let bindings must be in pairs") let bName = bindings.items[i] let bVal = bindings.items[i+1] if bName.kind != ckSymbol: return EvalResult(ok: false, error: "let binding name must be a symbol") let valRes = evalAst(bVal, letEnv) if not valRes.ok: return valRes envSet(letEnv, bName.symName, valRes.value) i += 2 var lastVal: CljVal = cljNil() for bi in 2..= 4: return evalAst(items[3], env) else: return EvalResult(ok: true, value: cljNil()) of "do": var lastVal: CljVal = cljNil() for i in 1..= 3 and fnVal.items[0].kind == ckSymbol and fnVal.items[0].symName == "fn": let params = fnVal.items[1] let body = fnVal.items[2..^1] if params.kind != ckVector: return EvalResult(ok: false, error: "fn params must be a vector") let callEnv = newEnv(env) for pi in 0..": numArgs(2) if args[0].kind == ckInt and args[1].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal > args[1].intVal)) var a, b: float64 if args[0].kind == ckInt: a = args[0].intVal.float64 elif args[0].kind == ckFloat: a = args[0].floatVal else: return EvalResult(ok: false, error: "> requires numbers") if args[1].kind == ckInt: b = args[1].intVal.float64 elif args[1].kind == ckFloat: b = args[1].floatVal else: return EvalResult(ok: false, error: "> requires numbers") return EvalResult(ok: true, value: cljBool(a > b)) of "<": numArgs(2) if args[0].kind == ckInt and args[1].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal < args[1].intVal)) var a, b: float64 if args[0].kind == ckInt: a = args[0].intVal.float64 elif args[0].kind == ckFloat: a = args[0].floatVal else: return EvalResult(ok: false, error: "< requires numbers") if args[1].kind == ckInt: b = args[1].intVal.float64 elif args[1].kind == ckFloat: b = args[1].floatVal else: return EvalResult(ok: false, error: "< requires numbers") return EvalResult(ok: true, value: cljBool(a < b)) of ">=": numArgs(2) if args[0].kind == ckInt and args[1].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal >= args[1].intVal)) var a, b: float64 if args[0].kind == ckInt: a = args[0].intVal.float64 elif args[0].kind == ckFloat: a = args[0].floatVal else: return EvalResult(ok: false, error: ">= requires numbers") if args[1].kind == ckInt: b = args[1].intVal.float64 elif args[1].kind == ckFloat: b = args[1].floatVal else: return EvalResult(ok: false, error: ">= requires numbers") return EvalResult(ok: true, value: cljBool(a >= b)) of "<=": numArgs(2) if args[0].kind == ckInt and args[1].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal <= args[1].intVal)) var a, b: float64 if args[0].kind == ckInt: a = args[0].intVal.float64 elif args[0].kind == ckFloat: a = args[0].floatVal else: return EvalResult(ok: false, error: "<= requires numbers") if args[1].kind == ckInt: b = args[1].intVal.float64 elif args[1].kind == ckFloat: b = args[1].floatVal else: return EvalResult(ok: false, error: "<= requires numbers") return EvalResult(ok: true, value: cljBool(a <= b)) of "println": var parts: seq[string] = @[] for a in args: if a.kind == ckNil: parts.add("nil") elif a.kind == ckString: parts.add(a.strVal) elif a.kind == ckBool: parts.add(if a.boolVal: "true" else: "false") else: parts.add(cljReprLocal(a)) echo parts.join(" ") return EvalResult(ok: true, value: cljNil()) of "prn": var parts: seq[string] = @[] for a in args: parts.add(cljReprLocal(a)) echo parts.join(" ") return EvalResult(ok: true, value: cljNil()) of "str": atLeast(1) var s = "" for a in args: if a.kind == ckNil: s.add("nil") elif a.kind == ckString: s.add(a.strVal) elif a.kind == ckBool: s.add(if a.boolVal: "true" else: "false") elif a.kind == ckInt: s.add($a.intVal) elif a.kind == ckFloat: s.add($a.floatVal) else: s.add(cljReprLocal(a)) return EvalResult(ok: true, value: cljString(s)) of "pr-str": atLeast(0) var parts: seq[string] = @[] for a in args: parts.add(cljReprLocal(a)) return EvalResult(ok: true, value: cljString(parts.join(" "))) of "inc": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljInt(args[0].intVal + 1)) if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljFloat(args[0].floatVal + 1.0)) return EvalResult(ok: false, error: "inc requires a number") of "dec": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljInt(args[0].intVal - 1)) if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljFloat(args[0].floatVal - 1.0)) return EvalResult(ok: false, error: "dec requires a number") of "zero?": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal == 0)) if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljBool(args[0].floatVal == 0.0)) return EvalResult(ok: false, error: "zero? requires a number") of "pos?": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal > 0)) if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljBool(args[0].floatVal > 0.0)) return EvalResult(ok: false, error: "pos? requires a number") of "neg?": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal < 0)) if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljBool(args[0].floatVal < 0.0)) return EvalResult(ok: false, error: "neg? requires a number") of "even?": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal mod 2 == 0)) return EvalResult(ok: false, error: "even? requires an integer") of "odd?": numArgs(1) if args[0].kind == ckInt: return EvalResult(ok: true, value: cljBool(args[0].intVal mod 2 != 0)) return EvalResult(ok: false, error: "odd? requires an integer") of "count": numArgs(1) if args[0].kind in {ckList, ckVector}: return EvalResult(ok: true, value: cljInt(args[0].items.len.int64)) if args[0].kind == ckString: return EvalResult(ok: true, value: cljInt(args[0].strVal.len.int64)) if args[0].kind == ckNil: return EvalResult(ok: true, value: cljInt(0)) return EvalResult(ok: false, error: "count requires a collection") of "first": numArgs(1) if args[0].kind in {ckList, ckVector}: if args[0].items.len == 0: return EvalResult(ok: true, value: cljNil()) return EvalResult(ok: true, value: args[0].items[0]) if args[0].kind == ckNil: return EvalResult(ok: true, value: cljNil()) return EvalResult(ok: false, error: "first requires a collection") of "rest": numArgs(1) if args[0].kind in {ckList, ckVector}: if args[0].items.len <= 1: return EvalResult(ok: true, value: cljList(@[])) return EvalResult(ok: true, value: cljList(args[0].items[1..^1])) if args[0].kind == ckNil: return EvalResult(ok: true, value: cljList(@[])) return EvalResult(ok: false, error: "rest requires a collection") of "last": numArgs(1) if args[0].kind in {ckList, ckVector}: if args[0].items.len == 0: return EvalResult(ok: true, value: cljNil()) return EvalResult(ok: true, value: args[0].items[^1]) return EvalResult(ok: false, error: "last requires a collection") of "nth": numArgs(2) if args[0].kind in {ckList, ckVector} and args[1].kind == ckInt: let idx = args[1].intVal.int if idx < 0 or idx >= args[0].items.len: return EvalResult(ok: false, error: "nth: index out of bounds") return EvalResult(ok: true, value: args[0].items[idx]) return EvalResult(ok: false, error: "nth requires a collection and integer index") of "conj": atLeast(2) let coll = args[0] let items = args[1..^1] if coll.kind == ckList: var newItems: seq[CljVal] = @[] for item in items: newItems.add(item) newItems.add(coll.items) return EvalResult(ok: true, value: cljList(newItems)) if coll.kind == ckVector: var newItems = coll.items for item in items: newItems.add(item) return EvalResult(ok: true, value: cljVector(newItems)) return EvalResult(ok: false, error: "conj requires a collection") of "cons": numArgs(2) let item = args[0] let coll = args[1] if coll.kind in {ckList, ckVector}: var newItems = @[item] newItems.add(coll.items) return EvalResult(ok: true, value: cljList(newItems)) return EvalResult(ok: false, error: "cons requires a collection") of "concat": var result: seq[CljVal] = @[] for a in args: if a.kind in {ckList, ckVector}: result.add(a.items) return EvalResult(ok: true, value: cljList(result)) of "reverse": numArgs(1) if args[0].kind in {ckList, ckVector}: var newItems: seq[CljVal] = @[] for i in countdown(args[0].items.len - 1, 0): newItems.add(args[0].items[i]) return EvalResult(ok: true, value: cljList(newItems)) return EvalResult(ok: false, error: "reverse requires a collection") of "vec": numArgs(1) if args[0].kind == ckList: return EvalResult(ok: true, value: cljVector(args[0].items)) if args[0].kind == ckVector: return EvalResult(ok: true, value: args[0]) return EvalResult(ok: false, error: "vec requires a list") of "list": return EvalResult(ok: true, value: cljList(args)) of "vector": return EvalResult(ok: true, value: cljVector(args)) of "map": # (map f coll) numArgs(2) let fn = args[0] let coll = args[1] if coll.kind in {ckList, ckVector}: var result: seq[CljVal] = @[] for item in coll.items: let callItems = @[fn, item] let callRes = evalList(callItems, env) if not callRes.ok: return callRes result.add(callRes.value) return EvalResult(ok: true, value: cljList(result)) return EvalResult(ok: false, error: "map requires a function and collection") of "filter": numArgs(2) let fn = args[0] let coll = args[1] if coll.kind in {ckList, ckVector}: var result: seq[CljVal] = @[] for item in coll.items: let callItems = @[fn, item] let callRes = evalList(callItems, env) if not callRes.ok: return callRes let isTruthy = not (callRes.value.kind == ckNil or (callRes.value.kind == ckBool and not callRes.value.boolVal)) if isTruthy: result.add(item) return EvalResult(ok: true, value: cljList(result)) return EvalResult(ok: false, error: "filter requires a function and collection") of "reduce": atLeast(2) let fn = args[0] var acc: CljVal var coll: CljVal if args.len == 3: acc = args[1] coll = args[2] else: coll = args[1] if coll.kind in {ckList, ckVector} and coll.items.len > 0: acc = coll.items[0] # reduce rest var result = acc for i in 1..= 3: args[2] else: cljNil() if m.kind == ckMap: for i in 0..= 0 and idx < m.items.len: return EvalResult(ok: true, value: m.items[idx]) return EvalResult(ok: true, value: default) if m.kind == ckNil: return EvalResult(ok: true, value: default) return EvalResult(ok: false, error: "get requires a map or vector") of "assoc": atLeast(3) let m = args[0] if m.kind == ckMap: var newKeys = m.mapKeys var newVals = m.mapVals var i = 1 while i + 1 < args.len: let key = args[i] let val = args[i+1] var found = false for j in 0.. 2: args[2..^1] else: @[] let currentVal = agentRegistry[agentId.strVal] var callItems = @[fn, currentVal] callItems.add(fnArgs) let callRes = evalList(callItems, env) if not callRes.ok: return callRes agentRegistry[agentId.strVal] = callRes.value return EvalResult(ok: true, value: callRes.value) of "await": numArgs(1) return EvalResult(ok: true, value: cljNil()) of "shutdown-agents": return EvalResult(ok: true, value: cljNil()) of "chan": channelCounter += 1 let id = "chan_" & $channelCounter let cap = if args.len > 0 and args[0].kind == ckInt: args[0].intVal.int else: 0 let ch = Channel(buf: initDeque[CljVal](), capacity: cap) channelRegistry[id] = ch return EvalResult(ok: true, value: cljString(id)) of "put!", ">!": numArgs(2) if args[0].kind != ckString or not channelRegistry.hasKey(args[0].strVal): return EvalResult(ok: false, error: "put!/ 0 and ch.buf.len >= ch.capacity: return EvalResult(ok: false, error: "Channel buffer full") ch.buf.addLast(args[1]) return EvalResult(ok: true, value: cljBool(true)) of "take!", "