diff --git a/CMakeLists.txt b/CMakeLists.txt index 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-config diff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cpp new file mode 100644 index 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 +#include + +namespace nix_irc { + +using namespace nix; + +struct IREnvironment { + IREnvironment* parent; + std::vector 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 expr; + IREnvironment* env; + bool blackholed; + + Thunk(std::shared_ptr e, IREnvironment* en) + : expr(e), env(en), blackholed(false) {} +}; + +struct Evaluator::Impl { + EvalState& state; + std::unordered_map> thunks; + std::vector> 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(env)); + return env; + } + + Value* make_thunk(const std::shared_ptr& expr, IREnvironment* env) { + Value* v = state.allocValue(); + thunks[v] = std::make_unique(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("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, Value& v, IREnvironment* env) { + if (!node) { + v.mkNull(); + return; + } + + if (auto* n = node->get_if()) { + v.mkInt(n->value); + } + else if (auto* n = node->get_if()) { + v.mkString(n->value); + } + else if (auto* n = node->get_if()) { + v.mkPath(state.rootPath(CanonPath(n->value))); + } + else if (auto* n = node->get_if()) { + v.mkBool(n->value); + } + else if (auto* n = node->get_if()) { + v.mkNull(); + } + else if (auto* n = node->get_if()) { + 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("variable not found").debugThrow(); + } + force(bound); + v = *bound; + } + else if (auto* n = node->get_if()) { + 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(n->arity), + .fun = primOp + }); + } + else if (auto* n = node->get_if()) { + 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()) { + 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("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("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("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("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("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("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("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("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("type error in multiplication").debugThrow(); + } + break; + case BinaryOp::DIV: + if (left->type() == nInt && right->type() == nInt) { + if (right->integer() == NixInt(0)) { + state.error("division by zero").debugThrow(); + } + v.mkInt((left->integer() / right->integer()).valueWrapping()); + } else { + state.error("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("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("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("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("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("type error in concatenation").debugThrow(); + } + break; + default: + state.error("unknown binary operator").debugThrow(); + } + break; + } + } + else if (auto* n = node->get_if()) { + 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("type error in negation").debugThrow(); + } + break; + case UnaryOp::NOT: + if (operand->type() == nBool) { + v.mkBool(!operand->boolean()); + } else { + state.error("type error in logical NOT").debugThrow(); + } + break; + default: + state.error("unknown unary operator").debugThrow(); + } + } + else if (auto* n = node->get_if()) { + Value* cond = state.allocValue(); + eval_node(n->cond, *cond, env); + force(cond); + + if (cond->type() != nBool) { + state.error("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()) { + 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()) { + auto letrec_env = make_env(env); + std::vector 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()) { + 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()) { + 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("selection on non-attrset").debugThrow(); + } + + if (attr_val->type() != nString) { + state.error("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("attribute not found").debugThrow(); + } + } + else if (auto* n = node->get_if()) { + 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("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()) { + Value* attrs = state.allocValue(); + eval_node(n->attrs, *attrs, env); + force(attrs); + + if (attrs->type() != nAttrs) { + state.error("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()) { + Value* cond = state.allocValue(); + eval_node(n->cond, *cond, env); + force(cond); + + if (cond->type() != nBool) { + state.error("assertion must be boolean").debugThrow(); + } + + if (!cond->boolean()) { + state.error("assertion failed").debugThrow(); + } + + eval_node(n->body, v, env); + } + else { + v.mkNull(); + } + } +}; + +Evaluator::Evaluator(EvalState& state) : pImpl(std::make_unique(state)) {} +Evaluator::~Evaluator() = default; + +void Evaluator::eval_to_nix(const std::shared_ptr& ir_node, + Value& result, + IREnvironment* env) { + pImpl->eval_node(ir_node, result, env); +} + +} diff --git a/src/irc/evaluator.h b/src/irc/evaluator.h new file mode 100644 index 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 +#include + +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& ir_node, + nix::Value& result, + IREnvironment* env = nullptr); + +private: + struct Impl; + std::unique_ptr pImpl; +}; + +} + +#endif