read.lua
Branches > main > 9a99ede > files / org / faddle / lang / read.lua
local list = require("org.faddle.lang.list")
local cons = list.cons
local empty = list.empty
local symbol = require("org.faddle.lang.symbol")
local intern = symbol.intern
local is_valid_symbol = symbol.is_valid_symbol
-- comments
-- multi-line strings
-- .f symbols
-- ' reader syntax
local whitespace = {[" "] = true,
["\t"] = true,
["\n"] = true,
["\r"] = true,
[","] = true}
local delim = {['"'] = true,
["("] = true,
[")"] = true,
["["] = true,
["]"] = true,
["{"] = true,
["}"] = true}
local function read(s)
local pos = 1
-- TODO If #s returns the number of bytes this would be wrong?
local len = #s
local read_value
local function peek()
return s:sub(pos,pos)
end
local function next_char()
if pos > len then error("EOF while reading") end
local c = s:sub(pos,pos)
pos = pos + 1
return c
end
local function skip_whitespace()
while pos <= len and whitespace[peek()] do
pos = pos + 1
end
end
-- TODO comments
local function next_token()
skip_whitespace()
if pos > len then error("EOF while reading") end
local c = peek()
if delim[c] then
pos = pos + 1
return c
end
local start = pos
while pos <= len do
c = peek()
if delim[c] or whitespace[c] then
break
end
pos = pos + 1
end
return s:sub(start, pos-1)
end
-- TODO multi-line strings
-- Would require you to keep track of indentation...
local function read_string()
local buf = {}
while true do
local c = next_char()
if c == '"' then break end
if c == "\\" then
local e = next_char()
if e == "n" then buf[#buf+1] = "\n"
elseif e == "r" then buf[#buf+1] = "\r"
elseif e == "t" then buf[#buf+1] = "\t"
elseif e == "\\" then buf[#buf+1] = "\\"
elseif e == '"' then buf[#buf+1] = '"'
elseif e == "0" then buf[#buf+1] = "\0"
else buf[#buf+1] = e end
else
buf[#buf+1] = c
end
end
return table.concat(buf)
end
local function read_list()
local tok = next_token()
if tok == ")" then return empty end
return cons(read_value(tok), read_list())
end
local function read_sequential_table()
local arr = {}
local i = 1
while true do
local tok = next_token()
if tok == "]" then break end
arr[i] = read_value(tok)
i = i + 1
end
return arr
end
local function read_associative_table()
local t = {}
while true do
local k_tok = next_token()
if k_tok == "}" then break end
local k = read_value(k_tok)
local v_tok = next_token()
if v_tok == "}" then
error("Associative table requires an even number of elements")
end
local v = read_value(v_tok)
if k ~= nil then
t[k] = v
end
end
return t
end
read_value = function(token)
token = token or next_token()
if token == '"' then return read_string() end
if token == "(" then return read_list() end
if token == "[" then return read_sequential_table() end
if token == "{" then return read_associative_table() end
if token == "true" then return true end
if token == "false" then return false end
-- I don't think this is quite right, it has to be : followed by a-Z and a bunch of other things as well...
if token:sub(1,1) == ":" and #token > 1 then
return token:sub(2)
end
local n = tonumber(token)
if n ~= nil then return n end
if is_valid_symbol(token) then return intern(token) end
error("Don't know how to read: " .. token)
end
local values = {}
while true do
skip_whitespace()
if pos > len then break end
-- read never returns nils
table.insert(values, read_value())
end
return values
end
return {read = read}