diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 5149f42..fd019d0 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -151,6 +151,7 @@ Token* lex(Arena* a, const char* source, const char* filename, else if (c == '>' && peek_next(&l) == '=') { tokens[idx++] = make_token(&l, TOK_GT_EQ, l.pos, 2); advance(&l); advance(&l); } else if (c == '>') { tokens[idx++] = make_token(&l, TOK_GT, l.pos, 1); advance(&l); } else if (c == '&' && peek_next(&l) == '&') { tokens[idx++] = make_token(&l, TOK_AND_AND, l.pos, 2); advance(&l); advance(&l); } + else if (c == '|' && peek_next(&l) == '>') { tokens[idx++] = make_token(&l, TOK_PIPE, l.pos, 2); advance(&l); advance(&l); } else if (c == '|' && peek_next(&l) == '|') { tokens[idx++] = make_token(&l, TOK_PIPE_PIPE, l.pos, 2); advance(&l); advance(&l); } else if (c == '.') { tokens[idx++] = make_token(&l, TOK_DOT, l.pos, 1); advance(&l); } else if (c == '[') { tokens[idx++] = make_token(&l, TOK_LBRACKET, l.pos, 1); advance(&l); } diff --git a/src/lexer/token.c b/src/lexer/token.c index eec53c7..e5dfb52 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -19,7 +19,7 @@ static const char* NAMES[] = { [TOK_SLASH] = "/", [TOK_PERCENT] = "%", [TOK_EQ_EQ] = "==", [TOK_BANG_EQ] = "!=", [TOK_LT] = "<", [TOK_GT] = ">", [TOK_LT_EQ] = "<=", [TOK_GT_EQ] = ">=", - [TOK_AND_AND] = "&&", [TOK_PIPE_PIPE] = "||", [TOK_BANG] = "!", + [TOK_AND_AND] = "&&", [TOK_PIPE_PIPE] = "||", [TOK_PIPE] = "|>", [TOK_BANG] = "!", [TOK_ARROW] = "->", [TOK_TO] = "to", [TOK_MATCH_ARROW] = "=>", [TOK_PLUS_EQ] = "+=", [TOK_MINUS_EQ] = "-=", [TOK_STAR_EQ] = "*=", [TOK_SLASH_EQ] = "/=", [TOK_LPAREN] = "(", [TOK_RPAREN] = ")", diff --git a/src/lexer/token.h b/src/lexer/token.h index d544278..96eea30 100644 --- a/src/lexer/token.h +++ b/src/lexer/token.h @@ -17,7 +17,7 @@ typedef enum { // 运算符 TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT, TOK_EQ_EQ, TOK_BANG_EQ, TOK_LT, TOK_GT, TOK_LT_EQ, TOK_GT_EQ, - TOK_AND_AND, TOK_PIPE_PIPE, TOK_BANG, + TOK_AND_AND, TOK_PIPE_PIPE, TOK_PIPE, TOK_BANG, TOK_ARROW, TOK_TO, TOK_MATCH_ARROW, TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ, // 分隔符 diff --git a/src/parser/parser.c b/src/parser/parser.c index 57444bc..66866b1 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -31,6 +31,7 @@ static const Token* expect(Parser* p, TokenKind k, ErrorInfo* e, const char* msg // === 运算符优先级定义 === typedef enum { PREC_NONE = 0, + PREC_PIPE = 10, PREC_OR = 20, PREC_AND = 30, PREC_COMPARE = 40, @@ -89,7 +90,7 @@ static AstNode* parse_group(Parser* p, ErrorInfo* error) { return expr; } -static AstNode* parse_literal(Parser* p) { +static AstNode* parse_literal(Parser* p, ErrorInfo* error) { const Token* t = advance(p); switch (t->kind) { case TOK_INT_LIT: return ast_make_literal_i64(p->arena, tok_int_value(t), tok_loc(t)); @@ -115,6 +116,33 @@ static AstNode* parse_literal(Parser* p) { char* str = arena_alloc_impl(p->arena, t->length + 1); memcpy(str, t->start, t->length); str[t->length] = '\0'; + // 字符串插值: "Hello, \(name)!" → "Hello, " + name + "!" + char* interp = strstr(str, "\\("); + if (interp) { + *interp = '\0'; // 截断前半部分 + char* pre = str; + char* expr_start = interp + 2; // 跳过 \( + char* close = strchr(expr_start, ')'); + if (!close) { + error->message = "字符串插值缺少 ')'"; error->filename = p->filename; + error->line = t->line; error->col = t->col; return NULL; + } + *close = '\0'; + char* post = close + 1; + // 生成: pre + expr + post + AstNode* result = ast_make_literal_str(p->arena, + arena_strdup_impl(p->arena, pre, strlen(pre)), tok_loc(t)); + // 将插值表达式按标识符解析 + AstNode* expr = ast_make_ident(p->arena, + arena_strdup_impl(p->arena, expr_start, strlen(expr_start)), tok_loc(t)); + result = ast_make_binary(p->arena, OP_ADD, result, expr, tok_loc(t)); + if (post[0] != '\0') { + AstNode* post_str = ast_make_literal_str(p->arena, + arena_strdup_impl(p->arena, post, strlen(post)), tok_loc(t)); + result = ast_make_binary(p->arena, OP_ADD, result, post_str, tok_loc(t)); + } + return result; + } return ast_make_literal_str(p->arena, str, tok_loc(t)); } default: return NULL; @@ -238,7 +266,7 @@ static AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error tok->kind == TOK_CHAR_LIT || tok->kind == TOK_TRUE || tok->kind == TOK_FALSE || tok->kind == TOK_STR_LIT) { - left = parse_literal(p); + left = parse_literal(p, error); } else if (tok->kind == TOK_IDENT) { left = parse_ident_or_call(p, error); } else { @@ -252,6 +280,33 @@ static AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error while (!error->message) { TokenKind kind = peek(p)->kind; + // 管道: expr |> func(args...) → func(args..., expr) + if (kind == TOK_PIPE) { + Precedence prec = PREC_PIPE; + if (prec <= min_prec) break; + const Token* op = advance(p); + // RHS 必须是函数调用(不带管道时解析) + AstNode* right = parse_expr_prec(p, prec, error); + if (!right) return NULL; + if (right->kind != AST_CALL_EXPR) { + error->message = "管道右侧必须是函数调用"; error->filename = p->filename; + error->line = op->line; error->col = op->col; + return NULL; + } + // 将 left 作为第一个参数插入(F#/Elixir 风格) + if (right->as.call.arg_count >= 16) { + error->message = "管道参数过多"; error->filename = p->filename; + error->line = op->line; error->col = op->col; return NULL; + } + AstNode** new_args = arena_alloc_impl(p->arena, (right->as.call.arg_count + 1) * sizeof(AstNode*)); + new_args[0] = left; + memcpy(new_args + 1, right->as.call.args, right->as.call.arg_count * sizeof(AstNode*)); + right->as.call.args = new_args; + right->as.call.arg_count++; + left = right; + continue; + } + // 后置字段访问: expr.field 或 expr.method(args) if (kind == TOK_DOT) { advance(p); // 跳过 '.' diff --git a/test/programs/28_pipe.l b/test/programs/28_pipe.l new file mode 100644 index 0000000..a373aec --- /dev/null +++ b/test/programs/28_pipe.l @@ -0,0 +1,14 @@ +fn double(x: i64) -> i64 { + return x * 2; +} + +fn add(a: i64, b: i64) -> i64 { + return a + b; +} + +fn main() -> i64 { + // 管道: 10 |> double() |> add(5) → add(double(10), 5) = 25 + let result = 10 |> double() |> add(5); + print_i64(result); // 25 + return 0; +} diff --git a/test/programs/29_interp.l b/test/programs/29_interp.l new file mode 100644 index 0000000..44b80e4 --- /dev/null +++ b/test/programs/29_interp.l @@ -0,0 +1,6 @@ +fn main() -> i64 { + let name = "World"; + let greeting = "Hello, \(name)!"; + print_str(greeting); // "Hello, World!" + return 0; +}