git @ Cat's Eye Technologies Decoy / master src / decoy / compile_js.lua
master

Tree @master (Download .tar.gz)

compile_js.lua @masterraw · history · blame

--
-- 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