diff --git a/CMakeLists.txt b/CMakeLists.txt index 88e2f3a..9d20224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,3 +111,7 @@ 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}) + +add_custom_target(test8 + COMMAND env STEP=step_env MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step8_macros.mal -- ./${PROJECT}) +add_dependencies(test8 ${PROJECT}) diff --git a/src/ast.cpp b/src/ast.cpp index 7feb3ad..1a8c59b 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -107,6 +107,14 @@ Lambda::Lambda(const std::vector& bindings, ValuePtr body, Environm { } +Lambda::Lambda(std::shared_ptr that, bool is_macro) + : m_bindings(that->m_bindings) + , m_body(that->m_body) + , m_env(that->m_env) + , m_is_macro(is_macro) +{ +} + // ----------------------------------------- Atom::Atom(ValuePtr pointer) diff --git a/src/ast.h b/src/ast.h index 623ebcd..243f4d9 100644 --- a/src/ast.h +++ b/src/ast.h @@ -265,11 +265,13 @@ private: class Lambda final : public Callable { public: Lambda(const std::vector& bindings, ValuePtr body, EnvironmentPtr env); + Lambda(std::shared_ptr that, bool is_macro); virtual ~Lambda() = default; const std::vector& bindings() const { return m_bindings; } ValuePtr body() const { return m_body; } EnvironmentPtr env() const { return m_env; } + bool isMacro() const { return m_is_macro; } private: virtual bool isLambda() const override { return true; } @@ -277,6 +279,7 @@ private: const std::vector m_bindings; const ValuePtr m_body; const EnvironmentPtr m_env; + const bool m_is_macro { false }; }; // ----------------------------------------- diff --git a/src/eval-special-form.cpp b/src/eval-special-form.cpp new file mode 100644 index 0000000..56758dd --- /dev/null +++ b/src/eval-special-form.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2023 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "ast.h" +#include "error.h" +#include "eval.h" +#include "forward.h" +#include "types.h" +#include "util.h" + +namespace blaze { + +static ValuePtr evalQuasiQuoteImpl(ValuePtr ast); + +// (def! x 2) +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); +} + +// (defmacro! x (fn* (x) x)) +ValuePtr Eval::evalDefMacro(const std::list& nodes, EnvironmentPtr env) +{ + CHECK_ARG_COUNT_IS("defmacro!", 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(); + VALUE_CAST(lambda, Lambda, value); + + // Dont overwrite symbols after an error + if (Error::the().hasAnyError()) { + return nullptr; + } + + // Modify existing environment + return env->set(symbol->symbol(), makePtr(lambda, true)); +} + +// (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::evalMacroExpand(const std::list& nodes, EnvironmentPtr env) +{ + CHECK_ARG_COUNT_IS("macroexpand", nodes.size(), 1); + + return macroExpand(nodes.front(), env); +} + +// (quasiquoteexpand x) +ValuePtr Eval::evalQuasiQuoteExpand(const std::list& nodes) +{ + CHECK_ARG_COUNT_IS("quasiquoteexpand", nodes.size(), 1); + + return evalQuasiQuoteImpl(nodes.front()); +} + +// (quote x) +ValuePtr Eval::evalQuote(const std::list& nodes) +{ + CHECK_ARG_COUNT_IS("quote", nodes.size(), 1); + + return nodes.front(); +} + +// (do 1 2 3) +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 +} + +// (if x true false) +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 +} + +// (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 +} + +// ----------------------------------------- + +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); +} + +// (quasiquote x) +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 +} + +// ----------------------------------------- + +} // namespace blaze diff --git a/src/eval.cpp b/src/eval.cpp index 22490fe..0131f89 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -68,6 +68,15 @@ ValuePtr Eval::evalImpl() return ast; } + ast = macroExpand(ast, env); + + if (!is(ast.get())) { + return evalAst(ast, env); + } + + // Macro-expand modifies `ast', so get the new list + list = std::static_pointer_cast(ast); + // Special forms auto nodes = list->nodes(); if (is(nodes.front().get())) { @@ -76,20 +85,22 @@ ValuePtr Eval::evalImpl() if (symbol == "def!") { return evalDef(nodes, env); } - if (symbol == "let*") { - evalLet(nodes, env); - continue; // TCO + if (symbol == "defmacro!") { + return evalDefMacro(nodes, env); } - if (symbol == "quote") { - return evalQuote(nodes); + if (symbol == "fn*") { + return evalFn(nodes, env); } - if (symbol == "quasiquote") { - evalQuasiQuote(nodes, env); - continue; // TCO + if (symbol == "macroexpand") { + return evalMacroExpand(nodes, env); } if (symbol == "quasiquoteexpand") { - return evalQuasiQuoteExpand(nodes, env); + return evalQuasiQuoteExpand(nodes); } + if (symbol == "quote") { + return evalQuote(nodes); + } + // Tail call optimized functions if (symbol == "do") { evalDo(nodes, env); continue; // TCO @@ -98,8 +109,13 @@ ValuePtr Eval::evalImpl() evalIf(nodes, env); continue; // TCO } - if (symbol == "fn*") { - return evalFn(nodes, env); + if (symbol == "let*") { + evalLet(nodes, env); + continue; // TCO + } + if (symbol == "quasiquote") { + evalQuasiQuote(nodes, env); + continue; // TCO } } @@ -178,225 +194,43 @@ ValuePtr Eval::evalAst(ValuePtr ast, EnvironmentPtr env) // ----------------------------------------- -ValuePtr Eval::evalDef(const std::list& nodes, EnvironmentPtr env) +// (x y z) +bool Eval::isMacroCall(ValuePtr ast, 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; + if (!is(ast.get())) { + return false; } - // 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(); -} + auto nodes = std::static_pointer_cast(ast)->nodes(); -static bool isSymbol(ValuePtr value, const std::string& symbol) -{ - if (!is(value.get())) { + if (nodes.size() == 0 || !is(nodes.front().get())) { return false; } - auto valueSymbol = std::static_pointer_cast(value)->symbol(); + auto value = env->get(std::static_pointer_cast(nodes.front())->symbol()); - if (valueSymbol != symbol) { + if (!is(value.get()) || !std::static_pointer_cast(value)->isMacro()) { 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) +// (x y z) +ValuePtr Eval::macroExpand(ValuePtr ast, EnvironmentPtr env) { - if (is(ast.get()) || is(ast.get())) { - return makePtr(makePtr("quote"), ast); + while (isMacroCall(ast, env)) { + auto nodes = std::static_pointer_cast(ast)->nodes(); + auto value = env->get(std::static_pointer_cast(nodes.front())->symbol()); + auto lambda = std::static_pointer_cast(value); + nodes.pop_front(); + + m_ast_stack.push(lambda->body()); + m_env_stack.push(Environment::create(lambda, nodes)); + ast = evalImpl(); } - 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); + return ast; } //----------------------------------------- diff --git a/src/eval.h b/src/eval.h index 6091be1..7e13561 100644 --- a/src/eval.h +++ b/src/eval.h @@ -29,14 +29,19 @@ private: ValuePtr evalImpl(); ValuePtr evalAst(ValuePtr ast, EnvironmentPtr env); + bool isMacroCall(ValuePtr ast, EnvironmentPtr env); + ValuePtr macroExpand(ValuePtr ast, EnvironmentPtr env); + ValuePtr evalDef(const std::list& nodes, EnvironmentPtr env); - void evalLet(const std::list& nodes, EnvironmentPtr env); + ValuePtr evalDefMacro(const std::list& nodes, EnvironmentPtr env); + ValuePtr evalFn(const std::list& nodes, EnvironmentPtr env); + ValuePtr evalMacroExpand(const std::list& nodes, EnvironmentPtr env); + ValuePtr evalQuasiQuoteExpand(const std::list& nodes); ValuePtr evalQuote(const std::list& nodes); - 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); + void evalLet(const std::list& nodes, EnvironmentPtr env); + void evalQuasiQuote(const std::list& nodes, EnvironmentPtr env); ValuePtr apply(std::shared_ptr evaluated_list); diff --git a/src/functions.cpp b/src/functions.cpp index 727f5eb..8438c1b 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -197,7 +197,7 @@ ADD_FUNCTION( result = std::static_pointer_cast(first_argument)->size(); } else { - Error::the().add(format("wrong argument type: collection, '{}'", first_argument)); + Error::the().add(format("wrong argument type: Collection, '{}'", first_argument)); return nullptr; } @@ -483,6 +483,67 @@ ADD_FUNCTION( return makePtr(collection->nodes()); }); +// (nth (list 1 2 3) 0) +ADD_FUNCTION( + "nth", + { + CHECK_ARG_COUNT_IS("nth", nodes.size(), 2); + + VALUE_CAST(collection, Collection, nodes.front()); + VALUE_CAST(number_node, Number, (*std::next(nodes.begin()))); + auto collection_nodes = collection->nodes(); + auto index = (size_t)number_node->number(); + + if (number_node->number() < 0 || index >= collection_nodes.size()) { + Error::the().add("index is out of range"); + return nullptr; + } + + auto result = collection_nodes.begin(); + for (size_t i = 0; i < index; ++i) { + result++; + } + + return *result; + }); + +// (first (list 1 2 3)) +ADD_FUNCTION( + "first", + { + CHECK_ARG_COUNT_IS("first", nodes.size(), 1); + + if (is(nodes.front().get()) + && std::static_pointer_cast(nodes.front())->state() == Constant::Nil) { + return makePtr(Constant::Nil); + } + + VALUE_CAST(collection, Collection, nodes.front()); + auto collection_nodes = collection->nodes(); + + return (collection_nodes.empty()) ? makePtr(Constant::Nil) : collection_nodes.front(); + }); + +// (rest (list 1 2 3)) +ADD_FUNCTION( + "rest", + { + CHECK_ARG_COUNT_IS("rest", nodes.size(), 1); + + if (is(nodes.front().get()) + && std::static_pointer_cast(nodes.front())->state() == Constant::Nil) { + return makePtr(); + } + + VALUE_CAST(collection, Collection, nodes.front()); + auto collection_nodes = collection->nodes(); + if (collection_nodes.size() > 0) { + collection_nodes.pop_front(); + } + + return makePtr(collection_nodes); + }); + // ----------------------------------------- void installFunctions(EnvironmentPtr env) diff --git a/src/printer.cpp b/src/printer.cpp index d13721b..0b0ca54 100644 --- a/src/printer.cpp +++ b/src/printer.cpp @@ -139,7 +139,8 @@ void Printer::printImpl(ValuePtr node, bool print_readably) } else if (is(node_raw_ptr)) { printSpacing(); - m_print += format("#({:p})", node_raw_ptr); + auto lambda = std::static_pointer_cast(node); + m_print += format("#({:p})", (lambda->isMacro()) ? "macro" : "function", node_raw_ptr); } else if (is(node_raw_ptr)) { printSpacing(); diff --git a/src/step7_quote.cpp b/src/step7_quote.cpp index cd113e3..618ef7b 100644 --- a/src/step7_quote.cpp +++ b/src/step7_quote.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/step8_macros.cpp b/src/step8_macros.cpp new file mode 100644 index 0000000..20fbb4f --- /dev/null +++ b/src/step8_macros.cpp @@ -0,0 +1,167 @@ +/* + * 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)\")))))", + "(defmacro! cond (fn* (& xs) \ + (if (> (count xs) 0) \ + (list 'if (first xs) \ + (if (> (count xs) 1) \ + (nth xs 1) \ + (throw \"odd number of forms to cond\")) \ + (cons 'cond (rest (rest xs)))))))", +}; + +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); + // TODO: Add overload for addArgument(std::vector) + 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 diff --git a/src/util.h b/src/util.h index 87d135d..aa38500 100644 --- a/src/util.h +++ b/src/util.h @@ -10,6 +10,9 @@ #include #include +#include "error.h" +#include "types.h" + // ----------------------------------------- // TODO: Move these ruc/test/macro.h -> ruc/src/meta/macro.h