1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
import rdstdin, tables, sequtils, os, types, reader, printer, env, core
proc read(str: string): MalType = str.read_str
proc is_pair(x: MalType): bool =
x.kind in {List, Vector} and x.list.len > 0
proc quasiquote(ast: MalType): MalType =
if not ast.is_pair:
return list(symbol "quote", ast)
elif ast.list[0] == symbol "unquote":
return ast.list[1]
elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
return list(symbol "concat", ast.list[0].list[1],
quasiquote(list ast.list[1 .. -1]))
else:
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
proc eval(ast: MalType, env: var Env): MalType
proc eval_ast(ast: MalType, env: var Env): MalType =
case ast.kind
of Symbol:
result = env.get(ast.str)
of List:
result = list ast.list.mapIt(MalType, it.eval(env))
of Vector:
result = vector ast.list.mapIt(MalType, it.eval(env))
of HashMap:
result = hash_map()
for k, v in ast.hash_map.pairs:
result.hash_map[k] = v.eval(env)
else:
result = ast
proc eval(ast: MalType, env: var Env): MalType =
var ast = ast
template defaultApply =
let el = ast.eval_ast(env)
let f = el.list[0]
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
else:
return f.fun(el.list[1 .. -1])
while true:
if ast.kind != List: return ast.eval_ast(env)
let a0 = ast.list[0]
case a0.kind
of Symbol:
case a0.str
of "def!":
let
a1 = ast.list[1]
a2 = ast.list[2]
return env.set(a1.str, a2.eval(env))
of "let*":
let
a1 = ast.list[1]
a2 = ast.list[2]
var let_env = Env(env)
case a1.kind
of List, Vector:
for i in countup(0, a1.list.high, 2):
let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
else: raise newException(ValueError, "Illegal kind in let*")
ast = a2
env = let_env
# Continue loop (TCO)
of "quote":
return ast.list[1]
of "quasiquote":
ast = ast.list[1].quasiquote
# Continue loop (TCO)
of "do":
let last = ast.list.high
let el = (list ast.list[1 .. <last]).eval_ast(env)
ast = ast.list[last]
# Continue loop (TCO)
of "if":
let
a1 = ast.list[1]
a2 = ast.list[2]
cond = a1.eval(env)
if cond.kind in {Nil, False}:
if ast.list.len > 3: ast = ast.list[3]
else: ast = nilObj
else: ast = a2
of "fn*":
let
a1 = ast.list[1]
a2 = ast.list[2]
var env2 = env
let fn = proc(a: varargs[MalType]): MalType =
var newEnv = initEnv(env2, a1, list(a))
a2.eval(newEnv)
return malfun(fn, a2, a1, env)
else:
defaultApply()
else:
defaultApply()
proc print(exp: MalType): string = exp.pr_str
var repl_env = initEnv()
for k, v in ns.items:
repl_env.set(k, v)
repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env)))
var ps = commandLineParams()
repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str)))
# core.nim: defined using nim
proc rep(str: string): string {.discardable.} =
str.read.eval(repl_env).print
# core.mal: defined using mal itself
rep "(def! not (fn* (a) (if a false true)))"
rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
if paramCount() >= 1:
rep "(load-file \"" & paramStr(1) & "\")"
quit()
while true:
try:
let line = readLineFromStdin("user> ")
echo line.rep
except Blank: discard
except:
echo getCurrentExceptionMsg()
echo getCurrentException().getStackTrace()
|