diff options
author | Case Duckworth | 2024-02-21 09:28:49 -0600 |
---|---|---|
committer | Case Duckworth | 2024-02-21 09:28:49 -0600 |
commit | 70ec5254814f9531e5ca2024465d0e01130306b7 (patch) | |
tree | 45a4d49115eca436a8467ce4cebfc8cee1f6f9c6 | |
download | lam-70ec5254814f9531e5ca2024465d0e01130306b7.tar.gz lam-70ec5254814f9531e5ca2024465d0e01130306b7.zip |
Initial commit
-rw-r--r-- | eval.lua | 145 | ||||
-rw-r--r-- | pp.lua | 36 | ||||
-rw-r--r-- | read.lua | 74 | ||||
-rw-r--r-- | repl.lua | 41 | ||||
-rw-r--r-- | types.lua | 37 | ||||
-rw-r--r-- | util.lua | 41 |
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 | |||
3 | local eval = {} | ||
4 | local read = require "read" | ||
5 | local types = require "types" | ||
6 | local util = require "util" | ||
7 | local pp = require "pp" | ||
8 | |||
9 | Env = 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 | |||
22 | Proc = 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 | |||
34 | global_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 | ||
92 | for k, v in pairs(math) do | ||
93 | global_env[k] = v | ||
94 | end | ||
95 | |||
96 | function 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 | ||
130 | end | ||
131 | |||
132 | --- | ||
133 | return 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 | |||
3 | local pp = {} | ||
4 | |||
5 | function 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 | ||
26 | end | ||
27 | |||
28 | function pp.pp (x) | ||
29 | print(pp.dump(x)) | ||
30 | end | ||
31 | |||
32 | return 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 | |||
3 | local read = {} | ||
4 | |||
5 | local types = require "types" | ||
6 | local util = require "util" | ||
7 | |||
8 | function 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 | ||
37 | end | ||
38 | |||
39 | function 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)) | ||
68 | end | ||
69 | |||
70 | return 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 | |||
3 | local repl = {} | ||
4 | local eval = require "eval" | ||
5 | local read = require "read" | ||
6 | local util = require "util" | ||
7 | |||
8 | function 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 | ||
23 | end | ||
24 | |||
25 | function 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 | ||
38 | end | ||
39 | |||
40 | --- | ||
41 | return 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 | |||
3 | local types = {} | ||
4 | |||
5 | function 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 | ||
20 | end | ||
21 | |||
22 | types.Object = { __type = "Object" } | ||
23 | function types.Object:new(o) | ||
24 | o = o or {} | ||
25 | setmetatable(o, self) | ||
26 | self.__index = self | ||
27 | return o | ||
28 | end | ||
29 | |||
30 | --- Boxed types | ||
31 | |||
32 | -- Strings | ||
33 | |||
34 | -- Lists | ||
35 | |||
36 | --- | ||
37 | return 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 | |||
3 | local util = {} | ||
4 | |||
5 | function util.table (x) | ||
6 | if type(x) == "table" then | ||
7 | return x | ||
8 | else | ||
9 | return { x } | ||
10 | end | ||
11 | end | ||
12 | |||
13 | function util.pop (tbl) | ||
14 | return table.remove(tbl, 1) | ||
15 | end | ||
16 | |||
17 | function util.car (tbl) | ||
18 | return tbl[1] | ||
19 | end | ||
20 | |||
21 | function util.cdr (tbl) | ||
22 | local t = {} | ||
23 | for i = 2, #tbl do t[i-1] = tbl[i] end | ||
24 | return t | ||
25 | end | ||
26 | |||
27 | function util.reduce (tbl, seed, fn) | ||
28 | if #tbl == 0 then return seed end | ||
29 | return util.reduce(tbl, fn(seed, util.pop(tbl)), fn) | ||
30 | end | ||
31 | |||
32 | function 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 | ||
38 | end | ||
39 | |||
40 | --- | ||
41 | return util | ||