diff options
-rw-r--r-- | Makefile | 19 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | base.lua | 37 | ||||
-rw-r--r-- | eval.lua | 39 | ||||
-rw-r--r-- | read.lua | 2 | ||||
-rw-r--r-- | repl.lua | 21 | ||||
-rw-r--r-- | test.lua | 43 | ||||
-rw-r--r-- | type.lua | 1 |
8 files changed, 109 insertions, 59 deletions
diff --git a/Makefile b/Makefile index 54c4f2c..f08ae06 100644 --- a/Makefile +++ b/Makefile | |||
@@ -1,4 +1,12 @@ | |||
1 | LUA ?= rlwrap luajit | 1 | LUA ?= rlwrap luajit \ |
2 | -e 'pp = require "pp"' \ | ||
3 | -e 'eval = require "eval"' \ | ||
4 | -e 'read = require "read"' \ | ||
5 | -e 'type = require "type"' \ | ||
6 | -e 'utf8 = require "utf8"' \ | ||
7 | -e 'util = require "util"' \ | ||
8 | -e 'test = require "test"' | ||
9 | |||
2 | 10 | ||
3 | .PHONY: repl | 11 | .PHONY: repl |
4 | repl: | 12 | repl: |
@@ -6,14 +14,7 @@ repl: | |||
6 | 14 | ||
7 | .PHONY: test | 15 | .PHONY: test |
8 | test: | 16 | test: |
9 | $(LUA) -i \ | 17 | $(LUA) -e 'test.runtests()' |
10 | -e 'pp = require "pp"' \ | ||
11 | -e 'eval = require "eval"' \ | ||
12 | -e 'read = require "read"' \ | ||
13 | -e 'type = require "type"' \ | ||
14 | -e 'utf8 = require "utf8"' \ | ||
15 | -e 'util = require "util"' \ | ||
16 | -e 'test = require "test"' | ||
17 | 18 | ||
18 | .PHONY: check | 19 | .PHONY: check |
19 | check: | 20 | check: |
diff --git a/README.md b/README.md index 3728ba6..1dcd392 100644 --- a/README.md +++ b/README.md | |||
@@ -36,6 +36,12 @@ see COPYING for details. | |||
36 | 36 | ||
37 | [email me]: mailto:acdw@acdw.net | 37 | [email me]: mailto:acdw@acdw.net |
38 | 38 | ||
39 | ## references | ||
40 | |||
41 | - [Lua 5.1 reference](https://www.lua.org/manual/5.1/manual.html) | ||
42 | - [Luajit](https://luajit.org/) | ||
43 | - [R7RS](https://standards.scheme.org/corrected-r7rs/r7rs.html) | ||
44 | |||
39 | ## thanks | 45 | ## thanks |
40 | 46 | ||
41 | thanks to peter norvig's [series on lisp interpreters][norvig] | 47 | thanks to peter norvig's [series on lisp interpreters][norvig] |
diff --git a/base.lua b/base.lua new file mode 100644 index 0000000..9c5b5b7 --- /dev/null +++ b/base.lua | |||
@@ -0,0 +1,37 @@ | |||
1 | --- lam.base --- base environment | ||
2 | |||
3 | local base = {} | ||
4 | local type = require "type" | ||
5 | local isNull = type.isNull | ||
6 | |||
7 | base.env = { | ||
8 | begin = | ||
9 | function (r) | ||
10 | local r = r | ||
11 | while not isNull(r.cdr) do | ||
12 | r = r.cdr | ||
13 | end | ||
14 | return r.car | ||
15 | end, | ||
16 | ["+"] = | ||
17 | function (r) | ||
18 | local r, a = r, 0 | ||
19 | while r.cdr do | ||
20 | r, a = r.cdr, a + r.car | ||
21 | end | ||
22 | return a | ||
23 | end, | ||
24 | ["-"] = | ||
25 | function (r) | ||
26 | if isNull(r) then return -1 end | ||
27 | if isNull(r.cdr) then return (- r.car) end | ||
28 | local r, a = r.cdr, r.car | ||
29 | while r.cdr do | ||
30 | r, a = r.cdr, a - r.car | ||
31 | end | ||
32 | return a | ||
33 | end, | ||
34 | } | ||
35 | |||
36 | --- | ||
37 | return base | ||
diff --git a/eval.lua b/eval.lua index 5e897e2..e926bc4 100644 --- a/eval.lua +++ b/eval.lua | |||
@@ -1,6 +1,7 @@ | |||
1 | --- lam.eval | 1 | --- lam.eval |
2 | 2 | ||
3 | local eval = {} | 3 | local eval = {} |
4 | local base = require "base" | ||
4 | local type = require "type" | 5 | local type = require "type" |
5 | local isNull, isList, isa, List, Cons = | 6 | local isNull, isList, isa, List, Cons = |
6 | type.isNull, type.isList, type.isa, type.List, type.Cons | 7 | type.isNull, type.isList, type.isa, type.List, type.Cons |
@@ -30,6 +31,7 @@ function eval.Proc (params, body, env) | |||
30 | inner[p.car] = a.car | 31 | inner[p.car] = a.car |
31 | p, a = p.cdr, a.cdr | 32 | p, a = p.cdr, a.cdr |
32 | end | 33 | end |
34 | pp.pp(self.body) | ||
33 | return eval.eval( | 35 | return eval.eval( |
34 | self.body, | 36 | self.body, |
35 | eval.Env(inner, self.env)) | 37 | eval.Env(inner, self.env)) |
@@ -38,37 +40,8 @@ function eval.Proc (params, body, env) | |||
38 | return setmetatable(v, mt) | 40 | return setmetatable(v, mt) |
39 | end | 41 | end |
40 | 42 | ||
41 | local global = { | ||
42 | begin = | ||
43 | function (r) | ||
44 | local r = r | ||
45 | while not isNull(r.cdr) do | ||
46 | r = r.cdr | ||
47 | end | ||
48 | return r.car | ||
49 | end, | ||
50 | ["+"] = | ||
51 | function (r) | ||
52 | local r, a = r, 0 | ||
53 | while r.cdr do | ||
54 | r, a = r.cdr, a + r.car | ||
55 | end | ||
56 | return a | ||
57 | end, | ||
58 | ["-"] = | ||
59 | function (r) | ||
60 | if isNull(r) then return -1 end | ||
61 | if isNull(r.cdr) then return (- r.car) end | ||
62 | local r, a = r.cdr, r.car | ||
63 | while r.cdr do | ||
64 | r, a = r.cdr, a - r.car | ||
65 | end | ||
66 | return a | ||
67 | end, | ||
68 | } | ||
69 | |||
70 | function eval.eval (x, env) | 43 | function eval.eval (x, env) |
71 | env = env or global | 44 | env = env or base.env |
72 | if isa(x, "Symbol") then | 45 | if isa(x, "Symbol") then |
73 | return env[x] | 46 | return env[x] |
74 | elseif not isList(x) then | 47 | elseif not isList(x) then |
@@ -77,13 +50,13 @@ function eval.eval (x, env) | |||
77 | local op, args = x.car, x.cdr | 50 | local op, args = x.car, x.cdr |
78 | if op == "quote" then | 51 | if op == "quote" then |
79 | return args.car | 52 | return args.car |
80 | elseif op == "define" then | 53 | elseif op == "define" or op == "def" then |
81 | env[args.car] = eval.eval(args.cdr.car, env) | 54 | env[args.car] = eval.eval(args.cdr.car, env) |
82 | return nil | 55 | return nil |
83 | elseif op == "lambda" then | 56 | elseif op == "lambda" or op == "lam" then |
84 | return eval.Proc( | 57 | return eval.Proc( |
85 | args.car, | 58 | args.car, |
86 | Cons("begin", args.cdr), | 59 | Cons("begin", args.cdr), -- i don't like this |
87 | env) | 60 | env) |
88 | elseif op == "if" then | 61 | elseif op == "if" then |
89 | assert(not isNull(args.cdr), "Malformed 'if'") | 62 | assert(not isNull(args.cdr), "Malformed 'if'") |
diff --git a/read.lua b/read.lua index c5dacc0..d21e4cb 100644 --- a/read.lua +++ b/read.lua | |||
@@ -7,7 +7,7 @@ local util = require "util" | |||
7 | local pop = util.pop | 7 | local pop = util.pop |
8 | local unpack = table.unpack or unpack | 8 | local unpack = table.unpack or unpack |
9 | 9 | ||
10 | function program_characters (program) | 10 | local function program_characters (program) |
11 | local chars = {} | 11 | local chars = {} |
12 | for pos, code in utf8.codes(program) do | 12 | for pos, code in utf8.codes(program) do |
13 | table.insert(chars, code) | 13 | table.insert(chars, code) |
diff --git a/repl.lua b/repl.lua new file mode 100644 index 0000000..34e4c94 --- /dev/null +++ b/repl.lua | |||
@@ -0,0 +1,21 @@ | |||
1 | --- lam.repl | ||
2 | |||
3 | local repl = {} | ||
4 | local eval = require("eval").eval | ||
5 | local read = require("read").read | ||
6 | |||
7 | function repl.repl (prompt) | ||
8 | if not prompt then prompt = "lam> " end | ||
9 | io.input():setvbuf("line") | ||
10 | repeat | ||
11 | io.write(prompt) | ||
12 | io.output():flush() | ||
13 | local input = io.read() | ||
14 | if input == nil then break end | ||
15 | local value = eval(read(input)) | ||
16 | if value then print(value) end | ||
17 | until false | ||
18 | end | ||
19 | |||
20 | --- | ||
21 | return repl | ||
diff --git a/test.lua b/test.lua index ce8c034..1d90df2 100644 --- a/test.lua +++ b/test.lua | |||
@@ -1,28 +1,39 @@ | |||
1 | --- lam.test | 1 | --- lam.test |
2 | -- testing helpers | ||
3 | 2 | ||
4 | local test = {} | 3 | local test = {} |
5 | local eval = require("eval").eval | 4 | local eval = require("eval").eval |
6 | local read = require("read").read | 5 | local read = require("read").read |
6 | local luatype = require("type").luatype | ||
7 | 7 | ||
8 | function test.lambda () | 8 | function test.test (form, expected) |
9 | local ls = { | 9 | local diag = string.format("%s == %s", form, expected) |
10 | [ [[((lambda (x) (+ x x)) 3)]] ] = 6, | 10 | local value = eval(read(form)) |
11 | [ [[((lambda () 100))]] ] = 100, | 11 | if value == expected then |
12 | [ [[((lambda (x) 1 2 3) 4)]] ] = 3, | 12 | print(string.format("ok: %s", diag)) |
13 | [ [[((lambda () 1 2 3))]] ] = 3, | 13 | else |
14 | [ [[((lambda (x) x (+ x x) (+ x x x)) 9)]] ] = 27, | 14 | print(string.format("not ok: %s != %s", diag, value)) |
15 | } | 15 | end |
16 | for l, target in pairs(ls) do | 16 | end |
17 | io.write(string.format("%s == %s\n\t", l, target)) | 17 | |
18 | local value = eval(read(l)) | 18 | function test.runtests () |
19 | if value == target then | 19 | for name, fn in pairs(test) do |
20 | print "ok" | 20 | if luatype(fn) == "function" |
21 | else | 21 | and name ~= "test" |
22 | print(string.format("not ok : %s", value)) | 22 | and name ~= "runtests" |
23 | then | ||
24 | print(">>>", name) | ||
25 | fn() | ||
23 | end | 26 | end |
24 | end | 27 | end |
25 | end | 28 | end |
26 | 29 | ||
30 | function test.lambda () | ||
31 | test.test([[((lambda (x) (+ x x)) 3)]], 6) | ||
32 | test.test([[((lambda () 100))]], 100) | ||
33 | test.test([[((lambda (x) 1 2 3) 4)]], 3) | ||
34 | test.test([[((lambda () 1 2 3))]], 3) | ||
35 | test.test([[((lambda (x) x (+ x x) (+ x x x)) 9)]], 27) | ||
36 | end | ||
37 | |||
27 | --- | 38 | --- |
28 | return test | 39 | return test |
diff --git a/type.lua b/type.lua index 945f4d1..0d3b776 100644 --- a/type.lua +++ b/type.lua | |||
@@ -31,6 +31,7 @@ end | |||
31 | -- Symbols and Numbers are strings and numbers, respectively. At some point | 31 | -- Symbols and Numbers are strings and numbers, respectively. At some point |
32 | -- I'll want to implement a full numeric tower and symbol tables or namespaces | 32 | -- I'll want to implement a full numeric tower and symbol tables or namespaces |
33 | -- or whatever, but today is not that day | 33 | -- or whatever, but today is not that day |
34 | |||
34 | t.Symbol = tostring | 35 | t.Symbol = tostring |
35 | t.Number = tonumber | 36 | t.Number = tonumber |
36 | 37 | ||