Files
bara-lang/src/eval.nim
T

1181 lines
43 KiB
Nim

# 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..<v.mapKeys.len:
parts.add(cljReprLocal(v.mapKeys[i]) & " " & cljReprLocal(v.mapVals[i]))
"{" & parts.join(", ") & "}"
proc evalAst*(form: CljVal, env: Env): EvalResult
proc evalList(items: seq[CljVal], env: Env): EvalResult =
if items.len == 0:
return EvalResult(ok: true, value: cljList(@[]))
let head = items[0]
# Special forms
if head.kind == ckSymbol:
case head.symName
of "def":
if items.len < 3:
return EvalResult(ok: false, error: "def requires name and value")
let name = items[1]
if name.kind != ckSymbol:
return EvalResult(ok: false, error: "def name must be a symbol")
let valRes = evalAst(items[2], env)
if not valRes.ok: return valRes
envSetGlobal(env, name.symName, valRes.value)
return EvalResult(ok: true, value: valRes.value, isDef: true, defName: name.symName)
of "defn":
if items.len < 4:
return EvalResult(ok: false, error: "defn requires name, params, and body")
let name = items[1]
if name.kind != ckSymbol:
return EvalResult(ok: false, error: "defn name must be a symbol")
let params = items[2]
let body = items[3..^1]
let fnVal = cljList(@[cljSymbol("fn"), params] & body)
envSetGlobal(env, name.symName, fnVal)
return EvalResult(ok: true, value: fnVal, isDef: true, defName: name.symName)
of "fn":
if items.len < 3:
return EvalResult(ok: false, error: "fn requires params and body")
return EvalResult(ok: true, value: cljList(items))
of "let":
if items.len < 3:
return EvalResult(ok: false, error: "let requires bindings and body")
let bindings = items[1]
if bindings.kind != ckVector:
return EvalResult(ok: false, error: "let bindings must be a vector")
let letEnv = newEnv(env)
var i = 0
while i < bindings.items.len:
if i + 1 >= 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..<items.len:
let bodyRes = evalAst(items[bi], letEnv)
if not bodyRes.ok: return bodyRes
lastVal = bodyRes.value
return EvalResult(ok: true, value: lastVal)
of "if":
if items.len < 3:
return EvalResult(ok: false, error: "if requires condition and then")
let condRes = evalAst(items[1], env)
if not condRes.ok: return condRes
let isTruthy = not (condRes.value.kind == ckNil or
(condRes.value.kind == ckBool and not condRes.value.boolVal))
if isTruthy:
return evalAst(items[2], env)
elif items.len >= 4:
return evalAst(items[3], env)
else:
return EvalResult(ok: true, value: cljNil())
of "do":
var lastVal: CljVal = cljNil()
for i in 1..<items.len:
let res = evalAst(items[i], env)
if not res.ok: return res
lastVal = res.value
return EvalResult(ok: true, value: lastVal)
of "quote":
if items.len < 2:
return EvalResult(ok: false, error: "quote requires an argument")
return EvalResult(ok: true, value: items[1])
of "when":
if items.len < 3:
return EvalResult(ok: false, error: "when requires condition and body")
let condRes = evalAst(items[1], env)
if not condRes.ok: return condRes
let isTruthy = not (condRes.value.kind == ckNil or
(condRes.value.kind == ckBool and not condRes.value.boolVal))
if isTruthy:
var lastVal: CljVal = cljNil()
for i in 2..<items.len:
let res = evalAst(items[i], env)
if not res.ok: return res
lastVal = res.value
return EvalResult(ok: true, value: lastVal)
return EvalResult(ok: true, value: cljNil())
of "cond":
var i = 1
while i + 1 < items.len:
let condRes = evalAst(items[i], env)
if not condRes.ok: return condRes
let isTruthy = not (condRes.value.kind == ckNil or
(condRes.value.kind == ckBool and not condRes.value.boolVal))
if isTruthy:
return evalAst(items[i+1], env)
i += 2
return EvalResult(ok: true, value: cljNil())
of "not":
if items.len < 2:
return EvalResult(ok: false, error: "not requires an argument")
let argRes = evalAst(items[1], env)
if not argRes.ok: return argRes
let isFalsy = argRes.value.kind == ckNil or
(argRes.value.kind == ckBool and not argRes.value.boolVal)
return EvalResult(ok: true, value: cljBool(isFalsy))
of "and":
var lastVal: CljVal = cljBool(true)
for i in 1..<items.len:
let res = evalAst(items[i], env)
if not res.ok: return res
let isFalsy = res.value.kind == ckNil or
(res.value.kind == ckBool and not res.value.boolVal)
if isFalsy:
return EvalResult(ok: true, value: res.value)
lastVal = res.value
return EvalResult(ok: true, value: lastVal)
of "or":
for i in 1..<items.len:
let res = evalAst(items[i], env)
if not res.ok: return res
let isTruthy = not (res.value.kind == ckNil or
(res.value.kind == ckBool and not res.value.boolVal))
if isTruthy:
return EvalResult(ok: true, value: res.value)
return EvalResult(ok: true, value: cljNil())
else:
discard
# Function call: evaluate head and all args
let headRes = evalAst(head, env)
if not headRes.ok: return headRes
var args: seq[CljVal] = @[]
for i in 1..<items.len:
let argRes = evalAst(items[i], env)
if not argRes.ok: return argRes
args.add(argRes.value)
let fnVal = headRes.value
# Check if it's a lambda (fn form stored as list)
if fnVal.kind == ckList and fnVal.items.len >= 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..<params.items.len:
if params.items[pi].kind == ckSymbol:
let pName = params.items[pi].symName
if pName == "&":
# variadic
if pi + 1 < params.items.len:
let restName = params.items[pi+1].symName
envSet(callEnv, restName, cljList(args[pi..^1]))
break
elif pi < args.len:
envSet(callEnv, pName, args[pi])
var lastVal: CljVal = cljNil()
for bi in 0..<body.len:
let bodyRes = evalAst(body[bi], callEnv)
if not bodyRes.ok: return bodyRes
lastVal = bodyRes.value
return EvalResult(ok: true, value: lastVal)
# Built-in functions
proc evalBuiltin(name: string, args: seq[CljVal]): EvalResult =
template numArgs(n: int) =
if args.len != n:
return EvalResult(ok: false, error: name & " requires " & $n & " args")
template atLeast(n: int) =
if args.len < n:
return EvalResult(ok: false, error: name & " requires at least " & $n & " args")
case name
of "+":
atLeast(1)
var sum: int64 = 0
var hasFloat = false
var fsum: float64 = 0.0
for a in args:
if a.kind == ckFloat:
hasFloat = true
fsum += a.floatVal
elif a.kind == ckInt:
if hasFloat: fsum += a.floatVal.float64
else: sum += a.intVal
else:
return EvalResult(ok: false, error: "+ requires numbers")
if hasFloat:
return EvalResult(ok: true, value: cljFloat(fsum + sum.float64))
return EvalResult(ok: true, value: cljInt(sum))
of "-":
atLeast(1)
if args.len == 1:
if args[0].kind == ckInt: return EvalResult(ok: true, value: cljInt(-args[0].intVal))
if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljFloat(-args[0].floatVal))
return EvalResult(ok: false, error: "- requires numbers")
var result: float64
if args[0].kind == ckInt: result = args[0].intVal.float64
elif args[0].kind == ckFloat: result = args[0].floatVal
else: return EvalResult(ok: false, error: "- requires numbers")
for i in 1..<args.len:
if args[i].kind == ckInt: result -= args[i].intVal.float64
elif args[i].kind == ckFloat: result -= args[i].floatVal
else: return EvalResult(ok: false, error: "- requires numbers")
if result == result.int64.float64 and args[0].kind == ckInt:
var allInt = true
for i in 1..<args.len:
if args[i].kind != ckInt: allInt = false
if allInt: return EvalResult(ok: true, value: cljInt(result.int64))
return EvalResult(ok: true, value: cljFloat(result))
of "*":
atLeast(1)
var product: int64 = 1
var hasFloat = false
var fproduct: float64 = 1.0
for a in args:
if a.kind == ckFloat:
hasFloat = true
fproduct *= a.floatVal
elif a.kind == ckInt:
if hasFloat: fproduct *= a.floatVal.float64
else: product *= a.intVal
else:
return EvalResult(ok: false, error: "* requires numbers")
if hasFloat:
return EvalResult(ok: true, value: cljFloat(fproduct * product.float64))
return EvalResult(ok: true, value: cljInt(product))
of "/":
numArgs(2)
if args[0].kind == ckInt and args[1].kind == ckInt:
if args[1].intVal == 0:
return EvalResult(ok: false, error: "Division by zero")
return EvalResult(ok: true, value: cljInt(args[0].intVal div 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")
if b == 0: return EvalResult(ok: false, error: "Division by zero")
return EvalResult(ok: true, value: cljFloat(a / b))
of "=":
numArgs(2)
let a = args[0]
let b = args[1]
if a.kind == ckNil and b.kind == ckNil:
return EvalResult(ok: true, value: cljBool(true))
if a.kind == ckNil or b.kind == ckNil:
return EvalResult(ok: true, value: cljBool(false))
if a.kind == ckInt and b.kind == ckInt:
return EvalResult(ok: true, value: cljBool(a.intVal == b.intVal))
if a.kind == ckFloat and b.kind == ckFloat:
return EvalResult(ok: true, value: cljBool(a.floatVal == b.floatVal))
if (a.kind == ckInt and b.kind == ckFloat) or (a.kind == ckFloat and b.kind == ckInt):
let av = if a.kind == ckInt: a.intVal.float64 else: a.floatVal
let bv = if b.kind == ckInt: b.intVal.float64 else: b.floatVal
return EvalResult(ok: true, value: cljBool(av == bv))
if a.kind == ckString and b.kind == ckString:
return EvalResult(ok: true, value: cljBool(a.strVal == b.strVal))
if a.kind == ckBool and b.kind == ckBool:
return EvalResult(ok: true, value: cljBool(a.boolVal == b.boolVal))
if a.kind == ckKeyword and b.kind == ckKeyword:
return EvalResult(ok: true, value: cljBool(a.kwName == b.kwName))
if a.kind == ckSymbol and b.kind == ckSymbol:
return EvalResult(ok: true, value: cljBool(a.symName == b.symName))
return EvalResult(ok: true, value: cljBool(false))
of "not=":
numArgs(2)
let eqRes = evalBuiltin("=", args)
if not eqRes.ok: return eqRes
return EvalResult(ok: true, value: cljBool(not eqRes.value.boolVal))
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 "<=":
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..<coll.items.len:
let callItems = @[fn, result, coll.items[i]]
let callRes = evalList(callItems, env)
if not callRes.ok: return callRes
result = callRes.value
return EvalResult(ok: true, value: result)
return EvalResult(ok: true, value: cljNil())
if coll.kind in {ckList, ckVector}:
var result = acc
for item in coll.items:
let callItems = @[fn, result, item]
let callRes = evalList(callItems, env)
if not callRes.ok: return callRes
result = callRes.value
return EvalResult(ok: true, value: result)
return EvalResult(ok: false, error: "reduce requires a function and collection")
of "apply":
atLeast(2)
let fn = args[0]
let lastColl = args[^1]
var allArgs: seq[CljVal] = @[]
for i in 1..<args.len - 1:
allArgs.add(args[i])
if lastColl.kind in {ckList, ckVector}:
allArgs.add(lastColl.items)
var callItems = @[fn]
callItems.add(allArgs)
return evalList(callItems, env)
of "identity":
numArgs(1)
return EvalResult(ok: true, value: args[0])
of "get":
atLeast(2)
let m = args[0]
let key = args[1]
let default = if args.len >= 3: args[2] else: cljNil()
if m.kind == ckMap:
for i in 0..<m.mapKeys.len:
let eqRes = evalBuiltin("=", @[m.mapKeys[i], key])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
return EvalResult(ok: true, value: m.mapVals[i])
return EvalResult(ok: true, value: default)
if m.kind == ckVector and key.kind == ckInt:
let idx = key.intVal.int
if idx >= 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..<newKeys.len:
let eqRes = evalBuiltin("=", @[newKeys[j], key])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
newVals[j] = val
found = true
break
if not found:
newKeys.add(key)
newVals.add(val)
i += 2
return EvalResult(ok: true, value: cljMap(newKeys, newVals))
return EvalResult(ok: false, error: "assoc requires a map")
of "dissoc":
atLeast(2)
let m = args[0]
if m.kind == ckMap:
var newKeys: seq[CljVal] = @[]
var newVals: seq[CljVal] = @[]
for i in 0..<m.mapKeys.len:
var shouldRemove = false
for j in 1..<args.len:
let eqRes = evalBuiltin("=", @[m.mapKeys[i], args[j]])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
shouldRemove = true
break
if not shouldRemove:
newKeys.add(m.mapKeys[i])
newVals.add(m.mapVals[i])
return EvalResult(ok: true, value: cljMap(newKeys, newVals))
return EvalResult(ok: false, error: "dissoc requires a map")
of "keys":
numArgs(1)
if args[0].kind == ckMap:
return EvalResult(ok: true, value: cljList(args[0].mapKeys))
return EvalResult(ok: false, error: "keys requires a map")
of "vals":
numArgs(1)
if args[0].kind == ckMap:
return EvalResult(ok: true, value: cljList(args[0].mapVals))
return EvalResult(ok: false, error: "vals requires a map")
of "contains?":
numArgs(2)
let coll = args[0]
let key = args[1]
if coll.kind == ckMap:
for i in 0..<coll.mapKeys.len:
let eqRes = evalBuiltin("=", @[coll.mapKeys[i], key])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
return EvalResult(ok: true, value: cljBool(true))
return EvalResult(ok: true, value: cljBool(false))
if coll.kind in {ckList, ckVector}:
for item in coll.items:
let eqRes = evalBuiltin("=", @[item, key])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
return EvalResult(ok: true, value: cljBool(true))
return EvalResult(ok: true, value: cljBool(false))
return EvalResult(ok: false, error: "contains? requires a collection")
of "merge":
var resultKeys: seq[CljVal] = @[]
var resultVals: seq[CljVal] = @[]
for a in args:
if a.kind == ckMap:
for i in 0..<a.mapKeys.len:
var found = false
for j in 0..<resultKeys.len:
let eqRes = evalBuiltin("=", @[resultKeys[j], a.mapKeys[i]])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
resultVals[j] = a.mapVals[i]
found = true
break
if not found:
resultKeys.add(a.mapKeys[i])
resultVals.add(a.mapVals[i])
elif a.kind != ckNil:
discard
return EvalResult(ok: true, value: cljMap(resultKeys, resultVals))
of "empty?":
numArgs(1)
if args[0].kind in {ckList, ckVector}:
return EvalResult(ok: true, value: cljBool(args[0].items.len == 0))
if args[0].kind == ckMap:
return EvalResult(ok: true, value: cljBool(args[0].mapKeys.len == 0))
if args[0].kind == ckString:
return EvalResult(ok: true, value: cljBool(args[0].strVal.len == 0))
if args[0].kind == ckNil:
return EvalResult(ok: true, value: cljBool(true))
return EvalResult(ok: false, error: "empty? requires a collection")
of "nil?":
numArgs(1)
return EvalResult(ok: true, value: cljBool(args[0].kind == ckNil))
of "true?":
numArgs(1)
return EvalResult(ok: true, value: cljBool(args[0].kind == ckBool and args[0].boolVal))
of "false?":
numArgs(1)
return EvalResult(ok: true, value: cljBool(args[0].kind == ckBool and not args[0].boolVal))
of "type":
numArgs(1)
let typeName = case args[0].kind
of ckNil: "nil"
of ckBool: "boolean"
of ckInt: "integer"
of ckFloat: "float"
of ckString: "string"
of ckKeyword: "keyword"
of ckSymbol: "symbol"
of ckList: "list"
of ckVector: "vector"
of ckMap: "map"
return EvalResult(ok: true, value: cljKeyword(typeName))
of "not":
numArgs(1)
let isFalsy = args[0].kind == ckNil or
(args[0].kind == ckBool and not args[0].boolVal)
return EvalResult(ok: true, value: cljBool(isFalsy))
of "abs":
numArgs(1)
if args[0].kind == ckInt:
return EvalResult(ok: true, value: cljInt(abs(args[0].intVal)))
if args[0].kind == ckFloat:
return EvalResult(ok: true, value: cljFloat(abs(args[0].floatVal)))
return EvalResult(ok: false, error: "abs requires a number")
of "mod":
numArgs(2)
if args[0].kind == ckInt and args[1].kind == ckInt:
return EvalResult(ok: true, value: cljInt(args[0].intVal mod args[1].intVal))
return EvalResult(ok: false, error: "mod requires integers")
of "quot":
numArgs(2)
if args[0].kind == ckInt and args[1].kind == ckInt:
if args[1].intVal == 0:
return EvalResult(ok: false, error: "Division by zero")
return EvalResult(ok: true, value: cljInt(args[0].intVal div args[1].intVal))
return EvalResult(ok: false, error: "quot requires integers")
of "rem":
numArgs(2)
if args[0].kind == ckInt and args[1].kind == ckInt:
if args[1].intVal == 0:
return EvalResult(ok: false, error: "Division by zero")
return EvalResult(ok: true, value: cljInt(args[0].intVal mod args[1].intVal))
return EvalResult(ok: false, error: "rem requires integers")
of "min":
numArgs(2)
if args[0].kind == ckInt and args[1].kind == ckInt:
return EvalResult(ok: true, value: cljInt(min(args[0].intVal, args[1].intVal)))
return EvalResult(ok: false, error: "min requires integers")
of "max":
numArgs(2)
if args[0].kind == ckInt and args[1].kind == ckInt:
return EvalResult(ok: true, value: cljInt(max(args[0].intVal, args[1].intVal)))
return EvalResult(ok: false, error: "max requires integers")
of "range":
if args.len == 0:
return EvalResult(ok: false, error: "range requires at least 1 arg")
if args.len == 1 and args[0].kind == ckInt:
var items: seq[CljVal] = @[]
for i in 0..<args[0].intVal:
items.add(cljInt(i))
return EvalResult(ok: true, value: cljList(items))
if args.len == 2 and args[0].kind == ckInt and args[1].kind == ckInt:
var items: seq[CljVal] = @[]
for i in args[0].intVal..<args[1].intVal:
items.add(cljInt(i))
return EvalResult(ok: true, value: cljList(items))
if args.len == 3 and args[0].kind == ckInt and args[1].kind == ckInt and args[2].kind == ckInt:
var items: seq[CljVal] = @[]
var i = args[0].intVal
while i < args[1].intVal:
items.add(cljInt(i))
i += args[2].intVal
return EvalResult(ok: true, value: cljList(items))
return EvalResult(ok: false, error: "range requires integers")
of "take":
numArgs(2)
if args[0].kind == ckInt and args[1].kind in {ckList, ckVector}:
let n = min(args[0].intVal.int, args[1].items.len)
return EvalResult(ok: true, value: cljList(args[1].items[0..<n]))
return EvalResult(ok: false, error: "take requires an integer and collection")
of "drop":
numArgs(2)
if args[0].kind == ckInt and args[1].kind in {ckList, ckVector}:
let n = min(args[0].intVal.int, args[1].items.len)
return EvalResult(ok: true, value: cljList(args[1].items[n..^1]))
return EvalResult(ok: false, error: "drop requires an integer and collection")
of "sort":
numArgs(1)
if args[0].kind in {ckList, ckVector}:
var items = args[0].items
sort(items, proc(a, b: CljVal): int =
if a.kind == ckInt and b.kind == ckInt:
result = cmp(a.intVal, b.intVal)
elif a.kind == ckFloat and b.kind == ckFloat:
result = cmp(a.floatVal, b.floatVal)
else:
result = 0
)
return EvalResult(ok: true, value: cljList(items))
return EvalResult(ok: false, error: "sort requires a collection")
of "distinct":
numArgs(1)
if args[0].kind in {ckList, ckVector}:
var result: seq[CljVal] = @[]
for item in args[0].items:
var isDup = false
for existing in result:
let eqRes = evalBuiltin("=", @[existing, item])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
isDup = true
break
if not isDup:
result.add(item)
return EvalResult(ok: true, value: cljList(result))
return EvalResult(ok: false, error: "distinct requires a collection")
of "slurp":
numArgs(1)
if args[0].kind == ckString:
try:
let content = readFile(args[0].strVal)
return EvalResult(ok: true, value: cljString(content))
except:
return EvalResult(ok: false, error: "slurp: cannot read file: " & args[0].strVal)
return EvalResult(ok: false, error: "slurp requires a string path")
of "spit":
numArgs(2)
if args[0].kind == ckString and args[1].kind == ckString:
try:
writeFile(args[0].strVal, args[1].strVal)
return EvalResult(ok: true, value: cljNil())
except:
return EvalResult(ok: false, error: "spit: cannot write file: " & args[0].strVal)
return EvalResult(ok: false, error: "spit requires two string args")
of "read-line":
try:
let line = stdin.readLine()
return EvalResult(ok: true, value: cljString(line))
except:
return EvalResult(ok: false, error: "read-line: EOF")
of "print":
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))
stdout.write(parts.join(" "))
return EvalResult(ok: true, value: cljNil())
of "instance?":
numArgs(2)
if args[0].kind == ckKeyword and args[1].kind != ckNil:
let typeName = case args[1].kind
of ckBool: "boolean"
of ckInt: "integer"
of ckFloat: "float"
of ckString: "string"
of ckKeyword: "keyword"
of ckSymbol: "symbol"
of ckList: "list"
of ckVector: "vector"
of ckMap: "map"
of ckNil: "nil"
return EvalResult(ok: true, value: cljBool(args[0].kwName == typeName))
return EvalResult(ok: true, value: cljBool(false))
of "meta":
numArgs(1)
return EvalResult(ok: true, value: cljNil())
of "with-meta":
numArgs(2)
return EvalResult(ok: true, value: args[0])
of "deref":
numArgs(1)
if args[0].kind == ckString and agentRegistry.hasKey(args[0].strVal):
return EvalResult(ok: true, value: agentRegistry[args[0].strVal])
return EvalResult(ok: true, value: args[0])
of "atom":
numArgs(1)
return EvalResult(ok: true, value: args[0])
of "agent":
numArgs(1)
agentCounter += 1
let id = "agent_" & $agentCounter
agentRegistry[id] = args[0]
return EvalResult(ok: true, value: cljString(id))
of "send":
atLeast(2)
let agentId = args[0]
if agentId.kind != ckString or not agentRegistry.hasKey(agentId.strVal):
return EvalResult(ok: false, error: "send requires an agent")
let fn = args[1]
let fnArgs = if args.len > 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!/<! requires a channel")
let ch = channelRegistry[args[0].strVal]
if ch.capacity > 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!", "<!":
numArgs(1)
if args[0].kind != ckString or not channelRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "take!/<! requires a channel")
let ch = channelRegistry[args[0].strVal]
if ch.buf.len == 0:
return EvalResult(ok: true, value: cljNil())
return EvalResult(ok: true, value: ch.buf.popFirst())
of "close!":
numArgs(1)
if args[0].kind != ckString or not channelRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "close! requires a channel")
channelRegistry.del(args[0].strVal)
return EvalResult(ok: true, value: cljNil())
of "go":
# In synchronous interpreter, go just evaluates the body
var lastVal: CljVal = cljNil()
for i in 1..<items.len:
let res = evalAst(items[i], env)
if not res.ok: return res
lastVal = res.value
return EvalResult(ok: true, value: lastVal)
else:
return EvalResult(ok: false, error: "Unknown function: " & name & " (use compile mode for full runtime)")
# Try to call as builtin
if fnVal.kind == ckSymbol:
let builtinRes = evalBuiltin(fnVal.symName, args)
if builtinRes.ok or builtinRes.error.startsWith("Unknown function"):
return builtinRes
return builtinRes
return EvalResult(ok: false, error: "Cannot call: " & cljReprLocal(fnVal))
proc evalAst*(form: CljVal, env: Env): EvalResult =
if form.isNil:
return EvalResult(ok: true, value: cljNil())
case form.kind
of ckNil:
return EvalResult(ok: true, value: cljNil())
of ckBool, ckInt, ckFloat, ckString, ckKeyword:
return EvalResult(ok: true, value: form)
of ckSymbol:
if form.symName == "nil":
return EvalResult(ok: true, value: cljNil())
if form.symName == "true":
return EvalResult(ok: true, value: cljBool(true))
if form.symName == "false":
return EvalResult(ok: true, value: cljBool(false))
let val = envGet(env, form.symName)
if val != nil:
return EvalResult(ok: true, value: val)
# Return symbol as-is (may be a builtin function name)
return EvalResult(ok: true, value: form)
of ckVector:
var items: seq[CljVal] = @[]
for item in form.items:
let res = evalAst(item, env)
if not res.ok: return res
items.add(res.value)
return EvalResult(ok: true, value: cljVector(items))
of ckMap:
var keys: seq[CljVal] = @[]
var vals: seq[CljVal] = @[]
for i in 0..<form.mapKeys.len:
let kRes = evalAst(form.mapKeys[i], env)
if not kRes.ok: return kRes
let vRes = evalAst(form.mapVals[i], env)
if not vRes.ok: return vRes
keys.add(kRes.value)
vals.add(vRes.value)
return EvalResult(ok: true, value: cljMap(keys, vals))
of ckList:
return evalList(form.items, env)
proc eval*(formStr: string, env: Env): EvalResult =
let parsed = reader.read(formStr)
return evalAst(parsed, env)
proc evalAll*(formsStr: string, env: Env): seq[EvalResult] =
let forms = reader.readAll(formsStr)
for form in forms:
result.add(evalAst(form, env))
proc newTopLevelEnv*(): Env =
result = newEnv()