2015-03-27 09:24:20 +03:00
|
|
|
#include "MAL.h"
|
|
|
|
|
|
|
|
#include "Environment.h"
|
|
|
|
#include "ReadLine.h"
|
|
|
|
#include "Types.h"
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
malValuePtr READ(const String& input);
|
|
|
|
String PRINT(malValuePtr ast);
|
|
|
|
static void installFunctions(malEnvPtr env);
|
2019-05-08 04:06:45 +03:00
|
|
|
// Installs functions, macros and constants implemented in MAL.
|
2015-03-27 09:24:20 +03:00
|
|
|
|
|
|
|
static void makeArgv(malEnvPtr env, int argc, char* argv[]);
|
2016-05-11 22:50:19 +03:00
|
|
|
static String safeRep(const String& input, malEnvPtr env);
|
2015-03-27 09:24:20 +03:00
|
|
|
static malValuePtr quasiquote(malValuePtr obj);
|
|
|
|
|
|
|
|
static ReadLine s_readLine("~/.mal-history");
|
|
|
|
|
2015-12-14 10:06:50 +03:00
|
|
|
static malEnvPtr replEnv(new malEnv);
|
|
|
|
|
2015-03-27 09:24:20 +03:00
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
String prompt = "user> ";
|
|
|
|
String input;
|
|
|
|
installCore(replEnv);
|
|
|
|
installFunctions(replEnv);
|
|
|
|
makeArgv(replEnv, argc - 2, argv + 2);
|
|
|
|
if (argc > 1) {
|
|
|
|
String filename = escape(argv[1]);
|
|
|
|
safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
|
|
|
|
return 0;
|
|
|
|
}
|
2016-02-15 05:57:31 +03:00
|
|
|
rep("(println (str \"Mal [\" *host-language* \"]\"))", replEnv);
|
2015-03-27 09:24:20 +03:00
|
|
|
while (s_readLine.get(prompt, input)) {
|
2016-05-11 22:50:19 +03:00
|
|
|
String out = safeRep(input, replEnv);
|
|
|
|
if (out.length() > 0)
|
|
|
|
std::cout << out << "\n";
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-05-11 22:50:19 +03:00
|
|
|
static String safeRep(const String& input, malEnvPtr env)
|
2015-03-27 09:24:20 +03:00
|
|
|
{
|
|
|
|
try {
|
2016-05-11 22:50:19 +03:00
|
|
|
return rep(input, env);
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
catch (malEmptyInputException&) {
|
2016-05-11 22:50:19 +03:00
|
|
|
return String();
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
Test uncaught throw, catchless try* . Fix 46 impls.
Fixes made to: ada, c, chuck, clojure, coffee, common-lisp, cpp,
crystal, d, dart, elm, erlang, es6, factor, fsharp, gnu-smalltalk,
groovy, guile, haxe, hy, js, livescript, matlab, miniMAL, nasm, nim,
objc, objpascal, ocaml, perl, perl6, php, plsql, ps, python, r,
rpython, ruby, scheme, swift3, tcl, ts, vb, vimscript, wasm, yorick.
Catchless try* test is an optional test. Not all implementations
support catchless try* but a number were fixed so they at least don't
crash on catchless try*.
2018-12-03 22:20:44 +03:00
|
|
|
catch (malValuePtr& mv) {
|
|
|
|
return "Error: " + mv->print(true);
|
|
|
|
}
|
2015-03-27 09:24:20 +03:00
|
|
|
catch (String& s) {
|
Test uncaught throw, catchless try* . Fix 46 impls.
Fixes made to: ada, c, chuck, clojure, coffee, common-lisp, cpp,
crystal, d, dart, elm, erlang, es6, factor, fsharp, gnu-smalltalk,
groovy, guile, haxe, hy, js, livescript, matlab, miniMAL, nasm, nim,
objc, objpascal, ocaml, perl, perl6, php, plsql, ps, python, r,
rpython, ruby, scheme, swift3, tcl, ts, vb, vimscript, wasm, yorick.
Catchless try* test is an optional test. Not all implementations
support catchless try* but a number were fixed so they at least don't
crash on catchless try*.
2018-12-03 22:20:44 +03:00
|
|
|
return "Error: " + s;
|
2015-03-27 09:24:20 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
static void makeArgv(malEnvPtr env, int argc, char* argv[])
|
|
|
|
{
|
|
|
|
malValueVec* args = new malValueVec();
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
|
|
args->push_back(mal::string(argv[i]));
|
|
|
|
}
|
|
|
|
env->set("*ARGV*", mal::list(args));
|
|
|
|
}
|
|
|
|
|
|
|
|
String rep(const String& input, malEnvPtr env)
|
|
|
|
{
|
|
|
|
return PRINT(EVAL(READ(input), env));
|
|
|
|
}
|
|
|
|
|
|
|
|
malValuePtr READ(const String& input)
|
|
|
|
{
|
|
|
|
return readStr(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
|
|
|
|
{
|
2015-12-14 10:06:50 +03:00
|
|
|
if (!env) {
|
|
|
|
env = replEnv;
|
|
|
|
}
|
2015-03-27 09:24:20 +03:00
|
|
|
while (1) {
|
|
|
|
|
2022-01-10 02:15:40 +03:00
|
|
|
const malEnvPtr dbgenv = env->find("DEBUG-EVAL");
|
|
|
|
if (dbgenv && dbgenv->get("DEBUG-EVAL")->isTrue()) {
|
|
|
|
std::cout << "EVAL: " << PRINT(ast) << "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
const malList* list = DYNAMIC_CAST(malList, ast);
|
2015-03-27 09:24:20 +03:00
|
|
|
if (!list || (list->count() == 0)) {
|
|
|
|
return ast->eval(env);
|
|
|
|
}
|
|
|
|
|
|
|
|
// From here on down we are evaluating a non-empty list.
|
|
|
|
// First handle the special forms.
|
|
|
|
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
|
|
|
|
String special = symbol->value();
|
|
|
|
int argCount = list->count() - 1;
|
|
|
|
|
|
|
|
if (special == "def!") {
|
|
|
|
checkArgsIs("def!", 2, argCount);
|
|
|
|
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
|
|
|
|
return env->set(id->value(), EVAL(list->item(2), env));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "defmacro!") {
|
|
|
|
checkArgsIs("defmacro!", 2, argCount);
|
|
|
|
|
|
|
|
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
|
|
|
|
malValuePtr body = EVAL(list->item(2), env);
|
|
|
|
const malLambda* lambda = VALUE_CAST(malLambda, body);
|
|
|
|
return env->set(id->value(), mal::macro(*lambda));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "do") {
|
|
|
|
checkArgsAtLeast("do", 1, argCount);
|
|
|
|
|
|
|
|
for (int i = 1; i < argCount; i++) {
|
|
|
|
EVAL(list->item(i), env);
|
|
|
|
}
|
|
|
|
ast = list->item(argCount);
|
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "fn*") {
|
|
|
|
checkArgsIs("fn*", 2, argCount);
|
|
|
|
|
|
|
|
const malSequence* bindings =
|
|
|
|
VALUE_CAST(malSequence, list->item(1));
|
|
|
|
StringVec params;
|
|
|
|
for (int i = 0; i < bindings->count(); i++) {
|
|
|
|
const malSymbol* sym =
|
|
|
|
VALUE_CAST(malSymbol, bindings->item(i));
|
|
|
|
params.push_back(sym->value());
|
|
|
|
}
|
|
|
|
|
|
|
|
return mal::lambda(params, list->item(2), env);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "if") {
|
|
|
|
checkArgsBetween("if", 2, 3, argCount);
|
|
|
|
|
|
|
|
bool isTrue = EVAL(list->item(1), env)->isTrue();
|
|
|
|
if (!isTrue && (argCount == 2)) {
|
|
|
|
return mal::nilValue();
|
|
|
|
}
|
|
|
|
ast = list->item(isTrue ? 2 : 3);
|
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "let*") {
|
|
|
|
checkArgsIs("let*", 2, argCount);
|
|
|
|
const malSequence* bindings =
|
|
|
|
VALUE_CAST(malSequence, list->item(1));
|
|
|
|
int count = checkArgsEven("let*", bindings->count());
|
|
|
|
malEnvPtr inner(new malEnv(env));
|
|
|
|
for (int i = 0; i < count; i += 2) {
|
|
|
|
const malSymbol* var =
|
|
|
|
VALUE_CAST(malSymbol, bindings->item(i));
|
|
|
|
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
|
|
|
|
}
|
|
|
|
ast = list->item(2);
|
|
|
|
env = inner;
|
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "quasiquote") {
|
|
|
|
checkArgsIs("quasiquote", 1, argCount);
|
|
|
|
ast = quasiquote(list->item(1));
|
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "quote") {
|
|
|
|
checkArgsIs("quote", 1, argCount);
|
|
|
|
return list->item(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special == "try*") {
|
|
|
|
malValuePtr tryBody = list->item(1);
|
2019-03-01 02:36:45 +03:00
|
|
|
|
|
|
|
if (argCount == 1) {
|
2024-08-18 23:27:51 +03:00
|
|
|
ast = tryBody;
|
2019-03-01 02:36:45 +03:00
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
checkArgsIs("try*", 2, argCount);
|
2015-03-27 09:24:20 +03:00
|
|
|
const malList* catchBlock = VALUE_CAST(malList, list->item(2));
|
|
|
|
|
|
|
|
checkArgsIs("catch*", 2, catchBlock->count() - 1);
|
2015-03-28 14:54:26 +03:00
|
|
|
MAL_CHECK(VALUE_CAST(malSymbol,
|
2015-03-27 09:24:20 +03:00
|
|
|
catchBlock->item(0))->value() == "catch*",
|
|
|
|
"catch block must begin with catch*");
|
|
|
|
|
|
|
|
// We don't need excSym at this scope, but we want to check
|
|
|
|
// that the catch block is valid always, not just in case of
|
|
|
|
// an exception.
|
|
|
|
const malSymbol* excSym =
|
|
|
|
VALUE_CAST(malSymbol, catchBlock->item(1));
|
|
|
|
|
|
|
|
malValuePtr excVal;
|
|
|
|
|
|
|
|
try {
|
2024-08-18 23:27:51 +03:00
|
|
|
return EVAL(tryBody, env);
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
catch(String& s) {
|
|
|
|
excVal = mal::string(s);
|
|
|
|
}
|
|
|
|
catch (malEmptyInputException&) {
|
|
|
|
// Not an error, continue as if we got nil
|
|
|
|
ast = mal::nilValue();
|
|
|
|
}
|
|
|
|
catch(malValuePtr& o) {
|
|
|
|
excVal = o;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (excVal) {
|
|
|
|
// we got some exception
|
|
|
|
env = malEnvPtr(new malEnv(env));
|
|
|
|
env->set(excSym->value(), excVal);
|
|
|
|
ast = catchBlock->item(2);
|
|
|
|
}
|
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we're left with the case of a regular list to be evaluated.
|
2022-01-10 02:15:40 +03:00
|
|
|
malValuePtr op = EVAL(list->item(0), env);
|
2015-03-27 09:24:20 +03:00
|
|
|
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
|
2022-01-10 02:15:40 +03:00
|
|
|
if (lambda->isMacro()) {
|
|
|
|
ast = lambda->apply(list->begin()+1, list->end());
|
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
malValueVec* items = STATIC_CAST(malList, list->rest())->evalItems(env);
|
2015-03-27 09:24:20 +03:00
|
|
|
ast = lambda->getBody();
|
2022-01-10 02:15:40 +03:00
|
|
|
env = lambda->makeEnv(items->begin(), items->end());
|
2015-03-27 09:24:20 +03:00
|
|
|
continue; // TCO
|
|
|
|
}
|
|
|
|
else {
|
2022-01-10 02:15:40 +03:00
|
|
|
malValueVec* items = STATIC_CAST(malList, list->rest())->evalItems(env);
|
|
|
|
return APPLY(op, items->begin(), items->end());
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String PRINT(malValuePtr ast)
|
|
|
|
{
|
|
|
|
return ast->print(true);
|
|
|
|
}
|
|
|
|
|
2015-12-14 10:06:50 +03:00
|
|
|
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd)
|
2015-03-27 09:24:20 +03:00
|
|
|
{
|
|
|
|
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
|
2015-03-28 14:54:26 +03:00
|
|
|
MAL_CHECK(handler != NULL,
|
|
|
|
"\"%s\" is not applicable", op->print(true).c_str());
|
2015-03-27 09:24:20 +03:00
|
|
|
|
2015-12-14 10:06:50 +03:00
|
|
|
return handler->apply(argsBegin, argsEnd);
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool isSymbol(malValuePtr obj, const String& text)
|
|
|
|
{
|
|
|
|
const malSymbol* sym = DYNAMIC_CAST(malSymbol, obj);
|
|
|
|
return sym && (sym->value() == text);
|
|
|
|
}
|
|
|
|
|
2020-07-21 19:01:48 +03:00
|
|
|
// Return arg when ast matches ('sym, arg), else NULL.
|
|
|
|
static malValuePtr starts_with(const malValuePtr ast, const char* sym)
|
2015-03-27 09:24:20 +03:00
|
|
|
{
|
2020-07-21 19:01:48 +03:00
|
|
|
const malList* list = DYNAMIC_CAST(malList, ast);
|
|
|
|
if (!list || list->isEmpty() || !isSymbol(list->item(0), sym))
|
|
|
|
return NULL;
|
|
|
|
checkArgsIs(sym, 1, list->count() - 1);
|
|
|
|
return list->item(1);
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static malValuePtr quasiquote(malValuePtr obj)
|
|
|
|
{
|
2020-07-21 19:01:48 +03:00
|
|
|
if (DYNAMIC_CAST(malSymbol, obj) || DYNAMIC_CAST(malHash, obj))
|
2015-03-27 09:24:20 +03:00
|
|
|
return mal::list(mal::symbol("quote"), obj);
|
|
|
|
|
2020-07-21 19:01:48 +03:00
|
|
|
const malSequence* seq = DYNAMIC_CAST(malSequence, obj);
|
|
|
|
if (!seq)
|
|
|
|
return obj;
|
|
|
|
|
|
|
|
const malValuePtr unquoted = starts_with(obj, "unquote");
|
|
|
|
if (unquoted)
|
|
|
|
return unquoted;
|
|
|
|
|
|
|
|
malValuePtr res = mal::list(new malValueVec(0));
|
|
|
|
for (int i=seq->count()-1; 0<=i; i--) {
|
|
|
|
const malValuePtr elt = seq->item(i);
|
|
|
|
const malValuePtr spl_unq = starts_with(elt, "splice-unquote");
|
|
|
|
if (spl_unq)
|
|
|
|
res = mal::list(mal::symbol("concat"), spl_unq, res);
|
|
|
|
else
|
|
|
|
res = mal::list(mal::symbol("cons"), quasiquote(elt), res);
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
2020-07-21 19:01:48 +03:00
|
|
|
if (DYNAMIC_CAST(malVector, obj))
|
|
|
|
res = mal::list(mal::symbol("vec"), res);
|
|
|
|
return res;
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
|
2019-05-08 04:06:45 +03:00
|
|
|
static const char* malFunctionTable[] = {
|
2015-03-27 09:24:20 +03:00
|
|
|
"(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)))))))",
|
|
|
|
"(def! not (fn* (cond) (if cond false true)))",
|
|
|
|
"(def! load-file (fn* (filename) \
|
2019-07-16 00:57:02 +03:00
|
|
|
(eval (read-string (str \"(do \" (slurp filename) \"\nnil)\")))))",
|
2016-02-15 05:57:31 +03:00
|
|
|
"(def! *host-language* \"C++\")",
|
2015-03-27 09:24:20 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static void installFunctions(malEnvPtr env) {
|
2015-03-28 04:22:01 +03:00
|
|
|
for (auto &function : malFunctionTable) {
|
|
|
|
rep(function, env);
|
2015-03-27 09:24:20 +03:00
|
|
|
}
|
|
|
|
}
|
2019-03-01 02:36:45 +03:00
|
|
|
|
|
|
|
// Added to keep the linker happy at step A
|
|
|
|
malValuePtr readline(const String& prompt)
|
|
|
|
{
|
|
|
|
String input;
|
|
|
|
if (s_readLine.get(prompt, input)) {
|
|
|
|
return mal::string(input);
|
|
|
|
}
|
|
|
|
return mal::nilValue();
|
|
|
|
}
|
|
|
|
|