clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 63a9eddc4973
tarballunverified1 files changed+406-430
@@ -7,481 +7,457 @@ #include "nix/expr/value.hh" #include "nix/util/error.hh" -#include <unordered_map> #include <stdexcept>+#include <unordered_map> namespace nix_irc { using namespace nix; struct IREnvironment {- IREnvironment* parent;- std::vector<Value*> bindings;- Value* with_attrs;-- explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {}-- void bind(Value* val) {- bindings.push_back(val);+ IREnvironment* parent;+ std::vector<Value*> bindings;+ Value* with_attrs;++ explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {}++ void bind(Value* 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; }-- 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_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; }- return nullptr;- }-- 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;+ }+ env = env->parent; }+ return nullptr;+ } }; struct Thunk {- std::shared_ptr<Node> expr;- IREnvironment* env;- bool blackholed;+ std::shared_ptr<Node> expr;+ IREnvironment* env;+ bool blackholed; - Thunk(std::shared_ptr<Node> e, IREnvironment* en)- : expr(e), env(en), blackholed(false) {}+ Thunk(const std::shared_ptr<Node>& e, IREnvironment* en) : expr(e), env(en), blackholed(false) {} }; struct Evaluator::Impl {- EvalState& state;- std::unordered_map<Value*, std::unique_ptr<Thunk>> thunks;- std::vector<std::unique_ptr<IREnvironment>> environments;+ EvalState& state;+ std::unordered_map<Value*, std::unique_ptr<Thunk>> thunks;+ std::vector<std::unique_ptr<IREnvironment>> environments; - explicit Impl(EvalState& s) : state(s) {}+ explicit Impl(EvalState& s) : state(s) {} - ~Impl() {- for (auto& env : environments) {- delete env.release();- }+ ~Impl() {+ for (auto& env : environments) {+ delete env.release(); }-- IREnvironment* make_env(IREnvironment* parent = nullptr) {- auto env = new IREnvironment(parent);- environments.push_back(std::unique_ptr<IREnvironment>(env));- return env;+ }++ 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; } - 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;+ Thunk* thunk = it->second.get();+ if (thunk->blackholed) {+ state.error<EvalError>("infinite recursion encountered").debugThrow(); } - void force(Value* v) {- auto it = thunks.find(v);- if (it == thunks.end()) {- return;- }-- Thunk* thunk = it->second.get();- if (thunk->blackholed) {- state.error<EvalError>("infinite recursion encountered").debugThrow();- }+ thunk->blackholed = true;+ eval_node(thunk->expr, *v, thunk->env);+ thunks.erase(v);+ } - thunk->blackholed = true;- eval_node(thunk->expr, *v, thunk->env);- thunks.erase(v);+ void eval_node(const std::shared_ptr<Node>& node, Value& v, IREnvironment* env) {+ if (!node) {+ v.mkNull();+ return; } - void eval_node(const std::shared_ptr<Node>& node, Value& v, IREnvironment* env) {- if (!node) {- v.mkNull();- return;- }-- if (auto* n = node->get_if<ConstIntNode>()) {- v.mkInt(n->value);- }- else if (auto* n = node->get_if<ConstStringNode>()) {- 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>()) {- 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;-- auto primOp = [this, lambda_env, body](EvalState& state, PosIdx pos,- Value** args, Value& result) {- auto call_env = make_env(lambda_env);- call_env->bind(args[0]);- eval_node(body, result, call_env);- };-- v.mkPrimOp(new PrimOp {- .name = n->param_name.value_or("lambda"),- .arity = static_cast<size_t>(n->arity),- .fun = primOp- });- }- else if (auto* n = node->get_if<AppNode>()) {- Value* func_val = state.allocValue();- eval_node(n->func, *func_val, env);- force(func_val);-- Value* arg_val = make_thunk(n->arg, env);-- state.callFunction(*func_val, *arg_val, v, noPos);- }- else if (auto* n = node->get_if<BinaryOpNode>()) {- Value* left = state.allocValue();- Value* right = state.allocValue();-- switch (n->op) {- case BinaryOp::AND:- eval_node(n->left, *left, env);- force(left);- if (left->type() != nBool) {- state.error<EvalError>("type error in logical AND").debugThrow();- }- if (!left->boolean()) {- v.mkBool(false);- } else {- eval_node(n->right, *right, env);- force(right);- if (right->type() != nBool) {- state.error<EvalError>("type error in logical AND").debugThrow();- }- v.mkBool(right->boolean());- }- break;- case BinaryOp::OR:- eval_node(n->left, *left, env);- force(left);- if (left->type() != nBool) {- state.error<EvalError>("type error in logical OR").debugThrow();- }- if (left->boolean()) {- v.mkBool(true);- } else {- eval_node(n->right, *right, env);- force(right);- if (right->type() != nBool) {- state.error<EvalError>("type error in logical OR").debugThrow();- }- v.mkBool(right->boolean());- }- break;- case BinaryOp::IMPL:- eval_node(n->left, *left, env);- force(left);- if (left->type() != nBool) {- state.error<EvalError>("type error in implication").debugThrow();- }- if (!left->boolean()) {- v.mkBool(true);- } else {- eval_node(n->right, *right, env);- force(right);- if (right->type() != nBool) {- state.error<EvalError>("type error in implication").debugThrow();- }- v.mkBool(right->boolean());- }- break;- default:- eval_node(n->left, *left, env);- eval_node(n->right, *right, env);- force(left);- force(right);-- switch (n->op) {- case BinaryOp::ADD:- if (left->type() == nInt && right->type() == nInt) {- v.mkInt((left->integer() + right->integer()).valueWrapping());- } else if (left->type() == nString && right->type() == nString) {- v.mkString(std::string(left->c_str()) + std::string(right->c_str()));- } else {- state.error<EvalError>("type error in addition").debugThrow();- }- break;- case BinaryOp::SUB:- if (left->type() == nInt && right->type() == nInt) {- v.mkInt((left->integer() - right->integer()).valueWrapping());- } else {- state.error<EvalError>("type error in subtraction").debugThrow();- }- break;- case BinaryOp::MUL:- if (left->type() == nInt && right->type() == nInt) {- v.mkInt((left->integer() * right->integer()).valueWrapping());- } else {- state.error<EvalError>("type error in multiplication").debugThrow();- }- break;- case BinaryOp::DIV:- if (left->type() == nInt && right->type() == nInt) {- if (right->integer() == NixInt(0)) {- state.error<EvalError>("division by zero").debugThrow();- }- v.mkInt((left->integer() / right->integer()).valueWrapping());- } else {- state.error<EvalError>("type error in division").debugThrow();- }- break;- case BinaryOp::EQ:- v.mkBool(state.eqValues(*left, *right, noPos, ""));- break;- case BinaryOp::NE:- v.mkBool(!state.eqValues(*left, *right, noPos, ""));- break;- case BinaryOp::LT:- if (left->type() == nInt && right->type() == nInt) {- v.mkBool(left->integer() < right->integer());- } else if (left->type() == nString && right->type() == nString) {- v.mkBool(std::string(left->c_str()) < std::string(right->c_str()));- } else {- state.error<EvalError>("type error in comparison").debugThrow();- }- break;- case BinaryOp::GT:- if (left->type() == nInt && right->type() == nInt) {- v.mkBool(left->integer() > right->integer());- } else if (left->type() == nString && right->type() == nString) {- v.mkBool(std::string(left->c_str()) > std::string(right->c_str()));- } else {- state.error<EvalError>("type error in comparison").debugThrow();- }- break;- case BinaryOp::LE:- if (left->type() == nInt && right->type() == nInt) {- v.mkBool(left->integer() <= right->integer());- } else if (left->type() == nString && right->type() == nString) {- v.mkBool(std::string(left->c_str()) <= std::string(right->c_str()));- } else {- state.error<EvalError>("type error in comparison").debugThrow();- }- break;- case BinaryOp::GE:- if (left->type() == nInt && right->type() == nInt) {- v.mkBool(left->integer() >= right->integer());- } else if (left->type() == nString && right->type() == nString) {- v.mkBool(std::string(left->c_str()) >= std::string(right->c_str()));- } else {- state.error<EvalError>("type error in comparison").debugThrow();- }- break;- case BinaryOp::CONCAT:- // ++ is list concatenation in Nix; string concat uses ADD (+)- state.error<EvalError>("list concatenation not yet implemented").debugThrow();- break;- default:- state.error<EvalError>("unknown binary operator").debugThrow();- }- break;- }+ if (auto* n = node->get_if<ConstIntNode>()) {+ v.mkInt(n->value);+ } else if (auto* n = node->get_if<ConstStringNode>()) {+ 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;++ auto primOp = [this, lambda_env, body](EvalState& state, PosIdx pos, Value** args,+ Value& result) {+ auto call_env = make_env(lambda_env);+ call_env->bind(args[0]);+ eval_node(body, result, call_env);+ };++ v.mkPrimOp(new PrimOp{.name = n->param_name.value_or("lambda"),+ .arity = static_cast<size_t>(n->arity),+ .fun = primOp});+ } else if (auto* n = node->get_if<AppNode>()) {+ Value* func_val = state.allocValue();+ eval_node(n->func, *func_val, env);+ force(func_val);++ Value* arg_val = make_thunk(n->arg, env);++ state.callFunction(*func_val, *arg_val, v, noPos);+ } else if (auto* n = node->get_if<BinaryOpNode>()) {+ Value* left = state.allocValue();+ Value* right = state.allocValue();++ switch (n->op) {+ case BinaryOp::AND:+ eval_node(n->left, *left, env);+ force(left);+ if (left->type() != nBool) {+ state.error<EvalError>("type error in logical AND").debugThrow(); }- else if (auto* n = node->get_if<UnaryOpNode>()) {- Value* operand = state.allocValue();- eval_node(n->operand, *operand, env);- force(operand);-- switch (n->op) {- case UnaryOp::NEG:- if (operand->type() == nInt) {- v.mkInt((NixInt(0) - operand->integer()).valueWrapping());- } else {- state.error<EvalError>("type error in negation").debugThrow();- }- break;- case UnaryOp::NOT:- if (operand->type() == nBool) {- v.mkBool(!operand->boolean());- } else {- state.error<EvalError>("type error in logical NOT").debugThrow();- }- break;- default:- state.error<EvalError>("unknown unary operator").debugThrow();- }+ if (!left->boolean()) {+ v.mkBool(false);+ } else {+ eval_node(n->right, *right, env);+ force(right);+ if (right->type() != nBool) {+ state.error<EvalError>("type error in logical AND").debugThrow();+ }+ v.mkBool(right->boolean()); }- else if (auto* n = node->get_if<IfNode>()) {- Value* cond = state.allocValue();- eval_node(n->cond, *cond, env);- force(cond);-- if (cond->type() != nBool) {- state.error<EvalError>("condition must be a boolean").debugThrow();- }-- if (cond->boolean()) {- eval_node(n->then_branch, v, env);- } else {- eval_node(n->else_branch, v, env);- }+ break;+ case BinaryOp::OR:+ eval_node(n->left, *left, env);+ force(left);+ if (left->type() != nBool) {+ state.error<EvalError>("type error in logical OR").debugThrow(); }- else if (auto* n = node->get_if<LetNode>()) {- auto let_env = make_env(env);- for (const auto& [name, expr] : n->bindings) {- Value* val = make_thunk(expr, env);- let_env->bind(val);- }- eval_node(n->body, v, let_env);+ if (left->boolean()) {+ v.mkBool(true);+ } else {+ eval_node(n->right, *right, env);+ force(right);+ if (right->type() != nBool) {+ state.error<EvalError>("type error in logical OR").debugThrow();+ }+ v.mkBool(right->boolean()); }- else if (auto* n = node->get_if<LetRecNode>()) {- auto letrec_env = make_env(env);- std::vector<Value*> thunk_vals;-- for (const auto& [name, expr] : n->bindings) {- Value* val = make_thunk(expr, letrec_env);- thunk_vals.push_back(val);- letrec_env->bind(val);- }-- eval_node(n->body, v, letrec_env);+ break;+ case BinaryOp::IMPL:+ eval_node(n->left, *left, env);+ force(left);+ if (left->type() != nBool) {+ state.error<EvalError>("type error in implication").debugThrow(); }- else if (auto* n = node->get_if<AttrsetNode>()) {- auto bindings = state.buildBindings(n->attrs.size());-- IREnvironment* attr_env = env;- if (n->recursive) {- attr_env = make_env(env);- for (const auto& [key, val] : n->attrs) {- Value* thunk = make_thunk(val, attr_env);- attr_env->bind(thunk);- }- }-- for (const auto& [key, val] : n->attrs) {- Value* attr_val = state.allocValue();- if (n->recursive) {- eval_node(val, *attr_val, attr_env);- } else {- eval_node(val, *attr_val, env);- }- bindings.insert(state.symbols.create(key), attr_val);- }-- v.mkAttrs(bindings.finish());+ if (!left->boolean()) {+ v.mkBool(true);+ } else {+ eval_node(n->right, *right, env);+ force(right);+ if (right->type() != nBool) {+ state.error<EvalError>("type error in implication").debugThrow();+ }+ v.mkBool(right->boolean()); }- else if (auto* n = node->get_if<SelectNode>()) {- 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) {- state.error<EvalError>("selection on non-attrset").debugThrow();- }-- if (attr_val->type() != nString) {- state.error<EvalError>("attribute name must be string").debugThrow();- }-- 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();+ break;+ default:+ eval_node(n->left, *left, env);+ eval_node(n->right, *right, env);+ force(left);+ force(right);++ switch (n->op) {+ case BinaryOp::ADD:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkInt((left->integer() + right->integer()).valueWrapping());+ } else if (left->type() == nString && right->type() == nString) {+ v.mkString(std::string(left->c_str()) + std::string(right->c_str()));+ } else {+ state.error<EvalError>("type error in addition").debugThrow();+ }+ break;+ case BinaryOp::SUB:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkInt((left->integer() - right->integer()).valueWrapping());+ } else {+ state.error<EvalError>("type error in subtraction").debugThrow();+ }+ break;+ case BinaryOp::MUL:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkInt((left->integer() * right->integer()).valueWrapping());+ } else {+ state.error<EvalError>("type error in multiplication").debugThrow();+ }+ break;+ case BinaryOp::DIV:+ if (left->type() == nInt && right->type() == nInt) {+ if (right->integer() == NixInt(0)) {+ state.error<EvalError>("division by zero").debugThrow(); }+ v.mkInt((left->integer() / right->integer()).valueWrapping());+ } else {+ state.error<EvalError>("type error in division").debugThrow();+ }+ break;+ case BinaryOp::EQ:+ v.mkBool(state.eqValues(*left, *right, noPos, ""));+ break;+ case BinaryOp::NE:+ v.mkBool(!state.eqValues(*left, *right, noPos, ""));+ break;+ case BinaryOp::LT:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkBool(left->integer() < right->integer());+ } else if (left->type() == nString && right->type() == nString) {+ v.mkBool(std::string(left->c_str()) < std::string(right->c_str()));+ } else {+ state.error<EvalError>("type error in comparison").debugThrow();+ }+ break;+ case BinaryOp::GT:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkBool(left->integer() > right->integer());+ } else if (left->type() == nString && right->type() == nString) {+ v.mkBool(std::string(left->c_str()) > std::string(right->c_str()));+ } else {+ state.error<EvalError>("type error in comparison").debugThrow();+ }+ break;+ case BinaryOp::LE:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkBool(left->integer() <= right->integer());+ } else if (left->type() == nString && right->type() == nString) {+ v.mkBool(std::string(left->c_str()) <= std::string(right->c_str()));+ } else {+ state.error<EvalError>("type error in comparison").debugThrow();+ }+ break;+ case BinaryOp::GE:+ if (left->type() == nInt && right->type() == nInt) {+ v.mkBool(left->integer() >= right->integer());+ } else if (left->type() == nString && right->type() == nString) {+ v.mkBool(std::string(left->c_str()) >= std::string(right->c_str()));+ } else {+ state.error<EvalError>("type error in comparison").debugThrow();+ }+ break;+ case BinaryOp::CONCAT:+ // ++ is list concatenation in Nix; string concat uses ADD (+)+ state.error<EvalError>("list concatenation not yet implemented").debugThrow();+ break;+ default:+ state.error<EvalError>("unknown binary operator").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);- }+ break;+ }+ } else if (auto* n = node->get_if<UnaryOpNode>()) {+ Value* operand = state.allocValue();+ eval_node(n->operand, *operand, env);+ force(operand);++ switch (n->op) {+ case UnaryOp::NEG:+ if (operand->type() == nInt) {+ v.mkInt((NixInt(0) - operand->integer()).valueWrapping());+ } else {+ state.error<EvalError>("type error in negation").debugThrow(); }- 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);+ break;+ case UnaryOp::NOT:+ if (operand->type() == nBool) {+ v.mkBool(!operand->boolean());+ } else {+ state.error<EvalError>("type error in logical NOT").debugThrow(); }- 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);+ break;+ default:+ state.error<EvalError>("unknown unary operator").debugThrow();+ }+ } else if (auto* n = node->get_if<IfNode>()) {+ Value* cond = state.allocValue();+ eval_node(n->cond, *cond, env);+ force(cond);++ if (cond->type() != nBool) {+ state.error<EvalError>("condition must be a boolean").debugThrow();+ }++ if (cond->boolean()) {+ eval_node(n->then_branch, v, env);+ } else {+ eval_node(n->else_branch, v, env);+ }+ } else if (auto* n = node->get_if<LetNode>()) {+ auto let_env = make_env(env);+ for (const auto& [name, expr] : n->bindings) {+ Value* val = make_thunk(expr, env);+ let_env->bind(val);+ }+ eval_node(n->body, v, let_env);+ } else if (auto* n = node->get_if<LetRecNode>()) {+ auto letrec_env = make_env(env);+ std::vector<Value*> thunk_vals;++ for (const auto& [name, expr] : n->bindings) {+ Value* val = make_thunk(expr, letrec_env);+ thunk_vals.push_back(val);+ letrec_env->bind(val);+ }++ eval_node(n->body, v, letrec_env);+ } else if (auto* n = node->get_if<AttrsetNode>()) {+ auto bindings = state.buildBindings(n->attrs.size());++ IREnvironment* attr_env = env;+ if (n->recursive) {+ attr_env = make_env(env);+ for (const auto& [key, val] : n->attrs) {+ Value* thunk = make_thunk(val, attr_env);+ attr_env->bind(thunk); }- else {- v.mkNull();+ }++ for (const auto& [key, val] : n->attrs) {+ Value* attr_val = state.allocValue();+ if (n->recursive) {+ eval_node(val, *attr_val, attr_env);+ } else {+ eval_node(val, *attr_val, env); }+ bindings.insert(state.symbols.create(key), attr_val);+ }++ v.mkAttrs(bindings.finish());+ } else if (auto* n = node->get_if<SelectNode>()) {+ 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) {+ state.error<EvalError>("selection on non-attrset").debugThrow();+ }++ if (attr_val->type() != nString) {+ state.error<EvalError>("attribute name must be string").debugThrow();+ }++ 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() = default; -void Evaluator::eval_to_nix(const std::shared_ptr<Node>& ir_node,- Value& result,+void Evaluator::eval_to_nix(const std::shared_ptr<Node>& ir_node, Value& result, IREnvironment* env) {- pImpl->eval_node(ir_node, result, env);+ pImpl->eval_node(ir_node, result, env); } -}+} // namespace nix_irc