clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 3387e0d822a2
tarballunverified4 files changed+71-18
@@ -117,13 +117,13 @@ struct Evaluator::Impl { 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)+ } else if (auto* n = node->get_if<ConstNullNode>()) { // NOLINT(bugprone-branch-clone) v.mkNull(); } else if (auto* n = node->get_if<ConstURINode>()) { // Parse and validate URI, then create string with URI context auto parsed = parseURL(n->value, true);- // Store URI with context - the parsed URL string- v.mkString(parsed.to_string(), nix::NixStringContext{}, state.mem);+ // Store URI with context - use simple mkString with context+ v.mkString(parsed.to_string(), nix::NixStringContext{}); } else if (auto* n = node->get_if<VarNode>()) { Value* bound = env ? env->lookup(n->index) : nullptr; if (!bound && env && n->name.has_value()) {@@ -298,6 +298,32 @@ struct Evaluator::Impl { // ++ is list concatenation in Nix; string concat uses ADD (+) state.error<EvalError>("list concatenation not yet implemented").debugThrow(); break;+ case BinaryOp::MERGE: {+ // // is attrset merge - right overrides left+ if (left->type() != nAttrs || right->type() != nAttrs) {+ state.error<EvalError>("attrset merge requires two attrsets").debugThrow();+ }++ // Build a map of right attrs first (these have priority)+ std::unordered_map<Symbol, Value*> right_attrs;+ for (auto& attr : *right->attrs()) {+ right_attrs[attr.name] = attr.value;+ }++ // Copy right attrs to result+ auto builder = state.buildBindings(left->attrs()->size() + right->attrs()->size());+ for (auto& attr : *right->attrs()) {+ builder.insert(attr.name, attr.value);+ }+ // Add left attrs that don't exist in right+ for (auto& attr : *left->attrs()) {+ if (right_attrs.find(attr.name) == right_attrs.end()) {+ builder.insert(attr.name, attr.value);+ }+ }+ v.mkAttrs(builder.finish());+ break;+ } default: state.error<EvalError>("unknown binary operator").debugThrow(); }@@ -162,7 +162,8 @@ struct IRGenerator::Impl { name_resolver.bind(key); } std::vector<std::pair<std::string, std::shared_ptr<Node>>> new_bindings;- for (const auto& [key, val] : n->bindings) {+ new_bindings.reserve(n->bindings.size());+for (const auto& [key, val] : n->bindings) { new_bindings.push_back({key, convert(val)}); } auto body = convert(n->body);@@ -177,7 +178,8 @@ struct IRGenerator::Impl { name_resolver.bind(key); } std::vector<std::pair<std::string, std::shared_ptr<Node>>> new_bindings;- for (const auto& [key, val] : n->bindings) {+ new_bindings.reserve(n->bindings.size());+for (const auto& [key, val] : n->bindings) { new_bindings.push_back({key, convert(val)}); } auto body = convert(n->body);@@ -95,6 +95,7 @@ struct Token { STAR, SLASH, CONCAT,+ MERGE, EQEQ, NE, LT,@@ -173,6 +174,10 @@ public: tokens.push_back(TOKEN(CONCAT)); pos += 2; col += 2;+ } else if (c == '/' && pos + 1 < input.size() && input[pos + 1] == '/') {+ tokens.push_back(TOKEN(MERGE));+ pos += 2;+ col += 2; } else if (c == '&' && pos + 1 < input.size() && input[pos + 1] == '&') { tokens.push_back(TOKEN(AND)); pos += 2;@@ -237,13 +242,13 @@ public: } else if (isalpha(c)) { // Check if it's a URI (contains ://) - look ahead size_t lookahead = pos;- while (lookahead < input.size() && (isalnum(input[lookahead]) || input[lookahead] == '_' ||- input[lookahead] == '-' || input[lookahead] == '+' ||- input[lookahead] == '.'))+ while (lookahead < input.size() &&+ (isalnum(input[lookahead]) || input[lookahead] == '_' || input[lookahead] == '-' ||+ input[lookahead] == '+' || input[lookahead] == '.')) lookahead++; std::string potential_scheme = input.substr(pos, lookahead - pos);- if (lookahead + 2 < input.size() && input[lookahead] == ':' && input[lookahead + 1] == '/' &&- input[lookahead + 2] == '/') {+ if (lookahead + 2 < input.size() && input[lookahead] == ':' &&+ input[lookahead + 1] == '/' && input[lookahead + 2] == '/') { // It's a URI, consume the whole thing tokenize_uri(); } else {@@ -267,7 +272,7 @@ private: size_t line; size_t col; - void emit(Token t) {+ void emit(const Token& t) { tokens.push_back(t); pos++; col++;@@ -394,7 +399,7 @@ private: void tokenize_ident() { size_t start = pos; while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' ||- input[pos] == '+' || input[pos] == '.'))+ input[pos] == '+' || input[pos] == '.')) pos++; std::string ident = input.substr(start, pos - start); @@ -471,6 +476,8 @@ public: // Get operator precedence (higher = tighter binding) int get_precedence(Token::Type type) { switch (type) {+ case Token::MERGE:+ return 1; // Low precedence - binds loosely, but must be > 0 to be recognized as binary op case Token::OR: return 1; case Token::AND:@@ -511,6 +518,8 @@ public: return BinaryOp::DIV; case Token::CONCAT: return BinaryOp::CONCAT;+ case Token::MERGE:+ return BinaryOp::MERGE; case Token::EQEQ: return BinaryOp::EQ; case Token::NE:@@ -43,7 +43,23 @@ enum class NodeType : uint8_t { ERROR = 0xFF }; -enum class BinaryOp : uint8_t { ADD, SUB, MUL, DIV, CONCAT, EQ, NE, LT, GT, LE, GE, AND, OR, IMPL };+enum class BinaryOp : uint8_t {+ ADD,+ SUB,+ MUL,+ DIV,+ CONCAT,+ EQ,+ NE,+ LT,+ GT,+ LE,+ GE,+ AND,+ OR,+ IMPL,+ MERGE+}; enum class UnaryOp : uint8_t { NEG, NOT }; @@ -203,11 +219,11 @@ struct ForceNode { // Node wraps a variant for type-safe AST 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>;+ 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>; Variant data;