irc/evaluator: fix e passed by value in Thunk constructor
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: If3bdfd1fc0851d4113b89827474a74a86a6a6964
This commit is contained in:
parent
eb84d4617b
commit
63a9eddc49
1 changed files with 406 additions and 430 deletions
|
|
@ -7,481 +7,457 @@
|
||||||
#include "nix/expr/value.hh"
|
#include "nix/expr/value.hh"
|
||||||
#include "nix/util/error.hh"
|
#include "nix/util/error.hh"
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace nix_irc {
|
namespace nix_irc {
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
struct IREnvironment {
|
struct IREnvironment {
|
||||||
IREnvironment* parent;
|
IREnvironment* parent;
|
||||||
std::vector<Value*> bindings;
|
std::vector<Value*> bindings;
|
||||||
Value* with_attrs;
|
Value* with_attrs;
|
||||||
|
|
||||||
explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {}
|
explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {}
|
||||||
|
|
||||||
void bind(Value* val) {
|
void bind(Value* val) { bindings.push_back(val); }
|
||||||
bindings.push_back(val);
|
|
||||||
|
Value* lookup(uint32_t index) {
|
||||||
|
IREnvironment* env = this;
|
||||||
|
while (env) {
|
||||||
|
if (index < env->bindings.size()) {
|
||||||
|
return env->bindings[index];
|
||||||
|
}
|
||||||
|
index -= env->bindings.size();
|
||||||
|
env = env->parent;
|
||||||
}
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
Value* lookup(uint32_t index) {
|
Value* lookup_with(EvalState& state, const std::string& name) {
|
||||||
IREnvironment* env = this;
|
IREnvironment* env = this;
|
||||||
while (env) {
|
while (env) {
|
||||||
if (index < env->bindings.size()) {
|
if (env->with_attrs && env->with_attrs->type() == nAttrs) {
|
||||||
return env->bindings[index];
|
auto sym = state.symbols.create(name);
|
||||||
}
|
auto attr = env->with_attrs->attrs()->get(sym);
|
||||||
index -= env->bindings.size();
|
if (attr) {
|
||||||
env = env->parent;
|
return attr->value;
|
||||||
}
|
}
|
||||||
return nullptr;
|
}
|
||||||
}
|
env = env->parent;
|
||||||
|
|
||||||
Value* lookup_with(EvalState& state, const std::string& name) {
|
|
||||||
IREnvironment* env = this;
|
|
||||||
while (env) {
|
|
||||||
if (env->with_attrs && env->with_attrs->type() == nAttrs) {
|
|
||||||
auto sym = state.symbols.create(name);
|
|
||||||
auto attr = env->with_attrs->attrs()->get(sym);
|
|
||||||
if (attr) {
|
|
||||||
return attr->value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
env = env->parent;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Thunk {
|
struct Thunk {
|
||||||
std::shared_ptr<Node> expr;
|
std::shared_ptr<Node> expr;
|
||||||
IREnvironment* env;
|
IREnvironment* env;
|
||||||
bool blackholed;
|
bool blackholed;
|
||||||
|
|
||||||
Thunk(std::shared_ptr<Node> e, IREnvironment* en)
|
Thunk(const std::shared_ptr<Node>& e, IREnvironment* en) : expr(e), env(en), blackholed(false) {}
|
||||||
: expr(e), env(en), blackholed(false) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Evaluator::Impl {
|
struct Evaluator::Impl {
|
||||||
EvalState& state;
|
EvalState& state;
|
||||||
std::unordered_map<Value*, std::unique_ptr<Thunk>> thunks;
|
std::unordered_map<Value*, std::unique_ptr<Thunk>> thunks;
|
||||||
std::vector<std::unique_ptr<IREnvironment>> environments;
|
std::vector<std::unique_ptr<IREnvironment>> environments;
|
||||||
|
|
||||||
explicit Impl(EvalState& s) : state(s) {}
|
explicit Impl(EvalState& s) : state(s) {}
|
||||||
|
|
||||||
~Impl() {
|
~Impl() {
|
||||||
for (auto& env : environments) {
|
for (auto& env : environments) {
|
||||||
delete env.release();
|
delete env.release();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IREnvironment* make_env(IREnvironment* parent = nullptr) {
|
||||||
|
auto env = new IREnvironment(parent);
|
||||||
|
environments.push_back(std::unique_ptr<IREnvironment>(env));
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* make_thunk(const std::shared_ptr<Node>& expr, IREnvironment* env) {
|
||||||
|
Value* v = state.allocValue();
|
||||||
|
thunks[v] = std::make_unique<Thunk>(expr, env);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void force(Value* v) {
|
||||||
|
auto it = thunks.find(v);
|
||||||
|
if (it == thunks.end()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IREnvironment* make_env(IREnvironment* parent = nullptr) {
|
Thunk* thunk = it->second.get();
|
||||||
auto env = new IREnvironment(parent);
|
if (thunk->blackholed) {
|
||||||
environments.push_back(std::unique_ptr<IREnvironment>(env));
|
state.error<EvalError>("infinite recursion encountered").debugThrow();
|
||||||
return env;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value* make_thunk(const std::shared_ptr<Node>& expr, IREnvironment* env) {
|
thunk->blackholed = true;
|
||||||
Value* v = state.allocValue();
|
eval_node(thunk->expr, *v, thunk->env);
|
||||||
thunks[v] = std::make_unique<Thunk>(expr, env);
|
thunks.erase(v);
|
||||||
return v;
|
}
|
||||||
|
|
||||||
|
void eval_node(const std::shared_ptr<Node>& node, Value& v, IREnvironment* env) {
|
||||||
|
if (!node) {
|
||||||
|
v.mkNull();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void force(Value* v) {
|
if (auto* n = node->get_if<ConstIntNode>()) {
|
||||||
auto it = thunks.find(v);
|
v.mkInt(n->value);
|
||||||
if (it == thunks.end()) {
|
} else if (auto* n = node->get_if<ConstStringNode>()) {
|
||||||
return;
|
v.mkString(n->value);
|
||||||
}
|
} else if (auto* n = node->get_if<ConstPathNode>()) {
|
||||||
|
v.mkPath(state.rootPath(CanonPath(n->value)));
|
||||||
|
} else if (auto* n = node->get_if<ConstBoolNode>()) {
|
||||||
|
v.mkBool(n->value);
|
||||||
|
} else if (auto* n = node->get_if<ConstNullNode>()) { // NOLINT(bugprone-branch-clone)
|
||||||
|
v.mkNull();
|
||||||
|
} else if (auto* n = node->get_if<VarNode>()) {
|
||||||
|
Value* bound = env ? env->lookup(n->index) : nullptr;
|
||||||
|
if (!bound && env && n->name.has_value()) {
|
||||||
|
bound = env->lookup_with(state, n->name.value());
|
||||||
|
}
|
||||||
|
if (!bound) {
|
||||||
|
state.error<EvalError>("variable not found").debugThrow();
|
||||||
|
}
|
||||||
|
force(bound);
|
||||||
|
v = *bound;
|
||||||
|
} else if (auto* n = node->get_if<LambdaNode>()) {
|
||||||
|
auto lambda_env = env;
|
||||||
|
auto body = n->body;
|
||||||
|
|
||||||
Thunk* thunk = it->second.get();
|
auto primOp = [this, lambda_env, body](EvalState& state, PosIdx pos, Value** args,
|
||||||
if (thunk->blackholed) {
|
Value& result) {
|
||||||
state.error<EvalError>("infinite recursion encountered").debugThrow();
|
auto call_env = make_env(lambda_env);
|
||||||
}
|
call_env->bind(args[0]);
|
||||||
|
eval_node(body, result, call_env);
|
||||||
|
};
|
||||||
|
|
||||||
thunk->blackholed = true;
|
v.mkPrimOp(new PrimOp{.name = n->param_name.value_or("lambda"),
|
||||||
eval_node(thunk->expr, *v, thunk->env);
|
.arity = static_cast<size_t>(n->arity),
|
||||||
thunks.erase(v);
|
.fun = primOp});
|
||||||
}
|
} else if (auto* n = node->get_if<AppNode>()) {
|
||||||
|
Value* func_val = state.allocValue();
|
||||||
void eval_node(const std::shared_ptr<Node>& node, Value& v, IREnvironment* env) {
|
eval_node(n->func, *func_val, env);
|
||||||
if (!node) {
|
force(func_val);
|
||||||
v.mkNull();
|
|
||||||
return;
|
Value* arg_val = make_thunk(n->arg, env);
|
||||||
}
|
|
||||||
|
state.callFunction(*func_val, *arg_val, v, noPos);
|
||||||
if (auto* n = node->get_if<ConstIntNode>()) {
|
} else if (auto* n = node->get_if<BinaryOpNode>()) {
|
||||||
v.mkInt(n->value);
|
Value* left = state.allocValue();
|
||||||
}
|
Value* right = state.allocValue();
|
||||||
else if (auto* n = node->get_if<ConstStringNode>()) {
|
|
||||||
v.mkString(n->value);
|
switch (n->op) {
|
||||||
}
|
case BinaryOp::AND:
|
||||||
else if (auto* n = node->get_if<ConstPathNode>()) {
|
eval_node(n->left, *left, env);
|
||||||
v.mkPath(state.rootPath(CanonPath(n->value)));
|
force(left);
|
||||||
}
|
if (left->type() != nBool) {
|
||||||
else if (auto* n = node->get_if<ConstBoolNode>()) {
|
state.error<EvalError>("type error in logical AND").debugThrow();
|
||||||
v.mkBool(n->value);
|
}
|
||||||
}
|
if (!left->boolean()) {
|
||||||
else if (auto* n = node->get_if<ConstNullNode>()) {
|
v.mkBool(false);
|
||||||
v.mkNull();
|
} else {
|
||||||
}
|
eval_node(n->right, *right, env);
|
||||||
else if (auto* n = node->get_if<VarNode>()) {
|
force(right);
|
||||||
Value* bound = env ? env->lookup(n->index) : nullptr;
|
if (right->type() != nBool) {
|
||||||
if (!bound && env && n->name.has_value()) {
|
state.error<EvalError>("type error in logical AND").debugThrow();
|
||||||
bound = env->lookup_with(state, n->name.value());
|
}
|
||||||
}
|
v.mkBool(right->boolean());
|
||||||
if (!bound) {
|
}
|
||||||
state.error<EvalError>("variable not found").debugThrow();
|
break;
|
||||||
}
|
case BinaryOp::OR:
|
||||||
force(bound);
|
eval_node(n->left, *left, env);
|
||||||
v = *bound;
|
force(left);
|
||||||
}
|
if (left->type() != nBool) {
|
||||||
else if (auto* n = node->get_if<LambdaNode>()) {
|
state.error<EvalError>("type error in logical OR").debugThrow();
|
||||||
auto lambda_env = env;
|
}
|
||||||
auto body = n->body;
|
if (left->boolean()) {
|
||||||
|
v.mkBool(true);
|
||||||
auto primOp = [this, lambda_env, body](EvalState& state, PosIdx pos,
|
} else {
|
||||||
Value** args, Value& result) {
|
eval_node(n->right, *right, env);
|
||||||
auto call_env = make_env(lambda_env);
|
force(right);
|
||||||
call_env->bind(args[0]);
|
if (right->type() != nBool) {
|
||||||
eval_node(body, result, call_env);
|
state.error<EvalError>("type error in logical OR").debugThrow();
|
||||||
};
|
}
|
||||||
|
v.mkBool(right->boolean());
|
||||||
v.mkPrimOp(new PrimOp {
|
}
|
||||||
.name = n->param_name.value_or("lambda"),
|
break;
|
||||||
.arity = static_cast<size_t>(n->arity),
|
case BinaryOp::IMPL:
|
||||||
.fun = primOp
|
eval_node(n->left, *left, env);
|
||||||
});
|
force(left);
|
||||||
}
|
if (left->type() != nBool) {
|
||||||
else if (auto* n = node->get_if<AppNode>()) {
|
state.error<EvalError>("type error in implication").debugThrow();
|
||||||
Value* func_val = state.allocValue();
|
}
|
||||||
eval_node(n->func, *func_val, env);
|
if (!left->boolean()) {
|
||||||
force(func_val);
|
v.mkBool(true);
|
||||||
|
} else {
|
||||||
Value* arg_val = make_thunk(n->arg, env);
|
eval_node(n->right, *right, env);
|
||||||
|
force(right);
|
||||||
state.callFunction(*func_val, *arg_val, v, noPos);
|
if (right->type() != nBool) {
|
||||||
}
|
state.error<EvalError>("type error in implication").debugThrow();
|
||||||
else if (auto* n = node->get_if<BinaryOpNode>()) {
|
}
|
||||||
Value* left = state.allocValue();
|
v.mkBool(right->boolean());
|
||||||
Value* right = state.allocValue();
|
}
|
||||||
|
break;
|
||||||
switch (n->op) {
|
default:
|
||||||
case BinaryOp::AND:
|
eval_node(n->left, *left, env);
|
||||||
eval_node(n->left, *left, env);
|
eval_node(n->right, *right, env);
|
||||||
force(left);
|
force(left);
|
||||||
if (left->type() != nBool) {
|
force(right);
|
||||||
state.error<EvalError>("type error in logical AND").debugThrow();
|
|
||||||
}
|
switch (n->op) {
|
||||||
if (!left->boolean()) {
|
case BinaryOp::ADD:
|
||||||
v.mkBool(false);
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
} else {
|
v.mkInt((left->integer() + right->integer()).valueWrapping());
|
||||||
eval_node(n->right, *right, env);
|
} else if (left->type() == nString && right->type() == nString) {
|
||||||
force(right);
|
v.mkString(std::string(left->c_str()) + std::string(right->c_str()));
|
||||||
if (right->type() != nBool) {
|
} else {
|
||||||
state.error<EvalError>("type error in logical AND").debugThrow();
|
state.error<EvalError>("type error in addition").debugThrow();
|
||||||
}
|
}
|
||||||
v.mkBool(right->boolean());
|
break;
|
||||||
}
|
case BinaryOp::SUB:
|
||||||
break;
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
case BinaryOp::OR:
|
v.mkInt((left->integer() - right->integer()).valueWrapping());
|
||||||
eval_node(n->left, *left, env);
|
} else {
|
||||||
force(left);
|
state.error<EvalError>("type error in subtraction").debugThrow();
|
||||||
if (left->type() != nBool) {
|
}
|
||||||
state.error<EvalError>("type error in logical OR").debugThrow();
|
break;
|
||||||
}
|
case BinaryOp::MUL:
|
||||||
if (left->boolean()) {
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
v.mkBool(true);
|
v.mkInt((left->integer() * right->integer()).valueWrapping());
|
||||||
} else {
|
} else {
|
||||||
eval_node(n->right, *right, env);
|
state.error<EvalError>("type error in multiplication").debugThrow();
|
||||||
force(right);
|
}
|
||||||
if (right->type() != nBool) {
|
break;
|
||||||
state.error<EvalError>("type error in logical OR").debugThrow();
|
case BinaryOp::DIV:
|
||||||
}
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
v.mkBool(right->boolean());
|
if (right->integer() == NixInt(0)) {
|
||||||
}
|
state.error<EvalError>("division by zero").debugThrow();
|
||||||
break;
|
}
|
||||||
case BinaryOp::IMPL:
|
v.mkInt((left->integer() / right->integer()).valueWrapping());
|
||||||
eval_node(n->left, *left, env);
|
} else {
|
||||||
force(left);
|
state.error<EvalError>("type error in division").debugThrow();
|
||||||
if (left->type() != nBool) {
|
}
|
||||||
state.error<EvalError>("type error in implication").debugThrow();
|
break;
|
||||||
}
|
case BinaryOp::EQ:
|
||||||
if (!left->boolean()) {
|
v.mkBool(state.eqValues(*left, *right, noPos, ""));
|
||||||
v.mkBool(true);
|
break;
|
||||||
} else {
|
case BinaryOp::NE:
|
||||||
eval_node(n->right, *right, env);
|
v.mkBool(!state.eqValues(*left, *right, noPos, ""));
|
||||||
force(right);
|
break;
|
||||||
if (right->type() != nBool) {
|
case BinaryOp::LT:
|
||||||
state.error<EvalError>("type error in implication").debugThrow();
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
}
|
v.mkBool(left->integer() < right->integer());
|
||||||
v.mkBool(right->boolean());
|
} else if (left->type() == nString && right->type() == nString) {
|
||||||
}
|
v.mkBool(std::string(left->c_str()) < std::string(right->c_str()));
|
||||||
break;
|
} else {
|
||||||
default:
|
state.error<EvalError>("type error in comparison").debugThrow();
|
||||||
eval_node(n->left, *left, env);
|
}
|
||||||
eval_node(n->right, *right, env);
|
break;
|
||||||
force(left);
|
case BinaryOp::GT:
|
||||||
force(right);
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
|
v.mkBool(left->integer() > right->integer());
|
||||||
switch (n->op) {
|
} else if (left->type() == nString && right->type() == nString) {
|
||||||
case BinaryOp::ADD:
|
v.mkBool(std::string(left->c_str()) > std::string(right->c_str()));
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
} else {
|
||||||
v.mkInt((left->integer() + right->integer()).valueWrapping());
|
state.error<EvalError>("type error in comparison").debugThrow();
|
||||||
} else if (left->type() == nString && right->type() == nString) {
|
}
|
||||||
v.mkString(std::string(left->c_str()) + std::string(right->c_str()));
|
break;
|
||||||
} else {
|
case BinaryOp::LE:
|
||||||
state.error<EvalError>("type error in addition").debugThrow();
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
}
|
v.mkBool(left->integer() <= right->integer());
|
||||||
break;
|
} else if (left->type() == nString && right->type() == nString) {
|
||||||
case BinaryOp::SUB:
|
v.mkBool(std::string(left->c_str()) <= std::string(right->c_str()));
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
} else {
|
||||||
v.mkInt((left->integer() - right->integer()).valueWrapping());
|
state.error<EvalError>("type error in comparison").debugThrow();
|
||||||
} else {
|
}
|
||||||
state.error<EvalError>("type error in subtraction").debugThrow();
|
break;
|
||||||
}
|
case BinaryOp::GE:
|
||||||
break;
|
if (left->type() == nInt && right->type() == nInt) {
|
||||||
case BinaryOp::MUL:
|
v.mkBool(left->integer() >= right->integer());
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
} else if (left->type() == nString && right->type() == nString) {
|
||||||
v.mkInt((left->integer() * right->integer()).valueWrapping());
|
v.mkBool(std::string(left->c_str()) >= std::string(right->c_str()));
|
||||||
} else {
|
} else {
|
||||||
state.error<EvalError>("type error in multiplication").debugThrow();
|
state.error<EvalError>("type error in comparison").debugThrow();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BinaryOp::DIV:
|
case BinaryOp::CONCAT:
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
// ++ is list concatenation in Nix; string concat uses ADD (+)
|
||||||
if (right->integer() == NixInt(0)) {
|
state.error<EvalError>("list concatenation not yet implemented").debugThrow();
|
||||||
state.error<EvalError>("division by zero").debugThrow();
|
break;
|
||||||
}
|
default:
|
||||||
v.mkInt((left->integer() / right->integer()).valueWrapping());
|
state.error<EvalError>("unknown binary operator").debugThrow();
|
||||||
} else {
|
}
|
||||||
state.error<EvalError>("type error in division").debugThrow();
|
break;
|
||||||
}
|
}
|
||||||
break;
|
} else if (auto* n = node->get_if<UnaryOpNode>()) {
|
||||||
case BinaryOp::EQ:
|
Value* operand = state.allocValue();
|
||||||
v.mkBool(state.eqValues(*left, *right, noPos, ""));
|
eval_node(n->operand, *operand, env);
|
||||||
break;
|
force(operand);
|
||||||
case BinaryOp::NE:
|
|
||||||
v.mkBool(!state.eqValues(*left, *right, noPos, ""));
|
switch (n->op) {
|
||||||
break;
|
case UnaryOp::NEG:
|
||||||
case BinaryOp::LT:
|
if (operand->type() == nInt) {
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
v.mkInt((NixInt(0) - operand->integer()).valueWrapping());
|
||||||
v.mkBool(left->integer() < right->integer());
|
} else {
|
||||||
} else if (left->type() == nString && right->type() == nString) {
|
state.error<EvalError>("type error in negation").debugThrow();
|
||||||
v.mkBool(std::string(left->c_str()) < std::string(right->c_str()));
|
}
|
||||||
} else {
|
break;
|
||||||
state.error<EvalError>("type error in comparison").debugThrow();
|
case UnaryOp::NOT:
|
||||||
}
|
if (operand->type() == nBool) {
|
||||||
break;
|
v.mkBool(!operand->boolean());
|
||||||
case BinaryOp::GT:
|
} else {
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
state.error<EvalError>("type error in logical NOT").debugThrow();
|
||||||
v.mkBool(left->integer() > right->integer());
|
}
|
||||||
} else if (left->type() == nString && right->type() == nString) {
|
break;
|
||||||
v.mkBool(std::string(left->c_str()) > std::string(right->c_str()));
|
default:
|
||||||
} else {
|
state.error<EvalError>("unknown unary operator").debugThrow();
|
||||||
state.error<EvalError>("type error in comparison").debugThrow();
|
}
|
||||||
}
|
} else if (auto* n = node->get_if<IfNode>()) {
|
||||||
break;
|
Value* cond = state.allocValue();
|
||||||
case BinaryOp::LE:
|
eval_node(n->cond, *cond, env);
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
force(cond);
|
||||||
v.mkBool(left->integer() <= right->integer());
|
|
||||||
} else if (left->type() == nString && right->type() == nString) {
|
if (cond->type() != nBool) {
|
||||||
v.mkBool(std::string(left->c_str()) <= std::string(right->c_str()));
|
state.error<EvalError>("condition must be a boolean").debugThrow();
|
||||||
} else {
|
}
|
||||||
state.error<EvalError>("type error in comparison").debugThrow();
|
|
||||||
}
|
if (cond->boolean()) {
|
||||||
break;
|
eval_node(n->then_branch, v, env);
|
||||||
case BinaryOp::GE:
|
} else {
|
||||||
if (left->type() == nInt && right->type() == nInt) {
|
eval_node(n->else_branch, v, env);
|
||||||
v.mkBool(left->integer() >= right->integer());
|
}
|
||||||
} else if (left->type() == nString && right->type() == nString) {
|
} else if (auto* n = node->get_if<LetNode>()) {
|
||||||
v.mkBool(std::string(left->c_str()) >= std::string(right->c_str()));
|
auto let_env = make_env(env);
|
||||||
} else {
|
for (const auto& [name, expr] : n->bindings) {
|
||||||
state.error<EvalError>("type error in comparison").debugThrow();
|
Value* val = make_thunk(expr, env);
|
||||||
}
|
let_env->bind(val);
|
||||||
break;
|
}
|
||||||
case BinaryOp::CONCAT:
|
eval_node(n->body, v, let_env);
|
||||||
// ++ is list concatenation in Nix; string concat uses ADD (+)
|
} else if (auto* n = node->get_if<LetRecNode>()) {
|
||||||
state.error<EvalError>("list concatenation not yet implemented").debugThrow();
|
auto letrec_env = make_env(env);
|
||||||
break;
|
std::vector<Value*> thunk_vals;
|
||||||
default:
|
|
||||||
state.error<EvalError>("unknown binary operator").debugThrow();
|
for (const auto& [name, expr] : n->bindings) {
|
||||||
}
|
Value* val = make_thunk(expr, letrec_env);
|
||||||
break;
|
thunk_vals.push_back(val);
|
||||||
}
|
letrec_env->bind(val);
|
||||||
}
|
}
|
||||||
else if (auto* n = node->get_if<UnaryOpNode>()) {
|
|
||||||
Value* operand = state.allocValue();
|
eval_node(n->body, v, letrec_env);
|
||||||
eval_node(n->operand, *operand, env);
|
} else if (auto* n = node->get_if<AttrsetNode>()) {
|
||||||
force(operand);
|
auto bindings = state.buildBindings(n->attrs.size());
|
||||||
|
|
||||||
switch (n->op) {
|
IREnvironment* attr_env = env;
|
||||||
case UnaryOp::NEG:
|
if (n->recursive) {
|
||||||
if (operand->type() == nInt) {
|
attr_env = make_env(env);
|
||||||
v.mkInt((NixInt(0) - operand->integer()).valueWrapping());
|
for (const auto& [key, val] : n->attrs) {
|
||||||
} else {
|
Value* thunk = make_thunk(val, attr_env);
|
||||||
state.error<EvalError>("type error in negation").debugThrow();
|
attr_env->bind(thunk);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case UnaryOp::NOT:
|
|
||||||
if (operand->type() == nBool) {
|
for (const auto& [key, val] : n->attrs) {
|
||||||
v.mkBool(!operand->boolean());
|
Value* attr_val = state.allocValue();
|
||||||
} else {
|
if (n->recursive) {
|
||||||
state.error<EvalError>("type error in logical NOT").debugThrow();
|
eval_node(val, *attr_val, attr_env);
|
||||||
}
|
} else {
|
||||||
break;
|
eval_node(val, *attr_val, env);
|
||||||
default:
|
}
|
||||||
state.error<EvalError>("unknown unary operator").debugThrow();
|
bindings.insert(state.symbols.create(key), attr_val);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (auto* n = node->get_if<IfNode>()) {
|
v.mkAttrs(bindings.finish());
|
||||||
Value* cond = state.allocValue();
|
} else if (auto* n = node->get_if<SelectNode>()) {
|
||||||
eval_node(n->cond, *cond, env);
|
Value* obj = state.allocValue();
|
||||||
force(cond);
|
eval_node(n->expr, *obj, env);
|
||||||
|
force(obj);
|
||||||
if (cond->type() != nBool) {
|
|
||||||
state.error<EvalError>("condition must be a boolean").debugThrow();
|
Value* attr_val = state.allocValue();
|
||||||
}
|
eval_node(n->attr, *attr_val, env);
|
||||||
|
force(attr_val);
|
||||||
if (cond->boolean()) {
|
|
||||||
eval_node(n->then_branch, v, env);
|
if (obj->type() != nAttrs) {
|
||||||
} else {
|
state.error<EvalError>("selection on non-attrset").debugThrow();
|
||||||
eval_node(n->else_branch, v, env);
|
}
|
||||||
}
|
|
||||||
}
|
if (attr_val->type() != nString) {
|
||||||
else if (auto* n = node->get_if<LetNode>()) {
|
state.error<EvalError>("attribute name must be string").debugThrow();
|
||||||
auto let_env = make_env(env);
|
}
|
||||||
for (const auto& [name, expr] : n->bindings) {
|
|
||||||
Value* val = make_thunk(expr, env);
|
auto sym = state.symbols.create(attr_val->c_str());
|
||||||
let_env->bind(val);
|
auto attr = obj->attrs()->get(sym);
|
||||||
}
|
|
||||||
eval_node(n->body, v, let_env);
|
if (attr) {
|
||||||
}
|
Value* val = attr->value;
|
||||||
else if (auto* n = node->get_if<LetRecNode>()) {
|
force(val);
|
||||||
auto letrec_env = make_env(env);
|
v = *val;
|
||||||
std::vector<Value*> thunk_vals;
|
} else if (n->default_expr) {
|
||||||
|
eval_node(*n->default_expr, v, env);
|
||||||
for (const auto& [name, expr] : n->bindings) {
|
} else {
|
||||||
Value* val = make_thunk(expr, letrec_env);
|
state.error<EvalError>("attribute not found").debugThrow();
|
||||||
thunk_vals.push_back(val);
|
}
|
||||||
letrec_env->bind(val);
|
} else if (auto* n = node->get_if<HasAttrNode>()) {
|
||||||
}
|
Value* obj = state.allocValue();
|
||||||
|
eval_node(n->expr, *obj, env);
|
||||||
eval_node(n->body, v, letrec_env);
|
force(obj);
|
||||||
}
|
|
||||||
else if (auto* n = node->get_if<AttrsetNode>()) {
|
Value* attr_val = state.allocValue();
|
||||||
auto bindings = state.buildBindings(n->attrs.size());
|
eval_node(n->attr, *attr_val, env);
|
||||||
|
force(attr_val);
|
||||||
IREnvironment* attr_env = env;
|
|
||||||
if (n->recursive) {
|
if (obj->type() != nAttrs) {
|
||||||
attr_env = make_env(env);
|
v.mkBool(false);
|
||||||
for (const auto& [key, val] : n->attrs) {
|
} else if (attr_val->type() != nString) {
|
||||||
Value* thunk = make_thunk(val, attr_env);
|
state.error<EvalError>("attribute name must be string").debugThrow();
|
||||||
attr_env->bind(thunk);
|
} else {
|
||||||
}
|
auto sym = state.symbols.create(attr_val->c_str());
|
||||||
}
|
auto attr = obj->attrs()->get(sym);
|
||||||
|
v.mkBool(attr != nullptr);
|
||||||
for (const auto& [key, val] : n->attrs) {
|
}
|
||||||
Value* attr_val = state.allocValue();
|
} else if (auto* n = node->get_if<WithNode>()) {
|
||||||
if (n->recursive) {
|
Value* attrs = state.allocValue();
|
||||||
eval_node(val, *attr_val, attr_env);
|
eval_node(n->attrs, *attrs, env);
|
||||||
} else {
|
force(attrs);
|
||||||
eval_node(val, *attr_val, env);
|
|
||||||
}
|
if (attrs->type() != nAttrs) {
|
||||||
bindings.insert(state.symbols.create(key), attr_val);
|
state.error<EvalError>("with expression requires attrset").debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
v.mkAttrs(bindings.finish());
|
auto with_env = make_env(env);
|
||||||
}
|
with_env->with_attrs = attrs;
|
||||||
else if (auto* n = node->get_if<SelectNode>()) {
|
eval_node(n->body, v, with_env);
|
||||||
Value* obj = state.allocValue();
|
} else if (auto* n = node->get_if<AssertNode>()) {
|
||||||
eval_node(n->expr, *obj, env);
|
Value* cond = state.allocValue();
|
||||||
force(obj);
|
eval_node(n->cond, *cond, env);
|
||||||
|
force(cond);
|
||||||
Value* attr_val = state.allocValue();
|
|
||||||
eval_node(n->attr, *attr_val, env);
|
if (cond->type() != nBool) {
|
||||||
force(attr_val);
|
state.error<EvalError>("assertion must be boolean").debugThrow();
|
||||||
|
}
|
||||||
if (obj->type() != nAttrs) {
|
|
||||||
state.error<EvalError>("selection on non-attrset").debugThrow();
|
if (!cond->boolean()) {
|
||||||
}
|
state.error<EvalError>("assertion failed").debugThrow();
|
||||||
|
}
|
||||||
if (attr_val->type() != nString) {
|
|
||||||
state.error<EvalError>("attribute name must be string").debugThrow();
|
eval_node(n->body, v, env);
|
||||||
}
|
} else {
|
||||||
|
v.mkNull();
|
||||||
auto sym = state.symbols.create(attr_val->c_str());
|
|
||||||
auto attr = obj->attrs()->get(sym);
|
|
||||||
|
|
||||||
if (attr) {
|
|
||||||
Value* val = attr->value;
|
|
||||||
force(val);
|
|
||||||
v = *val;
|
|
||||||
} else if (n->default_expr) {
|
|
||||||
eval_node(*n->default_expr, v, env);
|
|
||||||
} else {
|
|
||||||
state.error<EvalError>("attribute not found").debugThrow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (auto* n = node->get_if<HasAttrNode>()) {
|
|
||||||
Value* obj = state.allocValue();
|
|
||||||
eval_node(n->expr, *obj, env);
|
|
||||||
force(obj);
|
|
||||||
|
|
||||||
Value* attr_val = state.allocValue();
|
|
||||||
eval_node(n->attr, *attr_val, env);
|
|
||||||
force(attr_val);
|
|
||||||
|
|
||||||
if (obj->type() != nAttrs) {
|
|
||||||
v.mkBool(false);
|
|
||||||
} else if (attr_val->type() != nString) {
|
|
||||||
state.error<EvalError>("attribute name must be string").debugThrow();
|
|
||||||
} else {
|
|
||||||
auto sym = state.symbols.create(attr_val->c_str());
|
|
||||||
auto attr = obj->attrs()->get(sym);
|
|
||||||
v.mkBool(attr != nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (auto* n = node->get_if<WithNode>()) {
|
|
||||||
Value* attrs = state.allocValue();
|
|
||||||
eval_node(n->attrs, *attrs, env);
|
|
||||||
force(attrs);
|
|
||||||
|
|
||||||
if (attrs->type() != nAttrs) {
|
|
||||||
state.error<EvalError>("with expression requires attrset").debugThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto with_env = make_env(env);
|
|
||||||
with_env->with_attrs = attrs;
|
|
||||||
eval_node(n->body, v, with_env);
|
|
||||||
}
|
|
||||||
else if (auto* n = node->get_if<AssertNode>()) {
|
|
||||||
Value* cond = state.allocValue();
|
|
||||||
eval_node(n->cond, *cond, env);
|
|
||||||
force(cond);
|
|
||||||
|
|
||||||
if (cond->type() != nBool) {
|
|
||||||
state.error<EvalError>("assertion must be boolean").debugThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cond->boolean()) {
|
|
||||||
state.error<EvalError>("assertion failed").debugThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
eval_node(n->body, v, env);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
v.mkNull();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Evaluator::Evaluator(EvalState& state) : pImpl(std::make_unique<Impl>(state)) {}
|
Evaluator::Evaluator(EvalState& state) : pImpl(std::make_unique<Impl>(state)) {}
|
||||||
Evaluator::~Evaluator() = default;
|
Evaluator::~Evaluator() = default;
|
||||||
|
|
||||||
void Evaluator::eval_to_nix(const std::shared_ptr<Node>& ir_node,
|
void Evaluator::eval_to_nix(const std::shared_ptr<Node>& ir_node, Value& result,
|
||||||
Value& result,
|
|
||||||
IREnvironment* env) {
|
IREnvironment* env) {
|
||||||
pImpl->eval_node(ir_node, result, env);
|
pImpl->eval_node(ir_node, result, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace nix_irc
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue