diff options
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/Makefile | 26 | ||||
| -rw-r--r-- | lua/core.lua | 226 | ||||
| -rw-r--r-- | lua/env.lua | 53 | ||||
| -rw-r--r-- | lua/printer.lua | 55 | ||||
| -rw-r--r-- | lua/reader.lua | 127 | ||||
| -rw-r--r-- | lua/readline.lua | 26 | ||||
| -rwxr-xr-x | lua/step0_repl.lua | 25 | ||||
| -rwxr-xr-x | lua/step1_read_print.lua | 42 | ||||
| -rwxr-xr-x | lua/step2_eval.lua | 75 | ||||
| -rwxr-xr-x | lua/step3_env.lua | 88 | ||||
| -rwxr-xr-x | lua/step4_if_fn_do.lua | 106 | ||||
| -rwxr-xr-x | lua/step5_tco.lua | 114 | ||||
| -rwxr-xr-x | lua/step6_file.lua | 123 | ||||
| -rwxr-xr-x | lua/step7_quote.lua | 149 | ||||
| -rwxr-xr-x | lua/step8_macros.lua | 178 | ||||
| -rwxr-xr-x | lua/step9_try.lua | 196 | ||||
| -rwxr-xr-x | lua/stepA_interop.lua | 200 | ||||
| -rw-r--r-- | lua/types.lua | 193 | ||||
| -rw-r--r-- | lua/utils.lua | 53 |
19 files changed, 2055 insertions, 0 deletions
diff --git a/lua/Makefile b/lua/Makefile new file mode 100644 index 0000000..d1498bd --- /dev/null +++ b/lua/Makefile @@ -0,0 +1,26 @@ +TESTS = + +SOURCES_BASE = utils.lua types.lua reader.lua printer.lua +SOURCES_LISP = env.lua core.lua stepA_interop.lua +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: libs + +.PHONY: stats tests $(TESTS) + +clean: + rm -f linenoise.so + +stats: $(SOURCES) + @wc $^ +stats-lisp: $(SOURCES_LISP) + @wc $^ + + +.PHONY: libs +libs: linenoise.so + +linenoise.so: + luarocks install --tree=./ linenoise + ln -sf lib/lua/5.1/linenoise.so $@ + diff --git a/lua/core.lua b/lua/core.lua new file mode 100644 index 0000000..3f46aeb --- /dev/null +++ b/lua/core.lua @@ -0,0 +1,226 @@ +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local readline = require('readline') + +local Nil, List, _pr_str = types.Nil, types.List, printer._pr_str + +local M = {} + +-- string functions + +function pr_str(...) + return table.concat( + utils.map(function(e) return _pr_str(e, true) end, arg), " ") +end + +function str(...) + return table.concat( + utils.map(function(e) return _pr_str(e, false) end, arg), "") +end + +function prn(...) + print(table.concat( + utils.map(function(e) return _pr_str(e, true) end, arg), " ")) + return Nil +end + +function println(...) + print(table.concat( + utils.map(function(e) return _pr_str(e, false) end, arg), " ")) + return Nil +end + +function slurp(file) + local lines = {} + for line in io.lines(file) do + lines[#lines+1] = line + end + return table.concat(lines, "\n") .. "\n" +end + +function do_readline(prompt) + local line = readline.readline(prompt) + if line == nil then + return Nil + else + return line + end +end + +-- hash map functions + +function assoc(hm, ...) + return types._assoc_BANG(types.copy(hm), unpack(arg)) +end + +function dissoc(hm, ...) + return types._dissoc_BANG(types.copy(hm), unpack(arg)) +end + +function get(hm, key) + local res = hm[key] + if res == nil then return Nil end + return res +end + +function keys(hm) + local res = {} + for k,v in pairs(hm) do + res[#res+1] = k + end + return List:new(res) +end + +function vals(hm) + local res = {} + for k,v in pairs(hm) do + res[#res+1] = v + end + return List:new(res) +end + +-- sequential functions + +function cons(a,lst) + local new_lst = lst:slice(1) + table.insert(new_lst, 1, a) + return List:new(new_lst) +end + +function concat(...) + local new_lst = {} + for i = 1, #arg do + for j = 1, #arg[i] do + table.insert(new_lst, arg[i][j]) + end + end + return List:new(new_lst) +end + +function nth(seq, idx) + if idx+1 <= #seq then + return seq[idx+1] + else + types.throw("nth: index out of range") + end +end + +function first(a) + if #a == 0 then + return Nil + else + return a[1] + end +end + +function apply(f, ...) + if types._malfunc_Q(f) then + f = f.fn + end + local args = concat(types.slice(arg, 1, #arg-1), + arg[#arg]) + return f(unpack(args)) +end + +function map(f, lst) + if types._malfunc_Q(f) then + f = f.fn + end + return List:new(utils.map(f, lst)) +end + +-- metadata functions + +function meta(obj) + local m = getmetatable(obj) + if m == nil or m.meta == nil then return Nil end + return m.meta +end + +function with_meta(obj, meta) + local new_obj = types.copy(obj) + getmetatable(new_obj).meta = meta + return new_obj +end + +-- atom functions + +function swap_BANG(atm,f,...) + if types._malfunc_Q(f) then + f = f.fn + end + local args = List:new(arg) + table.insert(args, 1, atm.val) + atm.val = f(unpack(args)) + return atm.val +end + +M.ns = { + ['='] = types._equal_Q, + throw = types.throw, + + ['nil?'] = function(a) return a==Nil end, + ['true?'] = function(a) return a==true end, + ['false?'] = function(a) return a==false end, + symbol = function(a) return types.Symbol:new(a) end, + ['symbol?'] = function(a) return types._symbol_Q(a) end, + keyword = function(a) return "\177"..a end, + ['keyword?'] = function(a) return types._keyword_Q(a) end, + + ['pr-str'] = pr_str, + str = str, + prn = prn, + println = println, + ['read-string'] = reader.read_str, + readline = do_readline, + slurp = slurp, + + ['<'] = function(a,b) return a<b end, + ['<='] = function(a,b) return a<=b end, + ['>'] = function(a,b) return a>b end, + ['>='] = function(a,b) return a>=b end, + ['+'] = function(a,b) return a+b end, + ['-'] = function(a,b) return a-b end, + ['*'] = function(a,b) return a*b end, + ['/'] = function(a,b) return math.floor(a/b) end, + -- TODO: get actual milliseconds + ['time-ms'] = function() return os.time() * 1000 end, + + list = function(...) return List:new(arg) end, + ['list?'] = function(a) return types._list_Q(a) end, + vector = function(...) return types.Vector:new(arg) end, + ['vector?'] = types._vector_Q, + ['hash-map'] = types.hash_map, + ['map?'] = types._hash_map_Q, + assoc = assoc, + dissoc = dissoc, + get = get, + ['contains?'] = function(a,b) return a[b] ~= nil end, + keys = keys, + vals = vals, + + ['sequential?'] = types._sequential_Q, + cons = cons, + concat = concat, + nth = nth, + first = first, + rest = function(a) return List:new(a:slice(2)) end, + ['empty?'] = function(a) return a==Nil or #a == 0 end, + count = function(a) return #a end, + apply = apply, + map = map, + conj = function(...) return Nil end, + + meta = meta, + ['with-meta'] = with_meta, + atom = function(a) return types.Atom:new(a) end, + ['atom?'] = types._atom_Q, + deref = function(a) return a.val end, + ['reset!'] = function(a,b) a.val = b; return b end, + ['swap!'] = swap_BANG, +} + +return M + diff --git a/lua/env.lua b/lua/env.lua new file mode 100644 index 0000000..ee19c90 --- /dev/null +++ b/lua/env.lua @@ -0,0 +1,53 @@ +local rex = require('rex_pcre') +local string = require('string') +local table = require('table') +local utils = require('utils') +local types = require('types') + +local Env = {} + +function Env:new(outer, binds, exprs) + local data = {} + local newObj = {outer = outer, data = data} + self.__index = self + if binds then + for i, b in ipairs(binds) do + if binds[i].val == '&' then + local new_exprs = types.List:new() + for j = i, #exprs do + table.insert(new_exprs, exprs[j]) + end + table.remove(exprs, 1) + data[binds[i+1].val] = new_exprs + break + end + data[binds[i].val] = exprs[i] + end + end + return setmetatable(newObj, self) +end +function Env:find(sym) + if self.data[sym.val] ~= nil then + return self + else + if self.outer ~= nil then + return self.outer:find(sym) + else + return nil + end + end +end +function Env:set(sym,val) + self.data[sym.val] = val + return val +end +function Env:get(sym) + local env = self:find(sym) + if env then + return env.data[sym.val] + else + types.throw("'"..sym.val.."' not found") + end +end + +return Env diff --git a/lua/printer.lua b/lua/printer.lua new file mode 100644 index 0000000..8c1cdad --- /dev/null +++ b/lua/printer.lua @@ -0,0 +1,55 @@ +local string = require('string') +local table = require('table') +local types = require('types') +local utils = require('utils') + +local M = {} + +function M._pr_str(obj, print_readably) + local _r = print_readably + if utils.instanceOf(obj, types.Symbol) then + return obj.val + elseif types._list_Q(obj) then + return "(" .. table.concat(utils.map(function(e) + return M._pr_str(e,_r) end, obj), " ") .. ")" + elseif types._vector_Q(obj) then + return "[" .. table.concat(utils.map(function(e) + return M._pr_str(e,_r) end, obj), " ") .. "]" + elseif types._hash_map_Q(obj) then + local res = {} + for k,v in pairs(obj) do + res[#res+1] = M._pr_str(k, _r) + res[#res+1] = M._pr_str(v, _r) + end + return "{".. table.concat(res, " ").."}" + elseif type(obj) == 'string' then + if string.sub(obj,1,1) == "\177" then + return ':' .. string.sub(obj,2) + else + if _r then + local sval = obj:gsub('\\', '\\\\') + sval = sval:gsub('"', '\\"') + sval = sval:gsub('\n', '\\n') + return '"' .. sval .. '"' + else + return obj + end + end + elseif obj == types.Nil then + return "nil" + elseif obj == true then + return "true" + elseif obj == false then + return "false" + elseif types._malfunc_Q(obj) then + return "(fn* "..M._pr_str(obj.params).." "..M._pr_str(obj.ast)..")" + elseif types._atom_Q(obj) then + return "(atom "..M._pr_str(obj.val)..")" + elseif type(obj) == 'function' then + return "#<function>" + else + return string.format("%s", obj) + end +end + +return M diff --git a/lua/reader.lua b/lua/reader.lua new file mode 100644 index 0000000..2d29a52 --- /dev/null +++ b/lua/reader.lua @@ -0,0 +1,127 @@ +local rex = require('rex_pcre') +local string = require('string') +local table = require('table') +local types = require('types') +local throw, Nil, Symbol, List = types.throw, types.Nil, + types.Symbol, types.List + +local M = {} + +Reader = {} +function Reader:new(tokens) + local newObj = {tokens = tokens, position = 1} + self.__index = self + return setmetatable(newObj, self) +end +function Reader:next() + self.position = self.position + 1 + return self.tokens[self.position-1] +end +function Reader:peek() + return self.tokens[self.position] +end + +function M.tokenize(str) + local results = {} + local re_pos = 1 + local re = rex.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)", rex.flags().EXTENDED) + while true do + local s, e, t = re:exec(str, re_pos) + if not s or s > e then break end + re_pos = e + 1 + local val = string.sub(str,t[1],t[2]) + if string.sub(val,1,1) ~= ";" then + table.insert(results, val) + end + end + return results +end + +function M.read_atom(rdr) + local int_re = rex.new("^-?[0-9]+$") + local float_re = rex.new("^-?[0-9][0-9.]*$") + local token = rdr:next() + if int_re:exec(token) then return tonumber(token) + elseif float_re:exec(token) then return tonumber(token) + elseif string.sub(token,1,1) == '"' then + local sval = string.sub(token,2,string.len(token)-1) + sval = string.gsub(sval, '\\"', '"') + sval = string.gsub(sval, '\\n', '\n') + return sval + elseif string.sub(token,1,1) == ':' then + return "\177" .. string.sub(token,2) + elseif token == "nil" then return Nil + elseif token == "true" then return true + elseif token == "false" then return false + else return Symbol:new(token) + end +end + +function M.read_sequence(rdr, start, last) + local ast = {} + local token = rdr:next() + if token ~= start then throw("expected '"..start.."'") end + + token = rdr:peek() + while token ~= last do + if not token then throw("expected '"..last.."', got EOF") end + table.insert(ast, M.read_form(rdr)) + token = rdr:peek() + end + rdr:next() + return ast +end + +function M.read_list(rdr) + return types.List:new(M.read_sequence(rdr, '(', ')')) +end + +function M.read_vector(rdr) + return types.Vector:new(M.read_sequence(rdr, '[', ']')) +end + +function M.read_hash_map(rdr) + local seq = M.read_sequence(rdr, '{', '}') + return types._assoc_BANG(types.HashMap:new(), unpack(seq)) +end + +function M.read_form(rdr) + local token = rdr:peek() + + if "'" == token then + rdr:next() + return List:new({Symbol:new('quote'), M.read_form(rdr)}) + elseif '`' == token then + rdr:next() + return List:new({Symbol:new('quasiquote'), M.read_form(rdr)}) + elseif '~' == token then + rdr:next() + return List:new({Symbol:new('unquote'), M.read_form(rdr)}) + elseif '~@' == token then + rdr:next() + return List:new({Symbol:new('splice-unquote'), M.read_form(rdr)}) + elseif '^' == token then + rdr:next() + local meta = M.read_form(rdr) + return List:new({Symbol:new('with-meta'), M.read_form(rdr), meta}) + elseif '@' == token then + rdr:next() + return List:new({Symbol:new('deref'), M.read_form(rdr)}) + + elseif ')' == token then throw("unexpected ')'") + elseif '(' == token then return M.read_list(rdr) + elseif ']' == token then throw("unexpected ']'") + elseif '[' == token then return M.read_vector(rdr) + elseif '}' == token then throw("unexpected '}'") + elseif '{' == token then return M.read_hash_map(rdr) + else return M.read_atom(rdr) + end +end + +function M.read_str(str) + local tokens = M.tokenize(str) + if #tokens == 0 then error(nil) end + return M.read_form(Reader:new(tokens)) +end + +return M diff --git a/lua/readline.lua b/lua/readline.lua new file mode 100644 index 0000000..a75f4ff --- /dev/null +++ b/lua/readline.lua @@ -0,0 +1,26 @@ +local LN = require('linenoise') + +local M = {} + +local history_loaded = false +local history_file = os.getenv("HOME") .. "/.mal-history" + +function M.readline(prompt) + if not history_loaded then + history_loaded = true + for line in io.lines(history_file) do + LN.historyadd(line) + end + end + + line = LN.linenoise(prompt) + if line then + LN.historyadd(line) + local f = io.open(history_file, "a") + f:write(line.."\n") + f:close() + end + return line +end + +return M diff --git a/lua/step0_repl.lua b/lua/step0_repl.lua new file mode 100755 index 0000000..24584d2 --- /dev/null +++ b/lua/step0_repl.lua @@ -0,0 +1,25 @@ +#!/usr/bin/env lua + +local readline = require('readline') + +function READ(str) + return str +end + +function EVAL(ast, any) + return ast +end + +function PRINT(exp) + return exp +end + +function rep(str) + return PRINT(EVAL(READ(str),"")) +end + +while true do + line = readline.readline("user> ") + if not line then break end + print(rep(line)) +end diff --git a/lua/step1_read_print.lua b/lua/step1_read_print.lua new file mode 100755 index 0000000..abd555d --- /dev/null +++ b/lua/step1_read_print.lua @@ -0,0 +1,42 @@ +#!/usr/bin/env lua + +local readline = require('readline') +local utils = require('utils') +local reader = require('reader') +local printer = require('printer') + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function EVAL(ast, env) + return ast +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +function rep(str) + return PRINT(EVAL(READ(str),"")) +end + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step2_eval.lua b/lua/step2_eval.lua new file mode 100755 index 0000000..7487064 --- /dev/null +++ b/lua/step2_eval.lua @@ -0,0 +1,75 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function eval_ast(ast, env) + if types._symbol_Q(ast) then + if env[ast.val] == nil then + types.throw("'"..ast.val.."' not found") + end + return env[ast.val] + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + return f(unpack(args)) +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = {['+'] = function(a,b) return a+b end, + ['-'] = function(a,b) return a-b end, + ['*'] = function(a,b) return a*b end, + ['/'] = function(a,b) return math.floor(a/b) end} +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step3_env.lua b/lua/step3_env.lua new file mode 100755 index 0000000..ed2e62a --- /dev/null +++ b/lua/step3_env.lua @@ -0,0 +1,88 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + local a0,a1,a2 = ast[1], ast[2],ast[3] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + return EVAL(a2, let_env) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + return f(unpack(args)) + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +repl_env:set(types.Symbol:new('+'), function(a,b) return a+b end) +repl_env:set(types.Symbol:new('-'), function(a,b) return a-b end) +repl_env:set(types.Symbol:new('*'), function(a,b) return a*b end) +repl_env:set(types.Symbol:new('/'), function(a,b) return math.floor(a/b) end) + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step4_if_fn_do.lua b/lua/step4_if_fn_do.lua new file mode 100755 index 0000000..e211973 --- /dev/null +++ b/lua/step4_if_fn_do.lua @@ -0,0 +1,106 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + return EVAL(a2, let_env) + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast), env) + return el[#el] + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then return EVAL(a3, env) else return types.Nil end + else + return EVAL(a2, env) + end + elseif 'fn*' == a0sym then + return function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + return f(unpack(args)) + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end + +-- core.mal: defined using mal +rep("(def! not (fn* (a) (if a false true)))") + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step5_tco.lua b/lua/step5_tco.lua new file mode 100755 index 0000000..fa4e41f --- /dev/null +++ b/lua/step5_tco.lua @@ -0,0 +1,114 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + while true do + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + env = let_env + ast = a2 -- TCO + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast-1), env) + ast = ast[#ast] -- TCO + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then ast = a3 else return types.Nil end -- TCO + else + ast = a2 -- TCO + end + elseif 'fn*' == a0sym then + return types.MalFunc:new(function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end, a2, env, a1) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + if types._malfunc_Q(f) then + ast = f.ast + env = Env:new(f.env, f.params, args) -- TCO + else + return f(unpack(args)) + end + end + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end + +-- core.mal: defined using mal +rep("(def! not (fn* (a) (if a false true)))") + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step6_file.lua b/lua/step6_file.lua new file mode 100755 index 0000000..b109a90 --- /dev/null +++ b/lua/step6_file.lua @@ -0,0 +1,123 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + while true do + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + env = let_env + ast = a2 -- TCO + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast-1), env) + ast = ast[#ast] -- TCO + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then ast = a3 else return types.Nil end -- TCO + else + ast = a2 -- TCO + end + elseif 'fn*' == a0sym then + return types.MalFunc:new(function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end, a2, env, a1) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + if types._malfunc_Q(f) then + ast = f.ast + env = Env:new(f.env, f.params, args) -- TCO + else + return f(unpack(args)) + end + end + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end +repl_env:set(types.Symbol:new('eval'), + function(ast) return EVAL(ast, repl_env) end) +repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2))) + +-- core.mal: defined using mal +rep("(def! not (fn* (a) (if a false true)))") +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +if #arg > 0 then + rep("(load-file \""..arg[1].."\")") + os.exit(0) +end + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step7_quote.lua b/lua/step7_quote.lua new file mode 100755 index 0000000..974e342 --- /dev/null +++ b/lua/step7_quote.lua @@ -0,0 +1,149 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function is_pair(x) + return types._sequential_Q(x) and #x > 0 +end + +function quasiquote(ast) + if not is_pair(ast) then + return types.List:new({types.Symbol:new("quote"), ast}) + elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then + return ast[2] + elseif is_pair(ast[1]) and + types._symbol_Q(ast[1][1]) and + ast[1][1].val == 'splice-unquote' then + return types.List:new({types.Symbol:new("concat"), + ast[1][2], + quasiquote(ast:slice(2))}) + else + return types.List:new({types.Symbol:new("cons"), + quasiquote(ast[1]), + quasiquote(ast:slice(2))}) + end +end + +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + while true do + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + env = let_env + ast = a2 -- TCO + elseif 'quote' == a0sym then + return a1 + elseif 'quasiquote' == a0sym then + ast = quasiquote(a1) -- TCO + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast-1), env) + ast = ast[#ast] -- TCO + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then ast = a3 else return types.Nil end -- TCO + else + ast = a2 -- TCO + end + elseif 'fn*' == a0sym then + return types.MalFunc:new(function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end, a2, env, a1) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + if types._malfunc_Q(f) then + ast = f.ast + env = Env:new(f.env, f.params, args) -- TCO + else + return f(unpack(args)) + end + end + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end +repl_env:set(types.Symbol:new('eval'), + function(ast) return EVAL(ast, repl_env) end) +repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2))) + +-- core.mal: defined using mal +rep("(def! not (fn* (a) (if a false true)))") +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +if #arg > 0 then + rep("(load-file \""..arg[1].."\")") + os.exit(0) +end + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step8_macros.lua b/lua/step8_macros.lua new file mode 100755 index 0000000..46a5881 --- /dev/null +++ b/lua/step8_macros.lua @@ -0,0 +1,178 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function is_pair(x) + return types._sequential_Q(x) and #x > 0 +end + +function quasiquote(ast) + if not is_pair(ast) then + return types.List:new({types.Symbol:new("quote"), ast}) + elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then + return ast[2] + elseif is_pair(ast[1]) and + types._symbol_Q(ast[1][1]) and + ast[1][1].val == 'splice-unquote' then + return types.List:new({types.Symbol:new("concat"), + ast[1][2], + quasiquote(ast:slice(2))}) + else + return types.List:new({types.Symbol:new("cons"), + quasiquote(ast[1]), + quasiquote(ast:slice(2))}) + end +end + +function is_macro_call(ast, env) + if types._list_Q(ast) and + types._symbol_Q(ast[1]) and + env:find(ast[1]) then + local f = env:get(ast[1]) + return types._malfunc_Q(f) and f.ismacro + end +end + +function macroexpand(ast, env) + while is_macro_call(ast, env) do + local mac = env:get(ast[1]) + ast = mac.fn(unpack(ast:slice(2))) + end + return ast +end + +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + while true do + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + -- apply list + ast = macroexpand(ast, env) + if not types._list_Q(ast) then return ast end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + env = let_env + ast = a2 -- TCO + elseif 'quote' == a0sym then + return a1 + elseif 'quasiquote' == a0sym then + ast = quasiquote(a1) -- TCO + elseif 'defmacro!' == a0sym then + local mac = EVAL(a2, env) + mac.ismacro = true + return env:set(a1, mac) + elseif 'macroexpand' == a0sym then + return macroexpand(a1, env) + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast-1), env) + ast = ast[#ast] -- TCO + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then ast = a3 else return types.Nil end -- TCO + else + ast = a2 -- TCO + end + elseif 'fn*' == a0sym then + return types.MalFunc:new(function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end, a2, env, a1) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + if types._malfunc_Q(f) then + ast = f.ast + env = Env:new(f.env, f.params, args) -- TCO + else + return f(unpack(args)) + end + end + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end +repl_env:set(types.Symbol:new('eval'), + function(ast) return EVAL(ast, repl_env) end) +repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2))) + +-- core.mal: defined using mal +rep("(def! not (fn* (a) (if a false true)))") +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +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)))))))") +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))))))))") + +if #arg > 0 then + rep("(load-file \""..arg[1].."\")") + os.exit(0) +end + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/step9_try.lua b/lua/step9_try.lua new file mode 100755 index 0000000..f3d8cce --- /dev/null +++ b/lua/step9_try.lua @@ -0,0 +1,196 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function is_pair(x) + return types._sequential_Q(x) and #x > 0 +end + +function quasiquote(ast) + if not is_pair(ast) then + return types.List:new({types.Symbol:new("quote"), ast}) + elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then + return ast[2] + elseif is_pair(ast[1]) and + types._symbol_Q(ast[1][1]) and + ast[1][1].val == 'splice-unquote' then + return types.List:new({types.Symbol:new("concat"), + ast[1][2], + quasiquote(ast:slice(2))}) + else + return types.List:new({types.Symbol:new("cons"), + quasiquote(ast[1]), + quasiquote(ast:slice(2))}) + end +end + +function is_macro_call(ast, env) + if types._list_Q(ast) and + types._symbol_Q(ast[1]) and + env:find(ast[1]) then + local f = env:get(ast[1]) + return types._malfunc_Q(f) and f.ismacro + end +end + +function macroexpand(ast, env) + while is_macro_call(ast, env) do + local mac = env:get(ast[1]) + ast = mac.fn(unpack(ast:slice(2))) + end + return ast +end + +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + while true do + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + -- apply list + ast = macroexpand(ast, env) + if not types._list_Q(ast) then return ast end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + env = let_env + ast = a2 -- TCO + elseif 'quote' == a0sym then + return a1 + elseif 'quasiquote' == a0sym then + ast = quasiquote(a1) -- TCO + elseif 'defmacro!' == a0sym then + local mac = EVAL(a2, env) + mac.ismacro = true + return env:set(a1, mac) + elseif 'macroexpand' == a0sym then + return macroexpand(a1, env) + elseif 'try*' == a0sym then + local exc, result = nil, nil + xpcall(function() + result = EVAL(a1, env) + end, function(err) + exc = err + end) + if exc ~= nil then + if types._malexception_Q(exc) then + exc = exc.val + end + if a2 and a2[1].val == 'catch*' then + result = EVAL(a2[3], Env:new(env, {a2[2]}, {exc})) + else + types.throw(exc) + end + end + return result + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast-1), env) + ast = ast[#ast] -- TCO + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then ast = a3 else return types.Nil end -- TCO + else + ast = a2 -- TCO + end + elseif 'fn*' == a0sym then + return types.MalFunc:new(function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end, a2, env, a1) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + if types._malfunc_Q(f) then + ast = f.ast + env = Env:new(f.env, f.params, args) -- TCO + else + return f(unpack(args)) + end + end + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end +repl_env:set(types.Symbol:new('eval'), + function(ast) return EVAL(ast, repl_env) end) +repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2))) + +-- core.mal: defined using mal +rep("(def! not (fn* (a) (if a false true)))") +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +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)))))))") +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))))))))") + +if #arg > 0 then + rep("(load-file \""..arg[1].."\")") + os.exit(0) +end + +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() + print(rep(line)) + end, function(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end + end) +end diff --git a/lua/stepA_interop.lua b/lua/stepA_interop.lua new file mode 100755 index 0000000..430fffc --- /dev/null +++ b/lua/stepA_interop.lua @@ -0,0 +1,200 @@ +#!/usr/bin/env lua + +local table = require('table') + +local readline = require('readline') +local utils = require('utils') +local types = require('types') +local reader = require('reader') +local printer = require('printer') +local Env = require('env') +local core = require('core') +local List, Vector, HashMap = types.List, types.Vector, types.HashMap + +-- read +function READ(str) + return reader.read_str(str) +end + +-- eval +function is_pair(x) + return types._sequential_Q(x) and #x > 0 +end + +function quasiquote(ast) + if not is_pair(ast) then + return types.List:new({types.Symbol:new("quote"), ast}) + elseif types._symbol_Q(ast[1]) and ast[1].val == 'unquote' then + return ast[2] + elseif is_pair(ast[1]) and + types._symbol_Q(ast[1][1]) and + ast[1][1].val == 'splice-unquote' then + return types.List:new({types.Symbol:new("concat"), + ast[1][2], + quasiquote(ast:slice(2))}) + else + return types.List:new({types.Symbol:new("cons"), + quasiquote(ast[1]), + quasiquote(ast:slice(2))}) + end +end + +function is_macro_call(ast, env) + if types._list_Q(ast) and + types._symbol_Q(ast[1]) and + env:find(ast[1]) then + local f = env:get(ast[1]) + return types._malfunc_Q(f) and f.ismacro + end +end + +function macroexpand(ast, env) + while is_macro_call(ast, env) do + local mac = env:get(ast[1]) + ast = mac.fn(unpack(ast:slice(2))) + end + return ast +end + +function eval_ast(ast, env) + if types._symbol_Q(ast) then + return env:get(ast) + elseif types._list_Q(ast) then + return List:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._vector_Q(ast) then + return Vector:new(utils.map(function(x) return EVAL(x,env) end,ast)) + elseif types._hash_map_Q(ast) then + local new_hm = {} + for k,v in pairs(ast) do + new_hm[EVAL(k, env)] = EVAL(v, env) + end + return HashMap:new(new_hm) + else + return ast + end +end + +function EVAL(ast, env) + while true do + --print("EVAL: "..printer._pr_str(ast,true)) + if not types._list_Q(ast) then return eval_ast(ast, env) end + + -- apply list + ast = macroexpand(ast, env) + if not types._list_Q(ast) then return ast end + + local a0,a1,a2,a3 = ast[1], ast[2],ast[3],ast[4] + local a0sym = types._symbol_Q(a0) and a0.val or "" + if 'def!' == a0sym then + return env:set(a1, EVAL(a2, env)) + elseif 'let*' == a0sym then + local let_env = Env:new(env) + for i = 1,#a1,2 do + let_env:set(a1[i], EVAL(a1[i+1], let_env)) + end + env = let_env + ast = a2 -- TCO + elseif 'quote' == a0sym then + return a1 + elseif 'quasiquote' == a0sym then + ast = quasiquote(a1) -- TCO + elseif 'defmacro!' == a0sym then + local mac = EVAL(a2, env) + mac.ismacro = true + return env:set(a1, mac) + elseif 'macroexpand' == a0sym then + return macroexpand(a1, env) + elseif 'try*' == a0sym then + local exc, result = nil, nil + xpcall(function() + result = EVAL(a1, env) + end, function(err) + exc = err + end) + if exc ~= nil then + if types._malexception_Q(exc) then + exc = exc.val + end + if a2 and a2[1].val == 'catch*' then + result = EVAL(a2[3], Env:new(env, {a2[2]}, {exc})) + else + types.throw(exc) + end + end + return result + elseif 'do' == a0sym then + local el = eval_ast(ast:slice(2,#ast-1), env) + ast = ast[#ast] -- TCO + elseif 'if' == a0sym then + local cond = EVAL(a1, env) + if cond == types.Nil or cond == false then + if a3 then ast = a3 else return types.Nil end -- TCO + else + ast = a2 -- TCO + end + elseif 'fn*' == a0sym then + return types.MalFunc:new(function(...) + return EVAL(a2, Env:new(env, a1, arg)) + end, a2, env, a1) + else + local args = eval_ast(ast, env) + local f = table.remove(args, 1) + if types._malfunc_Q(f) then + ast = f.ast + env = Env:new(f.env, f.params, args) -- TCO + else + return f(unpack(args)) + end + end + end +end + +-- print +function PRINT(exp) + return printer._pr_str(exp, true) +end + +-- repl +local repl_env = Env:new() +function rep(str) + return PRINT(EVAL(READ(str),repl_env)) +end + +-- core.lua: defined using Lua +for k,v in pairs(core.ns) do + repl_env:set(types.Symbol:new(k), v) +end +repl_env:set(types.Symbol:new('eval'), + function(ast) return EVAL(ast, repl_env) end) +repl_env:set(types.Symbol:new('*ARGV*'), types.List:new(types.slice(arg,2))) + +-- core.mal: defined using mal +rep("(def! *host-language* \"lua\")") +rep("(def! not (fn* (a) (if a false true)))") +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +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)))))))") +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))))))))") + +function print_exception(exc) + if exc then + if types._malexception_Q(exc) then + exc = printer._pr_str(exc.val, true) + end + print("Error: " .. exc) + print(debug.traceback()) + end +end + +if #arg > 0 then + xpcall(function() rep("(load-file \""..arg[1].."\")") end, + print_exception) + os.exit(0) +end + +rep("(println (str \"Mal [\" *host-language* \"]\"))") +while true do + line = readline.readline("user> ") + if not line then break end + xpcall(function() print(rep(line)) end, + print_exception) +end diff --git a/lua/types.lua b/lua/types.lua new file mode 100644 index 0000000..23a003b --- /dev/null +++ b/lua/types.lua @@ -0,0 +1,193 @@ +local utils = require('utils') + +local M = {} + +-- type functions + +function M._sequential_Q(obj) + return M._list_Q(obj) or M._vector_Q(obj) +end + +function M._equal_Q(a,b) + if M._symbol_Q(a) and M._symbol_Q(b) then + return a.val == b.val + elseif M._sequential_Q(a) and M._sequential_Q(b) then + if #a ~= #b then return false end + for i, v in ipairs(a) do + if not M._equal_Q(v,b[i]) then return false end + end + return true + else + return a == b + end +end + +function M.copy(obj) + if type(obj) ~= "table" then return obj end + + -- copy object data + local new_obj = {} + for k,v in pairs(obj) do + new_obj[k] = v + end + + -- copy metatable and link to original + local old_mt = getmetatable(obj) + if old_mt ~= nil then + local new_mt = {} + for k,v in pairs(old_mt) do + new_mt[k] = v + end + setmetatable(new_mt, old_mt) + setmetatable(new_obj, new_mt) + end + + return new_obj +end + +function M.slice(lst, start, last) + if last == nil then last = #lst end + local new_lst = {} + if start <= last then + for i = start, last do + new_lst[#new_lst+1] = lst[i] + end + end + return new_lst +end + +-- Error/exceptions + +M.MalException = {} +function M.MalException:new(val) + local newObj = {val = val} + self.__index = self + return setmetatable(newObj, self) +end +function M._malexception_Q(obj) + return utils.instanceOf(obj, M.MalException) +end + +function M.throw(val) + error(M.MalException:new(val)) +end + +-- Nil + +local NilType = {} +function NilType:new(val) + local newObj = {} + self.__index = self + return setmetatable(newObj, self) +end +M.Nil = NilType:new() +function M._nil_Q(obj) + return obj == Nil +end + +-- Strings +function M._string_Q(obj) + return type(obj) == "string" +end + +-- Symbols + +M.Symbol = {} +function M.Symbol:new(val) + local newObj = {val = val} + self.__index = self + return setmetatable(newObj, self) +end +function M._symbol_Q(obj) + return utils.instanceOf(obj, M.Symbol) +end + +-- Keywords +function M._keyword_Q(obj) + return M._string_Q(obj) and "\177" == string.sub(obj,1,1) +end + + +-- Lists + +M.List = {} +function M.List:new(lst) + local newObj = lst and lst or {} + self.__index = self + return setmetatable(newObj, self) +end +function M._list_Q(obj) + return utils.instanceOf(obj, M.List) +end +function M.List:slice(start,last) + return M.List:new(M.slice(self,start,last)) +end + +-- Vectors + +M.Vector = {} +function M.Vector:new(lst) + local newObj = lst and lst or {} + self.__index = self + return setmetatable(newObj, self) +end +function M._vector_Q(obj) + return utils.instanceOf(obj, M.Vector) +end +function M.Vector:slice(start,last) + return M.Vector:new(M.slice(self,start,last)) +end + +-- Hash Maps +-- +M.HashMap = {} +function M.HashMap:new(val) + local newObj = val and val or {} + self.__index = self + return setmetatable(newObj, self) +end +function M.hash_map(...) + return M._assoc_BANG(M.HashMap:new(), unpack(arg)) +end +function M._hash_map_Q(obj) + return utils.instanceOf(obj, M.HashMap) +end +function M._assoc_BANG(hm, ...) + for i = 1, #arg, 2 do + hm[arg[i]] = arg[i+1] + end + return hm +end +function M._dissoc_BANG(hm, ...) + for i = 1, #arg do + hm[arg[i]] = nil + end + return hm +end + +-- Functions + +M.MalFunc = {} +function M.MalFunc:new(fn, ast, env, params) + local newObj = {fn = fn, ast = ast, env = env, + params = params, ismacro = false} + self.__index = self + return setmetatable(newObj, self) +end +function M._malfunc_Q(obj) + return utils.instanceOf(obj, M.MalFunc) +end + +-- Atoms + +M.Atom = {} +function M.Atom:new(val) + local newObj = {val = val} + self.__index = self + return setmetatable(newObj, self) +end +function M._atom_Q(obj) + return utils.instanceOf(obj, M.Atom) +end + +return M diff --git a/lua/utils.lua b/lua/utils.lua new file mode 100644 index 0000000..1ed03e1 --- /dev/null +++ b/lua/utils.lua @@ -0,0 +1,53 @@ +local M = {} + +function M.try(f, catch_f) + local status, exception = pcall(f) + if not status then + catch_f(exception) + end +end + +function M.instanceOf(subject, super) + super = tostring(super) + local mt = getmetatable(subject) + + while true do + if mt == nil then return false end + if tostring(mt) == super then return true end + mt = getmetatable(mt) + end +end + +--[[ +function M.isArray(o) + local i = 0 + for _ in pairs(o) do + i = i + 1 + if o[i] == nil then return false end + end + return true +end +]]-- + +function M.map(func, obj) + local new_obj = {} + for i,v in ipairs(obj) do + new_obj[i] = func(v) + end + return new_obj +end + +function M.dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. M.dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + +return M |
