diff --git a/CMakeLists.txt b/CMakeLists.txt index 3757967..5a6fd20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,3 +91,7 @@ add_dependencies(test1 ${PROJECT}) add_custom_target(test2 COMMAND env STEP=step_eval MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step2_eval.mal -- ./${PROJECT}) add_dependencies(test2 ${PROJECT}) + +add_custom_target(test3 + COMMAND env STEP=step_env MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step3_env.mal -- ./${PROJECT}) +add_dependencies(test3 ${PROJECT}) diff --git a/src/ast.cpp b/src/ast.cpp index 02edd6f..a756420 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -74,6 +74,7 @@ Function::Function(Lambda lambda) void Formatter::format(Builder& builder, blaze::ASTNodePtr value) const { + // printf("ASDJASJKDASJKDNAJK\n"); blaze::Printer printer; return Formatter::format(builder, printer.printNoErrorCheck(value)); } diff --git a/src/environment.cpp b/src/environment.cpp new file mode 100644 index 0000000..fcc4131 --- /dev/null +++ b/src/environment.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include "ruc/format/print.h" + +#include "ast.h" +#include "environment.h" + +namespace blaze { + +Environment::Environment(EnvironmentPtr outer) + : m_outer(outer) +{ +} + +// ----------------------------------------- + +bool Environment::exists(const std::string& symbol) +{ + return m_values.find(symbol) != m_values.end(); +} + +ASTNodePtr Environment::set(const std::string& symbol, ASTNodePtr value) +{ + if (exists(symbol)) { + m_values.erase(symbol); + } + + m_values.emplace(symbol, value); + + return value; +} + +ASTNodePtr Environment::get(const std::string& symbol) +{ + m_current_key = symbol; + + if (exists(symbol)) { + return m_values[symbol]; + } + + if (m_outer) { + return m_outer->get(symbol); + } + + return nullptr; +} + +// ----------------------------------------- + +GlobalEnvironment::GlobalEnvironment() +{ + add(); + sub(); + mul(); + div(); +} + +} // namespace blaze diff --git a/src/environment.h b/src/environment.h index 992f925..f79ef45 100644 --- a/src/environment.h +++ b/src/environment.h @@ -6,146 +6,43 @@ #pragma once -#include // int64_t -#include -#include -#include -#include +#include #include -#include - -#include "error.h" -#include "ruc/format/color.h" -#include "ruc/singleton.h" #include "ast.h" -#include "types.h" namespace blaze { +class Environment; +typedef std::shared_ptr EnvironmentPtr; + class Environment { public: Environment() = default; + Environment(EnvironmentPtr outer); virtual ~Environment() = default; - ASTNodePtr lookup(const std::string& symbol) - { - m_current_key = symbol; - return m_values.find(symbol) != m_values.end() ? m_values[symbol] : nullptr; - } + bool exists(const std::string& symbol); + ASTNodePtr set(const std::string& symbol, ASTNodePtr value); + ASTNodePtr get(const std::string& symbol); protected: std::string m_current_key; std::unordered_map m_values; + EnvironmentPtr m_outer { nullptr }; }; class GlobalEnvironment final : public Environment { public: - GlobalEnvironment() - { - // TODO: Add more native functions - // TODO: Move the functions to their own file - auto add = [](std::span nodes) -> ASTNodePtr { - int64_t result = 0; - - for (auto node : nodes) { - if (!is(node.get())) { - Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); - return nullptr; - } - - result += static_pointer_cast(node)->number(); - } - - return makePtr(result); - }; - - auto sub = [](std::span nodes) -> ASTNodePtr { - int64_t result = 0; - - if (nodes.size() == 0) { - return makePtr(0); - } - - for (auto node : nodes) { - if (!is(node.get())) { - Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); - return nullptr; - } - } - - // Start with the first number - result += static_pointer_cast(nodes[0])->number(); - - // Skip the first node - for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) { - result -= static_pointer_cast(*it)->number(); - } - - return makePtr(result); - }; - - auto mul = [](std::span nodes) -> ASTNodePtr { - int64_t result = 1; - - for (auto node : nodes) { - if (!is(node.get())) { - Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); - return nullptr; - } - - result *= static_pointer_cast(node)->number(); - } - - return makePtr(result); - }; - - auto div = [this](std::span nodes) -> ASTNodePtr { - double result = 0; - - if (nodes.size() == 0) { - Error::the().addError(format("wrong number of arguments: {}, 0", m_current_key)); - return nullptr; - } - - for (auto node : nodes) { - if (!is(node.get())) { - Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); - return nullptr; - } - } - - // Start with the first number - result += static_pointer_cast(nodes[0])->number(); - - // Skip the first node - for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) { - result /= static_pointer_cast(*it)->number(); - } - - return makePtr((int64_t)result); - }; - - m_values.emplace("+", makePtr(add)); - m_values.emplace("-", makePtr(sub)); - m_values.emplace("*", makePtr(mul)); - m_values.emplace("/", makePtr(div)); - } + GlobalEnvironment(); virtual ~GlobalEnvironment() = default; + +private: + // TODO: Add more native functions + void add(); + void sub(); + void mul(); + void div(); }; } // namespace blaze - -// associative data structure that maps symbols (the keys) to values - -// values = anything, including other symbols. -// an environment is like a hash table - -// value can map to: -// list -// vector -// hash-map -// symbol -// number -// string -// function diff --git a/src/eval.cpp b/src/eval.cpp index e56ac5e..6edebc1 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -4,17 +4,20 @@ * SPDX-License-Identifier: MIT */ -#include // std::span +#include // std::static_pointer_cast +#include // std::span +#include #include "ast.h" #include "environment.h" +#include "error.h" #include "eval.h" #include "ruc/meta/assert.h" #include "types.h" namespace blaze { -Eval::Eval(ASTNodePtr ast, Environment* env) +Eval::Eval(ASTNodePtr ast, EnvironmentPtr env) : m_ast(ast) , m_env(env) { @@ -25,37 +28,46 @@ void Eval::eval() m_ast = evalImpl(m_ast, m_env); } -ASTNodePtr Eval::evalImpl(ASTNodePtr ast, Environment* env) +ASTNodePtr Eval::evalImpl(ASTNodePtr ast, EnvironmentPtr env) { if (!is(ast.get())) { return evalAst(ast, env); } - if (static_cast(ast.get())->empty()) { + + auto list = std::static_pointer_cast(ast); + + if (list->empty()) { return ast; } - return apply(static_pointer_cast(evalAst(ast, env))); + // Environment + auto nodes = list->nodes(); + if (is(nodes[0].get())) { + auto symbol = std::static_pointer_cast(nodes[0])->symbol(); + if (symbol == "def!") { + return evalDef(nodes, env); + } + if (symbol == "let*") { + return evalLet(nodes, env); + } + } + + return apply(std::static_pointer_cast(evalAst(ast, env))); } -ASTNodePtr Eval::evalAst(ASTNodePtr ast, Environment* env) +ASTNodePtr Eval::evalAst(ASTNodePtr ast, EnvironmentPtr env) { ASTNode* ast_raw_ptr = ast.get(); if (is(ast_raw_ptr)) { - auto result = env->lookup(static_pointer_cast(ast)->symbol()); + auto result = env->get(std::static_pointer_cast(ast)->symbol()); if (!result) { - // TODO: Maybe add backlink to parent nodes? - if (is(m_ast)) { - Error::the().addError(format("symbol's function definition is void: {}", ast_raw_ptr)); - } - else { - Error::the().addError(format("symbol's value as variable is void: {}", ast_raw_ptr)); - } + Error::the().addError(format("'{}' not found", ast)); } return result; } else if (is(ast_raw_ptr)) { auto result = makePtr(); - auto nodes = static_pointer_cast(ast)->nodes(); + auto nodes = std::static_pointer_cast(ast)->nodes(); for (auto node : nodes) { result->addNode(evalImpl(node, env)); } @@ -63,7 +75,7 @@ ASTNodePtr Eval::evalAst(ASTNodePtr ast, Environment* env) } else if (is(ast_raw_ptr)) { auto result = makePtr(); - auto nodes = static_pointer_cast(ast)->nodes(); + auto nodes = std::static_pointer_cast(ast)->nodes(); for (auto node : nodes) { result->addNode(evalImpl(node, env)); } @@ -71,7 +83,7 @@ ASTNodePtr Eval::evalAst(ASTNodePtr ast, Environment* env) } else if (is(ast_raw_ptr)) { auto result = makePtr(); - auto elements = static_pointer_cast(ast)->elements(); + auto elements = std::static_pointer_cast(ast)->elements(); for (auto& element : elements) { result->addElement(element.first, evalImpl(element.second, env)); } @@ -81,6 +93,67 @@ ASTNodePtr Eval::evalAst(ASTNodePtr ast, Environment* env) return ast; } +ASTNodePtr Eval::evalDef(const std::vector& nodes, EnvironmentPtr env) +{ + if (nodes.size() != 3) { + Error::the().addError(format("wrong number of arguments: def!, {}", nodes.size() - 1)); + return nullptr; + } + + // First element needs to be a Symbol + if (!is(nodes[1].get())) { + Error::the().addError(format("wrong type argument: symbol, {}", nodes[1])); + return nullptr; + } + + std::string symbol = std::static_pointer_cast(nodes[1])->symbol(); + + // Modify existing environment + return env->set(symbol, evalImpl(nodes[2], env)); +} + +ASTNodePtr Eval::evalLet(const std::vector& nodes, EnvironmentPtr env) +{ + if (nodes.size() != 3) { + Error::the().addError(format("wrong number of arguments: let*, {}", nodes.size() - 1)); + return nullptr; + } + + // Create new environment + auto let_env = makePtr(env); + + // First argument needs to be a List + if (!is(nodes[1].get())) { + Error::the().addError(format("wrong type argument: list, {}", nodes[1])); + return nullptr; + } + + // List needs to have an even number of elements + auto bindings = std::static_pointer_cast(nodes[1]); + auto binding_nodes = bindings->nodes(); + if (bindings->nodes().size() % 2 != 0) { + // FIXME: Print correct value + Error::the().addError("FIXME"); + // Error::the().addError(format("wrong number of arguments: {}, {}", bindings, bindings->nodes().size())); + } + + size_t count = binding_nodes.size(); + for (size_t i = 0; i < count; i += 2) { + // First element needs to be a Symbol + if (!is(binding_nodes[i].get())) { + Error::the().addError(format("wrong type argument: symbol, {}", binding_nodes[i])); + } + + std::string key = std::static_pointer_cast(binding_nodes[i])->symbol(); + ASTNodePtr value = evalAst(binding_nodes[i + 1], let_env); + let_env->set(key, value); + } + + // TODO: Remove limitation of 3 arguments + // Eval all values in this new env, return last sexp of the result + return evalImpl(nodes[2], let_env); +} + ASTNodePtr Eval::apply(std::shared_ptr evaluated_list) { auto nodes = evaluated_list->nodes(); @@ -91,7 +164,7 @@ ASTNodePtr Eval::apply(std::shared_ptr evaluated_list) } // car - auto lambda = static_pointer_cast(nodes[0])->lambda(); + auto lambda = std::static_pointer_cast(nodes[0])->lambda(); // cdr std::span span { nodes.data() + 1, nodes.size() - 1 }; diff --git a/src/eval.h b/src/eval.h index 06dcd2d..2587321 100644 --- a/src/eval.h +++ b/src/eval.h @@ -6,6 +6,8 @@ #pragma once +#include + #include "ast.h" #include "environment.h" @@ -13,7 +15,7 @@ namespace blaze { class Eval { public: - Eval(ASTNodePtr ast, Environment* env); + Eval(ASTNodePtr ast, EnvironmentPtr env); virtual ~Eval() = default; void eval(); @@ -21,12 +23,14 @@ public: ASTNodePtr ast() const { return m_ast; } private: - ASTNodePtr evalImpl(ASTNodePtr ast, Environment* env); - ASTNodePtr evalAst(ASTNodePtr ast, Environment* env); + ASTNodePtr evalImpl(ASTNodePtr ast, EnvironmentPtr env); + ASTNodePtr evalAst(ASTNodePtr ast, EnvironmentPtr env); + ASTNodePtr evalDef(const std::vector& nodes, EnvironmentPtr env); + ASTNodePtr evalLet(const std::vector& nodes, EnvironmentPtr env); ASTNodePtr apply(std::shared_ptr evaluated_list); - ASTNodePtr m_ast { nullptr }; - Environment* m_env { nullptr }; + ASTNodePtr m_ast; + EnvironmentPtr m_env; }; } // namespace blaze diff --git a/src/functions.cpp b/src/functions.cpp new file mode 100644 index 0000000..d56aa80 --- /dev/null +++ b/src/functions.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 Riyi + * + * SPDX-License-Identifier: MIT + */ + +#include // std::static_pointer_cast + +#include "ruc/format/format.h" + +#include "ast.h" +#include "environment.h" +#include "error.h" +#include "types.h" + +namespace blaze { + +void GlobalEnvironment::add() +{ + auto add = [](std::span nodes) -> ASTNodePtr { + int64_t result = 0; + + for (auto node : nodes) { + if (!is(node.get())) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + + result += std::static_pointer_cast(node)->number(); + } + + return makePtr(result); + }; + + m_values.emplace("+", makePtr(add)); +} + +void GlobalEnvironment::sub() +{ + auto sub = [](std::span nodes) -> ASTNodePtr { + int64_t result = 0; + + if (nodes.size() == 0) { + return makePtr(0); + } + + for (auto node : nodes) { + if (!is(node.get())) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + } + + // Start with the first number + result += std::static_pointer_cast(nodes[0])->number(); + + // Skip the first node + for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) { + result -= std::static_pointer_cast(*it)->number(); + } + + return makePtr(result); + }; + + m_values.emplace("-", makePtr(sub)); +} + +void GlobalEnvironment::mul() +{ + auto mul = [](std::span nodes) -> ASTNodePtr { + int64_t result = 1; + + for (auto node : nodes) { + if (!is(node.get())) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + + result *= std::static_pointer_cast(node)->number(); + } + + return makePtr(result); + }; + + m_values.emplace("*", makePtr(mul)); +} + +void GlobalEnvironment::div() +{ + auto div = [this](std::span nodes) -> ASTNodePtr { + double result = 0; + + if (nodes.size() == 0) { + Error::the().addError(format("wrong number of arguments: {}, 0", m_current_key)); + return nullptr; + } + + for (auto node : nodes) { + if (!is(node.get())) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + } + + // Start with the first number + result += std::static_pointer_cast(nodes[0])->number(); + + // Skip the first node + for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) { + result /= std::static_pointer_cast(*it)->number(); + } + + return makePtr((int64_t)result); + }; + + m_values.emplace("/", makePtr(div)); +} + +} // namespace blaze diff --git a/src/step2_eval.cpp b/src/step2_eval.cpp index 8a896ac..2fdd470 100644 --- a/src/step2_eval.cpp +++ b/src/step2_eval.cpp @@ -15,7 +15,7 @@ #include "readline.h" #include "settings.h" -#if 1 +#if 0 auto read(std::string_view input) -> blaze::ASTNodePtr { blaze::Lexer lexer(input); diff --git a/src/step3_env.cpp b/src/step3_env.cpp new file mode 100644 index 0000000..2e0bf9e --- /dev/null +++ b/src/step3_env.cpp @@ -0,0 +1,108 @@ +#include // std::signal +#include // std::exit +#include +#include + +#include "ruc/argparser.h" +#include "ruc/format/color.h" + +#include "ast.h" +#include "environment.h" +#include "error.h" +#include "eval.h" +#include "lexer.h" +#include "printer.h" +#include "reader.h" +#include "readline.h" +#include "settings.h" + +#if 1 +static blaze::EnvironmentPtr env = blaze::makePtr(); + +auto read(std::string_view input) -> blaze::ASTNodePtr +{ + blaze::Lexer lexer(input); + lexer.tokenize(); + if (blaze::Settings::the().get("dump-lexer") == "1") { + lexer.dump(); + } + + blaze::Reader reader(std::move(lexer.tokens())); + reader.read(); + if (blaze::Settings::the().get("dump-reader") == "1") { + reader.dump(); + } + + return reader.node(); +} + +auto eval(blaze::ASTNodePtr ast) -> blaze::ASTNodePtr +{ + blaze::Eval eval(ast, env); + eval.eval(); + + return eval.ast(); +} + +auto print(blaze::ASTNodePtr exp) -> std::string +{ + blaze::Printer printer; + + return printer.print(exp); +} + +auto rep(std::string_view input) -> std::string +{ + blaze::Error::the().clearErrors(); + blaze::Error::the().setInput(input); + + return print(eval(read(input))); +} + +static auto cleanup(int signal) -> void +{ + print("\033[0m\n"); + std::exit(signal); +} + +auto main(int argc, char* argv[]) -> int +{ + bool dump_lexer = false; + bool dump_reader = false; + bool pretty_print = false; + std::string_view history_path = "~/.mal-history"; + + // CLI arguments + ruc::ArgParser arg_parser; + arg_parser.addOption(dump_lexer, 'l', "dump-lexer", nullptr, nullptr); + arg_parser.addOption(dump_reader, 'r', "dump-reader", nullptr, nullptr); + arg_parser.addOption(pretty_print, 'c', "color", nullptr, nullptr); + arg_parser.addOption(history_path, 'h', "history", nullptr, nullptr, nullptr, ruc::ArgParser::Required::Yes); + arg_parser.parse(argc, argv); + + // Set settings + blaze::Settings::the().set("dump-lexer", dump_lexer ? "1" : "0"); + blaze::Settings::the().set("dump-reader", dump_reader ? "1" : "0"); + blaze::Settings::the().set("pretty-print", pretty_print ? "1" : "0"); + + // Signal callbacks + std::signal(SIGINT, cleanup); + std::signal(SIGTERM, cleanup); + + blaze::Readline readline(pretty_print, history_path); + + std::string input; + while (readline.get(input)) { + if (pretty_print) { + print("\033[0m"); + } + print("{}\n", rep(input)); + } + + if (pretty_print) { + print("\033[0m"); + } + + return 0; +} +#endif