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 string_re = rex.new("^\"(?:\\\\.|[^\\\\\"])*\"$") local token = rdr:next() if int_re:exec(token) then return tonumber(token) elseif float_re:exec(token) then return tonumber(token) elseif string_re:exec(token) then local sval = string.sub(token,2,string.len(token)-1) sval = string.gsub(sval, '\\\\', '\177') sval = string.gsub(sval, '\\"', '"') sval = string.gsub(sval, '\\n', '\n') sval = string.gsub(sval, '\177', '\\') return sval elseif string.sub(token,1,1) == '"' then throw("expected '\"', got EOF") 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(), table.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