stage2 提供c后端 提交stage1的测试
This commit is contained in:
1 parent
846f441e3a
commit
51d408d524
82 files changed
+6872
-65
No files matched your search
@@ -0,0 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): i32 {
|
||||
ret 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Expect retcode = 1
|
||||
|
||||
fn main(): i32 {
|
||||
var x: i32 = 1;
|
||||
ret x;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
fn main(): i32 {
|
||||
var x: i32 = undefined;
|
||||
x = 0;
|
||||
ret x;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Expect retcode = 10
|
||||
|
||||
fn main(): i32 {
|
||||
var i: i32 = 0;
|
||||
while i < 10 {
|
||||
i = i + 1;
|
||||
}
|
||||
ret i;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Expect retcode = 4
|
||||
|
||||
fn main(): i32 {
|
||||
var str = "hello world";
|
||||
ret str[2] as i32 - str[0] as i32;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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'");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)");
|
||||
}
|
||||
Reference in new issue
Block a user