feat: 模块系统 mod + pub — 多文件编译支持

This commit is contained in:
2026-06-06 16:09:30 +08:00
parent e02cc7b1d6
commit fa734b8a23
9 changed files with 197 additions and 22 deletions
+145 -7
View File
@@ -1,4 +1,5 @@
#include "parser.h"
#include "lexer.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -72,7 +73,7 @@ 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_function(Parser* p, bool is_pub, ErrorInfo* error);
// === 前缀解析 ===
static AstNode* parse_unary(Parser* p, ErrorInfo* error) {
@@ -188,11 +189,56 @@ static AstNode* parse_struct_init(Parser* p, const Token* name, ErrorInfo* error
static AstNode* parse_ident_or_call(Parser* p, ErrorInfo* error) {
const Token* name = advance(p);
// 枚举变体引用: Name::Variant 或 Name::Variant(payload)
// 枚举变体或模块函数: Name::Variant 或 Name::fn
if (peek(p)->kind == TOK_COLON_COLON) {
advance(p); // 跳过 ::
const Token* variant = expect(p, TOK_IDENT, error, "枚举变体名");
if (!variant) return NULL;
// Name::fn 或 Name::Variant 或 Name::Variant(payload)
if (peek(p)->kind == TOK_LPAREN) {
// 前进探测: 检查括号内是否有多参数或命名参数(→函数调用)还是单表达式(→枚举payload)
size_t probe = p->pos + 1;
int paren_depth = 1;
bool has_comma = false, has_named = false;
while (paren_depth > 0 && p->tokens[probe].kind != TOK_EOF) {
if (p->tokens[probe].kind == TOK_LPAREN) paren_depth++;
else if (p->tokens[probe].kind == TOK_RPAREN) { paren_depth--; if (paren_depth == 0) break; }
else if (paren_depth == 1 && p->tokens[probe].kind == TOK_COMMA) has_comma = true;
else if (paren_depth == 1 && p->tokens[probe].kind == TOK_COLON) has_named = true;
probe++;
}
if (has_comma || has_named) {
// 模块函数调用: Name::fn(a, b) 或 Name::fn(x: 1)
advance(p); // 跳过 '('
AstNode* args[16]; const char* arg_names[16]; int arg_count = 0;
bool seen_named = false;
while (peek(p)->kind != TOK_RPAREN && !error->message) {
if (arg_count >= 16) { error->message = "参数过多"; error->filename = p->filename; error->line = peek(p)->line; error->col = peek(p)->col; return NULL; }
if (peek(p)->kind == TOK_IDENT && (p->tokens[p->pos + 1].kind == TOK_COLON)) {
const Token* aname = advance(p); advance(p);
arg_names[arg_count] = arena_strdup_impl(p->arena, aname->start, aname->length);
seen_named = true;
} else {
if (seen_named) { error->message = "命名参数必须放在位置参数之后"; error->filename = p->filename; error->line = peek(p)->line; error->col = peek(p)->col; return NULL; }
arg_names[arg_count] = NULL;
}
args[arg_count] = parse_expr(p, error);
if (!args[arg_count]) return NULL;
arg_count++;
if (peek(p)->kind == TOK_COMMA) advance(p); else break;
}
if (!expect(p, TOK_RPAREN, error, "缺少 ')'")) return NULL;
AstNode** arg_arr = arena_alloc_impl(p->arena, arg_count * sizeof(AstNode*));
memcpy(arg_arr, args, arg_count * sizeof(AstNode*));
const char** name_arr = seen_named
? memcpy(arena_alloc_impl(p->arena, arg_count * sizeof(const char*)), arg_names, arg_count * sizeof(const char*))
: NULL;
char* full_name = arena_alloc_impl(p->arena, name->length + variant->length + 4);
sprintf(full_name, "%.*s::%.*s", name->length, name->start, variant->length, variant->start);
return ast_make_call(p->arena, full_name, arg_arr, name_arr, arg_count, tok_loc(name));
}
}
// 枚举 payload: Name::Variant 或 Name::Variant(expr)
AstNode* payload = NULL;
if (match(p, TOK_LPAREN)) {
payload = parse_expr(p, error);
@@ -857,7 +903,7 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) {
}
// === 函数解析 ===
static AstNode* parse_function(Parser* p, ErrorInfo* error) {
static AstNode* parse_function(Parser* p, bool is_pub, ErrorInfo* error) {
const Token* fn_tok = advance(p); // fn
const Token* name = expect(p, TOK_IDENT, error, "fn 后应为函数名");
if (!name) return NULL;
@@ -897,7 +943,51 @@ static AstNode* parse_function(Parser* p, ErrorInfo* error) {
memcpy(parr, params, pcount * sizeof(AstNode*));
return ast_make_function(p->arena,
arena_strdup_impl(p->arena, name->start, name->length),
parr, pcount, ret, ret_struct_name, body, tok_loc(fn_tok));
parr, pcount, ret, ret_struct_name, body, is_pub, tok_loc(fn_tok));
}
// === 模块文件加载辅助 ===
// parse 前向声明(定义在后面)
AstNode* parse(Arena* a, const Token* tokens, size_t count,
const char* filename, ErrorInfo* error);
static AstNode* load_module(Arena* a, const char* parent_file,
const char* mod_name, ErrorInfo* error) {
// 构造模块文件路径: 同目录下 mod_name.l
char mod_path[512];
const char* last_slash = strrchr(parent_file, '/');
const char* last_bs = strrchr(parent_file, '\\');
const char* dir_end = parent_file;
if (last_slash && last_slash > dir_end) dir_end = last_slash;
if (last_bs && last_bs > dir_end) dir_end = last_bs;
if (dir_end != parent_file) {
size_t dir_len = dir_end - parent_file + 1;
memcpy(mod_path, parent_file, dir_len);
snprintf(mod_path + dir_len, sizeof(mod_path) - dir_len, "%s.l", mod_name);
} else {
snprintf(mod_path, sizeof(mod_path), "%s.l", mod_name);
}
// 读取文件
FILE* f = fopen(mod_path, "rb");
if (!f) {
error->message = "无法打开模块文件"; error->filename = mod_path;
error->line = 0; error->col = 0;
return NULL;
}
fseek(f, 0, SEEK_END);
size_t sz = ftell(f); fseek(f, 0, SEEK_SET);
char* src = malloc(sz + 1);
if (!src) { fclose(f); return NULL; }
fread(src, 1, sz, f); src[sz] = '\0'; fclose(f);
size_t tc;
ErrorInfo lex_err = {0};
Token* toks = lex(a, src, mod_path, &tc, &lex_err);
free(src);
if (!toks) { *error = lex_err; return NULL; }
AstNode* ast = parse(a, toks, tc, mod_path, error);
return ast;
}
// === 程序入口 ===
@@ -910,7 +1000,14 @@ AstNode* parse(Arena* a, const Token* tokens, size_t count,
AstNode* aliases[64]; int alias_count = 0;
AstNode* enums[64]; int enum_count = 0;
AstNode* impls[64]; int impl_count = 0;
AstNode* mods[64]; int mod_count = 0;
AstNode* uses[64]; int use_count = 0;
while (peek(&p)->kind != TOK_EOF && !error->message) {
// pub 前缀
bool is_pub = false;
if (peek(&p)->kind == TOK_PUB) {
is_pub = true; advance(&p);
}
if (peek(&p)->kind == TOK_STRUCT) {
if (struct_count >= 64) { error->message = "结构体过多 (最多64)"; error->filename = p.filename; error->line = peek(&p)->line; error->col = peek(&p)->col; return NULL; }
structs[struct_count++] = parse_struct_decl(&p, error);
@@ -975,7 +1072,7 @@ AstNode* parse(Arena* a, const Token* tokens, size_t count,
while (peek(&p)->kind != TOK_RBRACE && !error->message) {
if (mcount >= 64) { error->message = "方法过多 (最多64)"; error->filename = p.filename; error->line = peek(&p)->line; error->col = peek(&p)->col; return NULL; }
if (peek(&p)->kind != TOK_FN) { error->message = "extend 块内只允许 fn"; error->filename = p.filename; error->line = peek(&p)->line; error->col = peek(&p)->col; return NULL; }
methods[mcount++] = parse_function(&p, error);
methods[mcount++] = parse_function(&p, false, error);
}
if (!expect(&p, TOK_RBRACE, error, "缺少 '}'")) return NULL;
AstNode** m_arr = arena_alloc_impl(p.arena, mcount * sizeof(AstNode*));
@@ -984,11 +1081,52 @@ AstNode* parse(Arena* a, const Token* tokens, size_t count,
impls[impl_count++] = ast_make_impl_block(p.arena,
arena_strdup_impl(p.arena, st_name->start, st_name->length),
m_arr, mcount, tok_loc(i_tok));
} else if (peek(&p)->kind == TOK_MOD) {
advance(&p);
const Token* mn = expect(&p, TOK_IDENT, error, "mod 后应为模块名");
if (!mn) return NULL;
if (!expect(&p, TOK_SEMICOLON, error, "缺少 ';'")) return NULL;
const char* mod_name = arena_strdup_impl(p.arena, mn->start, mn->length);
AstNode* sub = load_module(a, filename, mod_name, error);
if (!sub) return NULL;
// 合并子模块项到当前文件(以 mod_name:: 为前缀)
for (size_t i = 0; i < sub->as.program.fn_count; i++) {
AstNode* fn = sub->as.program.functions[i];
if (fn->as.function.is_pub) {
char* mangled = arena_alloc_impl(p.arena, strlen(mod_name) + strlen(fn->as.function.name) + 4);
sprintf(mangled, "%s::%s", mod_name, fn->as.function.name);
fn->as.function.name = mangled;
if (fn_count >= 256) { error->message = "函数过多"; error->filename = p.filename; return NULL; }
functions[fn_count++] = fn;
}
}
for (size_t i = 0; i < sub->as.program.struct_count; i++) {
if (struct_count >= 64) break;
structs[struct_count++] = sub->as.program.structs[i];
}
for (size_t i = 0; i < sub->as.program.enum_count; i++) {
if (enum_count >= 64) break;
enums[enum_count++] = sub->as.program.enums[i];
}
if (mod_count < 64) mods[mod_count++] = ast_make_mod_decl(a, mod_name, sub, tok_loc(mn));
} else if (peek(&p)->kind == TOK_USE) {
advance(&p);
const Token* path_tok = expect(&p, TOK_IDENT, error, "use 后应为模块名");
if (!path_tok) return NULL;
if (!expect(&p, TOK_COLON_COLON, error, "缺少 '::'")) return NULL;
const Token* item_tok = expect(&p, TOK_IDENT, error, "use 后应为项目名");
if (!item_tok) return NULL;
if (!expect(&p, TOK_SEMICOLON, error, "缺少 ';'")) return NULL;
if (use_count >= 64) { error->message = "use 过多 (最多64)"; error->filename = p.filename; error->line = peek(&p)->line; error->col = peek(&p)->col; return NULL; }
uses[use_count++] = ast_make_use_decl(a,
arena_strdup_impl(p.arena, path_tok->start, path_tok->length),
arena_strdup_impl(p.arena, item_tok->start, item_tok->length),
tok_loc(path_tok));
} else if (peek(&p)->kind == TOK_FN) {
if (fn_count >= 256) { error->message = "函数过多 (最多256)"; error->filename = p.filename; error->line = peek(&p)->line; error->col = peek(&p)->col; return NULL; }
functions[fn_count++] = parse_function(&p, error);
functions[fn_count++] = parse_function(&p, is_pub, error);
} else {
error->message = "顶层只允许 fn、struct、type、enumextend";
error->message = "顶层只允许 fn、struct、type、enumextend、mod 或 use";
error->filename = p.filename;
error->line = peek(&p)->line;
error->col = peek(&p)->col;