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:
raf 2026-02-22 00:16:42 +03:00
commit 63a9eddc49
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -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