defmacro added, doc improvements

This commit is contained in:
vaclavt
2022-01-29 15:35:42 +01:00
parent 249af03f60
commit f1d102130f
9 changed files with 155 additions and 97 deletions

130
ml.cpp
View File

@@ -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> &params, MlValue ret, MlEnvironment const &env) : type(LAMBDA) {
MlValue::MlValue(const std::vector<MlValue> &params, 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);