// Expect retcode = 0 // splc1 (stage2 自举编译器入口)。 // 当前实现 `--dump [out]`:读取源文件,按与 stage1 // splc0 完全一致的格式打印各阶段产物,供 diff 测试逐字节比对。 type .{ .ByteView } = @import("./utils/byte_view.spl"); type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl"); type .{ .Vec } = @import("./utils/vec.spl"); type .{ .Token, .kind_name } = @import("./token.spl"); type .{ .Lexer } = @import("./lexer.spl"); type Ast = @import("./ast.spl"); type Parser = @import("./parser.spl"); type Sema = @import("./sema.spl"); type .{ .append_usize } = @import("./utils/fmt.spl"); type .{ .read_all, .print, .eprint } = @import("./utils/io.spl"); type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl"); type .{ .Diag, .Code, .span_none } = @import("./diag.spl"); type Logger = @import("./utils/log.spl"); type .{ .Loader, .LoadResult } = @import("./sema.spl"); type IrBuilder = @import("./ir_builder.spl"); type Ir = @import("./ir.spl"); type Ast2Ir = @import("./ast2ir.spl"); type LayoutM = @import("./layout.spl"); type .{ .Layout } = @import("./layout.spl"); type Ir2VmM = @import("./ir2vm.spl"); type .{ .Ir2Vm } = @import("./ir2vm.spl"); type .{ .alloc, .free } = @import("./sys/mem.spl"); fn token_at(tokens: *Vec, index: usize): Token { match Vec.get(tokens, index) { .Ok(ptr) => { ret (ptr as *Token).*; }, .Err(error) => { ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0); }, } ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0); } // 追加成功返回 true;失败(仅 OOM)返回 false。 fn push(out: *ByteBuffer, bytes: []u8): bool { match ByteBuffer.append(out, bytes) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn push_byte(out: *ByteBuffer, byte: u8): bool { match ByteBuffer.append_byte(out, byte) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn push_usize(out: *ByteBuffer, value: usize): bool { match append_usize(out, value, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } // 输出格式与 splc0.c 的 dump_tokens 一致: // tokens got (N)\n // [TOK_TYPE] (line:col)\n fn dump_tokens(tokens: *Vec): bool { var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !push(&out, "tokens got (") { ret false; } if !push_usize(&out, tokens.len) { ret false; } if !push(&out, ")\n") { ret false; } var i: usize = 0; while i < tokens.len { var tok = token_at(tokens, i); if !push(&out, "[") { ret false; } if !push(&out, kind_name(tok.kind)) { ret false; } if !push(&out, "] ") { ret false; } if !push(&out, ByteView.as_slice(tok.text)) { ret false; } if !push(&out, " (") { ret false; } if !push_usize(&out, tok.line) { ret false; } if !push_byte(&out, ':') { ret false; } if !push_usize(&out, tok.col) { ret false; } if !push(&out, ")\n") { ret false; } i = i + 1; } match print(ByteBuffer.as_slice(out)) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn dump_lex(log: *Logger, path: ByteView): i32 { var content = ByteBuffer.new(); defer { ByteBuffer.drop(&content); } match read_all(path, &content) { .Ok => {}, .Err(error) => { Logger.error(log, "cannot read input file"); ret 1; }, } var text = ByteView.from_slice(ByteBuffer.as_slice(content)); var sources = set_new(); defer { set_drop(&sources); } match set_add(&sources, path, text) { .Ok(index) => {}, .Err(error) => { Logger.error(log, "cannot register source"); ret 1; }, } var diag = Diag.new(&sources); defer { Diag.drop(&diag); } var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); match Lexer.lex(text, &diag, 0) { .Ok(v) => { tokens = v; }, .Err(error) => { Logger.error(log, "lex failed"); ret 1; }, } defer { Vec.drop(&tokens); } // 非法输入:走诊断(stage1 在此处是宽容无视,stage2 显式报错)。 if Diag.has_error(diag) { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {}, } eprint(ByteBuffer.as_slice(rendered)); ret 1; } if !dump_tokens(&tokens) { Logger.fatal(log, "out of memory while dumping tokens"); ret 1; } ret 0; } fn dump_ast(log: *Logger, path: ByteView): i32 { var content = ByteBuffer.new(); defer { ByteBuffer.drop(&content); } match read_all(path, &content) { .Ok => {}, .Err(error) => { Logger.error(log, "cannot read input file"); ret 1; }, } var text = ByteView.from_slice(ByteBuffer.as_slice(content)); var sources = set_new(); defer { set_drop(&sources); } match set_add(&sources, path, text) { .Ok(index) => {}, .Err(error) => { Logger.error(log, "cannot register source"); ret 1; }, } var diag = Diag.new(&sources); defer { Diag.drop(&diag); } var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); match Lexer.lex(text, &diag, 0) { .Ok(v) => { tokens = v; }, .Err(error) => { Logger.error(log, "lex failed"); ret 1; }, } defer { Vec.drop(&tokens); } var ast: Ast = undefined; match Ast.new(&sources) { .Ok(v) => { ast = v; }, .Err(error) => { Logger.error(log, "ast init failed"); ret 1; }, } defer { Ast.drop(&ast); } var p = Parser.new(&ast, tokens, &diag); Parser.parse(&p); if Diag.has_error(diag) || p.failed { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {}, } eprint(ByteBuffer.as_slice(rendered)); ret 1; } var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !Ast.dump(&ast, &out) { Logger.fatal(log, "out of memory while dumping ast"); ret 1; } match print(ByteBuffer.as_slice(out)) { .Ok => {}, .Err(error) => { ret 1; }, } ret 0; } // ---- @import 模块加载器(driver 侧)---- type ModEntry = struct { path: ByteView, ast: *Ast, ok: bool, } type LoaderCtx = struct { sources: *Sources, mods: Vec, // ModEntry buffers: Vec, // ByteBuffer(路径/文本稳定存储) diag: *Diag, } fn ignore_bool(b: bool) {} fn append_bytes(buf: *ByteBuffer, bytes: []u8): bool { match ByteBuffer.append(buf, bytes) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn append_byte(buf: *ByteBuffer, b: u8): bool { match ByteBuffer.append_byte(buf, b) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn sub(p: ByteView, a: usize, b: usize): []u8 { ret ByteView.as_slice(p)[a..b]; } // 处理 "..":弹出 out 的最后一个路径分量;无分量可弹(空 / 根 / 末段已是 "..")时, // 退化为追加 ".."(相对路径的语义),绝对根则忽略。这样 "../x" 与规范路径能归一到同一模块。 fn normalize_pop(out: *ByteBuffer) { var cur = ByteBuffer.as_slice(out.*); if cur.len == 0 { ignore_bool(append_bytes(out, "..")); ret; } var end = cur.len; if cur.ptr[end - 1] == '/' { if end == 1 { ret; } // 绝对根 "/":忽略 end = end - 1; } var start = end; while start > 0 && cur.ptr[start - 1] != '/' { start = start - 1; } if end - start == 2 && cur.ptr[start] == '.' && cur.ptr[start + 1] == '.' { ignore_bool(append_bytes(out, "..")); ret; } var new_len: usize = 0; if start > 0 { new_len = start - 1; } if new_len == 0 && cur.ptr[0] == '/' { new_len = 1; } out.*.storage.len = new_len; } // 折叠重复分隔符、解析 "." 与 ".."、'\' 统一成 '/'。 fn normalize_into(out: *ByteBuffer, p: ByteView) { if p.len > 0 && (p.ptr[0] == '/' || p.ptr[0] == '\\') { ignore_bool(append_byte(out, '/')); } var i: usize = 0; while i < p.len { while i < p.len && (p.ptr[i] == '/' || p.ptr[i] == '\\') { i = i + 1; } if i >= p.len { break; } var start = i; while i < p.len && p.ptr[i] != '/' && p.ptr[i] != '\\' { i = i + 1; } var seg = sub(p, start, i); if seg.len == 1 && seg.ptr[0] == '.' { continue; } if seg.len == 2 && seg.ptr[0] == '.' && seg.ptr[1] == '.' { normalize_pop(out); continue; } var cur = ByteBuffer.as_slice(out.*); if cur.len > 0 && cur.ptr[cur.len - 1] != '/' { ignore_bool(append_byte(out, '/')); } ignore_bool(append_bytes(out, seg)); } } // 把 path 拼到 from 所在目录,并规范化;绝对路径直通。 fn resolve_path(from: ByteView, path: ByteView): ByteBuffer { var out = ByteBuffer.new(); var absolute = false; if path.len > 0 && (path.ptr[0] == '/' || path.ptr[0] == '\\') { absolute = true; } if path.len >= 2 && path.ptr[1] == ':' { absolute = true; } if absolute { normalize_into(&out, path); ret out; } var last: isize = -1; var i: usize = 0; while i < from.len { if from.ptr[i] == '/' || from.ptr[i] == '\\' { last = i as isize; } i = i + 1; } if last >= 0 { ignore_bool(append_bytes(&out, sub(from, 0, (last as usize) + 1))); } ignore_bool(append_bytes(&out, ByteView.as_slice(path))); var norm = ByteBuffer.new(); normalize_into(&norm, ByteView.from_slice(ByteBuffer.as_slice(out))); ByteBuffer.drop(&out); ret norm; } fn mod_at(ctx: *LoaderCtx, i: usize): *ModEntry { match Vec.get(&ctx.mods, i) { .Ok(ptr) => { ret ptr as *ModEntry; }, .Err(error) => { ret null; }, } ret null; } fn driver_load(user: *any, from: ByteView, path: ByteView, diag: *Diag): LoadResult { var ctx = user as *LoaderCtx; var absbuf = resolve_path(from, path); var abs = ByteView.from_slice(ByteBuffer.as_slice(absbuf)); var i: usize = 0; while i < ctx.mods.len { var m = mod_at(ctx, i); if m != null && ByteView.eq(m.path, abs) { ByteBuffer.drop(&absbuf); if !m.ok { Diag.err(diag, span_none(), .CircularDep, "@import: circular import"); ret .Missing; } ret .Ok(m.ast); } i = i + 1; } var midx = ctx.mods.len; var placeholder = ModEntry { .path = abs, .ast = null, .ok = false }; match Vec.push(&ctx.mods, &placeholder as *u8) { .Ok => {}, .Err(error) => { ByteBuffer.drop(&absbuf); ret .Missing; }, } match Vec.push(&ctx.buffers, &absbuf as *u8) { .Ok => {}, .Err(error) => { ByteBuffer.drop(&absbuf); ret .Missing; }, } var content = ByteBuffer.new(); match read_all(abs, &content) { .Ok => {}, .Err(error) => { Diag.err(diag, span_none(), .LexUnexpected, "@import: cannot open module"); ByteBuffer.drop(&content); ret .Missing; }, } var text = ByteView.from_slice(ByteBuffer.as_slice(content)); var sidx: usize = 0; match set_add(ctx.sources, abs, text) { .Ok(index) => { sidx = index; }, .Err(error) => { ByteBuffer.drop(&content); ret .Missing; }, } match Vec.push(&ctx.buffers, &content as *u8) { .Ok => {}, .Err(error) => { ByteBuffer.drop(&content); ret .Missing; }, } var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); match Lexer.lex(text, diag, 0) { .Ok(v) => { tokens = v; }, .Err(error) => { Vec.drop(&tokens); ret .Missing; }, } var ast_val: Ast = undefined; match Ast.new(ctx.sources) { .Ok(v) => { ast_val = v; }, .Err(error) => { Vec.drop(&tokens); ret .Missing; }, } var p = Parser.new(&ast_val, tokens, diag); p.file = sidx; Parser.parse(&p); Vec.drop(&tokens); if Diag.has_error(diag.*) || p.failed { Ast.drop(&ast_val); ret .Missing; } var raw = alloc(@sizeof(Ast)); if raw == null { Ast.drop(&ast_val); ret .Missing; } var ap = raw as *Ast; ap.* = ast_val; var slot = mod_at(ctx, midx); if slot != null { slot.ast = ap; slot.ok = true; } ret .Ok(ap); } fn loader_ctx_drop(ctx: *LoaderCtx) { var i: usize = 0; while i < ctx.mods.len { var m = mod_at(ctx, i); if m != null && m.ok && m.ast != null { Ast.drop(m.ast); free(m.ast as *any); m.ast = null; m.ok = false; } i = i + 1; } Vec.drop(&ctx.mods); var j: usize = 0; while j < ctx.buffers.len { match Vec.get(&ctx.buffers, j) { .Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); }, .Err(error) => {}, } j = j + 1; } Vec.drop(&ctx.buffers); } fn dump_sema(log: *Logger, path: ByteView): i32 { var content = ByteBuffer.new(); defer { ByteBuffer.drop(&content); } match read_all(path, &content) { .Ok => {}, .Err(error) => { Logger.error(log, "cannot read input file"); ret 1; }, } var text = ByteView.from_slice(ByteBuffer.as_slice(content)); var sources = set_new(); defer { set_drop(&sources); } match set_add(&sources, path, text) { .Ok(index) => {}, .Err(error) => { Logger.error(log, "cannot register source"); ret 1; }, } var diag = Diag.new(&sources); defer { Diag.drop(&diag); } var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); match Lexer.lex(text, &diag, 0) { .Ok(v) => { tokens = v; }, .Err(error) => { Logger.error(log, "lex failed"); ret 1; }, } defer { Vec.drop(&tokens); } var ast: Ast = undefined; match Ast.new(&sources) { .Ok(v) => { ast = v; }, .Err(error) => { Logger.error(log, "ast init failed"); ret 1; }, } defer { Ast.drop(&ast); } var p = Parser.new(&ast, tokens, &diag); Parser.parse(&p); if Diag.has_error(diag) || p.failed { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {}, } eprint(ByteBuffer.as_slice(rendered)); ret 1; } var ctx = LoaderCtx { .sources = &sources, .mods = Vec.blank(@sizeof(ModEntry), @alignof(ModEntry)), .buffers = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)), .diag = &diag, }; defer { loader_ctx_drop(&ctx); } var loader = Loader { .load = driver_load, .user = &ctx as *any }; var sema: Sema = undefined; match Sema.new(&ast, &diag) { .Ok(v) => { sema = v; }, .Err(error) => { Logger.error(log, "sema init failed"); ret 1; }, } defer { Sema.drop(&sema); } sema.sources = &sources; sema.loader = &loader; Sema.run(&sema); if Diag.has_error(diag) { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {}, } eprint(ByteBuffer.as_slice(rendered)); ret 1; } var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !Sema.dump(&sema, &out) { Logger.fatal(log, "out of memory while dumping sema"); ret 1; } if !Ast.dump(&ast, &out) { Logger.fatal(log, "out of memory while dumping ast"); ret 1; } match print(ByteBuffer.as_slice(out)) { .Ok => {}, .Err(error) => { ret 1; }, } ret 0; } type PipeMode = enum { Ir, // 只 dump IR Vm, // 继续 layout + ir2vm,dump VM Compile, // 继续 layout + ir2vm,写 outpath State, // 只逐步打日志(诊断“卡在哪一步”) } fn step(mode: PipeMode, s: []u8) { if @enum2int(mode) == @enum2int(PipeMode.State) { match eprint(s) { .Ok => {}, .Err(error) => {} } } } // 完整前端流水线:lex -> parse -> sema -> ast2ir ->(可选)layout + ir2vm。 fn run_pipeline(log: *Logger, path: ByteView, mode: PipeMode, outpath: ByteView): i32 { var content = ByteBuffer.new(); defer { ByteBuffer.drop(&content); } match read_all(path, &content) { .Ok => {}, .Err(error) => { Logger.error(log, "cannot read input file"); ret 1; }, } var text = ByteView.from_slice(ByteBuffer.as_slice(content)); step(mode, "state: read ok\n"); var sources = set_new(); defer { set_drop(&sources); } var main_idx: usize = 0; match set_add(&sources, path, text) { .Ok(index) => { main_idx = index; }, .Err(error) => { Logger.error(log, "cannot register source"); ret 1; }, } var diag = Diag.new(&sources); defer { Diag.drop(&diag); } var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); match Lexer.lex(text, &diag, 0) { .Ok(v) => { tokens = v; }, .Err(error) => { Logger.error(log, "lex failed"); ret 1; }, } defer { Vec.drop(&tokens); } var ast: Ast = undefined; match Ast.new(&sources) { .Ok(v) => { ast = v; }, .Err(error) => { Logger.error(log, "ast init failed"); ret 1; }, } defer { Ast.drop(&ast); } var p = Parser.new(&ast, tokens, &diag); p.file = main_idx; Parser.parse(&p); if Diag.has_error(diag) || p.failed { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} } eprint(ByteBuffer.as_slice(rendered)); ret 1; } step(mode, "state: parse ok\n"); var ctx = LoaderCtx { .sources = &sources, .mods = Vec.blank(@sizeof(ModEntry), @alignof(ModEntry)), .buffers = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)), .diag = &diag, }; defer { loader_ctx_drop(&ctx); } var loader = Loader { .load = driver_load, .user = &ctx as *any }; var sema: Sema = undefined; match Sema.new(&ast, &diag) { .Ok(v) => { sema = v; }, .Err(error) => { Logger.error(log, "sema init failed"); ret 1; }, } defer { Sema.drop(&sema); } sema.sources = &sources; sema.loader = &loader; Sema.run(&sema); if Diag.has_error(diag) { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} } eprint(ByteBuffer.as_slice(rendered)); ret 1; } step(mode, "state: sema ok\n"); var builder = IrBuilder.builder_new(); defer { IrBuilder.builder_drop(&builder); } var a2ir = Ast2Ir.new(&builder, &sema, &diag); defer { Ast2Ir.drop(&a2ir); } var asts = Vec.blank(@sizeof(usize), @alignof(usize)); defer { Vec.drop(&asts); } var ap: *Ast = * match Vec.push(&asts, &ap as *u8) { .Ok => {}, .Err(error) => {} } var mi: usize = 0; while mi < ctx.mods.len { var m = mod_at(&ctx, mi); if m != null && m.ok && m.ast != null { var mp: *Ast = m.ast; match Vec.push(&asts, &mp as *u8) { .Ok => {}, .Err(error) => {} } } mi = mi + 1; } Ast2Ir.run(&a2ir, asts); if Diag.has_error(diag) { var rendered = ByteBuffer.new(); defer { ByteBuffer.drop(&rendered); } match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} } eprint(ByteBuffer.as_slice(rendered)); ret 1; } step(mode, "state: ast2ir ok\n"); if @enum2int(mode) == @enum2int(PipeMode.Ir) { var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !Ir.dump(&builder.ir, &sema.types.ir, &sema.attr, &out) { Logger.fatal(log, "out of memory while dumping ir"); ret 1; } match print(ByteBuffer.as_slice(out)) { .Ok => {}, .Err(error) => { ret 1; }, } ret 0; } // 类型就绪后解析布局,再做 IR -> VM。 var layout: Layout = undefined; match LayoutM.new() { .Ok(v) => { layout = v; }, .Err(error) => { Logger.error(log, "layout init failed"); ret 1; }, } defer { LayoutM.drop(&layout); } LayoutM.resolve(&layout, &sema.types.ir); step(mode, "state: layout ok\n"); var ir2vm: Ir2Vm = undefined; Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr); defer { Ir2VmM.drop(&ir2vm); } ignore_i32(Ir2VmM.run(&ir2vm)); step(mode, "state: ir2vm ok\n"); if @enum2int(mode) == @enum2int(PipeMode.Vm) { var vout = ByteBuffer.new(); defer { ByteBuffer.drop(&vout); } if !Ir2VmM.dump(&ir2vm, &vout) { Logger.fatal(log, "out of memory while dumping vm"); ret 1; } match print(ByteBuffer.as_slice(vout)) { .Ok => {}, .Err(error) => { ret 1; }, } ret 0; } if @enum2int(mode) == @enum2int(PipeMode.State) { ret 0; } if !Ir2VmM.store(&ir2vm, outpath) { Logger.error(log, "cannot write output file"); ret 1; } ret 0; } fn ignore_i32(v: i32) {} fn main(argc: i32, argv: **u8): i32 { // 无参数:自测运行(`nob test stage2`)视为成功。 if argc <= 1 { ret 0; } var log = Logger.new(ByteView.from_slice("splc1")); var arg1 = ByteView.from_cstr(argv[1]); if !ByteView.eq(arg1, ByteView.from_slice("--dump")) { // 编译模式:splc1 if argc < 3 { Logger.fatal(&log, "usage: splc1 | splc1 --dump "); ret 1; } var in = ByteView.from_cstr(argv[1]); var out = ByteView.from_cstr(argv[2]); ret run_pipeline(&log, in, .Compile, out); } if argc < 4 { Logger.fatal(&log, "usage: splc1 --dump [out]"); ret 1; } var mode = ByteView.from_cstr(argv[2]); var path = ByteView.from_cstr(argv[3]); if ByteView.eq(mode, ByteView.from_slice("lex")) { ret dump_lex(&log, path); } if ByteView.eq(mode, ByteView.from_slice("ast")) { ret dump_ast(&log, path); } if ByteView.eq(mode, ByteView.from_slice("sema")) { ret dump_sema(&log, path); } if ByteView.eq(mode, ByteView.from_slice("ir")) { ret run_pipeline(&log, path, .Ir, ByteView.empty()); } if ByteView.eq(mode, ByteView.from_slice("vm")) { ret run_pipeline(&log, path, .Vm, ByteView.empty()); } if ByteView.eq(mode, ByteView.from_slice("state")) { ret run_pipeline(&log, path, .State, ByteView.empty()); } Logger.fatal(&log, "dump mode not implemented"); ret 1; }