compile.lua

Faddle · faddle.git · Tickets

Branches > main > a85f72c > 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
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

-- 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)
  assert(type(bindings) == "table" and #bindings % 2 == 0, "Let requires an even number of binding forms")
  assert(#bindings == 2)
  local sym = bindings[1]
  local binding = bindings[2]
  if statement then
    return "local " .. munge(sym) .. "=" .. compile(binding) .. ";" .. compile_body(body)
  else
    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}