brewery
notashelf /
c32328f14ddf8f4604a5e34a49b05c837c970b4d

nixir

public

Import-resolving Nix IR plugin

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.git

Commit c32328f14ddf

tarball

NotAShelf <raf@notashelf.dev> · 2026-02-21 21:07 UTC

unverified3 files changed+519-1
evaulator: implement IR evaluation engine

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I980aaef64177b76620e3d1f9f39001046a6a6964
diff --git a/CMakeLists.txt b/CMakeLists.txtindex 0fdccae..c310f42 100644--- a/CMakeLists.txt+++ b/CMakeLists.txt@@ -13,7 +13,7 @@ pkg_check_modules(NIX_UTIL REQUIRED IMPORTED_TARGET nix-util) pkg_check_modules(NIX_FETCHERS REQUIRED IMPORTED_TARGET nix-fetchers) pkg_check_modules(NIX_MAIN REQUIRED IMPORTED_TARGET nix-main) -# nix-irc (External IR Compiler)+# nix-irc add_executable(nix-irc     src/irc/main.cpp     src/irc/parser.cpp@@ -41,6 +41,7 @@ add_library(nix-ir-plugin MODULE     src/irc/resolver.cpp     src/irc/ir_gen.cpp     src/irc/serializer.cpp+    src/irc/evaluator.cpp )  # Include directories from pkg-configdiff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cppnew file mode 100644index 0000000..8aa64c7--- /dev/null+++ b/src/irc/evaluator.cpp@@ -0,0 +1,482 @@+#ifdef unix+#undef unix+#endif++#include "evaluator.h"+#include "nix/expr/eval.hh"+#include "nix/expr/value.hh"+#include "nix/util/error.hh"++#include <unordered_map>+#include <stdexcept>++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) {}++    IREnvironment* push() {+        return new IREnvironment(this);+    }++    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;+        }+        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;+    }+};++struct Thunk {+    std::shared_ptr<Node> expr;+    IREnvironment* env;+    bool blackholed;++    Thunk(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;++    explicit Impl(EvalState& s) : state(s) {}++    ~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;+    }++    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;+        }++        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(it);+    }++    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 {+                                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 {+                                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 {+                                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 {+                                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 {+                                state.error<EvalError>("type error in comparison").debugThrow();+                            }+                            break;+                        case BinaryOp::CONCAT:+                            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 concatenation").debugThrow();+                            }+                            break;+                        default:+                            state.error<EvalError>("unknown binary operator").debugThrow();+                    }+                    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();+                    }+                    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();+            }+        }+        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);+                }+            }++            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) {+                v = *attr->value;+            } 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,+                            IREnvironment* env) {+    pImpl->eval_node(ir_node, result, env);+}++}diff --git a/src/irc/evaluator.h b/src/irc/evaluator.hnew file mode 100644index 0000000..107bd78--- /dev/null+++ b/src/irc/evaluator.h@@ -0,0 +1,35 @@+#ifndef NIX_IRC_EVALUATOR_H+#define NIX_IRC_EVALUATOR_H++#include "types.h"+#include <memory>+#include <vector>++namespace nix {+class EvalState;+class Value;+class PosIdx;+}++namespace nix_irc {++class IRValue;+class IREnvironment;++class Evaluator {+public:+    explicit Evaluator(nix::EvalState& state);+    ~Evaluator();++    void eval_to_nix(const std::shared_ptr<Node>& ir_node,+                     nix::Value& result,+                     IREnvironment* env = nullptr);++private:+    struct Impl;+    std::unique_ptr<Impl> pImpl;+};++}++#endif