/* * Copyright (C) 2023 Riyyi * * SPDX-License-Identifier: MIT */ #include // sd::advance, std::next, std::prev #include #include // std::static_pointer_cast #include // std::span #include #include "ast.h" #include "environment.h" #include "error.h" #include "eval.h" #include "forward.h" #include "types.h" #include "util.h" namespace blaze { Eval::Eval(ValuePtr ast, EnvironmentPtr env) : m_ast(ast) , m_env(env) { } // ----------------------------------------- void Eval::eval() { m_ast_stack = std::stack(); m_env_stack = std::stack(); m_ast_stack.push(m_ast); m_env_stack.push(m_env); m_ast = evalImpl(); } ValuePtr Eval::evalImpl() { ValuePtr ast = nullptr; EnvironmentPtr env = nullptr; while (true) { if (m_ast_stack.size() == 0) { return nullptr; } if (m_env_stack.size() == 0) { m_env_stack.push(m_env); } ast = m_ast_stack.top(); env = m_env_stack.top(); m_ast_stack.pop(); m_env_stack.pop(); if (!is(ast.get())) { return evalAst(ast, env); } auto list = std::static_pointer_cast(ast); if (list->empty()) { return ast; } // Special forms auto nodes = list->nodes(); if (is(nodes.front().get())) { auto symbol = std::static_pointer_cast(nodes.front())->symbol(); nodes.pop_front(); if (symbol == "def!") { return evalDef(nodes, env); } if (symbol == "let*") { evalLet(nodes, env); continue; // TCO } if (symbol == "quote") { return evalQuote(nodes); } if (symbol == "quasiquote") { evalQuasiQuote(nodes, env); continue; // TCO } if (symbol == "quasiquoteexpand") { return evalQuasiQuoteExpand(nodes, env); } if (symbol == "do") { evalDo(nodes, env); continue; // TCO } if (symbol == "if") { evalIf(nodes, env); continue; // TCO } if (symbol == "fn*") { return evalFn(nodes, env); } } auto evaluated_list = std::static_pointer_cast(evalAst(ast, env)); if (evaluated_list == nullptr) { return nullptr; } // Regular list if (is(evaluated_list->nodes().front().get())) { auto evaluated_nodes = evaluated_list->nodes(); // car auto lambda = std::static_pointer_cast(evaluated_nodes.front()); // cdr evaluated_nodes.pop_front(); m_ast_stack.push(lambda->body()); m_env_stack.push(Environment::create(lambda, evaluated_nodes)); continue; // TCO } // Function call return apply(evaluated_list); } } ValuePtr Eval::evalAst(ValuePtr ast, EnvironmentPtr env) { if (ast == nullptr || env == nullptr) { return nullptr; } Value* ast_raw_ptr = ast.get(); if (is(ast_raw_ptr)) { auto result = env->get(std::static_pointer_cast(ast)->symbol()); if (!result) { Error::the().add(format("'{}' not found", ast)); return nullptr; } return result; } else if (is(ast_raw_ptr)) { std::shared_ptr result = nullptr; (is(ast_raw_ptr)) ? result = makePtr() : result = makePtr(); auto nodes = std::static_pointer_cast(ast)->nodes(); for (auto node : nodes) { m_ast_stack.push(node); m_env_stack.push(env); ValuePtr eval_node = evalImpl(); if (eval_node == nullptr) { return nullptr; } result->add(eval_node); } return result; } else if (is(ast_raw_ptr)) { auto result = makePtr(); auto elements = std::static_pointer_cast(ast)->elements(); for (auto& element : elements) { m_ast_stack.push(element.second); m_env_stack.push(env); ValuePtr element_node = evalImpl(); if (element_node == nullptr) { return nullptr; } result->add(element.first, element_node); } return result; } return ast; } // ----------------------------------------- ValuePtr Eval::evalDef(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("def!", nodes.size(), 2); // First argument needs to be a Symbol VALUE_CAST(symbol, Symbol, nodes.front()); // Eval second argument m_ast_stack.push(*std::next(nodes.begin())); m_env_stack.push(env); ValuePtr value = evalImpl(); // Dont overwrite symbols after an error if (Error::the().hasAnyError()) { return nullptr; } // Modify existing environment return env->set(symbol->symbol(), value); } ValuePtr Eval::evalQuote(const std::list& nodes) { CHECK_ARG_COUNT_IS("quote", nodes.size(), 1); return nodes.front(); } static bool isSymbol(ValuePtr value, const std::string& symbol) { if (!is(value.get())) { return false; } auto valueSymbol = std::static_pointer_cast(value)->symbol(); if (valueSymbol != symbol) { return false; } return true; } static ValuePtr startsWith(ValuePtr ast, const std::string& symbol) { if (!is(ast.get())) { return nullptr; } auto nodes = std::static_pointer_cast(ast)->nodes(); if (nodes.empty() || !isSymbol(nodes.front(), symbol)) { return nullptr; } // Dont count the Symbol as part of the arguments CHECK_ARG_COUNT_IS(symbol, nodes.size() - 1, 1); return *std::next(nodes.begin()); } static ValuePtr evalQuasiQuoteImpl(ValuePtr ast) { if (is(ast.get()) || is(ast.get())) { return makePtr(makePtr("quote"), ast); } if (!is(ast.get())) { return ast; } // `~2 or `(unquote 2) const auto unquote = startsWith(ast, "unquote"); // x if (unquote) { return unquote; } // `~@(list 2 2 2) or `(splice-unquote (list 2 2 2)) const auto splice_unquote = startsWith(ast, "splice-unquote"); // (list 2 2 2) if (splice_unquote) { return splice_unquote; } ValuePtr result = makePtr(); auto nodes = std::static_pointer_cast(ast)->nodes(); // `() or `(1 ~2 3) or `(1 ~@(list 2 2 2) 3) for (auto it = nodes.rbegin(); it != nodes.rend(); ++it) { const auto elt = *it; const auto splice_unquote = startsWith(elt, "splice-unquote"); // (list 2 2 2) if (splice_unquote) { // (cons 1 (concat (list 2 2 2) (cons 3 ()))) result = makePtr(makePtr("concat"), splice_unquote, result); continue; } // (cons 1 (cons 2 (cons 3 ()))) result = makePtr(makePtr("cons"), evalQuasiQuoteImpl(elt), result); } if (is(ast.get())) { return result; } // Wrap result in (vec) for Vector types return makePtr(makePtr("vec"), result); } void Eval::evalQuasiQuote(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("quasiquote", nodes.size(), 1, void()); auto result = evalQuasiQuoteImpl(nodes.front()); m_ast_stack.push(result); m_env_stack.push(env); return; // TCO } ValuePtr Eval::evalQuasiQuoteExpand(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("quasiquoteexpand", nodes.size(), 1); return evalQuasiQuoteImpl(nodes.front()); } // (let* (x 1) x) void Eval::evalLet(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("let*", nodes.size(), 2, void()); // First argument needs to be a List or Vector VALUE_CAST(bindings, Collection, nodes.front(), void()); auto binding_nodes = bindings->nodes(); // List or Vector needs to have an even number of elements CHECK_ARG_COUNT_EVEN("bindings", binding_nodes.size(), void()); // Create new environment auto let_env = Environment::create(env); for (auto it = binding_nodes.begin(); it != binding_nodes.end(); std::advance(it, 2)) { // First element needs to be a Symbol VALUE_CAST(elt, Symbol, (*it), void()); std::string key = elt->symbol(); m_ast_stack.push(*std::next(it)); m_env_stack.push(let_env); ValuePtr value = evalImpl(); let_env->set(key, value); } // TODO: Remove limitation of 3 arguments // Eval all arguments in this new env, return last sexp of the result m_ast_stack.push(*std::next(nodes.begin())); m_env_stack.push(let_env); return; // TCO } void Eval::evalDo(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_AT_LEAST("do", nodes.size(), 1, void()); // Evaluate all nodes except the last for (auto it = nodes.begin(); it != std::prev(nodes.end(), 1); ++it) { m_ast_stack.push(*it); m_env_stack.push(env); evalImpl(); } // Eval last node m_ast_stack.push(nodes.back()); m_env_stack.push(env); return; // TCO } void Eval::evalIf(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_BETWEEN("if", nodes.size(), 2, 3, void()); auto first_argument = *nodes.begin(); auto second_argument = *std::next(nodes.begin()); auto third_argument = (nodes.size() == 3) ? *std::next(std::next(nodes.begin())) : makePtr(Constant::Nil); m_ast_stack.push(first_argument); m_env_stack.push(env); auto first_evaluated = evalImpl(); if (!is(first_evaluated.get()) || std::static_pointer_cast(first_evaluated)->state() == Constant::True) { m_ast_stack.push(second_argument); m_env_stack.push(env); return; // TCO } m_ast_stack.push(third_argument); m_env_stack.push(env); return; // TCO } // (fn* (x) x) ValuePtr Eval::evalFn(const std::list& nodes, EnvironmentPtr env) { CHECK_ARG_COUNT_IS("fn*", nodes.size(), 2); // First element needs to be a List or Vector VALUE_CAST(collection, Collection, nodes.front()); std::vector bindings; for (auto node : collection->nodes()) { // All nodes need to be a Symbol VALUE_CAST(symbol, Symbol, node); bindings.push_back(symbol->symbol()); } // TODO: Remove limitation of 3 arguments // Wrap all other nodes in list and add that as lambda body return makePtr(bindings, *std::next(nodes.begin()), env); } //----------------------------------------- ValuePtr Eval::apply(std::shared_ptr evaluated_list) { if (evaluated_list == nullptr) { return nullptr; } auto nodes = evaluated_list->nodes(); if (!is(nodes.front().get())) { Error::the().add(format("invalid function: {}", nodes.front())); return nullptr; } // car auto function = std::static_pointer_cast(nodes.front())->function(); // cdr nodes.pop_front(); return function(nodes); } } // namespace blaze