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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
-- atom types:
-- nil
-- true
-- {num=3.4}
-- {char='a'}
-- {str='bc'}
-- {sym='foo'}
-- non-atom type:
-- {car={num=3.4}, cdr=nil}
--
-- should {} mean anything special? currently just '(nil)
function atom(x)
return x == nil or x.num or x.char or x.str or x.sym
end
function car(x) return x.car end
function cdr(x) return x.cdr end
function cons(x, y) return {car=x, cdr=y} end
function iso(x, y)
if x == nil then return y == nil end
local done={}
if done[x] then return done[x] == y end
done[x] = y
if atom(x) then
if not atom(y) then return nil end
for k, v in pairs(x) do
if y[k] ~= v then return nil end
end
return true
end
for k, v in pairs(x) do
if not iso(y[k], v) then return nil end
end
for k, v in pairs(y) do
if not iso(x[k], v) then return nil end
end
return true
end
-- primitives; feel free to add more
-- format: lisp name = lua function that implements it
unary_functions = {
atom=atom,
car=car,
cdr=cdr,
}
binary_functions = {
cons=cons,
iso=iso,
}
function lookup(env, s)
if env[s] then return env[s] end
if env.next then return lookup(env.next, s) end
end
function eval(x, env)
function symeq(x, s)
return x and x.sym == s
end
if x.sym then
return lookup(env, x.sym)
elseif atom(x) then
return x
-- otherwise x is a pair
elseif symeq(x.car, 'quote') then
return x.cdr
elseif unary_functions[x.car.sym] then
return eval_unary(x, env)
elseif binary_functions[x.car.sym] then
return eval_binary(x, env)
-- special forms that don't always eval all their args
elseif symeq(x.car, 'if') then
return eval_if(x, env)
elseif symeq(x.car.car, 'fn') then
return eval_fn(x, env)
elseif symeq(x.car.car, 'label') then
return eval_label(x, env)
end
end
function eval_unary(x, env)
return unary_functions[x.car.sym](eval(x.cdr.car, env))
end
function eval_binary(x, env)
return binary_functions[x.car.sym](eval(x.cdr.car, env),
eval(x.cdr.cdr.car, env))
end
function eval_if(x, env)
-- syntax: (if check b1 b2)
local check = x.cdr.car
local b1 = x.cdr.cdr.car
local b2 = x.cdr.cdr.cdr.car
if eval(check, env) then
return eval(b1, env)
else
return eval(b2, env)
end
end
function eval_fn(x, env)
-- syntax: ((fn params body*) args*)
local callee = x.car
local args = x.cdr
local params = callee.cdr.car
local body = callee.cdr.cdr
return eval_exprs(body,
bind_env(params, args, env))
end
function bind_env(params, args, env)
if params == nil then return env end
local result = {next=env}
while true do
result[params.car.sym] = eval(args.car, env)
params = params.cdr
args = args.cdr
if params == nil then break end
end
return result
end
function eval_exprs(xs, env)
local result = nil
while xs do
result = eval(xs.car, env)
xs = xs.cdr
end
return result
end
function eval_label(x, env)
-- syntax: ((label f (fn params body*)) args*)
local callee = x.car
local args = x.cdr
local f = callee.cdr.car
local fn = callee.cdr.cdr.car
return eval({car=fn, cdr=args},
bind_env({f}, {callee}, env))
end
-- testing
function num(n) return {num=n} end
function char(c) return {char=c} end
function str(s) return {str=s} end
function sym(s) return {sym=s} end
function list(...)
-- gotcha: no element in arg can be nil; that short-circuits the ipairs below
local result = nil
local curr = nil
for _, x in ipairs({...}) do
if curr == nil then
result = {car=x}
curr = result
else
curr.cdr = {car=x}
curr = curr.cdr
end
end
return result
end
function p(x)
p2(x)
print()
end
function p2(x)
if x == nil then
io.write('nil')
elseif x == true then
io.write('true')
elseif x.num then
io.write(x.num)
elseif x.char then
io.write("\\"..x.char)
elseif x.str then
io.write('"'..x.str..'"')
elseif x.sym then
io.write(x.sym)
elseif x.cdr == nil then
io.write('(')
p2(x.car)
io.write(')')
elseif atom(x.cdr) then
io.write('(')
p2(x.car)
io.write(' . ')
p2(x.cdr)
io.write(')')
else
io.write('(')
while true do
p2(x.car)
x = x.cdr
if x == nil then break end
if atom(x) then
io.write(' . ')
p2(x)
break
end
io.write(' ')
end
io.write(')')
end
end
x = {num=3.4}
p(x)
p(cons(x, nil))
p(list(x))
p(iso(cons(x, nil), cons(x, nil)))
p(iso(list(x), list(x)))
p(iso(list(x, x), list(x)))
p(iso(list(x, x), list(x, x)))
p(iso(x, cons(x, nil)))
p (list(sym("cons"), num(42), num(1)))
p(eval(list(sym("cons"), num(42), num(1)), {}))
-- ((fn () 42)) => 42
-- can't use list here because of the gotcha above
assert(iso(eval(cons(cons(sym('fn'), cons(nil, cons(num(42))))), {}), num(42)))
-- ((fn (a) (cons a 1)) 42) => '(42 . 1)
assert(iso(eval(cons(cons(sym('fn'), cons(cons(sym('a')), cons(cons(sym('cons'), cons(sym('a'), cons(num(1))))))), cons(num(42)))), cons(num(42), num(1))))
|