Everywhere: Implement step8
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -107,6 +107,14 @@ Lambda::Lambda(const std::vector<std::string>& bindings, ValuePtr body, Environm
|
||||
{
|
||||
}
|
||||
|
||||
Lambda::Lambda(std::shared_ptr<Lambda> 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)
|
||||
|
||||
@@ -265,11 +265,13 @@ private:
|
||||
class Lambda final : public Callable {
|
||||
public:
|
||||
Lambda(const std::vector<std::string>& bindings, ValuePtr body, EnvironmentPtr env);
|
||||
Lambda(std::shared_ptr<Lambda> that, bool is_macro);
|
||||
virtual ~Lambda() = default;
|
||||
|
||||
const std::vector<std::string>& 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<std::string> m_bindings;
|
||||
const ValuePtr m_body;
|
||||
const EnvironmentPtr m_env;
|
||||
const bool m_is_macro { false };
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
@@ -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
|
||||
+48
-214
@@ -68,6 +68,15 @@ ValuePtr Eval::evalImpl()
|
||||
return ast;
|
||||
}
|
||||
|
||||
ast = macroExpand(ast, env);
|
||||
|
||||
if (!is<List>(ast.get())) {
|
||||
return evalAst(ast, env);
|
||||
}
|
||||
|
||||
// Macro-expand modifies `ast', so get the new list
|
||||
list = std::static_pointer_cast<List>(ast);
|
||||
|
||||
// Special forms
|
||||
auto nodes = list->nodes();
|
||||
if (is<Symbol>(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 == "fn*") {
|
||||
return evalFn(nodes, env);
|
||||
}
|
||||
if (symbol == "macroexpand") {
|
||||
return evalMacroExpand(nodes, env);
|
||||
}
|
||||
if (symbol == "quasiquoteexpand") {
|
||||
return evalQuasiQuoteExpand(nodes);
|
||||
}
|
||||
if (symbol == "quote") {
|
||||
return evalQuote(nodes);
|
||||
}
|
||||
if (symbol == "quasiquote") {
|
||||
evalQuasiQuote(nodes, env);
|
||||
continue; // TCO
|
||||
}
|
||||
if (symbol == "quasiquoteexpand") {
|
||||
return evalQuasiQuoteExpand(nodes, env);
|
||||
}
|
||||
// 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,227 +194,45 @@ ValuePtr Eval::evalAst(ValuePtr ast, EnvironmentPtr env)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
ValuePtr Eval::evalDef(const std::list<ValuePtr>& 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;
|
||||
}
|
||||
|
||||
// Modify existing environment
|
||||
return env->set(symbol->symbol(), value);
|
||||
}
|
||||
|
||||
ValuePtr Eval::evalQuote(const std::list<ValuePtr>& nodes)
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("quote", nodes.size(), 1);
|
||||
|
||||
return nodes.front();
|
||||
}
|
||||
|
||||
static bool isSymbol(ValuePtr value, const std::string& symbol)
|
||||
{
|
||||
if (!is<Symbol>(value.get())) {
|
||||
if (!is<List>(ast.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto valueSymbol = std::static_pointer_cast<Symbol>(value)->symbol();
|
||||
auto nodes = std::static_pointer_cast<List>(ast)->nodes();
|
||||
|
||||
if (valueSymbol != symbol) {
|
||||
if (nodes.size() == 0 || !is<Symbol>(nodes.front().get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto value = env->get(std::static_pointer_cast<Symbol>(nodes.front())->symbol());
|
||||
|
||||
if (!is<Lambda>(value.get()) || !std::static_pointer_cast<Lambda>(value)->isMacro()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static ValuePtr startsWith(ValuePtr ast, const std::string& symbol)
|
||||
// (x y z)
|
||||
ValuePtr Eval::macroExpand(ValuePtr ast, EnvironmentPtr env)
|
||||
{
|
||||
if (!is<List>(ast.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (isMacroCall(ast, env)) {
|
||||
auto nodes = std::static_pointer_cast<List>(ast)->nodes();
|
||||
auto value = env->get(std::static_pointer_cast<Symbol>(nodes.front())->symbol());
|
||||
auto lambda = std::static_pointer_cast<Lambda>(value);
|
||||
nodes.pop_front();
|
||||
|
||||
if (nodes.empty() || !isSymbol(nodes.front(), symbol)) {
|
||||
return nullptr;
|
||||
m_ast_stack.push(lambda->body());
|
||||
m_env_stack.push(Environment::create(lambda, nodes));
|
||||
ast = evalImpl();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ValuePtr Eval::evalQuasiQuoteExpand(const std::list<ValuePtr>& 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<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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// (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::apply(std::shared_ptr<List> evaluated_list)
|
||||
|
||||
+9
-4
@@ -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<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
void evalLet(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
ValuePtr evalDefMacro(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
ValuePtr evalFn(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
ValuePtr evalMacroExpand(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
ValuePtr evalQuasiQuoteExpand(const std::list<ValuePtr>& nodes);
|
||||
ValuePtr evalQuote(const std::list<ValuePtr>& nodes);
|
||||
void evalQuasiQuote(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
ValuePtr evalQuasiQuoteExpand(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
void evalDo(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
void evalIf(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
ValuePtr evalFn(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
void evalLet(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
void evalQuasiQuote(const std::list<ValuePtr>& nodes, EnvironmentPtr env);
|
||||
|
||||
ValuePtr apply(std::shared_ptr<List> evaluated_list);
|
||||
|
||||
|
||||
+62
-1
@@ -197,7 +197,7 @@ ADD_FUNCTION(
|
||||
result = std::static_pointer_cast<Collection>(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<Vector>(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<Constant>(nodes.front().get())
|
||||
&& std::static_pointer_cast<Constant>(nodes.front())->state() == Constant::Nil) {
|
||||
return makePtr<Constant>(Constant::Nil);
|
||||
}
|
||||
|
||||
VALUE_CAST(collection, Collection, nodes.front());
|
||||
auto collection_nodes = collection->nodes();
|
||||
|
||||
return (collection_nodes.empty()) ? makePtr<Constant>(Constant::Nil) : collection_nodes.front();
|
||||
});
|
||||
|
||||
// (rest (list 1 2 3))
|
||||
ADD_FUNCTION(
|
||||
"rest",
|
||||
{
|
||||
CHECK_ARG_COUNT_IS("rest", nodes.size(), 1);
|
||||
|
||||
if (is<Constant>(nodes.front().get())
|
||||
&& std::static_pointer_cast<Constant>(nodes.front())->state() == Constant::Nil) {
|
||||
return makePtr<List>();
|
||||
}
|
||||
|
||||
VALUE_CAST(collection, Collection, nodes.front());
|
||||
auto collection_nodes = collection->nodes();
|
||||
if (collection_nodes.size() > 0) {
|
||||
collection_nodes.pop_front();
|
||||
}
|
||||
|
||||
return makePtr<List>(collection_nodes);
|
||||
});
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void installFunctions(EnvironmentPtr env)
|
||||
|
||||
+2
-1
@@ -139,7 +139,8 @@ void Printer::printImpl(ValuePtr node, bool print_readably)
|
||||
}
|
||||
else if (is<Lambda>(node_raw_ptr)) {
|
||||
printSpacing();
|
||||
m_print += format("#<user-function>({:p})", node_raw_ptr);
|
||||
auto lambda = std::static_pointer_cast<Lambda>(node);
|
||||
m_print += format("#<user-{}>({:p})", (lambda->isMacro()) ? "macro" : "function", node_raw_ptr);
|
||||
}
|
||||
else if (is<Atom>(node_raw_ptr)) {
|
||||
printSpacing();
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
#include "readline.h"
|
||||
#include "settings.h"
|
||||
|
||||
#if 1
|
||||
#if 0
|
||||
namespace blaze {
|
||||
|
||||
static EnvironmentPtr s_outer_env = Environment::create();
|
||||
|
||||
@@ -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
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "error.h"
|
||||
#include "types.h"
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// TODO: Move these ruc/test/macro.h -> ruc/src/meta/macro.h
|
||||
|
||||
Reference in New Issue
Block a user