From bd02a4989e08f4459e7aa0b21c2102f1a40e3288 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 00:42:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20let=20mut=20+=20=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=20=E2=80=94=20while=20=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E5=8F=AF=E4=BF=AE=E6=94=B9=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lexer: 新增 TOK_MUT 关键字 - ast: AST_ASSIGN_STMT 节点 + let_stmt.is_mut 标志 - parser: ‘let mut’ 前缀识别 + ‘ident = expr;’ 赋值语句 - sema: Symbol.is_mut 可变性检查(不可变变量赋值报错) - codegen: AST_ASSIGN_STMT → store 指令 - 新增集成测试 06_mut_while.l(while 循环 + 计数器) 基于 Codex 分析报告 P0 建议。 --- src/ast/ast.c | 10 ++++++++-- src/ast/ast.h | 8 ++++++-- src/codegen/codegen.c | 9 +++++++++ src/lexer/lexer.c | 1 + src/lexer/token.c | 2 +- src/lexer/token.h | 2 +- src/parser/parser.c | 16 +++++++++++++++- src/sema/sema.c | 37 +++++++++++++++++++++++++++++++++++- src/sema/symbol.c | 2 ++ src/sema/symbol.h | 1 + test/programs/06_mut_while.l | 8 ++++++++ 11 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 test/programs/06_mut_while.l diff --git a/src/ast/ast.c b/src/ast/ast.c index e7a998a..5eb6812 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -35,10 +35,16 @@ AstNode* ast_make_block(void* alloc, AstNode** stmts, size_t count, int line, in return n; } -AstNode* ast_make_let(void* alloc, const char* name, TypeKind annot_type, bool has_type_annot, AstNode* init, int line, int col) { +AstNode* ast_make_let(void* alloc, const char* name, TypeKind annot_type, bool has_type_annot, bool is_mut, AstNode* init, int line, int col) { NEW(alloc, AST_LET_STMT); n->as.let_stmt.name = name; n->as.let_stmt.annot_type = annot_type; - n->as.let_stmt.has_type_annot = has_type_annot; n->as.let_stmt.init = init; + n->as.let_stmt.has_type_annot = has_type_annot; n->as.let_stmt.is_mut = is_mut; n->as.let_stmt.init = init; + return n; +} + +AstNode* ast_make_assign(void* alloc, const char* name, AstNode* value, int line, int col) { + NEW(alloc, AST_ASSIGN_STMT); + n->as.assign_stmt.name = name; n->as.assign_stmt.value = value; return n; } diff --git a/src/ast/ast.h b/src/ast/ast.h index fe1dc43..06d463d 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -10,6 +10,7 @@ typedef enum { AST_PARAMETER, AST_BLOCK, AST_LET_STMT, + AST_ASSIGN_STMT, AST_IF_STMT, AST_WHILE_STMT, AST_RETURN_STMT, @@ -52,7 +53,9 @@ struct AstNode { // AST_BLOCK struct { struct AstNode** stmts; size_t stmt_count; } block; // AST_LET_STMT - struct { const char* name; TypeKind annot_type; bool has_type_annot; struct AstNode* init; } let_stmt; + struct { const char* name; TypeKind annot_type; bool has_type_annot; bool is_mut; struct AstNode* init; } let_stmt; + // AST_ASSIGN_STMT + struct { const char* name; struct AstNode* value; } assign_stmt; // AST_IF_STMT struct { struct AstNode* cond; struct AstNode* then_block; struct AstNode* else_block; } if_stmt; // AST_WHILE_STMT @@ -80,7 +83,8 @@ AstNode* ast_make_function(void* alloc, const char* name, AstNode** params, size TypeKind ret, AstNode* body, int line, int col); AstNode* ast_make_parameter(void* alloc, const char* name, TypeKind type, int line, int col); AstNode* ast_make_block(void* alloc, AstNode** stmts, size_t count, int line, int col); -AstNode* ast_make_let(void* alloc, const char* name, TypeKind annot_type, bool has_type_annot, AstNode* init, int line, int col); +AstNode* ast_make_let(void* alloc, const char* name, TypeKind annot_type, bool has_type_annot, bool is_mut, AstNode* init, int line, int col); +AstNode* ast_make_assign(void* alloc, const char* name, AstNode* value, int line, int col); AstNode* ast_make_if(void* alloc, AstNode* cond, AstNode* then_b, AstNode* else_b, int line, int col); AstNode* ast_make_while(void* alloc, AstNode* cond, AstNode* body, int line, int col); AstNode* ast_make_return(void* alloc, AstNode* expr, int line, int col); diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 967296d..664d417 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -229,6 +229,15 @@ static void codegen_stmt(CgCtx* ctx, AstNode* node) { break; } + case AST_ASSIGN_STMT: { + LLVMValueRef ptr = find_var(ctx, node->as.assign_stmt.name); + if (!ptr) return; + LLVMValueRef val = codegen_expr(ctx, node->as.assign_stmt.value); + if (!val) return; + LLVMBuildStore(ctx->builder, val, ptr); + break; + } + case AST_EXPR_STMT: codegen_expr(ctx, node->as.expr_stmt.expr); break; diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 4b7807a..d5ac78b 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -55,6 +55,7 @@ static Token lex_number(Lexer* l) { static TokenKind check_keyword(const Token* tok) { #define KW(s, k) if (tok->length == sizeof(s)-1 && memcmp(tok->start, s, sizeof(s)-1) == 0) return k KW("fn", TOK_FN); KW("let", TOK_LET); + KW("mut", TOK_MUT); KW("if", TOK_IF); KW("else", TOK_ELSE); KW("while", TOK_WHILE); KW("return", TOK_RETURN); KW("i64", TOK_I64); KW("f64", TOK_F64); diff --git a/src/lexer/token.c b/src/lexer/token.c index 49f3ea2..dc59e8f 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -5,7 +5,7 @@ #include static const char* NAMES[] = { - [TOK_FN] = "fn", [TOK_LET] = "let", [TOK_IF] = "if", + [TOK_FN] = "fn", [TOK_LET] = "let", [TOK_MUT] = "mut", [TOK_IF] = "if", [TOK_ELSE] = "else", [TOK_WHILE] = "while", [TOK_RETURN] = "return", [TOK_I64] = "i64", [TOK_F64] = "f64", [TOK_BOOL] = "bool", [TOK_VOID] = "void", [TOK_INT_LIT] = "整数", [TOK_FLOAT_LIT] = "浮点数", diff --git a/src/lexer/token.h b/src/lexer/token.h index 470409e..a74d471 100644 --- a/src/lexer/token.h +++ b/src/lexer/token.h @@ -6,7 +6,7 @@ // === Token 类型枚举 === typedef enum { // 关键字 - TOK_FN, TOK_LET, TOK_IF, TOK_ELSE, TOK_WHILE, TOK_RETURN, + TOK_FN, TOK_LET, TOK_MUT, TOK_IF, TOK_ELSE, TOK_WHILE, TOK_RETURN, // 类型关键字 TOK_I64, TOK_F64, TOK_BOOL, TOK_VOID, // 字面量 diff --git a/src/parser/parser.c b/src/parser/parser.c index 0ebed43..bab032a 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -194,6 +194,8 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { if (t->kind == TOK_LET) { advance(p); + bool is_mut = false; + if (peek(p)->kind == TOK_MUT) { is_mut = true; advance(p); } const Token* name = expect(p, TOK_IDENT, error, "let 后应为变量名"); if (!name) return NULL; // 可选的类型标注 @@ -214,7 +216,7 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { if (!expect(p, TOK_SEMICOLON, error, "缺少 ';'")) return NULL; return ast_make_let(p->arena, arena_strdup_impl(p->arena, name->start, name->length), - annot_type, has_type_annot, init, t->line, t->col); + annot_type, has_type_annot, is_mut, init, t->line, t->col); } if (t->kind == TOK_IF) { @@ -255,6 +257,18 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { return ast_make_return(p->arena, expr, t->line, t->col); } + // 赋值语句: ident = expr ; + if (t->kind == TOK_IDENT && (t + 1)->kind == TOK_ASSIGN) { + const Token* name = advance(p); // 消费标识符 + advance(p); // 消费 '=' + AstNode* value = parse_expr(p, error); + if (!value) return NULL; + if (!expect(p, TOK_SEMICOLON, error, "缺少 ';'")) return NULL; + return ast_make_assign(p->arena, + arena_strdup_impl(p->arena, name->start, name->length), + value, name->line, name->col); + } + // 表达式语句 AstNode* expr = parse_expr(p, error); if (!expr) return NULL; diff --git a/src/sema/sema.c b/src/sema/sema.c index 2ff4074..2cbd3d8 100644 --- a/src/sema/sema.c +++ b/src/sema/sema.c @@ -204,13 +204,48 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena* } node->type.kind = var_type; - if (!scope_insert(scope, a, node->as.let_stmt.name, SYM_VARIABLE, var_type)) { + Symbol* sym = scope_insert(scope, a, node->as.let_stmt.name, SYM_VARIABLE, var_type); + if (!sym) { error_add(errors, "", node->line, node->col, "变量 '%s' 重复定义", node->as.let_stmt.name); + } else { + sym->is_mut = node->as.let_stmt.is_mut; } break; } + case AST_ASSIGN_STMT: { + Symbol* sym = scope_lookup(scope, node->as.assign_stmt.name); + if (!sym) { + error_add(errors, "", node->line, node->col, + "未定义的变量 '%s'", node->as.assign_stmt.name); + node->type.kind = TYPE_ERROR; + break; + } + if (sym->kind != SYM_VARIABLE) { + error_add(errors, "", node->line, node->col, + "'%s' 不是变量,不能赋值", node->as.assign_stmt.name); + node->type.kind = TYPE_ERROR; + break; + } + if (!sym->is_mut) { + error_add(errors, "", node->line, node->col, + "不能对不可变变量 '%s' 赋值(需用 let mut 声明)", + node->as.assign_stmt.name); + node->type.kind = TYPE_ERROR; + break; + } + analyze_expr(node->as.assign_stmt.value, scope, errors, a); + TypeKind value_ty = node->as.assign_stmt.value->type.kind; + if (value_ty != TYPE_ERROR && value_ty != sym->type) { + error_add(errors, "", node->line, node->col, + "赋值类型不匹配: 变量 '%s' 类型为 '%s',但表达式类型为 '%s'", + node->as.assign_stmt.name, type_name(sym->type), type_name(value_ty)); + } + node->type.kind = TYPE_VOID; + break; + } + case AST_IF_STMT: analyze_expr(node->as.if_stmt.cond, scope, errors, a); if (node->as.if_stmt.cond->type.kind != TYPE_BOOL && diff --git a/src/sema/symbol.c b/src/sema/symbol.c index aeb67b7..56a3642 100644 --- a/src/sema/symbol.c +++ b/src/sema/symbol.c @@ -27,6 +27,8 @@ Symbol* scope_insert(Scope* scope, void* alloc, const char* name, } Symbol* sym = (Symbol*)arena_alloc_impl(alloc, sizeof(Symbol)); sym->name = name; sym->kind = kind; sym->type = type; + sym->is_mut = false; sym->return_type = TYPE_VOID; + sym->param_types = NULL; sym->param_count = 0; sym->next = scope->head; scope->head = sym; return sym; diff --git a/src/sema/symbol.h b/src/sema/symbol.h index 72a36b0..6a0f681 100644 --- a/src/sema/symbol.h +++ b/src/sema/symbol.h @@ -10,6 +10,7 @@ typedef struct Symbol { const char* name; SymbolKind kind; TypeKind type; // 变量/参数的类型 + bool is_mut; // 变量是否可变(可被赋值) // 函数特有 TypeKind return_type; TypeKind* param_types; diff --git a/test/programs/06_mut_while.l b/test/programs/06_mut_while.l new file mode 100644 index 0000000..9520090 --- /dev/null +++ b/test/programs/06_mut_while.l @@ -0,0 +1,8 @@ +fn main() -> i64 { + let mut i: i64 = 0; + while i < 5 { + print_i64(i); + i = i + 1; + } + return 0; +}