defmacro added, doc improvements
This commit is contained in:
130
ml.cpp
130
ml.cpp
@@ -25,6 +25,7 @@
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
|
||||
|
||||
#define TOO_FEW_ARGS "too few arguments to function"
|
||||
@@ -47,6 +48,8 @@
|
||||
#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"
|
||||
@@ -123,7 +126,7 @@ MlValue MlValue::nil() {
|
||||
}
|
||||
|
||||
// Construct a lambda function
|
||||
MlValue::MlValue(const std::vector<MlValue> ¶ms, MlValue ret, MlEnvironment const &env) : type(LAMBDA) {
|
||||
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);
|
||||
@@ -162,7 +165,8 @@ std::vector<std::string> MlValue::get_used_atoms() {
|
||||
result.push_back(as_atom());
|
||||
return result;
|
||||
case LAMBDA:
|
||||
// If this is a lambda, get the list of used atoms in the body
|
||||
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:
|
||||
@@ -184,6 +188,10 @@ 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;
|
||||
}
|
||||
@@ -318,8 +326,9 @@ bool MlValue::operator==(MlValue other) const {
|
||||
// data in the str member.
|
||||
return str == other.str;
|
||||
case LAMBDA:
|
||||
case MACRO:
|
||||
case LIST:
|
||||
// Both lambdas and lists store their
|
||||
// All lambdas, macros and lists store their
|
||||
// data in the list member.
|
||||
return list == other.list;
|
||||
case QUOTE:
|
||||
@@ -526,9 +535,10 @@ std::string MlValue::get_type_name() const {
|
||||
return STRING_TYPE;
|
||||
case BUILTIN:
|
||||
case LAMBDA:
|
||||
case MACRO:
|
||||
// Instead of differentiating between
|
||||
// lambda and builtin types, we group them together.
|
||||
// This is because they are both callable.
|
||||
// lambda, macro and builtin types, we group them together.
|
||||
// This is because they are all callable.
|
||||
return FUNCTION_TYPE;
|
||||
case NIL:
|
||||
return NIL_TYPE;
|
||||
@@ -541,7 +551,6 @@ std::string MlValue::get_type_name() const {
|
||||
}
|
||||
|
||||
std::string MlValue::display() const {
|
||||
std::string result;
|
||||
switch (type) {
|
||||
case QUOTE:
|
||||
return "'" + list[0].debug();
|
||||
@@ -554,17 +563,14 @@ std::string MlValue::display() const {
|
||||
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 + ")";
|
||||
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:
|
||||
for (size_t i = 0; i < list.size(); i++) {
|
||||
result += list[i].debug();
|
||||
if (i < list.size() - 1) result += " ";
|
||||
}
|
||||
return "(" + result + ")";
|
||||
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:
|
||||
@@ -580,41 +586,15 @@ std::string MlValue::display() const {
|
||||
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:
|
||||
result.reserve(str.size());
|
||||
for (char c : str) {
|
||||
if (c == '"') result += "\\\"";
|
||||
else result.push_back(c);
|
||||
}
|
||||
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);
|
||||
return display();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,19 +626,15 @@ const char * MlError::what() const noexcept {
|
||||
}
|
||||
|
||||
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.
|
||||
auto itr = other.defs.begin();
|
||||
for (; itr != other.defs.end(); itr++) {
|
||||
// Iterate through the keys and assign each value.
|
||||
// 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) {
|
||||
auto itr = e.defs.begin();
|
||||
os << "{ ";
|
||||
for (; itr != e.defs.end(); itr++) {
|
||||
for (auto itr = e.defs.begin(); itr != e.defs.end(); itr++) {
|
||||
os << '\'' << itr->first << "' : " << itr->second.debug() << ", ";
|
||||
}
|
||||
return os << "}";
|
||||
@@ -687,8 +663,11 @@ void MlEnvironment::setX(const std::string &name, const MlValue& 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())
|
||||
@@ -708,8 +687,17 @@ MlValue MlValue::apply(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
e.set(params[i].str, args[i]);
|
||||
}
|
||||
|
||||
// Evaluate the function body with the function scope
|
||||
return list[1].eval(e);
|
||||
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.
|
||||
@@ -740,11 +728,11 @@ MlValue MlValue::eval(MlEnvironment &env) {
|
||||
args = std::vector<MlValue>(list.begin() + 1, list.end());
|
||||
|
||||
function = list[0].eval(env);
|
||||
if (function.type == BUILTIN || function.type == LAMBDA) {
|
||||
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())
|
||||
if (!function.is_builtin() && !function.is_macro())
|
||||
for (auto & arg : args)
|
||||
arg = arg.eval(env);
|
||||
|
||||
@@ -785,7 +773,6 @@ MlValue parse(std::string &s, int &ptr) {
|
||||
if (s[ptr] == ';')
|
||||
throw std::runtime_error(INTERNAL_ERROR);
|
||||
|
||||
|
||||
if (s.empty()) {
|
||||
return MlValue();
|
||||
|
||||
@@ -906,8 +893,7 @@ MlValue run(const std::string &code, MlEnvironment &env) {
|
||||
if (parsed.empty())
|
||||
return MlValue::nil();
|
||||
|
||||
// Iterate over the expressions and evaluate them
|
||||
// in this environment.
|
||||
// Iterate over the expressions and evaluate them in this environment.
|
||||
for (size_t i = 0; i < parsed.size() - 1; i++)
|
||||
parsed[i].eval(env);
|
||||
|
||||
@@ -936,7 +922,7 @@ MlValue lambda(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
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);
|
||||
return MlValue(args[0].as_list(), args[1], env, MlValue::Type::LAMBDA);
|
||||
}
|
||||
|
||||
// if-else (SPECIAL FORM)
|
||||
@@ -992,7 +978,21 @@ MlValue defun(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
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 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;
|
||||
}
|
||||
@@ -1213,8 +1213,10 @@ MlValue read_url(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
// headers
|
||||
if (args.size() > 1) {
|
||||
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();
|
||||
if (pair.size() != 2)
|
||||
throw MlError(MlValue("read_url", read_url), env, INVALID_HEADER_FORMAT);
|
||||
|
||||
headers[pair[0].as_string()] = pair[1].as_string();
|
||||
}
|
||||
}
|
||||
@@ -1466,7 +1468,7 @@ MlValue divide(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
|
||||
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.");
|
||||
throw std::invalid_argument(DIVISION_BY_ZERO);
|
||||
|
||||
return args[0] / args[1];
|
||||
}
|
||||
@@ -1952,7 +1954,6 @@ MlValue range(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
|
||||
// Benchmarks a block of expressions in the current environment (SPECIAL FORM)
|
||||
MlValue benchmark(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
// TODO add some memory stats
|
||||
using namespace std::chrono;
|
||||
high_resolution_clock::time_point t1 = high_resolution_clock::now();
|
||||
|
||||
@@ -2110,6 +2111,7 @@ std::map <const std::string, Builtin> builtin_funcs
|
||||
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),
|
||||
@@ -2225,7 +2227,7 @@ std::map <const std::string, Builtin> builtin_funcs
|
||||
// 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 == "defe") return MlValue("def", builtin::define);
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user