irc/parser: fix list parsing and function application

Fixes bug where `concat [1 2 3] [4 5 6]` tried to apply integer 1
as a function.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6f373dd83bcac9e59286b0448472200b6a6a6964
This commit is contained in:
raf 2026-02-23 02:10:34 +03:00
commit 6587d07833
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -636,8 +636,9 @@ private:
void tokenize_ident() {
size_t start = pos;
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' ||
input[pos] == '+' || input[pos] == '.'))
// Note: Don't include '.' here - it's used for selection (a.b.c)
// URIs are handled separately by checking for '://' pattern
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-'))
pos++;
std::string ident = input.substr(start, pos - start);
@ -927,16 +928,25 @@ public:
std::shared_ptr<Node> left = parse_expr3();
while (true) {
if (current().type == Token::LBRACKET) {
advance();
auto arg = parse_expr();
expect(Token::RBRACKET);
left = std::make_shared<Node>(AppNode(left, arg));
} else if (current().type == Token::STRING) {
if (current().type == Token::STRING) {
Token s = current();
advance();
auto arg = std::make_shared<Node>(ConstStringNode(s.value));
left = std::make_shared<Node>(AppNode(left, arg));
} else if (current().type == Token::LPAREN) {
// Function application with parenthesized argument: func (expr)
advance();
auto arg = parse_expr();
expect(Token::RPAREN);
left = std::make_shared<Node>(AppNode(left, arg));
} else if (current().type == Token::IDENT || current().type == Token::INT ||
current().type == Token::FLOAT || current().type == Token::BOOL ||
current().type == Token::PATH || current().type == Token::LOOKUP_PATH ||
current().type == Token::URI || current().type == Token::LBRACKET) {
// Juxtaposition application: f x
// Parse the argument as a primary expression (which handles lists, etc.)
auto arg = parse_expr3();
left = std::make_shared<Node>(AppNode(left, arg));
} else {
break;
}
@ -969,6 +979,16 @@ public:
return expr;
}
// Handle rec { ... } syntax
if (consume(Token::REC)) {
expect(Token::LBRACE);
auto attrs = parse_attrs();
if (auto* attrset = attrs->get_if<AttrsetNode>()) {
attrset->recursive = true;
}
return attrs;
}
if (consume(Token::LBRACE)) {
return parse_attrs();
}
@ -1151,6 +1171,35 @@ public:
return std::make_shared<Node>(std::move(attrs));
}
// Parse a list element: supports selections but NOT juxtaposition application
// This prevents [1 2 3] from being parsed as ((1 2) 3)
std::shared_ptr<Node> parse_list_element() {
auto left = parse_expr3();
// Handle selections (a.b.c)
while (current().type == Token::DOT) {
advance();
Token name = current();
if (name.type == Token::IDENT) {
advance();
auto attr = std::make_shared<Node>(ConstStringNode(name.value));
left = std::make_shared<Node>(SelectNode(left, attr));
continue;
}
break;
}
// Check for 'or' default value
if (left->get_if<SelectNode>() && current().type == Token::IDENT && current().value == "or") {
advance();
auto default_expr = parse_expr3();
auto* select = left->get_if<SelectNode>();
select->default_expr = default_expr;
}
return left;
}
std::shared_ptr<Node> parse_list() {
std::vector<std::shared_ptr<Node>> elements;
@ -1158,18 +1207,14 @@ public:
return std::make_shared<Node>(ListNode(elements));
}
while (current().type != Token::RBRACKET) {
elements.push_back(parse_expr());
if (!consume(Token::RBRACKET)) {
// Elements are whitespace-separated in Nix, no comma required
// But we'll continue parsing until we hit ]
} else {
// Found closing bracket
return std::make_shared<Node>(ListNode(elements));
while (current().type != Token::RBRACKET && current().type != Token::EOF_) {
elements.push_back(parse_list_element());
if (current().type == Token::RBRACKET) {
break;
}
}
// Unreachable, but for safety
expect(Token::RBRACKET);
return std::make_shared<Node>(ListNode(elements));
}