diff --git a/gramma.pest b/gramma.pest index fc0fe8c..85c3888 100644 --- a/gramma.pest +++ b/gramma.pest @@ -109,6 +109,10 @@ PostfixExpr = { ExprList = { (Expr ~ COMMA)* ~ Expr? } +// 注:类型即表达式**仅限 comptime 上下文**(`comptime { ... }`、`comptime expr`、 +// `comptime fn` 的函数体)内。在这些上下文中,PrimaryExpr 额外接受 +// `AggregateTypeExpr`(如 `struct { ... }`)以及 `type` 元类型;其它上下文 +// 中类型只允许出现在 TypeExpr 位置。 PrimaryExpr = { FLOAT_LITERAL | INTEGER_LITERAL @@ -232,6 +236,7 @@ TypePrimary = { | KEYWORD_bool | KEYWORD_void | KEYWORD_ptr + | KEYWORD_type // `type` 元类型(编译期类型的值类型) | FnTypeExpr | AggregateTypeExpr | TypeParenExpr diff --git a/nob.c b/nob.c index 7cd1c9a..f267fb1 100644 --- a/nob.c +++ b/nob.c @@ -46,6 +46,65 @@ static const char *splc0_srcs[] = { "stage1/vm_opt.c", "stage0/spl_mcode.c", }; + +static const char* splc1_deps[] = { + "stage2/sys/exit.spl", + "stage2/sys/file.spl", + "stage2/sys/mem.spl", + "stage2/sys/spl_rt.h", + "stage2/sys/spl_vm_embed.h", + "stage2/sys/time.spl", + "stage2/sys/vm.spl", + "stage2/utils/algorithm.spl", + "stage2/utils/arena.spl", + "stage2/utils/ascii.spl", + "stage2/utils/byte_buffer.spl", + "stage2/utils/byte_view.spl", + "stage2/utils/error.spl", + "stage2/utils/fmt.spl", + "stage2/utils/hash.spl", + "stage2/utils/io.spl", + "stage2/utils/json.spl", + "stage2/utils/log.spl", + "stage2/utils/lsp_frame.spl", + "stage2/utils/map.spl", + "stage2/utils/memory.spl", + "stage2/utils/mp.spl", + "stage2/utils/path.spl", + "stage2/utils/profile.spl", + "stage2/utils/set.spl", + "stage2/utils/source.spl", + "stage2/utils/str.spl", + "stage2/utils/utf8.spl", + "stage2/utils/value.spl", + "stage2/utils/vec.spl", + "stage2/ast.spl", + "stage2/ast2ir.spl", + "stage2/attr.spl", + "stage2/def.spl", + "stage2/diag.spl", + "stage2/eval.spl", + "stage2/intrin.spl", + "stage2/ir.spl", + "stage2/ir2c.spl", + "stage2/ir2vm.spl", + "stage2/ir_builder.spl", + "stage2/lang_type.spl", + "stage2/layout.spl", + "stage2/lexer.spl", + "stage2/mcode.spl", + "stage2/parser.spl", + "stage2/scope.spl", + "stage2/sema.spl", + "stage2/splc1.spl", + "stage2/token.spl", + "stage2/type_table.spl", +}; + +static const char* splc2_deps[] = { + +}; + /* clang-format on */ static const char *obj_path(const char *src) { @@ -76,6 +135,8 @@ static const char *sir_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.sir", name); } +static const char *c_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.c", name); } + static bool sync_cflags_stamp(void) { Nob_String_View cflags = nob_sv_from_cstr(CFLAGS_STR); Nob_String_Builder old = {0}; @@ -107,6 +168,7 @@ typedef struct build { BUILD_EXE, BUILD_EXE_WITH_OPTIMIZATION, BUILD_SIR, + BUILD_NATIVE, } kind; union { struct { @@ -118,7 +180,15 @@ typedef struct build { struct { const char *src; const char *compiler; + const char **deps; + size_t deps_count; } sir; + struct { + const char *src; + const char *compiler; + const char **deps; + size_t deps_count; + } native; }; } build_t; @@ -201,6 +271,60 @@ defer: return NULL; } +static const char *build_native(const char *name) { + const char *exe = exe_path(name); + const char *cli = build_target("spl_cli"); + const char *sir = build_target("splc1"); + if (!cli || !sir) { + nob_log(NOB_ERROR, "failed to build prerequisites for `%s`", name); + return NULL; + } + const char *cpath = BUILD_FOLDER "splc1.c"; + const char *inputs[] = {sir, + "stage2/sys/spl_rt.h", + "stage2/sys/spl_vm_embed.h", + "stage2/sys/spl_vm_embed.c", + "stage0/spl_vm.c", + "stage0/spl_mcode.c", + "stage0/spl_syscall.c", + "stage0/spl_vm.h", + "stage0/spl_mcode.h"}; + int needs = nob_needs_rebuild(exe, inputs, NOB_ARRAY_LEN(inputs)); + if (needs < 0) + return NULL; + if (needs == 0) + return exe; + + Nob_Cmd cmd = {0}; + if (nob_file_exists(exe)) { + // 已有原生编译器:用原生自举重新生成 C(0.1s 级) + nob_cmd_append(&cmd, exe, "--dump", "c", "stage2/splc1.spl", cpath); + } else { + // 冷启动:用 VM 版 splc1 生成 C + nob_cmd_append(&cmd, cli, sir, "--dump", "c", "stage2/splc1.spl", cpath); + } + if (!nob_cmd_run(&cmd)) { + nob_log(NOB_ERROR, "ir2c C generation failed"); + nob_cmd_free(cmd); + return NULL; + } + cmd.count = 0; + nob_cc(&cmd); + nob_cc_inputs(&cmd, cpath); + nob_cc_inputs(&cmd, "stage2/sys/spl_vm_embed.c"); + nob_cc_inputs(&cmd, "stage0/spl_vm.c"); + nob_cc_inputs(&cmd, "stage0/spl_mcode.c"); + nob_cc_inputs(&cmd, "stage0/spl_syscall.c"); + nob_cc_output(&cmd, exe); + if (!nob_cmd_run(&cmd)) { + nob_log(NOB_ERROR, "native link failed"); + nob_cmd_free(cmd); + return NULL; + } + nob_cmd_free(cmd); + return exe; +} + static build_t targets[] = { { "spl_cli", @@ -237,17 +361,50 @@ static build_t targets[] = { { "splc1", BUILD_SIR, - .sir = {.src = "stage2/splc1.spl", .compiler = "splc0"}, + .sir = {.src = "stage2/splc1.spl", + .compiler = "splc0", + .deps = splc1_deps, + .deps_count = NOB_ARRAY_LEN(splc1_deps)}, }, { "splc1s", BUILD_SIR, - .sir = {.src = "stage2/splc1.spl", .compiler = "splc1"}, + .sir = {.src = "stage2/splc1.spl", + .compiler = "splc1", + .deps = splc1_deps, + .deps_count = NOB_ARRAY_LEN(splc1_deps)}, }, { "splc1r", BUILD_SIR, - .sir = {.src = "stage2/splc1.spl", .compiler = "splc1s"}, + .sir = {.src = "stage2/splc1.spl", + .compiler = "splc1s", + .deps = splc1_deps, + .deps_count = NOB_ARRAY_LEN(splc1_deps)}, + }, + { + "splc1n", + BUILD_NATIVE, + .native = {.src = "stage2/splc1.spl", + .compiler = "splc1", + .deps = splc1_deps, + .deps_count = NOB_ARRAY_LEN(splc1_deps)}, + }, + { + "splc2", + BUILD_SIR, + .sir = {.src = "stage3/splc2.spl", + .compiler = "splc1n", + .deps = splc2_deps, + .deps_count = NOB_ARRAY_LEN(splc2_deps)}, + }, + { + "splc2n", + BUILD_NATIVE, + .native = {.src = "stage3/splc2.spl", + .compiler = "splc1n", + .deps = splc2_deps, + .deps_count = NOB_ARRAY_LEN(splc2_deps)}, }, }; @@ -276,6 +433,38 @@ const char *build_target(const char *name) { nob_cmd_free(cmd); return target_sir; } + case BUILD_NATIVE: { + Nob_Cmd cmd = {0}; + const char *target_exe = exe_path(name); + const char *target_c = c_path(name); + + // TODO need_rebuild + int needs = + nob_needs_rebuild(target_exe, target->native.deps, target->native.deps_count); + if (needs < 0) + return NULL; + if (needs == 0) + return target_exe; + + nob_cmd_append(&cmd, "--dump", "c", target->native.src, target_c); + if (!run_target(target->native.compiler, &cmd)) { + nob_log(NOB_ERROR, "failed to build `%s`", name); + nob_cmd_free(cmd); + return NULL; + } + cmd.count = 0; + nob_cc(&cmd); + nob_cmd_append(&cmd, "-I./stage2/sys", "-O2", "-D_CRT_SECURE_NO_WARNINGS"); + nob_cc_inputs(&cmd, target_c); + nob_cc_output(&cmd, target_exe); + if (!nob_cmd_run(&cmd)) { + nob_log(NOB_ERROR, "failed to build `%s`", name); + nob_cmd_free(cmd); + return NULL; + } + nob_cmd_free(cmd); + return target_exe; + } default: NOB_UNREACHABLE("build_t.kind"); } @@ -287,6 +476,7 @@ const char *build_target(const char *name) { static run_t runs[] = { {"spl_cli", NULL}, {"vm_test", NULL}, {"splc0", NULL}, {"splc1", "spl_cli"}, {"splc1r", "spl_cli"}, {"splc1s", "spl_cli"}, + {"splc1n", NULL}, {"splc2", "spl_cli"}, {"splc2n", NULL}, }; bool run_target_dry(const char *name, Nob_Cmd *args, Nob_Cmd *result) { diff --git a/stage0/spl_mcode.c b/stage0/spl_mcode.c index 1b2378a..4dba445 100644 --- a/stage0/spl_mcode.c +++ b/stage0/spl_mcode.c @@ -127,44 +127,20 @@ static inline void wr64(unsigned char **p, spl_vm_val_t v) { /* Round n up to next multiple of 8 */ #define ALIGN8(n) (((n) + 7) & ~7) -int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { - FILE *f; - unsigned char *data; - long len; +static int spl_prog_parse(const unsigned char *data, usize len, spl_prog_t *prog) { const unsigned char *p; spl_vm_val_t nfuncs, ninsns, nnatives, nstrs, ndata; - if (!fname || !prog) + if (!data || !prog) return -1; - - f = fopen(fname, "rb"); - if (!f) + if (len < 48) return -1; - fseek(f, 0, SEEK_END); - len = ftell(f); - fseek(f, 0, SEEK_SET); - if (len < 48) { - fclose(f); - return -1; - } - data = (unsigned char *)malloc((size_t)len); - if (!data) { - fclose(f); - return -1; - } - if (fread(data, 1, (size_t)len, f) != (size_t)len) { - free(data); - fclose(f); - return -1; - } - fclose(f); p = data; /* magic */ if (p[0] != 'S' || p[1] != 'P' || p[2] != 'L' || p[3] != 'B' || p[4] != 'I' || p[5] != 'N' || p[6] != '\0' || p[7] != '\0') { - free(data); return -1; } p += 8; @@ -186,7 +162,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { func.name = (char *)malloc((usize)nlen); if (!func.name) { - free(data); return -1; } memcpy(func.name, p, (usize)nlen); @@ -202,7 +177,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { /* ---- instructions ---- */ for (spl_vm_val_t i = 0; i < ninsns; i++) { if ((size_t)(p - data) + 12 > (size_t)len) { - free(data); return -1; } spl_vm_ins_t ins; @@ -221,7 +195,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { nat.name = (char *)malloc((usize)nlen); if (!nat.name) { - free(data); return -1; } memcpy(nat.name, p, (usize)nlen); @@ -238,7 +211,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { usize pad = ALIGN8((usize)slen) - (usize)slen; char *s = (char *)malloc((usize)slen + 1); if (!s) { - free(data); return -1; } memcpy(s, p, (usize)slen); @@ -254,7 +226,6 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { usize pad = ALIGN8(entry.size) - entry.size; entry.data = (unsigned char *)malloc(entry.size); if (!entry.data) { - free(data); return -1; } memcpy(entry.data, p, entry.size); @@ -279,11 +250,46 @@ int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { } } - free(data); return 0; } -int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog) {} +int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) { + FILE *f; + unsigned char *data; + long len; + int rc; + + if (!fname || !prog) + return -1; + f = fopen(fname, "rb"); + if (!f) + return -1; + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + if (len < 48) { + fclose(f); + return -1; + } + data = (unsigned char *)malloc((size_t)len); + if (!data) { + fclose(f); + return -1; + } + if (fread(data, 1, (size_t)len, f) != (size_t)len) { + free(data); + fclose(f); + return -1; + } + fclose(f); + rc = spl_prog_parse(data, (usize)len, prog); + free(data); + return rc; +} + +int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog) { + return spl_prog_parse((const unsigned char *)buffer, len, prog); +} int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) { unsigned char *buf, *p; diff --git a/stage0/spl_mcode.h b/stage0/spl_mcode.h index d0d0ced..631c904 100644 --- a/stage0/spl_mcode.h +++ b/stage0/spl_mcode.h @@ -195,6 +195,7 @@ void spl_prog_dump(spl_prog_t *prog); int spl_prog_load_from_file(const char *fname, spl_prog_t *prog); +int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog); int spl_prog_store_to_file(const char *fname, spl_prog_t *prog); int spl_prog_add_instr(spl_prog_t *prog, uint8_t opcode, uint8_t type, spl_vm_val_t imm); diff --git a/stage0/spl_syscall.c b/stage0/spl_syscall.c index 33a96fd..a0cf4b2 100644 --- a/stage0/spl_syscall.c +++ b/stage0/spl_syscall.c @@ -423,6 +423,33 @@ static spl_vm_val_t vm_load(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { return (spl_vm_val_t)(uintptr_t)prog; } +/* vm_load_from_buffer - 从内存加载字节码(comptime 内嵌 VM 用)。 + * 参数 1 是 slice 结构 { ptr, len } 的地址(聚合参数按指针传递)。 */ +static spl_vm_val_t vm_load_from_buffer(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { + (void)_vm; + CHECK_NARGS("vm_load_from_buffer", 3); + spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0]; + const char *buf = (const char *)(uintptr_t)args[1]; + usize len = (usize)args[2]; + spl_prog_t *prog; + if (!vm || !buf) + return -1; + prog = (spl_prog_t *)malloc(sizeof(spl_prog_t)); + if (!prog) + return -1; + if (spl_prog_load_from_buffer(buf, len, prog) != 0) { + free(prog); + return -1; + } + spl_syscall_register(prog); + if (spl_vm_load_prog(vm, prog) != 0) { + spl_prog_drop(prog); + free(prog); + return -1; + } + return 0; +} + /* vm_push - push a value onto the sub-VM's stack (for passing arguments) */ static spl_vm_val_t vm_push(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { (void)_vm; @@ -464,31 +491,36 @@ static spl_vm_val_t vm_call(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { fprintf(stderr, "vm_call: function '%s' not found\n", name); return -1; } - if (vm->cp >= vm->config.max_call_depth - 1) { + if (vm->cp >= vm->config.max_call_depth) { fprintf(stderr, "vm_call: call stack overflow\n"); return -1; } - /* Sentinel frame so the entry function's RET halts cleanly */ - if (vm->cp == 0) { - vm->frames.data[vm->cp].saved_fp = 0; - vm->frames.data[vm->cp].saved_ip = (uintptr_t)-1; - vm->frames.data[vm->cp].nargs = 0; - vm->cp++; - } - - /* Actual call frame (same logic as the CALL instruction) */ - vm->frames.data[vm->cp].saved_fp = vm->fp; - vm->frames.data[vm->cp].saved_ip = vm->ip; - vm->frames.data[vm->cp].nargs = call_nargs; - vm->cp++; - vm->fp = vm->sp - call_nargs; + /* 入口调用:ip 置 -1 作为 sentinel,RET 时 halt 并把返回值写入 exit_code。 + * 实参须已由 vm_push 压栈(sp = nargs)。 */ + vm->ip = (uintptr_t)-1; + vm->cp = 0; + vm->fp = 0; + vm->frames.data[0].saved_sp = vm->sp - call_nargs; + vm->frames.data[0].saved_fp = vm->fp; + vm->frames.data[0].saved_ip = vm->ip; + vm->frames.data[0].nargs = call_nargs; + vm->cp = 1; vm->ip = (uintptr_t)func->address; + vm->fp = vm->sp - call_nargs; return 0; } -/* vm_run - run a sub-VM to completion, returns exit_code */ -SYSCALL_1(vm_run, spl_vm_t *, spl_vm_run_until(a1, 0)) +/* vm_run - run a sub-VM to completion, returns exit_code(入口 RET 的值) */ +static spl_vm_val_t vm_run(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { + (void)_vm; + CHECK_NARGS("vm_run", 1); + spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0]; + if (!vm) + return -1; + spl_vm_run_until(vm, 0); + return (spl_vm_val_t)vm->exit_code; +} /* ================================================================= * Registration @@ -1163,6 +1195,7 @@ void spl_syscall_register(spl_prog_t *prog) { {"vm_new", 0, vm_new}, {"vm_drop", 0, vm_drop}, {"vm_load", 0, vm_load}, + {"vm_load_from_buffer", 0, vm_load_from_buffer}, {"vm_push", 0, vm_push}, {"vm_call", 0, vm_call}, {"vm_run", 0, vm_run}, diff --git a/stage0/spl_vm.c b/stage0/spl_vm.c index 535c265..828ba6a 100644 --- a/stage0/spl_vm.c +++ b/stage0/spl_vm.c @@ -824,7 +824,7 @@ static void spl_vm_sample(spl_vm_t *vm) { char buf[8192]; usize len = 0; - for (isize i = 0; i < vm->cp; i++) { + for (usize i = 0; i < vm->cp; i++) { intptr_t s = (intptr_t)vm->frames.data[i].saved_ip; if (s <= 0) continue; diff --git a/stage2/ast.spl b/stage2/ast.spl index e90908b..606cc0d 100644 --- a/stage2/ast.spl +++ b/stage2/ast.spl @@ -121,6 +121,8 @@ type Body = enum { PatternList: struct { items: Vec }, PatternAggregate: struct { items: Vec }, PatternField: struct { name: ByteView, pattern: usize }, + ExprComptime: struct { body: usize }, + ExprComptimeInit: struct { name: ByteView, args: Vec, fields: Vec }, } fn kind_name(body: Body): []u8 { @@ -143,6 +145,7 @@ fn kind_name(body: Body): []u8 { .StatementContinue(_) => { ret "statement_continue"; }, .StatementDefer(_) => { ret "statement_defer"; }, .StatementComptime(_) => { ret "statement_comptime"; }, + .ExprComptime(_) => { ret "expr_comptime"; }, .StatementDecl(_) => { ret "statement_decl"; }, .StatementExpr(_) => { ret "statement_expr"; }, .AggregateMember(_) => { ret "aggregate_member"; }, @@ -168,6 +171,7 @@ fn kind_name(body: Body): []u8 { .ExprArrayLit(_) => { ret "array_literal"; }, .ExprAggregateLit(_) => { ret "aggregate_literal"; }, .ExprComptimeCall(_) => { ret "comptime_call"; }, + .ExprComptimeInit(_) => { ret "comptime_init"; }, .ExprBlock(_) => { ret "block"; }, .ExprIf(_) => { ret "if_expr"; }, .ExprIfvar(_) => { ret "ifvar_expr"; }, @@ -463,6 +467,7 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): .StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); }, .StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); }, .StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); }, + .ExprComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); }, .StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); }, .StatementExpr(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); }, .AggregateMember(d) => { @@ -514,6 +519,7 @@ fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): }, .ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); }, .ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); }, + .ExprComptimeInit(x) => { ok = dump_list(ast, buf, stack, x.fields); }, .ExprBlock(b) => { ok = dump_list(ast, buf, stack, b.stmts) && dump_opt(ast, buf, stack, b.tail_expr, true); diff --git a/stage2/ir2c.spl b/stage2/ir2c.spl new file mode 100644 index 0000000..f947cd9 --- /dev/null +++ b/stage2/ir2c.spl @@ -0,0 +1,1304 @@ +// Expect retcode = 0 + +// IR -> C99 后端(P1a 子集)。 +// +// 设计:每个 IR ref 一个 C 局部变量(C 编译器 -O2 会做 mem2reg),标签用 goto; +// 类型直接由 IR 的 tid 决定,生成期无运行时分发。 +// +// 每个非 extern 函数的 C 名为 `spl_f`;extern 函数直接用其 SPL 名 +// (由 runtime 提供)。暂不支持聚合/切片/全局数据。 + +type .{ .Vec } = @import("utils/vec.spl"); +type .{ .ByteView } = @import("utils/byte_view.spl"); +type .{ .ByteBuffer } = @import("utils/byte_buffer.spl"); +type .{ .append_usize, .append_i64, .append_u64 } = @import("utils/fmt.spl"); +type TypeTable = @import("./type_table.spl"); +type LayoutM = @import("./layout.spl"); +type .{ .Layout } = @import("./layout.spl"); +type AttrM = @import("./attr.spl"); +type .{ .Attr } = @import("./attr.spl"); +type IrM = @import("./ir.spl"); +type .{ .Ir, .IrFunc, .IrEntry } = @import("./ir.spl"); + +type Ir2C = @self; + +ir: *Ir, +ty: *TypeTable, +layout: *Layout, +attr: *Attr, +out: *ByteBuffer, +gdata_n: usize, +failed: bool, + +fn usize_at(v: *Vec, i: usize): usize { + ret (v.data as *usize)[i]; +} + +fn put(self: *Ir2C, s: []u8) { + match ByteBuffer.append(self.out, s) { + .Ok => {}, + .Err(error) => { self.failed = true; }, + } +} + +fn put_byte(self: *Ir2C, b: u8) { + match ByteBuffer.append_byte(self.out, b) { + .Ok => {}, + .Err(error) => { self.failed = true; }, + } +} + +fn put_u(self: *Ir2C, v: usize) { + match append_usize(self.out, v, 10, false) { + .Ok => {}, + .Err(error) => { self.failed = true; }, + } +} + +fn put_i(self: *Ir2C, v: i64) { + match append_i64(self.out, v, 10, false) { + .Ok => {}, + .Err(error) => { self.failed = true; }, + } +} + +fn put_u64(self: *Ir2C, v: u64) { + match append_u64(self.out, v, 10, false) { + .Ok => {}, + .Err(error) => { self.failed = true; }, + } +} + +fn put_bv(self: *Ir2C, v: ByteView) { + match ByteBuffer.append(self.out, ByteView.as_slice(v)) { + .Ok => {}, + .Err(error) => { self.failed = true; }, + } +} + +fn put_unsupported(self: *Ir2C, what: []u8) { + self.failed = true; + put(self, "/* unsupported: "); + put(self, what); + put(self, " */\n"); +} + +// ---- 类型查询 ---- + +fn info_of(self: *Ir2C, tid: usize): *LayoutM.LayoutInfo { + ret LayoutM.info_of(self.layout, tid); +} + +fn type_size(self: *Ir2C, tid: usize): usize { + var info = info_of(self, tid); + if info == null { ret 0; } + ret (info.size_bits + 7) / 8; +} + +fn type_align(self: *Ir2C, tid: usize): usize { + var info = info_of(self, tid); + if info == null { ret 1; } + var a = info.align_bits / 8; + if a == 0 { a = 1; } + ret a; +} + +fn is_aggregate(self: *Ir2C, tid: usize): bool { + var te = TypeTable.node(self.ty, tid); + if te == null { ret false; } + match te.node { + .Array => { ret true; }, + .Struct => { ret true; }, + .Union => { ret true; }, + .Enum => { ret true; }, + .Shape => { ret true; }, + _ => {}, + } + ret false; +} + +fn tid_is_signed(self: *Ir2C, tid: usize): bool { + var te = TypeTable.node(self.ty, tid); + if te == null { ret false; } + match te.node { + .Int(x) => { ret x.is_signed; }, + _ => {}, + } + ret false; +} + +fn tid_int_bits(self: *Ir2C, tid: usize): usize { + var te = TypeTable.node(self.ty, tid); + if te == null { ret 0; } + match te.node { + .Int(x) => { ret x.bits; }, + _ => {}, + } + ret 0; +} + +fn tid_is_bool(self: *Ir2C, tid: usize): bool { + var te = TypeTable.node(self.ty, tid); + if te == null { ret false; } + match te.node { + .Bool => { ret true; }, + _ => {}, + } + ret false; +} + +fn tid_is_unit(self: *Ir2C, tid: usize): bool { + if tid == 0 { ret true; } + var te = TypeTable.node(self.ty, tid); + if te == null { ret true; } + match te.node { + .Unit => { ret true; }, + _ => {}, + } + ret false; +} + +fn fn_ret_type(self: *Ir2C, fn_tid: usize): usize { + var te = TypeTable.node(self.ty, fn_tid); + if te == null { ret 0; } + match te.node { + .Fn(f) => { ret f.ret_type; }, + _ => {}, + } + ret 0; +} + +// ---- C 类型 ---- + +fn put_type(self: *Ir2C, tid: usize): bool { + var te = TypeTable.node(self.ty, tid); + if te == null { ret false; } + match te.node { + .Unit => { put(self, "void"); ret true; }, + .Never => { put(self, "void"); ret true; }, + .Bool => { put(self, "u8"); ret true; }, + .Int(x) => { + if x.bits == 8 { + if x.is_signed { put(self, "i8"); } else { put(self, "u8"); } + } else if x.bits == 16 { + if x.is_signed { put(self, "i16"); } else { put(self, "u16"); } + } else if x.bits == 32 { + if x.is_signed { put(self, "i32"); } else { put(self, "u32"); } + } else { + if x.is_signed { put(self, "i64"); } else { put(self, "u64"); } + } + ret true; + }, + .Float(x) => { + if x.bits == 32 { put(self, "float"); } else { put(self, "double"); } + ret true; + }, + .Ptr(x) => { put(self, "void*"); ret true; }, + .Fn(f) => { put(self, "void*"); ret true; }, + // 聚合值统一以「指向存储的指针」表示 + .Array => { put(self, "void*"); ret true; }, + .Struct => { put(self, "void*"); ret true; }, + .Union => { put(self, "void*"); ret true; }, + .Enum => { put(self, "void*"); ret true; }, + .Shape => { put(self, "void*"); ret true; }, + _ => {}, + } + ret false; +} + +fn fn_is_extern(self: *Ir2C, f: *IrFunc): bool { + var i: usize = 0; + while i < f.attrs.len { + var a = AttrM.node(self.attr, usize_at(&f.attrs, i)); + if a != null { + var info = AttrM.info_at(self.attr, a.info_index); + if info != null { + if ByteView.eq(info.name, ByteView.from_slice("extern")) { ret true; } + } + } + i = i + 1; + } + ret false; +} + +fn c_fn_name(self: *Ir2C, fid: usize) { + var f = IrM.func(self.ir, fid); + if f != null && fn_is_extern(self, f) { + put_bv(self, f.name); + ret; + } + put(self, "f"); + put_u(self, fid); +} + +// ---- 常量解析 ---- + +fn const_node(self: *Ir2C, f: *IrFunc, ref: usize): usize { + if ref == 0 || ref >= f.nodes.len { ret 0; } + var n = (f.nodes.data as *IrEntry) + ref; + match n.body { + .Const(c) => { + match c.lit { + .Int(v) => { ret v; }, + .Char(v) => { ret v as usize; }, + .Ptr(v) => { ret v; }, + _ => {}, + } + }, + _ => {}, + } + ret 0; +} + +// ---- 聚合字段 ---- + +fn field_tid(self: *Ir2C, tid: usize, idx: usize): usize { + var te = TypeTable.node(self.ty, tid); + if te == null { ret 0; } + match te.node { + .Enum(agg) => { + if idx == 0 { ret agg.tag_type; } + var vidx = idx - 1; + if vidx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, vidx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Struct(agg) => { + if idx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, idx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Union(agg) => { + if idx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, idx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Shape(agg) => { + if idx < agg.variants.len { + var m = TypeTable.member_at(&agg.variants, idx); + if m != null { ret m.type_id; } + } + ret 0; + }, + .Array(a) => { ret a.element; }, + _ => {}, + } + ret 0; +} + +fn field_off(self: *Ir2C, tid: usize, idx: usize): usize { + var te = TypeTable.node(self.ty, tid); + if te == null { ret 0; } + match te.node { + .Array(a) => { ret idx * type_size(self, a.element); }, + _ => {}, + } + var info = info_of(self, tid); + if info == null || idx >= info.field_bit_offsets.len { ret 0; } + ret usize_at(&info.field_bit_offsets, idx) / 8; +} + +// 聚合值统一表示为「指向存储的指针」:声明一个 8 字节对齐的缓冲并把指针存入 v。 +fn emit_agg_local(self: *Ir2C, ref: usize, tid: usize) { + var size = type_size(self, tid); + var words = (size + 7) / 8; + if words == 0 { words = 1; } + put(self, "u64 s"); + put_u(self, ref); + put(self, "["); + put_u(self, words); + put(self, "]; void* v"); + put_u(self, ref); + put(self, " = (void*)s"); + put_u(self, ref); + put(self, ";\n"); +} + +fn emit_agg_construct(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .AggConstruct(a) => { + if !is_aggregate(self, a.tid) { put_unsupported(self, "agg construct non-agg"); ret; } + bind_agg(self, ref); + var i: usize = 0; + while i < a.fields.len { + var fref = usize_at(&a.fields, i); + var ftid = field_tid(self, a.tid, i); + var foff = field_off(self, a.tid, i); + if ftid != 0 { + if is_aggregate(self, ftid) { + var fsz = type_size(self, ftid); + put(self, "memcpy((u8*)v"); + put_u(self, ref); + put(self, " + "); + put_u(self, foff); + put(self, ", v"); + put_u(self, fref); + put(self, ", "); + put_u(self, fsz); + put(self, ");\n"); + } else { + put(self, "*("); + put_type(self, ftid); + put(self, "*)((u8*)v"); + put_u(self, ref); + put(self, " + "); + put_u(self, foff); + put(self, ") = v"); + put_u(self, fref); + put(self, ";\n"); + } + } + i = i + 1; + } + }, + _ => {}, + } +} + +fn emit_agg_extract(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .AggExtract(a) => { + var foff = field_off(self, a.tid, a.field_idx); + if is_aggregate(self, a.field_tid) { + var fsz = type_size(self, a.field_tid); + bind_agg(self, ref); + put(self, "memcpy(v"); + put_u(self, ref); + put(self, ", (u8*)v"); + put_u(self, a.val); + put(self, " + "); + put_u(self, foff); + put(self, ", "); + put_u(self, fsz); + put(self, ");\n"); + } else { + put(self, "v"); + put_u(self, ref); + put(self, " = *("); + put_type(self, a.field_tid); + put(self, "*)((u8*)v"); + put_u(self, a.val); + put(self, " + "); + put_u(self, foff); + put(self, ");\n"); + } + }, + _ => {}, + } +} + +fn emit_agg_insert(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .AggInsert(a) => { + bind_agg(self, ref); + var sz = type_size(self, a.tid); + put(self, "memcpy(v"); + put_u(self, ref); + put(self, ", v"); + put_u(self, a.agg); + put(self, ", "); + put_u(self, sz); + put(self, ");\n"); + var ftid = field_tid(self, a.tid, a.field_idx); + var foff = field_off(self, a.tid, a.field_idx); + if ftid != 0 { + if is_aggregate(self, ftid) { + var fsz = type_size(self, ftid); + put(self, "memcpy((u8*)v"); + put_u(self, ref); + put(self, " + "); + put_u(self, foff); + put(self, ", v"); + put_u(self, a.field); + put(self, ", "); + put_u(self, fsz); + put(self, ");\n"); + } else { + put(self, "*("); + put_type(self, ftid); + put(self, "*)((u8*)v"); + put_u(self, ref); + put(self, " + "); + put_u(self, foff); + put(self, ") = v"); + put_u(self, a.field); + put(self, ";\n"); + } + } + }, + _ => {}, + } +} + +// ---- 节点发射 ---- + +fn emit_arith(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .Arith(a) => { + put(self, "v"); + put_u(self, ref); + put(self, " = "); + match n.kind { + .ArithAdd => { put(self, "(v"); put_u(self, a.left); put(self, " + v"); put_u(self, a.right); put(self, ")"); }, + .ArithSub => { put(self, "(v"); put_u(self, a.left); put(self, " - v"); put_u(self, a.right); put(self, ")"); }, + .ArithMul => { put(self, "(v"); put_u(self, a.left); put(self, " * v"); put_u(self, a.right); put(self, ")"); }, + .ArithDiv => { put(self, "(v"); put_u(self, a.left); put(self, " / v"); put_u(self, a.right); put(self, ")"); }, + .ArithRem => { put(self, "(v"); put_u(self, a.left); put(self, " % v"); put_u(self, a.right); put(self, ")"); }, + .ArithAnd => { put(self, "(v"); put_u(self, a.left); put(self, " & v"); put_u(self, a.right); put(self, ")"); }, + .ArithOr => { put(self, "(v"); put_u(self, a.left); put(self, " | v"); put_u(self, a.right); put(self, ")"); }, + .ArithXor => { put(self, "(v"); put_u(self, a.left); put(self, " ^ v"); put_u(self, a.right); put(self, ")"); }, + .ArithShl => { put(self, "(v"); put_u(self, a.left); put(self, " << v"); put_u(self, a.right); put(self, ")"); }, + .ArithShr => { put(self, "(v"); put_u(self, a.left); put(self, " >> v"); put_u(self, a.right); put(self, ")"); }, + .ArithNeg => { put(self, "(-v"); put_u(self, a.left); put(self, ")"); }, + .ArithNot => { + if tid_is_bool(self, a.tid) { + put(self, "(!v"); + } else { + put(self, "(~v"); + } + put_u(self, a.left); + put(self, ")"); + }, + .ArithAbs => { + put(self, "(v"); + put_u(self, a.left); + put(self, " < 0 ? -v"); + put_u(self, a.left); + put(self, " : v"); + put_u(self, a.left); + put(self, ")"); + }, + _ => { put(self, "0"); }, + } + put(self, ";\n"); + }, + _ => {}, + } +} + +fn emit_cmp(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .Cmp(c) => { + put(self, "v"); + put_u(self, ref); + put(self, " = (v"); + put_u(self, c.a); + match n.kind { + .CmpEq => { put(self, " == v"); }, + .CmpNe => { put(self, " != v"); }, + .CmpLt => { put(self, " < v"); }, + .CmpLe => { put(self, " <= v"); }, + .CmpGt => { put(self, " > v"); }, + .CmpGe => { put(self, " >= v"); }, + _ => { put(self, " == v"); }, + } + put_u(self, c.b); + put(self, ");\n"); + }, + _ => {}, + } +} + +fn emit_cast(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .Cast(c) => { + put(self, "v"); + put_u(self, ref); + put(self, " = "); + var from_is_ptr = false; + var from_is_bool = false; + var to_is_ptr = false; + var te = TypeTable.node(self.ty, c.from_tid); + if te != null { + match te.node { + .Ptr(p) => { from_is_ptr = true; }, + .Bool => { from_is_bool = true; }, + _ => {}, + } + } + var tte = TypeTable.node(self.ty, c.to_tid); + if tte != null { + match tte.node { .Ptr(p) => { to_is_ptr = true; }, _ => {} } + } + if to_is_ptr { + put(self, "(void*)(usize)v"); + put_u(self, c.val); + } else if from_is_ptr { + put(self, "(usize)v"); + put_u(self, c.val); + } else if from_is_bool { + put(self, "(v"); + put_u(self, c.val); + put(self, " ? 1 : 0)"); + } else { + put(self, "v"); + put_u(self, c.val); + } + put(self, ";\n"); + }, + _ => {}, + } +} + +fn emit_const(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .Const(c) => { + if tid_is_unit(self, c.tid) { ret; } + put(self, "v"); + put_u(self, ref); + put(self, " = "); + match c.lit { + .Bool(b) => { if b { put(self, "1"); } else { put(self, "0"); } }, + .Char(v) => { put_u(self, v as usize); }, + .Int(v) => { + if tid_is_signed(self, c.tid) { + var iv = v as i64; + if iv == (-9223372036854775807 - 1) { + put(self, "(-9223372036854775807 - 1)"); + } else { + put_i(self, iv); + } + } else { + put_u(self, v); + if tid_int_bits(self, c.tid) >= 64 { put(self, "ull"); } + } + }, + .Ptr(v) => { put(self, "(void*)"); put_u(self, v); }, + .Float(v) => { put(self, "0.0"); }, + .Fn(fid) => { put(self, "(void*)&"); c_fn_name(self, fid); }, + } + put(self, ";\n"); + }, + _ => {}, + } +} + +fn emit_mem(self: *Ir2C, f: *IrFunc, ref: usize, n: *IrEntry) { + match n.body { + .MemAlloc(m) => { + put(self, "v"); + put_u(self, ref); + put(self, " = (void*)s"); + put_u(self, ref); + put(self, ";\n"); + }, + .MemLoad(m) => { + if is_aggregate(self, m.tid) { + var sz = type_size(self, m.tid); + bind_agg(self, ref); + put(self, "memcpy(v"); + put_u(self, ref); + put(self, ", v"); + put_u(self, m.ptr); + put(self, ", "); + put_u(self, sz); + put(self, ");\n"); + } else { + put(self, "v"); + put_u(self, ref); + put(self, " = *("); + put_type(self, m.tid); + put(self, "*)v"); + put_u(self, m.ptr); + put(self, ";\n"); + } + }, + .MemStore(m) => { + if is_aggregate(self, m.tid) { + var sz = type_size(self, m.tid); + put(self, "memcpy(v"); + put_u(self, m.ptr); + put(self, ", v"); + put_u(self, m.val); + put(self, ", "); + put_u(self, sz); + put(self, ");\n"); + } else { + put(self, "*("); + put_type(self, m.tid); + put(self, "*)v"); + put_u(self, m.ptr); + put(self, " = v"); + put_u(self, m.val); + put(self, ";\n"); + } + }, + .MemOffset(m) => { + var size = type_size(self, m.tid); + if size == 0 { size = 1; } + put(self, "v"); + put_u(self, ref); + put(self, " = (void*)((u8*)v"); + put_u(self, m.ptr); + put(self, " + (u64)v"); + put_u(self, m.offset); + put(self, " * "); + put_u(self, size); + put(self, ");\n"); + }, + .MemFieldPtr(m) => { + var foff = field_off(self, m.tid, m.field_idx); + put(self, "v"); + put_u(self, ref); + put(self, " = (void*)((u8*)v"); + put_u(self, m.agg); + put(self, " + "); + put_u(self, foff); + put(self, ");\n"); + }, + .MemGlobalAlloc(m) => { + var ctid: usize = 0; + var pv: usize = 0; + var found = false; + if m.const_node > 0 && m.const_node < f.nodes.len { + var cn = (f.nodes.data as *IrEntry) + m.const_node; + match cn.body { + .Const(c) => { + ctid = c.tid; + match c.lit { .Ptr(v) => { pv = v; found = true; }, _ => {} } + }, + _ => {}, + } + } + if !found { put(self, "v"); put_u(self, ref); put(self, " = (void*)0;\n"); ret; } + var size = type_size(self, ctid); + if size == 0 { size = 8; } + var gid = self.gdata_n; + self.gdata_n = self.gdata_n + 1; + var words = (size + 7) / 8; + if words == 0 { words = 1; } + put(self, "static const u64 g"); + put_u(self, gid); + put(self, "["); + put_u(self, words); + put(self, "] = {"); + var j: usize = 0; + while j < words { + if j > 0 { put(self, ", "); } + var w: u64 = 0; + var k: usize = 0; + while k < 8 { + var idx = j * 8 + k; + var b: u64 = 0; + if idx < size { b = ((pv + idx) as *u8).* as u64; } + w = w | (b << (k as u64 * 8)); + k = k + 1; + } + put_u64(self, w); + put(self, "ull"); + j = j + 1; + } + put(self, "};\nv"); + put_u(self, ref); + put(self, " = (void*)g"); + put_u(self, gid); + put(self, ";\n"); + }, + _ => {}, + } +} + +fn emit_typeop(self: *Ir2C, ref: usize, n: *IrEntry) { + match n.body { + .TypeOp(t) => { + var v: usize = 0; + match n.kind { + .TypeSizeof => { v = type_size(self, t.tid); }, + .TypeBitsizeof => { + var info = info_of(self, t.tid); + if info != null { v = info.size_bits; } + }, + .TypeAlignof => { v = type_align(self, t.tid); }, + .TypeFieldCount => { + var te = TypeTable.node(self.ty, t.tid); + if te != null { + match te.node { + .Struct(a) => { v = a.variants.len; }, + .Union(a) => { v = a.variants.len; }, + .Array(a) => { v = a.len; }, + _ => {}, + } + } + }, + _ => {}, + } + put(self, "v"); + put_u(self, ref); + put(self, " = "); + put_u(self, v); + put(self, ";\n"); + }, + _ => {}, + } +} + +// 节点的「值类型」;0 表示无结果。 +fn result_tid(self: *Ir2C, n: *IrEntry): usize { + match n.body { + .Arith(a) => { ret a.tid; }, + .Cmp(c) => { ret TypeTable.make_bool(self.ty); }, + .Cast(c) => { ret c.to_tid; }, + .MemAlloc(m) => { ret TypeTable.make_ptr(self.ty, m.tid); }, + .MemGlobalAlloc(m) => { ret TypeTable.make_ptr(self.ty, m.tid); }, + .MemLoad(m) => { ret m.tid; }, + .MemOffset(m) => { ret TypeTable.make_ptr(self.ty, m.tid); }, + .MemFieldPtr(m) => { + var ft = field_tid(self, m.tid, m.field_idx); + ret TypeTable.make_ptr(self.ty, ft); + }, + .Const(c) => { ret c.tid; }, + .TypeOp(t) => { ret TypeTable.make_int(self.ty, 64, false); }, + .AggConstruct(a) => { ret a.tid; }, + .AggExtract(a) => { ret a.field_tid; }, + .AggInsert(a) => { ret a.tid; }, + .Select(s) => { ret s.tid; }, + .Call(c) => { ret c.tid; }, + .Param(p) => { ret p.tid; }, + _ => {}, + } + ret 0; +} + +fn res_tid_ref(self: *Ir2C, f: *IrFunc, ref: usize): usize { + if ref == 0 || ref >= f.nodes.len { ret 0; } + ret result_tid(self, (f.nodes.data as *IrEntry) + ref); +} + +fn emit_call(self: *Ir2C, f: *IrFunc, ref: usize, n: *IrEntry) { + match n.body { + .Call(c) => { + var callee: usize = 0; + if c.func > 0 && c.func < f.nodes.len { + var fnode = (f.nodes.data as *IrEntry) + c.func; + match fnode.body { + .Const(cc) => { + match cc.lit { .Fn(nf) => { callee = nf; }, _ => {} } + }, + _ => {}, + } + } + var ret_agg = is_aggregate(self, c.tid); + if callee != 0 { + if ret_agg { + bind_agg(self, ref); + } else if !tid_is_unit(self, c.tid) { + put(self, "v"); + put_u(self, ref); + put(self, " = "); + } + c_fn_name(self, callee); + put(self, "("); + if ret_agg { + put(self, "v"); + put_u(self, ref); + } + var k: usize = 0; + while k < c.params.len { + if k > 0 || ret_agg { put(self, ", "); } + put(self, "v"); + put_u(self, usize_at(&c.params, k)); + k = k + 1; + } + put(self, ");\n"); + ret; + } + // 间接调用:从 func 值的类型构造函数指针类型 + var func_tid = res_tid_ref(self, f, c.func); + var ftnode = TypeTable.node(self.ty, func_tid); + if ftnode == null { + put_unsupported(self, "indirect call type"); + ret; + } + var params_tid: usize = 0; + match ftnode.node { .Fn(fd) => { params_tid = func_tid; }, _ => {} } + if ret_agg { + bind_agg(self, ref); + } else if !tid_is_unit(self, c.tid) { + put(self, "v"); + put_u(self, ref); + put(self, " = "); + } + put(self, "(("); + if ret_agg || tid_is_unit(self, c.tid) { + put(self, "void"); + } else { + if !put_type(self, c.tid) { put(self, "void"); } + } + put(self, " (*)("); + var need_comma = false; + if ret_agg { + put(self, "void*"); + need_comma = true; + } + var np: usize = 0; + var te = TypeTable.node(self.ty, params_tid); + if te != null { + match te.node { + .Fn(fd) => { + np = fd.params.len; + var kp: usize = 0; + while kp < np { + if need_comma { put(self, ", "); } + var pt = usize_at(&fd.params, kp); + if is_aggregate(self, pt) { + put(self, "void*"); + } else { + if !put_type(self, pt) { put(self, "void*"); } + } + need_comma = true; + kp = kp + 1; + } + }, + _ => {}, + } + } + if !need_comma { put(self, "void"); } + put(self, "))v"); + put_u(self, c.func); + put(self, ")("); + if ret_agg { + put(self, "v"); + put_u(self, ref); + } + var k2: usize = 0; + while k2 < c.params.len { + if k2 > 0 || ret_agg { put(self, ", "); } + put(self, "v"); + put_u(self, usize_at(&c.params, k2)); + k2 = k2 + 1; + } + put(self, ");\n"); + }, + _ => {}, + } +} + +fn emit_control(self: *Ir2C, f: *IrFunc, ref: usize, n: *IrEntry, ret_tid: usize) { + match n.body { + .Param(p) => { + var idx = const_node(self, f, p.idx); + if is_aggregate(self, p.tid) { + put(self, "v"); + put_u(self, ref); + put(self, " = a"); + put_u(self, idx); + put(self, ";\n"); + } else { + put(self, "v"); + put_u(self, ref); + put(self, " = ("); + put_type(self, p.tid); + put(self, ")a"); + put_u(self, idx); + put(self, ";\n"); + } + }, + .Ret(r) => { + if is_aggregate(self, ret_tid) { + if r.val != 0 { + var sz = type_size(self, ret_tid); + put(self, "memcpy(ret__, v"); + put_u(self, r.val); + put(self, ", "); + put_u(self, sz); + put(self, ");\n"); + } + put(self, "return;\n"); + } else if tid_is_unit(self, ret_tid) { + put(self, "return;\n"); + } else { + put(self, "return v"); + put_u(self, r.val); + put(self, ";\n"); + } + }, + .Br(b) => { + put(self, "if (v"); + put_u(self, b.cond); + put(self, ") goto L"); + put_u(self, b.true_label); + put(self, ";\ngoto L"); + put_u(self, b.false_label); + put(self, ";\n"); + }, + .Jmp(j) => { + put(self, "goto L"); + put_u(self, j.label); + put(self, ";\n"); + }, + .Call(c) => { emit_call(self, f, ref, n); }, + _ => {}, + } +} + +fn hex_digit(v: u8): []u8 { + if v == 0 { ret "0"; } + if v == 1 { ret "1"; } + if v == 2 { ret "2"; } + if v == 3 { ret "3"; } + if v == 4 { ret "4"; } + if v == 5 { ret "5"; } + if v == 6 { ret "6"; } + if v == 7 { ret "7"; } + if v == 8 { ret "8"; } + if v == 9 { ret "9"; } + if v == 10 { ret "a"; } + if v == 11 { ret "b"; } + if v == 12 { ret "c"; } + if v == 13 { ret "d"; } + if v == 14 { ret "e"; } + ret "f"; +} + +fn emit_c_string(self: *Ir2C, msg: ByteView) { + put(self, "\""); + var i: usize = 0; + while i < msg.len { + var b = msg.ptr[i]; + if b == 34 { put(self, "\\\""); } + else if b == 92 { put(self, "\\\\"); } + else if b == 10 { put(self, "\\n"); } + else if b == 13 { put(self, "\\r"); } + else if b == 9 { put(self, "\\t"); } + else if b >= 32 && b < 127 { put_byte(self, b); } + else { + put(self, "\\x"); + put(self, hex_digit((b >> 4) & 15)); + put(self, hex_digit(b & 15)); + } + i = i + 1; + } + put(self, "\""); +} + +fn emit_node(self: *Ir2C, fid: usize, f: *IrFunc, ref: usize, n: *IrEntry, ret_tid: usize) { + match n.kind { + .TypeConst => { emit_const(self, ref, n); }, + + .ArithAdd => { emit_arith(self, ref, n); }, + .ArithSub => { emit_arith(self, ref, n); }, + .ArithMul => { emit_arith(self, ref, n); }, + .ArithDiv => { emit_arith(self, ref, n); }, + .ArithRem => { emit_arith(self, ref, n); }, + .ArithAnd => { emit_arith(self, ref, n); }, + .ArithOr => { emit_arith(self, ref, n); }, + .ArithXor => { emit_arith(self, ref, n); }, + .ArithShl => { emit_arith(self, ref, n); }, + .ArithShr => { emit_arith(self, ref, n); }, + .ArithNeg => { emit_arith(self, ref, n); }, + .ArithNot => { emit_arith(self, ref, n); }, + .ArithAbs => { emit_arith(self, ref, n); }, + + .CmpEq => { emit_cmp(self, ref, n); }, + .CmpNe => { emit_cmp(self, ref, n); }, + .CmpLt => { emit_cmp(self, ref, n); }, + .CmpLe => { emit_cmp(self, ref, n); }, + .CmpGt => { emit_cmp(self, ref, n); }, + .CmpGe => { emit_cmp(self, ref, n); }, + + .CastTrunc => { emit_cast(self, ref, n); }, + .CastZext => { emit_cast(self, ref, n); }, + .CastSext => { emit_cast(self, ref, n); }, + .CastBitcast => { emit_cast(self, ref, n); }, + .CastPtr2int => { emit_cast(self, ref, n); }, + .CastInt2ptr => { emit_cast(self, ref, n); }, + .CastBool2int => { emit_cast(self, ref, n); }, + + .MemAlloca => { emit_mem(self, f, ref, n); }, + .MemLoad => { emit_mem(self, f, ref, n); }, + .MemStore => { emit_mem(self, f, ref, n); }, + .MemOffset => { emit_mem(self, f, ref, n); }, + .MemFieldPtr => { emit_mem(self, f, ref, n); }, + .MemGlobalAlloc => { emit_mem(self, f, ref, n); }, + + .AggConstruct => { emit_agg_construct(self, ref, n); }, + .AggExtract => { emit_agg_extract(self, ref, n); }, + .AggInsert => { emit_agg_insert(self, ref, n); }, + + .TypeSizeof => { emit_typeop(self, ref, n); }, + .TypeBitsizeof => { emit_typeop(self, ref, n); }, + .TypeAlignof => { emit_typeop(self, ref, n); }, + .TypeFieldCount => { emit_typeop(self, ref, n); }, + + .ControlParam => { emit_control(self, f, ref, n, ret_tid); }, + .ControlRet => { emit_control(self, f, ref, n, ret_tid); }, + .ControlBr => { emit_control(self, f, ref, n, ret_tid); }, + .ControlJmp => { emit_control(self, f, ref, n, ret_tid); }, + .ControlCall => { emit_control(self, f, ref, n, ret_tid); }, + .ControlSelect => { + match n.body { + .Select(s) => { + if is_aggregate(self, s.tid) { + put(self, "v"); + put_u(self, ref); + put(self, " = v"); + put_u(self, s.cond); + put(self, " ? v"); + put_u(self, s.true_val); + put(self, " : v"); + put_u(self, s.false_val); + put(self, ";\n"); + } else { + put(self, "v"); + put_u(self, ref); + put(self, " = v"); + put_u(self, s.cond); + put(self, " ? v"); + put_u(self, s.true_val); + put(self, " : v"); + put_u(self, s.false_val); + put(self, ";\n"); + } + }, + _ => {}, + } + }, + .ControlTrap => { put(self, "spl_trap();\n"); }, + .ControlUnreachable => { put(self, "spl_trap();\n"); }, + .DbgBreakpoint => {}, + .DbgPanic => { + match n.body { + .Panic(p) => { + put(self, "spl_panic("); + emit_c_string(self, p.msg); + put(self, ");\n"); + }, + _ => {}, + } + }, + _ => { put_unsupported(self, "node kind"); }, + } +} + +fn emit_fn_sig(self: *Ir2C, fid: usize, with_names: bool) { + var f = IrM.func(self.ir, fid); + var ret_tid = fn_ret_type(self, f.fn_tid); + var ret_agg = is_aggregate(self, ret_tid); + if ret_agg { + put(self, "void"); + } else if !put_type(self, ret_tid) { + put(self, "void"); + } + put(self, " "); + c_fn_name(self, fid); + put(self, "("); + var need = false; + if ret_agg { + put(self, "void* ret__"); + need = true; + } + var nargs: usize = 0; + var te = TypeTable.node(self.ty, f.fn_tid); + if te != null { + match te.node { + .Fn(fd) => { + nargs = fd.params.len; + var k: usize = 0; + while k < nargs { + if need { put(self, ", "); } + var pt = usize_at(&fd.params, k); + if is_aggregate(self, pt) { + put(self, "void*"); + } else { + if !put_type(self, pt) { put(self, "void*"); } + } + if with_names { + put(self, " a"); + put_u(self, k); + } + need = true; + k = k + 1; + } + }, + _ => {}, + } + } + if !need { put(self, "void"); } + put(self, ")"); +} + +fn emit_proto(self: *Ir2C, fid: usize) { + emit_fn_sig(self, fid, false); + put(self, ";\n"); +} + +// 预声明一个 ref 的存储与值变量。 +fn declare_ref(self: *Ir2C, ref: usize, tid: usize) { + if is_aggregate(self, tid) { + var size = type_size(self, tid); + var words = (size + 7) / 8; + if words == 0 { words = 1; } + put(self, "u64 s"); + put_u(self, ref); + put(self, "["); + put_u(self, words); + put(self, "]; void* v"); + put_u(self, ref); + put(self, ";\n"); + ret; + } + if !put_type(self, tid) { put(self, "void*"); } + put(self, " v"); + put_u(self, ref); + put(self, ";\n"); +} + +fn declare_all(self: *Ir2C, f: *IrFunc) { + var i: usize = 1; + while i < f.nodes.len { + var n = (f.nodes.data as *IrEntry) + i; + var is_alloc = false; + var count: usize = 1; + var pointee: usize = 0; + match n.body { + .MemAlloc(m) => { + is_alloc = true; + count = const_node(self, f, m.count); + if count == 0 { count = 1; } + pointee = m.tid; + }, + _ => {}, + } + if is_alloc { + if is_aggregate(self, pointee) { + var esz = type_size(self, pointee); + var words = (esz * count + 7) / 8; + if words == 0 { words = 1; } + put(self, "u64 s"); + put_u(self, i); + put(self, "["); + put_u(self, words); + put(self, "]; void* v"); + put_u(self, i); + put(self, ";\n"); + } else { + if !put_type(self, pointee) { put(self, "void*"); } + put(self, " s"); + put_u(self, i); + put(self, "["); + put_u(self, count); + put(self, "]; void* v"); + put_u(self, i); + put(self, ";\n"); + } + } else { + var tid = result_tid(self, n); + if tid != 0 && !tid_is_unit(self, tid) { + declare_ref(self, i, tid); + } + } + i = i + 1; + } +} + +fn bind_agg(self: *Ir2C, ref: usize) { + put(self, "v"); + put_u(self, ref); + put(self, " = (void*)s"); + put_u(self, ref); + put(self, ";\n"); +} + +fn emit_def(self: *Ir2C, fid: usize) { + var f = IrM.func(self.ir, fid); + var ret_tid = fn_ret_type(self, f.fn_tid); + emit_fn_sig(self, fid, true); + put(self, " {\n"); + declare_all(self, f); + var i: usize = 1; + while i < f.nodes.len { + put(self, "L"); + put_u(self, i); + put(self, ": ;\n"); + var n = (f.nodes.data as *IrEntry) + i; + emit_node(self, fid, f, i, n, ret_tid); + i = i + 1; + } + put(self, "spl_trap();\n}\n"); +} + +// ---- driver ---- + +fn find_main(self: *Ir2C): usize { + var i: usize = 1; + while i < self.ir.funcs.len { + var f = IrM.func(self.ir, i); + if f != null && !fn_is_extern(self, f) { + if ByteView.eq(f.name, ByteView.from_slice("main")) { ret i; } + } + i = i + 1; + } + ret 0; +} + +fn run(self: *Ir2C) { + put(self, "#include \"spl_rt.h\"\n"); + + var i: usize = 1; + while i < self.ir.funcs.len { + var f = IrM.func(self.ir, i); + if f != null && !fn_is_extern(self, f) { + emit_proto(self, i); + } + i = i + 1; + } + i = 1; + while i < self.ir.funcs.len { + var f = IrM.func(self.ir, i); + if f != null && !fn_is_extern(self, f) { + emit_def(self, i); + } + i = i + 1; + } + var m = find_main(self); + if m != 0 { + var f = IrM.func(self.ir, m); + var nargs: usize = 0; + var te = TypeTable.node(self.ty, f.fn_tid); + if te != null { + match te.node { .Fn(fd) => { nargs = fd.params.len; }, _ => {} } + } + if nargs == 0 { + put(self, "int main(void) { f"); + put_u(self, m); + put(self, "(); return 0; }\n"); + } else { + put(self, "int main(int argc, char** argv) { "); + if !tid_is_unit(self, fn_ret_type(self, f.fn_tid)) { + put(self, "return (int)f"); + put_u(self, m); + put(self, "(argc, (void*)argv); }\n"); + } else { + put(self, "f"); + put_u(self, m); + put(self, "(argc, (void*)argv); return 0; }\n"); + } + } + } +} + +fn new(ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr, out: *ByteBuffer): Ir2C { + ret Ir2C { + .ir = ir, + .ty = ty, + .layout = layout, + .attr = attr, + .out = out, + .gdata_n = 1, + .failed = false, + }; +} + +fn compile(ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr, out: *ByteBuffer): bool { + var c = new(ir, ty, layout, attr, out); + run(&c); + ret !c.failed; +} + +fn main() {} diff --git a/stage2/parser.spl b/stage2/parser.spl index 7e2d575..8fa6508 100644 --- a/stage2/parser.spl +++ b/stage2/parser.spl @@ -25,6 +25,7 @@ failed: bool, stop_agg: i32, error_count: usize, file: usize, // 当前源文件在 Sources 中的下标 +comptime_depth: usize, // >0 表示处于 comptime 上下文(类型可作为表达式) fn new(ast: *Ast, toks: Vec, diag: *Diag): Parser { ret Parser { @@ -36,6 +37,7 @@ fn new(ast: *Ast, toks: Vec, diag: *Diag): Parser { .stop_agg = 0, .error_count = 0, .file = 0, + .comptime_depth = 0, }; } @@ -71,6 +73,21 @@ fn peek(self: *Parser): *Token { ret base + i; } +// 跳过 trivia,返回当前 token 之后的下一个非 trivia token(不移动 pos)。 +fn peek_next(self: *Parser): *Token { + var n = self.toks.len; + var base = self.toks.data as *Token; + var i = self.pos + 1; + while i < n { + if base == null { break; } + var t = base + i; + if !is_trivia(t.kind) { break; } + i = i + 1; + } + if i >= n || base == null { ret null; } + ret base + i; +} + fn advance(self: *Parser): *Token { var t = peek(self); if t == null { ret null; } @@ -264,6 +281,7 @@ fn can_start_expr(kind: TokenKind): bool { .Loop => { ret true; }, .For => { ret true; }, .Match => { ret true; }, + .Comptime => { ret true; }, _ => { ret false; }, } }, @@ -655,7 +673,33 @@ fn parse_param_list(self: *Parser): Vec { ret out; } -fn parse_fn_decl(self: *Parser, tags: Vec): usize { +// 该 token 是否可能开始一个类型表达式(用于省略冒号的返回类型)。 +fn token_starts_type(t: *Token): bool { + if t == null { ret false; } + match t.kind { + .Ident => { ret true; }, + .Keyword(k) => { + if @enum2int(k) == @enum2int(Keyword.Type) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Bool) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Any) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Fn) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Struct) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Union) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Enum) { ret true; } + if @enum2int(k) == @enum2int(Keyword.Shape) { ret true; } + }, + .Punct(p) => { + if @enum2int(p) == @enum2int(Punct.Mul) { ret true; } + if @enum2int(p) == @enum2int(Punct.LBracket) { ret true; } + if @enum2int(p) == @enum2int(Punct.At) { ret true; } + if @enum2int(p) == @enum2int(Punct.Sharp) { ret true; } + }, + _ => {}, + } + ret false; +} + +fn parse_fn_decl(self: *Parser, tags: Vec, is_comptime: bool): usize { var kw = peek(self); if kw == null || !kind_is_keyword(kw.kind, .Fn) { ret 0; } advance(self); @@ -672,19 +716,26 @@ fn parse_fn_decl(self: *Parser, tags: Vec): usize { advance(self); ret_type = parse_type_expr(self); t = peek(self); + } else if token_starts_type(t) { + // 省略冒号:`fn f() T { ... }` + ret_type = parse_type_expr(self); + t = peek(self); } var body: usize = 0; + if is_comptime { self.comptime_depth = self.comptime_depth + 1; } if t != null && kind_is_punct(t.kind, .LBrace) { body = parse_block(self); } else if t != null && kind_is_punct(t.kind, .Semicolon) { advance(self); } else { + if is_comptime { self.comptime_depth = self.comptime_depth - 1; } parse_error(self, t, "function declaration must end with ';' or block"); ret 0; } + if is_comptime { self.comptime_depth = self.comptime_depth - 1; } ret new_node(self, .FnDecl(.{ .tag_list = tags, - .is_comptime = false, + .is_comptime = is_comptime, .name = name.text, .params = params, .ret_type = ret_type, @@ -802,8 +853,10 @@ fn parse_comptime_decl(self: *Parser): usize { advance(self); var body: usize = 0; var t = peek(self); + self.comptime_depth = self.comptime_depth + 1; if t != null && kind_is_punct(t.kind, .LBrace) { body = parse_block(self); } else { body = parse_expr(self); } + self.comptime_depth = self.comptime_depth - 1; ret new_node(self, .ComptimeDecl(.{ .body = body }), kw); } @@ -815,7 +868,7 @@ fn parse_container_declaration(self: *Parser): usize { match t.kind { .Eof => { ret 0; }, .Keyword(k) => { - if @enum2int(k) == @enum2int(Keyword.Fn) { ret parse_fn_decl(self, tags); } + if @enum2int(k) == @enum2int(Keyword.Fn) { ret parse_fn_decl(self, tags, false); } if @enum2int(k) == @enum2int(Keyword.Type) { ret parse_type_decl(self, tags); } if @enum2int(k) == @enum2int(Keyword.Var) { ret parse_var_const_decl(self, tags, false); @@ -823,7 +876,15 @@ fn parse_container_declaration(self: *Parser): usize { if @enum2int(k) == @enum2int(Keyword.Const) { ret parse_var_const_decl(self, tags, true); } - if @enum2int(k) == @enum2int(Keyword.Comptime) { ret parse_comptime_decl(self); } + if @enum2int(k) == @enum2int(Keyword.Comptime) { + // `comptime fn ...` -> 标记为编译期函数;否则 `comptime { }` 声明。 + var nxt = peek_next(self); + if nxt != null && kind_is_keyword(nxt.kind, .Fn) { + advance(self); + ret parse_fn_decl(self, tags, true); + } + ret parse_comptime_decl(self); + } }, .Ident => { ret parse_member_decl(self, tags); }, .Punct(p) => { @@ -951,15 +1012,41 @@ fn parse_type_base(self: *Parser): usize { match bt.kind { .Ident => { advance(self); + // 类型调用 `Name(T, ...)`(comptime 类型构造,如 `vec(i32)`)。 + if at_punct(self, .LParen) { + advance(self); + var ref = new_node(self, .TypePath(.{ + .name = bt.text, + .generic_args = ref_vec(), + }), bt); + var args = ref_vec(); + while !self.failed && !at_punct(self, .RParen) { + var a = parse_type_expr(self); + match Vec.push(&args, &a as *u8) { .Ok => {}, .Err(e) => { ret 0; } } + if at_punct(self, .Comma) { advance(self); continue; } + break; + } + expect_punct(self, .RParen, "')' in type call"); + var tn = Ast.node(self.ast, ref); + match tn.body { + .TypePath(tp) => { + tp.generic_args = args; + tn.body = .TypePath(tp); + }, + _ => {}, + } + ret ref; + } ret new_node(self, .TypePath(.{ .name = bt.text, .generic_args = ref_vec(), }), bt); }, .Keyword(k) => { - // stage1 is_builtin_type_kw:bool / any 作为类型路径。 + // stage1 is_builtin_type_kw:bool / any 作为类型路径;stage3 增加 `type`。 if @enum2int(k) == @enum2int(Keyword.Bool) || - @enum2int(k) == @enum2int(Keyword.Any) { + @enum2int(k) == @enum2int(Keyword.Any) || + @enum2int(k) == @enum2int(Keyword.Type) { advance(self); ret new_node(self, .TypePath(.{ .name = bt.text, @@ -1357,6 +1444,21 @@ fn parse_primary(self: *Parser): usize { if @enum2int(k) == @enum2int(Keyword.Loop) { ret parse_loop_expr(self); } if @enum2int(k) == @enum2int(Keyword.For) { ret parse_for_expr(self); } if @enum2int(k) == @enum2int(Keyword.Match) { ret parse_match_expr(self); } + // `comptime `:编译期表达式(可调用普通函数、返回类型等)。 + if @enum2int(k) == @enum2int(Keyword.Comptime) { + advance(self); + var cbody = parse_expr(self); + ret new_node(self, .ExprComptime(.{ .body = cbody }), t); + } + // 类型即表达式:仅在 comptime 上下文内允许 `struct { ... }` 等聚合类型。 + if self.comptime_depth > 0 { + if @enum2int(k) == @enum2int(Keyword.Struct) || + @enum2int(k) == @enum2int(Keyword.Union) || + @enum2int(k) == @enum2int(Keyword.Enum) || + @enum2int(k) == @enum2int(Keyword.Shape) { + ret parse_type_base(self); + } + } parse_error(self, t, "unrecognized expression"); ret 0; }, @@ -1553,6 +1655,15 @@ fn parse_comptime_call(self: *Parser): usize { args = parse_expr_list(self); if !expect_punct(self, .RParen, "')' in comptime call") { ret 0; } } + // `@name(args){ ... }`:构造该编译期类型的值(聚合初始化)。 + if at_punct(self, .LBrace) && self.stop_agg == 0 { + var fields = parse_aggregate_fields(self); + ret new_node(self, .ExprComptimeInit(.{ + .name = name.text, + .args = args, + .fields = fields, + }), start); + } ret new_node(self, .ExprComptimeCall(.{ .name = name.text, .args = args }), start); } @@ -1791,7 +1902,18 @@ fn parse_ret_stmt(self: *Parser): usize { advance(self); var expr: usize = 0; var t = peek(self); - if t != null && can_start_expr(t.kind) { expr = parse_expr(self); } + var can = false; + if t != null { + can = can_start_expr(t.kind); + // comptime 上下文:允许返回类型表达式(如 `ret struct { ... };`)。 + if !can && self.comptime_depth > 0 { + if kind_is_keyword(t.kind, .Struct) || kind_is_keyword(t.kind, .Union) || + kind_is_keyword(t.kind, .Enum) || kind_is_keyword(t.kind, .Shape) { + can = true; + } + } + } + if can { expr = parse_expr(self); } t = peek(self); if t != null && kind_is_punct(t.kind, .Semicolon) { advance(self); } ret new_node(self, .StatementRet(.{ .expr = expr }), kw); @@ -2223,10 +2345,60 @@ fn test_big_int_literal() { check_int("0o_17", "15"); } +fn expect_contains(buf: *ByteBuffer, needle: []u8) { + var actual = ByteBuffer.as_slice(buf.*); + if needle.len > actual.len { @assert(false, "contains length"); } + var i: usize = 0; + while i + needle.len <= actual.len { + var j: usize = 0; + var ok = true; + while j < needle.len { + if actual.ptr[i + j] != needle.ptr[j] { ok = false; break; } + j = j + 1; + } + if ok { ret; } + i = i + 1; + } + @assert(false, "expected substring not found"); +} + +// stage3 语法:`comptime fn ... (t: type) type { ... }` 与省略冒号的返回类型。 +fn test_parse_comptime_fn() { + var src: []u8 = "comptime fn vec(t: type) type { ret t; }\n"; + var toks = lex_tokens(src); + defer { Vec.drop(&toks); } + + var sources = set_new(); + defer { set_drop(&sources); } + match set_add(&sources, ByteView.from_slice("test.spl"), ByteView.from_slice(src)) { + .Ok(index) => {}, + .Err(error) => { @assert(false, "set_add"); }, + } + + var ast: Ast = undefined; + match Ast.new(&sources) { + .Ok(v) => { ast = v; }, + .Err(error) => { @assert(false, "ast new"); }, + } + defer { Ast.drop(&ast); } + + var p = new(&ast, toks, null); + parse(&p); + @assert(!p.failed, "comptime fn parse ok"); + @assert(p.error_count == 0, "comptime fn no errors"); + + var buf = ByteBuffer.new(); + defer { ByteBuffer.drop(&buf); } + @assert(Ast.dump(&ast, &buf), "dump"); + expect_contains(&buf, "fn_decl"); + expect_contains(&buf, "type_path"); +} + fn main() { test_peek_advance(); test_new_node_span(); test_parse_decls(); test_parse_main(); test_big_int_literal(); + test_parse_comptime_fn(); } diff --git a/stage2/splc1.spl b/stage2/splc1.spl index 7dd0045..b9fce3c 100644 --- a/stage2/splc1.spl +++ b/stage2/splc1.spl @@ -13,7 +13,8 @@ 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 .{ .read_all, .print, .eprint, .open_cstr, .write_all } = @import("./utils/io.spl"); +type File = @import("./sys/file.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"); @@ -25,6 +26,7 @@ type LayoutM = @import("./layout.spl"); type .{ .Layout } = @import("./layout.spl"); type Ir2VmM = @import("./ir2vm.spl"); type .{ .Ir2Vm } = @import("./ir2vm.spl"); +type Ir2CM = @import("./ir2c.spl"); type .{ .alloc, .free } = @import("./sys/mem.spl"); fn token_at(tokens: *Vec, index: usize): Token { @@ -566,6 +568,7 @@ type PipeMode = enum { Vm, // 继续 layout + ir2vm,dump VM Compile, // 继续 layout + ir2vm,写 outpath State, // 只逐步打日志(诊断“卡在哪一步”) + C, // 继续 layout + ir2c,dump C } fn step(mode: PipeMode, s: []u8) { @@ -723,6 +726,31 @@ fn run_pipeline(log: *Logger, path: ByteView, mode: PipeMode, outpath: ByteView) LayoutM.resolve(&layout, &sema.types.ir); step(mode, "state: layout ok\n"); + if @enum2int(mode) == @enum2int(PipeMode.C) { + var cout = ByteBuffer.new(); + defer { ByteBuffer.drop(&cout); } + var ok = Ir2CM.compile(&builder.ir, &sema.types.ir, &layout, &sema.attr, &cout); + if outpath.len != 0 { + var f = open_cstr(outpath, ByteView.from_slice("wb")); + if !File.valid(&f) { + Logger.error(log, "cannot open C output file"); + ret 1; + } + match write_all(&f, ByteBuffer.as_slice(cout)) { .Ok => {}, .Err(error) => {} } + ignore_i32(File.close(&f)); + } else { + match print(ByteBuffer.as_slice(cout)) { + .Ok => {}, + .Err(error) => { ret 1; }, + } + } + if !ok { + Logger.error(log, "ir2c failed (see generated C for unsupported markers)"); + ret 1; + } + ret 0; + } + var ir2vm: Ir2Vm = undefined; Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr); defer { Ir2VmM.drop(&ir2vm); } @@ -776,11 +804,13 @@ fn main(argc: i32, argv: **u8): i32 { } if argc < 4 { - Logger.fatal(&log, "usage: splc1 --dump [out]"); + Logger.fatal(&log, "usage: splc1 --dump [out]"); ret 1; } var mode = ByteView.from_cstr(argv[2]); var path = ByteView.from_cstr(argv[3]); + var dout = ByteView.empty(); + if argc >= 5 { dout = ByteView.from_cstr(argv[4]); } if ByteView.eq(mode, ByteView.from_slice("lex")) { ret dump_lex(&log, path); @@ -800,6 +830,9 @@ fn main(argc: i32, argv: **u8): i32 { if ByteView.eq(mode, ByteView.from_slice("state")) { ret run_pipeline(&log, path, .State, ByteView.empty()); } + if ByteView.eq(mode, ByteView.from_slice("c")) { + ret run_pipeline(&log, path, .C, dout); + } Logger.fatal(&log, "dump mode not implemented"); ret 1; } diff --git a/stage2/sys/spl_rt.h b/stage2/sys/spl_rt.h new file mode 100644 index 0000000..56d1382 --- /dev/null +++ b/stage2/sys/spl_rt.h @@ -0,0 +1,629 @@ +/* spl_rt.h - SPL native runtime (header-only, C89-friendly) + * + * 提供 ir2c 生成代码所需的所有 `#extern(vm)` 符号,以及 SPL 基础类型别名。 + * 目标:生成代码可用 C89 语法通过(不依赖 stdint/stdbool)。 + */ + +#ifndef __SPL_RT_H__ +#define __SPL_RT_H__ + +#include +#include +#include +#include +#include + +/* 语义清晰的 inline:C99 用 static inline,GNU 用 __inline__,C89 退化为 static */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define SPL_INLINE static inline +#elif defined(__GNUC__) || defined(__clang__) +#define SPL_INLINE static __inline__ +#else +#define SPL_INLINE static +#endif + +/* ---- SPL 基础类型别名 ---- */ +typedef unsigned char u8; +typedef signed char i8; +typedef unsigned short u16; +typedef signed short i16; +typedef unsigned int u32; +typedef signed int i32; +typedef unsigned long long u64; +typedef signed long long i64; +typedef u64 usize; +typedef i64 isize; +/* 注:不定义 bool/true/false,避免与 C23 关键字冲突;生成代码用 u8 表示布尔。 */ + +/* ---- panic / trap ---- */ +SPL_INLINE void spl_panic(const char *msg) { + fprintf(stderr, "%s\n", msg ? msg : "panic"); + exit(1); +} +SPL_INLINE void spl_trap(void) { abort(); } + +/* ---- mem ---- */ +SPL_INLINE void *vm_alloc(u64 sz) { + if (sz == 0) + return NULL; + return malloc((size_t)sz); +} +SPL_INLINE void vm_free(void *p) { + if (p) + free(p); +} +SPL_INLINE void *vm_realloc(void *p, u64 sz) { + if (!p) + return vm_alloc(sz); + if (sz == 0) { + free(p); + return NULL; + } + return realloc(p, (size_t)sz); +} +SPL_INLINE void *vm_memcpy(void *dst, const void *src, u64 n) { + if (n) + memcpy(dst, src, (size_t)n); + return dst; +} +SPL_INLINE void *vm_memmove(void *dst, const void *src, u64 n) { + if (n) + memmove(dst, src, (size_t)n); + return dst; +} +SPL_INLINE i64 vm_memcmp(const void *a, const void *b, u64 n) { + if (n == 0) + return 0; + return (i64)memcmp(a, b, (size_t)n); +} +SPL_INLINE i32 vm_bytes_eq(const void *a, u64 alen, const void *b, u64 blen) { + if (alen != blen) + return 0; + if (alen == 0) + return 1; + if (!a || !b) + return 0; + return memcmp(a, b, (size_t)alen) == 0; +} + +/* ---- Vec ---- */ +typedef struct { + usize raw; + usize data; + usize len; + usize cap; + usize elem_size; + usize elem_align; +} spl_vec_t; + +SPL_INLINE int spl_vec_is_valid(const spl_vec_t *v) { + usize a; + if (v->elem_size == 0) + return 0; + a = v->elem_align; + if (a == 0 || (a & (a - 1)) != 0) + return 0; + if (v->len > v->cap) + return 0; + if (v->cap == 0) + return v->raw == 0 && v->data == 0 && v->len == 0; + if (v->raw == 0 || v->data == 0) + return 0; + return (v->data & (a - 1)) == 0; +} + +SPL_INLINE int spl_align_ok(usize a) { return a != 0 && (a & (a - 1)) == 0; } + +SPL_INLINE void spl_grow(usize current, usize need, usize *out) { + usize cap = current < 8 ? 8 : current; + usize limit = ~(usize)0; + while (cap < need) { + if (cap > limit / 2) { + cap = need; + break; + } + cap = cap * 2; + } + *out = cap; +} + +SPL_INLINE int spl_vec_allocate(spl_vec_t *out, usize capacity, usize elem_size, usize elem_align) { + usize bytes, mask, addr; + void *raw; + if (elem_size == 0) + return 1; + if (!spl_align_ok(elem_align)) + return 4; + out->raw = 0; + out->data = 0; + out->len = 0; + out->cap = 0; + out->elem_size = elem_size; + out->elem_align = elem_align; + if (capacity == 0) + return 0; + if (capacity > ~(usize)0 / elem_size) + return 3; + bytes = capacity * elem_size; + mask = elem_align - 1; + if (bytes > ~(usize)0 - mask) + return 3; + raw = malloc(bytes + mask); + if (!raw) + return 3; + addr = (usize)raw; + if (addr > ~(usize)0 - mask) { + free(raw); + return 3; + } + out->raw = addr; + out->data = (addr + mask) & ~mask; + out->cap = capacity; + return 0; +} + +SPL_INLINE int spl_vec_reserve_raw(spl_vec_t *v, usize need) { + usize new_cap = 0, used = 0, old; + spl_vec_t fresh; + int rc; + if (need <= v->cap) + return 0; + spl_grow(v->cap, need, &new_cap); + rc = spl_vec_allocate(&fresh, new_cap, v->elem_size, v->elem_align); + if (rc) + return rc; + if (v->len != 0) { + if (v->len > ~(usize)0 / v->elem_size) { + free((void *)fresh.raw); + return 3; + } + used = v->len * v->elem_size; + } + if (used) + memcpy((void *)fresh.data, (void *)v->data, used); + fresh.len = v->len; + old = v->raw; + *v = fresh; + if (old) + free((void *)old); + return 0; +} + +SPL_INLINE void spl_vec_release(spl_vec_t *v) { + usize es = v->elem_size, ea = v->elem_align; + if (v->raw) + free((void *)v->raw); + v->raw = 0; + v->data = 0; + v->len = 0; + v->cap = 0; + v->elem_size = es; + v->elem_align = ea; +} + +SPL_INLINE i32 vm_vec_valid(void *vv) { + const spl_vec_t *v = (const spl_vec_t *)vv; + return v ? spl_vec_is_valid(v) : 0; +} +SPL_INLINE i32 vm_vec_push_raw(void *vv, const void *elem) { + spl_vec_t *v = (spl_vec_t *)vv; + if (!v || !elem) + return 0; + if (v->len < v->cap) { + memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size); + v->len++; + return 1; + } + return 0; +} +SPL_INLINE i32 vm_vec_get(void *vv, u64 index, void **out) { + const spl_vec_t *v = (const spl_vec_t *)vv; + if (!v || !out) + return 1; + if (!spl_vec_is_valid(v)) + return 1; + if (index >= v->len) + return 2; + *out = (void *)(v->data + index * v->elem_size); + return 0; +} +SPL_INLINE i32 vm_vec_push(void *vv, const void *elem) { + spl_vec_t *v = (spl_vec_t *)vv; + int rc; + if (!v || !elem || !spl_vec_is_valid(v)) + return 1; + if (v->len < v->cap) { + memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size); + v->len++; + return 0; + } + rc = spl_vec_reserve_raw(v, v->len + 1); + if (rc) + return rc; + memmove((void *)(v->data + v->len * v->elem_size), elem, v->elem_size); + v->len++; + return 0; +} +SPL_INLINE i32 vm_vec_allocate(void *out, u64 capacity, u64 elem_size, u64 elem_align) { + return spl_vec_allocate((spl_vec_t *)out, capacity, elem_size, elem_align); +} +SPL_INLINE i32 vm_vec_reserve_raw(void *vv, u64 need) { + spl_vec_t *v = (spl_vec_t *)vv; + if (!v) + return 1; + return spl_vec_reserve_raw(v, need); +} +SPL_INLINE i32 vm_vec_append(void *vv, const void *elems, u64 count) { + spl_vec_t *v = (spl_vec_t *)vv; + usize copy_bytes, new_len, used, source_offset = 0; + int source_inside = 0, rc; + if (!v || !spl_vec_is_valid(v)) + return 1; + if (count == 0) + return 0; + if (!elems) + return 1; + if (count > ~(usize)0 / v->elem_size) + return 3; + copy_bytes = count * v->elem_size; + if (v->len > ~(usize)0 - count) + return 3; + new_len = v->len + count; + used = v->len * v->elem_size; + if (used) { + usize base = v->data; + usize source = (usize)elems; + if (source >= base && source - base < used) { + source_offset = source - base; + if (copy_bytes > used - source_offset) + return 2; + source_inside = 1; + } + } + rc = spl_vec_reserve_raw(v, new_len); + if (rc) + return rc; + if (source_inside) + elems = (const void *)(v->data + source_offset); + memmove((void *)(v->data + used), elems, copy_bytes); + v->len = new_len; + return 0; +} +SPL_INLINE i32 vm_vec_set(void *vv, u64 index, const void *elem) { + spl_vec_t *v = (spl_vec_t *)vv; + if (!v || !elem || !spl_vec_is_valid(v)) + return 1; + if (index >= v->len) + return 2; + memmove((void *)(v->data + index * v->elem_size), elem, v->elem_size); + return 0; +} +SPL_INLINE i32 vm_vec_pop(void *vv, void *out) { + spl_vec_t *v = (spl_vec_t *)vv; + if (!v || !out || !spl_vec_is_valid(v)) + return 1; + if (v->len == 0) + return 2; + memmove(out, (void *)(v->data + (v->len - 1) * v->elem_size), v->elem_size); + v->len--; + return 0; +} +SPL_INLINE i32 vm_vec_clone(void *outp, const void *srcp) { + spl_vec_t *out = (spl_vec_t *)outp; + const spl_vec_t *src = (const spl_vec_t *)srcp; + spl_vec_t fresh; + usize bytes; + int rc; + if (!out || !src || !spl_vec_is_valid(out) || !spl_vec_is_valid(src)) + return 1; + if (out->elem_size != src->elem_size || out->elem_align != src->elem_align) + return 1; + fresh.raw = 0; + fresh.data = 0; + fresh.len = 0; + fresh.cap = 0; + fresh.elem_size = src->elem_size; + fresh.elem_align = src->elem_align; + rc = spl_vec_reserve_raw(&fresh, src->len); + if (rc) + return rc; + bytes = src->len * src->elem_size; + if (bytes) + memcpy((void *)fresh.data, (void *)src->data, bytes); + fresh.len = src->len; + if (out->raw) + free((void *)out->raw); + *out = fresh; + return 0; +} +SPL_INLINE void vm_vec_drop(void *vv) { + spl_vec_t *v = (spl_vec_t *)vv; + if (v) + spl_vec_release(v); +} + +/* ---- Map ---- */ +typedef struct { + spl_vec_t keys; + spl_vec_t vals; + spl_vec_t states; + usize size; + usize key_size; + usize val_size; + usize hash_kind; +} spl_map_t; + +SPL_INLINE usize spl_hash_usize_value(usize value) { + usize prime = 1099511628211ull; + usize h = value; + h = (h ^ (h >> 30)) * prime; + h = h ^ (h >> 27); + h = h * prime; + h = h ^ (h >> 31); + if (h == 0) + return prime; + return h; +} +SPL_INLINE usize spl_hash_bytes(const void *data, usize len) { + const unsigned char *p = (const unsigned char *)data; + usize h = 14695981039346656037ull; + usize prime = 1099511628211ull; + usize i; + for (i = 0; i < len; i++) + h = (h ^ (usize)p[i]) * prime; + return h; +} +SPL_INLINE usize spl_map_hash(const spl_map_t *m, const void *key) { + const usize *v; + if (m->hash_kind == 0) + return spl_hash_usize_value(*(const usize *)key); + if (m->hash_kind == 2) + return 3; + v = (const usize *)key; + return spl_hash_bytes((const void *)v[0], v[1]); +} +SPL_INLINE int spl_map_eq(const spl_map_t *m, const void *a, const void *b) { + const usize *x; + const usize *y; + if (m->hash_kind == 0 || m->hash_kind == 2) + return *(const usize *)a == *(const usize *)b; + x = (const usize *)a; + y = (const usize *)b; + if (x[1] != y[1]) + return 0; + if (x[1] == 0) + return 1; + return memcmp((const void *)x[0], (const void *)y[0], x[1]) == 0; +} +SPL_INLINE int spl_map_valid(const spl_map_t *m) { + if (m->key_size == 0 || m->val_size == 0) + return 0; + if (m->keys.len != m->states.len || m->vals.len != m->states.len) + return 0; + if (m->size > m->states.len) + return 0; + return 1; +} +SPL_INLINE usize spl_map_find_slot(const spl_map_t *m, const void *key) { + usize mask, i, probes; + unsigned char *st; + if (m->states.cap == 0) + return ~(usize)0; + mask = m->states.cap - 1; + i = spl_map_hash(m, key) & mask; + st = (unsigned char *)m->states.data; + probes = 0; + while (probes < m->states.cap && st[i] != 0) { + if (st[i] == 1 && spl_map_eq(m, (const void *)(m->keys.data + i * m->key_size), key)) + return i; + i = (i + 1) & mask; + probes++; + } + return ~(usize)0; +} +SPL_INLINE void spl_map_place(spl_map_t *m, const void *key, const void *val) { + usize mask, i, first_deleted, probes; + unsigned char *st; + mask = m->states.cap - 1; + i = spl_map_hash(m, key) & mask; + st = (unsigned char *)m->states.data; + first_deleted = ~(usize)0; + probes = 0; + while (probes < m->states.cap && st[i] != 0) { + if (st[i] == 2 && first_deleted == ~(usize)0) + first_deleted = i; + i = (i + 1) & mask; + probes++; + } + if (first_deleted != ~(usize)0) + i = first_deleted; + memcpy((void *)(m->keys.data + i * m->key_size), key, m->key_size); + memcpy((void *)(m->vals.data + i * m->val_size), val, m->val_size); + st[i] = 1; + m->size++; +} +SPL_INLINE int spl_map_reserve(spl_map_t *m, usize need) { + usize new_cap = 0, i; + spl_map_t fresh; + int rc; + if (!spl_map_valid(m)) + return 1; + if (need <= m->states.cap) + return 0; + spl_grow(m->states.cap, need, &new_cap); + fresh = *m; + fresh.keys.raw = 0; + fresh.keys.data = 0; + fresh.keys.len = 0; + fresh.keys.cap = 0; + fresh.vals.raw = 0; + fresh.vals.data = 0; + fresh.vals.len = 0; + fresh.vals.cap = 0; + fresh.states.raw = 0; + fresh.states.data = 0; + fresh.states.len = 0; + fresh.states.cap = 0; + fresh.size = 0; + rc = spl_vec_reserve_raw(&fresh.keys, new_cap); + if (rc) + goto fail; + rc = spl_vec_reserve_raw(&fresh.vals, new_cap); + if (rc) + goto fail; + rc = spl_vec_reserve_raw(&fresh.states, new_cap); + if (rc) + goto fail; + fresh.keys.len = new_cap; + fresh.vals.len = new_cap; + fresh.states.len = new_cap; + memset((void *)fresh.states.data, 0, new_cap); + for (i = 0; i < m->states.len; i++) { + if (((unsigned char *)m->states.data)[i] == 1) + spl_map_place(&fresh, (const void *)(m->keys.data + i * m->key_size), + (const void *)(m->vals.data + i * m->val_size)); + } + spl_vec_release(&m->keys); + spl_vec_release(&m->vals); + spl_vec_release(&m->states); + *m = fresh; + return 0; +fail: + spl_vec_release(&fresh.keys); + spl_vec_release(&fresh.vals); + spl_vec_release(&fresh.states); + return rc; +} +SPL_INLINE i32 vm_map_valid(void *mm) { + const spl_map_t *m = (const spl_map_t *)mm; + return m ? spl_map_valid(m) : 0; +} +SPL_INLINE i32 vm_map_reserve(void *mm, u64 need) { + spl_map_t *m = (spl_map_t *)mm; + if (!m) + return 1; + return spl_map_reserve(m, need); +} +SPL_INLINE i32 vm_map_put(void *mm, const void *key, const void *val) { + spl_map_t *m = (spl_map_t *)mm; + usize existing; + if (!m || !key || !val || !spl_map_valid(m)) + return 1; + existing = spl_map_find_slot(m, key); + if (existing != ~(usize)0) { + memcpy((void *)(m->vals.data + existing * m->val_size), val, m->val_size); + return 0; + } + if (m->states.cap == 0) { + int rc = spl_map_reserve(m, 8); + if (rc) + return rc; + } else { + usize next_size, scaled; + if (m->size > ~(usize)0 - 1) + return 3; + next_size = m->size + 1; + if (next_size > ~(usize)0 / 128) + return 3; + scaled = next_size * 128; + if (scaled / m->states.cap >= 70) { + int rc; + if (m->states.cap > ~(usize)0 / 2) + return 3; + rc = spl_map_reserve(m, m->states.cap * 2); + if (rc) + return rc; + } + } + spl_map_place(m, key, val); + return 0; +} +SPL_INLINE i32 vm_map_get(void *mm, const void *key, void *out) { + spl_map_t *m = (spl_map_t *)mm; + usize i; + if (!m || !key || !out || !spl_map_valid(m)) + return 1; + i = spl_map_find_slot(m, key); + if (i == ~(usize)0) + return 1; + memcpy(out, (void *)(m->vals.data + i * m->val_size), m->val_size); + return 0; +} +SPL_INLINE i32 vm_map_del(void *mm, const void *key) { + spl_map_t *m = (spl_map_t *)mm; + usize i; + if (!m || !key || !spl_map_valid(m)) + return 1; + i = spl_map_find_slot(m, key); + if (i == ~(usize)0) + return 1; + ((unsigned char *)m->states.data)[i] = 2; + m->size--; + return 0; +} +SPL_INLINE void vm_map_clear(void *mm) { + spl_map_t *m = (spl_map_t *)mm; + if (!m) + return; + if (m->states.data && m->states.len) + memset((void *)m->states.data, 0, m->states.len); + m->size = 0; +} +SPL_INLINE void vm_map_drop(void *mm) { + spl_map_t *m = (spl_map_t *)mm; + if (!m) + return; + spl_vec_release(&m->keys); + spl_vec_release(&m->vals); + spl_vec_release(&m->states); + m->size = 0; +} + +/* ---- file ---- */ +SPL_INLINE void *vm_fopen(void *path, void *mode) { + return (void *)fopen((const char *)path, (const char *)mode); +} +SPL_INLINE i32 vm_fclose(void *f) { return f ? fclose((FILE *)f) : 0; } +SPL_INLINE u64 vm_fread(void *buf, u64 sz, u64 n, void *f) { + return f ? (u64)fread(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0; +} +SPL_INLINE u64 vm_fwrite(void *buf, u64 sz, u64 n, void *f) { + return f ? (u64)fwrite(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0; +} +SPL_INLINE u64 vm_fsize(void *f) { + long cur, end; + if (!f) + return 0; + cur = ftell((FILE *)f); + if (cur < 0) + return 0; + if (fseek((FILE *)f, 0, SEEK_END) != 0) + return 0; + end = ftell((FILE *)f); + fseek((FILE *)f, cur, SEEK_SET); + return end < 0 ? 0 : (u64)end; +} +SPL_INLINE void *vm_stdin(void) { return (void *)stdin; } +SPL_INLINE void *vm_stdout(void) { return (void *)stdout; } +SPL_INLINE void *vm_stderr(void) { return (void *)stderr; } +SPL_INLINE i32 vm_putchar(i32 c) { return fputc(c, stdout); } +SPL_INLINE i32 vm_getchar(void) { return fgetc(stdin); } + +/* ---- misc ---- */ +SPL_INLINE void vm_exit(i32 code) { exit(code); } +SPL_INLINE u64 vm_timestamp(u64 kind) { + time_t sec = time(NULL); + if (kind == 1) + return (u64)sec; + if (kind == 2) + return 0; + if (kind == 4) + return (u64)sec * 1000000000ull; + return 0; +} + +/* ---- 内嵌 VM(comptime):实现在 spl_vm_embed.c,链接 stage0 VM ---- */ +#define SPL_VM_EMBED_IMPLIMENTATION +#include "spl_vm_embed.h" + +#endif /* __SPL_RT_H__ */ diff --git a/stage2/sys/spl_vm_embed.h b/stage2/sys/spl_vm_embed.h new file mode 100644 index 0000000..d026fdf --- /dev/null +++ b/stage2/sys/spl_vm_embed.h @@ -0,0 +1,109 @@ +/* spl_vm_embed.h - 原生编译器内嵌 VM(comptime 执行)。 + * + * 单 TU 合并式:定义 SPL_VM_EMBED_IMPLIMENTATION 后 include 本头,会把 stage0 的 + * spl_mcode.c / spl_syscall.c / spl_vm.c 一并纳入当前翻译单元(不单独编译/链接)。 + */ + +#ifndef __SPL_VM_EMBED_H__ +#define __SPL_VM_EMBED_H__ + +void *vm_new(void); +void vm_drop(void *vm); +int vm_load_from_buffer(void *vm, const void *buf, unsigned long long len); +void vm_push(void *vm, unsigned long long val); +int vm_call(void *vm, const char *name, unsigned long long nargs); +int vm_run(void *vm); + +#ifdef SPL_VM_EMBED_IMPLIMENTATION + +#include "../../stage0/spl_mcode.h" +void spl_syscall_register(spl_prog_t *prog) { (void)prog; /* stub */ } + +#include "../../stage0/spl_mcode.c" +#include "../../stage0/spl_vm.c" + +void *vm_new(void) { + spl_vm_t *vm = (spl_vm_t *)malloc(sizeof(spl_vm_t)); + if (!vm) + return 0; + spl_vm_init(vm); + return vm; +} + +void vm_drop(void *p) { + spl_vm_t *vm = (spl_vm_t *)p; + if (!vm) + return; + if (vm->prog) { + spl_prog_drop(vm->prog); + free(vm->prog); + vm->prog = 0; + } + spl_vm_drop(vm); + free(vm); +} + +int vm_load_from_buffer(void *p, const void *buf, unsigned long long len) { + spl_vm_t *vm = (spl_vm_t *)p; + spl_prog_t *prog; + if (!vm || !buf) + return -1; + prog = (spl_prog_t *)malloc(sizeof(spl_prog_t)); + if (!prog) + return -1; + if (spl_prog_load_from_buffer((const char *)buf, (usize)len, prog) != 0) { + free(prog); + return -1; + } + spl_syscall_register(prog); + if (spl_vm_load_prog(vm, prog) != 0) { + spl_prog_drop(prog); + free(prog); + return -1; + } + return 0; +} + +void vm_push(void *p, unsigned long long val) { + spl_vm_t *vm = (spl_vm_t *)p; + if (!vm) + return; + if (vm->sp >= vm->config.max_stack_depth) + return; + vm->stacks.data[vm->sp++] = (spl_vm_val_t)val; +} + +int vm_call(void *p, const char *name, unsigned long long nargs) { + spl_vm_t *vm = (spl_vm_t *)p; + spl_vm_func_t *func; + if (!vm || !name) + return -1; + func = spl_prog_get_func(vm->prog, name); + if (!func) + return -1; + if (vm->cp >= vm->config.max_call_depth) + return -1; + /* 入口调用:ip=-1 作 sentinel;实参由 vm_push 压栈。 */ + vm->ip = (usize)-1; + vm->cp = 0; + vm->fp = 0; + vm->frames.data[0].saved_sp = vm->sp - (usize)nargs; + vm->frames.data[0].saved_fp = vm->fp; + vm->frames.data[0].saved_ip = vm->ip; + vm->frames.data[0].nargs = (spl_vm_val_t)nargs; + vm->cp = 1; + vm->ip = (usize)func->address; + vm->fp = vm->sp - (usize)nargs; + return 0; +} + +int vm_run(void *p) { + spl_vm_t *vm = (spl_vm_t *)p; + if (!vm) + return -1; + spl_vm_run_until(vm, 0); + return (int)vm->exit_code; +} +#endif /* SPL_VM_EMBED_IMPLIMENTATION */ + +#endif /* __SPL_VM_EMBED_H__ */ diff --git a/stage2/sys/vm.spl b/stage2/sys/vm.spl index 71d49cb..caa10f6 100644 --- a/stage2/sys/vm.spl +++ b/stage2/sys/vm.spl @@ -5,7 +5,7 @@ type spl_vm_val_t = u64; // usize #extern(vm) fn vm_drop(vm: *any): void; #extern(vm) fn vm_load(vm: *any, fpath: *u8): void; -#extern(vm) fn vm_load_from_buffer(vm: *any, buffer: []u8): void; +#extern(vm) fn vm_load_from_buffer(vm: *any, buf: *any, len: usize): i32; #extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void; // TODO diff --git a/tests/stage1/001_main.spl b/tests/stage1/001_main.spl new file mode 100644 index 0000000..39151d6 --- /dev/null +++ b/tests/stage1/001_main.spl @@ -0,0 +1,5 @@ +// Expect retcode = 0 + +fn main(): i32 { + ret 0; +} diff --git a/tests/stage1/002_ident.spl b/tests/stage1/002_ident.spl new file mode 100644 index 0000000..80c2628 --- /dev/null +++ b/tests/stage1/002_ident.spl @@ -0,0 +1,6 @@ +// Expect retcode = 1 + +fn main(): i32 { + var x: i32 = 1; + ret x; +} diff --git a/tests/stage1/003_assign.spl b/tests/stage1/003_assign.spl new file mode 100644 index 0000000..ea99ba7 --- /dev/null +++ b/tests/stage1/003_assign.spl @@ -0,0 +1,7 @@ +// Expect retcode = 0 + +fn main(): i32 { + var x: i32 = undefined; + x = 0; + ret x; +} diff --git a/tests/stage1/004_op.spl b/tests/stage1/004_op.spl new file mode 100644 index 0000000..4c5d7d1 --- /dev/null +++ b/tests/stage1/004_op.spl @@ -0,0 +1,10 @@ +// Expect retcode = 42 + +fn main(): i32 { + var x: i32 = undefined; + x = 1; + var y = 6 + x; + x -= y; + y *= x * -1; + ret y; +} diff --git a/tests/stage1/005_call.spl b/tests/stage1/005_call.spl new file mode 100644 index 0000000..3fedf98 --- /dev/null +++ b/tests/stage1/005_call.spl @@ -0,0 +1,10 @@ +// Expect retcode = 6 + +fn add(a: i32, b: i32): i32 { + ret a + b; +} + +fn main(): i32 { + var a = add(1, 2); + ret add(a, 3); +} diff --git a/tests/stage1/006_cond.spl b/tests/stage1/006_cond.spl new file mode 100644 index 0000000..562168a --- /dev/null +++ b/tests/stage1/006_cond.spl @@ -0,0 +1,21 @@ +// Expect retcode = 10 + +fn main(): i32 { + var result: i32 = 0; + var x: i32 = 0; + if x != 0 { + result += 1; + } else { + result += 2; + } + + x = 2; + if x == 1 { + result += 4; + } else if x == 2 { + result += 8; + } else { + result += 16; + } + ret result; +} diff --git a/tests/stage1/007_while.spl b/tests/stage1/007_while.spl new file mode 100644 index 0000000..5df8303 --- /dev/null +++ b/tests/stage1/007_while.spl @@ -0,0 +1,9 @@ +// Expect retcode = 10 + +fn main(): i32 { + var i: i32 = 0; + while i < 10 { + i = i + 1; + } + ret i; +} diff --git a/tests/stage1/008_slice.spl b/tests/stage1/008_slice.spl new file mode 100644 index 0000000..fe6f01c --- /dev/null +++ b/tests/stage1/008_slice.spl @@ -0,0 +1,23 @@ +// Expect retcode = 20 + +fn main(): i32 { + var res: i32 = 0; + var array: [5]i32 = .[ 1, 2, 3, 4, 5 ]; + var slice: []i32 = array[1..3]; + + var i: u64 = undefined; + + i = 0; + while i < array.len { + res += array[i]; + i += 1; + } + + i = 0; + while i < slice.len { + res += slice[i]; + i += 1; + } + + ret res; +} diff --git a/tests/stage1/009_str.spl b/tests/stage1/009_str.spl new file mode 100644 index 0000000..d9a01f0 --- /dev/null +++ b/tests/stage1/009_str.spl @@ -0,0 +1,6 @@ +// Expect retcode = 4 + +fn main(): i32 { + var str = "hello world"; + ret str[2] as i32 - str[0] as i32; +} diff --git a/tests/stage1/010_vmprintf.spl b/tests/stage1/010_vmprintf.spl new file mode 100644 index 0000000..c4e5fc1 --- /dev/null +++ b/tests/stage1/010_vmprintf.spl @@ -0,0 +1,9 @@ +// Expect retcode = 0 +// Expect stdout = `hello vm_printf\n1 0 -1 65536\n` +#extern(vm) fn vm_printf(fmt: []u8, ...); + +fn main(): () { + vm_printf("hello vm_printf\n"); + vm_printf("%d %d %d %d\n", 1, 0, -1, 65536); + vm_printf("%s %d %s\n", "hello world", 666, "hello spl"); +} diff --git a/tests/stage1/011_assert.spl b/tests/stage1/011_assert.spl new file mode 100644 index 0000000..4163942 --- /dev/null +++ b/tests/stage1/011_assert.spl @@ -0,0 +1,11 @@ +// Expect retcode = 0 + +fn main(): i32 { + @assert(0 == 0); + @assert(0 != 1); + @assert(1 != 0); + @assert(1 == 1); + @assert(true); + @assert(!false); + ret 0; +} diff --git a/tests/stage1/012_struct.spl b/tests/stage1/012_struct.spl new file mode 100644 index 0000000..8cb657c --- /dev/null +++ b/tests/stage1/012_struct.spl @@ -0,0 +1,19 @@ +// Expect retcode = 0 + +type Vector2i = struct { + x: isize, + y: isize, +} + +fn main() { + var v = Vector2i {}; + v.x = -2; + v.y = 3; + @assert(v.x * v.y == -6); + @assert((v.x * v.y != -6) == false); + @assert((v.x * v.y * -1) as i32 == 6); + + var a: Vector2i = .{ .y = 2 }; + @assert(a.x == 0); + @assert(a.y == 2); +} diff --git a/tests/stage1/013_enum.spl b/tests/stage1/013_enum.spl new file mode 100644 index 0000000..9f3e18b --- /dev/null +++ b/tests/stage1/013_enum.spl @@ -0,0 +1,21 @@ +// Expect retcode = 0 +// non-optimized + +type Rectangle = struct { + x: isize, + y: isize +} + +type Shape = enum { + square: struct { x: isize }, + rectangle: Rectangle, +} + +fn main() { + var s: Shape = .{ .square = .{ .x = 6 } }; + @assert(s.square.x == 6); + // non-optimized + @assert(s.rectangle.x == 6); + // non-optimized + @assert(s.rectangle.y == 0); +} diff --git a/tests/stage1/014_method.spl b/tests/stage1/014_method.spl new file mode 100644 index 0000000..6d080fb --- /dev/null +++ b/tests/stage1/014_method.spl @@ -0,0 +1,15 @@ +// Expect retcode = 0 + +type Rectangle = struct { + fn area(self: *@self): isize { + ret self.x * self.y; + } + x: isize, + y: isize, +} + +fn main() { + var r = Rectangle { .x = 2, .y = 3 }; + @assert(r.area(&r) == 6, "method call"); + @assert(Rectangle.area(&r) == 6, "type call"); +} diff --git a/tests/stage1/015_order.spl b/tests/stage1/015_order.spl new file mode 100644 index 0000000..b050bec --- /dev/null +++ b/tests/stage1/015_order.spl @@ -0,0 +1,30 @@ +// Expect retcode = 0 + +type Outer = struct { + inner: Inner, + fn total(self: *@self): i32 { + ret self.compute(self) + constant_value(); + } + fn compute(self: *@self): i32 { + ret self.inner.value; + } +} + +type Inner = struct { + value: i32, + fn doubled(self: *@self): i32 { + ret self.value * 2; + } +} + +fn main() { + var i: Inner = .{ .value = 21 }; + @assert(i.doubled(&i) == 42); + + var o: Outer = .{ .inner = i }; + @assert(o.total(&o) == 21 + 7); +} + +fn constant_value(): i32 { + ret 7; +} diff --git a/tests/stage1/016_tuple.spl b/tests/stage1/016_tuple.spl new file mode 100644 index 0000000..568e77f --- /dev/null +++ b/tests/stage1/016_tuple.spl @@ -0,0 +1,12 @@ +// Expect retcode = 0 + +fn main() { + var res = add((1, 2), (3, 4)); + @assert(res[0] == 4); + @assert(res[1] == 6); + @assert(res[1] + res[0] == 10); +} + +fn add(a: (i32, i32), b: (i32, i32)): (i32, i32) { + ret (a[0] + b[0], a[1] + b[1]); +} diff --git a/tests/stage1/017_label.spl b/tests/stage1/017_label.spl new file mode 100644 index 0000000..a205dff --- /dev/null +++ b/tests/stage1/017_label.spl @@ -0,0 +1,54 @@ +// Expect retcode = 0 + +fn main() { + var count: i32 = 0; + $outter while count < 10 { + $inner loop { + count = count + 1; + if count == 3 { + continue $outter; + } + if count == 7 { + break $outter; + } + } + } + @assert(count == 7); + + count = 0; + while count < 10 { + count = count + 1; + if count == 5 { + break; + } + } + @assert(count == 5); + + count = 0; + var sum: i32 = 0; + while count < 5 { + count = count + 1; + if count == 3 { + continue; + } + sum = sum + count; + } + @assert(sum == 12); + + count = 0; + var outer_hits: i32 = 0; + $a loop { + $b loop { + count = count + 1; + if count == 2 { + continue; + } + break; + } + outer_hits = outer_hits + 1; + if outer_hits == 3 { + break; + } + } + @assert(outer_hits == 3); +} diff --git a/tests/stage1/018_defer.spl b/tests/stage1/018_defer.spl new file mode 100644 index 0000000..963c581 --- /dev/null +++ b/tests/stage1/018_defer.spl @@ -0,0 +1,50 @@ +// Expect retcode = 0 + +var g: i32 = 0; + +fn f1() { + defer { g = g + 1; } + defer { g = g * 10; } +} + +fn f2(): i32 { + var x: i32 = 42; + defer { x = 100; } + ret x; +} + +fn f3(): i32 { + defer { g = 999; } + ret 7; +} + +fn f4(): i32 { + var acc: i32 = 0; + acc = acc + 1; + defer { acc = acc + 100; } + acc = acc + 10; + ret acc; +} + +fn main() { + // 1. LIFO:先注册的 defer 最后执行 + g = 0; + f1(); + @assert(g == 1); // 0 * 10 = 0, 0 + 1 = 1 + + // 2. ret 先求值再 defer,defer 改不了返回值 + g = 0; + var r2: i32 = f2(); + @assert(r2 == 42); + @assert(g == 0); + + // 3. defer 在 ret 前执行 + g = 0; + var r3: i32 = f3(); + @assert(r3 == 7); + @assert(g == 999); + + // 4. defer 里的代码对 ret 表达式的结果无影响 + var r4: i32 = f4(); + @assert(r4 == 11); // acc = 1 + 10 = 11 +} \ No newline at end of file diff --git a/tests/stage1/019_null.spl b/tests/stage1/019_null.spl new file mode 100644 index 0000000..74bf035 --- /dev/null +++ b/tests/stage1/019_null.spl @@ -0,0 +1,15 @@ +// Expect retcode = 0 + +fn main() { + var str: []u8 = undefined; + str = "hello world"; + var p: *any = str.ptr; + @assert((p as *u8)[1] == 'e'); + p = null; + @assert(p == null); + p = str.ptr as *u16 + 1; + @assert((p as *u8)[0] == 'l'); + @assert((p as *u8).* == 'l'); + @assert((&(p as *u8)[1])[1] == 'o'); + @assert((&(p as *u8).*)[2] == 'o'); +} diff --git a/tests/stage1/020_import.spl b/tests/stage1/020_import.spl new file mode 100644 index 0000000..d6f7adb --- /dev/null +++ b/tests/stage1/020_import.spl @@ -0,0 +1,9 @@ +// Expect retcode = 0 + +type Rectangle = @import("014_method.spl").Rectangle; + +fn main() { + var r = Rectangle { .x = 2, .y = 3 }; + @assert(r.area(&r) == 6); + @assert(Rectangle.area(&r) == 6); +} diff --git a/tests/stage1/021_selfref.spl b/tests/stage1/021_selfref.spl new file mode 100644 index 0000000..97ba032 --- /dev/null +++ b/tests/stage1/021_selfref.spl @@ -0,0 +1,20 @@ +// Expect retcode = 0 + +type Node = struct { + val: i32, + next: *Node, +}; + +fn sum(n: *Node): i32 { + if n == null { + ret 0; + } + ret n.val + sum(n.next); +} + +fn main() { + var a = Node { .val = 1, .next = null }; + var b = Node { .val = 2, .next = &a }; + var c = Node { .val = 3, .next = &b }; + @assert(sum(&c) == 6); +} \ No newline at end of file diff --git a/tests/stage1/022_multiref.spl b/tests/stage1/022_multiref.spl new file mode 100644 index 0000000..b8f5b71 --- /dev/null +++ b/tests/stage1/022_multiref.spl @@ -0,0 +1,20 @@ +// Expect retcode = 0 + +type A = struct { + tag: i32, + b: *B, +}; + +type B = struct { + tag: i32, + a: *A, +}; + +fn main() { + var a = A { .tag = 1, .b = null }; + var b = B { .tag = 2, .a = &a }; + a.b = &b; + @assert(a.b.tag == 2); + @assert(b.a.tag == 1); + @assert(a.b.a.tag == 1); +} diff --git a/tests/stage1/023_cross_agg.spl b/tests/stage1/023_cross_agg.spl new file mode 100644 index 0000000..3a9c176 --- /dev/null +++ b/tests/stage1/023_cross_agg.spl @@ -0,0 +1,12 @@ +// Expect retcode = 0 + +type Rectangle = @import("014_method.spl").Rectangle; + +fn make_rect(): Rectangle { + ret Rectangle { .x = 5, .y = 7 }; +} + +fn main() { + var r = make_rect(); + @assert(r.area(&r) == 35); +} diff --git a/tests/stage1/024_bind.spl b/tests/stage1/024_bind.spl new file mode 100644 index 0000000..3b96828 --- /dev/null +++ b/tests/stage1/024_bind.spl @@ -0,0 +1,30 @@ +// Expect retcode = 0 + +type Option = enum { + None, + Some: i32, +} + +fn test_unit_variant() { + var x: Option = .{ .None }; + var .None = x; +} + +fn test_payload_variant() { + var x: Option = .{ .Some = 42 }; + var .Some(n) = x; + @assert(n == 42, "some payload"); +} + +fn unwrap_some(o: Option): i32 { + var .Some(n) = o; + ret n; +} + +fn main() { + test_unit_variant(); + test_payload_variant(); + + var x: Option = .{ .Some = 7 }; + @assert(unwrap_some(x) == 7, "cross-fn unwrap"); +} diff --git a/tests/stage1/025_ifvar.spl b/tests/stage1/025_ifvar.spl new file mode 100644 index 0000000..f97bb41 --- /dev/null +++ b/tests/stage1/025_ifvar.spl @@ -0,0 +1,67 @@ +// Expect retcode = 0 + +type Option = enum { + None, + Some: i32, +}; + +fn make_pair(): (i32, i32) { ret (1, 2); } +fn make_some(v: i32): Option { ret .Some(v); } + +fn test_ident() { + if var x = 42 { + @assert(x == 42, "ifvar ident x==42"); + } +} + +fn test_tuple() { + if var (a, b) = make_pair() { + @assert(a == 1, "ifvar tuple a==1"); + @assert(b == 2, "ifvar tuple b==2"); + } +} + +fn test_enum_sugar() { + if var .Some(n) = make_some(7) { + @assert(n == 7, "ifvar enum sugar n==7"); + } +} + +fn test_enum_unified() { + if var .{ .Some = n } = make_some(9) { + @assert(n == 9, "ifvar enum unified n==9"); + } +} + +fn test_enum_unit() { + var o: Option = .None; + if var .None = o { + @assert(true, "ifvar enum unit reached"); + } +} + +fn test_else_path() { + var hit = 0; + if var x = 42 { + hit = 1; + } else { + hit = 2; + } + @assert(hit == 1, "ifvar else-path hit==1"); +} + +fn test_typed() { + if var x: i32 = 100 { + @assert(x == 100, "ifvar typed x==100"); + } +} + +fn main() { + test_ident(); + test_tuple(); + test_enum_sugar(); + test_enum_unified(); + test_enum_unit(); + test_else_path(); + test_typed(); +} \ No newline at end of file diff --git a/tests/stage1/026_match.spl b/tests/stage1/026_match.spl new file mode 100644 index 0000000..192defd --- /dev/null +++ b/tests/stage1/026_match.spl @@ -0,0 +1,64 @@ +// Expect retcode = 0 + +type Option = enum { + None, + Some: i32, +}; + +fn test_literal() { + var x: i32 = 2; + var r: i32 = 0; + match x { + 1 => { r = 10; }, + 2 => { r = 20; }, + _ => { r = -1; }, + } + @assert(r == 20, "match literal r==20"); +} + +fn test_wildcard_only() { + var x: i32 = 42; + var r: i32 = 0; + match x { + _ => { r = 1; }, + } + @assert(r == 1, "match wildcard r==1"); +} + +fn test_enum_unit() { + var o: Option = .None; + var r: i32 = 0; + match o { + .None => { r = 1; }, + .Some(n) => { r = n; }, + } + @assert(r == 1, "match enum unit r==1"); +} + +fn test_enum_payload() { + var o: Option = .Some(42); + var r: i32 = 0; + match o { + .None => { r = 0; }, + .Some(n) => { r = n; }, + } + @assert(r == 42, "match enum payload r==42"); +} + +fn test_multi_pattern() { + var x: i32 = 3; + var r: i32 = 0; + match x { + 1, 2, 3 => { r = 123; }, + _ => { r = 0; }, + } + @assert(r == 123, "match multi r==123"); +} + +fn main() { + test_literal(); + test_wildcard_only(); + test_enum_unit(); + test_enum_payload(); + test_multi_pattern(); +} \ No newline at end of file diff --git a/tests/stage1/027_unpack.spl b/tests/stage1/027_unpack.spl new file mode 100644 index 0000000..f0f7125 --- /dev/null +++ b/tests/stage1/027_unpack.spl @@ -0,0 +1,70 @@ +// Expect retcode = 0 + +type LibA = struct { + fn a1(): i32 { ret 1; } +}; + +type LibB = struct { + fn b1(): i32 { ret 10; } + fn b2(): i32 { ret 20; } +}; + +type LibC = struct { + fn c1(): i32 { ret 100; } + fn c2(): i32 { ret 200; } + fn c3(): i32 { ret 300; } +}; + +type LibMixed = struct { + type Byte = u8; + type Word = u16; + fn identity(v: i32): i32 { ret v; } +}; + +type MyInt = i32; + +type .a1 = LibA; + +type .{ .b1, .b2 } = LibB; + +type .c1, .c2, .c3 = LibC; + +type .{ .Byte, .Word, .identity } = LibMixed; + +fn test_alias_regression() { + var x: MyInt = 10; + @assert(x == 10, "type alias MyInt still works"); +} + +fn test_single_unpack() { + @assert(a1() == 1, "single unpack a1()=1"); +} + +fn test_aggregate_unpack() { + @assert(b1() == 10, "aggregate unpack b1()=10"); + @assert(b2() == 20, "aggregate unpack b2()=20"); +} + +fn test_list_unpack() { + @assert(c1() == 100, "list unpack c1()=100"); + @assert(c2() == 200, "list unpack c2()=200"); + @assert(c3() == 300, "list unpack c3()=300"); +} + +fn test_type_and_fn_unpack() { + var b: Byte = 255; + @assert(b == 255, "Byte type unpack works"); + + var w: Word = 1000; + @assert(w == 1000, "Word type unpack works"); + + @assert(identity(42) == 42, "identity unpack works"); +} + +fn main() { + test_alias_regression(); + test_single_unpack(); + test_aggregate_unpack(); + test_list_unpack(); + test_type_and_fn_unpack(); +} diff --git a/tests/stage1/028_self_import.spl b/tests/stage1/028_self_import.spl new file mode 100644 index 0000000..efb0858 --- /dev/null +++ b/tests/stage1/028_self_import.spl @@ -0,0 +1,25 @@ +// Expect retcode = 0 + +type Str = @self; + +ptr: *u8, +len: u64, + +fn empty(): Str { + ret Str { .ptr = null, .len = 0 }; +} + +fn from_str(s: []u8): Str { + ret Str { .ptr = s.ptr, .len = s.len }; +} + +fn size(self: *Str): u64 { + ret self.len; +} + +fn at(self: *Str, idx: u64): u8 { + @assert(idx < self.len); + ret self.ptr[idx]; +} + +fn main() {} diff --git a/tests/stage1/029_self_import_impl.spl b/tests/stage1/029_self_import_impl.spl new file mode 100644 index 0000000..02cd89c --- /dev/null +++ b/tests/stage1/029_self_import_impl.spl @@ -0,0 +1,10 @@ +// Expect retcode = 0 + +type Str = @import("028_self_import.spl"); + +fn main() { + var s = Str.from_str("hello"); + @assert(s.len == 5, "hello len"); + @assert(s.ptr[0] == 104, "h"); + @assert(s.ptr[4] == 111, "o"); +} diff --git a/tests/stage1/030_short_circuit.spl b/tests/stage1/030_short_circuit.spl new file mode 100644 index 0000000..08234cb --- /dev/null +++ b/tests/stage1/030_short_circuit.spl @@ -0,0 +1,55 @@ +// Expect retcode = 0 + +fn rhs_and() : bool { + @assert(false, "right side of && evaluated"); + ret true; +} + +fn rhs_or() : bool { + @assert(false, "right side of || evaluated"); + ret false; +} + +fn main() : i32 { + var a: bool = false && rhs_and(); + if a { + ret 1 + } + + var b: bool = true || rhs_or(); + if !b { + ret 2 + } + + var c: bool = true && true; + if !c { + ret 3 + } + + var d: bool = false || true; + if !d { + ret 4 + } + + var e: bool = false && false; + if e { + ret 5 + } + + var f: bool = true || false; + if !f { + ret 6 + } + + var g: bool = (false || false) && rhs_and(); + if g { + ret 7 + } + + var h: bool = (true && true) || rhs_or(); + if !h { + ret 8 + } + + ret 0 +} diff --git a/tests/stage1/031_fnptr.spl b/tests/stage1/031_fnptr.spl new file mode 100644 index 0000000..6d264dd --- /dev/null +++ b/tests/stage1/031_fnptr.spl @@ -0,0 +1,64 @@ +// Expect retcode = 0 + +// 函数指针 = 函数名本身;fn 类型按可空指针处理(null 表示空函数指针)。 +// 覆盖:裸函数名取值、null 初始化/赋值/比较、聚合字段、函数返回、间接调用。 +// 不覆盖 &: 对函数标识符取地址是编译错误,由负向用例另行验证。 + +type Table = @self; + +hash_fn: fn(*u8): usize, +eq_fn: fn(*u8, *u8): bool, + +fn hash_byte(p: *u8): usize { + ret (p.* as usize) + 1; +} + +fn eq_byte(a: *u8, b: *u8): bool { + ret a.* == b.*; +} + +fn pick_ok(): fn(*u8): usize { + ret hash_byte; +} + +fn pick_null(): fn(*u8): usize { + ret null; +} + +var g_fn: fn(*u8): usize = hash_byte; +var g_table: Table = Table { .hash_fn = hash_byte, .eq_fn = eq_byte }; + +fn main() { + // 首个被翻译的函数地址必须非零(否则会被误判为 null)。 + @assert(hash_byte != null, "first function is not null"); + @assert(pick_ok() != null, "return function is not null"); + + // 局部变量:裸函数名取值、可空。 + var h: fn(*u8): usize = hash_byte; + @assert(h != null, "local function not null"); + @assert(h == g_fn, "local equals global"); + + var n: fn(*u8): usize = null; + @assert(n == null, "null initializes function pointer"); + + h = null; + @assert(h == null, "assign null to function pointer"); + h = hash_byte; + @assert(h != null, "reassign function pointer"); + + // 全局函数指针与聚合字段。 + @assert(g_table.hash_fn != null, "global aggregate field"); + var z = Table { .hash_fn = null, .eq_fn = null }; + @assert(z.hash_fn == null && z.eq_fn == null, "null aggregate fields"); + z.hash_fn = hash_byte; + @assert(z.hash_fn != null, "reassign aggregate field"); + + // 函数返回 null / 函数值。 + @assert(pick_null() == null, "return null function"); + + // 间接调用。 + var x: u8 = 41; + @assert(g_table.hash_fn(&x) == 42, "indirect call via field"); + @assert(h(&x) == 42, "indirect call via local"); + @assert(g_table.eq_fn(&x, &x), "indirect call with two args"); +} diff --git a/tests/stage1/032_enum2int.spl b/tests/stage1/032_enum2int.spl new file mode 100644 index 0000000..ee4d23d --- /dev/null +++ b/tests/stage1/032_enum2int.spl @@ -0,0 +1,21 @@ +// Expect retcode = 0 +// @enum2int:取 enum 的 tag(判别值)。注意 `as` 不允许 enum<->int。 + +type Color = enum { + Red, + Green: i32, + Blue, +} + +fn main() { + var r: Color = .Red; + var g: Color = .Green(7); + var b: Color = .Blue; + + @assert(@enum2int(r) == 0, "Red tag"); + @assert(@enum2int(g) == 1, "Green tag"); + @assert(@enum2int(b) == 2, "Blue tag"); + + var as_usize: usize = @enum2int(b) as usize; + @assert(as_usize == 2, "tag to usize"); +} diff --git a/tests/stage1/033_type_layout.spl b/tests/stage1/033_type_layout.spl new file mode 100644 index 0000000..6dfc0d4 --- /dev/null +++ b/tests/stage1/033_type_layout.spl @@ -0,0 +1,18 @@ +// Expect retcode = 0 +// @offsetof / @field_count / @sizeof / @alignof 全部来自 layout。 + +type P = struct { a: u8, b: usize, c: u32 } +type U = union { x: u8, y: u64 } + +fn main() { + @assert(@field_count(P) == 3, "P field_count"); + @assert(@offsetof(P, a) == 0, "P.a"); + @assert(@offsetof(P, b) == 8, "P.b"); + @assert(@offsetof(P, c) == 16, "P.c"); + @assert(@sizeof(P) == 24, "P size"); + @assert(@alignof(P) == 8, "P align"); + + @assert(@field_count(U) == 2, "U field_count"); + @assert(@offsetof(U, y) == 0, "U.y shared"); + @assert(@sizeof(U) == 8, "U size"); +} diff --git a/tests/stage1/034_rvalue_field.spl b/tests/stage1/034_rvalue_field.spl new file mode 100644 index 0000000..90ad58d --- /dev/null +++ b/tests/stage1/034_rvalue_field.spl @@ -0,0 +1,28 @@ +// Expect retcode = 0 +// 对函数返回的临时结构体取字段(rvalue.field)应可用: +// 直接取值、作为实参、参与运算等。 +// 现状缺陷:ast2ir 对非左值基对象的字段访问返回空节点且不报错,导致静默得到 0。 + +type Pair = struct { + x: i32, + y: i32, +} + +fn make(a: i32, b: i32): Pair { + ret Pair { .x = a, .y = b }; +} + +fn id(x: i32): i32 { + ret x; +} + +fn main(): i32 { + var direct = make(3, 4).y; + @assert(direct == 4, "direct rvalue field"); + + @assert(id(make(5, 6).x) == 5, "rvalue field as argument"); + + @assert(make(1, 2).x + make(3, 4).y == 5, "two rvalues combined"); + + ret 0; +} diff --git a/tests/stage1/035_enum_qualified.spl b/tests/stage1/035_enum_qualified.spl new file mode 100644 index 0000000..441a571 --- /dev/null +++ b/tests/stage1/035_enum_qualified.spl @@ -0,0 +1,20 @@ +// Expect retcode = 0 +// 用「枚举类型名.变体」限定访问枚举值(EnumType.Variant)。 +// 现状缺陷:会被当作普通成员访问,sema 报 no_field: cannot resolve field type。 + +type Color = enum { + Red, + Green, + Blue, +} + +fn main(): i32 { + @assert(@enum2int(Color.Red) == 0, "Red tag"); + @assert(@enum2int(Color.Green) == 1, "Green tag"); + @assert(@enum2int(Color.Blue) == 2, "Blue tag"); + + var c: Color = Color.Blue; + @assert(@enum2int(c) == 2, "qualified assignment"); + + ret 0; +} diff --git a/tests/stage1/101_int.spl b/tests/stage1/101_int.spl new file mode 100644 index 0000000..19ec21c --- /dev/null +++ b/tests/stage1/101_int.spl @@ -0,0 +1,307 @@ +// Expect retcode = 0 + +fn main(): void { + test_literals(); + test_var_types(); + test_arith(); + test_bitwise(); + test_shift(); + test_cmp(); + test_compound(); + test_as_trunc_ext(); + test_min_max(); + test_unsigned_wrap_free(); +} + +fn test_literals() { + // 十进制 + @assert(0 == 0, "dec 0"); + @assert(42 == 42, "dec 42"); + @assert(1234567 == 1234567, "dec 1234567"); + + // 十六进制 + @assert(0x0 == 0, "hex 0"); + @assert(0xFF == 255, "hex FF"); + @assert(0xDEAD == 57005, "hex DEAD"); + @assert(0x7FFFFFFF == 2147483647, "hex i32max"); + + // 八进制 + @assert(0o0 == 0, "oct 0"); + @assert(0o7 == 7, "oct 7"); + @assert(0o52 == 42, "oct 52"); + + // 二进制 + @assert(0b0 == 0, "bin 0"); + @assert(0b101 == 5, "bin 101"); + @assert(0b101010 == 42, "bin 101010"); + + // 下划线分隔 + @assert(1_000 == 1000, "1_000"); + @assert(1_000_000 == 1000000, "1_000_000"); + @assert(0xFF_FF == 65535, "hex underline"); + + // 负字面量 + @assert(-1 == 0 - 1, "-1"); + @assert(-42 == 0 - 42, "-42"); +} + +fn test_var_types() { + var a: i8 = 42; + var b: i16 = 42; + var c: i32 = 42; + var d: i64 = 42; + var e: u8 = 42; + var f: u16 = 42; + var g: u32 = 42; + var h: u64 = 42; + var i: isize = 42; + var j: usize = 42; + + @assert(a == 42, "i8"); + @assert(b == 42, "i16"); + @assert(c == 42, "i32"); + @assert(d == 42, "i64"); + @assert(e == 42, "u8"); + @assert(f == 42, "u16"); + @assert(g == 42, "u32"); + @assert(h == 42, "u64"); + @assert(i == 42, "isize"); + @assert(j == 42, "usize"); + + // 类型推断 + var k = 100; + @assert(k == 100, "infer"); +} + +fn test_arith() { + @assert(2 + 3 == 5, "add"); + @assert(10 - 3 == 7, "sub"); + @assert(6 * 7 == 42, "mul"); + @assert(20 / 4 == 5, "div"); + @assert(10 % 3 == 1, "rem"); + + // 优先级 + @assert(2 + 3 * 4 == 14, "prec 1"); + @assert((2 + 3) * 4 == 20, "parens"); + @assert(10 - 3 - 2 == 5, "left assoc sub"); + @assert(100 / 10 / 2 == 5, "left assoc div"); + @assert(2 * 3 % 4 == 2, "mul/rem left"); + + // 带类型 + var x: i32 = 10; + var y: i32 = 3; + @assert(x + y == 13, "i32 add"); + @assert(x - y == 7, "i32 sub"); + @assert(x * y == 30, "i32 mul"); + @assert(x / y == 3, "i32 div"); + @assert(x % y == 1, "i32 rem"); + + // 负数除法:向零截断 + var n: i32 = -7; + @assert(n / 3 == -2, "neg div"); + @assert(n % 3 == -1, "neg rem"); + @assert(7 / (0 - 3) == -2, "div neg"); + @assert(7 % (0 - 3) == 1, "rem neg"); + @assert(n / (0 - 3) == 2, "neg div neg"); + @assert(n % (0 - 3) == -1, "neg rem neg"); + + // 恒等式:(a/b)*b + a%b == a + var a: i32 = -17; + var b: i32 = 5; + @assert(a == (a / b) * b + a % b, "divrem identity"); +} + +fn test_bitwise() { + @assert((0b1100 & 0b1010) == 0b1000, "and"); + @assert((0b1100 | 0b1010) == 0b1110, "or"); + @assert((0b1100 ^ 0b1010) == 0b0110, "xor"); + + var x: i32 = 0x12345678; + @assert((x & x) == x, "x&x"); + @assert((x | x) == x, "x|x"); + @assert((x ^ x) == 0, "x^x"); + @assert((x & 0) == 0, "x&0"); + @assert((x | 0) == x, "x|0"); + @assert((x ^ 0) == x, "x^0"); + + // 全 1 掩码 + @assert((x & (0 - 1)) == x, "x&all1"); + @assert((x | (0 - 1)) == (0 - 1), "x|all1"); + + // NOT + @assert(~0 == 0 - 1, "~0"); + @assert(~(0 - 1) == 0, "~-1"); + + var y: i32 = 0; + @assert(~y == 0 - 1, "~i32 0"); + + // 无符号 + var u: u32 = 0xF0F0F0F0; + @assert((u & 0xFF) == 0xF0, "u32 & mask"); + @assert((u | 0) == u, "u32 | 0"); +} + +fn test_shift() { + var one: i32 = 1; + @assert((one << 0) == 1, "shl 0"); + @assert((one << 1) == 2, "shl 1"); + @assert((one << 4) == 16, "shl 4"); + @assert((one << 30) == 1073741824, "shl 30"); + + // 算术右移:保留符号 + var neg1: i32 = 0 - 1; + @assert((neg1 >> 1) == (0 - 1), "-1 >> 1"); + @assert(((0 - 2) >> 1) == (0 - 1), "-2 >> 1"); + @assert(((0 - 8) >> 2) == (0 - 2), "-8 >> 2"); + + // 正数右移 + @assert((8 >> 1) == 4, "8 >> 1"); + @assert((1 >> 1) == 0, "1 >> 1"); + + // 无符号逻辑右移 + var u: u32 = 0x80000000; + @assert((u >> 1) == 0x40000000, "u32 >> 1"); + + // i64 + var big: i64 = 1; + @assert((big << 40) == 1099511627776, "i64 << 40"); + @assert((big << 62) == 4611686018427387904, "i64 << 62"); +} + +fn test_cmp() { + @assert(1 < 2, "1<2"); + @assert(1 <= 1, "1<=1"); + @assert(2 > 1, "2>1"); + @assert(2 >= 2, "2>=2"); + @assert(1 == 1, "1==1"); + @assert(1 != 2, "1!=2"); + @assert(!(2 < 1), "!(2<1)"); + @assert(!(1 <= 0), "!(1<=0)"); + + // 有符号 + var a: i32 = 0 - 1; + var b: i32 = 1; + @assert(a < b, "-1 < 1"); + @assert(b > a, "1 > -1"); + + // 无符号:0xFFFFFFFF 应该 > 1 + var ua: u32 = 0xFFFFFFFF; + var ub: u32 = 1; + @assert(ua > ub, "u32 max > 1"); + @assert(ub < ua, "1 < u32 max"); + + // 跨类型比较:comptime 与 typed + var c: i32 = 42; + @assert(c == 42, "i32 == literal"); + @assert(c > 41, "i32 > literal"); + @assert(c <= 42, "i32 <= literal"); +} + +fn test_compound() { + var x: i32 = 10; + x += 5; @assert(x == 15, "+= "); + x -= 3; @assert(x == 12, "-="); + x *= 2; @assert(x == 24, "*="); + x /= 4; @assert(x == 6, "/="); + x %= 4; @assert(x == 2, "%="); + x <<= 2; @assert(x == 8, "<<="); + x >>= 1; @assert(x == 4, ">>="); + x &= 6; @assert(x == 4, "&="); + x |= 1; @assert(x == 5, "|="); + x ^= 3; @assert(x == 6, "^="); +} + +fn test_as_trunc_ext() { + // 窄化:截断到低位(补码) + var a: i32 = 1000; + var a8: i8 = a as i8; + // 1000 & 0xFF = 0xE8 = -24 (i8) + @assert(a8 == (0 - 24), "i32 1000 -> i8 -24"); + + var b: i32 = 127; + var b8: i8 = b as i8; + @assert(b8 == 127, "127 -> i8"); + + var c: i32 = 128; + var c8: i8 = c as i8; + @assert(c8 == 0 - 127 - 1, "128 -> i8 -128"); + + // 宽化有符号:符号扩展 + var d8: i8 = 0 - 1; + var d64: i64 = d8 as i64; + @assert(d64 == (0 - 1), "i8 -1 -> i64 -1"); + + var e8: i8 = 0 - 127 - 1; + var e32: i32 = e8 as i32; + @assert(e32 == (0 - 128), "i8 -128 -> i32 -128"); + + // 宽化无符号:零扩展 + var f8: u8 = 255; + var f32: i32 = f8 as i32; + @assert(f32 == 255, "u8 255 -> i32 255"); + + var g8: u8 = 200; + var g64: i64 = g8 as i64; + @assert(g64 == 200, "u8 200 -> i64 200"); + + // i8 -> u8 位模式不变 + var h8: i8 = 0 - 1; + var h8u: u8 = h8 as u8; + @assert(h8u == 255, "i8 -1 -> u8 255"); + + // 组合截断+扩展 + var k: i64 = 0x1_0000_0001; + var k32: i32 = k as i32; + @assert(k32 == 1, "i64 -> i32 trunc"); +} + +fn test_min_max() { + // i32 边界 + var max: i32 = 2147483647; + var min: i32 = (0 - 2147483647) - 1; + @assert(max == 0x7FFFFFFF, "i32 max"); + @assert(min == 0 - 2147483647 - 1, "i32 min"); + + // 边界比较 + @assert(max > min, "max > min"); + @assert(max >= max, "max >= max"); + @assert(min <= min, "min <= min"); + + // -max 与 -max-1 + @assert((0 - max) == (0 - 2147483647), "-max"); + @assert(((0 - max) - 1) == min, "-max-1 == min"); + + // i8 边界 + var i8max: i8 = 127; + var i8min: i8 = (0 - 127) - 1; + @assert(i8max == 127, "i8 max"); + @assert(i8min == 0 - 127 - 1, "i8 min"); + + // u8 边界 + var u8max: u8 = 255; + @assert(u8max == 255, "u8 max"); + @assert((u8max as i32) == 255, "u8 max as i32"); + + // i64 边界 + var i64max: i64 = 9223372036854775807; + @assert(i64max == 9223372036854775807, "i64 max"); +} + +fn test_unsigned_wrap_free() { + // 无符号运算不涉及负数 + var a: u32 = 0xFFFFFFFF; + var b: u32 = 1; + + // a + b 会溢出 u32,但作为字面量+变量组合,我们只测能表达的 + @assert((a & 0xFFFFFF00) == 0xFFFFFF00, "u32 mask"); + @assert((a | b) == 0xFFFFFFFF, "u32 |"); + @assert((a ^ a) == 0, "u32 ^ self"); + @assert((a >> 1) == 0x7FFFFFFF, "u32 >> 1"); + @assert((b << 31) == 0x80000000, "u32 << 31"); + + // usize / isize + var i: isize = 0 - 1; + @assert(i < 0, "isize -1"); + var u: usize = 100; + @assert(u > 50, "usize 100 > 50"); +} \ No newline at end of file diff --git a/tests/stage1/102_bool.spl b/tests/stage1/102_bool.spl new file mode 100644 index 0000000..2dad93a --- /dev/null +++ b/tests/stage1/102_bool.spl @@ -0,0 +1,79 @@ +// Expect retcode = 0 + +fn main(): void { + test_literals(); + test_not(); + test_and_or(); + test_cmp_result(); + test_assign(); +} + +fn test_literals() { + var t: bool = true; + var f: bool = false; + @assert(t, "true"); + @assert(!f, "false"); + @assert(t == true, "t == true"); + @assert(f == false, "f == false"); + @assert(t != f, "t != f"); +} + +fn test_not() { + @assert(!true == false, "!true"); + @assert(!false == true, "!false"); + var t: bool = true; + @assert(!t == false, "!t"); + @assert(!!t == true, "!!t"); +} + +fn test_and_or() { + // 纯表达式,不测副作用 + @assert(true && true, "T && T"); + @assert(!(true && false), "!(T && F)"); + @assert(!(false && true), "!(F && T)"); + @assert(!(false && false), "!(F && F)"); + + @assert(true || true, "T || T"); + @assert(true || false, "T || F"); + @assert(false || true, "F || T"); + @assert(!(false || false), "!(F || F)"); + + // 优先级:&& 高于 || + @assert(true || false && false, "T || (F && F)"); + @assert((true || false) && false == false, "(T||F) && F == F"); + + // 组合 + var a: i32 = 5; + var b: i32 = 10; + @assert(a < b && b < 20, "a b || b > 5, "a>b || b>5"); + @assert(a == 5 && b == 10, "both eq"); +} + +fn test_cmp_result() { + // 比较返回 bool + var x: bool = 1 < 2; + @assert(x, "1<2 as bool"); + var y: bool = 1 == 2; + @assert(!y, "1==2 as bool"); + + var a: i32 = 5; + var b: bool = a > 0; + @assert(b, "a > 0"); + var c: bool = a == 5; + @assert(c, "a == 5"); + + // bool 变量作为条件 + if x { } else { @assert(false, "if x"); } + if !y { } else { @assert(false, "if !y"); } +} + +fn test_assign() { + var b: bool = false; + b = true; + @assert(b, "assign true"); + b = !b; + @assert(!b, "assign !b"); + b = 1 < 2; + @assert(b, "assign cmp"); +} \ No newline at end of file diff --git a/tests/stage1/103_ctrl.spl b/tests/stage1/103_ctrl.spl new file mode 100644 index 0000000..492826a --- /dev/null +++ b/tests/stage1/103_ctrl.spl @@ -0,0 +1,189 @@ +// Expect retcode = 0 + +fn main(): void { + test_if(); + test_if_else(); + test_while(); + test_while_else(); + test_loop_break(); + test_continue(); + test_nested(); + test_label(); +} + +fn test_if() { + var x: i32 = 10; + if x > 5 { + @assert(true, "x>5"); + } else { + @assert(false, "x>5 else"); + } + + if x < 5 { + @assert(false, "x<5"); + } else { + @assert(true, "x<5 else"); + } + + // if 表达式求值:作为 tail + var y: i32 = if x > 5 { 1 } else { 0 }; + @assert(y == 1, "if expr y"); +} + +fn test_if_else() { + var x: i32 = 5; + if x < 0 { + @assert(false, "x<0"); + } else if x == 0 { + @assert(false, "x==0"); + } else if x == 5 { + @assert(true, "x==5"); + } else { + @assert(false, "else"); + } + + // 嵌套 + if x > 0 { + if x < 10 { + @assert(true, "0=10"); + } + } +} + +fn test_while() { + var i: i32 = 0; + var sum: i32 = 0; + while i < 10 { + sum = sum + i; + i = i + 1; + } + @assert(i == 10, "i==10"); + @assert(sum == 45, "sum 0..9"); + + // 零次循环 + var n: i32 = 0; + while n < 0 { + @assert(false, "never"); + } + @assert(n == 0, "n unchanged"); +} + +fn test_while_else() { + // while 的 else 分支:条件为假时进一次 + var i: i32 = 100; + var hit_else: bool = false; + while i < 10 { + i = i + 1; + } else { + hit_else = true; + } + @assert(hit_else, "while else when cond false initially"); + + var j: i32 = 0; + var hit_else2: bool = false; + while j < 3 { + j = j + 1; + } else { + hit_else2 = true; + } + @assert(j == 3, "j==3"); + @assert(hit_else2, "while else after normal end"); +} + +fn test_loop_break() { + var i: i32 = 0; + loop { + if i >= 5 { break; } + i = i + 1; + } + @assert(i == 5, "loop break"); + + // loop 表达式带 break 值 + var v: i32 = loop { + break 42; + }; + @assert(v == 42, "loop break value"); +} + +fn test_continue() { + var i: i32 = 0; + var sum: i32 = 0; + while i < 10 { + i = i + 1; + if i % 2 == 0 { continue; } + sum = sum + i; + } + // 1 + 3 + 5 + 7 + 9 = 25 + @assert(sum == 25, "sum odds"); + + var j: i32 = 0; + var cnt: i32 = 0; + loop { + j = j + 1; + if j > 100 { break; } + if j % 3 != 0 { continue; } + cnt = cnt + 1; + } + // 3,6,...,99 共 33 个 + @assert(cnt == 33, "multiples of 3 <= 100"); +} + +fn test_nested() { + var i: i32 = 0; + var total: i32 = 0; + while i < 5 { + var j: i32 = 0; + while j < 5 { + total = total + 1; + j = j + 1; + } + i = i + 1; + } + @assert(total == 25, "nested loops"); + + // 嵌套 break 只退一层 + var a: i32 = 0; + var b: i32 = 0; + while a < 3 { + a = a + 1; + while true { + b = b + 1; + break; + } + } + @assert(a == 3, "outer a"); + @assert(b == 3, "inner b"); +} + +fn test_label() { + // 带标签的 break 跳出多层 + var hit: i32 = 0; + $outer while true { + var i: i32 = 0; + while i < 10 { + i = i + 1; + hit = hit + 1; + if hit > 5 { + break $outer; + } + } + } + @assert(hit == 6, "label break hit"); + + // 带标签的 continue + var sum: i32 = 0; + var oi: i32 = 0; + $ol while oi < 3 { + oi = oi + 1; + var ii: i32 = 0; + while ii < 3 { + ii = ii + 1; + if ii == 2 { continue $ol; } + sum = sum + 1; + } + } + // 每个外循环:ii=1 时 sum+1;ii=2 continue;所以 3 次,sum=3 + @assert(sum == 3, "label continue sum"); +} diff --git a/tests/stage1/104_scope.spl b/tests/stage1/104_scope.spl new file mode 100644 index 0000000..7b0dac6 --- /dev/null +++ b/tests/stage1/104_scope.spl @@ -0,0 +1,87 @@ +// Expect retcode = 0 + +fn main(): void { + test_block_scope(); + test_shadowing(); + test_loop_scope(); + test_if_scope(); + test_fn_scope(); +} + +fn test_block_scope() { + var x: i32 = 1; + { + var y: i32 = 2; + @assert(y == 2, "inner y"); + } + // y 已离开作用域,不能再引用 + @assert(x == 1, "outer x"); +} + +fn test_shadowing() { + var x: i32 = 1; + { + var x: i32 = 100; + @assert(x == 100, "shadow inner"); + } + @assert(x == 1, "outer x after block"); + + // if 体里的遮蔽 + var y: i32 = 5; + if true { + var y: i32 = 50; + @assert(y == 50, "if body y"); + } + @assert(y == 5, "outer y after if"); +} + +fn test_loop_scope() { + // 每次迭代都是新作用域 + var i: i32 = 0; + while i < 3 { + var z: i32 = i * 10; + @assert(z == i * 10, "loop z"); + i = i + 1; + } + + // 循环变量在体外不可见:无法直接测,但同名不冲突 + var j: i32 = 0; + while j < 2 { + var j2: i32 = 100; + @assert(j2 == 100, "inner j2"); + j = j + 1; + } +} + +fn test_if_scope() { + var x: i32 = 1; + if true { + var x: i32 = 2; + @assert(x == 2, "if inner x"); + } else { + var x: i32 = 3; + @assert(false, "not reached"); + } + @assert(x == 1, "outer x"); + + // 兄弟分支的变量不冲突 + if true { + var a: i32 = 10; + @assert(a == 10, "then a"); + } else { + var a: i32 = 20; + @assert(false, "not reached"); + } +} + +fn test_fn_scope() { + var r1: i32 = make(5); + var r2: i32 = make(10); + @assert(r1 == 6, "make 5"); + @assert(r2 == 11, "make 10"); +} + +fn make(x: i32): i32 { + var y: i32 = x + 1; + ret y; +} \ No newline at end of file diff --git a/tests/stage1/105_fn.spl b/tests/stage1/105_fn.spl new file mode 100644 index 0000000..39c1af1 --- /dev/null +++ b/tests/stage1/105_fn.spl @@ -0,0 +1,112 @@ +// Expect retcode = 0 + +fn main(): void { + test_void(); + test_multi_args(); + test_recursion(); + test_mutual_recursion(); + test_nested_calls(); +} + +fn test_void() { + do_nothing(); + var r: i32 = compute(); + @assert(r == 42, "compute"); +} + +fn do_nothing() { +} + +fn compute(): i32 { + ret 42; +} + +fn test_multi_args() { + @assert(add3(1, 2, 3) == 6, "add3"); + @assert(sub(10, 3) == 7, "sub"); + @assert(max2(5, 3) == 5, "max2 a"); + @assert(max2(3, 5) == 5, "max2 b"); + @assert(mix(1, 2, 3, 4, 5) == 15, "mix 5 args"); +} + +fn add3(a: i32, b: i32, c: i32): i32 { + ret a + b + c; +} + +fn sub(a: i32, b: i32): i32 { + ret a - b; +} + +fn max2(a: i32, b: i32): i32 { + if a > b { ret a; } + ret b; +} + +fn mix(a: i32, b: i32, c: i32, d: i32, e: i32): i32 { + ret a + b + c + d + e; +} + +fn test_recursion() { + @assert(fact(0) == 1, "fact 0"); + @assert(fact(1) == 1, "fact 1"); + @assert(fact(5) == 120, "fact 5"); + @assert(fact(10) == 3628800, "fact 10"); + + @assert(fib(0) == 0, "fib 0"); + @assert(fib(1) == 1, "fib 1"); + @assert(fib(10) == 55, "fib 10"); + @assert(fib(20) == 6765, "fib 20"); + + @assert(sum_to(100) == 5050, "sum_to 100"); +} + +fn fact(n: i32): i32 { + if n <= 1 { ret 1; } + ret n * fact(n - 1); +} + +fn fib(n: i32): i32 { + if n < 2 { ret n; } + ret fib(n - 1) + fib(n - 2); +} + +fn sum_to(n: i32): i32 { + if n == 0 { ret 0; } + ret n + sum_to(n - 1); +} + +fn test_mutual_recursion() { + @assert(is_even(0), "even 0"); + @assert(is_even(10), "even 10"); + @assert(!is_even(7), "not even 7"); + @assert(is_odd(7), "odd 7"); + @assert(!is_odd(10), "not odd 10"); +} + +fn is_even(n: i32): bool { + if n == 0 { ret true; } + ret is_odd(n - 1); +} + +fn is_odd(n: i32): bool { + if n == 0 { ret false; } + ret is_even(n - 1); +} + +fn test_nested_calls() { + // inc(5) = 6, square(6) = 36, 36 + 5 = 41 + @assert(outer(5) == 41, "outer 5"); + @assert(outer(0) == 1, "outer 0"); // inc(0)=1, square(1)=1, 1+0=1 +} + +fn square(x: i32): i32 { + ret x * x; +} + +fn inc(x: i32): i32 { + ret x + 1; +} + +fn outer(x: i32): i32 { + ret square(inc(x)) + x; +} \ No newline at end of file diff --git a/tests/stage1/106_cast.spl b/tests/stage1/106_cast.spl new file mode 100644 index 0000000..0d0016e --- /dev/null +++ b/tests/stage1/106_cast.spl @@ -0,0 +1,83 @@ +// Expect retcode = 0 + +fn main(): void { + test_i_to_u(); + test_u_to_i(); + test_width_chain(); + test_bool_int(); + test_ptr_int(); +} + +fn test_i_to_u() { + var a: i32 = -1; + var b: u32 = a as u32; + @assert(b == 0xFFFFFFFF, "i32 -1 -> u32 max"); + + var c: i8 = -1; + var d: u8 = c as u8; + @assert(d == 255, "i8 -1 -> u8 255"); + + var e: i64 = -1; + var f: u64 = e as u64; + @assert(f == 0xFFFFFFFFFFFFFFFF, "i64 -1 -> u64 max"); +} + +fn test_u_to_i() { + var a: u32 = 0xFFFFFFFF; + var b: i32 = a as i32; + @assert(b == -1, "u32 max -> i32 -1"); + + var c: u8 = 255; + var d: i8 = c as i8; + @assert(d == -1, "u8 255 -> i8 -1"); + + var e: u8 = 127; + var f: i8 = e as i8; + @assert(f == 127, "u8 127 -> i8 127"); +} + +fn test_width_chain() { + // i8 -> i16 -> i32 -> i64 保持符号 + var a: i8 = -100; + var b: i16 = a as i16; + var c: i32 = b as i32; + var d: i64 = c as i64; + @assert(d == -100, "i8->i64 -100"); + + // 下行截断 + var e: i64 = 0x1_0000_0001; + var f: i32 = e as i32; + @assert(f == 1, "i64->i32 trunc"); + + var g: i16 = 0x1_0000_0001 as i16; + // 0x0001 保留 + @assert(g == 1, "i64->i16 trunc"); +} + +fn test_bool_int() { + var t: bool = true; + var f: bool = false; + + // bool -> int + var a: i32 = t as i32; + var b: i32 = f as i32; + @assert(a == 1, "true as i32"); + @assert(b == 0, "false as i32"); + + var c: u8 = t as u8; + @assert(c == 1, "true as u8"); + + // int -> bool(由 sema 决定是否允许) + // 保守:不测,避免未实现路径 +} + +fn test_ptr_int() { + var x: i32 = 42; + var p: *i32 = &x; + // ptr -> int -> ptr 往返 + var addr: usize = p as usize; + var p2: *i32 = addr as *i32; + @assert(p2.* == 42, "ptr roundtrip"); + + @assert(p == p2, "ptr eq"); +} diff --git a/tests/stage1/107_ptr.spl b/tests/stage1/107_ptr.spl new file mode 100644 index 0000000..941a4f5 --- /dev/null +++ b/tests/stage1/107_ptr.spl @@ -0,0 +1,73 @@ +// Expect retcode = 0 + +fn main(): void { + test_basic(); + test_ptr_arg(); + test_ptr_field(); + test_ptr_arith(); + test_ptr_cmp(); +} + +fn test_basic() { + var x: i32 = 42; + var p: *i32 = &x; + @assert(p.* == 42, "deref"); + + p.* = 100; + @assert(x == 100, "store via ptr"); + + x = 7; + @assert(p.* == 7, "read after store"); +} + +fn test_ptr_arg() { + var x: i32 = 10; + double_it(&x); + @assert(x == 20, "double via ptr"); + + double_it(&x); + @assert(x == 40, "double twice"); +} + +fn double_it(p: *i32) { + p.* = p.* * 2; +} + +fn test_ptr_field() { + // 用 Str 作为例子 + var s: Str = Str { .ptr = null, .len = 0 }; + var ps: *Str = &s; + ps.len = 42; + @assert(s.len == 42, "field via ptr"); + ps.len = 100; + @assert(s.len == 100, "field via ptr again"); +} + +type Str = struct { + ptr: *u8, + len: u64, +} + +fn test_ptr_arith() { + var arr: [5]i32 = .[10, 20, 30, 40, 50]; + var base: *i32 = &arr[0]; + @assert(base.* == 10, "base[0]"); + @assert((base + 1).* == 20, "base[1]"); + @assert((base + 2).* == 30, "base[2]"); + @assert((base + 4).* == 50, "base[4]"); + + var p2: *i32 = base + 3; + @assert(p2.* == 40, "p2"); + var p3: *i32 = p2 - 1; + @assert(p3.* == 30, "p3"); +} + +fn test_ptr_cmp() { + var arr: [3]i32 = .[1, 2, 3]; + var p0: *i32 = &arr[0]; + var p1: *i32 = &arr[1]; + + @assert(p0 != p1, "p0 != p1"); + @assert(p0 == p0, "p0 == p0"); + @assert(p1 == p0 + 1, "p1 == p0+1"); +} \ No newline at end of file diff --git a/tests/stage1/108_struct.spl b/tests/stage1/108_struct.spl new file mode 100644 index 0000000..0d7ff67 --- /dev/null +++ b/tests/stage1/108_struct.spl @@ -0,0 +1,68 @@ +// Expect retcode = 0 + +fn main(): void { + test_construct(); + test_method(); + test_mut_self(); + test_nested(); +} + +type Point = struct { + x: i32, + y: i32, + + fn new(x: i32, y: i32): Point { + ret Point { .x = x, .y = y }; + } + + fn sum(self: *Point): i32 { + ret self.x + self.y; + } + + fn translate(self: *Point, dx: i32, dy: i32) { + self.x = self.x + dx; + self.y = self.y + dy; + } + + fn norm2(self: *Point): i32 { + ret self.x * self.x + self.y * self.y; + } +} + +fn test_construct() { + var p = Point { .x = 3, .y = 4 }; + @assert(p.x == 3, "p.x"); + @assert(p.y == 4, "p.y"); + + var q = Point.new(10, 20); + @assert(q.x == 10, "q.x"); + @assert(q.y == 20, "q.y"); +} + +fn test_method() { + var p = Point.new(3, 4); + @assert(Point.sum(&p) == 7, "sum"); + @assert(Point.norm2(&p) == 25, "norm2"); +} + +fn test_mut_self() { + var p = Point.new(1, 2); + Point.translate(&p, 10, 20); + @assert(p.x == 11, "after translate x"); + @assert(p.y == 22, "after translate y"); +} + +type Line = struct { + a: Point, + b: Point, +} + +fn test_nested() { + var l = Line { + .a = Point.new(0, 0), + .b = Point.new(3, 4), + }; + @assert(l.a.x == 0, "l.a.x"); + @assert(l.b.y == 4, "l.b.y"); + @assert(Point.norm2(&l.b) == 25, "l.b norm2"); +} diff --git a/tests/stage1/109_array.spl b/tests/stage1/109_array.spl new file mode 100644 index 0000000..f0f132e --- /dev/null +++ b/tests/stage1/109_array.spl @@ -0,0 +1,62 @@ +// Expect retcode = 0 + +fn main(): void { + test_literal(); + test_index(); + test_write(); + test_len(); + test_iterate(); +} + +fn test_literal() { + var a: [5]i32 = .[1, 2, 3, 4, 5]; + @assert(a[0] == 1, "a[0]"); + @assert(a[4] == 5, "a[4]"); + + var b: [3]i32 = .[10, 20, 30]; + @assert(b[0] == 10, "b[0]"); + @assert(b[2] == 30, "b[2]"); +} + +fn test_index() { + var a: [5]i32 = .[1, 2, 3, 4, 5]; + var i: i32 = 0; + while i < 5 { + @assert(a[i] == i + 1, "a[i]"); + i = i + 1; + } +} + +fn test_write() { + var a: [3]i32 = .[0, 0, 0]; + a[0] = 100; + a[1] = 200; + a[2] = 300; + @assert(a[0] == 100, "a[0]"); + @assert(a[1] == 200, "a[1]"); + @assert(a[2] == 300, "a[2]"); + + // 通过指针写 + var p: *i32 = &a[1]; + p.* = 999; + @assert(a[1] == 999, "a[1] via ptr"); +} + +fn test_len() { + var a: [5]i32 = .[1, 2, 3, 4, 5]; + @assert(a.len == 5, "a.len"); + + var b: [10]u8 = .[]; + @assert(b.len == 10, "b.len"); +} + +fn test_iterate() { + var a: [5]i32 = .[10, 20, 30, 40, 50]; + var sum: i32 = 0; + var i: u64 = 0; + while i < a.len { + sum = sum + a[i]; + i = i + 1; + } + @assert(sum == 150, "array sum"); +} \ No newline at end of file diff --git a/tests/stage1/110_slice.spl b/tests/stage1/110_slice.spl new file mode 100644 index 0000000..a794df4 --- /dev/null +++ b/tests/stage1/110_slice.spl @@ -0,0 +1,53 @@ +// Expect retcode = 0 + +fn main(): void { + test_from_array(); + test_from_ptr(); + test_from_slice(); + test_fields(); +} + +fn test_from_array() { + var a: [5]i32 = .[10, 20, 30, 40, 50]; + var s: []i32 = a[1..4]; + @assert(s.len == 3, "s.len"); + @assert(s.ptr[0] == 20, "s[0]"); + @assert(s.ptr[1] == 30, "s[1]"); + @assert(s.ptr[2] == 40, "s[2]"); + + // 整段切片 + var full: []i32 = a[0..5]; + @assert(full.len == 5, "full.len"); + @assert(full.ptr[4] == 50, "full[4]"); + + // 空切片 + var e: []i32 = a[2..2]; + @assert(e.len == 0, "empty slice"); +} + +fn test_from_ptr() { + var a: [5]i32 = .[1, 2, 3, 4, 5]; + var p: *i32 = &a[0]; + var s: []i32 = p[0..3]; + @assert(s.len == 3, "ptr slice len"); + @assert(s.ptr[0] == 1, "ptr s[0]"); + @assert(s.ptr[2] == 3, "ptr s[2]"); +} + +fn test_from_slice() { + var a: [10]i32 = .[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var s1: []i32 = a[2..8]; + @assert(s1.len == 6, "s1.len"); + + var s2: []i32 = s1[1..4]; + @assert(s2.len == 3, "s2.len"); + @assert(s2.ptr[0] == 3, "s2[0]"); + @assert(s2.ptr[2] == 5, "s2[2]"); +} + +fn test_fields() { + var a: [5]i32 = .[100, 200, 300, 400, 500]; + var s: []i32 = a[1..4]; + @assert(s.len == 3, "len field"); + @assert(s.ptr[0] == 200, "ptr field via index"); +} \ No newline at end of file diff --git a/tests/stage1/111_str.spl b/tests/stage1/111_str.spl new file mode 100644 index 0000000..7bb4b80 --- /dev/null +++ b/tests/stage1/111_str.spl @@ -0,0 +1,49 @@ +// Expect retcode = 0 + +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; + +type Str = struct { + ptr: *u8, + len: u64, + + fn from_str(s: []u8): Str { + ret Str { .ptr = s.ptr, .len = s.len }; + } + + fn empty(): Str { + ret Str { .ptr = null, .len = 0 }; + } + + fn eq_slice(a: Str, s: []u8): bool { + if a.len != s.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, s.ptr, a.len) == 0; + } + + fn eq(a: Str, b: Str): bool { + if a.len != b.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, b.ptr, a.len) == 0; + } +} + +fn main(): void { + var s = Str.from_str("hello"); + @assert(s.len == 5, "hello len"); + @assert(s.ptr[0] == 104, "h"); + @assert(s.ptr[4] == 111, "o"); + + @assert(Str.eq_slice(s, "hello"), "eq_slice hello"); + @assert(!Str.eq_slice(s, "world"), "!eq_slice world"); + + var e = Str.empty(); + @assert(e.len == 0, "empty len"); + @assert(Str.eq(e, e), "empty eq empty"); + @assert(!Str.eq(s, e), "!eq hello empty"); + + var t = Str.from_str("hello"); + @assert(Str.eq(s, t), "eq hello hello"); + + var u = Str.from_str(""); + @assert(u.len == 0, "empty literal"); +} \ No newline at end of file diff --git a/tests/stage1/112_sret.spl b/tests/stage1/112_sret.spl new file mode 100644 index 0000000..fa0177e --- /dev/null +++ b/tests/stage1/112_sret.spl @@ -0,0 +1,69 @@ +// Expect retcode = 0 + +fn main(): void { + test_single(); + test_nested_ret(); + test_recursive_ret(); + test_field_chain(); +} + +type Pair = struct { + a: i32, + b: i32, +} + +type Nested = struct { + p: Pair, + q: Pair, +} + +fn mk_pair(a: i32, b: i32): Pair { + ret Pair { .a = a, .b = b }; +} + +fn mk_nested(pa: Pair, qb: Pair): Nested { + ret Nested { .p = pa, .q = qb }; +} + +fn sum_pair(p: Pair): i32 { + ret p.a + p.b; +} + +fn test_single() { + var p = mk_pair(3, 4); + @assert(p.a == 3, "p.a"); + @assert(p.b == 4, "p.b"); + @assert(sum_pair(p) == 7, "sum_pair"); +} + +fn test_nested_ret() { + var p = mk_pair(1, 2); + var q = mk_pair(10, 20); + var n = mk_nested(p, q); + @assert(n.p.a == 1, "n.p.a"); + @assert(n.q.b == 20, "n.q.b"); +} + +fn test_recursive_ret() { + var p = countdown_pair(5); + @assert(p.a == 5, "countdown 5 a"); + @assert(p.b == 0, "countdown 5 b"); +} + +fn countdown_pair(n: i32): Pair { + if n == 0 { + ret Pair { .a = 0, .b = 0 }; + } + var sub = countdown_pair(n - 1); + ret Pair { .a = n, .b = sub.b }; +} + +fn test_field_chain() { + var p = mk_pair(7, 8); + var q = mk_pair(9, 10); + var n = mk_nested(p, q); + var s1 = sum_pair(n.p); + var s2 = sum_pair(n.q); + @assert(s1 == 15, "n.p sum"); + @assert(s2 == 19, "n.q sum"); +} \ No newline at end of file diff --git a/tests/stage1/113_vaargs.spl b/tests/stage1/113_vaargs.spl new file mode 100644 index 0000000..3345673 --- /dev/null +++ b/tests/stage1/113_vaargs.spl @@ -0,0 +1,13 @@ +// Expect retcode = 0 + +#extern(vm) fn vm_printf(fmt: []u8, ...); + +fn main(): void { + vm_printf("hello\n"); + vm_printf("num = %d\n", 42); + vm_printf("a=%d b=%d c=%d\n", 1, 2, 3); + vm_printf("char = %c\n", 65); + vm_printf("str = %s\n", "world"); + vm_printf("mixed: %d %s %c\n", 7, "x", 66); + vm_printf("neg = %d\n", 0 - 5); +} \ No newline at end of file diff --git a/tests/stage1/114_memory.spl b/tests/stage1/114_memory.spl new file mode 100644 index 0000000..aaae958 --- /dev/null +++ b/tests/stage1/114_memory.spl @@ -0,0 +1,49 @@ +// Expect retcode = 0 + +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any; +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; + +fn main(): void { + test_alloc(); + test_memcpy(); + test_memcmp(); +} + +fn test_alloc() { + var p = vm_alloc(64) as *u8; + @assert(p != null, "alloc"); + + // 写入若干字节 + p[0] = 1; + p[1] = 2; + p[63] = 255; + @assert(p[0] == 1, "p[0]"); + @assert(p[1] == 2, "p[1]"); + @assert(p[63] == 255, "p[63]"); + + vm_free(p); +} + +fn test_memcpy() { + var src: [8]u8 = .[1, 2, 3, 4, 5, 6, 7, 8]; + var dst = vm_alloc(8) as *u8; + vm_memcpy(dst, &src[0], 8); + + var i: u64 = 0; + while i < 8 { + @assert(dst[i] == src[i], "copy byte"); + i = i + 1; + } + vm_free(dst); +} + +fn test_memcmp() { + var a: [4]u8 = .[1, 2, 3, 4]; + var b: [4]u8 = .[1, 2, 3, 4]; + var c: [4]u8 = .[1, 2, 3, 5]; + + @assert(vm_memcmp(&a[0], &b[0], 4) == 0, "a==b"); + @assert(vm_memcmp(&a[0], &c[0], 4) != 0, "a!=c"); +} diff --git a/tests/stage1/115_stress.spl b/tests/stage1/115_stress.spl new file mode 100644 index 0000000..5816ea0 --- /dev/null +++ b/tests/stage1/115_stress.spl @@ -0,0 +1,109 @@ +// Expect retcode = 0 + +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); + +type Node = struct { + val: i32, + next: *Node, + + fn new(v: i32): Node { + ret Node { .val = v, .next = null }; + } +} + +type Tree = struct { + val: i32, + children: []Tree, // 切片间接 +} + +type A = struct { b: *B } +type B = struct { a: *A } // 互相引用 + +fn main(): void { + test_loop_sum(); + test_nested_loops(); + test_deep_recursion(); + test_linked_list(); + test_matrix(); + + var tree: Tree = undefined; + var a: A = undefined; + var b: B = undefined; +} + +fn test_loop_sum() { + var sum: i64 = 0; + var i: i64 = 1; + while i <= 1000 { + sum = sum + i; + i = i + 1; + } + @assert(sum == 500500, "sum 1..1000"); +} + +fn test_nested_loops() { + var total: i64 = 0; + var i: i32 = 0; + while i < 100 { + var j: i32 = 0; + while j < 100 { + total = total + 1; + j = j + 1; + } + i = i + 1; + } + @assert(total == 10000, "100x100"); +} + +fn test_deep_recursion() { + @assert(sum_deep(500) == 125250, "sum_deep 500"); +} + +fn sum_deep(n: i32): i32 { + if n == 0 { ret 0; } + ret n + sum_deep(n - 1); +} + +fn test_linked_list() { + var n3 = Node.new(30); + var n2 = Node.new(20); + var n1 = Node.new(10); + + n1.next = &n2; + n2.next = &n3; + + var total: i32 = 0; + var p: *Node = &n1; + while p != null { + total = total + p.val; + p = p.next; + } + @assert(total == 60, "list sum"); +} + +fn test_matrix() { + var m: [10][10]i32 = .[]; + var i: i32 = 0; + while i < 10 { + var j: i32 = 0; + while j < 10 { + m[i][j] = i * 10 + j; + j = j + 1; + } + i = i + 1; + } + + var sum: i32 = 0; + var a: i32 = 0; + while a < 10 { + var b: i32 = 0; + while b < 10 { + sum = sum + m[a][b]; + b = b + 1; + } + a = a + 1; + } + // 0+1+...+99 = 4950 + @assert(sum == 4950, "matrix sum"); +} diff --git a/tests/stage1/116_defer.spl b/tests/stage1/116_defer.spl new file mode 100644 index 0000000..e216924 --- /dev/null +++ b/tests/stage1/116_defer.spl @@ -0,0 +1,236 @@ +// Expect retcode = 0 + +fn mark(trace: *i32, n: i32) { + trace.* = trace.* * 10 + n; +} + +fn scopes(trace: *i32) { + defer { mark(trace, 4); } + { + defer { mark(trace, 3); } + { + defer { mark(trace, 2); } + defer { mark(trace, 1); } + } + @assert(trace.* == 12, "inner scope LIFO"); + } + @assert(trace.* == 123, "outer scope cleanup"); +} + +fn tail_value() { + var x: i32 = 7; + var result: i32 = { + defer { x = 9; } + x + }; + @assert(result == 7, "tail evaluated before cleanup"); + @assert(x == 9, "tail scope cleaned"); +} + +fn nested_cleanup(trace: *i32) { + defer { + defer { mark(trace, 2); } + mark(trace, 1); + } +} + +type Pair = struct { first: i32, second: i32, } + +fn aggregate_return(): Pair { + var value = Pair { .first = 7, .second = 8 }; + defer { value.first = 99; } + ret value; +} + +fn aggregate_tail() { + var value = Pair { .first = 7, .second = 8 }; + var result: Pair = { + defer { value.first = 99; } + value + }; + @assert(result.first == 7, "aggregate tail snapshot"); + @assert(value.first == 99, "aggregate tail cleanup"); +} + +fn defer_scope() { + var trace: i32 = 0; + scopes(&trace); + @assert(trace == 1234, "each scope cleaned exactly once"); + tail_value(); + trace = 0; + nested_cleanup(&trace); + @assert(trace == 12, "defer within cleanup scope"); + var pair = aggregate_return(); + @assert(pair.first == 7, "aggregate return snapshot"); + @assert(pair.second == 8); + aggregate_tail(); +} + +fn choose(trace: *i32, flag: bool): i32 { + defer { mark(trace, 3); } + if flag { + defer { mark(trace, 1); } + ret 10; + } else { + defer { mark(trace, 2); } + ret 20; + } +} + +fn conditional(trace: *i32, flag: bool) { + if flag { + defer { mark(trace, 1); } + } + mark(trace, 2); +} + +fn early(trace: *i32, flag: bool): i32 { + defer { mark(trace, 3); } + if flag { ret 10; } + ret 20; + defer { mark(trace, 9); } +} + +fn return_value(): i32 { + var x: i32 = 42; + defer { x = 100; } + ret x; +} + +type Choice = enum { Left, Right, } + +fn match_path(trace: *i32, choice: Choice): i32 { + defer { mark(trace, 3); } + match choice { + .Left => { + defer { mark(trace, 1); } + ret 10; + }, + .Right => { + defer { mark(trace, 2); } + ret 20; + }, + } + ret 0; +} + +fn defer_paths() { + var trace: i32 = 0; + @assert(choose(&trace, true) == 10); + @assert(trace == 13, "then return cleanup"); + trace = 0; + @assert(choose(&trace, false) == 20); + @assert(trace == 23, "else return cleanup"); + trace = 0; + conditional(&trace, false); + @assert(trace == 2, "unentered branch has no cleanup"); + trace = 0; + conditional(&trace, true); + @assert(trace == 12, "branch cleanup before following code"); + trace = 0; + @assert(early(&trace, false) == 20); + @assert(trace == 3, "fallthrough retains function cleanup"); + trace = 0; + @assert(early(&trace, true) == 10); + @assert(trace == 3, "early return skips unreachable defer"); + @assert(return_value() == 42, "return evaluated before cleanup"); + trace = 0; + @assert(match_path(&trace, .Left) == 10); + @assert(trace == 13, "left match cleanup"); + trace = 0; + @assert(match_path(&trace, .Right) == 20); + @assert(trace == 23, "right match cleanup"); +} + +fn iterations(trace: *i32) { + var i: i32 = 0; + while i < 3 { + i = i + 1; + defer { mark(trace, i); } + } + @assert(trace.* == 123, "normal iteration cleanup"); +} + +fn jumps(trace: *i32) { + defer { mark(trace, 4); } + var i: i32 = 0; + while i < 4 { + i = i + 1; + defer { mark(trace, i); } + if i == 1 { continue; } + if i == 2 { break; } + } + @assert(trace.* == 12, "continue and break cleanup"); +} + +fn labels(trace: *i32) { + var i: i32 = 0; + $outer while i < 3 { + i = i + 1; + defer { mark(trace, 2); } + loop { + defer { mark(trace, 1); } + if i == 1 { continue $outer; } + break $outer; + } + } + @assert(trace.* == 1212, "label exits clean inner and outer scopes"); +} + +fn inner_break(trace: *i32) { + loop { + defer { mark(trace, 2); } + loop { + defer { mark(trace, 1); } + break; + } + @assert(trace.* == 1, "inner break retains outer cleanup"); + break; + } + @assert(trace.* == 12); +} + +fn break_value() { + var x: i32 = 7; + var result: i32 = loop { + defer { x = 9; } + break x; + }; + @assert(result == 7, "break value evaluated before cleanup"); + @assert(x == 9, "break cleanup executed"); +} + +fn loop_in_cleanup(trace: *i32) { + loop { + defer { + var i: i32 = 0; + while i < 2 { + i = i + 1; + defer { mark(trace, i); } + } + } + break; + } + @assert(trace.* == 12, "cleanup can contain its own loop"); +} + +fn defer_loops() { + var trace: i32 = 0; + iterations(&trace); + trace = 0; + jumps(&trace); + @assert(trace == 124, "function cleanup retained after loop"); + trace = 0; + labels(&trace); + trace = 0; + inner_break(&trace); + break_value(); + trace = 0; + loop_in_cleanup(&trace); +} + +fn main() { + defer_scope(); + defer_paths(); + defer_loops(); +} diff --git a/tests/stage1/117_import_scope.spl b/tests/stage1/117_import_scope.spl new file mode 100644 index 0000000..f974b6c --- /dev/null +++ b/tests/stage1/117_import_scope.spl @@ -0,0 +1,38 @@ +// Expect retcode = 0 +// 导入模块时不能在被导入模块的作用域中提前解析当前模块的函数签名。 + +type imported = @import("028_self_import.spl"); +type QualifiedStr = imported.Str; +type .{ .Str, .from_str } = imported; + +type Result = enum { + Ok: usize, + Err: QualifiedStr, +} + +fn success(): Result { + ret .Ok(42); +} + +fn failure(): Result { + ret .Err(from_str("bad")); +} + +fn qualified(value: QualifiedStr): usize { + ret imported.size(&value); +} + +fn main() { + match success() { + .Ok(value) => { @assert(value == 42); }, + .Err(message) => { @assert(false); }, + } + match failure() { + .Ok(value) => { @assert(false); }, + .Err(message) => { + @assert(message.len == 3); + @assert(message.ptr[0] == 98 as u8); + @assert(qualified(message) == 3); + }, + } +} diff --git a/tests/stage1/118_slice_literal.spl b/tests/stage1/118_slice_literal.spl new file mode 100644 index 0000000..58ff610 --- /dev/null +++ b/tests/stage1/118_slice_literal.spl @@ -0,0 +1,43 @@ +// Expect retcode = 0 +// 切片支持按上下文类型构造 ptr/len 聚合字面量。 + +fn make(data: *u8, length: usize): []u8 { + ret .{ .ptr = data, .len = length }; +} + +type Packet = struct { bytes: []u8, } +type Result = enum { Ok: []u8, Err, } + +fn wrapped(data: *u8): Result { + ret .Ok(.{ .ptr = data, .len = 2 }); +} + +fn main() { + var data: [3]u8 = .[10, 20, 30]; + var direct: []u8 = .{ .len = 3, .ptr = &data[0] }; + @assert(direct.len == 3); + @assert(direct.ptr == &data[0]); + direct.ptr[1] = 21; + @assert(data[1] == 21); + + var returned = make(&data[1], 2); + @assert(returned.len == 2); + @assert(returned.ptr[0] == 21); + @assert(returned.ptr[1] == 30); + + var packet = Packet { .bytes = .{ .ptr = &data[0], .len = 1 } }; + @assert(packet.bytes.len == 1); + @assert(packet.bytes.ptr[0] == 10); + + match wrapped(&data[0]) { + .Ok(bytes) => { + @assert(bytes.len == 2); + @assert(bytes.ptr[1] == 21); + }, + .Err => { @assert(false); }, + } + + direct = .{ .ptr = null, .len = 0 }; + @assert(direct.ptr == null); + @assert(direct.len == 0); +} diff --git a/tests/stage1/119_type_intrinsics.spl b/tests/stage1/119_type_intrinsics.spl new file mode 100644 index 0000000..d1ba0bf --- /dev/null +++ b/tests/stage1/119_type_intrinsics.spl @@ -0,0 +1,18 @@ +// Expect retcode = 0 +// 类型查询内建的参数是类型表达式,不应按运行时标识符解析。 + +type Pair = struct { + left: u8, + right: u32, +} + +fn main() { + @assert(@sizeof(u8) == 1); + @assert(@bitsizeof(u8) == 8); + @assert(@sizeof(i32) == 4); + @assert(@bitsizeof(i32) == 32); + @assert(@alignof(i32) == 4); + @assert(@sizeof(Pair) >= 5); + @assert(@alignof(Pair) >= 1); + @assert(@sizeof((u8, u32)) >= 5); +} diff --git a/tests/stage1/120_import_type_intrinsics.spl b/tests/stage1/120_import_type_intrinsics.spl new file mode 100644 index 0000000..3de00d8 --- /dev/null +++ b/tests/stage1/120_import_type_intrinsics.spl @@ -0,0 +1,11 @@ +// Expect retcode = 0 +// 被导入模块中的类型 intrinsic 必须使用与入口模块相同的参数解析规则。 + +type imported = @import("120_import_type_intrinsics_impl.spl"); + +fn main() { + @assert(imported.int_size() == 4); + @assert(imported.int_alignment() == 4); + @assert(@sizeof(imported.Word) == @sizeof(usize)); + @assert(@alignof(imported.Word) == @alignof(usize)); +} diff --git a/tests/stage1/120_import_type_intrinsics_impl.spl b/tests/stage1/120_import_type_intrinsics_impl.spl new file mode 100644 index 0000000..d7ab86d --- /dev/null +++ b/tests/stage1/120_import_type_intrinsics_impl.spl @@ -0,0 +1,15 @@ +// Expect retcode = 0 + +type Word = struct { + value: usize, +} + +fn int_size(): usize { + ret @sizeof(i32); +} + +fn int_alignment(): usize { + ret @alignof(i32); +} + +fn main() {} diff --git a/tests/stage1/121_ptr_aggregate_assign.spl b/tests/stage1/121_ptr_aggregate_assign.spl new file mode 100644 index 0000000..40b77ee --- /dev/null +++ b/tests/stage1/121_ptr_aggregate_assign.spl @@ -0,0 +1,58 @@ +// Expect retcode = 0 +// 通过指针整体替换聚合体后,调用方必须观察到新值。 + +type Inner = struct { + value: i32, +} + +type Outer = struct { + inner: Inner, + marker: i32, +} + +fn replace(out: *Outer) { + var fresh = Outer { + .inner = Inner { .value = 42 }, + .marker = 99, + }; + out.* = fresh; + @assert(out.inner.value == 42); + @assert(out.marker == 99); +} + +fn replace_inner(out: *Inner) { + var fresh = Inner { .value = 77 }; + out.* = fresh; + @assert(out.value == 77); +} + +fn replace_nested(out: *Outer) { + replace_inner(&out.inner); + @assert(out.inner.value == 77); +} + +fn assign_nested_field(out: *Outer) { + out.inner = Inner { .value = 88 }; + @assert(out.inner.value == 88); +} + +fn main() { + var value = Outer { + .inner = Inner { .value = 1 }, + .marker = 2, + }; + replace(&value); + @assert(value.inner.value == 42, "nested aggregate replacement visible to caller"); + @assert(value.marker == 99, "aggregate replacement field visible to caller"); + + replace_inner(&value.inner); + @assert(value.inner.value == 77, "nested field replacement visible to caller"); + @assert(value.marker == 99, "nested field replacement preserves siblings"); + + value.inner.value = 5; + replace_nested(&value); + @assert(value.inner.value == 77, "transitive nested replacement visible to caller"); + + assign_nested_field(&value); + @assert(value.inner.value == 88, "aggregate field assignment through pointer visible to caller"); +} diff --git a/tests/stage1/122_slice_field_mutation.spl b/tests/stage1/122_slice_field_mutation.spl new file mode 100644 index 0000000..b75d607 --- /dev/null +++ b/tests/stage1/122_slice_field_mutation.spl @@ -0,0 +1,17 @@ +// Expect retcode = 0 +// 局部切片字段修改后,直接读取及后续按值传参都必须使用新值。 + +fn inspect(value: []u8, expected_ptr: *u8) { + @assert(value.len == 3, "mutated slice length passed by value"); + @assert(value.ptr == expected_ptr, "mutated slice pointer passed by value"); +} + +fn main() { + var data: [5]u8 = .[10, 20, 30, 40, 50]; + var view = data[0..5]; + view.ptr = view.ptr + 1; + view.len = 3; + @assert(view.len == 3, "mutated slice length read locally"); + @assert(view.ptr == &data[1], "mutated slice pointer read locally"); + inspect(view, &data[1]); +} diff --git a/tests/stage1/123_import_self_type_unpack.spl b/tests/stage1/123_import_self_type_unpack.spl new file mode 100644 index 0000000..a4a325a --- /dev/null +++ b/tests/stage1/123_import_self_type_unpack.spl @@ -0,0 +1,24 @@ +// Expect retcode = 0 +// 多个模块连续解包时,由 `type Name = @self` 暴露的类型仍是模块根命名空间。 +type .{ .Error, .Status } = @import("../../stage2/utils/error.spl"); +type .{ .checked_add, .move } = @import("../../stage2/utils/memory.spl"); +type .{ .Vec } = @import("../../stage2/utils/vec.spl"); + +type Holder = @self; + +storage: Vec, + +fn main() { + var status: Status = .Ok; + match status { .Ok => {}, .Err(error) => { @assert(false); }, } + var overflow = Error.make(.Overflow); + match overflow.kind { .Overflow => {}, _ => { @assert(false); }, } + + var value = Vec.blank(1, 1); + @assert(value.elem_size == 1); + @assert(value.elem_align == 1); + @assert(value.len == 0 && value.cap == 0); + + var holder = Holder { .storage = value }; + @assert(holder.storage.elem_size == 1); +} diff --git a/tests/stage1/124_import_self_type_namespace.spl b/tests/stage1/124_import_self_type_namespace.spl new file mode 100644 index 0000000..dede19b --- /dev/null +++ b/tests/stage1/124_import_self_type_namespace.spl @@ -0,0 +1,17 @@ +// Expect retcode = 0 +// 解包 @self 类型后,通过类型名访问模块根函数,不需要额外模块别名。 +type .{ .ByteView } = @import("../../stage2/utils/byte_view.spl"); +type .{ .ByteBuffer } = @import("../../stage2/utils/byte_buffer.spl"); + +fn main() { + var view = ByteView.from_slice("view"); + @assert(ByteView.size(view) == 4); + + var buffer = ByteBuffer.new(); + match ByteBuffer.append(&buffer, ByteView.as_slice(view)) { + .Ok => {}, + .Err(error) => { @assert(false); }, + } + @assert(ByteBuffer.size(buffer) == 4); + ByteBuffer.drop(&buffer); +} diff --git a/tests/stage1/125_short_circuit.spl b/tests/stage1/125_short_circuit.spl new file mode 100644 index 0000000..7ed3b35 --- /dev/null +++ b/tests/stage1/125_short_circuit.spl @@ -0,0 +1,21 @@ +// Expect retcode = 0 +// && 和 || 必须短路,未选择的右操作数不能产生副作用。 +fn mark(counter: *i32): bool { + counter.* = counter.* + 1; + ret true; +} + +fn main() { + var count: i32 = 0; + @assert(!(false && mark(&count))); + @assert(count == 0, "false && rhs must not run"); + + @assert(true || mark(&count)); + @assert(count == 0, "true || rhs must not run"); + + @assert(true && mark(&count)); + @assert(count == 1, "true && rhs runs once"); + + @assert(false || mark(&count)); + @assert(count == 2, "false || rhs runs once"); +} diff --git a/tests/stage1/126_boxed_format.spl b/tests/stage1/126_boxed_format.spl new file mode 100644 index 0000000..bab10fc --- /dev/null +++ b/tests/stage1/126_boxed_format.spl @@ -0,0 +1,42 @@ +// Expect retcode = 0 +// 显式装箱格式化:Value ADT + fmt.format({} 风格),数组字面量隐式转 []Value。 + +type .{ .Value, .int, .uint, .char, .str } = @import("../../stage2/utils/value.spl"); +type .{ .format } = @import("../../stage2/utils/fmt.spl"); +type .{ .ByteBuffer } = @import("../../stage2/utils/byte_buffer.spl"); + +fn byte_at(bytes: []u8, index: usize): u8 { + ret bytes.ptr[index]; +} + +fn main(): void { + var out = ByteBuffer.new(); + defer { ByteBuffer.drop(&out); } + + match format(&out, "n={} s={} c={} h={x}", .[int(42), str("hi"), char('!'), uint(255)]) { + .Ok => {}, + .Err(error) => { @assert(false, "format failed"); }, + } + + // 期望 "n=42 s=hi c=! h=ff"(长度 18)。 + var got = ByteBuffer.as_slice(out); + @assert(got.len == 18, "formatted length"); + @assert(byte_at(got, 0) == 'n', "byte 0"); + @assert(byte_at(got, 2) == '4' && byte_at(got, 3) == '2', "decimal"); + @assert(byte_at(got, 7) == 'h' && byte_at(got, 8) == 'i', "string"); + @assert(byte_at(got, 12) == '!', "char"); + @assert(byte_at(got, 16) == 'f' && byte_at(got, 17) == 'f', "hex"); + + // 参数不足:失败不改动已有输出。 + match format(&out, "{} {}", .[int(1)]) { + .Ok => { @assert(false, "expected invalid format"); }, + .Err(error) => { + match error.kind { + .InvalidFormat => {}, + _ => { @assert(false, "expected InvalidFormat"); }, + } + }, + } + got = ByteBuffer.as_slice(out); + @assert(got.len == 18, "failure preserves output"); +} diff --git a/tests/stage1/127_array_slice.spl b/tests/stage1/127_array_slice.spl new file mode 100644 index 0000000..080c73e --- /dev/null +++ b/tests/stage1/127_array_slice.spl @@ -0,0 +1,36 @@ +// Expect retcode = 0 +// 数组 -> 切片:具名数组用 arr[0..] 显式切整段;数组字面量可直接隐式转切片。 + +fn len_of(s: []i32): i32 { + ret s.len as i32; +} + +fn sum(a: []i32): i32 { + var total: i32 = 0; + var i: usize = 0; + while i < a.len { + total = total + a.ptr[i]; + i = i + 1; + } + ret total; +} + +fn main(): void { + var a: [4]i32 = .[1, 2, 3, 4]; + + var whole: []i32 = a[0..]; + @assert(whole.len == 4, "whole slice length"); + @assert(sum(whole) == 10, "whole slice sum"); + @assert(len_of(a[0..]) == 4, "whole slice as argument"); + + var tail: []i32 = a[1..]; + @assert(tail.len == 3, "tail length"); + @assert(tail.ptr[0] == 2 && tail.ptr[2] == 4, "tail contents"); + + var head: []i32 = a[0..2]; + @assert(head.len == 2 && sum(head) == 3, "bounded slice"); + + // 数组字面量在需要切片处隐式物化。 + @assert(sum(.[1, 2, 3]) == 6, "literal to slice sum"); + @assert(len_of(.[9, 8]) == 2, "literal to slice length"); +} diff --git a/tests/stage1/800_arena.spl b/tests/stage1/800_arena.spl new file mode 100644 index 0000000..a4887d6 --- /dev/null +++ b/tests/stage1/800_arena.spl @@ -0,0 +1,64 @@ +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); + +type Arena = struct { + base: *u8, + size: u64, + used: u64, + + fn new(size: u64): Arena { + ret Arena { .base = vm_alloc(size) as *u8, .size = size, .used = 0 }; + } + + fn alloc(self: *Arena, n: u64): *u8 { + var aligned: u64 = (self.used + 7) & (~(7 as u64)); + var n_aligned: u64 = (n + 7) & (~(7 as u64)); + if aligned + n > self.size { ret null; } + var p: *u8 = self.base + aligned; + self.used = aligned + n_aligned; + ret p; + } + fn reset(self: *Arena) { + self.used = 0; + } + + fn free(self: *Arena) { + if self.base != null { vm_free(self.base); } + self.base = null; + self.size = 0; + self.used = 0; + } +} + +fn main() { + var a = Arena.new(256); + @assert(a.base != null); + @assert(a.used == 0); + + var p1 = a.alloc(&a, 16); + @assert(p1 != null); + @assert(a.used == 16); + + var p2 = a.alloc(&a, 24); + @assert(p2 != null); + @assert(p2 > p1); + @assert(a.used == 40); + + /* 对齐检查:下次分配应该从 40 对齐到 40(已对齐) */ + var p3 = a.alloc(&a, 1); + @assert(a.used == 48); + + /* 容量不足 */ + var p4 = a.alloc(&a, 10000); + @assert(p4 == null); + @assert(a.used == 48); + + /* reset 复用 */ + a.reset(&a); + @assert(a.used == 0); + var p5 = a.alloc(&a, 8); + @assert(p5 == p1); + + a.free(&a); + @assert(a.base == null); +} diff --git a/tests/stage1/801_vec.spl b/tests/stage1/801_vec.spl new file mode 100644 index 0000000..621e725 --- /dev/null +++ b/tests/stage1/801_vec.spl @@ -0,0 +1,131 @@ +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any; +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; + +type Vec = struct { + data: *u8, + len: u64, + cap: u64, + elem_size: u64, + + fn new(elem_size: u64): Vec { + ret Vec { .data = null, .len = 0, .cap = 0, .elem_size = elem_size }; + } + + fn with_capacity(elem_size: u64, cap: u64): Vec { + ret Vec { + .data = vm_alloc(cap * elem_size) as *u8, + .len = 0, .cap = cap, .elem_size = elem_size, + }; + } + + fn reserve(self: *Vec, need: u64) { + if need <= self.cap { ret; } + var new_cap: u64 = self.cap; + if self.cap == 0 { + new_cap = 8; + } + while new_cap < need { new_cap = new_cap * 2; } + var new_data: *u8 = vm_alloc(new_cap * self.elem_size) as *u8; + @assert(new_data != null); + if self.data != null { + vm_memcpy(new_data, self.data, self.len * self.elem_size); + vm_free(self.data); + } + self.data = new_data; + self.cap = new_cap; + } + + fn push(self: *Vec, elem: *u8) { + self.reserve(self, self.len + 1); + @assert(self.data != null); + vm_memcpy(self.data + self.len * self.elem_size, elem, self.elem_size); + self.len = self.len + 1; + } + + fn pop(self: *Vec): *u8 { + if self.len == 0 { ret null; } + self.len = self.len - 1; + ret self.data + self.len * self.elem_size; + } + + fn get(self: *Vec, idx: u64): *u8 { + if idx >= self.len { ret null; } + ret self.data + idx * self.elem_size; + } + + fn set(self: *Vec, idx: u64, elem: *u8): bool { + if idx >= self.len { ret false; } + @assert(self.data != null); + vm_memcpy(self.data + idx * self.elem_size, elem, self.elem_size); + ret true; + } + + fn clear(self: *Vec) { + self.len = 0; + } + + fn free(self: *Vec) { + if self.data != null { vm_free(self.data); } + self.data = null; + self.len = 0; + self.cap = 0; + } +} + +fn main() { + var v = Vec.new(4); + @assert(v.len == 0); + @assert(v.cap == 0); + @assert(v.data == null); + + var x: i32 = 10; + var y: i32 = 20; + var z: i32 = 30; + v.push(&v, &x as *u8); + v.push(&v, &y as *u8); + v.push(&v, &z as *u8); + @assert(v.len == 3); + @assert(v.cap >= 3); + + var p = v.get(&v, 1); + @assert(p != null); + @assert((p as *i32).* == 20); + + /* 越界返回 null */ + @assert(v.get(&v, 3) == null); + @assert(v.get(&v, 100) == null); + + /* set */ + var w: i32 = 99; + @assert(v.set(&v, 1, &w as *u8)); + @assert((v.get(&v, 1) as *i32).* == 99); + + /* pop */ + var last = v.pop(&v); + @assert((last as *i32).* == 30); + @assert(v.len == 2); + + /* 空 Vec pop */ + var e = Vec.new(4); + @assert(e.pop(&e) == null); + + /* 扩容压力:1000 次 push */ + var i: i32 = 0; + while i < 1000 { + v.push(&v, &i as *u8); + i = i + 1; + } + @assert(v.len == 1002); + @assert(v.cap >= 1002); + + /* clear */ + v.clear(&v); + @assert(v.len == 0); + @assert(v.cap >= 1002); + + v.free(&v); + @assert(v.data == null); + @assert(v.cap == 0); +} diff --git a/tests/stage1/802_str.spl b/tests/stage1/802_str.spl new file mode 100644 index 0000000..8463a57 --- /dev/null +++ b/tests/stage1/802_str.spl @@ -0,0 +1,136 @@ +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any; +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; + +type Str = struct { + ptr: *u8, + len: u64, + + fn empty(): Str { + ret Str { .ptr = null, .len = 0 }; + } + + fn from_str(s: []u8): Str { + ret Str { .ptr = s.ptr, .len = s.len }; + } + + fn size(self: *Str): u64 { + ret self.len; + } + + fn at(self: *Str, idx: u64): u8 { + @assert(idx < self.len); + ret self.ptr[idx]; + } + + fn eq(a: Str, b: Str): bool { + if a.len != b.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, b.ptr, a.len) == 0; + } + + fn cmp(a: Str, b: Str): i32 { + var n: u64 = undefined; + if a.len < b.len { n = a.len; } else { n = b.len; } + var i: u64 = 0; + while i < n { + var ca = a.ptr[i]; + var cb = b.ptr[i]; + if ca != cb { + ret (ca as i32) - (cb as i32); + } + i = i + 1; + } + if a.len == b.len { + ret 0; + } else if a.len < b.len { + ret -1; + } else { + ret 1; + } + } + + fn slice(self: *Str, start: u64, end: u64): Str { + @assert(start <= end); + @assert(end <= self.len); + ret Str { .ptr = self.ptr + start, .len = end - start }; + } + + fn find(self: *Str, needle: Str): i64 { + if needle.len == 0 { ret 0; } + if needle.len > self.len { ret -1; } + var i: u64 = 0; + while i + needle.len <= self.len { + if vm_memcmp(self.ptr + i, needle.ptr, needle.len) == 0 { + ret i as i64; + } + i = i + 1; + } + ret -1; + } + + fn starts_with(self: *Str, prefix: Str): bool { + if prefix.len > self.len { ret false; } + if prefix.len == 0 { ret true; } + ret vm_memcmp(self.ptr, prefix.ptr, prefix.len) == 0; + } + + fn ends_with(self: *Str, suffix: Str): bool { + if suffix.len > self.len { ret false; } + if suffix.len == 0 { ret true; } + ret vm_memcmp(self.ptr + (self.len - suffix.len), suffix.ptr, suffix.len) == 0; + } +} + +fn main(): void { + var a = Str.from_str("hello"); + @assert(a.len == 5, "from_str('hello').len should be 5"); + @assert(a.ptr[0] == 104 as u8, "from_str('hello').ptr[0] should be 'h'"); + + var b = Str.from_str("hello"); + @assert(Str.eq(a, b), "eq('hello','hello')"); + + var c = Str.from_str("world"); + @assert(!Str.eq(a, c), "!eq('hello','world')"); + @assert(Str.cmp(a, c) < 0, "cmp('hello','world') < 0"); + @assert(Str.cmp(c, a) > 0, "cmp('world','hello') > 0"); + @assert(Str.cmp(a, b) == 0, "cmp('hello','hello') == 0"); + + var empty = Str.empty(); + @assert(empty.len == 0, "empty.len == 0"); + @assert(Str.eq(empty, empty), "eq(empty,empty)"); + @assert(!Str.eq(a, empty), "!eq('hello',empty)"); + + var sub = Str.slice(&a, 1, 3); + @assert(sub.len == 2, "slice(&a,1,3).len == 2"); + @assert(sub.ptr[0] == 101 as u8, "slice[0] == 'e'"); + @assert(sub.ptr[1] == 108 as u8, "slice[1] == 'l'"); + + var head = Str.slice(&a, 0, 0); + @assert(head.len == 0, "slice(&a,0,0).len == 0"); + + var whole = Str.slice(&a, 0, 5); + @assert(Str.eq(whole, a), "eq(slice(0,5), a)"); + + /* find:临时值先存变量,同时分步断言 */ + var ll = Str.from_str("ll"); + @assert(ll.len == 2, "from_str('ll').len should be 2"); + @assert(ll.ptr != null, "from_str('ll').ptr should not be null"); + + @assert(Str.find(&a, ll) == 2, "find('ll') in 'hello' should be 2"); + @assert(Str.find(&a, Str.from_str("hello")) == 0, "find('hello') in 'hello' should be 0"); + @assert(Str.find(&a, Str.from_str("o")) == 4, "find('o') in 'hello' should be 4"); + @assert(Str.find(&a, Str.from_str("xyz")) == -1, "find('xyz') in 'hello' should be -1"); + @assert(Str.find(&a, Str.empty()) == 0, "find(empty) in 'hello' should be 0"); + + @assert(Str.starts_with(&a, Str.from_str("he")), "starts_with('he')"); + @assert(Str.starts_with(&a, Str.empty()), "starts_with(empty)"); + @assert(!Str.starts_with(&a, Str.from_str("wo")), "!starts_with('wo')"); + @assert(Str.starts_with(&a, Str.from_str("hello")), "starts_with('hello')"); + + @assert(Str.ends_with(&a, Str.from_str("lo")), "ends_with('lo')"); + @assert(!Str.ends_with(&a, Str.from_str("he")), "!ends_with('he')"); + + @assert(Str.at(&a, 4) == 111 as u8, "at(4) should be 'o'"); +} diff --git a/tests/stage1/803_fmt.spl b/tests/stage1/803_fmt.spl new file mode 100644 index 0000000..63dc834 --- /dev/null +++ b/tests/stage1/803_fmt.spl @@ -0,0 +1,274 @@ +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any; +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; + +type Str = struct { + ptr: *u8, + len: u64, + + fn from_str(s: []u8): Str { + ret Str { .ptr = s.ptr, .len = s.len }; + } + + fn eq(a: Str, b: Str): bool { + if a.len != b.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, b.ptr, a.len) == 0; + } + + fn eq_slice(a: Str, s: []u8): bool { + if a.len != s.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, s.ptr, a.len) == 0; + } +} + +type StrBuilder = struct { + buf: *u8, + len: u64, + cap: u64, + + fn new(): StrBuilder { + ret StrBuilder { .buf = null, .len = 0, .cap = 0 }; + } + + fn with_capacity(cap: u64): StrBuilder { + ret StrBuilder { .buf = vm_alloc(cap) as *u8, .len = 0, .cap = cap }; + } + + fn reserve(self: *StrBuilder, need: u64) { + if need <= self.cap { ret; } + var new_cap: u64 = self.cap; + if new_cap == 0 { new_cap = 16; } + while new_cap < need { new_cap = new_cap * 2; } + var nb: *u8 = vm_alloc(new_cap) as *u8; + @assert(nb != null, "StrBuilder.reserve: alloc failed"); + if self.buf != null { + vm_memcpy(nb, self.buf, self.len); + vm_free(self.buf); + } + self.buf = nb; + self.cap = new_cap; + } + + fn append_char(self: *StrBuilder, c: u8) { + self.reserve(self, self.len + 1); + self.buf[self.len] = c; + self.len = self.len + 1; + } + + fn append_bytes(self: *StrBuilder, src: *u8, n: u64) { + if n == 0 { ret; } + self.reserve(self, self.len + n); + vm_memcpy(self.buf + self.len, src, n); + self.len = self.len + n; + } + + fn append_str(self: *StrBuilder, s: Str) { + self.append_bytes(self, s.ptr, s.len); + } + + fn append_slice(self: *StrBuilder, s: []u8) { + self.append_bytes(self, s.ptr, s.len); + } + + fn append_u64(self: *StrBuilder, n: u64, base: u64) { + @assert(base >= 2, "base must be >= 2"); + @assert(base <= 36, "base must be <= 36"); + if n == 0 { + self.append_char(self, 48); + ret; + } + var count: u64 = 0; + var t: u64 = n; + while t > 0 { + t = t / base; + count = count + 1; + } + self.reserve(self, self.len + count); + var i: u64 = count; + var v: u64 = n; + while i > 0 { + i = i - 1; + var d: u64 = v % base; + v = v / base; + var c: u8 = 0; + if d < 10 { + c = (48 + d) as u8; + } else { + c = (97 + d - 10) as u8; + } + self.buf[self.len + i] = c; + } + self.len = self.len + count; + } + + fn append_i64(self: *StrBuilder, n: i64, base: u64) { + if n < 0 { + self.append_char(self, 45); + /* (n as u64) 是补码,0 - x = |n| 在 u64 下正确 */ + self.append_u64(self, 0 - (n as u64), base); + } else { + self.append_u64(self, n as u64, base); + } + } + + fn append_bool(self: *StrBuilder, b: bool) { + if b { + self.append_slice(self, "true"); + } else { + self.append_slice(self, "false"); + } + } + + fn build(self: *StrBuilder): Str { + ret Str { .ptr = self.buf, .len = self.len }; + } + + fn clear(self: *StrBuilder) { + self.len = 0; + } + + fn free(self: *StrBuilder) { + if self.buf != null { vm_free(self.buf); } + self.buf = null; + self.len = 0; + self.cap = 0; + } +} + +/* format 组合:多参数含 slice,避免 varargs 类型问题 */ +fn fmt_kv(b: *StrBuilder, key: []u8, val: i64) { + b.append_slice(b, key); + b.append_slice(b, "="); + b.append_i64(b, val, 10); +} + +fn fmt_kv_hex(b: *StrBuilder, key: []u8, val: u64) { + b.append_slice(b, key); + b.append_slice(b, "="); + b.append_u64(b, val, 16); +} + +fn main() { + /* 1. 空 builder 状态 */ + var b = StrBuilder.new(); + @assert(b.len == 0, "new: len=0"); + @assert(b.cap == 0, "new: cap=0"); + @assert(b.buf == null, "new: buf=null"); + + /* 2. append_slice / append_char / build */ + b.append_slice(&b, "hello"); + @assert(b.len == 5, "append 'hello'"); + b.append_char(&b, 32); + b.append_slice(&b, "world"); + @assert(b.len == 11, "append ' world'"); + + var s = b.build(&b); + @assert(s.len == 11, "build: len"); + @assert(Str.eq_slice(s, "hello world"), "build: content"); + + /* 3. clear 保留 cap */ + b.clear(&b); + @assert(b.len == 0, "clear: len=0"); + @assert(b.cap >= 11, "clear: cap retained"); + + /* 4. 整数格式化 —— 覆盖 i64 正负、u64 零 */ + b.append_i64(&b, 42, 10); + @assert(Str.eq_slice(b.build(&b), "42"), "append 42"); + b.clear(&b); + b.append_i64(&b, -123, 10); + @assert(Str.eq_slice(b.build(&b), "-123"), "append -123"); + b.clear(&b); + b.append_i64(&b, 0, 10); + @assert(Str.eq_slice(b.build(&b), "0"), "append 0"); + b.clear(&b); + b.append_u64(&b, 0, 10); + @assert(Str.eq_slice(b.build(&b), "0"), "append_u64 0"); + + /* 5. 进制转换 */ + b.clear(&b); + b.append_u64(&b, 255, 16); + @assert(Str.eq_slice(b.build(&b), "ff"), "255 base 16"); + b.clear(&b); + b.append_u64(&b, 4096, 16); + @assert(Str.eq_slice(b.build(&b), "1000"), "4096 base 16"); + b.clear(&b); + b.append_u64(&b, 170, 2); + @assert(Str.eq_slice(b.build(&b), "10101010"), "170 base 2"); + b.clear(&b); + b.append_u64(&b, 35, 36); + @assert(Str.eq_slice(b.build(&b), "z"), "35 base 36"); + b.clear(&b); + b.append_u64(&b, 1234567890, 10); + @assert(Str.eq_slice(b.build(&b), "1234567890"), "large decimal"); + + /* 6. bool */ + b.clear(&b); + b.append_bool(&b, true); + @assert(Str.eq_slice(b.build(&b), "true"), "true"); + b.clear(&b); + b.append_bool(&b, false); + @assert(Str.eq_slice(b.build(&b), "false"), "false"); + + /* 7. 边界:空追加不改变状态 */ + b.clear(&b); + b.append_slice(&b, ""); + @assert(b.len == 0, "append empty slice"); + b.append_str(&b, Str.from_str("")); + @assert(b.len == 0, "append empty Str"); + + /* 8. with_capacity 直接分配 */ + var c = StrBuilder.with_capacity(128); + @assert(c.cap >= 128, "with_capacity: cap"); + @assert(c.len == 0, "with_capacity: len"); + @assert(c.buf != null, "with_capacity: buf"); + + /* 9. 压力:1000 次 append 触发多次扩容 */ + var i: u64 = 0; + while i < 1000 { + c.append_char(&c, 88); + i = i + 1; + } + @assert(c.len == 1000, "1000: len"); + @assert(c.cap >= 1000, "1000: cap"); + var cs = c.build(&c); + @assert(cs.len == 1000, "1000: build len"); + @assert(cs.ptr[0] == 88, "1000: ptr[0]"); + @assert(cs.ptr[499] == 88, "1000: ptr[499]"); + @assert(cs.ptr[999] == 88, "1000: ptr[999]"); + + /* 10. 组合 format —— 一次调用混合 append_slice / append_i64 / append_u64 / append_bool */ + c.clear(&c); + c.append_slice(&c, "x="); + c.append_i64(&c, -42, 10); + c.append_slice(&c, " hex="); + c.append_u64(&c, 255, 16); + c.append_slice(&c, " ok="); + c.append_bool(&c, true); + @assert(Str.eq_slice(c.build(&c), "x=-42 hex=ff ok=true"), "combined"); + + /* 11. 顶层 format helper:切片实参与聚合返回混合 */ + c.clear(&c); + fmt_kv(&c, "n", 42); + @assert(Str.eq_slice(c.build(&c), "n=42"), "fmt_kv n=42"); + + c.clear(&c); + fmt_kv_hex(&c, "addr", 0xDEADBEEF); + @assert(Str.eq_slice(c.build(&c), "addr=deadbeef"), "fmt_kv_hex"); + + /* 12. append_str 用 Str 值(非 slice) */ + c.clear(&c); + var tmp = Str.from_str("via Str"); + c.append_str(&c, tmp); + @assert(Str.eq_slice(c.build(&c), "via Str"), "append_str"); + + /* 13. free 清空 */ + c.free(&c); + @assert(c.buf == null, "free: buf"); + @assert(c.cap == 0, "free: cap"); + @assert(c.len == 0, "free: len"); + + b.free(&b); +} diff --git a/tests/stage1/804_log.spl b/tests/stage1/804_log.spl new file mode 100644 index 0000000..228c4b7 --- /dev/null +++ b/tests/stage1/804_log.spl @@ -0,0 +1,336 @@ +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any; +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; +#extern(vm) fn vm_fwrite(ptr: *any, size: usize, n: usize, f: *any): usize; +#extern(vm) fn vm_stderr(): *any; + +type Str = struct { + ptr: *u8, + len: u64, + + fn from_str(s: []u8): Str { + ret Str { .ptr = s.ptr, .len = s.len }; + } + + fn eq_slice(a: Str, s: []u8): bool { + if a.len != s.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, s.ptr, a.len) == 0; + } +} + +type StrBuilder = struct { + buf: *u8, + len: u64, + cap: u64, + + fn new(): StrBuilder { + ret StrBuilder { .buf = null, .len = 0, .cap = 0 }; + } + + fn reserve(self: *StrBuilder, need: u64) { + if need <= self.cap { ret; } + var new_cap: u64 = self.cap; + if new_cap == 0 { new_cap = 16; } + while new_cap < need { new_cap = new_cap * 2; } + var nb: *u8 = vm_alloc(new_cap) as *u8; + @assert(nb != null, "StrBuilder.reserve: alloc failed"); + if self.buf != null { + vm_memcpy(nb, self.buf, self.len); + vm_free(self.buf); + } + self.buf = nb; + self.cap = new_cap; + } + + fn append_slice(self: *StrBuilder, s: []u8) { + if s.len == 0 { ret; } + self.reserve(self, self.len + s.len); + vm_memcpy(self.buf + self.len, s.ptr, s.len); + self.len = self.len + s.len; + } + + fn append_str(self: *StrBuilder, s: Str) { + if s.len == 0 { ret; } + self.reserve(self, self.len + s.len); + vm_memcpy(self.buf + self.len, s.ptr, s.len); + self.len = self.len + s.len; + } + + fn append_char(self: *StrBuilder, c: u8) { + self.reserve(self, self.len + 1); + self.buf[self.len] = c; + self.len = self.len + 1; + } + + fn append_u64(self: *StrBuilder, n: u64) { + if n == 0 { + self.append_char(self, 48); + ret; + } + var t: u64 = n; + var count: u64 = 0; + while t > 0 { + t = t / 10; + count = count + 1; + } + self.reserve(self, self.len + count); + var i: u64 = count; + var v: u64 = n; + while i > 0 { + i = i - 1; + var d: u64 = v % 10; + v = v / 10; + self.buf[self.len + i] = (48 + d) as u8; + } + self.len = self.len + count; + } + + fn build(self: *StrBuilder): Str { + ret Str { .ptr = self.buf, .len = self.len }; + } + + fn clear(self: *StrBuilder) { + self.len = 0; + } + + fn free(self: *StrBuilder) { + if self.buf != null { vm_free(self.buf); } + self.buf = null; + self.len = 0; + self.cap = 0; + } +} + +/* 单条诊断记录:5 个字段各 8 字节 = 40 */ +type DiagRec = struct { + level: u64, + line: u64, + col: u64, + msg_ptr: *u8, + msg_len: u64, +} + +type Logger = struct { + min_level: u64, + diag_buf: *DiagRec, + diag_count: u64, + diag_cap: u64, + err_count: u64, + warn_count: u64, + + fn new(min_level: u64): Logger { + ret Logger { + .min_level = min_level, + .diag_buf = null, + .diag_count = 0, + .diag_cap = 0, + .err_count = 0, + .warn_count = 0, + }; + } + + fn write_tag(self: *Logger, sb: *StrBuilder, level: u64) { + sb.append_slice(sb, "["); + if level == 0 { + sb.append_slice(sb, "\x1B[36mDEBUG\x1B[0m"); + } else if level == 1 { + sb.append_slice(sb, "\x1B[32mINFO \x1B[0m"); + } else if level == 2 { + sb.append_slice(sb, "\x1B[33mWARN \x1B[0m"); + } else if level == 3 { + sb.append_slice(sb, "\x1B[31mERROR\x1B[0m"); + } else { + sb.append_slice(sb, "\x1B[1;31mFATAL\x1B[0m"); + } + sb.append_slice(sb, "] "); + } + + fn emit(self: *Logger, level: u64, msg: []u8) { + if level < self.min_level { ret; } + var sb = StrBuilder.new(); + self.write_tag(self, &sb, level); + sb.append_slice(&sb, msg); + sb.append_slice(&sb, "\n"); + var s = sb.build(&sb); + vm_fwrite(s.ptr, 1, s.len, vm_stderr()); + sb.free(&sb); + } + + fn grow_diag(self: *Logger, need: u64) { + if need <= self.diag_cap { ret; } + var new_cap: u64 = self.diag_cap; + if new_cap == 0 { new_cap = 8; } + while new_cap < need { new_cap = new_cap * 2; } + var nb: *DiagRec = vm_alloc(new_cap * 40) as *DiagRec; + @assert(nb != null, "Logger.grow_diag: alloc failed"); + if self.diag_buf != null { + vm_memcpy(nb, self.diag_buf, self.diag_count * 40); + vm_free(self.diag_buf); + } + self.diag_buf = nb; + self.diag_cap = new_cap; + } + + fn diag(self: *Logger, level: u64, line: u64, col: u64, msg: []u8) { + if level >= 3 { + self.err_count = self.err_count + 1; + } else if level >= 2 { + self.warn_count = self.warn_count + 1; + } + + if level >= self.min_level { + var sb = StrBuilder.new(); + self.write_tag(self, &sb, level); + sb.append_u64(&sb, line); + sb.append_slice(&sb, ":"); + sb.append_u64(&sb, col); + sb.append_slice(&sb, ": "); + sb.append_slice(&sb, msg); + sb.append_slice(&sb, "\n"); + var s = sb.build(&sb); + vm_fwrite(s.ptr, 1, s.len, vm_stderr()); + sb.free(&sb); + } + + self.grow_diag(self, self.diag_count + 1); + var rec: *DiagRec = self.diag_buf + self.diag_count; + rec.level = level; + rec.line = line; + rec.col = col; + var copy: *u8 = null; + if msg.len > 0 { + copy = vm_alloc(msg.len) as *u8; + vm_memcpy(copy, msg.ptr, msg.len); + } + rec.msg_ptr = copy; + rec.msg_len = msg.len; + self.diag_count = self.diag_count + 1; + } + + fn diag_msg_eq(self: *Logger, idx: u64, s: []u8): bool { + if idx >= self.diag_count { ret false; } + var rec: *DiagRec = self.diag_buf + idx; + if rec.msg_len != s.len { ret false; } + if rec.msg_len == 0 { ret true; } + ret vm_memcmp(rec.msg_ptr, s.ptr, rec.msg_len) == 0; + } + + fn dump(self: *Logger) { + var sb = StrBuilder.new(); + sb.append_slice(&sb, "--- diagnostics: "); + sb.append_u64(&sb, self.diag_count); + sb.append_slice(&sb, " entries, "); + sb.append_u64(&sb, self.err_count); + sb.append_slice(&sb, " errors, "); + sb.append_u64(&sb, self.warn_count); + sb.append_slice(&sb, " warnings ---\n"); + var s = sb.build(&sb); + vm_fwrite(s.ptr, 1, s.len, vm_stderr()); + sb.free(&sb); + + var i: u64 = 0; + while i < self.diag_count { + var rec: *DiagRec = self.diag_buf + i; + var sb2 = StrBuilder.new(); + sb2.append_slice(&sb2, " #"); + sb2.append_u64(&sb2, i); + sb2.append_slice(&sb2, " lv="); + sb2.append_u64(&sb2, rec.level); + sb2.append_slice(&sb2, " @"); + sb2.append_u64(&sb2, rec.line); + sb2.append_slice(&sb2, ":"); + sb2.append_u64(&sb2, rec.col); + sb2.append_slice(&sb2, " "); + var msg_str = Str { .ptr = rec.msg_ptr, .len = rec.msg_len }; + sb2.append_str(&sb2, msg_str); + sb2.append_slice(&sb2, "\n"); + var s2 = sb2.build(&sb2); + vm_fwrite(s2.ptr, 1, s2.len, vm_stderr()); + sb2.free(&sb2); + i = i + 1; + } + } + + fn free(self: *Logger) { + var i: u64 = 0; + while i < self.diag_count { + var rec: *DiagRec = self.diag_buf + i; + if rec.msg_ptr != null { + vm_free(rec.msg_ptr); + } + i = i + 1; + } + if self.diag_buf != null { + vm_free(self.diag_buf); + } + self.diag_buf = null; + self.diag_count = 0; + self.diag_cap = 0; + } +} + +fn main(): void { + /* min_level = INFO:DEBUG 被过滤掉但仍累积 */ + var lg = Logger.new(1); + + /* 1. emit 的级别过滤 */ + lg.emit(&lg, 0, "this debug line should be filtered"); + lg.emit(&lg, 1, "starting up"); + lg.emit(&lg, 2, "disk usage at 80%"); + + /* 2. diag 累积各种级别 */ + lg.diag(&lg, 0, 1, 1, "debug diag (output filtered, still recorded)"); + lg.diag(&lg, 2, 10, 5, "unused variable `x`"); + lg.diag(&lg, 3, 20, 12, "type mismatch"); + lg.diag(&lg, 3, 25, 3, "undefined identifier `foo`"); + lg.diag(&lg, 4, 30, 1, "fatal: cannot recover"); + + /* 3. 计数器 */ + @assert(lg.diag_count == 5, "diag_count should be 5"); + @assert(lg.err_count == 3, "err_count should be 3 (2 ERROR + 1 FATAL)"); + @assert(lg.warn_count == 1, "warn_count should be 1"); + + /* 4. 消息内容 —— 触发 diag_msg_eq 里的 memcmp */ + @assert(lg.diag_msg_eq(&lg, 0, "debug diag (output filtered, still recorded)"), "diag0"); + @assert(lg.diag_msg_eq(&lg, 1, "unused variable `x`"), "diag1"); + @assert(lg.diag_msg_eq(&lg, 2, "type mismatch"), "diag2"); + @assert(lg.diag_msg_eq(&lg, 3, "undefined identifier `foo`"), "diag3"); + @assert(lg.diag_msg_eq(&lg, 4, "fatal: cannot recover"), "diag4"); + + /* 5. 越界查询返回 false */ + @assert(!lg.diag_msg_eq(&lg, 5, "nope"), "diag oob"); + + /* 6. 扩容压力:再压 100 条 WARN,触发多次 grow_diag */ + var i: u64 = 0; + while i < 100 { + lg.diag(&lg, 2, i, 0, "warn"); + i = i + 1; + } + @assert(lg.diag_count == 105, "after 100 more: diag_count == 105"); + @assert(lg.warn_count == 101, "after 100 more: warn_count == 101"); + @assert(lg.err_count == 3, "after 100 more: err_count == 3"); + + /* 7. 扩容后老消息仍完整(memcpy 保留) */ + @assert(lg.diag_msg_eq(&lg, 0, "debug diag (output filtered, still recorded)"), "diag0 after grow"); + @assert(lg.diag_msg_eq(&lg, 4, "fatal: cannot recover"), "diag4 after grow"); + @assert(lg.diag_msg_eq(&lg, 100, "warn"), "diag100"); + + /* 8. 打印所有诊断 */ + lg.dump(&lg); + + /* 9. free 清空 */ + lg.free(&lg); + @assert(lg.diag_count == 0, "free: diag_count"); + @assert(lg.diag_cap == 0, "free: diag_cap"); + @assert(lg.diag_buf == null, "free: diag_buf"); + + /* 10. 空 logger 也能安全 dump / free / diag */ + var lg2 = Logger.new(0); + lg2.dump(&lg2); + lg2.diag(&lg2, 2, 1, 1, ""); + @assert(lg2.diag_count == 1, "empty msg diag_count"); + @assert(lg2.diag_msg_eq(&lg2, 0, ""), "empty msg eq"); + lg2.free(&lg2); +} diff --git a/tests/stage1/805_mpa.spl b/tests/stage1/805_mpa.spl new file mode 100644 index 0000000..8a83ddd --- /dev/null +++ b/tests/stage1/805_mpa.spl @@ -0,0 +1,534 @@ +#extern(vm) fn vm_alloc(sz: usize): *any; +#extern(vm) fn vm_free(ptr: *any); +#extern(vm) fn vm_memcpy(dst: *any, src: *any, size: usize): *any; +#extern(vm) fn vm_memcmp(dst: *any, src: *any, size: usize): isize; +#extern(vm) fn vm_fwrite(ptr: *any, size: usize, n: usize, f: *any): usize; +#extern(vm) fn vm_stderr(): *any; + +type Str = struct { + ptr: *u8, + len: u64, + fn from_str(s: []u8): Str { + ret Str { .ptr = s.ptr, .len = s.len }; + } + fn eq_slice(a: Str, s: []u8): bool { + if a.len != s.len { ret false; } + if a.len == 0 { ret true; } + ret vm_memcmp(a.ptr, s.ptr, a.len) == 0; + } + fn to_slice(a: Str): []u8 { + ret a.ptr[0..a.len]; + } +} + +type StrBuilder = struct { + buf: *u8, + len: u64, + cap: u64, + + fn new(): StrBuilder { + ret StrBuilder { .buf = null, .len = 0, .cap = 0 }; + } + fn reserve(self: *StrBuilder, need: u64) { + if need <= self.cap { ret; } + var nc: u64 = self.cap; + if nc == 0 { nc = 16; } + while nc < need { nc = nc * 2; } + var nb = vm_alloc(nc) as *u8; + if self.buf != null { + vm_memcpy(nb, self.buf, self.len); + vm_free(self.buf); + } + self.buf = nb; + self.cap = nc; + } + fn append_slice(self: *StrBuilder, s: []u8) { + if s.len == 0 { ret; } + self.reserve(self, self.len + s.len); + vm_memcpy(self.buf + self.len, s.ptr, s.len); + self.len = self.len + s.len; + } + fn append_char(self: *StrBuilder, c: u8) { + self.reserve(self, self.len + 1); + self.buf[self.len] = c; + self.len = self.len + 1; + } + fn build(self: *StrBuilder): Str { + ret Str { .ptr = self.buf, .len = self.len }; + } + fn free(self: *StrBuilder) { + if self.buf != null { vm_free(self.buf); } + self.buf = null; self.len = 0; self.cap = 0; + } +} + +type MpaInt = struct { + sign: i64, + len: u64, + limbs: [16]u32, + + fn zero(): MpaInt { + ret MpaInt { .sign = 0, .len = 0 }; + } + + fn copy(self: *MpaInt): MpaInt { + var r = MpaInt.zero(); + r.sign = self.sign; + r.len = self.len; + var i: u64 = 0; + while i < self.len { + r.limbs[i] = self.limbs[i]; + i = i + 1; + } + ret r; + } + + fn norm(self: *MpaInt) { + while self.len > 0 { + if self.limbs[self.len - 1] != 0 { break; } + self.len = self.len - 1; + } + if self.len == 0 { self.sign = 0; } + } + + fn from_u64(v: u64): MpaInt { + var m = MpaInt.zero(); + if v == 0 { ret m; } + var lo: u32 = (v & 0xFFFFFFFF) as u32; + var hi: u32 = (v >> 32) as u32; + m.limbs[0] = lo; + if hi == 0 { + m.len = 1; + } else { + m.limbs[1] = hi; + m.len = 2; + } + m.sign = 1; + ret m; + } + + fn from_i64(v: i64): MpaInt { + if v < 0 { + var u: u64 = 0 - (v as u64); + var m = MpaInt.from_u64(u); + m.sign = -1; + ret m; + } + ret MpaInt.from_u64(v as u64); + } + + fn cmp_abs(a: MpaInt, b: MpaInt): i32 { + if a.len > b.len { ret 1; } + if a.len < b.len { ret -1; } + var i: u64 = a.len; + while i > 0 { + i = i - 1; + if a.limbs[i] > b.limbs[i] { ret 1; } + if a.limbs[i] < b.limbs[i] { ret -1; } + } + ret 0; + } + + fn add_abs(a: MpaInt, b: MpaInt): MpaInt { + var r = MpaInt.zero(); + var max_len: u64 = 0; + if a.len > b.len { max_len = a.len; } else { max_len = b.len; } + var carry: u64 = 0; + var i: u64 = 0; + while i < max_len || carry != 0 { + if i >= 16 { break; } + var av: u64 = 0; + var bv: u64 = 0; + if i < a.len { av = a.limbs[i] as u64; } + if i < b.len { bv = b.limbs[i] as u64; } + var s: u64 = av + bv + carry; + r.limbs[i] = (s & 0xFFFFFFFF) as u32; + carry = s >> 32; + i = i + 1; + } + r.len = i; + r.sign = 1; + MpaInt.norm(&r); + ret r; + } + + /* 前提:|a| >= |b| */ + fn sub_abs(a: MpaInt, b: MpaInt): MpaInt { + var r = MpaInt.zero(); + var borrow: i64 = 0; + var i: u64 = 0; + while i < a.len { + var av: i64 = a.limbs[i] as i64; + var bv: i64 = 0; + if i < b.len { bv = b.limbs[i] as i64; } + var d: i64 = av - bv - borrow; + if d < 0 { + d = d + 4294967296; + borrow = 1; + } else { + borrow = 0; + } + r.limbs[i] = (d & 0xFFFFFFFF) as u32; + i = i + 1; + } + r.len = i; + r.sign = 1; + MpaInt.norm(&r); + ret r; + } + + fn add(a: MpaInt, b: MpaInt): MpaInt { + if a.sign == 0 { ret b; } + if b.sign == 0 { ret a; } + if a.sign == b.sign { + var r = MpaInt.add_abs(a, b); + r.sign = a.sign; + ret r; + } + var c: i32 = MpaInt.cmp_abs(a, b); + if c == 0 { ret MpaInt.zero(); } + if c > 0 { + var r = MpaInt.sub_abs(a, b); + r.sign = a.sign; + ret r; + } + var r2 = MpaInt.sub_abs(b, a); + r2.sign = b.sign; + ret r2; + } + + fn sub(a: MpaInt, b: MpaInt): MpaInt { + if b.sign == 0 { ret a; } + var nb = b; + nb.sign = 0 - nb.sign; + ret MpaInt.add(a, nb); + } + + fn neg(a: MpaInt): MpaInt { + var r = a; + if r.sign != 0 { r.sign = 0 - r.sign; } + ret r; + } + + fn mul(a: MpaInt, b: MpaInt): MpaInt { + var r = MpaInt.zero(); + if a.sign == 0 || b.sign == 0 { ret r; } + var i: u64 = 0; + while i < a.len { + var carry: u64 = 0; + var av: u64 = a.limbs[i] as u64; + var j: u64 = 0; + while j < b.len || carry != 0 { + if j >= 16 { break; } + if i + j >= 16 { break; } + var bv: u64 = 0; + if j < b.len { bv = b.limbs[j] as u64; } + var cur: u64 = r.limbs[i + j] as u64; + var prod: u64 = av * bv + cur + carry; + r.limbs[i + j] = (prod & 0xFFFFFFFF) as u32; + carry = prod >> 32; + j = j + 1; + } + i = i + 1; + } + var rl: u64 = a.len + b.len; + if rl > 16 { rl = 16; } + r.len = rl; + r.sign = a.sign * b.sign; + MpaInt.norm(&r); + ret r; + } + + fn eq(a: MpaInt, b: MpaInt): bool { + if a.sign != b.sign { ret false; } + if a.len != b.len { ret false; } + var i: u64 = 0; + while i < a.len { + if a.limbs[i] != b.limbs[i] { ret false; } + i = i + 1; + } + ret true; + } + + fn cmp(a: MpaInt, b: MpaInt): i32 { + if a.sign != b.sign { + if a.sign < b.sign { ret -1; } + ret 1; + } + if a.sign == 0 { ret 0; } + var c: i32 = MpaInt.cmp_abs(a, b); + if a.sign > 0 { ret c; } + ret 0 - c; + } + + fn is_zero(self: *MpaInt): bool { + ret self.len == 0; + } + + fn is_neg(self: *MpaInt): bool { + ret self.sign < 0; + } + + fn bit_length(self: *MpaInt): u64 { + if self.len == 0 { ret 0; } + var top: u32 = self.limbs[self.len - 1]; + var bits: u64 = (self.len - 1) * 32; + while top != 0 { + bits = bits + 1; + top = top >> 1; + } + ret bits; + } + + fn is_exact_pow2(self: *MpaInt, bit_idx: u64): bool { + /* |self| 是否恰好为 2^bit_idx,即仅 bit_idx 处为 1,其余全 0 */ + var limb_idx: u64 = bit_idx / 32; + var bit_in_limb: u64 = bit_idx % 32; + if limb_idx >= self.len { ret false; } + var target_mask: u32 = 1 as u32 << bit_in_limb as u32; + if (self.limbs[limb_idx] & target_mask) == 0 { ret false; } + var i: u64 = 0; + while i < self.len { + var expected: u32 = 0; + if i == limb_idx { expected = target_mask; } + if self.limbs[i] != expected { ret false; } + i = i + 1; + } + ret true; + } + + fn fits_bits(self: *MpaInt, bits: u64, is_signed: bool): bool { + if self.len == 0 { ret true; } + var bl: u64 = MpaInt.bit_length(self); + if !is_signed { + if self.sign < 0 { ret false; } + if bl > bits { ret false; } + ret true; + } + /* 有符号:正数上限 2^(bits-1)-1,负数下限 -2^(bits-1) */ + if self.sign >= 0 { + if bl >= bits { ret false; } + ret true; + } + /* 负数:先按绝对值长度过筛,等于 bits 时再确认是否是精确的 2^(bits-1) */ + if bl > bits { ret false; } + if bl < bits { ret true; } + ret MpaInt.is_exact_pow2(self, bits - 1); + } + + fn trunc_bits(self: *MpaInt, bits: u64, is_signed: bool): MpaInt { + var r = MpaInt.zero(); + var nlimbs: u64 = (bits + 31) / 32; + if nlimbs > self.len { nlimbs = self.len; } + if nlimbs > 16 { nlimbs = 16; } + var i: u64 = 0; + while i < nlimbs { + r.limbs[i] = self.limbs[i]; + i = i + 1; + } + if bits % 32 != 0 && nlimbs > 0 { + var top_bits: u32 = bits as u32 % 32; + var mask: u32 = ((1 as u32) << top_bits) - 1; + r.limbs[nlimbs - 1] = r.limbs[nlimbs - 1] & mask; + } + r.len = nlimbs; + r.sign = 1; + MpaInt.norm(&r); + if r.len == 0 { ret r; } + if is_signed { + var sign_bit_idx: u64 = bits - 1; + var limb_idx: u64 = sign_bit_idx / 32; + var bit_in_limb: u32 = sign_bit_idx as u32 % 32; + if limb_idx < r.len { + var mask2: u32 = (1 as u32) << bit_in_limb; + if (r.limbs[limb_idx] & mask2) != 0 { + r.sign = -1; + } + } + } + ret r; + } + + fn from_str(s: []u8, base: u64): MpaInt { + var r = MpaInt.zero(); + var i: u64 = 0; + var neg: bool = false; + if s.len > 0 { + var c0: u8 = s.ptr[0]; + if c0 == 45 { neg = true; i = 1; } + else if c0 == 43 { i = 1; } + } + var base_m = MpaInt.from_u64(base); + while i < s.len { + var c: u8 = s.ptr[i]; + var d: u64 = 999; + if c >= 48 && c <= 57 { d = (c - 48) as u64; } + else if c >= 97 && c <= 122 { d = (c - 97 + 10) as u64; } + else if c >= 65 && c <= 90 { d = (c - 65 + 10) as u64; } + if d < base { + var md = MpaInt.from_u64(d); + r = MpaInt.mul(r, base_m); + r = MpaInt.add(r, md); + } + i = i + 1; + } + if neg && !MpaInt.is_zero(&r) { r.sign = -1; } + ret r; + } + + fn to_str(self: *MpaInt, base: u64): Str { + var sb = StrBuilder.new(); + if self.len == 0 { + sb.append_char(&sb, 48); + ret sb.build(&sb); + } + var tmp = MpaInt.copy(self); + if tmp.sign < 0 { + sb.append_char(&sb, 45); + tmp.sign = 1; + } + var digits: [256]u8 = .[]; + var nd: u64 = 0; + while tmp.len > 0 { + var rem: u64 = 0; + var i: u64 = tmp.len; + while i > 0 { + i = i - 1; + var cur: u64 = (rem << 32) | (tmp.limbs[i] as u64); + tmp.limbs[i] = (cur / base) as u32; + rem = cur % base; + } + var d: u8 = 0; + if rem < 10 { d = (48 + rem) as u8; } + else { d = (97 + rem - 10) as u8; } + digits[nd] = d; + nd = nd + 1; + if nd >= 256 { break; } + MpaInt.norm(&tmp); + } + var i2: u64 = nd; + while i2 > 0 { + i2 = i2 - 1; + sb.append_char(&sb, digits[i2]); + } + ret sb.build(&sb); + } +} +fn main(): void { + /* 1. 零 */ + var z = MpaInt.zero(); + @assert(MpaInt.is_zero(&z), "zero is zero"); + @assert(MpaInt.bit_length(&z) == 0, "zero bit_length=0"); + @assert(!MpaInt.is_neg(&z), "zero not negative"); + + /* 2. 小整数往返 */ + var one = MpaInt.from_u64(1); + var two = MpaInt.from_u64(2); + var three = MpaInt.from_u64(3); + @assert(MpaInt.eq(MpaInt.add(one, two), three), "1+2=3"); + @assert(MpaInt.bit_length(&three) == 2, "3 bit_length=2"); + + /* 3. 加法的零元 / 交换律 */ + @assert(MpaInt.eq(MpaInt.add(one, z), one), "1+0=1"); + @assert(MpaInt.eq(MpaInt.add(z, one), one), "0+1=1"); + @assert(MpaInt.eq(MpaInt.add(one, two), MpaInt.add(two, one)), "1+2=2+1"); + + /* 4. 减法 */ + @assert(MpaInt.eq(MpaInt.sub(three, one), two), "3-1=2"); + @assert(MpaInt.eq(MpaInt.sub(one, one), z), "1-1=0"); + var neg_one = MpaInt.sub(z, one); + @assert(MpaInt.is_neg(&neg_one), "0-1 is negative"); + @assert(MpaInt.eq(MpaInt.add(neg_one, one), z), "-1+1=0"); + + /* 5. 负数构造 */ + var m5 = MpaInt.from_i64(-5); + var p5 = MpaInt.from_u64(5); + @assert(MpaInt.is_neg(&m5), "-5 is negative"); + @assert(MpaInt.eq(MpaInt.neg(m5), p5), "-(-5)=5"); + @assert(MpaInt.eq(MpaInt.add(m5, p5), z), "-5+5=0"); + @assert(MpaInt.eq(MpaInt.add(m5, MpaInt.from_u64(3)), MpaInt.from_i64(-2)), "-5+3=-2"); + + /* 6. 乘法 */ + var k = MpaInt.from_u64(1000000); + @assert(MpaInt.eq(MpaInt.mul(k, k), MpaInt.from_u64(1000000000000)), "1e6^2=1e12"); + @assert(MpaInt.eq(MpaInt.mul(k, z), z), "x*0=0"); + @assert(MpaInt.eq(MpaInt.mul(m5, p5), MpaInt.from_i64(-25)), "-5*5=-25"); + @assert(MpaInt.eq(MpaInt.mul(m5, m5), MpaInt.from_u64(25)), "-5*-5=25"); + + /* 7. 比较 */ + var s1 = MpaInt.from_u64(100); + var s2 = MpaInt.from_u64(200); + var s3 = MpaInt.from_i64(-100); + @assert(MpaInt.cmp(s1, s2) < 0, "100<200"); + @assert(MpaInt.cmp(s2, s1) > 0, "200>100"); + @assert(MpaInt.cmp(s1, s1) == 0, "100==100"); + @assert(MpaInt.cmp(s3, s1) < 0, "-100<100"); + @assert(MpaInt.cmp(s2, s3) > 0, "200>-100"); + @assert(MpaInt.cmp(s3, s3) == 0, "-100==-100"); + + /* 8. fits_bits */ + var u8max = MpaInt.from_u64(255); + var u8over = MpaInt.from_u64(256); + @assert(MpaInt.fits_bits(&u8max, 8, false), "255 fits u8"); + @assert(!MpaInt.fits_bits(&u8over, 8, false), "256 !fit u8"); + + var i8max = MpaInt.from_u64(127); + var i8over = MpaInt.from_u64(128); + var i8min = MpaInt.from_i64(-128); + var i8under = MpaInt.from_i64(-129); + @assert(MpaInt.fits_bits(&i8max, 8, true), "127 fits i8"); + @assert(!MpaInt.fits_bits(&i8over, 8, true), "128 !fit i8"); + @assert(MpaInt.fits_bits(&i8min, 8, true), "-128 fits i8"); + @assert(!MpaInt.fits_bits(&i8under, 8, true), "-129 !fit i8"); + @assert(!MpaInt.fits_bits(&i8min, 8, false), "-128 !fit u8"); + + /* 9. 十六进制解析 + 大整数往返 */ + var h = MpaInt.from_str("deadbeef", 16); + @assert(MpaInt.eq(h, MpaInt.from_u64(0xDEADBEEF)), "hex deadbeef"); + + var u64max = MpaInt.from_str("ffffffffffffffff", 16); + @assert(MpaInt.eq(u64max, MpaInt.from_u64(18446744073709551615)), "u64max"); + + var two_128 = MpaInt.from_str("340282366920938463463374607431768211456", 10); + var sbig = MpaInt.to_str(&two_128, 10); + @assert(Str.eq_slice(sbig, "340282366920938463463374607431768211456"), "2^128 round-trip"); + + var neg_big = MpaInt.from_str("-123456789012345678901234567890", 10); + @assert(MpaInt.is_neg(&neg_big), "neg_big is negative"); + var sneg = MpaInt.to_str(&neg_big, 10); + @assert(Str.eq_slice(sneg, "-123456789012345678901234567890"), "neg_big round-trip"); + + /* 10. 大数运算 —— 用 2^128 做溢出探测 */ + var two_64 = MpaInt.from_str("18446744073709551616", 10); + var prod = MpaInt.mul(two_64, two_64); + @assert(MpaInt.eq(prod, two_128), "2^64 * 2^64 = 2^128"); + + var diff = MpaInt.sub(two_128, two_64); + var diff_exp = MpaInt.from_str("340282366920938463444927863358058659840", 10); + @assert(MpaInt.eq(diff, diff_exp), "2^128 - 2^64"); + + /* 11. 大数比较 */ + @assert(MpaInt.cmp(two_64, two_128) < 0, "2^64 < 2^128"); + @assert(MpaInt.cmp(two_128, neg_big) > 0, "2^128 > -1.2e29"); + + /* 12. 大数截断 */ + var wide = MpaInt.from_str("123456789abcdef0123456789abcdef", 16); + var cut = MpaInt.trunc_bits(&wide, 32, false); + var cut_exp = MpaInt.from_u64(0x89abcdef); + @assert(MpaInt.eq(cut, cut_exp), "trunc low 32"); + + /* 13. Fibonacci 到 F(100) —— 大数压测 */ + var f0 = MpaInt.from_u64(0); + var f1 = MpaInt.from_u64(1); + var fi: u64 = 0; + while fi < 100 { + var nf = MpaInt.add(f0, f1); + f0 = f1; + f1 = nf; + fi = fi + 1; + } + var sf = MpaInt.to_str(&f0, 10); + // vm_fwrite(sf.ptr, 1, sf.len, vm_stderr()); + // vm_fwrite("\n".ptr, 1, 1, vm_stderr()); + @assert(Str.eq_slice(sf, "354224848179261915075"), "F(100)"); +}