clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 32f74c29fdcb
tarballunverified1 files changed+313-13
@@ -54,9 +54,9 @@ static std::pair<std::string, std::string> run_command(const std::string& cmd) { struct Token { enum Type { LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET,- IDENT, STRING, PATH, INT, BOOL,- LET, IN, REC, IF, THEN, ELSE, ASSERT, WITH,- DOT, SEMICOLON, COLON, AT, COMMA,+ IDENT, STRING, STRING_INTERP, PATH, INT, BOOL,+ LET, IN, REC, IF, THEN, ELSE, ASSERT, WITH, INHERIT,+ DOT, SEMICOLON, COLON, EQUALS, AT, COMMA, QUESTION, ELLIPSIS, // Operators PLUS, MINUS, STAR, SLASH, CONCAT, EQEQ, NE, LT, GT, LE, GE,@@ -97,6 +97,7 @@ public: tokens.push_back(TOKEN(EQEQ)); pos += 2; col += 2; }+ else if (c == '=') { emit(TOKEN(EQUALS)); } else if (c == '!' && pos + 1 < input.size() && input[pos + 1] == '=') { tokens.push_back(TOKEN(NE)); pos += 2; col += 2;@@ -139,7 +140,16 @@ public: else if (c == '<') { emit(TOKEN(LT)); } else if (c == '>') { emit(TOKEN(GT)); } else if (c == '!') { emit(TOKEN(NOT)); }- else if (c == '.') { emit(TOKEN(DOT)); }+ else if (c == '.') {+ // Check for ellipsis (...)+ if (pos + 2 < input.size() && input[pos + 1] == '.' && input[pos + 2] == '.') {+ tokens.push_back(TOKEN(ELLIPSIS));+ pos += 3; col += 3;+ } else {+ emit(TOKEN(DOT));+ }+ }+ else if (c == '?') { emit(TOKEN(QUESTION)); } else if (c == '-') { // Check if it's a negative number or minus operator if (pos + 1 < input.size() && isdigit(input[pos + 1])) {@@ -189,6 +199,8 @@ private: void tokenize_string() { pos++; std::string s;+ bool has_interp = false;+ while (pos < input.size() && input[pos] != '"') { if (input[pos] == '\\' && pos + 1 < input.size()) { pos++;@@ -198,15 +210,24 @@ private: case 'r': s += '\r'; break; case '"': s += '"'; break; case '\\': s += '\\'; break;+ case '$': s += '$'; break; // Escaped $ default: s += input[pos]; break; }+ pos++;+ } else if (input[pos] == '$' && pos + 1 < input.size() && input[pos + 1] == '{') {+ // Found interpolation marker+ has_interp = true;+ s += input[pos]; // Keep $ in raw string+ pos++; } else { s += input[pos];+ pos++; }- pos++; } pos++;- tokens.push_back({Token::STRING, s, line, col});++ Token::Type type = has_interp ? Token::STRING_INTERP : Token::STRING;+ tokens.push_back({type, s, line, col}); col += s.size() + 2; } @@ -246,6 +267,7 @@ private: else if (ident == "else") type = Token::ELSE; else if (ident == "assert") type = Token::ASSERT; else if (ident == "with") type = Token::WITH;+ else if (ident == "inherit") type = Token::INHERIT; else if (ident == "true") type = Token::BOOL; else if (ident == "false") type = Token::BOOL; @@ -323,6 +345,10 @@ public: } std::shared_ptr<Node> parse_expr() {+ // Try to parse lambda+ auto lambda = try_parse_lambda();+ if (lambda) return lambda;+ if (consume(Token::IF)) { auto cond = parse_expr(); expect(Token::THEN);@@ -500,6 +526,12 @@ public: return std::make_shared<Node>(ConstStringNode(t.value)); } + if (t.type == Token::STRING_INTERP) {+ Token str_token = current();+ advance();+ return parse_string_interp(str_token.value);+ }+ if (t.type == Token::PATH) { advance(); return std::make_shared<Node>(ConstPathNode(t.value));@@ -524,12 +556,45 @@ public: continue; } + // Handle inherit keyword+ if (consume(Token::INHERIT)) {+ std::shared_ptr<Node> source;++ // Check for (expr) form+ if (consume(Token::LPAREN)) {+ source = parse_expr();+ expect(Token::RPAREN);+ }++ // Parse identifier list+ while (current().type == Token::IDENT) {+ Token name = current();+ advance();++ if (source) {+ // inherit (expr) x → x = expr.x+ auto select = std::make_shared<Node>(SelectNode(+ source,+ std::make_shared<Node>(ConstStringNode(name.value))+ ));+ attrs.attrs.push_back({name.value, select});+ } else {+ // inherit x → x = x+ auto var = std::make_shared<Node>(VarNode(0, name.value));+ attrs.attrs.push_back({name.value, var});+ }+ }++ expect(Token::SEMICOLON);+ continue;+ }+ if (current().type == Token::IDENT || current().type == Token::STRING) { Token key = current(); advance(); std::string key_str = key.value; - if (consume(Token::COLON)) {+ if (consume(Token::EQUALS)) { auto value = parse_expr(); attrs.attrs.push_back({key_str, value}); } else if (consume(Token::AT)) {@@ -541,6 +606,11 @@ public: if (consume(Token::COMMA)) continue; if (consume(Token::SEMICOLON)) continue;++ // If we get here and haven't handled the token, break+ if (current().type != Token::RBRACE && current().type != Token::EOF_) {+ break;+ } } expect(Token::RBRACE);@@ -575,22 +645,50 @@ public: } void parse_bindings(std::vector<std::pair<std::string, std::shared_ptr<Node>>>& bindings) {- while (current().type == Token::IDENT || current().type == Token::LBRACE) {- if (current().type == Token::LBRACE) {- auto inherit = parse_expr();- (void)inherit;+ while (current().type == Token::IDENT || current().type == Token::INHERIT) {+ // Handle inherit keyword+ if (consume(Token::INHERIT)) {+ std::shared_ptr<Node> source;++ // Check for (expr) form+ if (consume(Token::LPAREN)) {+ source = parse_expr();+ expect(Token::RPAREN);+ }++ // Parse identifier list+ while (current().type == Token::IDENT) {+ Token name = current();+ advance();++ if (source) {+ // inherit (expr) x → x = expr.x+ auto select = std::make_shared<Node>(SelectNode(+ source,+ std::make_shared<Node>(ConstStringNode(name.value))+ ));+ bindings.push_back({name.value, select});+ } else {+ // inherit x → x = x+ auto var = std::make_shared<Node>(VarNode(0, name.value));+ bindings.push_back({name.value, var});+ }+ }++ expect(Token::SEMICOLON); continue; } + if (current().type != Token::IDENT) break; Token key = current();- expect(Token::IDENT);+ advance(); if (consume(Token::AT)) { auto pattern = parse_expr(); auto value = parse_expr(); bindings.push_back({key.value, value}); } else {- expect(Token::COLON);+ expect(Token::EQUALS); auto value = parse_expr(); bindings.push_back({key.value, value}); }@@ -598,6 +696,208 @@ public: if (!consume(Token::SEMICOLON)) break; } }++ // Try to parse lambda, return nullptr if not a lambda+ std::shared_ptr<Node> try_parse_lambda() {+ size_t saved_pos = pos;++ // Check for named pattern: arg@{ ... }:+ std::optional<std::string> named_arg;+ if (current().type == Token::IDENT) {+ Token name = current();+ advance();+ if (consume(Token::AT)) {+ named_arg = name.value;+ } else if (consume(Token::COLON)) {+ // Simple lambda: x: body+ auto body = parse_expr();+ auto lambda = LambdaNode(1, body);+ lambda.param_name = name.value;+ return std::make_shared<Node>(std::move(lambda));+ } else {+ // Not a lambda, restore position+ pos = saved_pos;+ return nullptr;+ }+ }++ // Check for pattern: { ... }:+ if (current().type == Token::LBRACE) {+ advance();++ // Parse pattern fields+ struct Field {+ std::string name;+ std::optional<std::shared_ptr<Node>> default_val;+ };+ std::vector<Field> fields;+ bool has_ellipsis = false;++ while (current().type != Token::RBRACE && current().type != Token::EOF_) {+ if (consume(Token::ELLIPSIS)) {+ has_ellipsis = true;+ if (consume(Token::COMMA)) continue;+ break;+ }++ if (current().type == Token::IDENT) {+ Token field_name = current();+ advance();++ Field field;+ field.name = field_name.value;++ // Check for default value+ if (consume(Token::QUESTION)) {+ field.default_val = parse_expr();+ }++ fields.push_back(field);++ if (consume(Token::COMMA)) continue;+ break;+ } else {+ break;+ }+ }++ expect(Token::RBRACE);++ if (!consume(Token::COLON)) {+ // Not a lambda, restore+ pos = saved_pos;+ return nullptr;+ }++ // Parse body+ auto body = parse_expr();++ // Desugar pattern to lambda with let bindings+ // { a, b ? x }: body → arg: let a = arg.a; b = if arg ? a then arg.a else x; in body++ std::string arg_name = named_arg.value_or("_arg");+ auto arg_var = std::make_shared<Node>(VarNode(0, arg_name));++ std::vector<std::pair<std::string, std::shared_ptr<Node>>> bindings;++ for (const auto& field : fields) {+ // Create arg.field selection+ auto select = std::make_shared<Node>(SelectNode(+ arg_var,+ std::make_shared<Node>(ConstStringNode(field.name))+ ));++ if (field.default_val) {+ // if arg ? field then arg.field else default+ 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 {+ bindings.push_back({field.name, select});+ }+ }++ // If named pattern, also bind the argument name+ 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;+ return std::make_shared<Node>(std::move(lambda));+ }++ // Not a lambda+ pos = saved_pos;+ return nullptr;+ }++ std::shared_ptr<Node> parse_string_interp(const std::string& raw) {+ std::vector<std::shared_ptr<Node>> parts;+ size_t i = 0;+ std::string current_str;++ while (i < raw.size()) {+ if (raw[i] == '$' && i + 1 < raw.size() && raw[i + 1] == '{') {+ // Save current string part if any+ if (!current_str.empty()) {+ parts.push_back(std::make_shared<Node>(ConstStringNode(current_str)));+ current_str.clear();+ }++ // Find matching }+ i += 2; // Skip ${+ int depth = 1;+ size_t expr_start = i;++ while (i < raw.size() && depth > 0) {+ if (raw[i] == '{') depth++;+ else if (raw[i] == '}') depth--;+ if (depth > 0) i++;+ }++ // Parse the expression+ std::string expr_str = raw.substr(expr_start, i - expr_start);++ // Tokenize and parse the expression+ Lexer lexer(expr_str);+ auto expr_tokens = lexer.tokenize();++ // Save current state+ auto saved_tokens = tokens;+ auto saved_pos = pos;++ // Parse expression+ tokens = expr_tokens;+ pos = 0;+ auto expr = parse_expr();++ // Restore state+ tokens = saved_tokens;+ pos = saved_pos;++ // Convert to string using toString builtin+ auto to_string = std::make_shared<Node>(VarNode(0, "toString"));+ auto str_expr = std::make_shared<Node>(AppNode(to_string, expr));+ parts.push_back(str_expr);++ i++; // Skip }+ } else {+ current_str += raw[i];+ i++;+ }+ }++ // Add remaining string part+ if (!current_str.empty()) {+ parts.push_back(std::make_shared<Node>(ConstStringNode(current_str)));+ }++ // Build concatenation tree+ if (parts.empty()) {+ return std::make_shared<Node>(ConstStringNode(""));+ }++ auto result = parts[0];+ for (size_t j = 1; j < parts.size(); j++) {+ result = std::make_shared<Node>(BinaryOpNode(BinaryOp::CONCAT, result, parts[j]));+ }++ return result;+ } }; Parser::Parser() : pImpl(std::make_unique<Impl>()) {}