about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorCase Duckworth2024-03-30 22:20:36 -0500
committerCase Duckworth2024-03-30 22:20:36 -0500
commitab8a02fd30451207578927c7e69aa397ad596459 (patch)
tree24803910776ed692f1610f44e35d0f23b9712ca3
parentSpecial-case '.', '...', '+', '-' (diff)
downloadlam-ab8a02fd30451207578927c7e69aa397ad596459.tar.gz
lam-ab8a02fd30451207578927c7e69aa397ad596459.zip
Read from ports now
-rw-r--r--Makefile2
-rw-r--r--base.lua257
-rw-r--r--core.lua75
-rw-r--r--eval.lua102
-rw-r--r--read.lua318
-rw-r--r--repl.lua48
-rw-r--r--type.lua202
7 files changed, 394 insertions, 610 deletions
diff --git a/Makefile b/Makefile index 0e1786c..51b3fc2 100644 --- a/Makefile +++ b/Makefile
@@ -13,7 +13,7 @@ luarepl:
13 13
14.PHONY: repl 14.PHONY: repl
15repl: 15repl:
16 $(LUA) -e 'require("repl").repl()' 16 $(LUA) -e 'require("repl").repl("lam> ")'
17 17
18.PHONY: test 18.PHONY: test
19test: 19test:
diff --git a/base.lua b/base.lua deleted file mode 100644 index b6e3a30..0000000 --- a/base.lua +++ /dev/null
@@ -1,257 +0,0 @@
1--- lam.base --- base environment
2
3local base = {}
4local type = require "type"
5local isNull, isa, totable = type.isNull, type.isa, type.totable
6local math = math
7
8base.env = {
9 -- Equivalence
10 ["eqv?"] =
11 function (r)
12 local a, b = r.car, r.cdr.car
13 if a == b then
14 return true
15 else
16 return false
17 end
18 end,
19 -- ["eq?"] = function (r) end, -- how would this be different to eqv?
20 -- Numbers
21 ["number?"] = function (r) return isa(r.car, "Number") end,
22 -- ["complex?"] = function (r) end,
23 -- ["real?"] = function (r) end,
24 -- ["rational?"] = function (r) end,
25 -- ["integer?"] = function (r) end,
26 -- ["exact?"] = function (r) end,
27 -- ["inexact?"] = function (r) end,
28 -- ["exact-integer?"] = function (r) end,
29 -- ["finite?"] = function (r) end,
30 -- ["infinite?"] = function (r) end,
31 ["="] =
32 function (r)
33 local n, r = r.car, r.cdr
34 while r.cdr do
35 if n ~= r.car then return false end
36 r = r.cdr
37 end
38 return true
39 end,
40 ["<"] =
41 function (r)
42 local n, r = r.car, r.cdr
43 while r.cdr do
44 if n >= r.car then return false end
45 r = r.cdr
46 end
47 return true
48 end,
49 [">"] =
50 function (r)
51 local n, r = r.car, r.cdr
52 while r.cdr do
53 if n <= r.car then return false end
54 r = r.cdr
55 end
56 return true
57 end,
58 ["<="] =
59 function (r)
60 local n, r = r.car, r.cdr
61 while r.cdr do
62 if n > r.car then return false end
63 r = r.cdr
64 end
65 return true
66 end,
67 [">="] =
68 function (r)
69 local n, r = r.car, r.cdr
70 while r.cdr do
71 if n < r.car then return false end
72 r = r.cdr
73 end
74 return true
75 end,
76 -- Math
77 ["+"] =
78 function (r)
79 local r, a = r, 0
80 while r.cdr do
81 r, a = r.cdr, a + r.car
82 end
83 return a
84 end,
85 ["-"] =
86 function (r)
87 if isNull(r) then return -1 end
88 if isNull(r.cdr) then return (- r.car) end
89 local r, a = r.cdr, r.car
90 while r.cdr do
91 r, a = r.cdr, a - r.car
92 end
93 return a
94 end,
95 ["*"] =
96 function (r)
97 local r, a = r, 1
98 while r.cdr do
99 if r.cdr == 0 then return 0 end
100 r, a = r.cdr, a * r.car
101 end
102 return a
103 end,
104 ["/"] =
105 function (r)
106 if isNull(r) then error("Wrong arity") end
107 if isNull(r.cdr) then return (1 / r.car) end
108 local r, a = r.cdr, r.car
109 while r.cdr do
110 r, a = r.cdr, a / r.car
111 end
112 return a
113 end,
114 quotient =
115 function (r)
116 end,
117 remainder =
118 function (r)
119 end,
120 modulo =
121 function (r)
122 end,
123 -- numerator = function (r) end,
124 -- denominator = function (r) end,
125 abs = function (r) return math.abs(r.car) end,
126 floor = -- largest integer <= x
127 function (r) return math.floor(r.car) end,
128 ceiling = -- smallest integer >= x
129 function (r) return math.ceil(r.car) end,
130 truncate = -- smallest |integer| <= |x|
131 function (r)
132 local i, _ = math.modf(r.car)
133 return i
134 end,
135 round = -- closest integer to x (ties go even)
136 function (r)
137 local i, f = math.modf(r.car)
138 if f == 0.5 then
139 if i % 2 == 0 then
140 return i
141 else
142 return i+1
143 end
144 else
145 return i -- is this right?
146 end
147 end,
148 -- Trig
149 exp = function (r) return math.exp(r.car) end,
150 log = function (r) return math.log(r.car) end,
151 pi = math.pi, -- extension
152 sin = function (r) return math.sin(r.car) end,
153 cos = function (r) return math.cos(r.car) end,
154 tan = function (r) return math.tan(r.car) end,
155 asin = function (r) return math.asin(r.car) end,
156 acos = function (r) return math.acos(r.car) end,
157 atan = -- the two-argument variant of atan computes
158 -- (angle (make-rectangular x y)), even in implementations that
159 -- don't support general complex numbers [ed. note: whatever
160 -- that means] --- atan2 ???
161 function (r) return math.atan(r.car) end,
162 sqrt = function (r) return math.sqrt(r.car) end,
163 expt = function (r) return r.car ^ r.cdr.car end,
164 -- ["make-rectangular"] = function (r) end,
165 -- ["make-polar"] = function (r) end,
166 -- ["real-part"] = function (r) end,
167 -- ["imag-part"] = function (r) end,
168 -- ["magnitude"] = function (r) end,
169 -- ["angle"] = function (r) end,
170 -- ["exact->inexact"] = function (r) end,
171 -- ["inexact->exact"] = function (r) end,
172 ["number->string"] =
173 function (r)
174 -- this will be somewhat complicated
175 end,
176 ["string->number"] =
177 function (r)
178 local n = r.car
179 if not isNull(r.cdr) then
180 local radix = r.cdr.car
181 end
182 -- This is technically an extension to r5rs
183 return tonumber(n, radix)
184 end,
185 -- Pairs
186 ["pair?"] = function (r) end,
187 cons = function (r) return type.Cons(r.car, r.cdr.car) end,
188 car = function (r) return r.car.car end,
189 cdr = function (r) return r.car.cdr end,
190 ["set-car!"] = function (r) r.car.car = r.cdr.car end,
191 ["set-cdr!"] = function (r) r.car.cdr = r.cdr.car end,
192 -- cxr
193 ["null?"] = function (r) return isNull(r.car) end,
194 ["list?"] = function (r) return type.isList(r.car) end,
195 list = function (r) return r end, -- r is already a list
196 -- Symbols
197 ["symbol?"] = function (r) return isa(r.car, "Symbol") end,
198 ["symbol->string"] = function (r) return type.String(r.car) end,
199 ["string->symbol"] = function (r) return type.Symbol(r.car.value) end,
200 -- Characters
201 ["char?"] = function (r) end,
202 ["char=?"] = function (r) end,
203 ["char<?"] = function (r) end,
204 ["char>?"] = function (r) end,
205 ["char<=?"] = function (r) end,
206 ["char>=?"] = function (r) end,
207 ["char->integer"] = function (r) end,
208 ["integer->char"] = function (r) end,
209 -- Strings
210 ["string?"] = function (r) end,
211 ["make-string"] = function (r) end,
212 ["string-length"] = function (r) end,
213 ["string-ref"] = function (r) end,
214 -- ["string-set!"] = function (r) end, -- not sure if i'll implement
215 -- Vectors
216 ["vector?"] = function (r) end,
217 ["make-vector"] = function (r) end,
218 ["vector-length"] = function (r) end,
219 ["vector-ref"] = function (r) end,
220 ["vector-set!"] = function (r) end,
221 -- Control
222 ["procedure?"] = function (r) end,
223 apply = function (r) end,
224 ["call-with-current-continuation"] = function (r) end,
225 values = function (r) end,
226 ["call-with-values"] = function (r) end,
227 ["dynamic-wind"] = function (r) end,
228 -- Eval
229 eval = function (r) end,
230 ["scheme-report-environment"] = function (r) end,
231 ["null-environment"] = function (r) end,
232 -- Ports
233 ["input-port?"] = function (r) end,
234 ["output-port?"] = function (r) end,
235 ["current-input-port"] = function (r) end,
236 ["current-output-port"] = function (r) end,
237 ["open-input-file"] = function (r) end,
238 ["open-output-file"] = function (r) end,
239 ["close-input-port"] = function (r) end,
240 ["close-output-port"] = function (r) end,
241 -- Input
242 read = function (r) end,
243 ["read-char"] = function (r) end,
244 ["peek-char"] = function (r) end,
245 ["eof-object?"] = function (r) end,
246 ["char-ready?"] = function (r) end,
247 -- Output
248 write = function (r) end,
249 display = function (r) end,
250 newline = function (r) end,
251 ["write-char"] = function (r) end,
252 -- System
253 load = function (r) end,
254}
255
256---
257return base
diff --git a/core.lua b/core.lua new file mode 100644 index 0000000..e8ad42b --- /dev/null +++ b/core.lua
@@ -0,0 +1,75 @@
1--- lam.core --- core procedures
2
3local m = {}
4local type = require "type"
5local isa, null = type.isa, type.null
6local math = math
7
8local function fold (kons, knil, r)
9 if r == null then
10 return knil
11 else
12 local step, early_return = kons(r[1], knil)
13 if early_return then return step end
14 return fold(kons, step, r[2])
15 end
16end
17
18m.env = { -- all functions here take R, which is the list of arguments
19 ------- numbers
20 ["number?"] = function (r) return isa(r[1], "number") end,
21 ["="] =
22 function (r)
23 local function go (a, b)
24 if a ~= b then return false, 1 end
25 return b
26 end
27 return fold(go, r[1], r[2]) and true
28 end,
29 ["<"] =
30 function (r)
31 local function go (a, b)
32 if a >= b then return false, 1 end
33 return b
34 end
35 return fold(go, r[1], r[2]) and true
36 end,
37 [">"] =
38 function (r)
39 local function go (a, b)
40 if a <= b then return false, 1 end
41 return b
42 end
43 return fold(go, r[1], r[2]) and true
44 end,
45 ["<="] = function (r) return not m.env[">"](r) end,
46 [">="] = function (r) return not m.env["<"](r) end,
47 ------- math
48 ["+"] =
49 function (r)
50 return fold(function (a, b) return a + b end, 0, r)
51 end,
52 ["-"] =
53 function (r)
54 if r == null then return -1 end
55 if r[2] == null then return (- r[1]) end
56 return fold(function (a, b) return a-b end, r[1], r[2])
57 end,
58 ["*"] =
59 function (r)
60 local function go (a, b)
61 if a == 0 or b == 0 then return 0, 1 end
62 return a * b
63 end
64 return fold(go, 1, r)
65 end,
66 ["/"] =
67 function (r)
68 if r == null then error("Wrong arity") end
69 if r[2] == null then return (1 / r[1]) end
70 return fold(function (a, b) return a/b end, r[1], r[2])
71 end,
72}
73
74--------
75return m
diff --git a/eval.lua b/eval.lua index 610b902..4a4ad0e 100644 --- a/eval.lua +++ b/eval.lua
@@ -1,114 +1,100 @@
1--- lam.eval 1--- lam.eval
2 2
3local eval = {} 3local m = {}
4local base = require "base" 4local core = require "core"
5local type = require "type" 5local type = require "type"
6local isNull, isList, isa, List, Cons =
7 type.isNull, type.isList, type.isa, type.List, type.Cons
8local unpack = table.unpack or unpack
9 6
10function eval.Env (inner, outer) 7function m.environ (inner, outer)
11 local mt = { 8 local mt = {
12 __type = "Environment", 9 __type = "environment",
13 __index = outer, 10 __index = outer,
14 __newindex = 11 __newindex =
15 function (self, key, value) 12 function (self, key, val)
16 if rawget(self, key) then 13 if rawget(self, key) then
17 -- Set the current environment's value 14 rawset(self, key, val)
18 rawset(self, key, value)
19 else 15 else
20 -- Set the outer value 16 getmetatable(self).__index[key] = val
21 getmetatable(self).__index[key] = value
22 end 17 end
23 end, 18 end,
24 } 19 }
25 return setmetatable(inner, mt) 20 return setmetatable(inner, mt)
26end 21end
27 22
28function eval.Proc (params, body, env) 23function m.procedure (params, body, env)
29 local v = { 24 local t = {
30 params = params, 25 params = params,
31 body = body, 26 body = body,
32 env = env, 27 env = env,
33 } 28 }
34 local mt = { 29 local mt = {
35 __type = "Procedure", 30 __type = "procedure",
36 __call = 31 __call =
37 function (self, args) 32 function (self, args)
38 local inner = {} 33 local inner = {}
39 local p, a = self.params, args 34 local p, a = self.params, args
40 while p.cdr and a.cdr do 35 while p[2] and a[2] do
41 inner[p.car] = a.car 36 inner[p[1]] = a[1]
42 p, a = p.cdr, a.cdr 37 p, a = p[2], a[2]
43 end 38 end
44 local b = self.body 39 local b = self.body
45 local e = eval.Env(inner, self.env) 40 local e = m.environ(inner, self.env)
46 while not isNull(b.cdr) do 41 while not b[2] == type.null do
47 eval.eval(b.car, e) 42 m.eval(b[1], e)
48 b = b.cdr 43 b = b[2]
49 end 44 end
50 return eval.eval(b.car, e) 45 return m.eval(b[1], e)
51 end, 46 end,
52 } 47 }
53 return setmetatable(v, mt) 48 return setmetatable(t, mt)
54end 49end
55 50
56local specials = { 51local specials = {
52 -- each of these takes R (a list of args) and E (an environment)
57 quote = 53 quote =
58 function (args, env) 54 function (r, e) return r[1] end,
59 return args.car
60 end,
61 define = 55 define =
62 function (args, env) 56 function (r, e) rawset(e, r[1], m.eval(r[2][1], e)) end,
63 rawset(env, args.car, eval(args.cdr.car, env))
64 return nil
65 end,
66 lambda = 57 lambda =
67 function (args, env) 58 function (r, e) return m.procedure(r[1], r[2], e) end,
68 return Proc(args.car, args.cdr, env)
69 end,
70 ["set!"] = 59 ["set!"] =
71 function (args, env) 60 function (r, e) e[r[1]] = m.eval(r[2][1], e) end,
72 env[args.car] = eval(args.cdr.car, env)
73 return nil
74 end,
75 ["if"] = 61 ["if"] =
76 function (args, env) 62 function (r, e)
77 local test, conseq, alt = 63 local test, conseq, alt =
78 args.car, args.cdr.car, args.cdr.cdr.car 64 r[1], r[2][1], r[2][2][1]
79 if eval(test) 65 if m.eval(test)
80 then return eval(conseq) 66 then return m.eval(conseq)
81 else return eval(alt) 67 else return m.eval(alt)
82 end 68 end
83 end, 69 end,
84 -- TODO: include, import, define-syntax, define-values(?) ... 70 -- TODO: include, import, define-syntax, ...
85} 71}
86-- Aliases 72-- Aliases
87specials.lam = specials.lambda 73specials.lam = specials.lambda
88specials.def = specials.define 74specials.def = specials.define
89 75
90function eval.eval (x, env) 76function m.eval (x, env)
91 env = env or base.env 77 local env = env or core.env
92 if isa(x, "Symbol") then 78 if type.isa(x, "symbol") then
93 return env[x] 79 return env[x]
94 elseif not isList(x) then 80 elseif not type.islist(x) then
95 return x 81 return x
96 else 82 else
97 local op, args = x.car, x.cdr 83 local op, args = x[1], x[2]
98 if specials[op] then 84 if specials[op] then
99 return specials[op](args, env) 85 return specials[op](args, env)
100 else -- procedure 86 else -- procedure call
101 local proc = eval.eval(op, env) 87 local fn = m.eval(op, env)
102 local params = {} 88 local params = {}
103 local a = args 89 local r = args
104 while a.cdr do 90 while r[2] do
105 table.insert(params, eval.eval(a.car, env)) 91 table.insert(params, m.eval(r[1], env))
106 a = a.cdr 92 r = r[2]
107 end 93 end
108 return proc(List(params)) 94 return fn(type.list(params))
109 end 95 end
110 end 96 end
111end 97end
112 98
113--- 99--------
114return eval 100return m
diff --git a/read.lua b/read.lua index f23c5cc..226af51 100644 --- a/read.lua +++ b/read.lua
@@ -1,230 +1,170 @@
1--- lam.read 1--- lam.read
2 2
3local read = {} 3local m = {}
4local type = require "type" 4local t = require "type"
5local utf8 = require "utf8" 5local utf8 = require "utf8"
6local util = require "util" 6local pop = require("util").pop
7local pop = util.pop 7
8local unpack = table.unpack or unpack 8local pp = require("pp").pp
9 9
10local function program_characters (program) 10function m.inport (source, kind)
11 local chars = {} 11 -- KIND can be one of "file", "string"; defaults to "file"
12 for pos, code in utf8.codes(program) do 12 -- SOURCE is the name of the file or the string to read, or nil; if nil,
13 table.insert(chars, code) 13 -- read from standard input.
14 end 14 local f, l
15 return chars 15 local k = kind or "file"
16end 16 if source then
17 17 if kind == "file" then
18local function consume_string_whitespace (chars) 18 f = io.open(source, "r")
19 -- \<intraline ws>*<line ending> <intraline ws>* : nothing 19 elseif kind == "string" then
20 local s = {"\\"} 20 l = m.tochars(source)
21 while chars[1]:match("[ \t]") do 21 end
22 table.insert(s, pop(chars)) 22 else
23 end 23 -- KIND is ignored here
24 if chars[1] ~= "\n" then 24 f = io.input()
25 table.insert(s, chars[1])
26 return table.concat(s), chars
27 end
28 while chars[1]:match("%s") do
29 pop(chars)
30 end 25 end
31 return chars[1], chars 26 local t = {
32end 27 file = f,
33 28 line = l or {},
34local function consume_string_hexvalue (chars) 29 next_token =
35 -- \x<hex scalar value>; : specified character 30 function (self)
36 local u8ch = {} 31 local tok, toktype
37 repeat 32 while true do
38 local c = pop(chars) 33 if #self.line == 0 and self.file then
39 table.insert(u8ch, c) 34 self.line = m.tochars(
40 until c == ";" 35 self.file:read("*l"))
41 table.remove(u8ch) -- remove semicolon 36 end
42 return utf8.char(tonumber(table.concat(u8ch), 16)), chars 37 if not self.line or #self.line == 0 then
43end 38 return nil
44 39 end
45local function consume_string (chars) 40 tok, toktype, self.line =
46 local str = {} 41 m.scan(self.line)()
47 local backslash = { 42 if tok then return tok, toktype end
48 a = "\a",
49 b = "\b",
50 t = "\t",
51 n = "\n",
52 r = "\r",
53 ["\""] = "\"",
54 ["\\"] = "\\",
55 ["|"] = "|",
56 [" "] = consume_string_whitespace,
57 ["\t"] = consume_string_whitespace,
58 ["\n"] = consume_string_whitespace,
59 x = consume_string_hexvalue,
60 }
61 pop(chars) -- throw initial " away
62 repeat
63 local c = pop(chars)
64 if c == [[\]] then
65 c = chars[1]
66 if backlash[c] then
67 if type(backslash[c]) == "function" then
68 c, chars = backslash[c](chars)
69 table.insert(str, c)
70 else
71 table.insert(str, backlash[c])
72 end 43 end
73 else 44 end,
74 table.insert(str, "\\"..c) 45 }
75 end 46 if t.file then t.close = function (self) self.file:close() end; end
76 pop(chars) 47 local mt = {
77 elseif c == [["]] then 48 __type = "port",
78 break 49 __tostring =
79 else 50 function (self)
80 table.insert(str, c) 51 return string.format("#<port %s>",
81 end 52 self.file or "(string)")
82 until #chars == 0 53 end,
83 return table.concat(str), "string", chars 54 }
55 return setmetatable(t, mt)
84end 56end
85 57
86local function consume_token (chars) 58function m.tochars (s)
87 local tok = {} 59 local chars = {}
88 while #chars>0 and chars[1]:match("[^%s()\"#'`,@;]") do 60 for _, code in utf8.codes(s) do
89 table.insert(tok, pop(chars)) 61 table.insert(chars, code)
90 end 62 end
91 return table.concat(tok), chars 63 return chars
92end 64end
93 65
94local consume_symbol = consume_token 66--- Consumers
67-- These take a table of characters (cs) and return a token and the rest of the
68-- chars
95 69
96local function consume_number (chars) 70local function consume_token (cs)
97 local digits, chars = consume_token(chars) 71 local token = {}
98 -- The signs by themselves are symbols, as well as '...' 72 while #cs > 0 and cs[1]:match("[^%s()\"#'`,@;]") do
99 if digits:match("[-+.]") or digits == "..." then 73 table.insert(token, pop(cs))
100 return digits, chars
101 end 74 end
102 -- Otherwise try converting the digits to a number 75 return table.concat(token), cs
103 local num = tonumber(digits)
104 if num == nil then error("Bad number: " .. num) end
105 return num, chars
106end 76end
107 77
108local function consume_whitespace (chars) 78local function consume_whitespace (cs)
109 while #chars>0 and chars[1]:match("%s") do pop(chars) end 79 while #cs > 0 and cs[1]:match("%s") do pop(cs) end
110 return chars 80 return nil, cs
111end 81end
112 82
113local function consume_comment (chars) 83--- Reading from a port
114 local comment = {}
115 repeat
116 table.insert(comment, pop(chars))
117 until #chars == 0 or chars[1]:match("\n")
118 return table.concat(comment), "comment", chars
119end
120 84
121--- API 85m.readtable = {
122 86 ["("] = function (cs) return pop(cs), "open", cs end,
123read.readtable = { 87 [")"] = function (cs) return pop(cs), "close", cs end,
124 ["("] = function(chars) return pop(chars), "open", chars end,
125 [")"] = function(chars) return pop(chars), "close", chars end,
126 ["'"] = function(chars) return pop(chars), "quote", chars end,
127 ["`"] = function(chars) return pop(chars), "quote", chars end,
128 [","] = function(chars) return pop(chars), "quote", chars end,
129 ["\""] = consume_string,
130 [";"] = consume_comment,
131 -- ["#"] = ...,
132} 88}
133 89
134function read.scan (chars) 90-- Return an iterator over a character table, so you can do:
135 local chars = chars 91-- for token, chars in scan(cs) do ... end
92function m.scan (cs)
93 local cs = cs
136 return function () 94 return function ()
137 if not next(chars) then return nil end 95 if not next(cs) then return nil end
138 local token, toktype = "", nil 96 local token, toktype
139 while true do 97 while true do
140 if read.readtable[chars[1]] then 98 if m.readtable[cs[1]] then
141 token, toktype, chars = 99 token, toktype, cs = m.readtable[cs[1]](cs)
142 read.readtable[chars[1]](chars) 100 -- return { v = token, u = toktype }, cs
143 return token, toktype 101 return token, toktype, cs
144 elseif chars[1]:match("%s") then 102 elseif cs[1]:match("%s") then
145 chars = consume_whitespace(chars) 103 _, cs = consume_whitespace(cs)
146 elseif chars[1]:match("%d") then 104 return nil, nil, cs
147 token, chars = consume_number(chars) 105 -- return nil, cs
148 return token, "number" 106 elseif cs[1]:match("[%d.+-]") then
149 elseif chars[1]:match("[.+-]") then 107 -- numbers, +, -, ., ...
150 -- special casing for ., ..., +, - 108 local token, cs = consume_token(cs)
151 token, chars = consume_number(chars) 109 if token:match("[-+]") or token == "..." then
152 if token == "." then 110 return token, "symbol", cs
153 return token, "dot" 111 -- return { v = token, u = "symbol" }, cs
154 elseif token == "..." then 112 elseif token == "." then
155 return token, "symbol" 113 return token, "dot", cs
114 -- return { v = token, u = "dot" }, cs
156 else 115 else
157 return token, "number" 116 local n = tonumber(token)
117 assert (n ~= nil, "Bad number: "..n)
118 return n, "number", cs
119 -- return { v = n, u = "number" }, cs
158 end 120 end
159 else 121 else
160 token, chars = consume_symbol(chars) 122 token, cs = consume_token(cs)
161 return token, "symbol" 123 return token, "symbol", cs
124 -- return { v = token, u = "symbol" }, cs
162 end 125 end
163 if #chars == 0 then return nil end
164 end 126 end
165 end 127 end
166end 128end
167 129
168function read.tokenize (program) 130function m.readchar (port)
169 if not program or #program == 0 then return nil end 131 if #port.line > 0 then
170 local tokens = {} 132 local ch = pop(port.line)
171 for token, toktype in read.scan(program_characters(program)) do 133 return ch
172 table.insert(tokens, {type = toktype, value = token}) 134 else
135 return port.file.read(1)
173 end 136 end
174 return tokens
175end 137end
176 138
177read.readmacros = { 139function m.read (port)
178 open = 140 local function read_ahead (tok, toktype)
179 function (token, tokens) 141 if not tok then error("Unexpected EOF") end
180 local L, lt = {}, nil 142 if toktype == "open" then
181 while tokens[1].type ~= "close" do 143 local L = {}
182 local nt = read.parse(tokens) 144 while true do
183 -- this isn't .. my /favorite/ implementation, 145 local tok, toktype = port:next_token()
184 -- but it works 146 if toktype == "close" then
185 if nt == "." then 147 return t.list(L)
186 lt = read.parse(tokens)
187 break
188 else 148 else
189 table.insert(L, nt) 149 table.insert(L,
150 read_ahead(tok, toktype))
190 end 151 end
191 assert(tokens[1], "Unexpected EOF")
192 end 152 end
193 pop(tokens) -- remove final ")" 153 elseif toktype == "close" then
194 return type.List(L, lt) 154 error("Unexpected ')'")
195 end, 155 else return tok
196 close = 156 end
197 function (token, tokens) 157 end
198 error ("Unexpected '" .. token.value .. "'") 158 -- body of read
199 end, 159 local tok1, toktype1 = port:next_token()
200 quote = 160 if not tok1 then return nil
201 function (token, tokens) 161 else return read_ahead(tok1, toktype1)
202 local Q
203 if token.value == "'" then
204 Q = {"quote"}
205 elseif token.value == "`" then
206 Q = {"quasiquote"}
207 elseif token.value == "," then
208 Q = {"unquote"}
209 end
210 table.insert(Q, read.parse(tokens))
211 return type.List(Q)
212 end,
213}
214
215function read.parse (tokens)
216 if not next(tokens) then return nil end
217 local token = pop(tokens)
218 if read.readmacros[token.type] then
219 return read.readmacros[token.type](token, tokens)
220 else
221 return token.value
222 end 162 end
223end 163end
224 164
225function read.read (program) 165function m.read_string (str)
226 return read.parse(read.tokenize(program)) 166 return m.read(m.inport(str, "string"))
227end 167end
228 168
229--- 169---
230return read 170return m
diff --git a/repl.lua b/repl.lua index b198880..556525c 100644 --- a/repl.lua +++ b/repl.lua
@@ -1,10 +1,12 @@
1--- lam.repl 1--- lam.repl
2 2
3local repl = {} 3local m = {}
4local eval = require("eval").eval 4local read = require("read")
5local read = require("read").read 5local eval = require("eval")
6local pp = require("pp").pp
6 7
7local function schemeprint (x) 8local function schemeprint (x)
9 -- if x == nil then return end
8 if x == true then 10 if x == true then
9 print("#t") 11 print("#t")
10 elseif x == false then 12 elseif x == false then
@@ -14,19 +16,33 @@ local function schemeprint (x)
14 end 16 end
15end 17end
16 18
17function repl.repl (prompt) 19function m.repl (prompt, infile, out)
18 if not prompt then prompt = "lam> " end 20 -- PROMPT should be a string, INFILE is a filename, and OUT is either a
19 io.input():setvbuf("line") 21 -- filename, nil (in which case it will be stdout), or false (which
20 repeat 22 -- suppresses output)
21 io.write(prompt) 23 local inport = read.inport(infile)
22 io.output():flush() 24 if out ~= false then io.output(out) end
23 local input = io.read() 25 io.output():setvbuf("line")
24 if input ~= "" then 26 if prompt then
25 local value = eval(read(input)) 27 stderr = io.open("/dev/stderr", "w") -- Linux-only !
26 if value ~= nil then schemeprint(value) end 28 end
29 while true do
30 if prompt then
31 stderr:write(prompt)
32 stderr:flush()
33 end
34 local x = read.read(inport)
35 if x then
36 local val = eval.eval(x)
37 if out ~= false then
38 schemeprint(val)
39 end
27 end 40 end
28 until false 41 end
42 inport:close()
43 stderr:close()
44 io.output():close()
29end 45end
30 46
31--- 47--------
32return repl 48return m
diff --git a/type.lua b/type.lua index 0a0c62d..3c26188 100644 --- a/type.lua +++ b/type.lua
@@ -1,138 +1,162 @@
1--- lam.type 1--- lam.type
2-- lisp types
3 2
4local t = {} 3local m = {}
5local util = require "util" 4local utf8 = require "utf8"
6local unpack = table.unpack or unpack 5utf_char, utf_codepoint = utf8.char, utf8.codepoint
7 6
8--- Determining types 7--- atomic types
9 8
10t.luatype = type 9-- true, false and nil are just ... true, false, and nil
11 10
12function t.lamtype (x) 11-- Characters contain both their string reputations and their codepoints
13 if t.luatype(x) == "number" then 12function m.character (x)
14 return "Number" 13 -- is storing a character with its string and numerical representation
15 elseif t.luatype(x) == "string" then 14 -- overkill? ... maybe.
16 return "Symbol" 15 local s = tostring(x)
17 elseif getmetatable(x) and getmetatable(x).__type then 16 local uc = utf_codepoint(s)
18 return getmetatable(x).__type 17 local t = { -- String representation of the character
19 else 18 v = utf_char(uc),
20 return t.luatype(x) 19 u = uc,
21 end 20 }
22end 21 local mt = {
23 22 __type = "character",
24-- isa is really only useful on basic types (i.e., not Lists) 23 __eq = function (self) return self.v end,
25function t.isa (x, type) 24 __lt = function (a, b) return a.u < b.u end,
26 return t.lamtype(x) == type 25 __tostring =
26 function (self)
27 local v = self.v
28 if v == "\n" then
29 return "#\\newline"
30 elseif v == " " then
31 return "#\\space"
32 else
33 return "#\\" .. v
34 end
35 end,
36 }
37 return setmetatable(t, mt)
27end 38end
28 39
29--- Creating types 40-- a symbol is just a string, unadorned. I was going to have a character be
30 41-- represented by a one-character string, but then it would be indistinguishable
31-- Symbols and Numbers are strings and numbers, respectively. At some point 42-- from a one-character symbol internally.
32-- I'll want to implement a full numeric tower and symbol tables or namespaces 43m.symbol = tostring
33-- or whatever, but today is not that day
34 44
35t.Symbol = tostring 45-- for now, number will just be lua's number. At *some* point, it will be the
36t.Number = tonumber 46-- whole numeric tower, yaaayyy
47m.number = tonumber
37 48
38-- Strings are (lightly) wrapped 49-- strings are wrapped strings
39function t.String (str) 50function m.string (x)
40 local v = { 51 local x = tostring(x)
41 value = str, 52 local t = {
53 v = x,
42 escape = 54 escape =
43 function (self) 55 function (self)
44 return self.gsub("[\\\"]", "\\%1") 56 return self.v:gsub("[\\\"]", "\\%1")
45 end, 57 end,
46 } 58 }
47 local mt = { 59 local mt = {
48 __type = "String", 60 __type = "string",
49 __tostring = 61 __tostring =
50 function (self) 62 function (self)
51 return string.format("\"%s\"", self:escape()) 63 return "\"" .. self:escape() .. "\""
52 end, 64 end,
53 } 65 }
54 return setmetatable(v, mt) 66 return setmetatable(t, mt)
55end 67end
56 68
57function t.totable (cons) 69-- null () is both an atom and a list (yay)
58 local out = {} 70-- this one is NOT a function
59 local car, cdr = cons.car, cons.cdr 71m.null = setmetatable({}, {
60 while cdr do 72 __type = "null",
61 table.insert(out, tostring(car)) 73 __tostring = function (self) return "()" end,
62 if t.luatype(cdr) == "table" then 74})
63 car = cdr.car 75
64 cdr = cdr.cdr 76--- collection types
65 else
66 table.insert(out, cdr)
67 break
68 end
69 end
70 return out
71end
72 77
73-- Conses are Lisp's fundamental collection type 78-- cons are lisp's fundamental collection type
74function t.Cons (a, b) 79function m.cons (a, b)
75 local v = { a, b, } 80 local t = { a, b, }
76 local mt = { 81 local mt = {
77 __type = "Cons", 82 __type = "cons",
78 __index =
79 function (self, key)
80 if key == "car" then
81 return self[1]
82 elseif key == "cdr" then
83 return self[2]
84 end
85 end,
86 __tostring = 83 __tostring =
87 function (self) 84 function (self)
88 local out = {} 85 local out = {}
89 local car, cdr = self.car, self.cdr 86 local car, cdr = self[1], self[2]
90 while cdr do 87 while cdr do
91 table.insert(out, tostring(car)) 88 table.insert(out, tostring(car))
92 if t.luatype(cdr) == "table" then 89 if m.luatype(cdr) == "table" then
93 car = cdr.car 90 car = cdr[1]
94 cdr = cdr.cdr 91 cdr = cdr[2]
95 else 92 else
96 table.insert(out, ".") 93 table.insert(out, ".")
97 table.insert(out, cdr) 94 table.insert(out, cdr)
98 break 95 break
99 end 96 end
100 end 97 end
101 return "("..table.concat(out, " ")..")" 98 return "(" .. table.concat(out, " ") .. ")"
102 end, 99 end,
103 } 100 }
104 return setmetatable(v, mt) 101 return setmetatable(t, mt)
105end 102end
106 103
107-- Null is the one value that is both an atom and a list 104-- lists are singly-linked cons cells
108t.Null = setmetatable({}, { 105function m.list (items, last)
109 __type = "Null", 106 -- ITEMS is a table and LAST is an optional final cdr. If it's nil, the
110 __tostring = function (self) return "()" end, 107 -- list is a "proper" list; that is, it ends in ().
111}) 108 local function tolist (base, items)
109 if #items == 0 then return base end
110 return tolist(m.cons(table.remove(items), base), items)
111 end
112 return tolist(last or m.null, items)
113end
112 114
113function t.isNull (x) 115-- convert a list to a lua table
114 return x == t.Null 116function m.totable (cons)
117 local t = {}
118 local car, cdr = cons[1], cons[2]
119 while cdr do
120 table.insert(t, car)
121 if m.luatype(cdr) == "table" then
122 car = cdr[1]
123 cdr = cdr[2]
124 else
125 table.insert(t, cdr)
126 end
127 end
128 return t
115end 129end
116 130
117-- Lists are chained Conses ending in Null 131-- testing types
118function t.List (items, last) 132
119 local function tolist (base, items) 133-- we love name collisions
120 if #items == 0 then return base end 134m.luatype = type
121 return tolist(t.Cons(table.remove(items), base), items) 135
136function m.lamtype (x)
137 if m.luatype(x) == "string" then
138 return "symbol"
139 elseif getmetatable(x) and getmetatable(x).__type then
140 return getmetatable(x).__type
141 else
142 return m.luatype(x)
122 end 143 end
123 return tolist(last or t.Null, items)
124end 144end
125 145
126function t.isList (x) 146function m.isa (x, t)
127 -- TODO: this does not detect circular lists yet 147 return m.lamtype(x) == t
128 if t.isNull(x) then 148end
149
150function m.islist (x)
151 -- TODO: detect circular lists
152 if x == m.null then
129 return true 153 return true
130 elseif t.isa(x, "Cons") then 154 elseif m.isa(x, "cons") then
131 return t.isList(x.cdr) 155 return m.islist(x[2])
132 else 156 else
133 return false 157 return false
134 end 158 end
135end 159end
136 160
137--- 161--------
138return t 162return m