clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 775bb42c6325
tarballunverified4 files changed+116-60
@@ -5,10 +5,7 @@ #include "evaluator.h" #include "nix/expr/eval.hh" #include "nix/expr/value.hh"-#include "nix/util/error.hh" #include "nix/util/url.hh"--#include <stdexcept> #include <unordered_map> namespace nix_irc {@@ -67,11 +64,7 @@ struct Evaluator::Impl { explicit Impl(EvalState& s) : state(s) {} - ~Impl() {- for (auto& env : environments) {- delete env.release();- }- }+ // Destructor not needed - unique_ptr handles cleanup automatically IREnvironment* make_env(IREnvironment* parent = nullptr) { auto env = new IREnvironment(parent);@@ -124,6 +117,11 @@ struct Evaluator::Impl { auto parsed = parseURL(n->value, true); // Store URI with context - use simple mkString with context v.mkString(parsed.to_string(), nix::NixStringContext{});+ } else if (auto* n = node->get_if<ConstLookupPathNode>()) {+ // Lookup path like <nixpkgs>; resolve via Nix search path+ // We can use EvalState's searchPath to resolve+ auto path = state.findFile(n->value);+ v.mkPath(path); } else if (auto* n = node->get_if<VarNode>()) { Value* bound = env ? env->lookup(n->index) : nullptr; if (!bound && env && n->name.has_value()) {@@ -369,17 +367,16 @@ struct Evaluator::Impl { } 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);+ // Create thunks in let_env so bindings can reference each other+ Value* val = make_thunk(expr, let_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); } @@ -389,6 +386,8 @@ struct Evaluator::Impl { IREnvironment* attr_env = env; if (n->recursive) {+ // For recursive attrsets, create environment where all bindings can+ // see each other attr_env = make_env(env); for (const auto& [key, val] : n->attrs) { Value* thunk = make_thunk(val, attr_env);@@ -396,13 +395,9 @@ struct Evaluator::Impl { } } + // Attributes should be lazy, so store as thunks and not evaluated values 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);- }+ Value* attr_val = make_thunk(val, attr_env); bindings.insert(state.symbols.create(key), attr_val); } @@ -480,6 +475,21 @@ struct Evaluator::Impl { } eval_node(n->body, v, env);+ } else if (auto* n = node->get_if<ImportNode>()) {+ // Evaluate path expression to get the file path+ Value* path_val = state.allocValue();+ eval_node(n->path, *path_val, env);+ force(path_val);++ // Path should be a string or path type, convert to SourcePath+ if (path_val->type() == nPath) {+ state.evalFile(path_val->path(), v);+ } else if (path_val->type() == nString) {+ auto path = state.rootPath(CanonPath(path_val->c_str()));+ state.evalFile(path, v);+ } else {+ state.error<EvalError>("import argument must be a path or string").debugThrow();+ } } else { v.mkNull(); }@@ -4,8 +4,6 @@ #include <cstdlib> #include <iostream> #include <memory>-#include <regex>-#include <sstream> #include <stdexcept> #include <vector> @@ -24,22 +22,27 @@ static std::string read_file(const std::string& path) { if (!f) { throw std::runtime_error("Cannot open file: " + path); }++ // Ensure FILE* is always closed+ auto file_closer = [](FILE* fp) {+ if (fp)+ fclose(fp);+ };+ std::unique_ptr<FILE, decltype(file_closer)> file_guard(f, file_closer);+ fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); std::string content(size, '\0'); if (fread(content.data(), 1, size, f) != static_cast<size_t>(size)) {- fclose(f); throw std::runtime_error("Failed to read file: " + path); }- fclose(f); return content; } static std::pair<std::string, std::string> run_command(const std::string& cmd) { std::array<char, 256> buffer; std::string result;- std::string error; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe)@@ -53,7 +56,7 @@ static std::pair<std::string, std::string> run_command(const std::string& cmd) { if (status != 0) { throw std::runtime_error("Command failed: " + cmd); }- return {result, error};+ return {result, ""}; } struct Token {@@ -68,6 +71,7 @@ struct Token { STRING, STRING_INTERP, PATH,+ LOOKUP_PATH, INT, FLOAT, URI,@@ -81,6 +85,7 @@ struct Token { ASSERT, WITH, INHERIT,+ IMPORT, DOT, SEMICOLON, COLON,@@ -204,7 +209,29 @@ public: emit(TOKEN(SLASH)); } } else if (c == '<') {- emit(TOKEN(LT));+ // Check for lookup path <nixpkgs> vs comparison operator+ size_t end = pos + 1;+ bool is_lookup_path = false;++ // Scan for valid lookup path characters until >+ while (end < input.size() &&+ (isalnum(input[end]) || input[end] == '-' || input[end] == '_' ||+ input[end] == '/' || input[end] == '.')) {+ end++;+ }++ // If we found > and there's content, it's a lookup path+ if (end < input.size() && input[end] == '>' && end > pos + 1) {+ std::string path = input.substr(pos + 1, end - pos - 1);+ tokens.push_back({Token::LOOKUP_PATH, path, line, col});+ pos = end + 1;+ col += (end - pos + 1);+ is_lookup_path = true;+ }++ if (!is_lookup_path) {+ emit(TOKEN(LT));+ } } else if (c == '>') { emit(TOKEN(GT)); } else if (c == '!') {@@ -430,6 +457,8 @@ private: type = Token::WITH; else if (ident == "inherit") type = Token::INHERIT;+ else if (ident == "import")+ type = Token::IMPORT; else if (ident == "true") type = Token::BOOL; else if (ident == "false")@@ -620,42 +649,18 @@ public: if (name.type == Token::IDENT) { advance(); auto attr = std::make_shared<Node>(ConstStringNode(name.value));- auto result = std::make_shared<Node>(SelectNode(left, attr));-- if (consume(Token::DOT)) {- Token name2 = current();- if (name2.type == Token::IDENT) {- advance();- auto attr2 = std::make_shared<Node>(ConstStringNode(name2.value));- auto* curr = result->get_if<SelectNode>();- while (curr && consume(Token::DOT)) {- Token n = current();- expect(Token::IDENT);- auto a = std::make_shared<Node>(ConstStringNode(n.value));- curr->attr =- std::make_shared<Node>(AppNode(std::make_shared<Node>(AppNode(curr->attr, a)),- std::make_shared<Node>(ConstNullNode())));- }- }- }- return result;- } else if (consume(Token::LBRACE)) {- auto result = std::make_shared<Node>(- SelectNode(left, std::make_shared<Node>(ConstStringNode(name.value))));- parse_expr_attrs(result);- expect(Token::RBRACE);- return result;+ left = std::make_shared<Node>(SelectNode(left, attr));+ // Continue loop to handle multi-dot selections (a.b.c)+ continue; }- return left;+ // If we get here, the token after DOT was not IDENT or LBRACE+ // This is a parse error, but we'll just return what we have+ break; } return left; } - void parse_expr_attrs(std::shared_ptr<Node>&) {- // Extended selection syntax- }- std::shared_ptr<Node> parse_expr2() { std::shared_ptr<Node> left = parse_expr3(); @@ -679,6 +684,12 @@ public: } std::shared_ptr<Node> parse_expr3() {+ // Handle import expression+ if (consume(Token::IMPORT)) {+ auto path_expr = parse_expr3();+ return std::make_shared<Node>(ImportNode(path_expr));+ }+ // Handle unary operators if (consume(Token::MINUS)) { auto operand = parse_expr3();@@ -742,6 +753,11 @@ public: return std::make_shared<Node>(ConstPathNode(t.value)); } + if (t.type == Token::LOOKUP_PATH) {+ advance();+ return std::make_shared<Node>(ConstLookupPathNode(t.value));+ }+ if (t.type == Token::BOOL) { advance(); return std::make_shared<Node>(ConstBoolNode(t.value == "true"));@@ -1,7 +1,6 @@ #include "serializer.h" #include <cstring> #include <iostream>-#include <sstream> namespace nix_irc { @@ -43,6 +42,8 @@ struct Serializer::Impl { return NodeType::CONST_NULL; if (node.holds<ConstURINode>()) return NodeType::CONST_URI;+ if (node.holds<ConstLookupPathNode>())+ return NodeType::CONST_LOOKUP_PATH; if (node.holds<VarNode>()) return NodeType::VAR; if (node.holds<LambdaNode>())@@ -53,6 +54,8 @@ struct Serializer::Impl { return NodeType::BINARY_OP; if (node.holds<UnaryOpNode>()) return NodeType::UNARY_OP;+ if (node.holds<ImportNode>())+ return NodeType::IMPORT; if (node.holds<AttrsetNode>()) return NodeType::ATTRSET; if (node.holds<SelectNode>())@@ -97,6 +100,8 @@ struct Serializer::Impl { // No data for null } else if (auto* n = node.get_if<ConstURINode>()) { write_string(n->value);+ } else if (auto* n = node.get_if<ConstLookupPathNode>()) {+ write_string(n->value); } else if (auto* n = node.get_if<VarNode>()) { write_u32(n->index); } else if (auto* n = node.get_if<LambdaNode>()) {@@ -118,6 +123,9 @@ struct Serializer::Impl { write_u8(static_cast<uint8_t>(n->op)); if (n->operand) write_node(*n->operand);+ } else if (auto* n = node.get_if<ImportNode>()) {+ if (n->path)+ write_node(*n->path); } else if (auto* n = node.get_if<AttrsetNode>()) { write_u8(n->recursive ? 1 : 0); write_u32(n->attrs.size());@@ -289,6 +297,10 @@ struct Deserializer::Impl { std::string val = read_string(); return std::make_shared<Node>(ConstURINode(val, line)); }+ case NodeType::CONST_LOOKUP_PATH: {+ std::string val = read_string();+ return std::make_shared<Node>(ConstLookupPathNode(val, line));+ } case NodeType::VAR: { uint32_t index = read_u32(); return std::make_shared<Node>(VarNode(index, "", line));@@ -314,6 +326,10 @@ struct Deserializer::Impl { auto operand = read_node(); return std::make_shared<Node>(UnaryOpNode(op, operand, line)); }+ case NodeType::IMPORT: {+ auto path = read_node();+ return std::make_shared<Node>(ImportNode(path, line));+ } case NodeType::ATTRSET: { bool recursive = read_u8() != 0; uint32_t num_attrs = read_u32();@@ -2,10 +2,8 @@ #define NIX_IRC_TYPES_H #include <cstdint>-#include <fstream> #include <memory> #include <optional>-#include <sstream> #include <string> #include <unordered_map> #include <utility>@@ -25,11 +23,13 @@ enum class NodeType : uint8_t { CONST_BOOL = 0x04, CONST_NULL = 0x05, CONST_URI = 0x07,+ CONST_LOOKUP_PATH = 0x08, VAR = 0x10, LAMBDA = 0x20, APP = 0x21, BINARY_OP = 0x22, UNARY_OP = 0x23,+ IMPORT = 0x24, ATTRSET = 0x30, SELECT = 0x31, HAS_ATTR = 0x34,@@ -107,6 +107,12 @@ struct ConstURINode { ConstURINode(std::string v = "", uint32_t l = 0) : value(std::move(v)), line(l) {} }; +struct ConstLookupPathNode {+ std::string value; // e.g., "nixpkgs" or "nixpkgs/lib"+ uint32_t line = 0;+ ConstLookupPathNode(std::string v = "", uint32_t l = 0) : value(std::move(v)), line(l) {}+};+ struct VarNode { uint32_t index = 0; std::optional<std::string> name;@@ -204,6 +210,12 @@ struct AssertNode { AssertNode(std::shared_ptr<Node> c, std::shared_ptr<Node> b, uint32_t l = 0); }; +struct ImportNode {+ std::shared_ptr<Node> path; // Path expression to import+ uint32_t line = 0;+ ImportNode(std::shared_ptr<Node> p, uint32_t l = 0);+};+ struct ThunkNode { std::shared_ptr<Node> expr; uint32_t line = 0;@@ -221,9 +233,9 @@ class Node { public: using Variant = std::variant<ConstIntNode, ConstFloatNode, ConstStringNode, ConstPathNode, ConstBoolNode,- ConstNullNode, ConstURINode, VarNode, LambdaNode, AppNode, BinaryOpNode,- UnaryOpNode, AttrsetNode, SelectNode, HasAttrNode, WithNode, IfNode, LetNode,- LetRecNode, AssertNode, ThunkNode, ForceNode>;+ ConstNullNode, ConstURINode, ConstLookupPathNode, VarNode, LambdaNode, AppNode,+ BinaryOpNode, UnaryOpNode, ImportNode, AttrsetNode, SelectNode, HasAttrNode,+ WithNode, IfNode, LetNode, LetRecNode, AssertNode, ThunkNode, ForceNode>; Variant data; @@ -270,6 +282,8 @@ inline LetRecNode::LetRecNode(std::shared_ptr<Node> b, uint32_t l) : body(std::m inline AssertNode::AssertNode(std::shared_ptr<Node> c, std::shared_ptr<Node> b, uint32_t l) : cond(std::move(c)), body(std::move(b)), line(l) {} +inline ImportNode::ImportNode(std::shared_ptr<Node> p, uint32_t l) : path(std::move(p)), line(l) {}+ inline ThunkNode::ThunkNode(std::shared_ptr<Node> e, uint32_t l) : expr(std::move(e)), line(l) {} inline ForceNode::ForceNode(std::shared_ptr<Node> e, uint32_t l) : expr(std::move(e)), line(l) {}