brewery
notashelf /
32f74c29fdcbe6362c2f8d5f29e38460fe2b8ec2

nixir

public

Import-resolving Nix IR plugin

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.git

Commit 32f74c29fdcb

tarball

NotAShelf <raf@notashelf.dev> · 2026-02-21 21:07 UTC

unverified1 files changed+313-13
parser: add inherit keyword; initial string interpolation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icdada04a72728e8881e2db4254bd88546a6a6964
diff --git a/src/irc/parser.cpp b/src/irc/parser.cppindex 77f325a..3ec6f86 100644--- a/src/irc/parser.cpp+++ b/src/irc/parser.cpp@@ -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>()) {}