feat: 结构体 struct — 最后一项 P0 功能

- lexer: TOK_STRUCT, TOK_DOT 关键字和运算符
- ast: AST_STRUCT_DECL/STRUCT_INIT/FIELD_ACCESS 3 种新节点
- parser: struct 声明 + .field 访问 + Name{field:val} 初始化
- sema: struct 类型符号表,字段类型解析,初始化字段检查
- codegen: LLVMStructType + extractvalue/insertvalue 字段操作
- 新增集成测试: 12_struct.l, 13_struct_nested.l
- 基于 Codex 分析报告 P0 #4

所有 P0 功能已全部完成。
This commit is contained in:
2026-06-05 12:21:22 +08:00
parent 620cec4d57
commit b390d390f3
17 changed files with 1521 additions and 47 deletions
+134 -12
View File
@@ -33,6 +33,7 @@ typedef enum {
PREC_TERM = 50,
PREC_FACTOR = 60,
PREC_UNARY = 70,
PREC_POSTFIX = 80, // .field, call()
} Precedence;
static Precedence tok_to_prec(TokenKind kind) {
@@ -64,6 +65,8 @@ static BinaryOp tok_to_binop(TokenKind kind) {
static AstNode* parse_expr(Parser* p, ErrorInfo* error);
static AstNode* parse_expr_prec(Parser* p, Precedence prec, ErrorInfo* error);
static AstNode* parse_block(Parser* p, ErrorInfo* error);
static AstNode* parse_statement(Parser* p, ErrorInfo* error);
static AstNode* parse_function(Parser* p, ErrorInfo* error);
// === 前缀解析 ===
static AstNode* parse_unary(Parser* p, ErrorInfo* error) {
@@ -99,10 +102,58 @@ static AstNode* parse_literal(Parser* p) {
}
}
// === 结构体初始化解析: Name { field: val, ... } ===
static AstNode* parse_struct_init(Parser* p, const Token* name, ErrorInfo* error) {
advance(p); // 跳过 '{'
const char* fnames[32];
AstNode* fvals[32];
int fcount = 0;
while (peek(p)->kind != TOK_RBRACE && !error->message) {
const Token* fname = expect(p, TOK_IDENT, error, "字段名");
if (!fname) return NULL;
if (!expect(p, TOK_COLON, error, "缺少 ':'")) return NULL;
AstNode* val = parse_expr(p, error);
if (!val) return NULL;
fnames[fcount] = arena_strdup_impl(p->arena, fname->start, fname->length);
fvals[fcount] = val;
fcount++;
if (peek(p)->kind == TOK_COMMA) advance(p);
else break;
}
if (!expect(p, TOK_RBRACE, error, "缺少 '}'")) return NULL;
const char** n_arr = arena_alloc_impl(p->arena, fcount * sizeof(const char*));
memcpy(n_arr, fnames, fcount * sizeof(const char*));
AstNode** v_arr = arena_alloc_impl(p->arena, fcount * sizeof(AstNode*));
memcpy(v_arr, fvals, fcount * sizeof(AstNode*));
return ast_make_struct_init(p->arena,
arena_strdup_impl(p->arena, name->start, name->length),
n_arr, v_arr, fcount, name->line, name->col);
}
// === 标识符 / 函数调用 / 结构体初始化 ===
static AstNode* parse_ident_or_call(Parser* p, ErrorInfo* error) {
const Token* name = advance(p);
// 结构体初始化: Name { field: val, ... }
// 用提前看来区别 struct init 和 block
// struct init → { IDENT COLON ... block → { 可能是 let/if/while/...
if (peek(p)->kind == TOK_LBRACE) {
const Token* after_brace = &p->tokens[p->pos + 1];
if (after_brace->kind == TOK_IDENT) {
const Token* after_fname = &p->tokens[p->pos + 2];
if (after_fname->kind == TOK_COLON) {
return parse_struct_init(p, name, error);
}
}
}
// 函数调用: name(...)
if (match(p, TOK_LPAREN)) {
// 函数调用
AstNode* args[16]; int arg_count = 0;
while (peek(p)->kind != TOK_RPAREN && !error->message) {
if (arg_count >= 16) {
@@ -149,9 +200,22 @@ static AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error
}
if (!left) return NULL;
// 中缀解析循环
// 中缀/后置解析循环
while (!error->message) {
TokenKind kind = peek(p)->kind;
// 后置字段访问: expr.field
if (kind == TOK_DOT) {
advance(p); // 跳过 '.'
const Token* field = expect(p, TOK_IDENT, error, "缺少字段名");
if (!field) return NULL;
left = ast_make_field_access(p->arena, left,
arena_strdup_impl(p->arena, field->start, field->length),
field->line, field->col);
continue;
}
// 中缀运算符
Precedence prec = tok_to_prec(kind);
if (prec <= min_prec) break;
@@ -179,8 +243,46 @@ static TypeKind token_to_type(TokenKind k) {
default: return TYPE_VOID; }
}
// === 结构体声明解析 ===
static AstNode* parse_struct_decl(Parser* p, ErrorInfo* error) {
const Token* s_tok = advance(p); // 跳过 'struct'
const Token* name = expect(p, TOK_IDENT, error, "struct 后应为结构体名");
if (!name) return NULL;
if (!expect(p, TOK_LBRACE, error, "缺少 '{'")) return NULL;
AstNode* fields[32]; int fcount = 0;
while (peek(p)->kind != TOK_RBRACE && !error->message) {
const Token* fname = expect(p, TOK_IDENT, error, "字段名");
if (!fname) return NULL;
if (!expect(p, TOK_COLON, error, "缺少 ':'")) return NULL;
const Token* ftype = advance(p);
TypeKind field_kind;
const char* field_struct_name = NULL;
if (is_type_token(ftype->kind)) {
field_kind = token_to_type(ftype->kind);
} else if (ftype->kind == TOK_IDENT) {
field_kind = TYPE_STRUCT;
field_struct_name = arena_strdup_impl(p->arena, ftype->start, ftype->length);
} else {
error->message = "无效的字段类型"; error->filename = p->filename;
error->line = ftype->line; error->col = ftype->col; return NULL;
}
fields[fcount++] = ast_make_parameter(p->arena,
arena_strdup_impl(p->arena, fname->start, fname->length),
field_kind, field_struct_name, fname->line, fname->col);
if (peek(p)->kind == TOK_COMMA) advance(p);
else break;
}
if (!expect(p, TOK_RBRACE, error, "缺少 '}'")) return NULL;
AstNode** farr = arena_alloc_impl(p->arena, fcount * sizeof(AstNode*));
memcpy(farr, fields, fcount * sizeof(AstNode*));
return ast_make_struct_decl(p->arena,
arena_strdup_impl(p->arena, name->start, name->length),
farr, fcount, s_tok->line, s_tok->col);
}
// === 语句解析 ===
static AstNode* parse_statement(Parser* p, ErrorInfo* error);
static AstNode* parse_block(Parser* p, ErrorInfo* error) {
const Token* open = peek(p);
@@ -209,13 +311,20 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) {
// 可选的类型标注
TypeKind annot_type = TYPE_UNKNOWN;
bool has_type_annot = false;
const char* struct_type_name = NULL;
if (match(p, TOK_COLON)) {
const Token* type_tok = advance(p);
if (!is_type_token(type_tok->kind)) {
if (!is_type_token(type_tok->kind) && type_tok->kind != TOK_IDENT) {
error->message = "无效的类型标注"; error->filename = p->filename;
error->line = type_tok->line; error->col = type_tok->col; return NULL;
}
annot_type = token_to_type(type_tok->kind);
if (is_type_token(type_tok->kind)) {
annot_type = token_to_type(type_tok->kind);
} else {
// struct 类型名
annot_type = TYPE_STRUCT;
struct_type_name = arena_strdup_impl(p->arena, type_tok->start, type_tok->length);
}
has_type_annot = true;
}
if (!expect(p, TOK_ASSIGN, error, "缺少 '='")) return NULL;
@@ -224,7 +333,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, is_mut, init, t->line, t->col);
annot_type, has_type_annot, is_mut, init, struct_type_name, t->line, t->col);
}
if (t->kind == TOK_IF) {
@@ -285,7 +394,7 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) {
const char* vname = arena_strdup_impl(p->arena, var_name->start, var_name->length);
// 构建: let mut i = start;
AstNode* let_stmt = ast_make_let(p->arena, vname, TYPE_UNKNOWN, false, true, start_expr, var_name->line, var_name->col);
AstNode* let_stmt = ast_make_let(p->arena, vname, TYPE_UNKNOWN, false, true, start_expr, NULL, var_name->line, var_name->col);
// 构建: i < end (while 条件)
AstNode* cond = ast_make_binary(p->arena, OP_LT,
@@ -399,7 +508,7 @@ static AstNode* parse_function(Parser* p, ErrorInfo* error) {
}
params[pcount++] = ast_make_parameter(p->arena,
arena_strdup_impl(p->arena, pname->start, pname->length),
token_to_type(ptype->kind), pname->line, pname->col);
token_to_type(ptype->kind), NULL, pname->line, pname->col);
if (match(p, TOK_COMMA)) continue;
else break;
}
@@ -432,11 +541,24 @@ AstNode* parse(Arena* a, const Token* tokens, size_t count,
Parser p = {.tokens = tokens, .count = count, .pos = 0,
.filename = filename, .arena = a};
AstNode* functions[256]; int fn_count = 0;
AstNode* structs[64]; int struct_count = 0;
while (peek(&p)->kind != TOK_EOF && !error->message) {
functions[fn_count++] = parse_function(&p, error);
if (peek(&p)->kind == TOK_STRUCT) {
structs[struct_count++] = parse_struct_decl(&p, error);
} else if (peek(&p)->kind == TOK_FN) {
functions[fn_count++] = parse_function(&p, error);
} else {
error->message = "顶层只允许 fn 或 struct";
error->filename = p.filename;
error->line = peek(&p)->line;
error->col = peek(&p)->col;
return NULL;
}
}
if (error->message) return NULL;
AstNode** arr = arena_alloc_impl(a, fn_count * sizeof(AstNode*));
memcpy(arr, functions, fn_count * sizeof(AstNode*));
return ast_make_program(a, arr, fn_count, 0, 0);
AstNode** fn_arr = arena_alloc_impl(a, fn_count * sizeof(AstNode*));
memcpy(fn_arr, functions, fn_count * sizeof(AstNode*));
AstNode** st_arr = arena_alloc_impl(a, struct_count * sizeof(AstNode*));
memcpy(st_arr, structs, struct_count * sizeof(AstNode*));
return ast_make_program(a, fn_arr, fn_count, st_arr, struct_count, 0, 0);
}