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() { void tokenize_ident() {
size_t start = pos; size_t start = pos;
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' || // Note: Don't include '.' here - it's used for selection (a.b.c)
input[pos] == '+' || input[pos] == '.')) // URIs are handled separately by checking for '://' pattern
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-'))
pos++; pos++;
std::string ident = input.substr(start, pos - start); std::string ident = input.substr(start, pos - start);
@ -927,16 +928,25 @@ public:
std::shared_ptr<Node> left = parse_expr3(); std::shared_ptr<Node> left = parse_expr3();
while (true) { while (true) {
if (current().type == Token::LBRACKET) { if (current().type == Token::STRING) {
advance();
auto arg = parse_expr();
expect(Token::RBRACKET);
left = std::make_shared<Node>(AppNode(left, arg));
} else if (current().type == Token::STRING) {
Token s = current(); Token s = current();
advance(); advance();
auto arg = std::make_shared<Node>(ConstStringNode(s.value)); auto arg = std::make_shared<Node>(ConstStringNode(s.value));
left = std::make_shared<Node>(AppNode(left, arg)); 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 { } else {
break; break;
} }
@ -969,6 +979,16 @@ public:
return expr; 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)) { if (consume(Token::LBRACE)) {
return parse_attrs(); return parse_attrs();
} }
@ -1151,6 +1171,35 @@ public:
return std::make_shared<Node>(std::move(attrs)); 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::shared_ptr<Node> parse_list() {
std::vector<std::shared_ptr<Node>> elements; std::vector<std::shared_ptr<Node>> elements;
@ -1158,18 +1207,14 @@ public:
return std::make_shared<Node>(ListNode(elements)); return std::make_shared<Node>(ListNode(elements));
} }
while (current().type != Token::RBRACKET) { while (current().type != Token::RBRACKET && current().type != Token::EOF_) {
elements.push_back(parse_expr()); elements.push_back(parse_list_element());
if (!consume(Token::RBRACKET)) { if (current().type == Token::RBRACKET) {
// Elements are whitespace-separated in Nix, no comma required break;
// But we'll continue parsing until we hit ]
} else {
// Found closing bracket
return std::make_shared<Node>(ListNode(elements));
} }
} }
// Unreachable, but for safety expect(Token::RBRACKET);
return std::make_shared<Node>(ListNode(elements)); return std::make_shared<Node>(ListNode(elements));
} }