From d5a94d45cb9ffa83a2716d14e3b56175d55ce423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Fri, 5 Jun 2026 02:40:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=8D=E5=90=88=E8=B5=8B=E5=80=BC=20?= =?UTF-8?q?+=3D=20-=3D=20*=3D=20/=3D=20+=20CHANGELOG=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lexer: 4 个复合赋值 Token (解析时优先于单字符) - parser: desugar x+=expr → x=x+expr(零 sema/codegen 改动) - 新增集成测试 09_compound_assign.l (15 12 24 6) - CHANGELOG 新增 v0.2.0 条目 --- CHANGELOG.md | 20 +++++++++++++++++++ src/lexer/lexer.c | 8 ++++++-- src/lexer/token.c | 1 + src/lexer/token.h | 1 + src/parser/parser.c | 31 ++++++++++++++++++++++++++++++ test/programs/09_compound_assign.l | 12 ++++++++++++ 6 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/programs/09_compound_assign.l diff --git a/CHANGELOG.md b/CHANGELOG.md index 235a3d7..5bab61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index d39677c..2e2c611 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -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); } diff --git a/src/lexer/token.c b/src/lexer/token.c index 7e6171f..f837e58 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -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] = ";", diff --git a/src/lexer/token.h b/src/lexer/token.h index 9e63706..4bd313b 100644 --- a/src/lexer/token.h +++ b/src/lexer/token.h @@ -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, diff --git a/src/parser/parser.c b/src/parser/parser.c index 49e8d59..c1c2ba1 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -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; diff --git a/test/programs/09_compound_assign.l b/test/programs/09_compound_assign.l new file mode 100644 index 0000000..dd21a54 --- /dev/null +++ b/test/programs/09_compound_assign.l @@ -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; +}