feat: 管道 |> + 字符串插值 \(expr) — P0 四特性收官

This commit is contained in:
2026-06-05 20:59:00 +08:00
parent 6b6925b2b8
commit 459d1e1e10
6 changed files with 80 additions and 4 deletions
+57 -2
View File
@@ -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); // 跳过 '.'