#include "ml.h" #include "ml_io.h" #include "ml_date.h" #include "ml_string.h" #include "ml_util.h" #include "ml_profiler.h" #include "ml_usql.h" #include "clib/csvparser.h" #include "clib/sslclient.h" #include "clib/tcpnet.h" #include "clib/json11.h" #include "clib/printf.h" #include #include #include #include #include #include #include #include #include #include #include #define TOO_FEW_ARGS "too few arguments to function" #define TOO_MANY_ARGS "too many arguments to function" #define INVALID_ARGUMENT "invalid argument" #define INVALID_ARGUMENT_NIL "invalid nil argument" #define MISMATCHED_TYPES "mismatched types" #define CALL_NON_FUNCTION "called non-function" #define UNKNOWN_ERROR "unknown exception" #define INVALID_LAMBDA "invalid lambda" #define INVALID_BIN_OP "invalid binary operation" #define INVALID_ORDER "cannot order expression" #define BAD_CAST_INT "cannot cast as int" #define BAD_CAST_FLOAT "cannot cast as float" #define BAD_CAST_STRING "cannot cast as string" #define BAD_CAST_ATOM "cannot cast as atom" #define BAD_CAST_LIST "cannot cast as list" #define ATOM_NOT_DEFINED "atom not defined" #define INTERNAL_ERROR "internal virtual machine error" #define INDEX_OUT_OF_RANGE "index out of range" #define MALFORMED_PROGRAM "malformed program" #define NOT_IMPLEMENTED_YET_ERROR "not implemented yet" #define STRING_TYPE "string" #define INT_TYPE "int" #define FLOAT_TYPE "float" #define NIL_TYPE "nil" #define TRUE_TYPE "#t" #define FUNCTION_TYPE "function" #define ATOM_TYPE "atom" #define QUOTE_TYPE "quote" #define LIST_TYPE "list" std::mutex register_mutex; std::vector threads_register; std::mutex interpreter_mutex; // Is this character a valid lisp symbol character bool is_symbol(char ch) { return (isalpha(ch) || ispunct(ch)) && ch != '(' && ch != ')' && ch != '"' && ch != '\''; } MlValue::MlValue() : type(NIL) {} MlValue::MlValue(long i) : type(INT) { stack_data.i = i; } MlValue::MlValue(double f) : type(FLOAT) { stack_data.f = f; } MlValue::MlValue(bool b) { if (b) type = TRUE; else type = NIL; } MlValue::MlValue(const std::vector &list) : type(LIST), list(list) {} MlValue MlValue::quote(const MlValue "ed) { 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; } MlValue MlValue::nil() { return MlValue(); } // 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])); } } // 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; } // 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. return list[0].get_used_atoms(); case ATOM: // If this is an atom, add it to the list // of used atoms in this expression. result.push_back(as_atom()); return result; case LAMBDA: // If this is a lambda, get the list of used atoms in the body // of the expression. return list[1].get_used_atoms(); 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 < list.size(); i++) { // Get the atoms used in the element tmp = list[i].get_used_atoms(); // Add the used atoms to the current list of used atoms result.insert(result.end(), tmp.begin(), tmp.end()); } return result; default: return result; } } bool MlValue::is_builtin() const { return type == BUILTIN; } bool MlValue::is_number() const { return type == INT || type == FLOAT; } bool MlValue::is_string() const { return type == STRING; } bool MlValue::is_list() const { return type == LIST; } bool MlValue::as_bool() const { return type != NIL; } long MlValue::as_int() const { return cast_to_int().stack_data.i; } double MlValue::as_float() const { return cast_to_float().stack_data.f; } std::string MlValue::as_string() const { if (type != STRING) throw MlError(*this, MlEnvironment(), BAD_CAST_STRING); return str; } std::string MlValue::as_atom() const { if (type != ATOM) throw MlError(*this, MlEnvironment(), BAD_CAST_ATOM); return str; } std::vector MlValue::as_list() const { if (type != LIST) throw MlError(*this, MlEnvironment(), BAD_CAST_LIST); 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. if (type != LIST) throw MlError(*this, MlEnvironment(), MISMATCHED_TYPES); list.push_back(val); } // Push an item from the end of this list MlValue MlValue::pop() { // If this item is not a list, you cannot pop from it. if (type != LIST) throw MlError(*this, MlEnvironment(), MISMATCHED_TYPES); // 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 MlValue MlValue::cast_to_int() const { switch (type) { case INT: return *this; case FLOAT: return MlValue(long(stack_data.f)); case STRING: return MlValue(std::stol(str)); default: throw MlError(*this, MlEnvironment(), BAD_CAST_INT); } } // 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)); case STRING: return MlValue(std::stod(str)); default: throw MlError(*this, MlEnvironment(), BAD_CAST_FLOAT); } } // Cast this to a string MlValue MlValue::cast_to_string() const { switch (type) { case INT: return MlValue::string(std::to_string(stack_data.i)); case FLOAT: return MlValue::string(std::to_string(stack_data.f)); case STRING: return *this; default: throw MlError(*this, MlEnvironment(), BAD_CAST_STRING); } } 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; switch (type) { case FLOAT: return stack_data.f == other.stack_data.f; case INT: return stack_data.i == other.stack_data.i; case BUILTIN: return stack_data.b == other.stack_data.b; case STRING: case ATOM: // Both atoms and strings store their // data in the str member. return str == other.str; case LAMBDA: case LIST: // Both lambdas and lists store their // data in the list member. return list == other.list; case QUOTE: // The values for quotes are stored in the // first slot of the list member. return list[0] == other.list[0]; case NIL: return other.type == NIL; case TRUE: return other.type == TRUE; default: return true; } } bool MlValue::operator!=(const MlValue &other) const { return !(*this == other); } bool MlValue::operator>=(const MlValue &other) const { return !(*this < other); } 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; case INT: // 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 else return stack_data.i < other.stack_data.i; default: // Only allow comparisons between integers and floats throw MlError(*this, MlEnvironment(), INVALID_ORDER); } } MlValue MlValue::operator+(const MlValue &other) const { if (other.type == NIL) throw MlError(*this, MlEnvironment(), INVALID_ARGUMENT_NIL); // 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 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 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 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 MlValue result = *this; // Add each item in the other list to the end of this list for (size_t i = 0; i < other.list.size(); i++) result.push(other.list[i]); return result; } else throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); default: throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); } } MlValue MlValue::operator-(const MlValue &other) const { if (other.type == NIL) throw MlError(*this, MlEnvironment(), INVALID_ARGUMENT_NIL); // 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 one is a float, promote the other by default and do float subtraction. 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 subtraction if (other.type == FLOAT) return MlValue(cast_to_float().stack_data.f - other.stack_data.f); // Otherwise, do integer subtraction. else return MlValue(stack_data.i - other.stack_data.i); default: // This operation was done on an unsupported type throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); } } MlValue MlValue::operator*(const MlValue &other) const { if (other.type == NIL) throw MlError(*this, MlEnvironment(), INVALID_ARGUMENT_NIL); // 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: 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 product if (other.type == FLOAT) return MlValue(cast_to_float().stack_data.f * other.stack_data.f); // Otherwise, do integer multiplication. else return MlValue(stack_data.i * other.stack_data.i); default: // This operation was done on an unsupported type throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); } } MlValue MlValue::operator/(const MlValue &other) const { if (other.type == NIL) throw MlError(*this, MlEnvironment(), INVALID_ARGUMENT_NIL); // 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: 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 product if (other.type == FLOAT) return MlValue(cast_to_float().stack_data.f / other.stack_data.f); // Otherwise, do integer multiplication. else return MlValue(stack_data.i / other.stack_data.i); default: // This operation was done on an unsupported type throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); } } MlValue MlValue::operator%(const MlValue &other) const { if (other.type == NIL) throw MlError(*this, MlEnvironment(), INVALID_ARGUMENT_NIL); // 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: return MlValue(fmod(stack_data.f, other.cast_to_float().stack_data.f)); case INT: if (other.type == FLOAT) return MlValue(fmod(cast_to_float().stack_data.f, other.stack_data.f)); else return MlValue(stack_data.i % other.stack_data.i); default: // This operation was done on an unsupported type throw MlError(*this, MlEnvironment(), INVALID_BIN_OP); } } // Get the name of the type of this value std::string MlValue::get_type_name() const { switch (type) { case QUOTE: return QUOTE_TYPE; case ATOM: return ATOM_TYPE; case INT: return INT_TYPE; case FLOAT: return FLOAT_TYPE; case LIST: return LIST_TYPE; case STRING: return STRING_TYPE; case BUILTIN: case LAMBDA: // Instead of differentiating between // lambda and builtin types, we group them together. // This is because they are both callable. return FUNCTION_TYPE; case NIL: return NIL_TYPE; case TRUE: return TRUE_TYPE; default: // This should never be reached. throw MlError(*this, MlEnvironment(), INTERNAL_ERROR); } } std::string MlValue::display() const { std::string result; switch (type) { case QUOTE: return "'" + list[0].debug(); case ATOM: return str; case INT: return std::to_string(stack_data.i); case FLOAT: return std::to_string(stack_data.f); case STRING: return str; case LAMBDA: for (size_t i = 0; i < list.size(); i++) { result += list[i].debug(); if (i < list.size() - 1) result += " "; } return "(lambda " + result + ")"; case LIST: for (size_t i = 0; i < list.size(); i++) { result += list[i].debug(); if (i < list.size() - 1) result += " "; } return "(" + result + ")"; case BUILTIN: return "<" + str + " at " + std::to_string(long(stack_data.b)) + ">"; case NIL: return "nil"; case TRUE: return "#t"; default: // This should never be reached. throw MlError(*this, MlEnvironment(), INTERNAL_ERROR); } } std::string MlValue::debug() const { std::string result; switch (type) { case QUOTE: return "'" + list[0].debug(); case ATOM: return str; case INT: return std::to_string(stack_data.i); case FLOAT: return std::to_string(stack_data.f); case STRING: for (size_t i = 0; i < str.length(); i++) { if (str[i] == '"') result += "\\\""; else result.push_back(str[i]); } return "\"" + result + "\""; case LAMBDA: for (size_t i = 0; i < list.size(); i++) { result += list[i].debug(); if (i < list.size() - 1) result += " "; } return "(lambda " + result + ")"; case LIST: for (size_t i = 0; i < list.size(); i++) { result += list[i].debug(); if (i < list.size() - 1) result += " "; } return "(" + result + ")"; case BUILTIN: return "<" + str + " at " + std::to_string(long(stack_data.b)) + ">"; case NIL: return "nil"; case TRUE: return "#t"; default: // This should never be reached. throw MlError(*this, MlEnvironment(), INTERNAL_ERROR); } } std::ostream &operator<<(std::ostream &os, MlValue const &v) { return os << v.display(); } MlError::MlError(const MlValue &v, MlEnvironment const &env, const char *msg) : env(env), msg(msg) { cause = new MlValue; *cause = v; } MlError::MlError(MlError const &other) : env(other.env), msg(other.msg) { cause = new MlValue(*other.cause); } MlError::~MlError() { delete cause; } std::string MlError::description() const { // return "error: the expression `" + cause->debug() + "` failed in scope " + to_string(env) + " with message \"" + msg + "\""; return MlPerfMon::instance().callstack() + "error: the expression `" + cause->debug() + "` with message \"" + msg + "\""; } 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++) { // Iterate through the keys and assign each value. defs[itr->first] = itr->second; } } 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() << ", "; } return os << "}"; } void MlEnvironment::set(const std::string &name, MlValue value) { defs[name] = value; } void MlEnvironment::setX(const std::string &name, MlValue value) { MlEnvironment *e = this; while (e != nullptr) { std::map::const_iterator itr = e->defs.find(name); if (itr != e->defs.end()) { e->set(name, value); return; } e = e->parent_scope; } // not found so define this->set(name, value); } 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 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); // Iterate through the list of parameters and // insert the arguments into the scope. for (size_t i = 0; i < params.size(); i++) { if (params[i].type != ATOM) throw MlError(*this, env, INVALID_LAMBDA); // Set the parameter name into the scope. e.set(params[i].str, args[i]); } // Evaluate the function body with the function scope return list[1].eval(e); case BUILTIN: // Here, we call the builtin function with the current scope. // This allows us to write special forms without syntactic sugar. // For functions that are not special forms, we just evaluate // the arguments before we run the function. return (stack_data.b)(args, env); default: // We can only call lambdas and builtins throw MlError(*this, env, CALL_NON_FUNCTION); } } MlValue MlValue::eval(MlEnvironment &env) { std::vector args; MlValue function; MlValue res; MlEnvironment e; switch (type) { case QUOTE: return list[0]; case ATOM: return env.get(str); case LIST: if (list.size() < 1) return MlValue::nil(); args = std::vector(list.begin() + 1, list.end()); function = list[0].eval(env); if (function.type == BUILTIN || function.type == LAMBDA) { // Only evaluate our arguments if it's not builtin! // Builtin functions can be special forms, so we // leave them to evaluate their arguments. if (!function.is_builtin()) for (size_t i = 0; i < args.size(); i++) args[i] = args[i].eval(env); MlPerfMon::instance().add_method_call(function, args); res = function.apply(args, env); MlPerfMon::instance().end_method_call(); return res; } default: return *this; } } void skip_whitespace(std::string &s, int &ptr); void erase_comments(std::string &s, int &ptr) { while (s[ptr] == ';') { int save_ptr = ptr; while (s[save_ptr] != '\n' && save_ptr < int(s.length())) { save_ptr++; } s.erase(ptr, save_ptr - ptr); skip_whitespace(s, ptr); } } void skip_whitespace(std::string &s, int &ptr) { while (isspace(s[ptr])) { ptr++; } erase_comments(s, ptr); } // Parse a single value and increment the pointer // to the beginning of the next value to parse. MlValue parse(std::string &s, int &ptr) { skip_whitespace(s, ptr); if (s[ptr] == ';') throw std::runtime_error(INTERNAL_ERROR); if (s == "") { return MlValue(); } else if (s[ptr] == '\'') { // If this is a quote ptr++; return MlValue::quote(parse(s, ptr)); } else if (s[ptr] == '(') { // If this is a list skip_whitespace(s, ++ptr); 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 - save_ptr); skip_whitespace(s, ptr); if (n.find('.') != std::string::npos) return MlValue((negate ? -1l : 1l) * atof(n.c_str())); else return MlValue((negate ? -1l : 1l) * atol(n.c_str())); } else if (s[ptr] == '\"') { // If this is a string int n = 1; 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; 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 < x.size(); i++) { if (x[i] == '\\' && x[i + 1] == '\\') x.replace(i, 2, "\\"); else if (x[i] == '\\' && x[i + 1] == '"') x.replace(i, 2, "\""); else if (x[i] == '\\' && x[i + 1] == 'n') x.replace(i, 2, "\n"); else if (x[i] == '\\' && x[i + 1] == 't') x.replace(i, 2, "\t"); } return MlValue::string(x); } else if (s[ptr] == '@') { ptr++; skip_whitespace(s, ptr); return MlValue(); } else if (is_symbol(s[ptr])) { // If this is a string int n = 0; while (is_symbol(s[ptr + n]) || isdigit(s[ptr + n])) { n++; } std::string x = s.substr(ptr, n); ptr += n; skip_whitespace(s, ptr); if (x == "nil") return MlValue::nil(); else if(x == "#f") return MlValue(false); else if (x == "#t") return MlValue(true); else return MlValue::atom(x); } else { throw std::runtime_error(MALFORMED_PROGRAM); } } // Parse an entire program and get its list of expressions. 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)) { // Parse another expression and add it to the list. last_i = i; result.push_back(parse(s, i)); } // If the whole string wasn't parsed, the program must be bad. if (i < int(s.length())) throw std::runtime_error(MALFORMED_PROGRAM); // Return the list of values parsed. return result; } // Execute code in an environment MlValue run(const std::string &code, MlEnvironment &env) { // Parse the code std::vector parsed = parse(code); if (parsed.empty()) return MlValue::nil(); // Iterate over the expressions and evaluate them // in this environment. for (size_t i = 0; i < parsed.size() - 1; i++) parsed[i].eval(env); // Return the result of the last expression. return parsed[parsed.size() - 1].eval(env); } // This namespace contains all the definitions of builtin functions namespace builtin { // This function is NOT a builtin function, but it is used // by almost all of them. // // Special forms are just builtin functions that don't evaluate // their arguments. To make a regular builtin that evaluates its // arguments, we just call this function in our builtin definition. void eval_args(std::vector &args, MlEnvironment &env) { for (size_t i = 0; i < args.size(); i++) args[i] = args[i].eval(env); } // Create a lambda function (SPECIAL FORM) MlValue lambda(std::vector args, MlEnvironment &env) { if (args.size() < 2) throw MlError(MlValue("lambda", lambda), env, TOO_FEW_ARGS); if (args[0].get_type_name() != LIST_TYPE) throw MlError(MlValue("lambda", lambda), env, INVALID_LAMBDA); return MlValue(args[0].as_list(), args[1], env); } // if-else (SPECIAL FORM) MlValue if_then_else(std::vector args, MlEnvironment &env) { if (args.size() != 2 && args.size() != 3) 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 if (args.size() == 3) return args[2].eval(env); return MlValue(0l); } // cond (SPECIAL FORM), in lisp it's macro, but we don't have support for macros yet MlValue cond(std::vector args, MlEnvironment &env) { if (args.size() < 2) throw MlError(MlValue("cond", cond), env, TOO_FEW_ARGS); MlValue acc; for (auto &arg : args) { if (!arg.is_list()) throw MlError(MlValue("cond", cond), env, INVALID_ARGUMENT); std::vector list = arg.as_list(); if (list[0].eval(env).as_bool()) { for (size_t i = 1; i < list.size(); i++) acc = list[i].eval(env); return acc; } } return MlValue::nil(); } // Define a variable with a value (SPECIAL FORM) MlValue define(std::vector args, MlEnvironment &env) { if (args.size() != 2) 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) MlValue defun(std::vector args, MlEnvironment &env) { if (args.size() != 3) throw MlError(MlValue("defun", defun), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args[1].get_type_name() != LIST_TYPE) throw MlError(MlValue("defun", defun), env, INVALID_LAMBDA); MlValue f = MlValue(args[1].as_list(), args[2], env); env.set(args[0].display(), f); return f; } // Sets (if exists) or define a variable with a value (SPECIAL FORM) MlValue setx(std::vector args, MlEnvironment &env) { if (args.size() != 2) throw MlError(MlValue("set!", define), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); MlValue result = args[1].eval(env); env.setX(args[0].display(), result); return result; } // Loop over a list of expressions with a condition (SPECIAL FORM) MlValue while_loop(std::vector args, MlEnvironment &env) { MlValue acc; while (args[0].eval(env).as_bool()) { for (size_t i = 1; i < args.size() - 1; i++) args[i].eval(env); acc = args[args.size() - 1].eval(env); } return acc; } // Iterate through a list of values in a list (SPECIAL FORM) 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 < list.size(); i++) { env.set(args[0].as_atom(), list[i]); for (size_t j = 2; j < args.size() - 1; j++) args[j].eval(env); acc = args[args.size() - 1].eval(env); } return acc; } // Evaluate a block of expressions in the current environment (SPECIAL FORM) MlValue do_block(std::vector args, MlEnvironment &env) { MlValue acc; for (auto &arg : args) acc = arg.eval(env); return acc; } // Evaluate a block of expressions in a new environment (SPECIAL FORM) MlValue scope(std::vector args, MlEnvironment &env) { MlEnvironment e = env; MlValue acc; for (auto &arg : args) acc = arg.eval(e); return acc; } // Quote an expression (SPECIAL FORM) MlValue quote(std::vector args, MlEnvironment &env) { std::vector v; for (const auto &arg : args) v.push_back(arg); return MlValue(v); } // Evaluate logical and on a list of expressions (SPECIAL FORM) MlValue do_and(std::vector args, MlEnvironment &env) { if (args.size() < 2) throw MlError(MlValue("and", do_and), env, TOO_FEW_ARGS); for (size_t i = 0; i < args.size(); i++) if (!args[i].eval(env).as_bool()) return MlValue::nil(); return MlValue{true}; } // Evaluate logical or on a list of expressions (SPECIAL FORM) MlValue do_or(std::vector args, MlEnvironment &env) { if (args.size() < 2) throw MlError(MlValue("or", do_or), env, TOO_FEW_ARGS); for (size_t i = 0; i < args.size(); i++) if (args[i].eval(env).as_bool()) return MlValue{true}; return MlValue::nil(); } // Exit the program with an integer code MlValue exit(std::vector args, MlEnvironment &env) { // Is not a special form, so we can evaluate our args. eval_args(args, env); std::lock_guard lockGuard(register_mutex); for(auto &t : threads_register) t.detach(); std::exit(args.size() < 1 ? 0 : args[0].cast_to_int().as_int()); return MlValue(); // will not be called :-) } // Print several values and return the last one MlValue print(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() < 1) throw MlError(MlValue("print", print), env, TOO_FEW_ARGS); MlValue acc; for (size_t i = 0; i < args.size(); i++) { acc = args[i]; std::cout << acc.display(); if (i < args.size() - 1) std::cout << " "; } std::cout << std::endl; return acc; } // Get user input with an optional prompt MlValue input(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() > 1) 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 MlValue::string(s); } // Get a random number between two numbers inclusively MlValue random(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("random", random), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); long low = args[0].as_int(), high = args[1].as_int(); return MlValue(rand() % (high - low + 1) + low); } // 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 MlError(MlValue("parse-csv", parse_csv), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); CsvParser csv(true); return csv.parseCSV(args[0].as_string()); } // Get the contents of a file MlValue read_file(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("read-file", read_file), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(read_file_contents(args[0].as_string())); } // Reads file line by line and call passed lambda MlValue read_file_lines(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("read-file-lines", read_file_lines), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); // TODO check args[1].is_lambda long lines_nr = 0; std::vector lambda_par{MlValue::nil()}; std::ifstream file(args[0].as_string()); if (file.is_open()) { std::string line; while (std::getline(file, line)) { lines_nr++; lambda_par[0] = MlValue::string(line); args[1].apply(lambda_par, env); } file.close(); } return MlValue{lines_nr}; } // Write a string to a file MlValue write_file(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("write-file", write_file), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(write_file_contents(args[0].as_string(), args[1].as_string())); } // Read URL to (code content) MlValue read_url(std::vector args, MlEnvironment &env) { eval_args(args, env); // PERF optimize it for memory usage and performance if (args.size() < 1 || args.size() > 2) throw MlError(MlValue("read_url", read_url), env, args.size() < 1 ? TOO_FEW_ARGS : TOO_MANY_ARGS); std::unordered_map headers = {}; HttpClient client; if (args.size() == 2) { for (const auto &hdr_val_pair: args[1].as_list()) { // TODO check its 2 string elements list const auto &pair = hdr_val_pair.as_list(); headers[pair[0].as_string()] = pair[1].as_string(); } } std::pair result = client.doGetRequest(args[0].as_string(), headers); return std::vector{MlValue(result.first), MlValue::string(result.second)}; } // Parse JSON string MlValue parse_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("parse-json", parse_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(); } // Get current time as secs from epoch MlValue get_universal_time(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 0) throw MlError(MlValue("get-universal-time", get_universal_time), env, TOO_MANY_ARGS); return MlValue(now()); } // Get offsets in secs between local timezone and gmt MlValue get_localtime_offset(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 0) throw MlError(MlValue("get-localtime-offset", get_localtime_offset), env, TOO_MANY_ARGS); return MlValue(get_gmt_localtime_offset()); } // Converts date to formated string. MlValue date_to_str(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("date_to_str", date_to_str), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(date_to_string(args[0].as_int(), args[1].as_string())); } // Converst string to time of secs since epoch MlValue str_to_date(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("str-to-date", str_to_date), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(string_to_date(args[0].as_string(), args[1].as_string())); } // Add number of units to date. A unit is one of 'year', 'month', 'day', 'hour', 'minute' or 'second' MlValue date_add(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("date-add", date_add), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(add_to_date(args[0].as_int(), args[1].as_int(), args[2].as_string())); } // Execute system command MlValue system_cmd(std::vector args, MlEnvironment &env) { eval_args(args, env); // TODO add support for more params constructing options as one string if (args.size() != 1) throw MlError(MlValue("system-cmd", system_cmd), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return exec_system_cmd(args[0].as_string()); } // list directory MlValue ls_dir(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("ls-dir", ls_dir), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return list_dir(args[0].as_string()); } // is_path file MlValue is_file(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("is-file?", is_file), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(is_path_file(args[0].as_string())); } // is_path directory MlValue is_dir(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("is-dir?", is_dir), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(is_path_dir(args[0].as_string())); } MlValue tcp_server(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("tcp-server", tcp_server), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); auto proccess_req = [&args, &env](const std::string &str) -> std::pair { std::vector tmp {MlValue::string(str)}; MlValue result = args[1].apply(tmp, env); // TODO more robust handling if (result.is_list() && result.as_list().size() >= 2) { std::vector list = result.as_list(); return std::make_pair(list[0].as_bool(), list[1].as_string()); } else { return std::make_pair(false, result.as_string()); } }; TcpNet server; int r = server.server(args[0].as_int(), proccess_req); return MlValue((long)r); } MlValue tcp_client(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("tcp-client", tcp_client), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); TcpNet tcpclient; std::string response = tcpclient.client(args[0].as_string(), args[1].as_int(), args[2].as_string()); return MlValue::string(response); } // Read a file and execute its code 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 MlError(MlValue("include", include), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); MlEnvironment e; MlValue result = run(read_file_contents(args[0].as_string()), e); env.combine(e); return result; } // Evaluate a value as code MlValue eval(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) 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 MlValue list(std::vector args, MlEnvironment &env) { eval_args(args, env); return MlValue(args); } // Sum multiple values MlValue sum(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() < 2) throw MlError(MlValue("+", sum), env, TOO_FEW_ARGS); MlValue acc = args[0]; for (size_t i = 1; i < args.size(); i++) acc = acc + args[i]; return acc; } // Subtract two values MlValue subtract(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("-", subtract), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0] - args[1]; } // Multiply several values MlValue product(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() < 2) throw MlError(MlValue("*", product), env, TOO_FEW_ARGS); MlValue acc = args[0]; for (size_t i = 1; i < args.size(); i++) acc = acc * args[i]; return acc; } // Divide two values MlValue divide(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("/", divide), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if ((args[1].get_type_name() == "int" && args[1] == 0l) || (args[1].get_type_name() == "float" && args[1] == 0.0)) throw std::invalid_argument("divide by zero."); return args[0] / args[1]; } // Get the remainder of values MlValue remainder(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("%", remainder), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0] % args[1]; } // Are two values equal? MlValue eq(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("=", eq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(args[0] == args[1]); } // Are two values not equal? MlValue neq(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("!=", neq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(args[0] != args[1]); } // Is one number greater than another? MlValue greater(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue(">", greater), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(args[0] > args[1]); } // Is one number less than another? MlValue less(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("<", less), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(args[0] < args[1]); } // Is one number greater than or equal to another? MlValue greater_eq(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue(">=", greater_eq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(args[0] >= args[1]); } // Is one number less than or equal to another? MlValue less_eq(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("<=", less_eq), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(args[0] <= args[1]); } // Get the type name of a value MlValue get_type_name(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("type", get_type_name), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(args[0].get_type_name()); } // Cast an item to a float MlValue cast_to_float(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) 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 MlValue cast_to_int(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue(INT_TYPE, cast_to_int), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0].cast_to_int(); } // Cast an item to a string MlValue cast_to_string(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue(STRING_TYPE, cast_to_string), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0].cast_to_string(); } // Index a list MlValue index(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("index", index), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector list = args[0].as_list(); long i = args[1].as_int(); if (list.empty() || i >= list.size()) throw MlError(list, env, INDEX_OUT_OF_RANGE); return list[i]; } // Insert a value into a list MlValue insert(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("insert", insert), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector list = args[0].as_list(); long i = args[1].as_int(); if (i > list.size()) throw MlError(list, env, INDEX_OUT_OF_RANGE); list.insert(list.begin() + args[1].as_int(), args[2]); return MlValue(list); } // Remove a value at an index from a list MlValue remove(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("remove", remove), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector list = args[0].as_list(); long i = args[1].as_int(); if (list.empty() || i >= list.size()) throw MlError(list, env, INDEX_OUT_OF_RANGE); list.erase(list.begin() + i); return MlValue(list); } // Get the length of a list MlValue len(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("len", len), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(long(args[0].as_list().size())); } // Add an item to the end of a list MlValue push(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() == 0) throw MlError(MlValue("push", push), env, TOO_FEW_ARGS); for (size_t i = 1; i < args.size(); i++) args[0].push(args[i]); return args[0]; } MlValue pop(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("pop", pop), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return args[0].pop(); } MlValue head(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) 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 MlError(MlValue("head", head), env, INDEX_OUT_OF_RANGE); return list[0]; } MlValue tail(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("tail", tail), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector result, list = args[0].as_list(); for (size_t i = 1; i < list.size(); i++) result.push_back(list[i]); return MlValue(result); } MlValue second(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("second", second), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector list = args[0].as_list(); if (list.empty() || list.size() < 2) throw MlError(MlValue("second", second), env, INDEX_OUT_OF_RANGE); return list[1]; } MlValue parse(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("parse", parse), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args[0].get_type_name() != STRING_TYPE) throw MlError(args[0], env, INVALID_ARGUMENT); std::vector parsed = ::parse(args[0].as_string()); return MlValue(parsed); } // Replace a substring with a replacement string in a source string MlValue string_replace(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("string-replace", string_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 MlValue::string(src); } // Replace a substring regexp with a replacement string in a source string MlValue string_replace_re(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("string-replace-re", string_replace_re), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(replace_substring_regexp(args[0].as_string(), args[1].as_string(), args[2].as_string())); } // Returns true if where contains regex MlValue string_regex(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) // if (args.size() < 2 || args.size() > 3) throw MlError(MlValue("string-regex?", string_regex), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue(regexp_search(args[0].as_string(), args[1].as_string())); } // Returns found substrings of a regex MlValue string_regex_list(std::vector args, MlEnvironment &env) { eval_args(args, env); bool match_mode = true; bool ignore_case = false; if (args.size() < 2 && args.size() > 4) throw MlError(MlValue("string-regex-list", string_regex_list), env, args.size() > 4 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args.size() >= 3) match_mode = args[2].as_string() == "match"; if (args.size() == 4) ignore_case = args[3].as_string() == "ignore"; auto found_matches = regexp_search2(args[0].as_string(), args[1].as_string(), match_mode, ignore_case); std::vector list; for(auto &l : found_matches) { std::vector sublist; for(auto &v : l) { sublist.push_back(MlValue::string(v)); } list.push_back(sublist); } return MlValue(list); } // Splits string by regexp and returns list containing splited parts MlValue string_split(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("string-split", string_split), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); // TODO do it more efficient std::vector elements = regexp_strsplit(args[0].as_string(), args[1].as_string()); std::vector result{}; for (size_t i = 0; i < elements.size(); i++) result.push_back(MlValue::string(elements[i])); return MlValue(result); } // converts string to upper or lower case MlValue string_case(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("string-case", string_case), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(string_lucase(args[0].as_string(), args[1].as_string())); } MlValue string_len(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("string-len", string_len), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue{(long) args[0].as_string().size()}; } MlValue string_substr(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() < 1 || args.size() > 3) throw MlError(MlValue("string-substr", string_substr), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); const std::string &str = args[0].as_string(); long pos = args.size() > 1 ? args[1].as_int() : 0; long count = args.size() > 2 ? args[2].as_int() : str.size(); return MlValue::string(string_substring(str, pos, count)); } MlValue string_find(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() < 2 || args.size() > 3) throw MlError(MlValue("string-find", string_find), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); size_t start_pos = args.size() > 2 ? args[2].as_int() : 0; size_t pos = string_find_substr(args[0].as_string(), args[1].as_string(), start_pos); return pos == -1 ? MlValue::nil() : MlValue((long) pos); } // trims characters " \n\r\t" from left or right or both ends of a string MlValue string_rltrim(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("string_rltrim", string_rltrim), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(string_trim(args[0].as_string(), args[1].as_string(), args[2].as_string())); } MlValue string_pad(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 4) throw MlError(MlValue("string_pad", string_pad), env, args.size() > 4 ? TOO_MANY_ARGS : TOO_FEW_ARGS); // TODO validate len > 0 etc return MlValue::string(string_padd(args[0].as_string(), args[1].as_int(), args[2].as_string()[0], (args[3].as_string() == "rpad"))); } MlValue display(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("display", display), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(args[0].display()); } MlValue debug(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("debug", debug), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(args[0].debug()); } MlValue sprintf(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() < 1 || args.size() > 2) throw MlError(MlValue("sprintf", sprintf), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string( mini_sprintf(args[0].as_string(), args.size() == 2 ? args[1].as_list() : std::vector{})); } // >>> (map (lambda (x) (+ x 10)) '(1 2 3 4 5 6)) // => (11 12 13 14 15 16) MlValue map_list(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("map_list", map_list), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector 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 MlValue(result); } //>>> (filter (lambda (x) (> x 2)) '(1 2 3 4 5)) // => (3 4 5) MlValue filter_list(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("filter_list", filter_list), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector 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 MlValue(result); } // >>> (reduce (lambda (x y) (+ (* x 10) y)) 0 '(1 2 3 4)) // => 1234 MlValue reduce_list(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 3) throw MlError(MlValue("reduce_list", reduce_list), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector 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); tmp.clear(); } return acc; } // >>> (range 1 5) // => (1 2 3 4) MlValue range(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 2) throw MlError(MlValue("range", range), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::vector result; MlValue low = args[0], high = args[1]; if (low.get_type_name() != INT_TYPE && low.get_type_name() != FLOAT_TYPE) throw MlError(low, env, MISMATCHED_TYPES); if (high.get_type_name() != INT_TYPE && high.get_type_name() != FLOAT_TYPE) throw MlError(high, env, MISMATCHED_TYPES); if (low >= high) return MlValue(result); while (low < high) { result.push_back(low); low = low + MlValue(1l); } return MlValue(result); } // Benchmarks a block of expressions in the current environment (SPECIAL FORM) MlValue benchmark(std::vector args, MlEnvironment &env) { // TODO add some memory stats using namespace std::chrono; high_resolution_clock::time_point t1 = high_resolution_clock::now(); MlValue acc; for (size_t i = 1; i < args.size(); i++) acc = args[i].eval(env); high_resolution_clock::time_point t2 = high_resolution_clock::now(); duration time_span = t2 - t1; std::cerr << args[0].as_string() << " " << time_span.count() << " ms" << std::endl; return acc; } MlValue thread_create(std::vector args, MlEnvironment &env) { auto functor = [](std::vector args, MlEnvironment &env) -> void { try { for (size_t i = 0; i < args.size(); i++) MlValue acc = args[i].eval(env); } catch (std::exception &e) { std::cerr << "thread exception: " << e.what() << std::endl; throw e; } }; std::thread th(functor, args, std::ref(env)); auto th_id = th.get_id(); std::lock_guard lockGuard(register_mutex); threads_register.push_back(std::move(th)); static_assert(sizeof(std::thread::id) == sizeof(uint64_t), "size of thead::id is not equal to the size of uint_64"); uint64_t *ptr = (uint64_t *) &th_id; long tid = (*ptr); return MlValue(tid); } MlValue thread_under_lock(std::vector args, MlEnvironment &env) { if (args.size() != 2) throw MlError(MlValue("thread_under_lock", thread_under_lock), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); if (args[0].as_string() != "ilock") throw MlError(MlValue("thread_under_lock", thread_under_lock), env, UNKNOWN_ERROR); std::lock_guard lockGuard(interpreter_mutex); MlValue acc; for (size_t i = 0; i < args.size(); i++) acc = args[i].eval(env); return acc; } MlValue thread_sleep(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("thread-sleep", thread_sleep), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::this_thread::sleep_for(std::chrono::milliseconds(args[0].as_int())); return args[0]; } MlValue threads_join(std::vector args, MlEnvironment &env) { if (args.size() != 0) throw MlError(MlValue("threads-join", threads_join), env, TOO_MANY_ARGS); // here is a question about using lockGuard, when used it holds lockGuard locked until // threads do not leave join // std::lock_guard lockGuard(register_mutex); for (auto &th : threads_register) if (th.joinable()) th.join(); return MlValue::nil(); } MlValue try_block(std::vector args, MlEnvironment &env) { if (args.size() < 2 || args.size() > 3) throw MlError(MlValue("try", try_block), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); MlValue value; size_t cs_posisition; // unroll stack position to state when try begun to prevent "forgotten" entries in case of exception // try block try { cs_posisition = MlPerfMon::instance().get_callstack_position(); value = args[0].eval(env); // catch block } catch (std::exception &e) { MlPerfMon::instance().restore_callstack_position(cs_posisition); env.set("ml-exception", MlValue::string(e.what())); value = args[1].eval(env); } // finally block if (args.size() == 3) { args[2].eval(env); } return value; } MlValue throw_exception(std::vector args, MlEnvironment &env) { if (args.size() != 1) throw MlError(MlValue("throw", throw_exception), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); throw std::runtime_error(args[0].as_string()); } MlValue usql(std::vector args, MlEnvironment &env) { eval_args(args, env); if (args.size() != 1) throw MlError(MlValue("usql", throw_exception), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return uSQL::instance().execute(args[0].as_string()); } } // namespace builtin void load_std_lib(MlEnvironment &env) { run(STDLIB_LOADER, env); } // Does this environment, or its parent environment, have a variable? bool MlEnvironment::has(const std::string &name) const { // Find the value in the map std::map::const_iterator itr = defs.find(name); if (itr != defs.end()) // If it was found return true; else if (parent_scope != nullptr) // If it was not found in the current environment, // try to find it in the parent environment return parent_scope->has(name); else return false; } // Get the value associated with this name in this scope MlValue MlEnvironment::get(const std::string &name) const { // Special forms if (name == "define") return MlValue("define", builtin::define); if (name == "set!") return MlValue("set!", builtin::setx); if (name == "if") return MlValue("if", builtin::if_then_else); if (name == "cond") return MlValue("if", builtin::cond); if (name == "do") return MlValue("do", builtin::do_block); 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 == "lambda") return MlValue("lambda", builtin::lambda); if (name == "and") return MlValue("and", builtin::do_and); if (name == "or") return MlValue("or", builtin::do_or); // Comparison operations 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); // Meta operations 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); // Arithmetic operations 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 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 MlValue("len", builtin::len); 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 == "second") return MlValue("second", builtin::second); if (name == "last") return MlValue("last", builtin::pop); if (name == "range") return MlValue("range", builtin::range); // Functional operations 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 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 == "read-file") return MlValue("read-file", builtin::read_file); if (name == "read-file-lines") return MlValue("read-file-lines", builtin::read_file_lines); if (name == "write-file") return MlValue("write-file", builtin::write_file); if (name == "read-url") return MlValue("read-url", builtin::read_url); if (name == "system-cmd") return MlValue("system-cmd", builtin::system_cmd); if (name == "ls-dir") return MlValue("ls-dir", builtin::ls_dir); if (name == "is-file?") return MlValue("is-file?", builtin::is_file); if (name == "is-dir?") return MlValue("is-dir?", builtin::is_dir); if (name == "tcp-server") return MlValue("tcp-client", builtin::tcp_server); if (name == "tcp-client") return MlValue("tcp-client", builtin::tcp_client); // parsing operations if (name == "parse-csv") return MlValue("parse-csv", builtin::parse_csv); if (name == "parse-json") return MlValue("parse-json", builtin::parse_json); // Datetime operations if (name == "get-universal-time") return MlValue("get-universal-time", builtin::get_universal_time); if (name == "get-localtime-offset") return MlValue("get-localtime-offset", builtin::get_localtime_offset); if (name == "date-to-str") return MlValue("date-to-str", builtin::date_to_str); if (name == "str-to-date") return MlValue("str-to-date", builtin::str_to_date); if (name == "date-add") return MlValue("date-add", builtin::date_add); // String operations if (name == "debug") return MlValue("debug", builtin::debug); if (name == "sprintf") return MlValue("sprintf", builtin::sprintf); if (name == "display") return MlValue("display", builtin::display); if (name == "string-replace") return MlValue("string-replace", builtin::string_replace); if (name == "string-replace-re") return MlValue("string-replace-re", builtin::string_replace_re); if (name == "string-regex?") return MlValue("string-regex?", builtin::string_regex); if (name == "string-regex-list") return MlValue("string-regex-list", builtin::string_regex_list); if (name == "string-split") return MlValue("string-split", builtin::string_split); if (name == "string-pad") return MlValue("string-pad", builtin::string_pad); if (name == "string-rltrim") return MlValue("string-rltrim", builtin::string_rltrim); if (name == "string-case") return MlValue("string-case", builtin::string_case); if (name == "string-len") return MlValue("string-len", builtin::string_len); if (name == "string-substr") return MlValue("string-substr", builtin::string_substr); if (name == "string-find") return MlValue("string-find", builtin::string_find); // Casting operations if (name == "int") return MlValue("int", builtin::cast_to_int); if (name == "float") return MlValue("float", builtin::cast_to_float); if (name == "string") return MlValue("string", builtin::cast_to_string); // Other special forms if (name == "benchmark") return MlValue("benchmark", builtin::benchmark); // Threading operations if (name == "thread-create") return MlValue("thread-create", builtin::thread_create); if (name == "thread-under-lock") return MlValue("thread-under-lock", builtin::thread_under_lock); if (name == "thread-sleep") return MlValue("thread-sleep", builtin::thread_sleep); if (name == "threads-join") return MlValue("threads-join", builtin::threads_join); // Exceptions if (name == "try") return MlValue("try", builtin::try_block); if (name == "throw") return MlValue("throw", builtin::throw_exception); // Usql if (name == "usql") return MlValue("usql", builtin::usql); std::map::const_iterator itr = defs.find(name); if (itr != defs.end()) return itr->second; else if (parent_scope != nullptr) { itr = parent_scope->defs.find(name); if (itr != parent_scope->defs.end()) return itr->second; else return parent_scope->get(name); } throw MlError(MlValue::atom(name), *this, ATOM_NOT_DEFINED); } // Get vector of executables in this scope std::vector MlEnvironment::get_lambdas_list() const { std::vector lambdas{128}; for (auto it = defs.begin(); it != defs.end(); it++) { if (it->second.get_type_name() == FUNCTION_TYPE) { lambdas.push_back(it->first); } } return lambdas; } void repl(MlEnvironment &env) { std::string code; std::string input; MlValue tmp; std::vector parsed; setup_linenoise(env); while (true) { char *line = linenoise(">>> "); if (line == nullptr) break; linenoise_line_read(line); input = std::string(line); if (input == "!quit" || input == "!q") break; else if (input == "!env" || input == "!e") std::cout << env << std::endl; else if (input == "!export" || input == "!x") { std::cout << "File to export to: "; std::getline(std::cin, input); write_file_contents(input, code); } else if (input != "") { try { tmp = run(input, env); std::cout << " => " << tmp.debug() << std::endl; code += input + "\n"; } catch (MlError &e) { std::cerr << e.description() << std::endl; MlPerfMon::instance().clear_callstack(); } catch (std::exception &e) { std::cerr << e.what() << std::endl; } } } close_linenoise(); } bool cmdOptionExists(char **begin, char **end, const std::string &option) { return std::find(begin, end, option) != end; } std::vector getCmdOption(char *argv[], int argc, const std::string &option) { std::vector tokens; for (int i = 1; i < argc; ++i) { if (option == argv[i] && i + 1 < argc) { i++; tokens.push_back(std::string(argv[i])); } } return tokens; } int main(int argc, char *argv[]) { MlEnvironment env; std::vector args; for (int i = 0; i < argc; i++) args.push_back(MlValue::string(argv[i])); env.set("cmd-args", MlValue(args)); srand(time(NULL)); try { // performance monitor on if (cmdOptionExists(argv, argv + argc, "-p")) MlPerfMon::instance().turnOn(); // better stacktrace if (cmdOptionExists(argv, argv + argc, "-d")) MlPerfMon::instance().debugOn(); // skip loading std lib if (!cmdOptionExists(argv, argv + argc, "-b")) { load_std_lib(env); } // help if (cmdOptionExists(argv, argv + argc, "-h")) { std::cout << "Usage:\n\t-h print this help\n\t-b skip stdlib loading\n\t-c code - runs code passed on command line\n\t-f source_file - executes code in file\n\t-i runs repl\n\t-run used for shebang\n\t-p prints profile info at the end\n\t-d better stacktrace when exception\n\t-v prints version string\n\n"; return 0; } // version if (cmdOptionExists(argv, argv + argc, "-v")) { std::cout << VERSION << std::endl; return 0; } // passed code if (cmdOptionExists(argv, argv + argc, "-c")) { std::vector codes = getCmdOption(argv, argc, "-c"); for (size_t i = 0; i < codes.size(); i++) run(codes[i], env); // run files } else if (cmdOptionExists(argv, argv + argc, "-f")) { for (auto & file : getCmdOption(argv, argc, "-f")) run(read_file_contents(file), env); // sheebang } else if (cmdOptionExists(argv, argv + argc, "-run")) { for (auto & file : getCmdOption(argv, argc, "-run")) { // TODO check only one file is specified ?? std::string file_content = read_file_contents(file); if (file_content.find("#!") == 0) // shebang ? file_content.erase(0, file_content.find("\n") + 1); // TODO mac osx newline?? run(file_content, env); } // repl } else { repl(env); } MlPerfMon::instance().print_results(); return 0; } catch (MlError &e) { std::cerr << e.description() << std::endl; } catch (const std::exception &e) { std::cerr << MlPerfMon::instance().callstack() << e.what() << std::endl; MlPerfMon::instance().clear_callstack(); } return 1; }