From 32f74c29fdcbe6362c2f8d5f29e38460fe2b8ec2 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 21 Feb 2026 19:47:04 +0300 Subject: [PATCH] parser: add inherit keyword; initial string interpolation Signed-off-by: NotAShelf Change-Id: Icdada04a72728e8881e2db4254bd88546a6a6964 --- src/irc/parser.cpp | 326 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 313 insertions(+), 13 deletions(-) diff --git a/src/irc/parser.cpp b/src/irc/parser.cpp index 77f325a..3ec6f86 100644 --- a/src/irc/parser.cpp +++ b/src/irc/parser.cpp @@ -54,9 +54,9 @@ static std::pair 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 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(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(ConstPathNode(t.value)); @@ -524,12 +556,45 @@ public: continue; } + // Handle inherit keyword + if (consume(Token::INHERIT)) { + std::shared_ptr 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(SelectNode( + source, + std::make_shared(ConstStringNode(name.value)) + )); + attrs.attrs.push_back({name.value, select}); + } else { + // inherit x → x = x + auto var = std::make_shared(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>>& 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 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(SelectNode( + source, + std::make_shared(ConstStringNode(name.value)) + )); + bindings.push_back({name.value, select}); + } else { + // inherit x → x = x + auto var = std::make_shared(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 try_parse_lambda() { + size_t saved_pos = pos; + + // Check for named pattern: arg@{ ... }: + std::optional 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(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> default_val; + }; + std::vector 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(VarNode(0, arg_name)); + + std::vector>> bindings; + + for (const auto& field : fields) { + // Create arg.field selection + auto select = std::make_shared(SelectNode( + arg_var, + std::make_shared(ConstStringNode(field.name)) + )); + + if (field.default_val) { + // if arg ? field then arg.field else default + auto has_attr = std::make_shared(HasAttrNode( + arg_var, + std::make_shared(ConstStringNode(field.name)) + )); + auto if_node = std::make_shared(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(std::move(let)); + + // Create lambda + auto lambda = LambdaNode(1, let_node); + lambda.param_name = arg_name; + return std::make_shared(std::move(lambda)); + } + + // Not a lambda + pos = saved_pos; + return nullptr; + } + + std::shared_ptr parse_string_interp(const std::string& raw) { + std::vector> 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(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(VarNode(0, "toString")); + auto str_expr = std::make_shared(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(ConstStringNode(current_str))); + } + + // Build concatenation tree + if (parts.empty()) { + return std::make_shared(ConstStringNode("")); + } + + auto result = parts[0]; + for (size_t j = 1; j < parts.size(); j++) { + result = std::make_shared(BinaryOpNode(BinaryOp::CONCAT, result, parts[j])); + } + + return result; + } }; Parser::Parser() : pImpl(std::make_unique()) {}