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