From 59fcd3ef92da0318e4f49d4414a8f82f3462cd06 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 22 Feb 2026 01:37:35 +0300 Subject: [PATCH] irc: support merge operator Signed-off-by: NotAShelf Change-Id: Icfb0cc81542e637d4b91c6a5788370fb6a6a6964 --- src/irc/evaluator.cpp | 32 +++++++++++++++++++++++++++++--- src/irc/ir_gen.cpp | 6 ++++-- src/irc/parser.cpp | 23 ++++++++++++++++------- src/irc/types.h | 28 ++++++++++++++++++++++------ 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cpp index d30862c..d4ddab8 100644 --- a/src/irc/evaluator.cpp +++ b/src/irc/evaluator.cpp @@ -117,13 +117,13 @@ struct Evaluator::Impl { 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()) { // NOLINT(bugprone-branch-clone) + } else if (auto* n = node->get_if()) { // NOLINT(bugprone-branch-clone) v.mkNull(); } else if (auto* n = node->get_if()) { // 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()) { 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("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("attrset merge requires two attrsets").debugThrow(); + } + + // Build a map of right attrs first (these have priority) + std::unordered_map 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("unknown binary operator").debugThrow(); } diff --git a/src/irc/ir_gen.cpp b/src/irc/ir_gen.cpp index a2561be..06318b4 100644 --- a/src/irc/ir_gen.cpp +++ b/src/irc/ir_gen.cpp @@ -162,7 +162,8 @@ struct IRGenerator::Impl { name_resolver.bind(key); } std::vector>> 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>> 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); diff --git a/src/irc/parser.cpp b/src/irc/parser.cpp index bead1e5..b3edbfc 100644 --- a/src/irc/parser.cpp +++ b/src/irc/parser.cpp @@ -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: diff --git a/src/irc/types.h b/src/irc/types.h index 5fb18f9..eb65734 100644 --- a/src/irc/types.h +++ b/src/irc/types.h @@ -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; + using Variant = + std::variant; Variant data;