feat: ?? 空值合并运算符 — a ?? b → if a!=0 {a} else {b}

Token(76): +TOK_QMARK_QMARK, 优先级 PREC_COALESCE=15 (||之上, |>之下)
parser 去糖为 if-expr, 零 sema/codegen 改动

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 14:18:48 +08:00
parent caf17e16fc
commit f7710ede9d
5 changed files with 23 additions and 1 deletions
+1
View File
@@ -155,6 +155,7 @@ Token* lex(Arena* a, const char* source, const char* filename,
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_QMARK_QMARK, 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); }
else if (c == ']') { tokens[idx++] = make_token(&l, TOK_RBRACKET, l.pos, 1); advance(&l); }
+1
View File
@@ -30,6 +30,7 @@ static const char* NAMES[] = {
[TOK_COMMA] = ",", [TOK_COLON] = ":", [TOK_SEMICOLON] = ";",
[TOK_ASSIGN] = "=",
[TOK_DOT] = ".", [TOK_COLON_COLON] = "::", [TOK_HASH] = "#",
[TOK_QMARK_QMARK] = "??",
[TOK_EOF] = "EOF", [TOK_ERROR] = "错误",
};
+1 -1
View File
@@ -26,7 +26,7 @@ typedef enum {
TOK_LBRACKET, TOK_RBRACKET,
TOK_COMMA, TOK_COLON, TOK_SEMICOLON, TOK_ASSIGN,
// 特殊
TOK_DOT, TOK_COLON_COLON, TOK_HASH,
TOK_DOT, TOK_COLON_COLON, TOK_HASH, TOK_QMARK_QMARK,
TOK_EOF, TOK_ERROR,
} TokenKind;
+19
View File
@@ -9,6 +9,7 @@ int parse_depth = 0;
// === 运算符优先级 → Precedence 映射 ===
Precedence tok_to_prec(TokenKind kind) {
switch (kind) {
case TOK_QMARK_QMARK: return PREC_COALESCE;
case TOK_PIPE_PIPE: return PREC_OR;
case TOK_AND_AND: return PREC_AND;
case TOK_EQ_EQ: case TOK_BANG_EQ:
@@ -419,6 +420,24 @@ AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error) {
continue;
}
// ?? 空值合并: a ?? b → if a != 0 { a; } else { b; }
if (kind == TOK_QMARK_QMARK) {
if (PREC_COALESCE <= min_prec) break;
advance(p);
AstNode* right = parse_expr_prec(p, PREC_COALESCE, error);
if (!right) return NULL;
AstNode* cond = ast_make_binary(p->arena, OP_NE, left,
ast_make_literal_i64(p->arena, 0, left->loc), left->loc);
AstNode** t_arr = (AstNode**)arena_alloc_impl(p->arena, sizeof(AstNode*));
t_arr[0] = ast_make_expr_stmt(p->arena, left, left->loc);
AstNode* then_block = ast_make_block(p->arena, t_arr, 1, left->loc);
AstNode** e_arr = (AstNode**)arena_alloc_impl(p->arena, sizeof(AstNode*));
e_arr[0] = ast_make_expr_stmt(p->arena, right, right->loc);
AstNode* else_block = ast_make_block(p->arena, e_arr, 1, right->loc);
left = ast_make_if(p->arena, cond, then_block, else_block, left->loc);
continue;
}
// 中缀运算符
Precedence prec = tok_to_prec(kind);
if (prec <= min_prec) break;
+1
View File
@@ -38,6 +38,7 @@ static inline const Token* expect(Parser* p, TokenKind k, ErrorInfo* e, const ch
typedef enum {
PREC_NONE = 0,
PREC_PIPE = 10,
PREC_COALESCE = 15,
PREC_OR = 20,
PREC_AND = 30,
PREC_COMPARE = 40,