diff --git a/CMakeLists.txt b/CMakeLists.txt index 015e32e..4f1d93b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,3 +87,7 @@ add_dependencies(test0 ${PROJECT}) add_custom_target(test1 COMMAND env STEP=step1_read_print MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step1_read_print.mal -- ./${PROJECT}) 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}) diff --git a/src/ast.cpp b/src/ast.cpp index 47da97e..803a627 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -59,6 +59,13 @@ Value::Value(const std::string& value) { } +// ----------------------------------------- + +Function::Function(Lambda lambda) + : m_lambda(lambda) +{ +} + } // namespace blaze // ----------------------------------------- diff --git a/src/ast.h b/src/ast.h index cafa549..6cf3134 100644 --- a/src/ast.h +++ b/src/ast.h @@ -6,7 +6,9 @@ #pragma once -#include // int64_t +#include // int64_t +#include // std::function +#include #include #include #include // typeid @@ -51,6 +53,8 @@ public: void addNode(ASTNode* node); const std::vector& nodes() const { return m_nodes; } + size_t size() const { return m_nodes.size(); } + bool empty() const { return m_nodes.size() == 0; } protected: Collection() {} @@ -173,6 +177,22 @@ public: private: std::string m_value; }; + +// ----------------------------------------- + +using Lambda = std::function)>; + +class Function final : public ASTNode { +public: + Function(Lambda lambda); + virtual ~Function() = default; + + virtual bool isFunction() const override { return true; } + + Lambda lambda() const { return m_lambda; } + +private: + Lambda m_lambda; }; // ----------------------------------------- @@ -204,6 +224,9 @@ inline bool ASTNode::fastIs() const { return isSymbol(); } template<> inline bool ASTNode::fastIs() const { return isValue(); } + +template<> +inline bool ASTNode::fastIs() const { return isFunction(); } // clang-format on } // namespace blaze diff --git a/src/environment.h b/src/environment.h new file mode 100644 index 0000000..8131626 --- /dev/null +++ b/src/environment.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include // int64_t +#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 { +public: + Environment() = default; + virtual ~Environment() = default; + + ASTNode* lookup(const std::string& symbol) + { + m_current_key = symbol; + return m_values.find(symbol) != m_values.end() ? m_values[symbol] : nullptr; + } + +protected: + std::string m_current_key; + std::unordered_map m_values; +}; + +class GlobalEnvironment final : public Environment { +public: + GlobalEnvironment() + { + auto add = [](std::span nodes) -> ASTNode* { + int64_t result = 0; + + for (auto node : nodes) { + if (!is(node)) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + + result += static_cast(node)->number(); + } + + return new Number(result); + }; + + auto sub = [](std::span nodes) -> ASTNode* { + int64_t result = 0; + + if (nodes.size() == 0) { + return new Number(0); + } + + for (auto node : nodes) { + if (!is(node)) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + } + + // Start with the first number + result += static_cast(nodes[0])->number(); + + // Skip the first node + for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) { + result -= static_cast(*it)->number(); + } + + return new Number(result); + }; + + auto mul = [](std::span nodes) -> ASTNode* { + int64_t result = 1; + + for (auto node : nodes) { + if (!is(node)) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + + result *= static_cast(node)->number(); + } + + return new Number(result); + }; + + auto div = [this](std::span nodes) -> ASTNode* { + 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)) { + Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node)); + return nullptr; + } + } + + // Start with the first number + result += static_cast(nodes[0])->number(); + + // Skip the first node + for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) { + result /= static_cast(*it)->number(); + } + + return new Number((int64_t)result); + }; + + m_values.emplace("+", new Function(add)); + m_values.emplace("-", new Function(sub)); + m_values.emplace("*", new Function(mul)); + m_values.emplace("/", new Function(div)); + } + virtual ~GlobalEnvironment() = default; +}; + +} // 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 new file mode 100644 index 0000000..85a43f0 --- /dev/null +++ b/src/eval.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // std::span + +#include "ast.h" +#include "environment.h" +#include "eval.h" +#include "ruc/meta/assert.h" +#include "types.h" + +namespace blaze { + +Eval::Eval(ASTNode* ast, Environment* env) + : m_ast(ast) + , m_env(env) +{ +} + +void Eval::eval() +{ + m_ast = evalImpl(m_ast, m_env); +} + +ASTNode* Eval::evalImpl(ASTNode* ast, Environment* env) +{ + if (!is(ast)) { + return evalAst(ast, env); + } + if (static_cast(ast)->empty()) { + return ast; + } + + return apply(static_cast(evalAst(ast, env))); +} + +ASTNode* Eval::evalAst(ASTNode* ast, Environment* env) +{ + if (is(ast)) { + auto result = env->lookup(static_cast(ast)->symbol()); + if (!result) { + Error::the().addError(format("'{}' not found", ast)); + } + return result; + } + else if (is(ast)) { + auto result = new List(); + auto nodes = static_cast(ast)->nodes(); + for (auto node : nodes) { + result->addNode(evalImpl(node, env)); + } + return result; + } + else if (is(ast)) { + auto result = new Vector(); + auto nodes = static_cast(ast)->nodes(); + for (auto node : nodes) { + result->addNode(evalImpl(node, env)); + } + return result; + } + else if (is(ast)) { + // TODO + VERIFY_NOT_REACHED(); + return nullptr; + } + + return ast; +} + +ASTNode* Eval::apply(List* evaluated_list) +{ + auto nodes = evaluated_list->nodes(); + + if (!is(nodes[0])) { + return nullptr; + } + + // car + auto lambda = static_cast(nodes[0])->lambda(); + // cdr + std::span span { nodes.data() + 1, nodes.size() - 1 }; + + return lambda(span); +} + +} // namespace blaze diff --git a/src/eval.h b/src/eval.h new file mode 100644 index 0000000..dcc9ace --- /dev/null +++ b/src/eval.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "ast.h" +#include "environment.h" + +namespace blaze { + +class Eval { +public: + Eval(ASTNode* ast, Environment* env); + virtual ~Eval() = default; + + void eval(); + + ASTNode* ast() const { return m_ast; } + +private: + ASTNode* evalImpl(ASTNode* ast, Environment* env); + ASTNode* evalAst(ASTNode* ast, Environment* env); + ASTNode* apply(List* evaluated_list); + + ASTNode* m_ast { nullptr }; + Environment* m_env { nullptr }; +}; + +} // namespace blaze diff --git a/src/step1_read_print.cpp b/src/step1_read_print.cpp index e901b2e..d28840d 100644 --- a/src/step1_read_print.cpp +++ b/src/step1_read_print.cpp @@ -14,7 +14,7 @@ #include "reader.h" #include "settings.h" -#if 1 +#if 0 auto read(std::string_view input) -> blaze::ASTNode* { blaze::Lexer lexer(input); diff --git a/src/step2_eval.cpp b/src/step2_eval.cpp new file mode 100644 index 0000000..e0a546c --- /dev/null +++ b/src/step2_eval.cpp @@ -0,0 +1,113 @@ +#include // std::signal +#include // std::exit +#include // std::cin +#include // std::getline +#include + +#include "environment.h" +#include "eval.h" +#include "ruc/argparser.h" +#include "ruc/format/color.h" + +#include "ast.h" +#include "error.h" +#include "lexer.h" +#include "printer.h" +#include "reader.h" +#include "settings.h" + +#if 1 +auto read(std::string_view input) -> blaze::ASTNode* +{ + 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::ASTNode* ast) -> blaze::ASTNode* +{ + blaze::GlobalEnvironment env; + blaze::Eval eval(ast, &env); + eval.eval(); + return eval.ast(); +} + +auto print(blaze::ASTNode* exp) -> void +{ + blaze::Printer printer(exp); + printer.dump(); +} + +auto rep(std::string_view input) -> void +{ + blaze::Error::the().clearErrors(); + blaze::Error::the().setInput(input); + + print(eval(read(input))); +} + +static auto cleanup(int signal) -> void +{ + print("\033[0m"); + std::exit(signal); +} + +auto main(int argc, char* argv[]) -> int +{ + bool dump_lexer = false; + bool dump_reader = false; + bool pretty_print = false; + + // 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.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); + + while (true) { + if (!pretty_print) { + print("user> "); + } + else { + print(fg(ruc::format::TerminalColor::Blue), "user>"); + print(" \033[1m"); + } + std::string line; + std::getline(std::cin, line); + if (pretty_print) { + print("\033[0m"); + } + + // Exit with Ctrl-D + if (std::cin.eof() || std::cin.fail()) { + break; + } + + rep(line); + } + + return 0; +} +#endif + +// - Add AST node printing support to ruc::format