-- 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))))