feat: 管道 |> + 字符串插值 \(expr) — P0 四特性收官
This commit is contained in:
@@ -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 == '>' && 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 == '>') { 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_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 == '|' && 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_DOT, l.pos, 1); advance(&l); }
|
||||||
else if (c == '[') { tokens[idx++] = make_token(&l, TOK_LBRACKET, l.pos, 1); advance(&l); }
|
else if (c == '[') { tokens[idx++] = make_token(&l, TOK_LBRACKET, l.pos, 1); advance(&l); }
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ static const char* NAMES[] = {
|
|||||||
[TOK_SLASH] = "/", [TOK_PERCENT] = "%",
|
[TOK_SLASH] = "/", [TOK_PERCENT] = "%",
|
||||||
[TOK_EQ_EQ] = "==", [TOK_BANG_EQ] = "!=",
|
[TOK_EQ_EQ] = "==", [TOK_BANG_EQ] = "!=",
|
||||||
[TOK_LT] = "<", [TOK_GT] = ">", [TOK_LT_EQ] = "<=", [TOK_GT_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_ARROW] = "->", [TOK_TO] = "to", [TOK_MATCH_ARROW] = "=>",
|
||||||
[TOK_PLUS_EQ] = "+=", [TOK_MINUS_EQ] = "-=", [TOK_STAR_EQ] = "*=", [TOK_SLASH_EQ] = "/=",
|
[TOK_PLUS_EQ] = "+=", [TOK_MINUS_EQ] = "-=", [TOK_STAR_EQ] = "*=", [TOK_SLASH_EQ] = "/=",
|
||||||
[TOK_LPAREN] = "(", [TOK_RPAREN] = ")",
|
[TOK_LPAREN] = "(", [TOK_RPAREN] = ")",
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ typedef enum {
|
|||||||
// 运算符
|
// 运算符
|
||||||
TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT,
|
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_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_ARROW, TOK_TO, TOK_MATCH_ARROW,
|
||||||
TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ,
|
TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ,
|
||||||
// 分隔符
|
// 分隔符
|
||||||
|
|||||||
+57
-2
@@ -31,6 +31,7 @@ static const Token* expect(Parser* p, TokenKind k, ErrorInfo* e, const char* msg
|
|||||||
// === 运算符优先级定义 ===
|
// === 运算符优先级定义 ===
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PREC_NONE = 0,
|
PREC_NONE = 0,
|
||||||
|
PREC_PIPE = 10,
|
||||||
PREC_OR = 20,
|
PREC_OR = 20,
|
||||||
PREC_AND = 30,
|
PREC_AND = 30,
|
||||||
PREC_COMPARE = 40,
|
PREC_COMPARE = 40,
|
||||||
@@ -89,7 +90,7 @@ static AstNode* parse_group(Parser* p, ErrorInfo* error) {
|
|||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AstNode* parse_literal(Parser* p) {
|
static AstNode* parse_literal(Parser* p, ErrorInfo* error) {
|
||||||
const Token* t = advance(p);
|
const Token* t = advance(p);
|
||||||
switch (t->kind) {
|
switch (t->kind) {
|
||||||
case TOK_INT_LIT: return ast_make_literal_i64(p->arena, tok_int_value(t), tok_loc(t));
|
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);
|
char* str = arena_alloc_impl(p->arena, t->length + 1);
|
||||||
memcpy(str, t->start, t->length);
|
memcpy(str, t->start, t->length);
|
||||||
str[t->length] = '\0';
|
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));
|
return ast_make_literal_str(p->arena, str, tok_loc(t));
|
||||||
}
|
}
|
||||||
default: return NULL;
|
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_CHAR_LIT ||
|
||||||
tok->kind == TOK_TRUE || tok->kind == TOK_FALSE ||
|
tok->kind == TOK_TRUE || tok->kind == TOK_FALSE ||
|
||||||
tok->kind == TOK_STR_LIT) {
|
tok->kind == TOK_STR_LIT) {
|
||||||
left = parse_literal(p);
|
left = parse_literal(p, error);
|
||||||
} else if (tok->kind == TOK_IDENT) {
|
} else if (tok->kind == TOK_IDENT) {
|
||||||
left = parse_ident_or_call(p, error);
|
left = parse_ident_or_call(p, error);
|
||||||
} else {
|
} else {
|
||||||
@@ -252,6 +280,33 @@ static AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error
|
|||||||
while (!error->message) {
|
while (!error->message) {
|
||||||
TokenKind kind = peek(p)->kind;
|
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)
|
// 后置字段访问: expr.field 或 expr.method(args)
|
||||||
if (kind == TOK_DOT) {
|
if (kind == TOK_DOT) {
|
||||||
advance(p); // 跳过 '.'
|
advance(p); // 跳过 '.'
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fn main() -> i64 {
|
||||||
|
let name = "World";
|
||||||
|
let greeting = "Hello, \(name)!";
|
||||||
|
print_str(greeting); // "Hello, World!"
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user