diff --git a/CMakeLists.txt b/CMakeLists.txt index 140730c..88e2f3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,3 +107,7 @@ add_dependencies(test5 ${PROJECT}) add_custom_target(test6 COMMAND env STEP=step_env MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step6_file.mal -- ./${PROJECT}) add_dependencies(test6 ${PROJECT}) + +add_custom_target(test7 + COMMAND env STEP=step_env MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step7_quote.mal -- ./${PROJECT}) +add_dependencies(test7 ${PROJECT}) diff --git a/src/ast.cpp b/src/ast.cpp index f4ff91b..7feb3ad 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -16,6 +16,11 @@ namespace blaze { +Collection::Collection(const std::list& nodes) + : m_nodes(nodes) +{ +} + void Collection::add(ValuePtr node) { if (node == nullptr) { @@ -27,6 +32,20 @@ void Collection::add(ValuePtr node) // ----------------------------------------- +List::List(const std::list& nodes) + : Collection(nodes) +{ +} + +// ----------------------------------------- + +Vector::Vector(const std::list& nodes) + : Collection(nodes) +{ +} + +// ----------------------------------------- + void HashMap::add(const std::string& key, ValuePtr value) { if (value == nullptr) { @@ -81,7 +100,7 @@ Function::Function(const std::string& name, FunctionType function) // ----------------------------------------- -Lambda::Lambda(std::vector bindings, ValuePtr body, EnvironmentPtr env) +Lambda::Lambda(const std::vector& bindings, ValuePtr body, EnvironmentPtr env) : m_bindings(bindings) , m_body(body) , m_env(env) diff --git a/src/ast.h b/src/ast.h index 9b3fa0c..b4af981 100644 --- a/src/ast.h +++ b/src/ast.h @@ -64,6 +64,7 @@ public: protected: Collection() = default; + Collection(const std::list& nodes); private: virtual bool isCollection() const override { return true; } @@ -77,6 +78,7 @@ private: class List final : public Collection { public: List() = default; + List(const std::list& nodes); virtual ~List() = default; private: @@ -89,6 +91,7 @@ private: class Vector final : public Collection { public: Vector() = default; + Vector(const std::list& nodes); virtual ~Vector() = default; private: @@ -236,10 +239,10 @@ private: class Lambda final : public Callable { public: - Lambda(std::vector bindings, ValuePtr body, EnvironmentPtr env); + Lambda(const std::vector& bindings, ValuePtr body, EnvironmentPtr env); virtual ~Lambda() = default; - std::vector bindings() const { return m_bindings; } + const std::vector& bindings() const { return m_bindings; } ValuePtr body() const { return m_body; } EnvironmentPtr env() const { return m_env; } diff --git a/src/eval.cpp b/src/eval.cpp index 69f88a1..56a3f9e 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -79,6 +79,16 @@ ValuePtr Eval::evalImpl() evalLet(nodes, env); continue; // TCO } + if (symbol == "quote") { + return evalQuote(nodes, env); + } + if (symbol == "quasiquote") { + evalQuasiQuote(nodes, env); + continue; // TCO + } + if (symbol == "quasiquoteexpand") { + return evalQuasiQuoteExpand(nodes, env); + } if (symbol == "do") { evalDo(nodes, env); continue; // TCO @@ -195,6 +205,136 @@ ValuePtr Eval::evalDef(const std::list& nodes, EnvironmentPtr env) return env->set(symbol, value); } +ValuePtr Eval::evalQuote(const std::list& nodes, EnvironmentPtr env) +{ + if (nodes.size() != 1) { + Error::the().add(format("wrong number of arguments: quote, {}", nodes.size())); + return nullptr; + } + + 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; + } + + if (nodes.size() != 2) { + Error::the().add(format("wrong number of arguments: {}, {}", symbol, nodes.size() - 1)); + return nullptr; + } + + return *std::next(nodes.begin()); +} + +static ValuePtr evalQuasiQuoteImpl(ValuePtr ast) +{ + if (is(ast.get()) || is(ast.get())) { + auto quoted_list = makePtr(); + quoted_list->add(makePtr("quote")); + quoted_list->add(ast); + return quoted_list; + } + + 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; + + auto list = makePtr(); + + const auto splice_unquote = startsWith(elt, "splice-unquote"); // (list 2 2 2) + if (splice_unquote) { + list->add(makePtr("concat")); + list->add(splice_unquote); + list->add(result); + result = list; // (cons 1 (concat (list 2 2 2) (cons 3 ()))) + continue; + } + + list->add(makePtr("cons")); + list->add(evalQuasiQuoteImpl(elt)); + list->add(result); + result = list; // (cons 1 (cons 2 (cons 3 ()))) + } + + if (is(ast.get())) { + return result; + } + + // Wrap Vector in (vec) + auto vector = makePtr(); + vector->add(makePtr("vec")); + vector->add(result); + return vector; +} + +void Eval::evalQuasiQuote(const std::list& nodes, EnvironmentPtr env) +{ + if (nodes.size() != 1) { + Error::the().add(format("wrong number of arguments: quasiquote, {}", nodes.size())); + return; + } + + 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) +{ + if (nodes.size() != 1) { + Error::the().add(format("wrong number of arguments: quasiquoteexpand, {}", nodes.size())); + return nullptr; + } + + return evalQuasiQuoteImpl(nodes.front()); +} + void Eval::evalLet(const std::list& nodes, EnvironmentPtr env) { if (nodes.size() != 2) { @@ -207,7 +347,7 @@ void Eval::evalLet(const std::list& nodes, EnvironmentPtr env) // First argument needs to be a List or Vector if (!is(first_argument.get())) { - Error::the().add(format("wrong argument type: collection, '{}'", first_argument)); + Error::the().add(format("wrong argument type: list, '{}'", first_argument)); return; } diff --git a/src/eval.h b/src/eval.h index 0ba2850..d5d5af8 100644 --- a/src/eval.h +++ b/src/eval.h @@ -30,6 +30,9 @@ private: ValuePtr evalAst(ValuePtr ast, EnvironmentPtr env); ValuePtr evalDef(const std::list& nodes, EnvironmentPtr env); void evalLet(const std::list& nodes, EnvironmentPtr env); + ValuePtr evalQuote(const std::list& nodes, EnvironmentPtr env); + void evalQuasiQuote(const std::list& nodes, EnvironmentPtr env); + ValuePtr evalQuasiQuoteExpand(const std::list& nodes, EnvironmentPtr env); void evalDo(const std::list& nodes, EnvironmentPtr env); void evalIf(const std::list& nodes, EnvironmentPtr env); ValuePtr evalFn(const std::list& nodes, EnvironmentPtr env); diff --git a/src/functions.cpp b/src/functions.cpp index 37c514f..5818910 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -497,7 +497,7 @@ ADD_FUNCTION( "swap!", { if (nodes.size() < 2) { - Error::the().add(format("wrong number of arguments: reset!, {}", nodes.size())); + Error::the().add(format("wrong number of arguments: swap!, {}", nodes.size())); return nullptr; } @@ -534,6 +534,67 @@ ADD_FUNCTION( return atom->reset(value); }); +// (cons 1 (list 2 3)) +ADD_FUNCTION( + "cons", + { + if (nodes.size() != 2) { + Error::the().add(format("wrong number of arguments: cons, {}", nodes.size())); + return nullptr; + } + + auto first_argument = *nodes.begin(); + auto second_argument = *std::next(nodes.begin()); + + if (!is(second_argument.get())) { + Error::the().add(format("wrong argument type: list, '{}'", second_argument)); + return nullptr; + } + + auto result_nodes = std::static_pointer_cast(second_argument)->nodes(); + result_nodes.push_front(first_argument); + + return makePtr(result_nodes); + }); + +// (concat (list 1) (list 2 3)) +ADD_FUNCTION( + "concat", + { + std::list result_nodes; + + for (auto node : nodes) { + if (!is(node.get())) { + Error::the().add(format("wrong argument type: list, '{}'", node)); + return nullptr; + } + + auto argument_nodes = std::static_pointer_cast(node)->nodes(); + result_nodes.splice(result_nodes.end(), argument_nodes); + } + + return makePtr(result_nodes); + }); + +// (vec (list 1 2 3)) +ADD_FUNCTION( + "vec", + { + if (nodes.size() != 1) { + Error::the().add(format("wrong number of arguments: vec, {}", nodes.size())); + return nullptr; + } + + if (!is(nodes.front().get())) { + Error::the().add(format("wrong argument type: list, '{}'", nodes.front())); + return nullptr; + } + + auto result_nodes = std::static_pointer_cast(nodes.front())->nodes(); + + return makePtr(result_nodes); + }); + // ----------------------------------------- void installFunctions(EnvironmentPtr env) diff --git a/src/step6_file.cpp b/src/step6_file.cpp index 6dbe93f..0ee8d5d 100644 --- a/src/step6_file.cpp +++ b/src/step6_file.cpp @@ -24,7 +24,7 @@ #include "readline.h" #include "settings.h" -#if 1 +#if 0 namespace blaze { static EnvironmentPtr s_outer_env = Environment::create(); diff --git a/src/step7_quote.cpp b/src/step7_quote.cpp new file mode 100644 index 0000000..6dbe93f --- /dev/null +++ b/src/step7_quote.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2023 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // std::signal +#include // std::exit +#include +#include +#include + +#include "ruc/argparser.h" +#include "ruc/format/color.h" + +#include "ast.h" +#include "environment.h" +#include "error.h" +#include "eval.h" +#include "forward.h" +#include "lexer.h" +#include "printer.h" +#include "reader.h" +#include "readline.h" +#include "settings.h" + +#if 1 +namespace blaze { + +static EnvironmentPtr s_outer_env = Environment::create(); + +static auto cleanup(int signal) -> void +{ + print("\033[0m\n"); + std::exit(signal); +} + +auto read(std::string_view input) -> ValuePtr +{ + Lexer lexer(input); + lexer.tokenize(); + if (Settings::the().get("dump-lexer") == "1") { + lexer.dump(); + } + + Reader reader(std::move(lexer.tokens())); + reader.read(); + if (Settings::the().get("dump-reader") == "1") { + reader.dump(); + } + + return reader.node(); +} + +auto eval(ValuePtr ast, EnvironmentPtr env) -> ValuePtr +{ + if (env == nullptr) { + env = s_outer_env; + } + + Eval eval(ast, env); + eval.eval(); + + return eval.ast(); +} + +static auto print(ValuePtr exp) -> std::string +{ + Printer printer; + + return printer.print(exp, true); +} + +static auto rep(std::string_view input, EnvironmentPtr env) -> std::string +{ + Error::the().clearErrors(); + Error::the().setInput(input); + + return print(eval(read(input), env)); +} + +static std::string_view lambdaTable[] = { + "(def! not (fn* (cond) (if cond false true)))", + "(def! load-file (fn* (filename) \ + (eval (read-string (str \"(do \" (slurp filename) \"\nnil)\")))))", +}; + +static auto installLambdas(EnvironmentPtr env) -> void +{ + for (auto function : lambdaTable) { + rep(function, env); + } +} + +static auto makeArgv(EnvironmentPtr env, std::vector arguments) -> void +{ + auto list = makePtr(); + if (arguments.size() > 1) { + for (auto it = arguments.begin() + 1; it != arguments.end(); ++it) { + list->add(makePtr(*it)); + } + } + env->set("*ARGV*", list); +} + +} // namespace blaze + +auto main(int argc, char* argv[]) -> int +{ + bool dump_lexer = false; + bool dump_reader = false; + bool pretty_print = false; + std::string_view history_path = "~/.blaze-history"; + std::vector arguments; + + // 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-path", nullptr, nullptr, nullptr, ruc::ArgParser::Required::Yes); + arg_parser.addArgument(arguments, "arguments", nullptr, nullptr, ruc::ArgParser::Required::No); + 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, blaze::cleanup); + std::signal(SIGTERM, blaze::cleanup); + + installFunctions(blaze::s_outer_env); + installLambdas(blaze::s_outer_env); + makeArgv(blaze::s_outer_env, arguments); + + if (arguments.size() > 0) { + rep(format("(load-file \"{}\")", arguments.front()), blaze::s_outer_env); + return 0; + } + + blaze::Readline readline(pretty_print, history_path); + + std::string input; + while (readline.get(input)) { + if (pretty_print) { + print("\033[0m"); + } + print("{}\n", rep(input, blaze::s_outer_env)); + } + + if (pretty_print) { + print("\033[0m"); + } + + return 0; +} +#endif