From dc55a967ef7f39e2fc38d3f1fa1918bb84607a45 Mon Sep 17 00:00:00 2001 From: VaclavT Date: Thu, 11 Feb 2021 00:30:36 +0100 Subject: [PATCH] basic json parsing added null not supported correctly --- .gitignore | 1 + .vscode/c_cpp_properties.json | 4 +- CMakeLists.txt | 3 +- Readme.md | 7 +- clib/json11.cpp | 854 ++++++++++++++++++ clib/json11.h | 175 ++++ clib/sslclient.cpp | 8 +- debug.lisp | 30 +- ml.cpp | 1607 ++++++++++++++++----------------- ml.h | 210 +++++ 10 files changed, 2050 insertions(+), 849 deletions(-) create mode 100644 clib/json11.cpp create mode 100644 clib/json11.h create mode 100644 ml.h diff --git a/.gitignore b/.gitignore index 378eac2..8e0f8b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +/cmake-build-debug/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 4a4d1df..7872929 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -4,14 +4,14 @@ "name": "Mac", "includePath": [ "${workspaceFolder}/**", - "${workspaceFolder}/stdlib" + "${workspaceFolder}/clib" ], "defines": [], "macFrameworkPath": [ "/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/Library/Frameworks" ], "compilerPath": "/usr/bin/clang", - "cStandard": "c11", + "cStandard": "c17", "intelliSenseMode": "macos-clang-x64", "configurationProvider": "ms-vscode.cmake-tools" } diff --git a/CMakeLists.txt b/CMakeLists.txt index 59b79aa..e2cb5ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,8 @@ set(PROJECT_NAME ml) set(SOURCE ml.cpp clib/csvparser.cpp - clib/sslclient.cpp) + clib/sslclient.cpp + clib/json11.cpp) add_executable(${PROJECT_NAME} ${SOURCE}) diff --git a/Readme.md b/Readme.md index 9bad78c..fcbb97f 100644 --- a/Readme.md +++ b/Readme.md @@ -1,7 +1,9 @@ ### TODO +- support for strings with " included - update openssl libs - +- documentation +- add url of source/inspiration to clib/*.cpp #### Functionality - readline @@ -12,7 +14,8 @@ - env - support for including lib - date support - +- file functions +- string funtions #### Performance diff --git a/clib/json11.cpp b/clib/json11.cpp new file mode 100644 index 0000000..534b5c7 --- /dev/null +++ b/clib/json11.cpp @@ -0,0 +1,854 @@ + +#include "json11.h" +#include +#include +#include +#include +#include + +namespace json11 { + + static const int max_depth = 200; + + using std::string; + using std::vector; + using std::map; + using std::make_shared; + using std::initializer_list; + using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ + struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } + }; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + + static void dump(NullStruct, string &out) { + out += "null"; + } + + static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } + } + + static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; + } + + static void dump(bool value, string &out) { + out += value ? "true" : "false"; + } + + static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; + } + + static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; + } + + static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; + } + + void Json::dump(string &out) const { + m_ptr->dump(out); + } + +//////////////////////// + + static MlValue ivalualize(NullStruct) { + MlValue null; + return null; + } + + static MlValue ivalualize(double value) { + MlValue d; + if (std::isfinite(value)) { + d = value; + } + return d; + } + + static MlValue ivalualize(int value) { + return MlValue(value); + } + + static MlValue ivalualize(bool value) { + return MlValue(value); + } + + static MlValue ivalualize(const string& value) { + return MlValue::string(value); + } + + static MlValue ivalualize(const Json::array &values) { + vector arry; + for (const auto &value : values) { + arry.push_back(value.ivalualize()); + } + return arry; + } + + static MlValue ivalualize(const Json::object &values) { + vector map; + + for (const auto &kv : values) { + std::string first = kv.first; + Json second = kv.second; + + vector pair; + pair.push_back(MlValue::string(first)); + pair.push_back(second.ivalualize()); + + map.push_back(pair); + } + return map; + } + + MlValue Json::ivalualize() const { + return m_ptr->ivalualize(); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + + template + class Value : public JsonValue { + protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } + + MlValue ivalualize() const override { return json11::ivalualize(m_value); } + }; + + class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } + public: + explicit JsonDouble(double value) : Value(value) {} + }; + + class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } + public: + explicit JsonInt(int value) : Value(value) {} + }; + + class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } + public: + explicit JsonBoolean(bool value) : Value(value) {} + }; + + class JsonString final : public Value { + const string &string_value() const override { return m_value; } + public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} + }; + + class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; + public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} + }; + + class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; + public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} + }; + + class JsonNull final : public Value { + public: + JsonNull() : Value({}) {} + }; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ + struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} + }; + + static const Statics & statics() { + static const Statics s {}; + return s; + } + + static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; + } + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + + Json::Json() noexcept : m_ptr(statics().null) {} + Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} + Json::Json(double value) : m_ptr(make_shared(value)) {} + Json::Json(int value) : m_ptr(make_shared(value)) {} + Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} + Json::Json(const string &value) : m_ptr(make_shared(value)) {} + Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} + Json::Json(const char * value) : m_ptr(make_shared(value)) {} + Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} + Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} + Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} + Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + + Json::Type Json::type() const { return m_ptr->type(); } + double Json::number_value() const { return m_ptr->number_value(); } + int Json::int_value() const { return m_ptr->int_value(); } + bool Json::bool_value() const { return m_ptr->bool_value(); } + const string & Json::string_value() const { return m_ptr->string_value(); } + const vector & Json::array_items() const { return m_ptr->array_items(); } + const map & Json::object_items() const { return m_ptr->object_items(); } + const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } + const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + + double JsonValue::number_value() const { return 0; } + int JsonValue::int_value() const { return 0; } + bool JsonValue::bool_value() const { return false; } + const string & JsonValue::string_value() const { return statics().empty_string; } + const vector & JsonValue::array_items() const { return statics().empty_vector; } + const map & JsonValue::object_items() const { return statics().empty_map; } + const Json & JsonValue::operator[] (size_t) const { return static_null(); } + const Json & JsonValue::operator[] (const string &) const { return static_null(); } + + const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; + } + const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; + } + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + + bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); + } + + bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); + } + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ + static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); + } + + static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); + } + + namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ + struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return (char)0; + if (i == str.size()) + return fail("unexpected end of input", (char)0); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } + }; + }//namespace { + + Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; + } + +// Documented in json11.hpp + vector Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; + } + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + + bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; + } + +} // namespace json11 diff --git a/clib/json11.h b/clib/json11.h new file mode 100644 index 0000000..ebe4a6b --- /dev/null +++ b/clib/json11.h @@ -0,0 +1,175 @@ +#pragma once + +#include "../ml.h" + +#include +#include +#include +#include +#include + +namespace json11 { + + enum JsonParse { + STANDARD, COMMENTS + }; + + class JsonValue; + + class Json final { + public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ().begin()->first)>::value + && std::is_constructible().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + MlValue ivalualize() const; + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list> shape; + bool has_shape(const shape & types, std::string & err) const; + + private: + std::shared_ptr m_ptr; + }; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. + class JsonValue { + protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual MlValue ivalualize() const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} + }; + +} // namespace json11 diff --git a/clib/sslclient.cpp b/clib/sslclient.cpp index 585dfd8..2114b17 100644 --- a/clib/sslclient.cpp +++ b/clib/sslclient.cpp @@ -172,7 +172,7 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re int s; s = socket(AF_INET, SOCK_STREAM, 0); if (!s) { - printf("Error creating socket.\n"); + printf("MlError creating socket.\n"); return -1; } @@ -187,7 +187,7 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re // connect to server if (connect(s, (struct sockaddr *)&sa, socklen)) { - printf("Error connecting to server.\n"); + printf("MlError connecting to server.\n"); return -1; } @@ -198,7 +198,7 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re SSL_CTX *ctx = SSL_CTX_new(meth); ssl = SSL_new(ctx); if (!ssl) { - printf("Error creating SSL.\n"); + printf("MlError creating SSL.\n"); log_ssl(); return -1; } @@ -209,7 +209,7 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re int err = SSL_connect(ssl); if (err <= 0) { - printf("Error creating SSL connection. err=%x\n", err); + printf("MlError creating SSL connection. err=%x\n", err); log_ssl(); fflush(stdout); return -1; diff --git a/debug.lisp b/debug.lisp index 6aec8d8..da3c5bd 100644 --- a/debug.lisp +++ b/debug.lisp @@ -1,18 +1,24 @@ (print "Debug starts") -(include "tmp/example.lisp") -(print "sorted: " (qs '(10 9 8 7 6 5 4 3 2 1))) - -(define csv (read-file "tmp/data.csv")) -; (print csv) -(define csv_list (parse-csv csv)) -; (print csv_list) -(for x csv_list +(define json_list (read-json "{\"k1\":\"v1\", \"k2\":42, \"k3\":[\"a\",123,true,false,null]}")) +(print json_list) +(for x json_list (print x)) -(define web_page (read-url "https://query1.finance.yahoo.com/v7/finance/download/FDX?period1=1581272585&period2=1612894985&interval=1d&events=history&includeAdjustedClose=true")) -(print web_page) -(define fdx_list (parse-csv (index web_page 1))) -(print fdx_list) + +;; (include "tmp/example.lisp") +;; (print "sorted: " (qs '(10 9 8 7 6 5 4 3 2 1))) + +;; (define csv (read-file "tmp/data.csv")) +;; ; (print csv) +;; (define csv_list (parse-csv csv)) +;; ; (print csv_list) +;; (for x csv_list +;; (print x)) + +;; (define web_page (read-url "https://query1.finance.yahoo.com/v7/finance/download/FDX?period1=1581272585&period2=1612894985&interval=1d&events=history&includeAdjustedClose=true")) +;; (print web_page) +;; (define fdx_list (parse-csv (index web_page 1))) +;; (print fdx_list) (print "Debug ends") diff --git a/ml.cpp b/ml.cpp index 7245b3b..7419c84 100644 --- a/ml.cpp +++ b/ml.cpp @@ -1,6 +1,6 @@ -//////////////////////////////////////////////////////////////////////////////// -/// LANGUAGE OPTIONS /////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// +// Comment this define out to drop support for standard library functions. +// This allows the program to run without a runtime. +#define USE_STD // Comment this define out to drop support for libm functions #ifdef HAS_LIBM @@ -10,36 +10,7 @@ #endif -// Comment this define out to drop support for standard library functions. -// This allows the program to run without a runtime. -#define USE_STD -#ifdef USE_STD -#include -#include -#include -#include -#include - -std::string read_file_contents(std::string filename) { - std::ifstream f; - f.open(filename.c_str()); - if (!f) - throw std::runtime_error("could not open file"); - - f.seekg(0, std::ios::end); - std::string contents; - contents.reserve(f.tellg()); - f.seekg(0, std::ios::beg); - contents.assign(std::istreambuf_iterator(f), - std::istreambuf_iterator()); - f.close(); - - return contents; -} - -#else -#define NO_STD "no standard library support" -#endif +#include "ml.h" //////////////////////////////////////////////////////////////////////////////// @@ -52,8 +23,21 @@ std::string read_file_contents(std::string filename) { #include #include + +#ifdef USE_STD + +#include +#include +#include +#include +#include + +#endif + + #include "csvparser.h" #include "sslclient.h" +#include "json11.h" //////////////////////////////////////////////////////////////////////////////// /// ERROR MESSAGES ///////////////////////////////////////////////////////////// @@ -92,13 +76,37 @@ std::string read_file_contents(std::string filename) { /// HELPER FUNCTIONS /////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// + +#ifdef USE_STD + +std::string read_file_contents(const std::string &filename) { + std::ifstream f; + f.open(filename.c_str()); + if (!f) + throw std::runtime_error("could not open file"); + + f.seekg(0, std::ios::end); + std::string contents; + contents.reserve(f.tellg()); + f.seekg(0, std::ios::beg); + contents.assign(std::istreambuf_iterator(f), + std::istreambuf_iterator()); + f.close(); + + return contents; +} + +#else +#define NO_STD "no standard library support" +#endif + // Convert an object to a string using a stringstream conveniently -#define to_string( x ) static_cast((std::ostringstream() << std::dec << x )).str() +#define to_string(x) static_cast((std::ostringstream() << std::dec << x )).str() // Replace a substring with a replacement string in a source string void replace_substring(std::string &src, std::string substr, std::string replacement) { - size_t i=0; - for (i=src.find(substr, i); i!=std::string::npos; i=src.find(substr, i)) { + size_t i = 0; + for (i = src.find(substr, i); i != std::string::npos; i = src.find(substr, i)) { src.replace(i, substr.size(), replacement); i += replacement.size(); } @@ -128,144 +136,79 @@ bool is_string_float(const std::string &str) { /// LISP CONSTRUCTS //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -// Forward declaration for Environment class definition -class Value; -// An instance of a function's scope. -class Environment { -public: - // Default constructor - Environment() : parent_scope(NULL) {} - // Does this environment, or its parent environment, - // have this atom in scope? - // This is only used to determine which atoms to capture when - // creating a lambda function. - bool has(std::string name) const; - // Get the value associated with this name in this scope - Value get(std::string name) const; - // Set the value associated with this name in this scope - void set(std::string name, Value value); +MlValue::MlValue() : type(UNIT) {} - void combine(Environment const &other); - void set_parent_scope(Environment *parent) { - parent_scope = parent; +MlValue::MlValue(int i) : type(INT) { stack_data.i = i; } + +MlValue::MlValue(double f) : type(FLOAT) { stack_data.f = f; } + +MlValue::MlValue(const std::vector &list) : type(LIST), list(list) {} + +MlValue MlValue::quote(MlValue quoted) { + MlValue result; + result.type = QUOTE; + + // The first position in the list is + // used to store the quoted expression. + result.list.push_back(quoted); + return result; +} + +MlValue MlValue::atom(const std::string &s) { + MlValue result; + result.type = ATOM; + + // We use the `str` member to store the atom. + result.str = s; + return result; +} + +MlValue MlValue::string(const std::string &s) { + MlValue result; + result.type = STRING; + + // We use the `str` member to store the string. + result.str = s; + return result; +} + +// Construct a lambda function +MlValue::MlValue(const std::vector ¶ms, MlValue ret, MlEnvironment const &env) : type(LAMBDA) { + // We store the params and the result in the list member + // instead of having dedicated members. This is to save memory. + list.push_back(MlValue(params)); + list.push_back(ret); + + // Lambdas capture only variables that they know they will use. + std::vector used_atoms = ret.get_used_atoms(); + for (size_t i = 0; i < used_atoms.size(); i++) { + // If the environment has a symbol that this lambda uses, capture it. + if (env.has(used_atoms[i])) + lambda_scope.set(used_atoms[i], env.get(used_atoms[i])); } - - // Output this scope in readable form to a stream. - friend std::ostream &operator<<(std::ostream &os, Environment const &v); -private: +} - // The definitions in the scope. - std::map defs; - Environment *parent_scope; -}; +// Construct a builtin function +MlValue::MlValue(const std::string &name, Builtin b) : type(BUILTIN) { + // Store the name of the builtin function in the str member + // to save memory, and use the builtin function slot in the union + // to store the function pointer. + str = name; + stack_data.b = b; +} +//////////////////////////////////////////////////////////////////////////////// +/// C++ INTEROP METHODS //////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// -// An exception thrown by the lisp -class Error { -public: - // Create an error with the value that caused the error, - // the scope where the error was found, and the message. - Error(const Value &v, Environment const &env, const char *msg); - // Copy constructor is needed to prevent double frees - Error(Error const &other); - ~Error(); - - // Get the printable error description. - std::string description(); -private: - Value *cause; - Environment env; - const char *msg; -}; - -// The type for a builtin function, which takes a list of values, -// and the environment to run the function in. -typedef Value (*Builtin)(std::vector, Environment &); - -class Value { -public: - //////////////////////////////////////////////////////////////////////////////// - /// CONSTRUCTORS /////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - - // Constructs a unit value - Value() : type(UNIT) {} - - // Constructs an integer - Value(int i) : type(INT) { stack_data.i = i; } - // Constructs a floating point value - Value(double f) : type(FLOAT) { stack_data.f = f; } - // Constructs a list - Value(const std::vector &list) : type(LIST), list(list) {} - - // Construct a quoted value - static Value quote(Value quoted) { - Value result; - result.type = QUOTE; - - // The first position in the list is - // used to store the quoted expression. - result.list.push_back(quoted); - return result; - } - - // Construct an atom - static Value atom(const std::string &s) { - Value result; - result.type = ATOM; - - // We use the `str` member to store the atom. - result.str = s; - return result; - } - - // Construct a string - static Value string(const std::string &s) { - Value result; - result.type = STRING; - - // We use the `str` member to store the string. - result.str = s; - return result; - } - - // Construct a lambda function - Value(const std::vector ¶ms, Value ret, Environment const &env) : type(LAMBDA) { - // We store the params and the result in the list member - // instead of having dedicated members. This is to save memory. - list.push_back(Value(params)); - list.push_back(ret); - - // Lambdas capture only variables that they know they will use. - std::vector used_atoms = ret.get_used_atoms(); - for (size_t i=0; i get_used_atoms() { - std::vector result, tmp; - switch (type) { +// Get all of the atoms used in a given MlValue +std::vector MlValue::get_used_atoms() { + std::vector result, tmp; + switch (type) { case QUOTE: // The data for a quote is stored in the // first slot of the list member. @@ -282,7 +225,7 @@ public: case LIST: // If this is a list, add each of the atoms used in all // of the elements in the list. - for (size_t i=0; i args, Environment &env); - // Evaluate this value as lisp code. - Value eval(Environment &env); +bool MlValue::is_number() const { + return type == INT || type == FLOAT; +} - bool is_number() const { - return type == INT || type == FLOAT; - } +// Get the "truthy" boolean value of this value. +bool MlValue::as_bool() const { + return *this != MlValue(0); +} - // Get the "truthy" boolean value of this value. - bool as_bool() const { - return *this != Value(0); - } +// Get this item's integer value +int MlValue::as_int() const { + return cast_to_int().stack_data.i; +} - // Get this item's integer value - int as_int() const { - return cast_to_int().stack_data.i; - } +// Get this item's floating point value +double MlValue::as_float() const { + return cast_to_int().stack_data.f; +} - // Get this item's floating point value - double as_float() const { - return cast_to_int().stack_data.f; - } +// Get this item's string value +std::string MlValue::as_string() const { + // If this item is not a string, throw a cast error. + if (type != STRING) + throw MlError(*this, MlEnvironment(), BAD_CAST); + return str; +} - // Get this item's string value - std::string as_string() const { - // If this item is not a string, throw a cast error. - if (type != STRING) - throw Error(*this, Environment(), BAD_CAST); - return str; - } +// Get this item's atom value +std::string MlValue::as_atom() const { + // If this item is not an atom, throw a cast error. + if (type != ATOM) + throw MlError(*this, MlEnvironment(), BAD_CAST); + return str; +} - // Get this item's atom value - std::string as_atom() const { - // If this item is not an atom, throw a cast error. - if (type != ATOM) - throw Error(*this, Environment(), BAD_CAST); - return str; - } +// Get this item's list value +std::vector MlValue::as_list() const { + // If this item is not a list, throw a cast error. + if (type != LIST) + throw MlError(*this, MlEnvironment(), BAD_CAST); + return list; +} - // Get this item's list value - std::vector as_list() const { - // If this item is not a list, throw a cast error. - if (type != LIST) - throw Error(*this, Environment(), BAD_CAST); - return list; - } +// Push an item to the end of this list +void MlValue::push(MlValue val) { + // If this item is not a list, you cannot push to it. + // Throw an error. + if (type != LIST) + throw MlError(*this, MlEnvironment(), MISMATCHED_TYPES); - // Push an item to the end of this list - void push(Value val) { - // If this item is not a list, you cannot push to it. - // Throw an error. - if (type != LIST) - throw Error(*this, Environment(), MISMATCHED_TYPES); - - list.push_back(val); - } + list.push_back(val); +} - // Push an item from the end of this list - Value pop() { - // If this item is not a list, you cannot pop from it. - // Throw an error. - if (type != LIST) - throw Error(*this, Environment(), MISMATCHED_TYPES); - - // Remember the last item in the list - Value result = list[list.size()-1]; - // Remove it from this instance - list.pop_back(); - // Return the remembered value - return result; - } +// Push an item from the end of this list +MlValue MlValue::pop() { + // If this item is not a list, you cannot pop from it. + // Throw an error. + if (type != LIST) + throw MlError(*this, MlEnvironment(), MISMATCHED_TYPES); - //////////////////////////////////////////////////////////////////////////////// - /// TYPECASTING METHODS //////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// + // Remember the last item in the list + MlValue result = list[list.size() - 1]; + // Remove it from this instance + list.pop_back(); + // Return the remembered value + return result; +} - // Cast this to an integer value - Value cast_to_int() const { - switch (type) { - case INT: return *this; - case FLOAT: return Value(int(stack_data.f)); - // Only ints and floats can be cast to an int +//////////////////////////////////////////////////////////////////////////////// +/// TYPECASTING METHODS //////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Cast this to an integer value +MlValue MlValue::cast_to_int() const { + switch (type) { + case INT: + return *this; + case FLOAT: + return MlValue(int(stack_data.f)); + // Only ints and floats can be cast to an int default: - throw Error(*this, Environment(), BAD_CAST); - } + throw MlError(*this, MlEnvironment(), BAD_CAST); } +} - // Cast this to a floating point value - Value cast_to_float() const { - switch (type) { - case FLOAT: return *this; - case INT: return Value(float(stack_data.i)); - // Only ints and floats can be cast to a float +// Cast this to a floating point value +MlValue MlValue::cast_to_float() const { + switch (type) { + case FLOAT: + return *this; + case INT: + return MlValue(float(stack_data.i)); + // Only ints and floats can be cast to a float default: - throw Error(*this, Environment(), BAD_CAST); - } + throw MlError(*this, MlEnvironment(), BAD_CAST); } +} - //////////////////////////////////////////////////////////////////////////////// - /// COMPARISON OPERATIONS ////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// COMPARISON OPERATIONS ////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// - bool operator==(Value other) const { - // If either of these values are floats, promote the - // other to a float, and then compare for equality. - if (type == FLOAT && other.type == INT) return *this == other.cast_to_float(); - else if (type == INT && other.type == FLOAT) return this->cast_to_float() == other; +bool MlValue::operator==(MlValue other) const { + // If either of these values are floats, promote the + // other to a float, and then compare for equality. + if (type == FLOAT && other.type == INT) return *this == other.cast_to_float(); + else if (type == INT && other.type == FLOAT) return this->cast_to_float() == other; // If the values types aren't equal, then they cannot be equal. - else if (type != other.type) return false; + else if (type != other.type) return false; - switch (type) { + switch (type) { case FLOAT: return stack_data.f == other.stack_data.f; case INT: @@ -433,35 +375,35 @@ public: return list[0] == other.list[0]; default: return true; - } - } - - bool operator!=(const Value &other) const { - return !(*this == other); } +} - //////////////////////////////////////////////////////////////////////////////// - /// ORDERING OPERATIONS //////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// +bool MlValue::operator!=(const MlValue &other) const { + return !(*this == other); +} - bool operator>=(const Value &other) const { - return !(*this < other); - } - - bool operator<=(const Value &other) const { - return (*this == other) || (*this < other); - } - - bool operator>(const Value &other) const { - return !(*this <= other); - } +//////////////////////////////////////////////////////////////////////////////// +/// ORDERING OPERATIONS //////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// - bool operator<(const Value &other) const { - // Other type must be a float or an int - if (other.type != FLOAT && other.type != INT) - throw Error(*this, Environment(), INVALID_BIN_OP); +bool MlValue::operator>=(const MlValue &other) const { + return !(*this < other); +} - switch (type) { +bool MlValue::operator<=(const MlValue &other) const { + return (*this == other) || (*this < other); +} + +bool MlValue::operator>(const MlValue &other) const { + return !(*this <= other); +} + +bool MlValue::operator<(const MlValue &other) const { + // Other type must be a float or an int + if (other.type != FLOAT && other.type != INT) + throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); + + switch (type) { case FLOAT: // If this is a float, promote the other value to a float and compare. return stack_data.f < other.cast_to_float().stack_data.f; @@ -469,205 +411,211 @@ public: // If the other value is a float, promote this value to a float and compare. if (other.type == FLOAT) return cast_to_float().stack_data.f < other.stack_data.f; - // Otherwise compare the integer values + // Otherwise compare the integer values else return stack_data.i < other.stack_data.i; default: // Only allow comparisons between integers and floats - throw Error(*this, Environment(), INVALID_ORDER); - } + throw MlError(*this, MlEnvironment(), INVALID_ORDER); } - - //////////////////////////////////////////////////////////////////////////////// - /// ARITHMETIC OPERATIONS ////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// +} - // This function adds two lisp values, and returns the lisp value result. - Value operator+(const Value &other) const { - // If the other value's type is the unit type, - // don't even bother continuing. - // Unit types consume all arithmetic operations. - if (other.type == UNIT) return other; +//////////////////////////////////////////////////////////////////////////////// +/// ARITHMETIC OPERATIONS ////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// - // Other type must be a float or an int - if ((is_number() || other.is_number()) && - !(is_number() && other.is_number())) - throw Error(*this, Environment(), INVALID_BIN_OP); +// This function adds two lisp values, and returns the lisp value result. +MlValue MlValue::operator+(const MlValue &other) const { + // If the other value's type is the unit type, + // don't even bother continuing. + // Unit types consume all arithmetic operations. + if (other.type == UNIT) return other; - switch (type) { + // Other type must be a float or an int + if ((is_number() || other.is_number()) && + !(is_number() && other.is_number())) + throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); + + switch (type) { case FLOAT: // If one is a float, promote the other by default and do // float addition. - return Value(stack_data.f + other.cast_to_float().stack_data.f); + return MlValue(stack_data.f + other.cast_to_float().stack_data.f); case INT: // If the other type is a float, go ahead and promote this expression // before continuing with the addition. if (other.type == FLOAT) - return Value(cast_to_float() + other.stack_data.f); - // Otherwise, do integer addition. - else return Value(stack_data.i + other.stack_data.i); + return MlValue(cast_to_float() + other.stack_data.f); + // Otherwise, do integer addition. + else return MlValue(stack_data.i + other.stack_data.i); case STRING: // If the other value is also a string, do the concat if (other.type == STRING) - return Value::string(str + other.str); - // We throw an error if we try to concat anything of non-string type - else throw Error(*this, Environment(), INVALID_BIN_OP); + return MlValue::string(str + other.str); + // We throw an error if we try to concat anything of non-string type + else throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); case LIST: // If the other value is also a list, do the concat if (other.type == LIST) { // Maintain the value that will be returned - Value result = *this; + MlValue result = *this; // Add each item in the other list to the end of this list - for (size_t i=0; i list; - Environment lambda_scope; -}; - -Error::Error(const Value &v, Environment const &env, const char *msg) : env(env), msg(msg) { - cause = new Value; +MlError::MlError(const MlValue &v, MlEnvironment const &env, const char *msg) : env(env), msg(msg) { + cause = new MlValue; *cause = v; } -Error::Error(Error const &other) : env(other.env), msg(other.msg) { - cause = new Value(*other.cause); +MlError::MlError(MlError const &other) : env(other.env), msg(other.msg) { + cause = new MlValue(*other.cause); } -Error::~Error() { +MlError::~MlError() { delete cause; } -std::string Error::description() { - return "error: the expression `" + cause->debug() + "` failed in scope " + to_string(env) + " with message \"" + msg + "\""; +std::string MlError::description() { + return "error: the expression `" + cause->debug() + "` failed in scope " + to_string(env) + " with message \"" + + msg + "\""; } -void Environment::combine(Environment const &other) { +void MlEnvironment::combine(MlEnvironment const &other) { // Normally, I would use the `insert` method of the `map` class, // but it doesn't overwrite previously declared values for keys. - std::map::const_iterator itr = other.defs.begin(); - for (; itr!=other.defs.end(); itr++) { + std::map::const_iterator itr = other.defs.begin(); + for (; itr != other.defs.end(); itr++) { // Iterate through the keys and assign each value. defs[itr->first] = itr->second; } } -std::ostream &operator<<(std::ostream &os, Environment const &e) { - std::map::const_iterator itr = e.defs.begin(); +std::ostream &operator<<(std::ostream &os, MlEnvironment const &e) { + std::map::const_iterator itr = e.defs.begin(); os << "{ "; for (; itr != e.defs.end(); itr++) { os << '\'' << itr->first << "' : " << itr->second.debug() << ", "; @@ -826,83 +752,83 @@ std::ostream &operator<<(std::ostream &os, Environment const &e) { return os << "}"; } -void Environment::set(std::string name, Value value) { +void MlEnvironment::set(std::string name, MlValue value) { defs[name] = value; } -Value Value::apply(std::vector args, Environment &env) { - Environment e; - std::vector params; +MlValue MlValue::apply(std::vector args, MlEnvironment &env) { + MlEnvironment e; + std::vector params; switch (type) { - case LAMBDA: - // Get the list of parameter atoms - params = list[0].list; - if (params.size() != args.size()) - throw Error(Value(args), env, args.size() > params.size()? - TOO_MANY_ARGS : TOO_FEW_ARGS - ); + case LAMBDA: + // Get the list of parameter atoms + params = list[0].list; + if (params.size() != args.size()) + throw MlError(MlValue(args), env, args.size() > params.size() ? + TOO_MANY_ARGS : TOO_FEW_ARGS + ); - // Get the captured scope from the lambda - e = lambda_scope; - // And make this scope the parent scope - e.set_parent_scope(&env); + // Get the captured scope from the lambda + e = lambda_scope; + // And make this scope the parent scope + e.set_parent_scope(&env); - // Iterate through the list of parameters and - // insert the arguments into the scope. - for (size_t i=0; i args; - Value function; - Environment e; +MlValue MlValue::eval(MlEnvironment &env) { + std::vector args; + MlValue function; + MlEnvironment e; switch (type) { - case QUOTE: - return list[0]; - case ATOM: - return env.get(str); - case LIST: - if (list.size() < 1) - throw Error(*this, env, EVAL_EMPTY_LIST); + case QUOTE: + return list[0]; + case ATOM: + return env.get(str); + case LIST: + if (list.size() < 1) + throw MlError(*this, env, EVAL_EMPTY_LIST); - args = std::vector(list.begin() + 1, list.end()); - - // Only evaluate our arguments if it's not builtin! - // Builtin functions can be special forms, so we - // leave them to evaluate their arguments. - function = list[0].eval(env); + args = std::vector(list.begin() + 1, list.end()); - if (!function.is_builtin()) - for (size_t i=0; i()); + MlValue result = MlValue(std::vector()); while (s[ptr] != ')') result.push(parse(s, ptr)); - + skip_whitespace(s, ++ptr); return result; - + } else if (isdigit(s[ptr]) || (s[ptr] == '-' && isdigit(s[ptr + 1]))) { // If this is a number bool negate = s[ptr] == '-'; if (negate) ptr++; - + int save_ptr = ptr; while (isdigit(s[ptr]) || s[ptr] == '.') ptr++; std::string n = s.substr(save_ptr, ptr); skip_whitespace(s, ptr); - + if (n.find('.') != std::string::npos) - return Value((negate? -1 : 1) * atof(n.c_str())); - else return Value((negate? -1 : 1) * atoi(n.c_str())); + return MlValue((negate ? -1 : 1) * atof(n.c_str())); + else return MlValue((negate ? -1 : 1) * atoi(n.c_str())); } else if (s[ptr] == '\"') { // If this is a string @@ -966,33 +892,33 @@ Value parse(std::string &s, int &ptr) { while (s[ptr + n] != '\"') { if (ptr + n >= int(s.length())) throw std::runtime_error(MALFORMED_PROGRAM); - + if (s[ptr + n] == '\\') n++; n++; } - std::string x = s.substr(ptr+1, n-1); - ptr += n+1; + std::string x = s.substr(ptr + 1, n - 1); + ptr += n + 1; skip_whitespace(s, ptr); // Iterate over the characters in the string, and // replace escaped characters with their intended values. - for (size_t i=0; i parse(std::string s) { - int i=0, last_i=-1; - std::vector result; +std::vector parse(std::string s) { + int i = 0, last_i = -1; + std::vector result; // While the parser is making progress (while the pointer is moving right) // and the pointer hasn't reached the end of the string, - while (last_i != i && i <= int(s.length()-1)) { + while (last_i != i && i <= int(s.length() - 1)) { // Parse another expression and add it to the list. last_i = i; result.push_back(parse(s, i)); @@ -1031,16 +957,16 @@ std::vector parse(std::string s) { } // Execute code in an environment -Value run(const std::string &code, Environment &env) { +MlValue run(const std::string &code, MlEnvironment &env) { // Parse the code - std::vector parsed = parse(code); + std::vector parsed = parse(code); // Iterate over the expressions and evaluate them // in this environment. - for (size_t i=0; i &args, Environment &env) { - for (size_t i=0; i &args, MlEnvironment &env) { + for (size_t i = 0; i < args.size(); i++) args[i] = args[i].eval(env); } // Create a lambda function (SPECIAL FORM) - Value lambda(std::vector args, Environment &env) { + MlValue lambda(std::vector args, MlEnvironment &env) { if (args.size() < 2) - throw Error(Value("lambda", lambda), env, TOO_FEW_ARGS); + throw MlError(MlValue("lambda", lambda), env, TOO_FEW_ARGS); if (args[0].get_type_name() != LIST_TYPE) - throw Error(Value("lambda", lambda), env, INVALID_LAMBDA); + throw MlError(MlValue("lambda", lambda), env, INVALID_LAMBDA); - return Value(args[0].as_list(), args[1], env); + return MlValue(args[0].as_list(), args[1], env); } // if-else (SPECIAL FORM) - Value if_then_else(std::vector args, Environment &env) { + MlValue if_then_else(std::vector args, MlEnvironment &env) { if (args.size() != 3) - throw Error(Value("if", if_then_else), env, args.size() > 3? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("if", if_then_else), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args[0].eval(env).as_bool()) return args[1].eval(env); else return args[2].eval(env); } // Define a variable with a value (SPECIAL FORM) - Value define(std::vector args, Environment &env) { + MlValue define(std::vector args, MlEnvironment &env) { if (args.size() != 2) - throw Error(Value("define", define), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - - Value result = args[1].eval(env); + throw MlError(MlValue("define", define), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + MlValue result = args[1].eval(env); env.set(args[0].display(), result); return result; } // Define a function with parameters and a result expression (SPECIAL FORM) - Value defun(std::vector args, Environment &env) { + MlValue defun(std::vector args, MlEnvironment &env) { if (args.size() != 3) - throw Error(Value("defun", defun), env, args.size() > 3? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("defun", defun), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args[1].get_type_name() != LIST_TYPE) - throw Error(Value("defun", defun), env, INVALID_LAMBDA); + throw MlError(MlValue("defun", defun), env, INVALID_LAMBDA); - Value f = Value(args[1].as_list(), args[2], env); + MlValue f = MlValue(args[1].as_list(), args[2], env); env.set(args[0].display(), f); return f; } // Loop over a list of expressions with a condition (SPECIAL FORM) - Value while_loop(std::vector args, Environment &env) { - Value acc; + MlValue while_loop(std::vector args, MlEnvironment &env) { + MlValue acc; while (args[0].eval(env).as_bool()) { - for (size_t i=1; i args, Environment &env) { - Value acc; - std::vector list = args[1].eval(env).as_list(); + MlValue for_loop(std::vector args, MlEnvironment &env) { + MlValue acc; + std::vector list = args[1].eval(env).as_list(); - for (size_t i=0; i args, Environment &env) { - Value acc; - for (size_t i=0; i args, MlEnvironment &env) { + MlValue acc; + for (size_t i = 0; i < args.size(); i++) acc = args[i].eval(env); return acc; } // Evaluate a block of expressions in a new environment (SPECIAL FORM) - Value scope(std::vector args, Environment &env) { - Environment e = env; - Value acc; - for (size_t i=0; i args, MlEnvironment &env) { + MlEnvironment e = env; + MlValue acc; + for (size_t i = 0; i < args.size(); i++) acc = args[i].eval(e); return acc; } // Quote an expression (SPECIAL FORM) - Value quote(std::vector args, Environment &env) { - std::vector v; - for (size_t i=0; i args, MlEnvironment &env) { + std::vector v; + for (size_t i = 0; i < args.size(); i++) v.push_back(args[i]); - return Value(v); + return MlValue(v); } - #ifdef USE_STD +#ifdef USE_STD + // Exit the program with an integer code - Value exit(std::vector args, Environment &env) { + MlValue exit(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - std::exit(args.size() < 1? 0 : args[0].cast_to_int().as_int()); - return Value(); + std::exit(args.size() < 1 ? 0 : args[0].cast_to_int().as_int()); + return MlValue(); } // Print several values and return the last one - Value print(std::vector args, Environment &env) { + MlValue print(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() < 1) - throw Error(Value("print", print), env, TOO_FEW_ARGS); + throw MlError(MlValue("print", print), env, TOO_FEW_ARGS); - Value acc; - for (size_t i=0; i args, Environment &env) { + MlValue input(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() > 1) - throw Error(Value("input", input), env, TOO_MANY_ARGS); + throw MlError(MlValue("input", input), env, TOO_MANY_ARGS); if (!args.empty()) std::cout << args[0]; std::string s; std::getline(std::cin, s); - return Value::string(s); + return MlValue::string(s); } // Get a random number between two numbers inclusively - Value random(std::vector args, Environment &env) { + MlValue random(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("random", random), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("random", random), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); int low = args[0].as_int(), high = args[1].as_int(); - return Value(rand()%(high-low+1) + low); + return MlValue(rand() % (high - low + 1) + low); } - // Get the contents of a file - Value parse_csv(std::vector args, Environment &env) { + // Parse CSV string + MlValue parse_csv(std::vector args, MlEnvironment &env) { eval_args(args, env); // TODO add support for more params specifying options if (args.size() != 1) - throw Error(Value("read-csv", parse_csv), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("parse-csv", parse_csv), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); // PERF optimize it for memory usage and performance - CsvParser csv(true); - std::vector< std::vector > parsed_data; // TODO some default size here + CsvParser csv(true); + std::vector > parsed_data; // TODO some default size here csv.parseCSV(args[0].as_string(), parsed_data); - int rows = parsed_data.size(); - int cols = rows > 0 ? parsed_data[0].size() : 0; + int rows = parsed_data.size(); + int cols = rows > 0 ? parsed_data[0].size() : 0; - std::vector result; + std::vector result; - if (rows > 0 && cols > 0) { + if (rows > 0 && cols > 0) { for (int r = 0; r < rows; r++) { - std::vector row; + std::vector row; for (int c = 0; c < cols; c++) { std::string value = parsed_data[r][c]; if (is_string_int(value)) { - row.push_back(Value(stoi(value))); - } if (is_string_float(value)) { - row.push_back(Value(std::stod(value))); + row.push_back(MlValue(stoi(value))); + } + if (is_string_float(value)) { + row.push_back(MlValue(std::stod(value))); } else { - row.push_back(Value::string(value)); + row.push_back(MlValue::string(value)); } } result.push_back(row); } - } + } - return Value(result); + return MlValue(result); } // Get the contents of a file - Value read_file(std::vector args, Environment &env) { + MlValue read_file(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("read-file", read_file), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("read-file", read_file), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value::string(read_file_contents(args[0].as_string())); + return MlValue::string(read_file_contents(args[0].as_string())); } // Write a string to a file - Value write_file(std::vector args, Environment &env) { + MlValue write_file(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("write-file", write_file), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("write-file", write_file), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::ofstream f; // The first argument is the file name f.open(args[0].as_string().c_str()); // The second argument is the contents of the file to write - Value result = Value((f << args[1].as_string())? 1 : 0); + MlValue result = MlValue((f << args[1].as_string()) ? 1 : 0); f.close(); return result; } // Read URL to (code content) - Value read_url(std::vector args, Environment &env) { + MlValue read_url(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); // PERF optimize it for memory usage and performance // TODO handle second parameter (headers) if (args.size() != 1) - throw Error(Value("read_url", write_file), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("read_url", read_url), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - std::unordered_map headers = {}; - HttpClient client; + std::unordered_map headers = {}; + HttpClient client; if (args.size() == 2) { - // do magick here + // do magick here // for (auto i = map.begin(); i != map.end(); ++i) { // headers[i->first] = i->second.getString(); // } - } + } - std::pair result = client.doGetRequest(args[0].as_string(), headers); - std::vector lst; - lst.push_back(Value(result.first)); - lst.push_back(Value::string(result.second)); + std::pair result = client.doGetRequest(args[0].as_string(), headers); + std::vector lst; + lst.push_back(MlValue(result.first)); + lst.push_back(MlValue::string(result.second)); return lst; } + // Parse JSON string + MlValue read_json(std::vector args, MlEnvironment &env) { + eval_args(args, env); + + // TODO add support for more params specifying options + if (args.size() != 1) + throw MlError(MlValue("read-json", read_json), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + std::string str = args[0].as_string(); + std::string err; + auto json = json11::Json::parse(str, err); + + if (!err.empty()) { + // TODO handle error + return MlValue::string("ERROR json parsing: " + err); + } + + return json.ivalualize(); + } + + // Read a file and execute its code - Value include(std::vector args, Environment &env) { + MlValue include(std::vector args, MlEnvironment &env) { // Import is technically not a special form, it's more of a macro. // We can evaluate our arguments. eval_args(args, env); if (args.size() != 1) - throw Error(Value("include", include), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("include", include), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - Environment e; - Value result = run(read_file_contents(args[0].as_string()), e); + MlEnvironment e; + MlValue result = run(read_file_contents(args[0].as_string()), e); env.combine(e); return result; } - #endif + +#endif // Evaluate a value as code - Value eval(std::vector args, Environment &env) { + MlValue eval(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("eval", eval), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("eval", eval), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); else return args[0].eval(env); } // Create a list of values - Value list(std::vector args, Environment &env) { + MlValue list(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - - return Value(args); + + return MlValue(args); } // Sum multiple values - Value sum(std::vector args, Environment &env) { + MlValue sum(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - + if (args.size() < 2) - throw Error(Value("+", sum), env, TOO_FEW_ARGS); - - Value acc = args[0]; - for (size_t i=1; i args, Environment &env) { + MlValue subtract(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("-", subtract), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("-", subtract), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0] - args[1]; } // Multiply several values - Value product(std::vector args, Environment &env) { + MlValue product(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() < 2) - throw Error(Value("*", product), env, TOO_FEW_ARGS); + throw MlError(MlValue("*", product), env, TOO_FEW_ARGS); - Value acc = args[0]; - for (size_t i=1; i args, Environment &env) { + MlValue divide(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("/", divide), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("/", divide), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0] / args[1]; } // Get the remainder of values - Value remainder(std::vector args, Environment &env) { + MlValue remainder(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("%", remainder), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("%", remainder), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0] % args[1]; } // Are two values equal? - Value eq(std::vector args, Environment &env) { + MlValue eq(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("=", eq), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value(int(args[0] == args[1])); + throw MlError(MlValue("=", eq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + return MlValue(int(args[0] == args[1])); } // Are two values not equal? - Value neq(std::vector args, Environment &env) { + MlValue neq(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("!=", neq), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value(int(args[0] != args[1])); + throw MlError(MlValue("!=", neq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + return MlValue(int(args[0] != args[1])); } // Is one number greater than another? - Value greater(std::vector args, Environment &env) { + MlValue greater(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value(">", greater), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value(int(args[0] > args[1])); + throw MlError(MlValue(">", greater), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + return MlValue(int(args[0] > args[1])); } // Is one number less than another? - Value less(std::vector args, Environment &env) { + MlValue less(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("<", less), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value(int(args[0] < args[1])); + throw MlError(MlValue("<", less), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + return MlValue(int(args[0] < args[1])); } // Is one number greater than or equal to another? - Value greater_eq(std::vector args, Environment &env) { + MlValue greater_eq(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value(">=", greater_eq), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value(int(args[0] >= args[1])); + throw MlError(MlValue(">=", greater_eq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + return MlValue(int(args[0] >= args[1])); } // Is one number less than or equal to another? - Value less_eq(std::vector args, Environment &env) { + MlValue less_eq(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("<=", less_eq), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value(int(args[0] <= args[1])); + throw MlError(MlValue("<=", less_eq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + return MlValue(int(args[0] <= args[1])); } // Get the type name of a value - Value get_type_name(std::vector args, Environment &env) { + MlValue get_type_name(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("type", get_type_name), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("type", get_type_name), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value::string(args[0].get_type_name()); + return MlValue::string(args[0].get_type_name()); } // Cast an item to a float - Value cast_to_float(std::vector args, Environment &env) { + MlValue cast_to_float(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value(FLOAT_TYPE, cast_to_float), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue(FLOAT_TYPE, cast_to_float), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0].cast_to_float(); } // Cast an item to an int - Value cast_to_int(std::vector args, Environment &env) { + MlValue cast_to_int(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value(INT_TYPE, cast_to_int), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue(INT_TYPE, cast_to_int), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0].cast_to_int(); } // Index a list - Value index(std::vector args, Environment &env) { + MlValue index(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("index", index), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("index", index), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - std::vector list = args[0].as_list(); + std::vector list = args[0].as_list(); int i = args[1].as_int(); if (list.empty() || i >= list.size()) - throw Error(list, env, INDEX_OUT_OF_RANGE); + throw MlError(list, env, INDEX_OUT_OF_RANGE); return list[i]; } // Insert a value into a list - Value insert(std::vector args, Environment &env) { + MlValue insert(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 3) - throw Error(Value("insert", insert), env, args.size() > 3? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("insert", insert), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - std::vector list = args[0].as_list(); + std::vector list = args[0].as_list(); int i = args[1].as_int(); if (i > list.size()) - throw Error(list, env, INDEX_OUT_OF_RANGE); + throw MlError(list, env, INDEX_OUT_OF_RANGE); list.insert(list.begin() + args[1].as_int(), args[2]); - return Value(list); + return MlValue(list); } // Remove a value at an index from a list - Value remove(std::vector args, Environment &env) { + MlValue remove(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 2) - throw Error(Value("remove", remove), env, args.size() > 2? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("remove", remove), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - std::vector list = args[0].as_list(); + std::vector list = args[0].as_list(); int i = args[1].as_int(); if (list.empty() || i >= list.size()) - throw Error(list, env, INDEX_OUT_OF_RANGE); + throw MlError(list, env, INDEX_OUT_OF_RANGE); list.erase(list.begin() + i); - return Value(list); + return MlValue(list); } // Get the length of a list - Value len(std::vector args, Environment &env) { + MlValue len(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("len", len), env, args.size() > 1? - TOO_MANY_ARGS : TOO_FEW_ARGS + throw MlError(MlValue("len", len), env, args.size() > 1 ? + TOO_MANY_ARGS : TOO_FEW_ARGS ); - - return Value(int(args[0].as_list().size())); + + return MlValue(int(args[0].as_list().size())); } // Add an item to the end of a list - Value push(std::vector args, Environment &env) { + MlValue push(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() == 0) - throw Error(Value("push", push), env, TOO_FEW_ARGS); - for (size_t i=1; i args, Environment &env) { + MlValue pop(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("pop", pop), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("pop", pop), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0].pop(); } - Value head(std::vector args, Environment &env) { + MlValue head(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("head", head), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); - std::vector list = args[0].as_list(); + throw MlError(MlValue("head", head), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + std::vector list = args[0].as_list(); if (list.empty()) - throw Error(Value("head", head), env, INDEX_OUT_OF_RANGE); + throw MlError(MlValue("head", head), env, INDEX_OUT_OF_RANGE); return list[0]; } - Value tail(std::vector args, Environment &env) { + MlValue tail(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("tail", tail), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("tail", tail), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - std::vector result, list = args[0].as_list(); + std::vector result, list = args[0].as_list(); - for (size_t i = 1; i args, Environment &env) { + MlValue parse(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("parse", parse), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("parse", parse), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args[0].get_type_name() != STRING_TYPE) - throw Error(args[0], env, INVALID_ARGUMENT); - std::vector parsed = ::parse(args[0].as_string()); + throw MlError(args[0], env, INVALID_ARGUMENT); + std::vector parsed = ::parse(args[0].as_string()); // if (parsed.size() == 1) // return parsed[0]; - // else return Value(parsed); - return Value(parsed); + // else return MlValue(parsed); + return MlValue(parsed); } - Value replace(std::vector args, Environment &env) { + MlValue replace(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 3) - throw Error(Value("replace", replace), env, args.size() > 3? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("replace", replace), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::string src = args[0].as_string(); replace_substring(src, args[1].as_string(), args[2].as_string()); - return Value::string(src); + return MlValue::string(src); } - Value display(std::vector args, Environment &env) { + MlValue display(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("display", display), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("display", display), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value::string(args[0].display()); + return MlValue::string(args[0].display()); } - Value debug(std::vector args, Environment &env) { + MlValue debug(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); if (args.size() != 1) - throw Error(Value("debug", debug), env, args.size() > 1? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("debug", debug), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); - return Value::string(args[0].debug()); + return MlValue::string(args[0].debug()); } - Value map_list(std::vector args, Environment &env) { + MlValue map_list(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - std::vector result, l=args[1].as_list(), tmp; - for (size_t i=0; i result, l = args[1].as_list(), tmp; + for (size_t i = 0; i < l.size(); i++) { tmp.push_back(l[i]); result.push_back(args[0].apply(tmp, env)); tmp.clear(); } - return Value(result); + return MlValue(result); } - Value filter_list(std::vector args, Environment &env) { + MlValue filter_list(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - std::vector result, l=args[1].as_list(), tmp; - for (size_t i=0; i result, l = args[1].as_list(), tmp; + for (size_t i = 0; i < l.size(); i++) { tmp.push_back(l[i]); if (args[0].apply(tmp, env).as_bool()) result.push_back(l[i]); tmp.clear(); } - return Value(result); + return MlValue(result); } - Value reduce_list(std::vector args, Environment &env) { + MlValue reduce_list(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - std::vector l=args[2].as_list(), tmp; - Value acc = args[1]; - for (size_t i=0; i l = args[2].as_list(), tmp; + MlValue acc = args[1]; + for (size_t i = 0; i < l.size(); i++) { tmp.push_back(acc); tmp.push_back(l[i]); acc = args[0].apply(tmp, env); @@ -1686,33 +1636,33 @@ namespace builtin { return acc; } - Value range(std::vector args, Environment &env) { + MlValue range(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); - std::vector result; - Value low = args[0], high = args[1]; + std::vector result; + MlValue low = args[0], high = args[1]; if (low.get_type_name() != INT_TYPE && low.get_type_name() != FLOAT_TYPE) - throw Error(low, env, MISMATCHED_TYPES); + throw MlError(low, env, MISMATCHED_TYPES); if (high.get_type_name() != INT_TYPE && high.get_type_name() != FLOAT_TYPE) - throw Error(high, env, MISMATCHED_TYPES); + throw MlError(high, env, MISMATCHED_TYPES); - if (low >= high) return Value(result); + if (low >= high) return MlValue(result); while (low < high) { result.push_back(low); - low = low + Value(1); + low = low + MlValue(1); } - return Value(result); + return MlValue(result); } } -void repl(Environment &env) { +void repl(MlEnvironment &env) { #ifdef USE_STD std::string code; std::string input; - Value tmp; - std::vector parsed; + MlValue tmp; + std::vector parsed; while (true) { std::cout << ">>> "; std::getline(std::cin, input); @@ -1733,7 +1683,7 @@ void repl(Environment &env) { tmp = run(input, env); std::cout << " => " << tmp.debug() << std::endl; code += input + "\n"; - } catch (Error &e) { + } catch (MlError &e) { std::cerr << e.description() << std::endl; } catch (std::runtime_error &e) { std::cerr << e.what() << std::endl; @@ -1744,9 +1694,9 @@ void repl(Environment &env) { } // Does this environment, or its parent environment, have a variable? -bool Environment::has(std::string name) const { +bool MlEnvironment::has(std::string name) const { // Find the value in the map - std::map::const_iterator itr = defs.find(name); + std::map::const_iterator itr = defs.find(name); if (itr != defs.end()) // If it was found return true; @@ -1758,86 +1708,87 @@ bool Environment::has(std::string name) const { } // Get the value associated with this name in this scope -Value Environment::get(std::string name) const { +MlValue MlEnvironment::get(std::string name) const { // Meta operations - if (name == "eval") return Value("eval", builtin::eval); - if (name == "type") return Value("type", builtin::get_type_name); - if (name == "parse") return Value("parse", builtin::parse); + if (name == "eval") return MlValue("eval", builtin::eval); + if (name == "type") return MlValue("type", builtin::get_type_name); + if (name == "parse") return MlValue("parse", builtin::parse); // Special forms - if (name == "do") return Value("do", builtin::do_block); - if (name == "if") return Value("if", builtin::if_then_else); - if (name == "for") return Value("for", builtin::for_loop); - if (name == "while") return Value("while", builtin::while_loop); - if (name == "scope") return Value("scope", builtin::scope); - if (name == "quote") return Value("quote", builtin::quote); - if (name == "defun") return Value("defun", builtin::defun); - if (name == "define") return Value("define", builtin::define); - if (name == "lambda") return Value("lambda", builtin::lambda); + if (name == "do") return MlValue("do", builtin::do_block); + if (name == "if") return MlValue("if", builtin::if_then_else); + if (name == "for") return MlValue("for", builtin::for_loop); + if (name == "while") return MlValue("while", builtin::while_loop); + if (name == "scope") return MlValue("scope", builtin::scope); + if (name == "quote") return MlValue("quote", builtin::quote); + if (name == "defun") return MlValue("defun", builtin::defun); + if (name == "define") return MlValue("define", builtin::define); + if (name == "lambda") return MlValue("lambda", builtin::lambda); // Comparison operations - if (name == "=") return Value("=", builtin::eq); - if (name == "!=") return Value("!=", builtin::neq); - if (name == ">") return Value(">", builtin::greater); - if (name == "<") return Value("<", builtin::less); - if (name == ">=") return Value(">=", builtin::greater_eq); - if (name == "<=") return Value("<=", builtin::less_eq); + if (name == "=") return MlValue("=", builtin::eq); + if (name == "!=") return MlValue("!=", builtin::neq); + if (name == ">") return MlValue(">", builtin::greater); + if (name == "<") return MlValue("<", builtin::less); + if (name == ">=") return MlValue(">=", builtin::greater_eq); + if (name == "<=") return MlValue("<=", builtin::less_eq); // Arithmetic operations - if (name == "+") return Value("+", builtin::sum); - if (name == "-") return Value("-", builtin::subtract); - if (name == "*") return Value("*", builtin::product); - if (name == "/") return Value("/", builtin::divide); - if (name == "%") return Value("%", builtin::remainder); + if (name == "+") return MlValue("+", builtin::sum); + if (name == "-") return MlValue("-", builtin::subtract); + if (name == "*") return MlValue("*", builtin::product); + if (name == "/") return MlValue("/", builtin::divide); + if (name == "%") return MlValue("%", builtin::remainder); // List operations - if (name == "list") return Value("list", builtin::list); - if (name == "insert") return Value("insert", builtin::insert); - if (name == "index") return Value("index", builtin::index); - if (name == "remove") return Value("remove", builtin::remove); + if (name == "list") return MlValue("list", builtin::list); + if (name == "insert") return MlValue("insert", builtin::insert); + if (name == "index") return MlValue("index", builtin::index); + if (name == "remove") return MlValue("remove", builtin::remove); - if (name == "len") return Value("len", builtin::len); + if (name == "len") return MlValue("len", builtin::len); - if (name == "push") return Value("push", builtin::push); - if (name == "pop") return Value("pop", builtin::pop); - if (name == "head") return Value("head", builtin::head); - if (name == "tail") return Value("tail", builtin::tail); - if (name == "first") return Value("first", builtin::head); - if (name == "last") return Value("last", builtin::pop); - if (name == "range") return Value("range", builtin::range); + if (name == "push") return MlValue("push", builtin::push); + if (name == "pop") return MlValue("pop", builtin::pop); + if (name == "head") return MlValue("head", builtin::head); + if (name == "tail") return MlValue("tail", builtin::tail); + if (name == "first") return MlValue("first", builtin::head); + if (name == "last") return MlValue("last", builtin::pop); + if (name == "range") return MlValue("range", builtin::range); // Functional operations - if (name == "map") return Value("map", builtin::map_list); - if (name == "filter") return Value("filter", builtin::filter_list); - if (name == "reduce") return Value("reduce", builtin::reduce_list); + if (name == "map") return MlValue("map", builtin::map_list); + if (name == "filter") return MlValue("filter", builtin::filter_list); + if (name == "reduce") return MlValue("reduce", builtin::reduce_list); // IO operations - #ifdef USE_STD - if (name == "exit") return Value("exit", builtin::exit); - if (name == "quit") return Value("quit", builtin::exit); - if (name == "print") return Value("print", builtin::print); - if (name == "input") return Value("input", builtin::input); - if (name == "random") return Value("random", builtin::random); - if (name == "include") return Value("include", builtin::include); - if (name == "parse-csv") return Value("parse-csv", builtin::parse_csv); - if (name == "read-file") return Value("read-file", builtin::read_file); - if (name == "write-file") return Value("write-file", builtin::write_file); - if (name == "read-url") return Value("read-url", builtin::read_url); - #endif +#ifdef USE_STD + if (name == "exit") return MlValue("exit", builtin::exit); + if (name == "quit") return MlValue("quit", builtin::exit); + if (name == "print") return MlValue("print", builtin::print); + if (name == "input") return MlValue("input", builtin::input); + if (name == "random") return MlValue("random", builtin::random); + if (name == "include") return MlValue("include", builtin::include); + if (name == "parse-csv") return MlValue("parse-csv", builtin::parse_csv); + if (name == "read-file") return MlValue("read-file", builtin::read_file); + if (name == "write-file") return MlValue("write-file", builtin::write_file); + if (name == "read-url") return MlValue("read-url", builtin::read_url); + if (name == "read-json") return MlValue("read-json", builtin::read_json); +#endif // String operations - if (name == "debug") return Value("debug", builtin::debug); - if (name == "replace") return Value("replace", builtin::replace); - if (name == "display") return Value("display", builtin::display); - + if (name == "debug") return MlValue("debug", builtin::debug); + if (name == "replace") return MlValue("replace", builtin::replace); + if (name == "display") return MlValue("display", builtin::display); + // Casting operations - if (name == "int") return Value("int", builtin::cast_to_int); - if (name == "float") return Value("float", builtin::cast_to_float); + if (name == "int") return MlValue("int", builtin::cast_to_int); + if (name == "float") return MlValue("float", builtin::cast_to_float); // Constants - if (name == "endl") return Value::string("\n"); - - std::map::const_iterator itr = defs.find(name); + if (name == "endl") return MlValue::string("\n"); + + std::map::const_iterator itr = defs.find(name); if (itr != defs.end()) return itr->second; else if (parent_scope != NULL) { itr = parent_scope->defs.find(name); @@ -1845,17 +1796,17 @@ Value Environment::get(std::string name) const { else return parent_scope->get(name); } - throw Error(Value::atom(name), *this, ATOM_NOT_DEFINED); + throw MlError(MlValue::atom(name), *this, ATOM_NOT_DEFINED); } int main(int argc, const char **argv) { - Environment env; - std::vector args; - for (int i=0; i args; + for (int i = 0; i < argc; i++) + args.push_back(MlValue::string(argv[i])); + env.set("cmd-args", MlValue(args)); - #ifdef USE_STD +#ifdef USE_STD srand(time(NULL)); try { if (argc == 1 || (argc == 2 && std::string(argv[1]) == "-i")) @@ -1865,15 +1816,15 @@ int main(int argc, const char **argv) { else if (argc == 3 && std::string(argv[1]) == "-f") run(read_file_contents(argv[2]), env); else std::cerr << "invalid arguments" << std::endl; - } catch (Error &e) { + } catch (MlError &e) { std::cerr << e.description() << std::endl; } catch (std::runtime_error &e) { std::cerr << e.what() << std::endl; } - #else +#else if (argc == 3 && std::string(argv[1]) == "-c") run(argv[2], env); - #endif +#endif return 0; } diff --git a/ml.h b/ml.h new file mode 100644 index 0000000..1e7f842 --- /dev/null +++ b/ml.h @@ -0,0 +1,210 @@ +#pragma once + +// Comment this define out to drop support for standard library functions. +// This allows the program to run without a runtime. +#define USE_STD + + +#include +#include +#include +#include +#include + + +// // Forward declaration for MlEnvironment class definition +class MlValue; + + +// An instance of a function's scope. +class MlEnvironment { +public: + // Default constructor + MlEnvironment() : parent_scope(NULL) {} + + // Does this environment, or its parent environment, + // have this atom in scope? + // This is only used to determine which atoms to capture when + // creating a lambda function. + bool has(std::string name) const; + // Get the value associated with this name in this scope + MlValue get(std::string name) const; + // Set the value associated with this name in this scope + void set(std::string name, MlValue value); + + void combine(MlEnvironment const &other); + + void set_parent_scope(MlEnvironment *parent) { + parent_scope = parent; + } + + // Output this scope in readable form to a stream. + friend std::ostream &operator<<(std::ostream &os, MlEnvironment const &v); +private: + + // The definitions in the scope. + std::map defs; + MlEnvironment *parent_scope; +}; + + + + +// An exception thrown by the lisp +class MlError { +public: + // Create an error with the value that caused the error, + // the scope where the error was found, and the message. + MlError(const MlValue &v, MlEnvironment const &env, const char *msg); + // Copy constructor is needed to prevent double frees + MlError(MlError const &other); + ~MlError(); + + // Get the printable error description. + std::string description(); +private: + MlValue *cause; + MlEnvironment env; + const char *msg; +}; + + + +// The type for a builtin function, which takes a list of values, +// and the environment to run the function in. +typedef MlValue (*Builtin)(std::vector, MlEnvironment &); + + + class MlValue { + public: + + // Constructs a unit value + MlValue(); + + // Constructs an integer + MlValue(int i); + // Constructs a floating point value + MlValue(double f); + // Constructs a list + MlValue(const std::vector &list); + + // Construct a quoted value + static MlValue quote(MlValue quoted); + + // Construct an atom + static MlValue atom(const std::string &s); + + // Construct a string + static MlValue string(const std::string &s); + + // Construct a lambda function + MlValue(const std::vector ¶ms, MlValue ret, MlEnvironment const &env); + + // Construct a builtin function + MlValue(const std::string &name, Builtin b); + + std::vector get_used_atoms(); + + // Is this a builtin function? + bool is_builtin(); + + // Apply this as a function to a list of arguments in a given environment. + MlValue apply(std::vector args, MlEnvironment &env); + // Evaluate this value as lisp code. + MlValue eval(MlEnvironment &env); + + bool is_number() const; + + // Get the "truthy" boolean value of this value. + bool as_bool() const; + + // Get this item's integer value + int as_int() const; + + // Get this item's floating point value + double as_float() const; + + // Get this item's string value + std::string as_string() const; + + // Get this item's atom value + std::string as_atom() const; + + // Get this item's list value + std::vector as_list() const; + + // Push an item to the end of this list + void push(MlValue val); + + // Push an item from the end of this list + MlValue pop(); + + + // Cast this to an integer value + MlValue cast_to_int() const; + + // Cast this to a floating point value + MlValue cast_to_float() const; + + + bool operator==(MlValue other) const; + + bool operator!=(const MlValue &other) const; + + + bool operator>=(const MlValue &other) const; + + bool operator<=(const MlValue &other) const; + + bool operator>(const MlValue &other) const; + + bool operator<(const MlValue &other) const; + + + // This function adds two lisp values, and returns the lisp value result. + MlValue operator+(const MlValue &other) const; + + // This function subtracts two lisp values, and returns the lisp value result. + MlValue operator-(const MlValue &other) const; + + // This function multiplies two lisp values, and returns the lisp value result. + MlValue operator*(const MlValue &other) const; + + // This function divides two lisp values, and returns the lisp value result. + MlValue operator/(const MlValue &other) const; + + // This function finds the remainder of two lisp values, and returns the lisp value result. + MlValue operator%(const MlValue &other) const; + + // Get the name of the type of this value + std::string get_type_name(); + + std::string display() const; + + std::string debug() const; + + friend std::ostream &operator<<(std::ostream &os, MlValue const &v); + + private: + enum { + QUOTE, + ATOM, + INT, + FLOAT, + LIST, + STRING, + LAMBDA, + BUILTIN, + UNIT + } type; + + union { + int i; + double f; + Builtin b; + } stack_data; + + std::string str; + std::vector list; + MlEnvironment lambda_scope; + }; \ No newline at end of file