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:
@@ -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_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, 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 == '?' && 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_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); }
|
||||||
else if (c == ']') { tokens[idx++] = make_token(&l, TOK_RBRACKET, l.pos, 1); advance(&l); }
|
else if (c == ']') { tokens[idx++] = make_token(&l, TOK_RBRACKET, l.pos, 1); advance(&l); }
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ static const char* NAMES[] = {
|
|||||||
[TOK_COMMA] = ",", [TOK_COLON] = ":", [TOK_SEMICOLON] = ";",
|
[TOK_COMMA] = ",", [TOK_COLON] = ":", [TOK_SEMICOLON] = ";",
|
||||||
[TOK_ASSIGN] = "=",
|
[TOK_ASSIGN] = "=",
|
||||||
[TOK_DOT] = ".", [TOK_COLON_COLON] = "::", [TOK_HASH] = "#",
|
[TOK_DOT] = ".", [TOK_COLON_COLON] = "::", [TOK_HASH] = "#",
|
||||||
|
[TOK_QMARK_QMARK] = "??",
|
||||||
[TOK_EOF] = "EOF", [TOK_ERROR] = "错误",
|
[TOK_EOF] = "EOF", [TOK_ERROR] = "错误",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ typedef enum {
|
|||||||
TOK_LBRACKET, TOK_RBRACKET,
|
TOK_LBRACKET, TOK_RBRACKET,
|
||||||
TOK_COMMA, TOK_COLON, TOK_SEMICOLON, TOK_ASSIGN,
|
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,
|
TOK_EOF, TOK_ERROR,
|
||||||
} TokenKind;
|
} TokenKind;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ int parse_depth = 0;
|
|||||||
// === 运算符优先级 → Precedence 映射 ===
|
// === 运算符优先级 → Precedence 映射 ===
|
||||||
Precedence tok_to_prec(TokenKind kind) {
|
Precedence tok_to_prec(TokenKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
|
case TOK_QMARK_QMARK: return PREC_COALESCE;
|
||||||
case TOK_PIPE_PIPE: return PREC_OR;
|
case TOK_PIPE_PIPE: return PREC_OR;
|
||||||
case TOK_AND_AND: return PREC_AND;
|
case TOK_AND_AND: return PREC_AND;
|
||||||
case TOK_EQ_EQ: case TOK_BANG_EQ:
|
case TOK_EQ_EQ: case TOK_BANG_EQ:
|
||||||
@@ -419,6 +420,24 @@ AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error) {
|
|||||||
continue;
|
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);
|
Precedence prec = tok_to_prec(kind);
|
||||||
if (prec <= min_prec) break;
|
if (prec <= min_prec) break;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ static inline const Token* expect(Parser* p, TokenKind k, ErrorInfo* e, const ch
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
PREC_NONE = 0,
|
PREC_NONE = 0,
|
||||||
PREC_PIPE = 10,
|
PREC_PIPE = 10,
|
||||||
|
PREC_COALESCE = 15,
|
||||||
PREC_OR = 20,
|
PREC_OR = 20,
|
||||||
PREC_AND = 30,
|
PREC_AND = 30,
|
||||||
PREC_COMPARE = 40,
|
PREC_COMPARE = 40,
|
||||||
|
|||||||
Reference in New Issue
Block a user