mirror of
https://github.com/yhirose/cpp-peglib.git
synced 2025-01-22 21:35:29 +00:00
361 lines
12 KiB
C++
361 lines
12 KiB
C++
#include "interpreter.hpp"
|
|
#include "parser.hpp"
|
|
#include <sstream>
|
|
|
|
using namespace peglib;
|
|
using namespace std;
|
|
|
|
struct Eval
|
|
{
|
|
static Value eval(const Ast& ast, shared_ptr<Environment> env) {
|
|
switch (ast.tag) {
|
|
case Statements: return eval_statements(ast, env);
|
|
case While: return eval_while(ast, env);
|
|
case If: return eval_if(ast, env);
|
|
case Function: return eval_function(ast, env);
|
|
case Call: return eval_call(ast, env);
|
|
case Assignment: return eval_assignment(ast, env);
|
|
case LogicalOr: return eval_logical_or(ast, env);
|
|
case LogicalAnd: return eval_logical_and(ast, env);
|
|
case Condition: return eval_condition(ast, env);
|
|
case UnaryPlus: return eval_unary_plus(ast, env);
|
|
case UnaryMinus: return eval_unary_minus(ast, env);
|
|
case UnaryNot: return eval_unary_not(ast, env);
|
|
case BinExpresion: return eval_bin_expression(ast, env);
|
|
case Identifier: return eval_identifier(ast, env);
|
|
case Object: return eval_object(ast, env);
|
|
case Array: return eval_array(ast, env);
|
|
case Number: return eval_number(ast, env);
|
|
case Boolean: return eval_bool(ast, env);
|
|
case InterpolatedString: return eval_interpolated_string(ast, env);
|
|
}
|
|
|
|
if (ast.is_token) {
|
|
return Value(string(ast.token));
|
|
}
|
|
|
|
// NOTREACHED
|
|
throw logic_error("invalid Ast type");
|
|
}
|
|
|
|
private:
|
|
static Value eval_statements(const Ast& ast, shared_ptr<Environment> env) {
|
|
if (ast.is_token) {
|
|
return eval(ast, env);
|
|
} else if (ast.nodes.empty()) {
|
|
return Value();
|
|
}
|
|
auto it = ast.nodes.begin();
|
|
while (it != ast.nodes.end() - 1) {
|
|
eval(**it, env);
|
|
++it;
|
|
}
|
|
return eval(**it, env);
|
|
}
|
|
|
|
static Value eval_while(const Ast& ast, shared_ptr<Environment> env) {
|
|
for (;;) {
|
|
auto cond = eval(*ast.nodes[0], env);
|
|
if (!cond.to_bool()) {
|
|
break;
|
|
}
|
|
eval(*ast.nodes[1], env);
|
|
}
|
|
return Value();
|
|
}
|
|
|
|
static Value eval_if(const Ast& ast, shared_ptr<Environment> env) {
|
|
const auto& nodes = ast.nodes;
|
|
|
|
for (auto i = 0u; i < nodes.size(); i += 2) {
|
|
if (i + 1 == nodes.size()) {
|
|
return eval(*nodes[i], env);
|
|
} else {
|
|
auto cond = eval(*nodes[i], env);
|
|
if (cond.to_bool()) {
|
|
return eval(*nodes[i + 1], env);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Value();
|
|
}
|
|
|
|
static Value eval_function(const Ast& ast, shared_ptr<Environment> env) {
|
|
std::vector<FunctionValue::Parameter> params;
|
|
for (auto node: ast.nodes[0]->nodes) {
|
|
auto mut = node->nodes[0]->token == "mut";
|
|
const auto& name = node->nodes[1]->token;
|
|
params.push_back({ name, mut });
|
|
}
|
|
|
|
auto body = ast.nodes[1];
|
|
|
|
auto f = FunctionValue(
|
|
params,
|
|
[=](shared_ptr<Environment> callEnv) {
|
|
callEnv->append_outer(env);
|
|
return eval(*body, callEnv);
|
|
}
|
|
);
|
|
|
|
return Value(std::move(f));
|
|
};
|
|
|
|
static Value eval_call(const Ast& ast, shared_ptr<Environment> env) {
|
|
Value val = eval(*ast.nodes[0], env);
|
|
|
|
for (auto i = 1u; i < ast.nodes.size(); i++) {
|
|
const auto& n = *ast.nodes[i];
|
|
if (n.original_tag == AstTag::Arguments) {
|
|
// Function call
|
|
const auto& f = val.to_function();
|
|
const auto& params = f.data->params;
|
|
const auto& args = n.nodes;
|
|
if (params.size() <= args.size()) {
|
|
auto callEnv = make_shared<Environment>();
|
|
|
|
callEnv->initialize("self", val, false);
|
|
|
|
for (auto iprm = 0u; iprm < params.size(); iprm++) {
|
|
auto param = params[iprm];
|
|
auto arg = args[iprm];
|
|
auto val = eval(*arg, env);
|
|
callEnv->initialize(param.name, val, param.mut);
|
|
}
|
|
|
|
callEnv->initialize("__LINE__", Value((long)ast.line), false);
|
|
callEnv->initialize("__COLUMN__", Value((long)ast.column), false);
|
|
|
|
val = f.data->eval(callEnv);
|
|
} else {
|
|
string msg = "arguments error...";
|
|
throw runtime_error(msg);
|
|
}
|
|
} else if (n.original_tag == AstTag::Index) {
|
|
// Array reference
|
|
const auto& arr = val.to_array();
|
|
auto idx = eval(n, env).to_long();
|
|
if (0 <= idx && idx < static_cast<long>(arr.values->size())) {
|
|
val = arr.values->at(idx);
|
|
}
|
|
} else if (n.original_tag == AstTag::Dot) {
|
|
// Property
|
|
auto name = n.token;
|
|
auto prop = val.get_property(name);
|
|
|
|
if (prop.get_type() == Value::Function) {
|
|
const auto& pf = prop.to_function();
|
|
|
|
auto f = FunctionValue(
|
|
pf.data->params,
|
|
[=](shared_ptr<Environment> callEnv) {
|
|
callEnv->initialize("this", val, false);
|
|
if (val.get_type() == Value::Object) {
|
|
callEnv->set_object(val.to_object());
|
|
}
|
|
return pf.data->eval(callEnv);
|
|
}
|
|
);
|
|
|
|
val = Value(std::move(f));
|
|
} else {
|
|
val = prop;
|
|
}
|
|
} else {
|
|
throw std::logic_error("invalid internal condition.");
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static Value eval_logical_or(const Ast& ast, shared_ptr<Environment> env) {
|
|
if (ast.nodes.size() == 1) {
|
|
return eval(*ast.nodes[0], env);
|
|
} else {
|
|
Value ret;
|
|
for (auto node: ast.nodes) {
|
|
ret = eval(*node, env);
|
|
if (ret.to_bool()) {
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static Value eval_logical_and(const Ast& ast, shared_ptr<Environment> env) {
|
|
Value ret;
|
|
for (auto node: ast.nodes) {
|
|
ret = eval(*node, env);
|
|
if (!ret.to_bool()) {
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static Value eval_condition(const Ast& ast, shared_ptr<Environment> env) {
|
|
if (ast.nodes.size() == 1) {
|
|
return eval(*ast.nodes[0], env);
|
|
} else {
|
|
auto lhs = eval(*ast.nodes[0], env);
|
|
auto ope = eval(*ast.nodes[1], env).to_string();
|
|
auto rhs = eval(*ast.nodes[2], env);
|
|
|
|
if (ope == "==") {
|
|
return Value(lhs == rhs);
|
|
} else if (ope == "!=") {
|
|
return Value(lhs != rhs);
|
|
} else if (ope == "<=") {
|
|
return Value(lhs <= rhs);
|
|
} else if (ope == "<") {
|
|
return Value(lhs < rhs);
|
|
} else if (ope == ">=") {
|
|
return Value(lhs >= rhs);
|
|
} else if (ope == ">") {
|
|
return Value(lhs > rhs);
|
|
} else {
|
|
throw std::logic_error("invalid internal condition.");
|
|
}
|
|
}
|
|
}
|
|
|
|
static Value eval_unary_plus(const Ast& ast, shared_ptr<Environment> env) {
|
|
if (ast.nodes.size() == 1) {
|
|
return eval(*ast.nodes[0], env);
|
|
} else {
|
|
return eval(*ast.nodes[1], env);
|
|
}
|
|
}
|
|
|
|
static Value eval_unary_minus(const Ast& ast, shared_ptr<Environment> env) {
|
|
if (ast.nodes.size() == 1) {
|
|
return eval(*ast.nodes[0], env);
|
|
} else {
|
|
return Value(eval(*ast.nodes[1], env).to_long() * -1);
|
|
}
|
|
}
|
|
|
|
static Value eval_unary_not(const Ast& ast, shared_ptr<Environment> env) {
|
|
if (ast.nodes.size() == 1) {
|
|
return eval(*ast.nodes[0], env);
|
|
} else {
|
|
return Value(!eval(*ast.nodes[1], env).to_bool());
|
|
}
|
|
}
|
|
|
|
static Value eval_bin_expression(const Ast& ast, shared_ptr<Environment> env) {
|
|
auto ret = eval(*ast.nodes[0], env).to_long();
|
|
for (auto i = 1u; i < ast.nodes.size(); i += 2) {
|
|
auto val = eval(*ast.nodes[i + 1], env).to_long();
|
|
auto ope = eval(*ast.nodes[i], env).to_string()[0];
|
|
switch (ope) {
|
|
case '+': ret += val; break;
|
|
case '-': ret -= val; break;
|
|
case '*': ret *= val; break;
|
|
case '/': ret /= val; break;
|
|
case '%': ret %= val; break;
|
|
}
|
|
}
|
|
return Value(ret);
|
|
}
|
|
|
|
static Value eval_assignment(const Ast& ast, shared_ptr<Environment> env) {
|
|
const auto& mut = ast.nodes[0]->token;
|
|
const auto& var = ast.nodes[1]->token;
|
|
auto val = eval(*ast.nodes[2], env);
|
|
if (env->has(var)) {
|
|
env->assign(var, val);
|
|
} else {
|
|
env->initialize(var, val, mut == "mut");
|
|
}
|
|
return val;
|
|
};
|
|
|
|
static Value eval_identifier(const Ast& ast, shared_ptr<Environment> env) {
|
|
const auto& var = ast.token;
|
|
return env->get(var);
|
|
};
|
|
|
|
static Value eval_object(const Ast& ast, shared_ptr<Environment> env) {
|
|
ObjectValue obj;
|
|
|
|
for (auto i = 0u; i < ast.nodes.size(); i++) {
|
|
const auto& prop = *ast.nodes[i];
|
|
const auto& name = prop.nodes[0]->token;
|
|
auto val = eval(*prop.nodes[1], env);
|
|
obj.properties->emplace(name, val);
|
|
}
|
|
|
|
return Value(std::move(obj));
|
|
}
|
|
|
|
static Value eval_array(const Ast& ast, shared_ptr<Environment> env) {
|
|
ArrayValue arr;
|
|
|
|
for (auto i = 0u; i < ast.nodes.size(); i++) {
|
|
auto expr = ast.nodes[i];
|
|
auto val = eval(*expr, env);
|
|
arr.values->push_back(val);
|
|
}
|
|
|
|
return Value(std::move(arr));
|
|
}
|
|
|
|
static Value eval_number(const Ast& ast, shared_ptr<Environment> env) {
|
|
return Value(stol(ast.token));
|
|
};
|
|
|
|
static Value eval_bool(const Ast& ast, shared_ptr<Environment> env) {
|
|
return Value(ast.token == "true");
|
|
};
|
|
|
|
static Value eval_interpolated_string(const Ast& ast, shared_ptr<Environment> env) {
|
|
string s;
|
|
for (auto node: ast.nodes) {
|
|
const auto& val = eval(*node, env);
|
|
s += val.str();
|
|
}
|
|
return Value(std::move(s));
|
|
};
|
|
};
|
|
|
|
bool run(
|
|
const string& path,
|
|
shared_ptr<Environment> env,
|
|
const char* expr,
|
|
size_t len,
|
|
Value& val,
|
|
string& msg,
|
|
bool print_ast)
|
|
{
|
|
try {
|
|
shared_ptr<Ast> ast;
|
|
|
|
auto& parser = get_parser();
|
|
|
|
parser.log = [&](size_t ln, size_t col, const string& err_msg) {
|
|
stringstream ss;
|
|
ss << path << ":" << ln << ":" << col << ": " << err_msg << endl;
|
|
msg = ss.str();
|
|
};
|
|
|
|
if (parser.parse_n(expr, len, ast)) {
|
|
if (print_ast) {
|
|
ast->print();
|
|
}
|
|
|
|
val = Eval::eval(*ast, env);
|
|
return true;
|
|
}
|
|
} catch (runtime_error& e) {
|
|
msg = e.what();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// vim: et ts=4 sw=4 cin cino={1s ff=unix
|