feat: struct参数/返回值 + SourceLoc + 测试补全
- struct 可作函数参数和返回值 (fn make_point -> Point) - SourceLoc 抽象: 所有 ast_make_* 参数 + AstNode 迁移完毕 - sema: +4 struct 类型检查测试 (字段类型/未定义/数量/嵌套) - codegen: +2 struct IR 生成测试 (decl + field_access) - 新增集成测试 14_struct_fn.l 测试: 104 单元 + 14 集成 = 全部通过
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
struct Point {
|
||||
x: i64,
|
||||
y: i64,
|
||||
}
|
||||
|
||||
fn make_point(x: i64, y: i64) -> Point {
|
||||
return Point { x: x, y: y };
|
||||
}
|
||||
|
||||
fn print_point(p: Point) -> void {
|
||||
print_i64(p.x);
|
||||
print_i64(p.y);
|
||||
}
|
||||
|
||||
fn main() -> i64 {
|
||||
let p: Point = make_point(3, 4);
|
||||
print_point(p);
|
||||
return 0;
|
||||
}
|
||||
+142
-27
@@ -8,12 +8,12 @@ void test_codegen_simple_function() {
|
||||
Arena a = arena_create(1);
|
||||
|
||||
// 构造 AST: fn main() -> i64 { return 42; }
|
||||
AstNode* ret = ast_make_return(&a, ast_make_literal_i64(&a, 42, 1, 1), 1, 1);
|
||||
AstNode* ret = ast_make_return(&a, ast_make_literal_i64(&a, 42, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* stmts[] = { ret };
|
||||
AstNode* body = ast_make_block(&a, stmts, 1, 1, 1);
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, body, 1, 1);
|
||||
AstNode* body = ast_make_block(&a, stmts, 1, loc_at(1, 1));
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, body, loc_at(1, 1));
|
||||
AstNode* fns[] = { fn };
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1);
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, loc_at(1, 1));
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx = NULL;
|
||||
@@ -35,19 +35,19 @@ void test_codegen_if_else() {
|
||||
Arena a = arena_create(1);
|
||||
|
||||
// fn main() -> i64 { if true { return 1; } else { return 0; } }
|
||||
AstNode* then_ret = ast_make_return(&a, ast_make_literal_i64(&a, 1, 1, 1), 1, 1);
|
||||
AstNode* then_ret = ast_make_return(&a, ast_make_literal_i64(&a, 1, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* then_stmts[] = { then_ret };
|
||||
AstNode* then_block = ast_make_block(&a, then_stmts, 1, 1, 1);
|
||||
AstNode* else_ret = ast_make_return(&a, ast_make_literal_i64(&a, 0, 1, 1), 1, 1);
|
||||
AstNode* then_block = ast_make_block(&a, then_stmts, 1, loc_at(1, 1));
|
||||
AstNode* else_ret = ast_make_return(&a, ast_make_literal_i64(&a, 0, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* else_stmts[] = { else_ret };
|
||||
AstNode* else_block = ast_make_block(&a, else_stmts, 1, 1, 1);
|
||||
AstNode* else_block = ast_make_block(&a, else_stmts, 1, loc_at(1, 1));
|
||||
AstNode* if_stmt = ast_make_if(&a,
|
||||
ast_make_literal_bool(&a, true, 1, 1), then_block, else_block, 1, 1);
|
||||
ast_make_literal_bool(&a, true, loc_at(1, 1)), then_block, else_block, loc_at(1, 1));
|
||||
AstNode* stmts[] = { if_stmt };
|
||||
AstNode* body = ast_make_block(&a, stmts, 1, 1, 1);
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, body, 1, 1);
|
||||
AstNode* body = ast_make_block(&a, stmts, 1, loc_at(1, 1));
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, body, loc_at(1, 1));
|
||||
AstNode* fns[] = { fn };
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1);
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, loc_at(1, 1));
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx2 = NULL;
|
||||
@@ -68,17 +68,17 @@ void test_codegen_binary_ops() {
|
||||
|
||||
// fn main() -> i64 { return 1 + 2 * 3; }
|
||||
AstNode* expr = ast_make_binary(&a, OP_ADD,
|
||||
ast_make_literal_i64(&a, 1, 1, 1),
|
||||
ast_make_literal_i64(&a, 1, loc_at(1, 1)),
|
||||
ast_make_binary(&a, OP_MUL,
|
||||
ast_make_literal_i64(&a, 2, 1, 1),
|
||||
ast_make_literal_i64(&a, 3, 1, 1), 1, 1),
|
||||
1, 1);
|
||||
AstNode* ret = ast_make_return(&a, expr, 1, 1);
|
||||
ast_make_literal_i64(&a, 2, loc_at(1, 1)),
|
||||
ast_make_literal_i64(&a, 3, loc_at(1, 1)), loc_at(1, 1)),
|
||||
loc_at(1, 1));
|
||||
AstNode* ret = ast_make_return(&a, expr, loc_at(1, 1));
|
||||
AstNode* stmts[] = { ret };
|
||||
AstNode* body = ast_make_block(&a, stmts, 1, 1, 1);
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, body, 1, 1);
|
||||
AstNode* body = ast_make_block(&a, stmts, 1, loc_at(1, 1));
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, body, loc_at(1, 1));
|
||||
AstNode* fns[] = { fn };
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1);
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, loc_at(1, 1));
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx3 = NULL;
|
||||
@@ -98,17 +98,17 @@ void test_codegen_while_loop() {
|
||||
Arena a = arena_create(1);
|
||||
// fn main() -> i64 { while true { return 0; } return 1; }
|
||||
AstNode* while_body_stmts[] = {
|
||||
ast_make_return(&a, ast_make_literal_i64(&a, 0, 1, 1), 1, 1)
|
||||
ast_make_return(&a, ast_make_literal_i64(&a, 0, loc_at(1, 1)), loc_at(1, 1))
|
||||
};
|
||||
AstNode* while_body = ast_make_block(&a, while_body_stmts, 1, 1, 1);
|
||||
AstNode* while_body = ast_make_block(&a, while_body_stmts, 1, loc_at(1, 1));
|
||||
AstNode* while_stmt = ast_make_while(&a,
|
||||
ast_make_literal_bool(&a, true, 1, 1), while_body, 1, 1);
|
||||
AstNode* ret = ast_make_return(&a, ast_make_literal_i64(&a, 1, 1, 1), 1, 1);
|
||||
ast_make_literal_bool(&a, true, loc_at(1, 1)), while_body, loc_at(1, 1));
|
||||
AstNode* ret = ast_make_return(&a, ast_make_literal_i64(&a, 1, loc_at(1, 1)), loc_at(1, 1));
|
||||
AstNode* stmts[] = { while_stmt, ret };
|
||||
AstNode* fn_body = ast_make_block(&a, stmts, 2, 1, 1);
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, fn_body, 1, 1);
|
||||
AstNode* fn_body = ast_make_block(&a, stmts, 2, loc_at(1, 1));
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, fn_body, loc_at(1, 1));
|
||||
AstNode* fns[] = { fn };
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1);
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, loc_at(1, 1));
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx4 = NULL;
|
||||
@@ -121,10 +121,125 @@ void test_codegen_while_loop() {
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
/* === struct IR 生成测试 === */
|
||||
|
||||
void test_codegen_struct_decl() {
|
||||
Arena a = arena_create(1);
|
||||
|
||||
/* 构造 AST: struct Point { x: i64, y: i64 } */
|
||||
AstNode* fields[2];
|
||||
fields[0] = ast_make_parameter(&a, "x", TYPE_I64, NULL, loc_at(1, 1));
|
||||
fields[1] = ast_make_parameter(&a, "y", TYPE_I64, NULL, loc_at(1, 1));
|
||||
AstNode* struct_decl = ast_make_struct_decl(&a, "Point", fields, 2, loc_at(1, 1));
|
||||
AstNode* structs[] = { struct_decl };
|
||||
|
||||
/* 构造 fn main() -> i64 { let p = Point { x: 1, y: 2 }; return p.x; } */
|
||||
const char* fnames[] = {"x", "y"};
|
||||
AstNode* fvals[] = {
|
||||
ast_make_literal_i64(&a, 1, loc_at(1, 1)),
|
||||
ast_make_literal_i64(&a, 2, loc_at(1, 1))
|
||||
};
|
||||
AstNode* init = ast_make_struct_init(&a, "Point", fnames, fvals, 2, loc_at(1, 1));
|
||||
/* 手动设置类型(绕过 sema,codegen 需要读取 type 字段) */
|
||||
init->type.kind = TYPE_STRUCT;
|
||||
init->type.struct_name = "Point";
|
||||
|
||||
AstNode* let_stmt = ast_make_let(&a, "p", TYPE_UNKNOWN, false, false,
|
||||
init, NULL, loc_at(1, 1));
|
||||
|
||||
/* return p.x; */
|
||||
AstNode* p_ident = ast_make_ident(&a, "p", loc_at(1, 1));
|
||||
p_ident->type.kind = TYPE_STRUCT;
|
||||
p_ident->type.struct_name = "Point";
|
||||
|
||||
AstNode* field_x = ast_make_field_access(&a, p_ident, "x", loc_at(1, 1));
|
||||
field_x->as.field_access.field_index = 0; /* x 是第 0 个字段 */
|
||||
field_x->type.kind = TYPE_I64;
|
||||
|
||||
AstNode* ret = ast_make_return(&a, field_x, loc_at(1, 1));
|
||||
AstNode* stmts[] = { let_stmt, ret };
|
||||
AstNode* body = ast_make_block(&a, stmts, 2, loc_at(1, 1));
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, body, loc_at(1, 1));
|
||||
AstNode* fns[] = { fn };
|
||||
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, structs, 1, loc_at(1, 1));
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx = NULL;
|
||||
LLVMModuleRef mod = codegen_module(prog, &a, "test_struct_decl", &err, &ctx);
|
||||
ASSERT(mod != NULL);
|
||||
ASSERT(err == NULL);
|
||||
|
||||
char* verify_err = NULL;
|
||||
int failed = LLVMVerifyModule(mod, LLVMReturnStatusAction, &verify_err);
|
||||
ASSERT(!failed);
|
||||
|
||||
LLVMDisposeModule(mod);
|
||||
LLVMContextDispose(ctx);
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
void test_codegen_struct_field_access() {
|
||||
Arena a = arena_create(1);
|
||||
|
||||
/* 构造 AST: struct Point { x: i64, y: i64 } */
|
||||
AstNode* fields[2];
|
||||
fields[0] = ast_make_parameter(&a, "x", TYPE_I64, NULL, loc_at(1, 1));
|
||||
fields[1] = ast_make_parameter(&a, "y", TYPE_I64, NULL, loc_at(1, 1));
|
||||
AstNode* struct_decl = ast_make_struct_decl(&a, "Point", fields, 2, loc_at(1, 1));
|
||||
AstNode* structs[] = { struct_decl };
|
||||
|
||||
/* 构造 fn main() -> i64 { let p = Point { x: 5, y: 10 }; return p.y; } */
|
||||
const char* fnames[] = {"x", "y"};
|
||||
AstNode* fvals[] = {
|
||||
ast_make_literal_i64(&a, 5, loc_at(1, 1)),
|
||||
ast_make_literal_i64(&a, 10, loc_at(1, 1))
|
||||
};
|
||||
AstNode* init = ast_make_struct_init(&a, "Point", fnames, fvals, 2, loc_at(1, 1));
|
||||
init->type.kind = TYPE_STRUCT;
|
||||
init->type.struct_name = "Point";
|
||||
|
||||
AstNode* let_stmt = ast_make_let(&a, "p", TYPE_UNKNOWN, false, false,
|
||||
init, NULL, loc_at(1, 1));
|
||||
|
||||
/* return p.y; */
|
||||
AstNode* p_ident = ast_make_ident(&a, "p", loc_at(1, 1));
|
||||
p_ident->type.kind = TYPE_STRUCT;
|
||||
p_ident->type.struct_name = "Point";
|
||||
|
||||
AstNode* field_y = ast_make_field_access(&a, p_ident, "y", loc_at(1, 1));
|
||||
field_y->as.field_access.field_index = 1; /* y 是第 1 个字段 */
|
||||
field_y->type.kind = TYPE_I64;
|
||||
|
||||
AstNode* ret = ast_make_return(&a, field_y, loc_at(1, 1));
|
||||
AstNode* stmts[] = { let_stmt, ret };
|
||||
AstNode* body = ast_make_block(&a, stmts, 2, loc_at(1, 1));
|
||||
AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, NULL, body, loc_at(1, 1));
|
||||
AstNode* fns[] = { fn };
|
||||
|
||||
AstNode* prog = ast_make_program(&a, fns, 1, structs, 1, loc_at(1, 1));
|
||||
|
||||
const char* err = NULL;
|
||||
LLVMContextRef ctx = NULL;
|
||||
LLVMModuleRef mod = codegen_module(prog, &a, "test_struct_field", &err, &ctx);
|
||||
ASSERT(mod != NULL);
|
||||
ASSERT(err == NULL);
|
||||
|
||||
char* verify_err = NULL;
|
||||
int failed = LLVMVerifyModule(mod, LLVMReturnStatusAction, &verify_err);
|
||||
ASSERT(!failed);
|
||||
|
||||
LLVMDisposeModule(mod);
|
||||
LLVMContextDispose(ctx);
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
TEST_RUN(test_codegen_simple_function);
|
||||
TEST_RUN(test_codegen_if_else);
|
||||
TEST_RUN(test_codegen_binary_ops);
|
||||
TEST_RUN(test_codegen_while_loop);
|
||||
TEST_RUN(test_codegen_struct_decl);
|
||||
TEST_RUN(test_codegen_struct_field_access);
|
||||
return test_summary();
|
||||
}
|
||||
|
||||
@@ -115,6 +115,76 @@ void test_str_concat_type_ok() {
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
/* === struct 类型检查测试 === */
|
||||
|
||||
void test_struct_field_type_mismatch() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"struct Point { x: i64, y: i64 } fn main() { let p: Point = Point { x: 10, y: true }; return; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count > 0); // y 字段类型不匹配: true 是 bool, 不是 i64
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
void test_struct_undefined() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"fn main() { let p: Unknown = Unknown { x: 1 }; return; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count > 0); // Unknown 未定义
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
void test_struct_field_count_mismatch() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"struct Point { x: i64, y: i64 } fn main() { let p: Point = Point { x: 10 }; return; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count > 0); // 缺少字段 'y'
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
void test_struct_nested_type_ok() {
|
||||
Arena a = arena_create(1);
|
||||
size_t tc; ErrorInfo lex_err = {0};
|
||||
Token* toks = lex(&a,
|
||||
"struct Point { x: i64, y: i64 } struct Rect { tl: Point, br: Point } fn main() { let r: Rect = Rect { tl: Point { x: 0, y: 0 }, br: Point { x: 1, y: 1 } }; print_i64(r.tl.x); return; }",
|
||||
"test", &tc, &lex_err);
|
||||
ASSERT(toks != NULL);
|
||||
ErrorInfo parse_err = {0};
|
||||
AstNode* ast = parse(&a, toks, tc, "test", &parse_err);
|
||||
ASSERT(ast != NULL);
|
||||
|
||||
ErrorList errors; error_init(&errors);
|
||||
sema_analyze(ast, &errors, &a);
|
||||
ASSERT(errors.count == 0); // 嵌套结构体类型检查应通过
|
||||
arena_destroy(&a);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
TEST_RUN(test_type_error);
|
||||
TEST_RUN(test_undefined_var);
|
||||
@@ -123,5 +193,9 @@ int main(void) {
|
||||
TEST_RUN(test_assign_immutable_error);
|
||||
TEST_RUN(test_str_type_ok);
|
||||
TEST_RUN(test_str_concat_type_ok);
|
||||
TEST_RUN(test_struct_field_type_mismatch);
|
||||
TEST_RUN(test_struct_undefined);
|
||||
TEST_RUN(test_struct_field_count_mismatch);
|
||||
TEST_RUN(test_struct_nested_type_ok);
|
||||
return test_summary();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user