diff options
| author | Joel Martin <github@martintribe.org> | 2015-01-08 23:25:40 -0600 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2015-01-08 23:25:40 -0600 |
| commit | 9d42904e47c50c5ff2306da04993b2a32bc9cd16 (patch) | |
| tree | e1b2d46a232e6573dc2c185967ebe988be3db973 /lua/reader.lua | |
| parent | fd888612ca589d7e1a46c36fc3fe12aed126f6a8 (diff) | |
| download | mal-9d42904e47c50c5ff2306da04993b2a32bc9cd16.tar.gz mal-9d42904e47c50c5ff2306da04993b2a32bc9cd16.zip | |
Lua: all steps and self-hosting.
Also some misc docs/TODO updates.
Diffstat (limited to 'lua/reader.lua')
| -rw-r--r-- | lua/reader.lua | 127 |
1 files changed, 127 insertions, 0 deletions
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 |
