about summary refs log tree commit diff stats
path: root/lisp.lua
blob: 08e0015ba858d5656657db6af4bb58a5086657db (plain) (blame)
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))))