Makefile: fix recursive make of compiled impls.
[jackhill/mal.git] / cpp / Core.cpp
index b902fee..d1e20a1 100644 (file)
@@ -3,6 +3,10 @@
 #include "StaticList.h"
 #include "Types.h"
 
+#include <chrono>
+#include <fstream>
+#include <iostream>
+
 #define CHECK_ARGS_IS(expected) \
     checkArgsIs(name.c_str(), expected, \
                   std::distance(argsBegin, argsEnd))
@@ -15,6 +19,9 @@
     checkArgsAtLeast(name.c_str(), expected, \
                         std::distance(argsBegin, argsEnd))
 
+static String printValues(malValueIter begin, malValueIter end,
+                           const String& sep, bool readably);
+
 static StaticList<malBuiltIn*> handlers;
 
 #define ARG(type, name) type* name = VALUE_CAST(type, *argsBegin++)
@@ -30,22 +37,46 @@ static StaticList<malBuiltIn*> handlers;
 
 #define BUILTIN(symbol)  BUILTIN_DEF(__LINE__, symbol)
 
+#define BUILTIN_ISA(symbol, type) \
+    BUILTIN(symbol) { \
+        CHECK_ARGS_IS(1); \
+        return mal::boolean(DYNAMIC_CAST(type, *argsBegin)); \
+    }
+
+#define BUILTIN_IS(op, constant) \
+    BUILTIN(op) { \
+        CHECK_ARGS_IS(1); \
+        return mal::boolean(*argsBegin == mal::constant()); \
+    }
+
 #define BUILTIN_INTOP(op, checkDivByZero) \
     BUILTIN(#op) { \
         CHECK_ARGS_IS(2); \
         ARG(malInteger, lhs); \
         ARG(malInteger, rhs); \
         if (checkDivByZero) { \
-            ASSERT(rhs->value() != 0, "Division by zero"); \
+            MAL_CHECK(rhs->value() != 0, "Division by zero"); \
         } \
         return mal::integer(lhs->value() op rhs->value()); \
     }
 
+BUILTIN_ISA("atom?",        malAtom);
+BUILTIN_ISA("keyword?",     malKeyword);
+BUILTIN_ISA("list?",        malList);
+BUILTIN_ISA("map?",         malHash);
+BUILTIN_ISA("sequential?",  malSequence);
+BUILTIN_ISA("symbol?",      malSymbol);
+BUILTIN_ISA("vector?",      malVector);
+
 BUILTIN_INTOP(+,            false);
 BUILTIN_INTOP(/,            true);
 BUILTIN_INTOP(*,            false);
 BUILTIN_INTOP(%,            true);
 
+BUILTIN_IS("true?",         trueValue);
+BUILTIN_IS("false?",        falseValue);
+BUILTIN_IS("nil?",          nilValue);
+
 BUILTIN("-")
 {
     int argCount = CHECK_ARGS_BETWEEN(1, 2);
@@ -58,9 +89,317 @@ BUILTIN("-")
     return mal::integer(lhs->value() - rhs->value());
 }
 
