clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit c32328f14ddf
tarballunverified3 files changed+519-1
@@ -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-config@@ -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);+}++}@@ -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