diff --git a/src/irc/parser.cpp b/src/irc/parser.cpp index 8d92617..2bb31ed 100644 --- a/src/irc/parser.cpp +++ b/src/irc/parser.cpp @@ -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 left = parse_expr3(); while (true) { - if (current().type == Token::LBRACKET) { - advance(); - auto arg = parse_expr(); - expect(Token::RBRACKET); - left = std::make_shared(AppNode(left, arg)); - } else if (current().type == Token::STRING) { + if (current().type == Token::STRING) { Token s = current(); advance(); auto arg = std::make_shared(ConstStringNode(s.value)); left = std::make_shared(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(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(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()) { + attrset->recursive = true; + } + return attrs; + } + if (consume(Token::LBRACE)) { return parse_attrs(); } @@ -1151,6 +1171,35 @@ public: return std::make_shared(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 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(ConstStringNode(name.value)); + left = std::make_shared(SelectNode(left, attr)); + continue; + } + break; + } + + // Check for 'or' default value + if (left->get_if() && current().type == Token::IDENT && current().value == "or") { + advance(); + auto default_expr = parse_expr3(); + auto* select = left->get_if(); + select->default_expr = default_expr; + } + + return left; + } + std::shared_ptr parse_list() { std::vector> elements; @@ -1158,18 +1207,14 @@ public: return std::make_shared(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(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(ListNode(elements)); }