Commit | Line | Data |
---|---|---|
9d42904e JM |
1 | #!/usr/bin/env lua |
2 | ||
3 | local table = require('table') | |
4 | ||
5 | local readline = require('readline') | |
6 | local utils = require('utils') | |
7 | local types = require('types') | |
8 | local reader = require('reader') | |
9 | local printer = require('printer') | |
10 | local Env = require('env') | |
11 | local core = require('core') | |
12 | local List, Vector, HashMap = types.List, types.Vector, types.HashMap | |
13 | ||
14 | -- read | |
15 | function READ(str) | |
16 | return reader.read_str(str) | |
17 | end | |
18 | ||
19 | -- eval | |
20 | function is_pair(x) | |
21 | return types._sequential_Q(x) and #x > 0 | |
22 | end | |
23 | ||
24 | function quasiquote(ast) | |
25 | if not is_pair(ast) then | |
26 | return types.List:new({types.Symbol:new("quote"), ast}) | |
27 | elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then | |
28 | return ast[2] | |
29 | elseif is_pair(ast[1]) and | |
30 | types._symbol_Q(ast[1][1]) and | |
31 | ast[1][1].val == 'splice-unquote' then | |
32 | return types.List:new({types.Symbol:new("concat"), | |
33 | ast[1][2], | |
34 | quasiquote(ast:slice(2))}) | |
35 | else | |
36 | return types.List:new({types.Symbol:new("cons"), | |
37 | quasiquote(ast[1]), | |
38 | quasiquote(ast:slice(2))}) | |
39 | end | |
40 | end | |
41 | ||
42 | function is_macro_call(ast, env) | |
43 | if types._list_Q(ast) and | |
44 | types._symbol_Q(ast[1]) and | |
45 | env:find(ast[1]) then | |
46 | local f = env:get(ast[1]) | |
47 | return types._malfunc_Q(f) and f.ismacro | |
48 | end | |
49 | end | |
50 | ||
51 | function macroexpand(ast, env) | |
52 | while is_macro_call(ast, env) do | |
53 | local mac = env:get(ast[1]) | |
54 | ast = mac.fn(unpack(ast:slice(2))) | |
55 | end | |
56 | return ast | |
57 | end | |
58 | ||
59 | function eval_ast(ast, env) | |
60 | if types._symbol_Q(ast) then | |
61 | return env:get(ast) | |
62 | elseif types._list_Q(ast) then | |
63 | return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) | |
64 | elseif types._vector_Q(ast) then | |
65 | return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) | |
66 | elseif types._hash_map_Q(ast) then | |
67 | local new_hm = {} | |
68 | for k,v in pairs(ast) do | |
69 | new_hm[EVAL(k, env)] = EVAL(v, env) | |
70 | end | |
71 | return HashMap:new(new_hm) | |
72 | else | |
73 | return ast | |
74 | end | |
75 | end | |
76 | ||
77 | function EVAL(ast, env) | |
78 | while true do | |
79 | --print("EVAL: "..printer._pr_str(ast,true)) | |
80 | if not types._list_Q(ast) then return eval_ast(ast, env) end | |
81 | ||
82 | -- apply list | |
83 | ast = macroexpand(ast, env) | |
0d629719 | 84 | if not types._list_Q(ast) then return eval_ast(ast, env) end |
9d42904e JM |
85 | |
86 | local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] | |
3c59a767 | 87 | if not a0 then return ast end |
9d42904e JM |
88 | local a0sym = types._symbol_Q(a0) and a0.val or "" |
89 | if 'def!' == a0sym then | |
90 | return env:set(a1, EVAL(a2, env)) | |
91 | elseif 'let*' == a0sym then | |
92 | local let_env = Env:new(env) | |
93 | for i = 1,#a1,2 do | |
94 | let_env:set(a1[i], EVAL(a1[i+1], let_env)) | |
95 | end | |
96 | env = let_env | |
97 | ast = a2 -- TCO | |
98 | elseif 'quote' == a0sym then | |
99 | return a1 | |
100 | elseif 'quasiquote' == a0sym then | |
101 | ast = quasiquote(a1) -- TCO | |
102 | elseif 'defmacro!' == a0sym then | |
103 | local mac = EVAL(a2, env) | |
104 | mac.ismacro = true | |
105 | return env:set(a1, mac) | |
106 | elseif 'macroexpand' == a0sym then | |
107 | return macroexpand(a1, env) | |
108 | elseif 'try*' == a0sym then | |
109 | local exc, result = nil, nil | |
110 | xpcall(function() | |
111 | result = EVAL(a1, env) | |
112 | end, function(err) | |
113 | exc = err | |
114 | end) | |
115 | if exc ~= nil then | |
116 | if types._malexception_Q(exc) then | |
117 | exc = exc.val | |
118 | end | |
119 | if a2 and a2[1].val == 'catch*' then | |
120 | result = EVAL(a2[3], Env:new(env, {a2[2]}, {exc})) | |
121 | else | |
122 | types.throw(exc) | |
123 | end | |
124 | end | |
125 | return result | |
126 | elseif 'do' == a0sym then | |
127 | local el = eval_ast(ast:slice(2,#ast-1), env) | |
128 | ast = ast[#ast] -- TCO | |
129 | elseif 'if' == a0sym then | |
130 | local cond = EVAL(a1, env) | |
131 | if cond == types.Nil or cond == false then | |
132 | if a3 then ast = a3 else return types.Nil end -- TCO | |
133 | else | |
134 | ast = a2 -- TCO | |
135 | end | |
136 | elseif 'fn*' == a0sym then | |
137 | return types.MalFunc:new(function(...) | |
138 | return EVAL(a2, Env:new(env, a1, arg)) | |
139 | end, a2, env, a1) | |
140 | else | |
141 | local args = eval_ast(ast, env) | |
142 | local f = table.remove(args, 1) | |
143 | if types._malfunc_Q(f) then | |
144 | ast = f.ast | |
145 | env = Env:new(f.env, f.params, args) -- TCO | |
146 | else | |
147 | return f(unpack(args)) | |
148 | end | |
149 | end | |
150 | end | |
151 | end | |
152 | ||
153 | ||
154 | function PRINT(exp) | |
155 | return printer._pr_str(exp, true) | |
156 | end | |
157 | ||
158 | -- repl | |
159 | local repl_env = Env:new() | |
160 | function rep(str) | |
161 | return PRINT(EVAL(READ(str),repl_env)) | |
162 | end | |
163 | ||
164 | -- core.lua: defined using Lua | |
165 | for k,v in pairs(core.ns) do | |
166 | repl_env:set(types.Symbol:new(k), v) | |
167 | end | |
168 | repl_env:set(types.Symbol:new('eval'), | |
169 | function(ast) return EVAL(ast, repl_env) end) | |
170 | repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2))) | |
171 | ||
172 | -- core.mal: defined using mal | |
173 | rep("(def! not (fn* (a) (if a false true)))") | |
174 | rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") | |
175 | rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") | |
176 | rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") | |
177 | ||
3e0b36dc JM |
178 | function print_exception(exc) |
179 | if exc then | |
180 | if types._malexception_Q(exc) then | |
181 | exc = printer._pr_str(exc.val, true) | |
182 | end | |
183 | print("Error: " .. exc) | |
184 | print(debug.traceback()) | |
185 | end | |
186 | end | |
187 | ||
188 | if #arg > 0 and arg[1] == "--raw" then | |
189 | readline.raw = true | |
190 | table.remove(arg,1) | |
191 | end | |
192 | ||
9d42904e | 193 | if #arg > 0 then |
3e0b36dc JM |
194 | xpcall(function() rep("(load-file \""..arg[1].."\")") end, |
195 | print_exception) | |
9d42904e JM |
196 | os.exit(0) |
197 | end | |
198 | ||
199 | while true do | |
200 | line = readline.readline("user> ") | |
201 | if not line then break end | |
3e0b36dc JM |
202 | xpcall(function() print(rep(line)) end, |
203 | print_exception) | |
9d42904e | 204 | end |