compile.lua
Branches > main > 4377935 > files / org / faddle / lang / compile.lua
local LIST = require("org.faddle.lang.list")
local is_list = LIST.is_list
local count = LIST.count
local first = LIST.first
local rest = LIST.rest
local symbol = require("org.faddle.lang.symbol")
local is_symbol = symbol.is_symbol
local intern = symbol.intern
local munge = symbol.munge
local module = symbol.module
local lisp_print = require("org.faddle.lang.print").print
-- Still bad :p
-- There are a handful of unimplemented special forms that can only be evalled but not compiled!
local FN = intern("fn")
local LET = intern("let")
local DO = intern("do")
local compile
local gensym_number = 0
local function gensym()
gensym_number = gensym_number + 1
return "G__" .. gensym_number
end
local function ret(statement)
return statement and "return " or ""
end
local function compile_body(body)
local lines = {}
local i = 1
local lst = body
if count(body) == 0 then
return "return"
else
while count(lst) ~= 0 do
local form = first(lst)
local last_form = i == count(body)
-- TODO not sure if this is correct
-- Basically `2` is not a valid lua statement, only function calls are...
if last_form or (is_list(form) and count(form) > 0) then
lines[#lines+1] = compile(form, last_form)
end
i = i + 1
lst = rest(lst)
end
return table.concat(lines, ";")
end
end
local function build_fn_args(fn_args)
assert(type(fn_args) == "table", "Function args must be a table")
local compiled_args = {}
local i = 1
while fn_args[i] ~= nil do
local arg = fn_args[i]
assert(is_symbol(arg) and not module(arg), "Function arguments must be a simple symbol")
table.insert(compiled_args, compile(arg))
i = i + 1
end
return table.concat(compiled_args, ",")
end
-- TODO should missing args error?
local function compile_fn(form, statement)
local args = rest(form)
local fn_args = first(args)
local body = rest(args)
return ret(statement) .. "function(" .. build_fn_args(fn_args) .. ") " .. compile_body(body) .. " end"
end
local function build_let_bindings(bindings)
assert(type(bindings) == "table" and #bindings >= 0 and #bindings % 2 == 0, "Let requires an even number of binding forms")
local locals = {}
local i = 1
while i < #bindings do
local sym = bindings[i]
local binding = bindings[i + 1]
locals[#locals + 1] = "local " .. munge(sym) .. "=" .. compile(binding) .. ";"
i = i + 2
end
return table.concat(locals)
end
-- If I'm just using the same lexical stuff as Lua, how do builtin Lua modules work?
-- In Lua you can just say `io`, but I'd like to wrap these libraries in something like `lua.io`
-- to make it clear where they're coming from...
-- Not sure how I could do that
-- I also want to check locals and fail during compilation if you reference a non-existent local
-- Well `io` is just something you'd have to import in Faddle, since the compiler can just look at what
-- modules have been imported, and `io` won't be one of them.
-- So then `lua.io` or whatever can be defined by Faddle, and map to `io`.
local function compile_let(form, statement)
local args = rest(form)
local bindings = first(args)
local body = rest(args)
if statement then
return "do " .. build_let_bindings(bindings) .. compile_body(body) .. " end"
else
-- Not sure if this is the best way to go about this :p
return "return (function()" .. compile_let(form, not statement) .. " end)()"
end
end
--- Returns a string of Lua source code from the form
-- TODO needs to compile returns vs not returns, I think...
-- So this may need to encode the context of whether this is in an expression or statement context
compile = function(form, statement)
if is_list(form) then
-- if count(form) == 0 then return "__rt.list()" end
if count(form) == 0 then return ret(statement) .. "require('org.faddle.lang.rt').list()" end
-- If it's a literal number or string you can compile directly to a lookup
-- Symbols still need to be checked whether they resolve to a string or number though
if first(form) == FN then return compile_fn(form, statement) end
if first(form) == LET then return compile_let(form, statement) end
print(lisp_print(form))
assert(count(form) == 1, "uhhh")
return ret(statement) .. compile(first(form)) .. "()"
-- macro expansion :D
-- core functions
-- vars that are local, however you're supposed to know that :p
elseif is_symbol(form) then
assert(not module(form), "can't compile symbols with modules yet :(")
return ret(statement) .. munge(form)
else
local t = type(form)
if t == "number" or t == "nil" or t == "boolean" then
return ret(statement) .. tostring(form)
elseif t == "string" then
return ret(statement) .. string.format("%q", form)
elseif t == "table" then
local entries = {}
for k, v in pairs(form) do
entries[#entries+1] = "[" .. compile(k) .. "]=" .. compile(v)
end
return ret(statement) .. "{" .. table.concat(entries, ",").. "}"
else
print(lisp_print(form))
error("Cannot compile form of type '" .. t .. "'")
end
end
end
local loadfn = loadstring or load
local function execute(lua_source, chunk_name)
-- Really, needs to just have all of the std library loaded :p
print(lua_source)
local chunk, err = loadfn("local __rt = require('org.faddle.lang.rt');" .. lua_source, chunk_name)
if not chunk then
error(err)
else
return chunk()
end
end
return {compile = compile, execute = execute}