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

Tree @master (Download .tar.gz)

eval.lua @masterraw · history · blame

--
-- decoy.eval
--

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

require "decoy.model"
local envi = require "decoy.env"


local Evaluator = {}

function Evaluator:eval_exprs(expr, env)
    local exprs = {}
    while expr ~= Nil do
        if Cons.is_class_of(expr) then
            local value = self:eval_expr(expr:head(), env)
            table.insert(exprs, value)
            debug("eval", "eval'ed " .. depict(expr:head()) .. ", inserted " .. depict(value) .. " to " .. render(exprs))
        else
            error("assertion failed: not a Cons")
        end
        expr = expr:tail()
    end
    return exprs
end

function Evaluator:eval_expr(ast, env)
    if Cons.is_class_of(ast) then
        local head = ast:head()
        if Symbol.is_class_of(head) then
            if head:text() == "bind" then
                local name = ast:tail():head()
                local expr = ast:tail():tail():head()
                local body = ast:tail():tail():tail():head()
                local new_env = envi.bind(name:text(), self:eval_expr(expr, env), env)
                return self:eval_expr(body, new_env)
            elseif head:text() == "if" then
                local expr = ast:tail()
                local result = self:eval_expr(expr:head(), env)
                if not Boolean.is_class_of(result) or result:value() then
                    return self:eval_expr(expr:tail():head(), env)
                else
                    return self:eval_expr(expr:tail():tail():head(), env)
                end
            elseif head:text() == "lambda" then
                local formals = ast:tail():head()
                local body = ast:tail():tail():head()
                local f = function(args)
                    --args is a table
                    local new_env = envi.extend(env, formals, args)
                    return self:eval_expr(body, new_env)
                end
                return f
            else
                local fn
                debug("eval", "head Symbol: " .. depict(head))
                fn = self:eval_expr(ast:head(), env)
                local args = self:eval_exprs(ast:tail(), env)
                do_debug("eval", function()
                    print("we got this:", depict(ast:tail()))
                    print("we turned it into:", render(args))
                end)
                return fn(args)
            end
        else
            local fn
            debug("eval", "head: " .. depict(head))
            fn = self:eval_expr(ast:head(), env)
            return fn(self:eval_exprs(ast:tail(), env))
        end
    elseif Symbol.is_class_of(ast) then
        local value = env[ast:text()]
        if value == nil then
            error("Unbound symbol: " .. ast:text())
        end
        return value
    elseif String.is_class_of(ast) or Number.is_class_of(ast) or Boolean.is_class_of(ast) then
        return ast
    else
        error("Unsupported form for evaluation: " ..  depict(ast))
    end
end

Evaluator.new = function()
    local self = {
    }
    setmetatable(self, {__index = Evaluator})
    return self
end

return Evaluator