feat: 复合赋值 += -= *= /= + CHANGELOG 更新

- lexer: 4 个复合赋值 Token (解析时优先于单字符)
- parser: desugar x+=expr → x=x+expr(零 sema/codegen 改动)
- 新增集成测试 09_compound_assign.l (15 12 24 6)
- CHANGELOG 新增 v0.2.0 条目
This commit is contained in:
2026-06-05 02:40:05 +08:00
parent 9e41b09318
commit d5a94d45cb
6 changed files with 71 additions and 2 deletions
+6 -2
View File
@@ -104,10 +104,14 @@ Token* lex(Arena* a, const char* source, const char* filename,
tokens[idx++] = make_token(&l, TOK_STR_LIT, start, len);
}
else if (isalpha(c) || c == '_') { tokens[idx++] = lex_ident_or_keyword(&l); }
else if (c == '+' && peek_next(&l) != '=') { tokens[idx++] = make_token(&l, TOK_PLUS, l.pos, 1); advance(&l); }
else if (c == '-' && peek_next(&l) != '>') { tokens[idx++] = make_token(&l, TOK_MINUS, l.pos, 1); advance(&l); }
else if (c == '+' && peek_next(&l) == '=') { tokens[idx++] = make_token(&l, TOK_PLUS_EQ, l.pos, 2); advance(&l); advance(&l); }
else if (c == '+') { tokens[idx++] = make_token(&l, TOK_PLUS, l.pos, 1); advance(&l); }
else if (c == '-' && peek_next(&l) == '=') { tokens[idx++] = make_token(&l, TOK_MINUS_EQ, l.pos, 2); advance(&l); advance(&l); }
else if (c == '-' && peek_next(&l) == '>') { tokens[idx++] = make_token(&l, TOK_ARROW, l.pos, 2); advance(&l); advance(&l); }
else if (c == '-') { tokens[idx++] = make_token(&l, TOK_MINUS, l.pos, 1); advance(&l); }
else if (c == '*' && peek_next(&l) == '=') { tokens[idx++] = make_token(&l, TOK_STAR_EQ, l.pos, 2); advance(&l); advance(&l); }
else if (c == '*') { tokens[idx++] = make_token(&l, TOK_STAR, l.pos, 1); advance(&l); }
else if (c == '/' && peek_next(&l) == '=') { tokens[idx++] = make_token(&l, TOK_SLASH_EQ, l.pos, 2); advance(&l); advance(&l); }
else if (c == '/') { tokens[idx++] = make_token(&l, TOK_SLASH, l.pos, 1); advance(&l); }
else if (c == '%') { tokens[idx++] = make_token(&l, TOK_PERCENT, l.pos, 1); advance(&l); }
else if (c == '=' && peek_next(&l) == '=') { tokens[idx++] = make_token(&l, TOK_EQ_EQ, l.pos, 2); advance(&l); advance(&l); }
+1
View File
@@ -17,6 +17,7 @@ static const char* NAMES[] = {
[TOK_LT] = "<", [TOK_GT] = ">", [TOK_LT_EQ] = "<=", [TOK_GT_EQ] = ">=",
[TOK_AND_AND] = "&&", [TOK_PIPE_PIPE] = "||", [TOK_BANG] = "!",
[TOK_ARROW] = "->",
[TOK_PLUS_EQ] = "+=", [TOK_MINUS_EQ] = "-=", [TOK_STAR_EQ] = "*=", [TOK_SLASH_EQ] = "/=",
[TOK_LPAREN] = "(", [TOK_RPAREN] = ")",
[TOK_LBRACE] = "{", [TOK_RBRACE] = "}",
[TOK_COMMA] = ",", [TOK_COLON] = ":", [TOK_SEMICOLON] = ";",
+1
View File
@@ -18,6 +18,7 @@ typedef enum {
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_ARROW,
TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ,
// 分隔符
TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE,
TOK_COMMA, TOK_COLON, TOK_SEMICOLON, TOK_ASSIGN,
+31
View File
@@ -277,6 +277,37 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) {
value, name->line, name->col);
}
// 复合赋值: ident += expr → ident = ident + expr
if (t->kind == TOK_IDENT) {
TokenKind next_kind = (t + 1)->kind;
if (next_kind >= TOK_PLUS_EQ && next_kind <= TOK_SLASH_EQ) {
const Token* name = advance(p); // 消费标识符
TokenKind comp_op = advance(p)->kind;
BinaryOp binop;
switch (comp_op) {
case TOK_PLUS_EQ: binop = OP_ADD; break;
case TOK_MINUS_EQ: binop = OP_SUB; break;
case TOK_STAR_EQ: binop = OP_MUL; break;
case TOK_SLASH_EQ: binop = OP_DIV; break;
default: break;
}
AstNode* rhs = parse_expr(p, error);
if (!rhs) return NULL;
if (!expect(p, TOK_SEMICOLON, error, "缺少 ';'")) return NULL;
AstNode* lhs_ident = ast_make_ident(p->arena,
arena_strdup_impl(p->arena, name->start, name->length),
name->line, name->col);
AstNode* bin_expr = ast_make_binary(p->arena, binop, lhs_ident, rhs,
name->line, name->col);
return ast_make_assign(p->arena,
arena_strdup_impl(p->arena, name->start, name->length),
bin_expr, name->line, name->col);
}
}
// 表达式语句
AstNode* expr = parse_expr(p, error);
if (!expr) return NULL;