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
+20
View File
@@ -1,5 +1,25 @@
# Changelog
## 0.2.0 (2026-06-05)
### Added
- `let mut` 可变变量 + 赋值语句 (`x = expr;`)
- 可变性检查:对不可变变量赋值报编译错误
- 字符串类型 `str` + 双引号字面量 (`"Hello"`)
- 字符串拼接 `str + str` (malloc + strlen + memcpy 运行时实现)
- `print_str` 内置函数 (委托 printf)
- 复合赋值运算符:`+=` `-=` `*=` `/=`
- 集成测试:`06_mut_while.l` (while 循环修改变量)、`07_hello_str.l` (字符串输出)、`08_str_concat.l` (字符串拼接)
### Changed
- codegen malloc → arena 统一分配
- LLVM 目标初始化解耦为 `target.h/c` 独立模块
- 新增 `.codegraphignore`
### Fixed
- codegen.c 内存管理不一致 (malloc 混用 arena)
- str+str 运行时拼接返回左操作数的 bug
## 0.1.0 (2026-06-05)
### Added
+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;
+12
View File
@@ -0,0 +1,12 @@
fn main() -> i64 {
let mut x: i64 = 10;
x += 5;
print_i64(x); // 15
x -= 3;
print_i64(x); // 12
x *= 2;
print_i64(x); // 24
x /= 4;
print_i64(x); // 6
return 0;
}