irc/parser: use explicit IR nodes for patterns and interpolation

Replaces lambda pattern desugaring with direct `LambdaPatternNode`
generation, which:

- Separates required and optional fields in pattern structure
- Preserves @-binding and ellipsis information in IR
- Remove like 50 lines of Let-binding desugaring logic

and replace string interpolation concatenation tree with
`StringInterpolationNode` to:

- Use `StringPart::make_literal/make_expr` for cleaner representation
- Optimize single-literal strings to `ConstStringNode`
- Remove `toString` builtin wrapper generation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I7d3fa038f743d02b9caae0979b79f5086a6a6964
This commit is contained in:
raf 2026-02-23 21:22:06 +03:00
commit f6481b3c01
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -1352,45 +1352,22 @@ public:
// Parse body // Parse body
auto body = parse_expr(); auto body = parse_expr();
// Desugar pattern to lambda with let bindings // Create LambdaPatternNode instead of desugaring
// { a, b ? x }: body → arg: let a = arg.a; b = if arg ? a then arg.a else x; in body auto pattern = LambdaPatternNode(body);
pattern.allow_extra = has_ellipsis;
std::string arg_name = named_arg.value_or("_arg"); pattern.at_binding = named_arg;
auto arg_var = std::make_shared<Node>(VarNode(0, arg_name));
std::vector<std::pair<std::string, std::shared_ptr<Node>>> bindings;
// Separate required and optional fields
for (const auto& field : fields) { for (const auto& field : fields) {
// Create arg.field selection PatternField pf(field.name, field.default_val);
auto select = std::make_shared<Node>(
SelectNode(arg_var, std::make_shared<Node>(ConstStringNode(field.name))));
if (field.default_val) { if (field.default_val) {
// if arg ? field then arg.field else default pattern.optional_fields.push_back(std::move(pf));
auto has_attr = std::make_shared<Node>(
HasAttrNode(arg_var, std::make_shared<Node>(ConstStringNode(field.name))));
auto if_node = std::make_shared<Node>(IfNode(has_attr, select, *field.default_val));
bindings.push_back({field.name, if_node});
} else { } else {
bindings.push_back({field.name, select}); pattern.required_fields.push_back(std::move(pf));
} }
} }
// If named pattern, also bind the argument name return std::make_shared<Node>(std::move(pattern));
if (named_arg) {
bindings.push_back({*named_arg, arg_var});
}
// Create let expression
auto let = LetNode(body);
let.bindings = std::move(bindings);
auto let_node = std::make_shared<Node>(std::move(let));
// Create lambda
auto lambda = LambdaNode(1, let_node);
lambda.param_name = arg_name;
lambda.strict_pattern = !has_ellipsis;
return std::make_shared<Node>(std::move(lambda));
} }
// Not a lambda // Not a lambda
@ -1399,7 +1376,7 @@ public:
} }
std::shared_ptr<Node> parse_string_interp(const std::string& raw) { std::shared_ptr<Node> parse_string_interp(const std::string& raw) {
std::vector<std::shared_ptr<Node>> parts; std::vector<StringPart> parts;
size_t i = 0; size_t i = 0;
std::string current_str; std::string current_str;
@ -1407,7 +1384,7 @@ public:
if (raw[i] == '$' && i + 1 < raw.size() && raw[i + 1] == '{') { if (raw[i] == '$' && i + 1 < raw.size() && raw[i + 1] == '{') {
// Save current string part if any // Save current string part if any
if (!current_str.empty()) { if (!current_str.empty()) {
parts.push_back(std::make_shared<Node>(ConstStringNode(current_str))); parts.push_back(StringPart::make_literal(current_str));
current_str.clear(); current_str.clear();
} }
@ -1463,10 +1440,8 @@ public:
tokens = saved_tokens; tokens = saved_tokens;
pos = saved_pos; pos = saved_pos;
// Convert to string using toString builtin // Add expression part (will be coerced to string during evaluation)
auto to_string = std::make_shared<Node>(VarNode(0, "toString")); parts.push_back(StringPart::make_expr(expr));
auto str_expr = std::make_shared<Node>(AppNode(to_string, expr));
parts.push_back(str_expr);
i++; // Skip } i++; // Skip }
} else { } else {
@ -1477,21 +1452,21 @@ public:
// Add remaining string part // Add remaining string part
if (!current_str.empty()) { if (!current_str.empty()) {
parts.push_back(std::make_shared<Node>(ConstStringNode(current_str))); parts.push_back(StringPart::make_literal(current_str));
} }
// Build concatenation tree // Return StringInterpolationNode
if (parts.empty()) { if (parts.empty()) {
return std::make_shared<Node>(ConstStringNode("")); return std::make_shared<Node>(ConstStringNode(""));
} }
auto result = parts[0]; // If only one literal part, return it directly as ConstStringNode
for (size_t j = 1; j < parts.size(); j++) { if (parts.size() == 1 && parts[0].type == StringPart::Type::LITERAL) {
// Use ADD (+) for string concatenation; CONCAT (++) is Nix list concatenation return std::make_shared<Node>(ConstStringNode(parts[0].literal));
result = std::make_shared<Node>(BinaryOpNode(BinaryOp::ADD, result, parts[j]));
} }
return result; // Otherwise return StringInterpolationNode
return std::make_shared<Node>(StringInterpolationNode(std::move(parts)));
} }
}; };