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