irc: support merge operator

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icfb0cc81542e637d4b91c6a5788370fb6a6a6964
This commit is contained in:
raf 2026-02-22 01:37:35 +03:00
commit 59fcd3ef92
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
4 changed files with 71 additions and 18 deletions

View file

@ -117,13 +117,13 @@ struct Evaluator::Impl {
v.mkPath(state.rootPath(CanonPath(n->value))); v.mkPath(state.rootPath(CanonPath(n->value)));
} else if (auto* n = node->get_if<ConstBoolNode>()) { } else if (auto* n = node->get_if<ConstBoolNode>()) {
v.mkBool(n->value); 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(); v.mkNull();
} else if (auto* n = node->get_if<ConstURINode>()) { } else if (auto* n = node->get_if<ConstURINode>()) {
// Parse and validate URI, then create string with URI context // Parse and validate URI, then create string with URI context
auto parsed = parseURL(n->value, true); auto parsed = parseURL(n->value, true);
// Store URI with context - the parsed URL string // Store URI with context - use simple mkString with context
v.mkString(parsed.to_string(), nix::NixStringContext{}, state.mem); v.mkString(parsed.to_string(), nix::NixStringContext{});
} else if (auto* n = node->get_if<VarNode>()) { } else if (auto* n = node->get_if<VarNode>()) {
Value* bound = env ? env->lookup(n->index) : nullptr; Value* bound = env ? env->lookup(n->index) : nullptr;
if (!bound && env && n->name.has_value()) { if (!bound && env && n->name.has_value()) {
@ -298,6 +298,32 @@ struct Evaluator::Impl {
// ++ is list concatenation in Nix; string concat uses ADD (+) // ++ is list concatenation in Nix; string concat uses ADD (+)
state.error<EvalError>("list concatenation not yet implemented").debugThrow(); state.error<EvalError>("list concatenation not yet implemented").debugThrow();
break; 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: default:
state.error<EvalError>("unknown binary operator").debugThrow(); state.error<EvalError>("unknown binary operator").debugThrow();
} }

View file

@ -162,7 +162,8 @@ struct IRGenerator::Impl {
name_resolver.bind(key); name_resolver.bind(key);
} }
std::vector<std::pair<std::string, std::shared_ptr<Node>>> new_bindings; 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)}); new_bindings.push_back({key, convert(val)});
} }
auto body = convert(n->body); auto body = convert(n->body);
@ -177,7 +178,8 @@ struct IRGenerator::Impl {
name_resolver.bind(key); name_resolver.bind(key);
} }
std::vector<std::pair<std::string, std::shared_ptr<Node>>> new_bindings; 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)}); new_bindings.push_back({key, convert(val)});
} }
auto body = convert(n->body); auto body = convert(n->body);

View file

@ -95,6 +95,7 @@ struct Token {
STAR, STAR,
SLASH, SLASH,
CONCAT, CONCAT,
MERGE,
EQEQ, EQEQ,
NE, NE,
LT, LT,
@ -173,6 +174,10 @@ public:
tokens.push_back(TOKEN(CONCAT)); tokens.push_back(TOKEN(CONCAT));
pos += 2; pos += 2;
col += 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] == '&') { } else if (c == '&' && pos + 1 < input.size() && input[pos + 1] == '&') {
tokens.push_back(TOKEN(AND)); tokens.push_back(TOKEN(AND));
pos += 2; pos += 2;
@ -237,13 +242,13 @@ public:
} else if (isalpha(c)) { } else if (isalpha(c)) {
// Check if it's a URI (contains ://) - look ahead // Check if it's a URI (contains ://) - look ahead
size_t lookahead = pos; size_t lookahead = pos;
while (lookahead < input.size() && (isalnum(input[lookahead]) || input[lookahead] == '_' || while (lookahead < input.size() &&
input[lookahead] == '-' || input[lookahead] == '+' || (isalnum(input[lookahead]) || input[lookahead] == '_' || input[lookahead] == '-' ||
input[lookahead] == '.')) input[lookahead] == '+' || input[lookahead] == '.'))
lookahead++; lookahead++;
std::string potential_scheme = input.substr(pos, lookahead - pos); std::string potential_scheme = input.substr(pos, lookahead - pos);
if (lookahead + 2 < input.size() && input[lookahead] == ':' && input[lookahead + 1] == '/' && if (lookahead + 2 < input.size() && input[lookahead] == ':' &&
input[lookahead + 2] == '/') { input[lookahead + 1] == '/' && input[lookahead + 2] == '/') {
// It's a URI, consume the whole thing // It's a URI, consume the whole thing
tokenize_uri(); tokenize_uri();
} else { } else {
@ -267,7 +272,7 @@ private:
size_t line; size_t line;
size_t col; size_t col;
void emit(Token t) { void emit(const Token& t) {
tokens.push_back(t); tokens.push_back(t);
pos++; pos++;
col++; col++;
@ -394,7 +399,7 @@ private:
void tokenize_ident() { void tokenize_ident() {
size_t start = pos; size_t start = pos;
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' || while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' ||
input[pos] == '+' || input[pos] == '.')) input[pos] == '+' || input[pos] == '.'))
pos++; pos++;
std::string ident = input.substr(start, pos - start); std::string ident = input.substr(start, pos - start);
@ -471,6 +476,8 @@ public:
// Get operator precedence (higher = tighter binding) // Get operator precedence (higher = tighter binding)
int get_precedence(Token::Type type) { int get_precedence(Token::Type type) {
switch (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: case Token::OR:
return 1; return 1;
case Token::AND: case Token::AND:
@ -511,6 +518,8 @@ public:
return BinaryOp::DIV; return BinaryOp::DIV;
case Token::CONCAT: case Token::CONCAT:
return BinaryOp::CONCAT; return BinaryOp::CONCAT;
case Token::MERGE:
return BinaryOp::MERGE;
case Token::EQEQ: case Token::EQEQ:
return BinaryOp::EQ; return BinaryOp::EQ;
case Token::NE: case Token::NE:

View file

@ -43,7 +43,23 @@ enum class NodeType : uint8_t {
ERROR = 0xFF 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 }; enum class UnaryOp : uint8_t { NEG, NOT };
@ -203,11 +219,11 @@ struct ForceNode {
// Node wraps a variant for type-safe AST // Node wraps a variant for type-safe AST
class Node { class Node {
public: public:
using Variant = std::variant<ConstIntNode, ConstFloatNode, ConstStringNode, ConstPathNode, using Variant =
ConstBoolNode, ConstNullNode, ConstURINode, VarNode, LambdaNode, std::variant<ConstIntNode, ConstFloatNode, ConstStringNode, ConstPathNode, ConstBoolNode,
AppNode, BinaryOpNode, UnaryOpNode, AttrsetNode, SelectNode, ConstNullNode, ConstURINode, VarNode, LambdaNode, AppNode, BinaryOpNode,
HasAttrNode, WithNode, IfNode, LetNode, LetRecNode, AssertNode, UnaryOpNode, AttrsetNode, SelectNode, HasAttrNode, WithNode, IfNode, LetNode,
ThunkNode, ForceNode>; LetRecNode, AssertNode, ThunkNode, ForceNode>;
Variant data; Variant data;