clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 51305298ee98
tarballunverified1 files changed+62-17
@@ -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)); }