2519 lines
73 KiB
C++
2519 lines
73 KiB
C++
|
|
#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 <cmath>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <sstream>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <ctime>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <numeric>
|
|
|
|
|
|
#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 DIVISION_BY_ZERO "division by zero"
|
|
#define INVALID_HEADER_FORMAT "invalid header format"
|
|
|
|
|
|
#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<std::thread> 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(std::vector<MlValue> list) : type(LIST), list(std::move(list)) {}
|
|
|
|
MlValue::MlValue(const std::vector<std::string> &slist) : type(LIST) {
|
|
list.reserve(slist.size());
|
|
for (const auto & item : slist)
|
|
list.push_back(MlValue::string(item));
|
|
}
|
|
|
|
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<MlValue> ¶ms, MlValue ret, MlEnvironment const &env, const Type ftype) : type(ftype) {
|
|
// We store the params and the result in the list member
|
|
// instead of having dedicated members. This is to save memory.
|
|
list.emplace_back(params);
|
|
list.push_back(ret);
|
|
|
|
// Lambdas capture only variables that they know they will use.
|
|
std::vector<std::string> used_atoms = ret.get_used_atoms();
|
|
for (const auto & used_atom : used_atoms) {
|
|
// If the environment has a symbol that this lambda uses, capture it.
|
|
if (env.has(used_atom))
|
|
lambda_scope.set(used_atom, env.get(used_atom));
|
|
}
|
|
}
|
|
|
|
// 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<std::string> MlValue::get_used_atoms() {
|
|
std::vector<std::string> 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:
|
|
case MACRO:
|
|
// If this is a lambda or macro, 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 (auto & item : list) {
|
|
// Get the atoms used in the element
|
|
tmp = item.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_nil() const {
|
|
return type == NIL;
|
|
}
|
|
|
|
bool MlValue::is_builtin() const {
|
|
return type == BUILTIN;
|
|
}
|
|
|
|
bool MlValue::is_macro() const {
|
|
return type == MACRO;
|
|
}
|
|
|
|
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 || (type == LIST && list.empty()));
|
|
}
|
|
|
|
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> 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(const 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;
|
|
case TRUE:
|
|
return MlValue::string("#t");
|
|
case NIL:
|
|
return MlValue::string("nil");
|
|
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 MACRO:
|
|
case LIST:
|
|
// All lambdas, macros 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 (const auto & item : other.list)
|
|
result.push(item);
|
|
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:
|
|
case MACRO:
|
|
// Instead of differentiating between
|
|
// lambda, macro and builtin types, we group them together.
|
|
// This is because they are all 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 {
|
|
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:
|
|
return "(lambda " + std::accumulate(list.begin(), list.end(), std::string(), [](const std::string &a, const MlValue &b) -> std::string
|
|
{return a + (a.empty() ? "" : " ") + b.debug(); }) + ")";
|
|
case MACRO:
|
|
return "(macro " + std::accumulate(list.begin(), list.end(), std::string(), [](const std::string &a, const MlValue &b) -> std::string
|
|
{return a + (a.empty() ? "" : " ") + b.debug(); }) + ")";
|
|
case LIST:
|
|
return "(" + std::accumulate(list.begin(), list.end(), std::string(), [](const std::string &a, const MlValue &b) -> std::string
|
|
{return a + (a.empty() ? "" : " ") + b.debug(); }) + ")";
|
|
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 STRING:
|
|
result.reserve(str.size());
|
|
for (char c : str) {
|
|
if (c == '"') result += "\\\"";
|
|
else result.push_back(c);
|
|
}
|
|
return "\"" + result + "\"";
|
|
default:
|
|
return display();
|
|
}
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, MlValue const &v) {
|
|
return os << v.display();
|
|
}
|
|
|
|
|
|
MlError::MlError(const MlValue &v, const MlEnvironment &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 + "\"";
|
|
}
|
|
|
|
const char * MlError::what() const noexcept {
|
|
return msg;
|
|
}
|
|
|
|
void MlEnvironment::combine(MlEnvironment const &other) {
|
|
// We need to overwrite previously declared values for keys.
|
|
for (auto itr = other.defs.begin(); itr != other.defs.end(); itr++) {
|
|
defs[itr->first] = itr->second;
|
|
}
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, MlEnvironment const &e) {
|
|
os << "{ ";
|
|
for (auto itr = e.defs.begin(); itr != e.defs.end(); itr++) {
|
|
os << '\'' << itr->first << "' : " << itr->second.debug() << ", ";
|
|
}
|
|
return os << "}";
|
|
}
|
|
|
|
void MlEnvironment::set(const std::string &name, MlValue value) {
|
|
defs[name] = std::move(value);
|
|
}
|
|
|
|
|
|
void MlEnvironment::setX(const std::string &name, const MlValue& value) {
|
|
MlEnvironment *e = this;
|
|
while (e != nullptr) {
|
|
auto 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<MlValue> args, MlEnvironment &env) {
|
|
MlEnvironment e;
|
|
std::vector<MlValue> params;
|
|
MlValue macro_eval;
|
|
|
|
switch (type) {
|
|
case LAMBDA:
|
|
case MACRO:
|
|
// 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]);
|
|
}
|
|
|
|
if (type == LAMBDA) {
|
|
// Evaluate the function body with the function scope
|
|
return list[1].eval(e);
|
|
} else {
|
|
// macro evals twice
|
|
macro_eval = list[1].eval(e);
|
|
if (MlPerfMon::instance().isDebugOn())
|
|
std::cout << std::endl << "DEBUG macro 1st eval: " << macro_eval.debug() << std::endl << std::endl;
|
|
|
|
return macro_eval.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<MlValue> args;
|
|
MlValue function;
|
|
MlValue res;
|
|
MlEnvironment e;
|
|
switch (type) {
|
|
case QUOTE:
|
|
return list[0];
|
|
case ATOM:
|
|
return env.get(str);
|
|
case LIST:
|
|
if (list.empty())
|
|
return MlValue::nil();
|
|
|
|
args = std::vector<MlValue>(list.begin() + 1, list.end());
|
|
|
|
function = list[0].eval(env);
|
|
if (function.type == BUILTIN || function.type == LAMBDA || function.type == MACRO) {
|
|
// 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() && !function.is_macro())
|
|
for (auto & arg : args)
|
|
arg = arg.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.empty()) {
|
|
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<MlValue>());
|
|
|
|
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 ? -1.0 : 1.0) * 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<MlValue> parse(std::string s) {
|
|
int i = 0, last_i = -1;
|
|
std::vector<MlValue> 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<MlValue> 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<MlValue> &args, MlEnvironment &env) {
|
|
for (auto & arg : args)
|
|
arg = arg.eval(env);
|
|
}
|
|
|
|
// Create a lambda function (SPECIAL FORM)
|
|
MlValue lambda(std::vector<MlValue> 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, MlValue::Type::LAMBDA);
|
|
}
|
|
|
|
// if-else (SPECIAL FORM)
|
|
MlValue if_then_else(std::vector<MlValue> 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::nil();
|
|
}
|
|
|
|
// cond (SPECIAL FORM), in lisp it's macro, but we don't have support for macros yet
|
|
MlValue cond(std::vector<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
if (args.size() != 2)
|
|
throw MlError(MlValue("def", 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<MlValue> args, MlEnvironment &env) {
|
|
if (args.size() != 3)
|
|
throw MlError(MlValue("defn", defun), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
if (args[1].get_type_name() != LIST_TYPE)
|
|
throw MlError(MlValue("defn", defun), env, INVALID_LAMBDA);
|
|
|
|
MlValue f = MlValue(args[1].as_list(), args[2], env, MlValue::Type::LAMBDA);
|
|
env.set(args[0].display(), f);
|
|
return f;
|
|
}
|
|
|
|
// Define a macro with parameters and a result expression (SPECIAL FORM)
|
|
MlValue defmacro(std::vector<MlValue> args, MlEnvironment &env)
|
|
{
|
|
if (args.size() != 3)
|
|
throw MlError(MlValue("defmacro", defmacro), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
if (args[1].get_type_name() != LIST_TYPE)
|
|
throw MlError(MlValue("defmacro", defmacro), env, INVALID_LAMBDA);
|
|
|
|
MlValue f = MlValue(args[1].as_list(), args[2], env, MlValue::Type::MACRO);
|
|
env.set(args[0].display(), f);
|
|
return f;
|
|
}
|
|
|
|
// Sets (if exists) or define a variable with a value (SPECIAL FORM)
|
|
MlValue setx(std::vector<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
MlValue acc;
|
|
std::vector<MlValue> list = args[1].eval(env).as_list();
|
|
|
|
for (auto & item : list) {
|
|
env.set(args[0].as_atom(), item);
|
|
|
|
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<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
std::vector<MlValue> v;
|
|
|
|
v.reserve(args.size());
|
|
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<MlValue> args, MlEnvironment &env) {
|
|
if (args.size() < 2)
|
|
throw MlError(MlValue("and", do_and), env, TOO_FEW_ARGS);
|
|
|
|
for (auto & arg : args)
|
|
if (!arg.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<MlValue> args, MlEnvironment &env) {
|
|
if (args.size() < 2)
|
|
throw MlError(MlValue("or", do_or), env, TOO_FEW_ARGS);
|
|
|
|
for (auto & arg : args)
|
|
if (arg.eval(env).as_bool()) return MlValue{true};
|
|
|
|
return MlValue::nil();
|
|
}
|
|
|
|
// Exit the program with an integer code
|
|
MlValue exit(std::vector<MlValue> args, MlEnvironment &env) {
|
|
// Is not a special form, so we can evaluate our args.
|
|
eval_args(args, env);
|
|
|
|
std::lock_guard<std::mutex> lockGuard(register_mutex);
|
|
for(auto &t : threads_register)
|
|
t.detach();
|
|
|
|
std::exit(args.empty() ? 0 : (int)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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.empty())
|
|
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<MlValue> 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 user input with an optional prompt using line noise
|
|
MlValue input2(std::vector<MlValue> args, MlEnvironment &env) {
|
|
// TODO add setup etc
|
|
eval_args(args, env);
|
|
|
|
if (args.size() > 1)
|
|
throw MlError(MlValue("input2", input2), env, TOO_MANY_ARGS);
|
|
|
|
char *line = linenoise(args.empty() ? ">>> " : args[0].as_string().c_str());
|
|
if (line == nullptr) throw std::runtime_error("date_to_string, cannot initialise linenoise");
|
|
|
|
std::string input{line};
|
|
linenoise_add_to_history(input);
|
|
|
|
|
|
return MlValue::string(input);
|
|
}
|
|
|
|
// Get a random number between two numbers inclusively
|
|
MlValue random(std::vector<MlValue> 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<MlValue> 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());
|
|
}
|
|
|
|
// Reads in the printed representation of a Lisp object from input-stream, builds a corresponding Lisp object, and returns the object
|
|
MlValue read(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 0)
|
|
throw MlError(MlValue("read", read), env, TOO_MANY_ARGS);
|
|
|
|
std::string line;
|
|
std::getline(std::cin, line);
|
|
return run(line, env);
|
|
}
|
|
|
|
// Get the contents of a file
|
|
MlValue read_file(std::vector<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
// PERF optimize it for memory usage and performance
|
|
if (args.empty() || args.size() > 4)
|
|
throw MlError(MlValue("read_url", read_url), env, args.empty() ? TOO_FEW_ARGS : TOO_MANY_ARGS);
|
|
|
|
std::string url = args[0].as_string();
|
|
std::map<std::string, std::string> headers = {};
|
|
std::string method = "GET";
|
|
std::string body;
|
|
|
|
// headers
|
|
if (args.size() > 1) {
|
|
for (const auto &hdr_val_pair: args[1].as_list()) {
|
|
const auto &pair = hdr_val_pair.as_list();
|
|
if (pair.size() != 2)
|
|
throw MlError(MlValue("read_url", read_url), env, INVALID_HEADER_FORMAT);
|
|
|
|
headers[pair[0].as_string()] = pair[1].as_string();
|
|
}
|
|
}
|
|
|
|
// body
|
|
if (args.size() > 2)
|
|
body = args[2].as_string();
|
|
|
|
// method
|
|
if (args.size() > 3)
|
|
method = args[3].as_string();
|
|
|
|
|
|
std::pair<long, std::string> result = HttpClient{}.doRequest(method, url, headers, body);
|
|
|
|
return std::vector<MlValue>{MlValue(result.first), MlValue::string(result.second)};
|
|
}
|
|
|
|
// Parse JSON string
|
|
MlValue parse_json(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
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())
|
|
return MlValue::string("ERROR json parsing: " + err);
|
|
|
|
|
|
return json.ivalualize();
|
|
}
|
|
|
|
// Get current time as secs from epoch
|
|
MlValue get_universal_time(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (!args.empty())
|
|
throw MlError(MlValue("get-universal-time", get_universal_time), env, TOO_MANY_ARGS);
|
|
|
|
return MlValue(now());
|
|
}
|
|
|
|
// Get current time as miliseconds from epoch
|
|
MlValue get_universal_time_ms(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (!args.empty())
|
|
throw MlError(MlValue("get-universal-time-ms", get_universal_time_ms), env, TOO_MANY_ARGS);
|
|
|
|
return MlValue(now_ms());
|
|
}
|
|
|
|
// Get offsets in secs between local timezone and gmt
|
|
MlValue get_localtime_offset(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (!args.empty())
|
|
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<MlValue> 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<MlValue> 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<MlValue> 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(), (int)args[1].as_int(), args[2].as_string()));
|
|
}
|
|
|
|
|
|
// Execute system command
|
|
MlValue system_cmd(std::vector<MlValue> 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());
|
|
}
|
|
|
|
// Execute system command as forked process so its independent from its parrent
|
|
MlValue system_cmd_fork(std::vector<MlValue> 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-fork", system_cmd_fork), env, TOO_FEW_ARGS);
|
|
|
|
std::vector<std::string> exec_args;
|
|
exec_args.reserve(args.size());
|
|
for (auto const& a : args)
|
|
exec_args.emplace_back(a.as_string());
|
|
|
|
return exec_system_cmd_fork(exec_args);
|
|
}
|
|
|
|
|
|
// list directory
|
|
MlValue ls_dir(std::vector<MlValue> 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<MlValue> 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<MlValue> 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 mk_dir(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 1)
|
|
throw MlError(MlValue("mk-dir", mk_dir), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
return MlValue((bool)mk_path_dir(args[0].as_string()));
|
|
}
|
|
|
|
MlValue rm_dir(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 1)
|
|
throw MlError(MlValue("rm-dir", rm_dir), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
return MlValue((bool)rm_path_dir(args[0].as_string()));
|
|
}
|
|
|
|
// starts tcp listening server
|
|
MlValue tcp_server(std::vector<MlValue> 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<bool, std::string> {
|
|
std::vector<MlValue> 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<MlValue> 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((int)args[0].as_int(), proccess_req);
|
|
return MlValue((long)r);
|
|
}
|
|
|
|
// tcp client
|
|
MlValue tcp_client(std::vector<MlValue> 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;
|
|
if (args[2].is_list()) {
|
|
auto request_list = args[2].as_list();
|
|
std::vector<std::string> requests;// PERF reserve
|
|
|
|
std::transform(request_list.begin(), request_list.end(), back_inserter(requests), std::mem_fn(&MlValue::as_string));
|
|
std::vector<std::string> response = tcpclient.client(args[0].as_string(), (int)args[1].as_int(), requests);
|
|
return MlValue(response);
|
|
} else {
|
|
std::string response = tcpclient.client(args[0].as_string(), (int)args[1].as_int(), args[2].as_string());
|
|
return MlValue::string(response);
|
|
}
|
|
}
|
|
|
|
// get environment variable
|
|
MlValue get_env(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 1)
|
|
throw MlError(MlValue("get-env", get_env), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
if (const char* env_p = std::getenv(args[0].as_string().c_str()))
|
|
return MlValue::string(env_p);
|
|
else
|
|
// TODO maybe better to return MlValue::nil();
|
|
return MlValue::string("");
|
|
}
|
|
|
|
// set environment variable
|
|
MlValue set_env(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 2)
|
|
throw MlError(MlValue("set-env", set_env), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
setenv(args[0].as_string().c_str(), args[1].as_string().c_str(), true);
|
|
|
|
return args[1];
|
|
}
|
|
|
|
// Read a file and execute its code
|
|
MlValue include(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 1)
|
|
throw MlError(MlValue("include", include), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
MlValue result = run(read_file_contents(args[0].as_string()), env);
|
|
return result;
|
|
}
|
|
|
|
|
|
// Evaluate a value as code
|
|
MlValue eval(std::vector<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
return MlValue(args);
|
|
}
|
|
|
|
// Sum multiple values
|
|
MlValue sum(std::vector<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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(DIVISION_BY_ZERO);
|
|
|
|
return args[0] / args[1];
|
|
}
|
|
|
|
// Get the remainder of values
|
|
MlValue remainder(std::vector<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.empty())
|
|
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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> result, list = args[0].as_list();
|
|
// PERF reserve result
|
|
for (size_t i = 1; i < list.size(); i++)
|
|
result.push_back(list[i]);
|
|
|
|
return MlValue(result);
|
|
}
|
|
|
|
MlValue second(std::vector<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> 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<MlValue> list;
|
|
list.reserve(found_matches.size());
|
|
for(auto &item : found_matches) {
|
|
list.emplace_back(item);
|
|
}
|
|
return MlValue(list);
|
|
}
|
|
|
|
// Splits string by regexp and returns list containing splited parts
|
|
MlValue string_split(std::vector<MlValue> 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);
|
|
|
|
std::vector<std::string> elements = regexp_strsplit(args[0].as_string(), args[1].as_string());
|
|
return MlValue(elements);
|
|
}
|
|
|
|
// converts string to upper or lower case
|
|
MlValue string_case(std::vector<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.empty() || 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;
|
|
auto count = args.size() > 2 ? args[2].as_int() : str.size();
|
|
|
|
return MlValue::string(string_substring(str, pos, count));
|
|
}
|
|
|
|
MlValue string_find(std::vector<MlValue> 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;
|
|
long pos = string_find_substr(args[0].as_string(), args[1].as_string(), start_pos);
|
|
|
|
return pos == -1 ? MlValue::nil() : MlValue((long) pos);
|
|
}
|
|
|
|
MlValue string_cmp(std::vector<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.size() != 2)
|
|
throw MlError(MlValue("string-cmp", string_cmp), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
|
|
|
if ( (args[0].is_nil() || (args[0].is_list() && args[0].as_list().empty())) ||
|
|
(args[1].is_nil() || (args[1].is_list() && args[1].as_list().empty())) )
|
|
return MlValue::nil();
|
|
|
|
|
|
return MlValue((long)args[0].as_string().compare(args[1].as_string()));
|
|
}
|
|
|
|
// trims characters " \n\r\t" from left or right or both ends of a string
|
|
MlValue string_rltrim(std::vector<MlValue> 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<MlValue> 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);
|
|
|
|
return MlValue::string(string_padd(args[0].as_string(), (size_t)args[1].as_int(), args[2].as_string()[0],
|
|
(args[3].as_string() == "rpad")));
|
|
}
|
|
|
|
MlValue display(std::vector<MlValue> 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<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, env);
|
|
|
|
if (args.empty() || 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<MlValue>{}));
|
|
}
|
|
|
|
// >>> (map (lambda (x) (+ x 10)) '(1 2 3 4 5 6))
|
|
// => (11 12 13 14 15 16)
|
|
MlValue map_list(std::vector<MlValue> 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<MlValue> result, list = args[1].as_list(), tmp;
|
|
result.reserve(list.size());
|
|
for (const auto & item : list) {
|
|
tmp.push_back(item);
|
|
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<MlValue> 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<MlValue> result, list = args[1].as_list(), tmp;
|
|
for (const auto & item : list) {
|
|
tmp.push_back(item);
|
|
if (args[0].apply(tmp, env).as_bool())
|
|
result.push_back(item);
|
|
tmp.clear();
|
|
}
|
|
return MlValue(result);
|
|
}
|
|
|
|
// >>> (reduce (lambda (x y) (+ (* x 10) y)) 0 '(1 2 3 4))
|
|
// => 1234
|
|
MlValue reduce_list(std::vector<MlValue> 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<MlValue> list = args[2].as_list(), tmp;
|
|
MlValue acc = args[1];
|
|
for (const auto & item : list) {
|
|
tmp.push_back(acc);
|
|
tmp.push_back(item);
|
|
acc = args[0].apply(tmp, env);
|
|
tmp.clear();
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
// >>> (range 1 5)
|
|
// => (1 2 3 4)
|
|
MlValue range(std::vector<MlValue> 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<MlValue> 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);
|
|
|
|
result.reserve(high.as_int() - low.as_int());
|
|
while (low < high) { // use MlValue to keep number datatype
|
|
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<MlValue> args, MlEnvironment &env) {
|
|
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<double, std::milli> time_span = t2 - t1;
|
|
|
|
std::cerr << args[0].as_string() << " " << time_span.count() << " ms" << std::endl;
|
|
|
|
return acc;
|
|
}
|
|
|
|
|
|
MlValue thread_create(std::vector<MlValue> args, MlEnvironment &env) {
|
|
auto functor = [](std::vector<MlValue> args, MlEnvironment &env) -> void {
|
|
try {
|
|
for (auto & arg : args)
|
|
MlValue acc = arg.eval(env);
|
|
} catch (const MlError &e) {
|
|
std::cerr << "thread_create exception: " << e.description() << std::endl;
|
|
throw;
|
|
} catch (const std::exception &e) {
|
|
std::cerr << "thread_create exception: " << e.what() << std::endl;
|
|
throw;
|
|
}
|
|
};
|
|
|
|
std::thread th(functor, args, std::ref(env));
|
|
auto th_id = th.get_id();
|
|
|
|
std::lock_guard<std::mutex> 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");
|
|
auto *ptr = (uint64_t *) &th_id;
|
|
long tid = (*ptr);
|
|
|
|
return MlValue(tid);
|
|
}
|
|
|
|
MlValue thread_under_lock(std::vector<MlValue> args, MlEnvironment &env) {
|
|
if (args.size() < 2)
|
|
throw MlError(MlValue("thread_under_lock", thread_under_lock), env, TOO_FEW_ARGS);
|
|
|
|
if (args[0].as_string() != "ilock")
|
|
throw MlError(MlValue("thread_under_lock", thread_under_lock), env, UNKNOWN_ERROR);
|
|
|
|
std::lock_guard<std::mutex> lockGuard(interpreter_mutex);
|
|
MlValue acc;
|
|
for (size_t i = 1; i < args.size(); i++)
|
|
acc = args[i].eval(env);
|
|
|
|
return acc;
|
|
}
|
|
|
|
MlValue thread_sleep(std::vector<MlValue> 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<MlValue> args, MlEnvironment &env) {
|
|
if (!args.empty())
|
|
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<std::mutex> lockGuard(register_mutex);
|
|
for (auto &th : threads_register)
|
|
if (th.joinable()) th.join();
|
|
|
|
return MlValue::nil();
|
|
}
|
|
|
|
|
|
MlValue try_block(std::vector<MlValue> 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 (const 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<MlValue> args, MlEnvironment &env) {
|
|
eval_args(args, 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].cast_to_string().as_string());
|
|
}
|
|
|
|
MlValue usql(std::vector<MlValue> 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
|
|
|
|
|
|
// Does this environment, or its parent environment, have a variable?
|
|
bool MlEnvironment::has(const std::string &name) const {
|
|
auto 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;
|
|
}
|
|
|
|
|
|
|
|
std::map<const std::string, Builtin> builtin_funcs
|
|
{
|
|
// Special forms
|
|
std::make_pair("def", builtin::define),
|
|
std::make_pair("lambda", builtin::lambda),
|
|
std::make_pair("if", builtin::if_then_else),
|
|
std::make_pair("cond", builtin::cond),
|
|
std::make_pair("do", builtin::do_block),
|
|
std::make_pair("for", builtin::for_loop),
|
|
std::make_pair("while", builtin::while_loop),
|
|
std::make_pair("scope", builtin::scope),
|
|
std::make_pair("quote", builtin::quote),
|
|
std::make_pair("defn", builtin::defun),
|
|
std::make_pair("defmacro", builtin::defmacro),
|
|
std::make_pair("and", builtin::do_and),
|
|
std::make_pair("or", builtin::do_or),
|
|
std::make_pair("set!", builtin::setx),
|
|
|
|
// Comparison operations
|
|
std::make_pair("=", builtin::eq),
|
|
std::make_pair("!=", builtin::neq),
|
|
std::make_pair(">", builtin::greater),
|
|
std::make_pair("<", builtin::less),
|
|
std::make_pair(">=", builtin::greater_eq),
|
|
std::make_pair("<=", builtin::less_eq),
|
|
|
|
// Meta operations
|
|
std::make_pair("eval", builtin::eval),
|
|
std::make_pair("type", builtin::get_type_name),
|
|
std::make_pair("parse", builtin::parse),
|
|
|
|
// Arithmetic operations
|
|
std::make_pair("+", builtin::sum),
|
|
std::make_pair("-", builtin::subtract),
|
|
std::make_pair("*", builtin::product),
|
|
std::make_pair("/", builtin::divide),
|
|
std::make_pair("%", builtin::remainder),
|
|
|
|
// List operations
|
|
std::make_pair("list", builtin::list),
|
|
std::make_pair("insert", builtin::insert),
|
|
std::make_pair("index", builtin::index),
|
|
std::make_pair("remove", builtin::remove),
|
|
std::make_pair("len", builtin::len),
|
|
std::make_pair("push", builtin::push),
|
|
std::make_pair("pop", builtin::pop),
|
|
std::make_pair("head", builtin::head),
|
|
std::make_pair("tail", builtin::tail),
|
|
std::make_pair("first", builtin::head),
|
|
std::make_pair("second", builtin::second),
|
|
std::make_pair("last", builtin::pop),
|
|
std::make_pair("range", builtin::range),
|
|
|
|
// Functional operations
|
|
std::make_pair("map", builtin::map_list),
|
|
std::make_pair("filter", builtin::filter_list),
|
|
std::make_pair("reduce", builtin::reduce_list),
|
|
|
|
// IO operations
|
|
std::make_pair("exit", builtin::exit),
|
|
std::make_pair("quit", builtin::exit),
|
|
std::make_pair("print", builtin::print),
|
|
std::make_pair("random", builtin::random),
|
|
std::make_pair("include", builtin::include),
|
|
std::make_pair("input", builtin::input),
|
|
std::make_pair("input2", builtin::input2),
|
|
std::make_pair("read", builtin::read),
|
|
std::make_pair("read-file", builtin::read_file),
|
|
std::make_pair("read-file-lines", builtin::read_file_lines),
|
|
std::make_pair("write-file", builtin::write_file),
|
|
std::make_pair("read-url", builtin::read_url),
|
|
std::make_pair("system-cmd", builtin::system_cmd),
|
|
std::make_pair("system-cmd-fork", builtin::system_cmd_fork),
|
|
std::make_pair("ls-dir", builtin::ls_dir),
|
|
std::make_pair("is-file?", builtin::is_file),
|
|
std::make_pair("is-dir?", builtin::is_dir),
|
|
std::make_pair("mk-dir", builtin::mk_dir),
|
|
std::make_pair("rm-dir", builtin::rm_dir),
|
|
std::make_pair("tcp-server", builtin::tcp_server),
|
|
std::make_pair("tcp-client", builtin::tcp_client),
|
|
std::make_pair("get-env", builtin::get_env),
|
|
std::make_pair("set-env", builtin::set_env),
|
|
|
|
// parsing operations
|
|
std::make_pair("parse-csv", builtin::parse_csv),
|
|
std::make_pair("parse-json", builtin::parse_json),
|
|
|
|
// Datetime operations
|
|
std::make_pair("get-universal-time", builtin::get_universal_time),
|
|
std::make_pair("get-universal-time-ms", builtin::get_universal_time_ms),
|
|
std::make_pair("get-localtime-offset", builtin::get_localtime_offset),
|
|
std::make_pair("date-to-str", builtin::date_to_str),
|
|
std::make_pair("str-to-date", builtin::str_to_date),
|
|
std::make_pair("date-add", builtin::date_add),
|
|
|
|
// String operations
|
|
std::make_pair("debug", builtin::debug),
|
|
std::make_pair("sprintf", builtin::sprintf),
|
|
std::make_pair("display", builtin::display),
|
|
std::make_pair("string-replace", builtin::string_replace),
|
|
std::make_pair("string-replace-re", builtin::string_replace_re),
|
|
std::make_pair("string-regex?", builtin::string_regex),
|
|
std::make_pair("string-regex-list", builtin::string_regex_list),
|
|
std::make_pair("string-split", builtin::string_split),
|
|
std::make_pair("string-pad", builtin::string_pad),
|
|
std::make_pair("string-rltrim", builtin::string_rltrim),
|
|
std::make_pair("string-case", builtin::string_case),
|
|
std::make_pair("string-len", builtin::string_len),
|
|
std::make_pair("string-substr", builtin::string_substr),
|
|
std::make_pair("string-find", builtin::string_find),
|
|
std::make_pair("string-cmp", builtin::string_cmp),
|
|
|
|
// Casting operations
|
|
std::make_pair("int", builtin::cast_to_int),
|
|
std::make_pair("float", builtin::cast_to_float),
|
|
std::make_pair("string", builtin::cast_to_string),
|
|
|
|
// Other special forms
|
|
std::make_pair("benchmark", builtin::benchmark),
|
|
|
|
// Threading operations
|
|
std::make_pair("thread-create", builtin::thread_create),
|
|
std::make_pair("thread-under-lock", builtin::thread_under_lock),
|
|
std::make_pair("thread-sleep", builtin::thread_sleep),
|
|
std::make_pair("threads-join", builtin::threads_join),
|
|
|
|
// Exceptions
|
|
std::make_pair("try", builtin::try_block),
|
|
std::make_pair("throw", builtin::throw_exception),
|
|
|
|
// Usql
|
|
std::make_pair("usql", builtin::usql)
|
|
};
|
|
|
|
|
|
// Get the value associated with this name in this scope
|
|
MlValue MlEnvironment::get(const std::string &name) const {
|
|
// PERF, here can be a few of for fast access
|
|
if (name == "def") return MlValue("def", builtin::define);
|
|
if (name == "if") return MlValue("if", builtin::if_then_else);
|
|
if (name == "lambda") return MlValue("lambda", builtin::lambda);
|
|
|
|
auto it = builtin_funcs.find(name);
|
|
if (it != builtin_funcs.end())
|
|
return MlValue(name, it->second);
|
|
|
|
auto 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<std::string> MlEnvironment::get_lambdas_list(const std::string &token) const {
|
|
std::vector<std::string> lambdas{};
|
|
lambdas.reserve(16);
|
|
|
|
for (auto it = defs.begin(); it != defs.end(); it++)
|
|
if (it->second.get_type_name() == FUNCTION_TYPE && (token.empty() || it->first.find(token) == 0))
|
|
lambdas.push_back(it->first);
|
|
|
|
return lambdas;
|
|
}
|
|
|
|
|
|
void repl(MlEnvironment &env) {
|
|
std::string code;
|
|
std::string input;
|
|
MlValue tmp;
|
|
std::vector<MlValue> parsed;
|
|
|
|
setup_linenoise_repl(env, builtin_funcs);
|
|
|
|
while (true) {
|
|
char *line = linenoise(">>> ");
|
|
if (line == nullptr) break;
|
|
|
|
input = std::string(line);
|
|
linenoise_add_to_history(input);
|
|
|
|
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.empty()) {
|
|
try {
|
|
tmp = run(input, env);
|
|
std::cout << " => " << tmp.debug() << std::endl;
|
|
code += input + "\n";
|
|
} catch (const MlError &e) {
|
|
std::cerr << e.description() << std::endl;
|
|
MlPerfMon::instance().clear_callstack();
|
|
} catch (const std::exception &e) {
|
|
std::cerr << e.what() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool cmdOptionExists(char **begin, char **end, const std::string &option) {
|
|
return std::find(begin, end, option) != end;
|
|
}
|
|
|
|
std::vector<std::string> getCmdOption(char *argv[], int argc, const std::string &option) {
|
|
std::vector<std::string> tokens;
|
|
for (int i = 1; i < argc; ++i) {
|
|
if (option == argv[i] && i + 1 < argc) {
|
|
i++;
|
|
tokens.emplace_back(argv[i]);
|
|
}
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
|
|
void load_std_lib(MlEnvironment &env) {
|
|
run(STDLIB_LOADER, env);
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
MlEnvironment env;
|
|
std::vector<MlValue> args;
|
|
|
|
args.reserve(argc);
|
|
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;
|
|
}
|
|
|
|
setup_linenoise();
|
|
|
|
// passed code
|
|
if (cmdOptionExists(argv, argv + argc, "-c")) {
|
|
std::vector<std::string> codes = getCmdOption(argv, argc, "-c");
|
|
for (auto & code : codes)
|
|
run(code, 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")) {
|
|
std::string file_content = read_file_contents(file);
|
|
if (file_content.find("#!") == 0) // shebang ?
|
|
file_content.erase(0, file_content.find('\n') + 1);
|
|
|
|
run(file_content, env);
|
|
}
|
|
// repl
|
|
} else {
|
|
repl(env);
|
|
}
|
|
|
|
close_linenoise();
|
|
|
|
MlPerfMon::instance().print_results();
|
|
|
|
return 0;
|
|
|
|
} catch (const MlError &e) {
|
|
std::cerr << e.description() << std::endl;
|
|
} catch (const std::exception &e) {
|
|
std::cerr << MlPerfMon::instance().callstack() << e.what() << std::endl;
|
|
}
|
|
|
|
return 1;
|
|
}
|