|
|
|
@ -4,17 +4,26 @@
|
|
|
|
|
* SPDX-License-Identifier: MIT |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#include <algorithm> // std::find_if, std::transform |
|
|
|
|
#include <cctype> // std::toupper |
|
|
|
|
#include <iterator> // std::distance, std::next, std::prev |
|
|
|
|
#include <list> |
|
|
|
|
#include <memory> |
|
|
|
|
#include <span> |
|
|
|
|
#include <string> |
|
|
|
|
|
|
|
|
|
#include "ruc/format/color.h" |
|
|
|
|
#include "ruc/format/format.h" |
|
|
|
|
#include "ruc/format/print.h" |
|
|
|
|
|
|
|
|
|
#include "ast.h" |
|
|
|
|
#include "env/environment.h" |
|
|
|
|
#include "error.h" |
|
|
|
|
#include "eval.h" |
|
|
|
|
#include "forward.h" |
|
|
|
|
#include "macro.h" |
|
|
|
|
#include "printer.h" |
|
|
|
|
#include "settings.h" |
|
|
|
|
#include "types.h" |
|
|
|
|
#include "util.h" |
|
|
|
|
|
|
|
|
@ -22,7 +31,7 @@ namespace blaze {
|
|
|
|
|
|
|
|
|
|
static ValuePtr evalQuasiQuoteImpl(ValuePtr ast); |
|
|
|
|
|
|
|
|
|
// (def! x 2)
|
|
|
|
|
EVAL_FUNCTION("def!", "symbol value", "Set SYMBOL to the value VALUE."); |
|
|
|
|
ValuePtr Eval::evalDef(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("def!", nodes.size(), 2); |
|
|
|
@ -44,7 +53,13 @@ ValuePtr Eval::evalDef(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
return env->set(symbol->symbol(), value); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (defmacro! x (fn* (x) x))
|
|
|
|
|
EVAL_FUNCTION("defmacro!", "symbol function", |
|
|
|
|
R"(Define SYMBOL as a macro. |
|
|
|
|
|
|
|
|
|
When the macro is called, as in (NAME ARGS...), |
|
|
|
|
the FUNCTION (fn* ARGLIST BODY...) is applied to |
|
|
|
|
the list ARGS... as it appears in the expression, |
|
|
|
|
and the result should be a form to be evaluated instead of the original.)"); |
|
|
|
|
ValuePtr Eval::evalDefMacro(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("defmacro!", nodes.size(), 2); |
|
|
|
@ -67,7 +82,139 @@ ValuePtr Eval::evalDefMacro(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
return env->set(symbol->symbol(), makePtr<Macro>(*lambda)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (fn* (x) x)
|
|
|
|
|
EVAL_FUNCTION("describe", "symbol", "Display the full documentation of SYMBOL."); |
|
|
|
|
ValuePtr Eval::evalDescribe(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("describe", nodes.size(), 1); |
|
|
|
|
|
|
|
|
|
// First argument needs to be a Symbol
|
|
|
|
|
VALUE_CAST(symbol, Symbol, nodes.front()); |
|
|
|
|
auto symbol_string = symbol->symbol(); |
|
|
|
|
|
|
|
|
|
std::string type; |
|
|
|
|
std::string signature; |
|
|
|
|
std::string documentation; |
|
|
|
|
std::string value_string; |
|
|
|
|
|
|
|
|
|
bool pretty_print = Settings::the().get("pretty-print") == "1"; |
|
|
|
|
auto bold = fg(ruc::format::TerminalColor::None) | ruc::format::Emphasis::Bold; |
|
|
|
|
|
|
|
|
|
auto describe = [&]() { |
|
|
|
|
print("{} is a {}.\n\n", symbol_string, type); |
|
|
|
|
|
|
|
|
|
if (!signature.empty()) { |
|
|
|
|
pretty_print ? print(bold, "Signature\n") : print("Signature\n"); |
|
|
|
|
print("({})\n", signature); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!documentation.empty()) { |
|
|
|
|
pretty_print ? print(bold, "\nDocumentation\n") : print("\nDocumentation\n"); |
|
|
|
|
print("{}\n", documentation); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!value_string.empty()) { |
|
|
|
|
pretty_print ? print(bold, "Value\n") : print("Value\n"); |
|
|
|
|
print("{}\n", value_string); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Verify if symbol is a special form
|
|
|
|
|
auto special_form = std::find_if( |
|
|
|
|
s_special_form_parts.begin(), |
|
|
|
|
s_special_form_parts.end(), |
|
|
|
|
[&symbol_string](const SpecialFormParts& special_form_parts) { |
|
|
|
|
return special_form_parts.name == symbol_string; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// If symbol is not special form, lookup in the environment
|
|
|
|
|
ValuePtr value; |
|
|
|
|
if (special_form == s_special_form_parts.end()) { |
|
|
|
|
value = env->get(symbol_string); |
|
|
|
|
if (!value) { |
|
|
|
|
Error::the().add(format("'{}' not found", symbol_string)); |
|
|
|
|
return nullptr; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Variable
|
|
|
|
|
if (special_form == s_special_form_parts.end() && !is<Callable>(value.get())) { |
|
|
|
|
type = "variable"; |
|
|
|
|
|
|
|
|
|
Printer printer; |
|
|
|
|
value_string = format("{}", printer.printNoErrorCheck(value, true)); |
|
|
|
|
|
|
|
|
|
describe(); |
|
|
|
|
|
|
|
|
|
return nullptr; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
signature = pretty_print ? format(fg(ruc::format::TerminalColor::BrightBlue), "{}", symbol_string) |
|
|
|
|
: symbol_string; |
|
|
|
|
|
|
|
|
|
// Special form
|
|
|
|
|
if (special_form != s_special_form_parts.end()) { |
|
|
|
|
type = "special form"; |
|
|
|
|
|
|
|
|
|
std::string signature_lower = std::string(special_form->signature); |
|
|
|
|
std::transform(signature_lower.begin(), signature_lower.end(), signature_lower.begin(), ::toupper); |
|
|
|
|
signature += !signature_lower.empty() ? " " : ""; |
|
|
|
|
signature += signature_lower; |
|
|
|
|
|
|
|
|
|
documentation = special_form->documentation; |
|
|
|
|
|
|
|
|
|
describe(); |
|
|
|
|
|
|
|
|
|
return nullptr; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Function / lambda / macro
|
|
|
|
|
if (is<Function>(value.get())) { |
|
|
|
|
type = "function"; |
|
|
|
|
|
|
|
|
|
auto function = std::static_pointer_cast<Function>(value); |
|
|
|
|
signature += !function->signature().empty() ? " " : ""; |
|
|
|
|
signature += function->signature(); |
|
|
|
|
|
|
|
|
|
documentation = function->documentation(); |
|
|
|
|
} |
|
|
|
|
else if (is<Lambda>(value.get()) || is<Macro>(value.get())) { |
|
|
|
|
type = is<Lambda>(value.get()) ? "function" : "macro"; |
|
|
|
|
|
|
|
|
|
auto lambda = std::static_pointer_cast<Lambda>(value); |
|
|
|
|
auto bindings = lambda->bindings(); |
|
|
|
|
std::string binding; |
|
|
|
|
for (size_t i = 0; i < bindings.size(); ++i) { |
|
|
|
|
binding = bindings[i]; |
|
|
|
|
std::transform(binding.begin(), binding.end(), binding.begin(), ::toupper); |
|
|
|
|
signature += " " + binding; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto body = lambda->body(); |
|
|
|
|
if (is<String>(body.get())) { |
|
|
|
|
documentation = std::static_pointer_cast<String>(body)->data(); |
|
|
|
|
} |
|
|
|
|
else if (is<List>(body.get())) { |
|
|
|
|
VALUE_CAST(list, List, body); |
|
|
|
|
if (list->size() > 1) { |
|
|
|
|
auto second = list->nodesRead()[1]; |
|
|
|
|
if (is<String>(second.get())) { |
|
|
|
|
documentation = std::static_pointer_cast<String>(second)->data(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
describe(); |
|
|
|
|
|
|
|
|
|
return nullptr; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
EVAL_FUNCTION("fn*", "args [docstring] body...", R"(Return an anonymous function. |
|
|
|
|
|
|
|
|
|
ARGS should take the form of an argument list or vector. |
|
|
|
|
DOCSTRING is an optional documentation string. |
|
|
|
|
If present, it should describe how to call the function. |
|
|
|
|
BODY should be a list of Lisp expressions.)"); |
|
|
|
|
ValuePtr Eval::evalFn(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_AT_LEAST("fn*", nodes.size(), 2); |
|
|
|
@ -102,6 +249,7 @@ ValuePtr Eval::evalFn(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
// -----------------------------------------
|
|
|
|
|
|
|
|
|
|
// (quasiquoteexpand x)
|
|
|
|
|
EVAL_FUNCTION("quasiquoteexpand", "arg", ""); // TODO
|
|
|
|
|
ValuePtr Eval::evalQuasiQuoteExpand(const ValueVector& nodes) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("quasiquoteexpand", nodes.size(), 1); |
|
|
|
@ -109,7 +257,7 @@ ValuePtr Eval::evalQuasiQuoteExpand(const ValueVector& nodes)
|
|
|
|
|
return evalQuasiQuoteImpl(nodes.front()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (quote x)
|
|
|
|
|
EVAL_FUNCTION("quote", "arg", "Return the ARG, without evaluating it. (quote x) yields x."); |
|
|
|
|
ValuePtr Eval::evalQuote(const ValueVector& nodes) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("quote", nodes.size(), 1); |
|
|
|
@ -117,7 +265,13 @@ ValuePtr Eval::evalQuote(const ValueVector& nodes)
|
|
|
|
|
return nodes.front(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (try* x ... (catch* y z))
|
|
|
|
|
EVAL_FUNCTION("try*", "body... [catch]", R"(Eval BODY allowing exceptions to get caught. |
|
|
|
|
|
|
|
|
|
CATCH should take the form of (catch* binding handler). |
|
|
|
|
|
|
|
|
|
The BODY is evaluated, if it throws an exception, then form CATCH is |
|
|
|
|
handled by creating a new environment that binds the symbol BINDING |
|
|
|
|
to the value of the exception that was thrown. Finally, HANDLER is evaluated.)"); |
|
|
|
|
ValuePtr Eval::evalTry(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_AT_LEAST("try*", nodes.size(), 1); |
|
|
|
@ -169,11 +323,11 @@ ValuePtr Eval::evalTry(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
|
|
|
|
|
VALUE_CAST(catch_binding, Symbol, (*std::next(catch_nodes.begin()))); |
|
|
|
|
|
|
|
|
|
// Create new Environment that binds 'y' to the value of the exception
|
|
|
|
|
// Create new Environment that binds 'binding' to the value of the exception
|
|
|
|
|
auto catch_env = Environment::create(env); |
|
|
|
|
catch_env->set(catch_binding->symbol(), error); |
|
|
|
|
|
|
|
|
|
// Evaluate 'z' using the new Environment
|
|
|
|
|
// Evaluate 'handler' using the new Environment
|
|
|
|
|
m_ast = catch_nodes.back(); |
|
|
|
|
m_env = catch_env; |
|
|
|
|
return evalImpl(); |
|
|
|
@ -181,7 +335,10 @@ ValuePtr Eval::evalTry(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------
|
|
|
|
|
|
|
|
|
|
// (and 1 2 3)
|
|
|
|
|
EVAL_FUNCTION("and", "args...", R"(Eval ARGS until one of them yields nil, then return nil. |
|
|
|
|
|
|
|
|
|
The remaining args are not evalled at all. |
|
|
|
|
If no arg yields nil, return the last arg's value.)"); |
|
|
|
|
void Eval::evalAnd(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
ValuePtr result = makePtr<Constant>(Constant::True); |
|
|
|
@ -205,7 +362,7 @@ void Eval::evalAnd(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
return; // TCO
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (do 1 2 3)
|
|
|
|
|
EVAL_FUNCTION("do", "body...", "Eval BODY forms sequentially and return value of the last one."); |
|
|
|
|
void Eval::evalDo(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_AT_LEAST("do", nodes.size(), 1, void()); |
|
|
|
@ -223,7 +380,11 @@ void Eval::evalDo(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
return; // TCO
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (if x true false)
|
|
|
|
|
EVAL_FUNCTION("if", "COND THEN [ELSE]", R"(If COND yields non-nil, do THEN, else do ELSE. |
|
|
|
|
|
|
|
|
|
Returns the value of THEN or the value of ELSE. |
|
|
|
|
Both THEN and ELSE must be one expression. |
|
|
|
|
If COND yields nil, and there is no ELSE, the value is nil.)"); |
|
|
|
|
void Eval::evalIf(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_BETWEEN("if", nodes.size(), 2, 3, void()); |
|
|
|
@ -247,7 +408,12 @@ void Eval::evalIf(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
return; // TCO
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (let* (x 1) x)
|
|
|
|
|
EVAL_FUNCTION("let*", "varlist body", R"(Bind variables accoring to VARLIST then eval BODY. |
|
|
|
|
|
|
|
|
|
The value of the BODY form is returned. |
|
|
|
|
VARLIST is a list or vector with an even amount of elements, |
|
|
|
|
where each odd number is a symbol gets bind the even element. |
|
|
|
|
All even elements are evalled before any symbols are bound.)"); |
|
|
|
|
void Eval::evalLet(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("let*", nodes.size(), 2, void()); |
|
|
|
@ -307,6 +473,7 @@ static bool isMacroCall(ValuePtr ast, EnvironmentPtr env)
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
EVAL_FUNCTION("macroexpand-1", "expression", "Macroexpand EXPRESSION and pretty-print its value."); |
|
|
|
|
void Eval::evalMacroExpand1(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("macroexpand-1", nodes.size(), 1, void()); |
|
|
|
@ -329,7 +496,10 @@ void Eval::evalMacroExpand1(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------
|
|
|
|
|
|
|
|
|
|
// (or 1 2 3)
|
|
|
|
|
EVAL_FUNCTION("or", "args...", R"(Eval ARGS until one of them yields non-nil, then return that value. |
|
|
|
|
|
|
|
|
|
The remaining args are not evalled at all. |
|
|
|
|
If all args return nil, return nil.)"); |
|
|
|
|
void Eval::evalOr(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
ValuePtr result; |
|
|
|
@ -442,6 +612,7 @@ static ValuePtr evalQuasiQuoteImpl(ValuePtr ast)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// (quasiquote x)
|
|
|
|
|
EVAL_FUNCTION("quasiquote", "arg", R"()"); // TODO
|
|
|
|
|
void Eval::evalQuasiQuote(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_IS("quasiquote", nodes.size(), 1, void()); |
|
|
|
@ -456,6 +627,12 @@ void Eval::evalQuasiQuote(const ValueVector& nodes, EnvironmentPtr env)
|
|
|
|
|
// -----------------------------------------
|
|
|
|
|
|
|
|
|
|
// (while true body...)
|
|
|
|
|
EVAL_FUNCTION("while", "test body...", R"(If TEST yields non-nil, eval BODY... and repeat |
|
|
|
|
|
|
|
|
|
The order of execution is thus TEST, BODY, TEST, BODY and so on |
|
|
|
|
until TEST returns nil. |
|
|
|
|
|
|
|
|
|
The value of a while form is always nil.)"); |
|
|
|
|
void Eval::evalWhile(const ValueVector& nodes, EnvironmentPtr env) |
|
|
|
|
{ |
|
|
|
|
CHECK_ARG_COUNT_AT_LEAST("while", nodes.size(), 2, void()); |
|
|
|
|