stage2 提供c后端 提交stage1的测试

This commit is contained in:
zzy committed 2026-09-20 17:56:11 +08:00
1 parent 846f441e3a
commit 51d408d524
82 files changed
+6872 -65

No files matched your search

+5
View File
@@ -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
+193 -3
View File
@@ -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) {
+40 -34
View File
@@ -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;
+1
View File
@@ -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);
+50 -17
View File
@@ -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},
+1 -1
View File
@@ -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;
+6
View File
@@ -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);
+1304
View File
File diff suppressed because it is too large. Load diff
+179 -7
View File
@@ -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 <expr>`:编译期表达式(可调用普通函数、返回类型等)。
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();
}
+35 -2
View File
@@ -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 <lex|ast|sema|ir|vm> <file> [out]");
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm|c> <file> [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;
}
+629
View File
@@ -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 <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* 语义清晰的 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__ */
+109
View File
@@ -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__ */
+1 -1
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
// Expect retcode = 0
fn main(): i32 {
ret 0;
}
+6
View File
@@ -0,0 +1,6 @@
// Expect retcode = 1
fn main(): i32 {
var x: i32 = 1;
ret x;
}
+7
View File
@@ -0,0 +1,7 @@
// Expect retcode = 0
fn main(): i32 {
var x: i32 = undefined;
x = 0;
ret x;
}
+10
View File
@@ -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;
}
+10
View File
@@ -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);
}
+21
View File
@@ -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;
}
+9
View File
@@ -0,0 +1,9 @@
// Expect retcode = 10
fn main(): i32 {
var i: i32 = 0;
while i < 10 {
i = i + 1;
}
ret i;
}
+23
View File
@@ -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;
}
+6
View File
@@ -0,0 +1,6 @@
// Expect retcode = 4
fn main(): i32 {
var str = "hello world";
ret str[2] as i32 - str[0] as i32;
}
+9
View File
@@ -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");
}
+11
View File
@@ -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;
}
+19
View File
@@ -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);
}
+21
View File
@@ -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);
}
+15
View File
@@ -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");
}
+30
View File
@@ -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;
}
+12
View File
@@ -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]);
}
+54
View File
@@ -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);
}
+50
View File
@@ -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
}
+15
View File
@@ -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');
}
+9
View File
@@ -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);
}
+20
View File
@@ -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);
}
+20
View File
@@ -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);
}
+12
View File
@@ -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);
}
+30
View File
@@ -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");
}
+67
View File
@@ -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();
}
+64
View File
@@ -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();
}
+70
View File
@@ -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();
}
+25
View File
@@ -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() {}
+10
View File
@@ -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");
}
+55
View File
@@ -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
}
+64
View File
@@ -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");
}
+21
View File
@@ -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");
}
+18
View File
@@ -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");
}
+28
View File
@@ -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;
}
+20
View File
@@ -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;
}
+307
View File
@@ -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");
}
+79
View File
@@ -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<20");
@assert(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");
}
+189
View File
@@ -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<x<10");
} else {
@assert(false, "x>=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");
}
+87
View File
@@ -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;
}
+112
View File
@@ -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;
}
+83
View File
@@ -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");
}
+73
View File
@@ -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");
}
+68
View File
@@ -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");
}
+62
View File
@@ -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");
}
+53
View File
@@ -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");
}
+49
View File
@@ -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");
}
+69
View File
@@ -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");
}
+13
View File
@@ -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);
}
+49
View File
@@ -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");
}
+109
View File
@@ -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");
}
+236
View File
@@ -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();
}
+38
View File
@@ -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);
},
}
}
+43
View File
@@ -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);
}
+18
View File
@@ -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);
}
@@ -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));
}
@@ -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() {}
+58
View File
@@ -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");
}
+17
View File
@@ -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]);
}
@@ -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);
}
@@ -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);
}
+21
View File
@@ -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");
}
+42
View File
@@ -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");
}
+36
View File
@@ -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");
}
+64
View File
@@ -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);
}
+131
View File
@@ -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);
}
+136
View File
@@ -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'");
}
+274
View File
@@ -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);
}
+336
View File
@@ -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);
}
+534
View File
@@ -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)");
}