about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorCase Duckworth2024-02-21 09:28:49 -0600
committerCase Duckworth2024-02-21 09:28:49 -0600
commit70ec5254814f9531e5ca2024465d0e01130306b7 (patch)
tree45a4d49115eca436a8467ce4cebfc8cee1f6f9c6
downloadlam-70ec5254814f9531e5ca2024465d0e01130306b7.tar.gz
lam-70ec5254814f9531e5ca2024465d0e01130306b7.zip
Initial commit
-rw-r--r--eval.lua145
-rw-r--r--pp.lua36
-rw-r--r--read.lua74
-rw-r--r--repl.lua41
-rw-r--r--types.lua37
-rw-r--r--util.lua41
6 files changed, 374 insertions, 0 deletions
diff --git a/eval.lua b/eval.lua new file mode 100644 index 0000000..3decded --- /dev/null +++ b/eval.lua
@@ -0,0 +1,145 @@
1--- lam.eval
2
3local eval = {}
4local read = require "read"
5local types = require "types"
6local util = require "util"
7local pp = require "pp"
8
9Env = types.Object:new {
10 __type = "Environment",
11 __extend =
12 function(self, parms, args, outer)
13 for _, p in ipairs(parms) do
14 for _, a in ipairs(args) do
15 self[p] = a
16 end
17 end
18 getmetatable(self).__index = outer
19 end,
20}
21
22Proc = types.Object:new {
23 __type = "Procedure",
24 __call =
25 function (self, args)
26 local e = Env:new()
27 e:__extend(self.parms,
28 util.table(args),
29 self.env)
30 return eval(self.body[1], e)
31 end
32}
33
34global_env = Env:new {
35 -- constants
36 ["#t"] = true,
37 ["#f"] = false,
38 -- basic math
39 ["+"] =
40 function (...)
41 print(...)
42 return util.reduce(
43 {...}, 0,
44 function (a, b) return a + b end)
45 end,
46 ["*"] =
47 function (...)
48 return util.reduce(
49 {...}, 1,
50 function (a, b) return a * b end)
51 end,
52 -- scheme predicates
53 ["null?"] =
54 function(x)
55 return x == {}
56 end,
57 ["number?"] =
58 function(x)
59 return types.Type(x) == "Number"
60 end,
61 ["symbol?"] =
62 function(x)
63 return types.Type(x) == "Symbol"
64 end,
65 -- scheme functions
66 ["apply"] =
67 function(fn, ...)
68 local args = {...}
69 local last = args[#args]
70 assert(type(last)=="table", "Bad apply")
71 table.remove(args)
72 for _,v in ipairs(last) do
73 table.insert(args, v)
74 end
75 return fn(table.unpack(args))
76 end,
77 ["begin"] =
78 function(...)
79 local xs = {...}
80 return xs[#xs]
81 end,
82 ["map"] =
83 function(fn, ...)
84 return util.map(fn, {...})
85 end,
86 ["car"] = util.car,
87 ["cdr"] = util.cdr,
88 ["list"] = function(...) return {...} end,
89}
90
91-- Math
92for k, v in pairs(math) do
93 global_env[k] = v
94end
95
96function eval.eval (x, env)
97 env = env or global_env
98 if types.Type(x) == "Symbol" then
99 return env[x]
100 elseif types.Type(x) ~= "List" then
101 return x
102 else
103 local op = util.car(x)
104 local args = util.cdr(x)
105 if op == "quote" then
106 return args[1]
107 elseif op == "define" then
108 local sym, exp = table.unpack(args)
109 env[sym] = eval(exp, env)
110 elseif op == "set!" then
111 local sym, exp = table.unpack(args)
112 env[sym] = eval(exp, env)
113 elseif op == "lambda" then
114 local parms = util.car(args)
115 local body = util.cdr(args)
116 return Proc:new {
117 parms = parms,
118 body = body,
119 env = env,
120 }
121 else -- procedure call
122 pp(op)
123 local proc = eval(op, env)
124 local vals = util.map(
125 function(v) return eval(v, env) end,
126 args)
127 return proc(table.unpack(vals))
128 end
129 end
130end
131
132---
133return setmetatable(eval, { __call =
134 function(_, x, env)
135 local success, result =
136 pcall(eval.eval, x, env)
137 if success then return result
138 else return ("ERROR: " .. result)
139 end
140 end
141})
142
143--[[
144 (begin (define sq (lambda (x) (* x x))) (define rep (lambda (f) (lambda (x) (f (f x))))))
145-- ]]
diff --git a/pp.lua b/pp.lua new file mode 100644 index 0000000..3710b07 --- /dev/null +++ b/pp.lua
@@ -0,0 +1,36 @@
1--- lam.pp
2
3local pp = {}
4
5function pp.dump (x, lvl)
6 lvl = lvl or 0
7 local space = string.rep(" ", lvl)
8 local output = ""
9 if type(x) == "table" then
10 local subo = ""
11 for k,v in pairs(x) do
12 if v == x then
13 v = "self"
14 else
15 v = pp.dump(v, lvl+2)
16 end
17 subo = subo .. string.format("\n%s[%s] = %s,",
18 (space.." "), k, v)
19 end
20 output = output .. string.format("\n%s{%s\n%s}",
21 space, subo, space)
22 else
23 output = output .. string.format("%s", x)
24 end
25 return output
26end
27
28function pp.pp (x)
29 print(pp.dump(x))
30end
31
32return setmetatable(pp, { __call =
33 function(_, x)
34 return pp.pp(x)
35 end,
36})
diff --git a/read.lua b/read.lua new file mode 100644 index 0000000..72bbd4d --- /dev/null +++ b/read.lua
@@ -0,0 +1,74 @@
1--- lam.read
2
3local read = {}
4
5local types = require "types"
6local util = require "util"
7
8function read.tokenize (str)
9 --[[ Convert a string of characters into a list of tokens ]]
10 assert(str, "No program given")
11 local tbl = {}
12 local word = ""
13 local push_word =
14 function ()
15 if word:len() > 0 then
16 table.insert(tbl, word)
17 word = ""
18 end
19 end
20
21 for c = 1, #str do
22 char = string.char(str:byte(c))
23 if char == " " or char == "\t" or char == "\n" then
24 push_word()
25 elseif char == "(" then
26 push_word()
27 table.insert(tbl, "(")
28 elseif char == ")" then
29 push_word()
30 table.insert(tbl, ")")
31 else
32 word = word .. char
33 end
34 end
35 push_word()
36 return tbl
37end
38
39function read.read (str)
40 -- [[ Read a scheme expression from a string ]]
41
42 local function Atom (token)
43 local n = tonumber(token)
44 if n then return n
45 else return tostring(token)
46 end
47 end
48
49 local function read_tokens (tokens)
50 --[[ Read a list of tokens from `tokenize' ]]
51 assert(next(tokens), "Unexpected EOF")
52 token = util.pop(tokens)
53 if token == "(" then
54 local L = {}
55 while tokens[1] ~= ")" do
56 table.insert(L, read_tokens(tokens))
57 end
58 util.pop(tokens) -- remove ")"
59 return L
60 elseif token == ")" then
61 error("Unexpected ')'")
62 else
63 return Atom(token)
64 end
65 end
66
67 return read_tokens(read.tokenize(str))
68end
69
70return setmetatable(read, { __call =
71 function(_, str)
72 return read.read(str)
73 end,
74})
diff --git a/repl.lua b/repl.lua new file mode 100644 index 0000000..3cdfe4e --- /dev/null +++ b/repl.lua
@@ -0,0 +1,41 @@
1--- lam.repl
2
3local repl = {}
4local eval = require "eval"
5local read = require "read"
6local util = require "util"
7
8function schemestr(x)
9 if type(x) == "table" then
10 local ts = "(" .. schemestr(util.pop(x))
11 for i,v in ipairs(x) do
12 ts = string.format("%s %s", ts, schemestr(v))
13 end
14 ts = ts .. ")"
15 return ts
16 elseif x == true then
17 return "#t"
18 elseif x == false then
19 return "#f"
20 else
21 return tostring(x)
22 end
23end
24
25function repl.repl (prompt)
26 prompt = prompt or "lam> "
27 repeat
28 io.write(prompt)
29 io.output():flush()
30 input = io.read()
31 if input == ",q" or input == ",quit" then
32 break
33 else
34 val = eval(read(input))
35 if val then print(schemestr(val)) end
36 end
37 until false
38end
39
40---
41return repl
diff --git a/types.lua b/types.lua new file mode 100644 index 0000000..042edce --- /dev/null +++ b/types.lua
@@ -0,0 +1,37 @@
1--- lam.types
2
3local types = {}
4
5function types.Type(x)
6 if type(x) == "string" then
7 -- Symbols are Lua strings
8 return "Symbol"
9 elseif type(x) == "number" then
10 -- Numbers are Lua numbers
11 return "Number"
12 elseif x.__type then
13 return x.__type
14 elseif type(x) == "table" then
15 -- Lists are Lua tables (non-adorned)
16 return "List"
17 else
18 return type(x)
19 end
20end
21
22types.Object = { __type = "Object" }
23function types.Object:new(o)
24 o = o or {}
25 setmetatable(o, self)
26 self.__index = self
27 return o
28end
29
30--- Boxed types
31
32-- Strings
33
34-- Lists
35
36---
37return types
diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..98536a1 --- /dev/null +++ b/util.lua
@@ -0,0 +1,41 @@
1--- lam.util
2
3local util = {}
4
5function util.table (x)
6 if type(x) == "table" then
7 return x
8 else
9 return { x }
10 end
11end
12
13function util.pop (tbl)
14 return table.remove(tbl, 1)
15end
16
17function util.car (tbl)
18 return tbl[1]
19end
20
21function util.cdr (tbl)
22 local t = {}
23 for i = 2, #tbl do t[i-1] = tbl[i] end
24 return t
25end
26
27function util.reduce (tbl, seed, fn)
28 if #tbl == 0 then return seed end
29 return util.reduce(tbl, fn(seed, util.pop(tbl)), fn)
30end
31
32function util.map (fn, tbl)
33 local out = {}
34 for k, v in pairs(tbl) do
35 out[k] = fn(v)
36 end
37 return out
38end
39
40---
41return util