aboutsummaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua')
-rw-r--r--lua/Makefile26
-rw-r--r--lua/core.lua226
-rw-r--r--lua/env.lua53
-rw-r--r--lua/printer.lua55
-rw-r--r--lua/reader.lua127
-rw-r--r--lua/readline.lua26
-rwxr-xr-xlua/step0_repl.lua25
-rwxr-xr-xlua/step1_read_print.lua42
-rwxr-xr-xlua/step2_eval.lua75
-rwxr-xr-xlua/step3_env.lua88
-rwxr-xr-xlua/step4_if_fn_do.lua106
-rwxr-xr-xlua/step5_tco.lua114
-rwxr-xr-xlua/step6_file.lua123
-rwxr-xr-xlua/step7_quote.lua149
-rwxr-xr-xlua/step8_macros.lua178
-rwxr-xr-xlua/step9_try.lua196
-rwxr-xr-xlua/stepA_interop.lua200
-rw-r--r--lua/types.lua193
-rw-r--r--lua/utils.lua53
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