Riyyi
2 years ago
11 changed files with 588 additions and 220 deletions
@ -0,0 +1,282 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <list> |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
#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<ValuePtr>& 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<ValuePtr>& 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>(lambda, true)); |
||||||
|
} |
||||||
|
|
||||||
|
// (fn* (x) x)
|
||||||
|
ValuePtr Eval::evalFn(const std::list<ValuePtr>& 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<std::string> 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<Lambda>(bindings, *std::next(nodes.begin()), env); |
||||||
|
} |
||||||
|
|
||||||
|
ValuePtr Eval::evalMacroExpand(const std::list<ValuePtr>& nodes, EnvironmentPtr env) |
||||||
|
{ |
||||||
|
CHECK_ARG_COUNT_IS("macroexpand", nodes.size(), 1); |
||||||
|
|
||||||
|
return macroExpand(nodes.front(), env); |
||||||
|
} |
||||||
|
|
||||||
|
// (quasiquoteexpand x)
|
||||||
|
ValuePtr Eval::evalQuasiQuoteExpand(const std::list<ValuePtr>& nodes) |
||||||
|
{ |
||||||
|
CHECK_ARG_COUNT_IS("quasiquoteexpand", nodes.size(), 1); |
||||||
|
|
||||||
|
return evalQuasiQuoteImpl(nodes.front()); |
||||||
|
} |
||||||
|
|
||||||
|
// (quote x)
|
||||||
|
ValuePtr Eval::evalQuote(const std::list<ValuePtr>& nodes) |
||||||
|
{ |
||||||
|
CHECK_ARG_COUNT_IS("quote", nodes.size(), 1); |
||||||
|
|
||||||
|
return nodes.front(); |
||||||
|
} |
||||||
|
|
||||||
|
// (do 1 2 3)
|
||||||
|
void Eval::evalDo(const std::list<ValuePtr>& 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<ValuePtr>& 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>(Constant::Nil); |
||||||
|
|
||||||
|
m_ast_stack.push(first_argument); |
||||||
|
m_env_stack.push(env); |
||||||
|
auto first_evaluated = evalImpl(); |
||||||
|
if (!is<Constant>(first_evaluated.get()) |
||||||
|
|| std::static_pointer_cast<Constant>(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<ValuePtr>& 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<Symbol>(value.get())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
auto valueSymbol = std::static_pointer_cast<Symbol>(value)->symbol(); |
||||||
|
|
||||||
|
if (valueSymbol != symbol) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static ValuePtr startsWith(ValuePtr ast, const std::string& symbol) |
||||||
|
{ |
||||||
|
if (!is<List>(ast.get())) { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
auto nodes = std::static_pointer_cast<List>(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<HashMap>(ast.get()) || is<Symbol>(ast.get())) { |
||||||
|
return makePtr<List>(makePtr<Symbol>("quote"), ast); |
||||||
|
} |
||||||
|
|
||||||
|
if (!is<Collection>(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<List>(); |
||||||
|
|
||||||
|
auto nodes = std::static_pointer_cast<Collection>(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<List>(makePtr<Symbol>("concat"), splice_unquote, result); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// (cons 1 (cons 2 (cons 3 ())))
|
||||||
|
result = makePtr<List>(makePtr<Symbol>("cons"), evalQuasiQuoteImpl(elt), result); |
||||||
|
} |
||||||
|
|
||||||
|
if (is<List>(ast.get())) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Wrap result in (vec) for Vector types
|
||||||
|
return makePtr<List>(makePtr<Symbol>("vec"), result); |
||||||
|
} |
||||||
|
|
||||||
|
// (quasiquote x)
|
||||||
|
void Eval::evalQuasiQuote(const std::list<ValuePtr>& 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
|
@ -0,0 +1,167 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <csignal> // std::signal |
||||||
|
#include <cstdlib> // std::exit |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#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<std::string> arguments) -> void |
||||||
|
{ |
||||||
|
auto list = makePtr<List>(); |
||||||
|
if (arguments.size() > 1) { |
||||||
|
for (auto it = arguments.begin() + 1; it != arguments.end(); ++it) { |
||||||
|
list->add(makePtr<String>(*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<std::string> 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<std::string_view>)
|
||||||
|
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 |
Loading…
Reference in new issue