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)));
} 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();
}

View file

@ -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);

View file

@ -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:

View file

@ -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;