--
-- decoy_compile_js.lua
--
-- SPDX-FileCopyrightText: Copyright (c) 2023-2024 Chris Pressey, Cat's Eye Technologies.
-- This work is distributed under a 2-clause BSD license. For more information, see:
-- SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Decoy
table = require "table"
string = require "string"
io = require "io"
require "decoy.model"
local envi = require "decoy.env"
local ast = require "decoy.ast"
local Mangler = require "decoy.mangler"
local Compiler = {}
function Compiler:emit(s)
self.outfile:write(s)
end
function Compiler:emit_symbol(sym)
if not Symbol.is_class_of(sym) then error("Not a symbol: " .. depict(sym)) end
self:emit(self.mangler:mangle(depict(sym)))
end
function Compiler:emit_symbols(sexp)
while sexp ~= Nil do
if Cons.is_class_of(sexp) then
self:emit_symbol(sexp:head())
if sexp:tail() ~= Nil then
self:emit(",")
end
else
error("assertion failed: not a Cons")
end
sexp = sexp:tail()
end
end
function Compiler:compile_expr(expr, env)
debug("compile", depict(expr))
if Cons.is_class_of(expr) then
local head = expr:head()
if Symbol.is_class_of(head) then
if head:text() == "bind" then
self:compile_bind(expr:tail(), env)
elseif head:text() == "if" then
self:compile_if(expr:tail(), env)
elseif head:text() == "lambda" then
self:compile_lambda(expr:tail(), env)
else
self:compile_application(expr, env)
end
else
error("Can't compile application not in A-normal form: " .. depict(expr))
end
elseif Symbol.is_class_of(expr) then
self:compile_lookup(expr, env)
elseif String.is_class_of(expr) or Number.is_class_of(expr) or Boolean.is_class_of(expr) then
self:compile_literal(expr, env)
else
error("Unsupported form for expression compilation: " .. depict(expr))
end
end
function Compiler:compile_exprs(sexp, inter, env)
while sexp ~= Nil do
if Cons.is_class_of(sexp) then
self:compile_expr(sexp:head(), env)
if sexp:tail() ~= Nil then
inter()
end
else
error("assertion failed: not a Cons")
end
sexp = sexp:tail()
end
end
function Compiler:compile_print_expr(sexp, env)
self:emit("prn(")
self:compile_expr(sexp, env)
self:emit(");\n")
end
--[[ toplevels ]]--
function Compiler:compile_toplevel(expr, env)
if ast.Define.is_class_of(expr) then
self:compile_define(expr.name, expr.defn, env)
elseif ast.ImportFrom.is_class_of(expr) then
self:compile_import_from(expr.name, expr.imports, env)
else
self:compile_print_expr(expr, env)
end
end
function Compiler:compile_import_from(name, imports, env)
self:emit("// imported from " .. render(name) .. "\n")
end
function Compiler:compile_raw(program_text)
self:emit(program_text)
end
function Compiler:compile_define(name, defn, env)
if name == "module" then
self:emit("// ")
end
self:emit("const ")
self:emit_symbol(Symbol.new(name))
self:emit(" = ")
self:compile_expr(defn, env)
self:emit(";\n")
end
--[[ evaluable expressions ]]--
function Compiler:compile_bind(ast, env)
local name = ast:head()
local expr = ast:tail():head()
local body = ast:tail():tail():head()
self:emit("(function(")
self:emit_symbol(name)
self:emit("){return ")
self:compile_expr(body, envi.bind(name:text(), True, env))
self:emit(";}(")
self:compile_expr(expr, env)
self:emit("))")
end
function Compiler:compile_application(ast, env)
self:compile_lookup(ast:head(), env)
self:emit("(")
self:compile_exprs(ast:tail(), function() self:emit(",") end, env)
self:emit(")")
end
function Compiler:compile_if(ast, env)
self:emit("(truthy(")
self:compile_expr(ast:head(), env)
self:emit(") ? ")
self:compile_expr(ast:tail():head(), env)
self:emit(" : ")
self:compile_expr(ast:tail():tail():head(), env)
self:emit(")")
end
function Compiler:compile_lambda(ast, env)
local args = ast:head()
self:emit("function")
self:emit("(")
self:emit_symbols(args)
self:emit(") {")
local new_env = envi.clone(env)
local sexp = args
while sexp ~= Nil do
new_env[sexp:head():text()] = True -- It's defined. There may be more information here someday.
sexp = sexp:tail()
end
local body = ast:tail():head()
self:emit("return ")
self:compile_expr(body, new_env)
self:emit(";}")
end
function Compiler:compile_lookup(ast, env)
self:emit_symbol(ast)
local bound_to = env[ast:text()]
if not bound_to then
error("Undefined symbol: " .. depict(ast))
end
end
function Compiler:compile_literal(ast, env)
self:emit(depict(ast))
end
Compiler.new = function(outfile)
local self = {
outfile = outfile,
mangler = Mangler.new(),
}
setmetatable(self, {__index = Compiler})
return self
end
return Compiler