+BUILTIN("<=")
+{
+    CHECK_ARGS_IS(2);
+    ARG(malInteger, lhs);
+    ARG(malInteger, rhs);
+
+    return mal::boolean(lhs->value() <= rhs->value());
+}
+
+BUILTIN("=")
+{
+    CHECK_ARGS_IS(2);
+    const malValue* lhs = (*argsBegin++).ptr();
+    const malValue* rhs = (*argsBegin++).ptr();
+
+    return mal::boolean(lhs->isEqualTo(rhs));
+}
+
+BUILTIN("apply")
+{
+    CHECK_ARGS_AT_LEAST(2);
+    malValuePtr op = *argsBegin++; // this gets checked in APPLY
+
+    // Copy the first N-1 arguments in.
+    malValueVec args(argsBegin, argsEnd-1);
+
+    // Then append the argument as a list.
+    const malSequence* lastArg = VALUE_CAST(malSequence, *(argsEnd-1));
+    for (int i = 0; i < lastArg->count(); i++) {
+        args.push_back(lastArg->item(i));
+    }
+
+    return APPLY(op, args.begin(), args.end(), env->getRoot());
+}
+
+BUILTIN("assoc")
+{
+    CHECK_ARGS_AT_LEAST(1);
+    ARG(malHash, hash);
+
+    return hash->assoc(argsBegin, argsEnd);
+}
+
+BUILTIN("atom")
+{
+    CHECK_ARGS_IS(1);
+
+    return mal::atom(*argsBegin);
+}
+
+BUILTIN("concat")
+{
+    int count = 0;
+    for (auto it = argsBegin; it != argsEnd; ++it) {
+        const malSequence* seq = VALUE_CAST(malSequence, *it);
+        count += seq->count();
+    }
+
+    malValueVec* items = new malValueVec(count);
+    int offset = 0;
+    for (auto it = argsBegin; it != argsEnd; ++it) {
+        const malSequence* seq = STATIC_CAST(malSequence, *it);
+        std::copy(seq->begin(), seq->end(), items->begin() + offset);
+        offset += seq->count();
+    }
+
+    return mal::list(items);
+}
+
+BUILTIN("conj")
+{
+    CHECK_ARGS_AT_LEAST(1);
+    ARG(malSequence, seq);
+
+    return seq->conj(argsBegin, argsEnd);
+}
+
+BUILTIN("cons")
+{
+    CHECK_ARGS_IS(2);
+    malValuePtr first = *argsBegin++;
+    ARG(malSequence, rest);
+
+    malValueVec* items = new malValueVec(1 + rest->count());
+    items->at(0) = first;
+    std::copy(rest->begin(), rest->end(), items->begin() + 1);
+
+    return mal::list(items);
+}
+
+BUILTIN("contains?")
+{
+    CHECK_ARGS_IS(2);
+    if (*argsBegin == mal::nilValue()) {
+        return *argsBegin;
+    }
+    ARG(malHash, hash);
+    return mal::boolean(hash->contains(*argsBegin));
+}
+
+BUILTIN("count")
+{
+    CHECK_ARGS_IS(1);
+    if (*argsBegin == mal::nilValue()) {
+        return mal::integer(0);
+    }
+
+    ARG(malSequence, seq);
+    return mal::integer(seq->count());
+}
+
+BUILTIN("deref")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malAtom, atom);
+
+    return atom->deref();
+}
+
+BUILTIN("dissoc")
+{
+    CHECK_ARGS_AT_LEAST(1);
+    ARG(malHash, hash);
+
+    return hash->dissoc(argsBegin, argsEnd);
+}
+
+BUILTIN("empty?")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malSequence, seq);
+
+    return mal::boolean(seq->isEmpty());
+}
+
+BUILTIN("eval")
+{
+    CHECK_ARGS_IS(1);
+    return EVAL(*argsBegin, env->getRoot());
+}
+
+BUILTIN("first")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malSequence, seq);
+    return seq->first();
+}
+
+BUILTIN("get")
+{
+    CHECK_ARGS_IS(2);
+    if (*argsBegin == mal::nilValue()) {
+        return *argsBegin;
+    }
+    ARG(malHash, hash);
+    return hash->get(*argsBegin);
+}
+
 BUILTIN("hash-map")
 {
-    return mal::hash(argsBegin, argsEnd);
+    return mal::hash(argsBegin, argsEnd, true);
+}
+
+BUILTIN("keys")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malHash, hash);
+    return hash->keys();
+}
+
+BUILTIN("keyword")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malString, token);
+    return mal::keyword(":" + token->value());
+}
+
+BUILTIN("meta")
+{
+    CHECK_ARGS_IS(1);
+    malValuePtr obj = *argsBegin++;
+
+    return obj->meta();
+}
+
+BUILTIN("nth")
+{
+    CHECK_ARGS_IS(2);
+    ARG(malSequence, seq);
+    ARG(malInteger,  index);
+
+    int i = index->value();
+    MAL_CHECK(i >= 0 && i < seq->count(), "Index out of range");
+
+    return seq->item(i);
+}
+
+BUILTIN("pr-str")
+{
+    return mal::string(printValues(argsBegin, argsEnd, " ", true));
+}
+
+BUILTIN("println")
+{
+    std::cout << printValues(argsBegin, argsEnd, " ", false) << "\n";
+    return mal::nilValue();
+}
+
+BUILTIN("prn")
+{
+    std::cout << printValues(argsBegin, argsEnd, " ", true) << "\n";
+    return mal::nilValue();
+}
+
+BUILTIN("read-string")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malString, str);
+
+    return readStr(str->value());
+}
+
+BUILTIN("readline")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malString, str);
+
+    return readline(str->value());
+}
+
+BUILTIN("reset!")
+{
+    CHECK_ARGS_IS(2);
+    ARG(malAtom, atom);
+    return atom->reset(*argsBegin);
+}
+
+BUILTIN("rest")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malSequence, seq);
+    return seq->rest();
+}
+
+BUILTIN("slurp")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malString, filename);
+
+    std::ios_base::openmode openmode =
+        std::ios::ate | std::ios::in | std::ios::binary;
+    std::ifstream file(filename->value().c_str(), openmode);
+    MAL_CHECK(!file.fail(), "Cannot open %s", filename->value().c_str());
+
+    String data;
+    data.reserve(file.tellg());
+    file.seekg(0, std::ios::beg);
+    data.append(std::istreambuf_iterator<char>(file.rdbuf()),
+                std::istreambuf_iterator<char>());
+
+    return mal::string(data);
+}
+
+BUILTIN("str")
+{
+    return mal::string(printValues(argsBegin, argsEnd, "", false));
+}
+
+BUILTIN("symbol")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malString, token);
+    return mal::symbol(token->value());
+}
+
+BUILTIN("throw")
+{
+    CHECK_ARGS_IS(1);
+    throw *argsBegin;
+}
+
+BUILTIN("time-ms")
+{
+    CHECK_ARGS_IS(0);
+
+    using namespace std::chrono;
+    milliseconds ms = duration_cast<milliseconds>(
+        high_resolution_clock::now().time_since_epoch()
+    );
+
+    return mal::integer(ms.count());
+}
+
+BUILTIN("vals")
+{
+    CHECK_ARGS_IS(1);
+    ARG(malHash, hash);
+    return hash->values();
+}
+
+BUILTIN("vector")
+{
+    return mal::vector(argsBegin, argsEnd);
+}
+
+BUILTIN("with-meta")
+{
+    CHECK_ARGS_IS(2);
+    malValuePtr obj  = *argsBegin++;
+    malValuePtr meta = *argsBegin++;
+    return obj->withMeta(meta);
 }
 
 void installCore(malEnvPtr env) {
@@ -69,3 +408,21 @@ void installCore(malEnvPtr env) {
         env->set(handler->name(), handler);
     }
 }
+
+static String printValues(malValueIter begin, malValueIter end,
+                          const String& sep, bool readably)
+{
+    String out;
+
+    if (begin != end) {
+        out += (*begin)->print(readably);
+        ++begin;
+    }
+
+    for ( ; begin != end; ++begin) {
+        out += sep;
+        out += (*begin)->print(readably);
+    }
+
+    return out;
+}