239 lines
6.9 KiB
Plaintext
239 lines
6.9 KiB
Plaintext
// Expect retcode = 0
|
||
|
||
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
|
||
// 不分配额外内存(除目标 ByteBuffer 自身扩容),失败不产生部分输出。
|
||
|
||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||
type .{ .digit_char } = @import("ascii.spl");
|
||
|
||
// 注意:radix 边界直接用字面量。stage1 刻意不做常量求值/折叠,
|
||
// 顶层 const 不应用于运行时表达式。
|
||
fn valid_radix(radix: u8): bool {
|
||
ret radix >= 2 && radix <= 36;
|
||
}
|
||
|
||
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
|
||
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
|
||
|
||
var digits: [64]u8 = undefined;
|
||
var count: usize = 0;
|
||
|
||
if value == 0 {
|
||
digits[0] = '0';
|
||
count = 1;
|
||
} else {
|
||
var v = value;
|
||
var base = radix as u64;
|
||
while v != 0 {
|
||
var d = (v % base) as u8;
|
||
match digit_char(d, uppercase) {
|
||
.Ok(c) => { digits[count] = c; },
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
count = count + 1;
|
||
v = v / base;
|
||
}
|
||
}
|
||
|
||
// digits 是低位在前,反向写入得到正常顺序。
|
||
var i = count;
|
||
while i > 0 {
|
||
i = i - 1;
|
||
match ByteBuffer.append_byte(out, digits[i]) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
}
|
||
ret .Ok;
|
||
}
|
||
|
||
fn append_i64(out: *ByteBuffer, value: i64, radix: u8, uppercase: bool): Status {
|
||
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
|
||
if value < 0 {
|
||
match ByteBuffer.append_byte(out, '-') {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
// value 为补码,(0 - (value as u64)) 得到绝对值(含 i64 最小值)。
|
||
var magnitude = 0 - (value as u64);
|
||
ret append_u64(out, magnitude, radix, uppercase);
|
||
}
|
||
ret append_u64(out, value as u64, radix, uppercase);
|
||
}
|
||
|
||
fn append_usize(out: *ByteBuffer, value: usize, radix: u8, uppercase: bool): Status {
|
||
ret append_u64(out, value as u64, radix, uppercase);
|
||
}
|
||
|
||
fn append_isize(out: *ByteBuffer, value: isize, radix: u8, uppercase: bool): Status {
|
||
ret append_i64(out, value as i64, radix, uppercase);
|
||
}
|
||
|
||
fn append_bool(out: *ByteBuffer, value: bool): Status {
|
||
if value { ret ByteBuffer.append(out, "true"); }
|
||
ret ByteBuffer.append(out, "false");
|
||
}
|
||
|
||
// ---- 测试辅助 ----
|
||
|
||
fn expect_ok(result: Status) {
|
||
match result {
|
||
.Ok => {},
|
||
.Err(error) => { @assert(false, "unexpected fmt error"); },
|
||
}
|
||
}
|
||
|
||
fn expect_invalid(result: Status) {
|
||
match result {
|
||
.Ok => { @assert(false, "expected fmt error"); },
|
||
.Err(error) => {
|
||
match error.kind {
|
||
.InvalidArgument => {},
|
||
_ => { @assert(false, "unexpected fmt error kind"); },
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
fn expect_text(buffer: *ByteBuffer, expected: []u8) {
|
||
var actual = ByteBuffer.as_slice(buffer.*);
|
||
@assert(actual.len == expected.len, "formatted length");
|
||
var i: usize = 0;
|
||
while i < expected.len {
|
||
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
|
||
i = i + 1;
|
||
}
|
||
}
|
||
|
||
fn test_unsigned() {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
|
||
expect_ok(append_u64(&b, 0, 10, false));
|
||
expect_text(&b, "0");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 42, 10, false));
|
||
expect_text(&b, "42");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 1234567890, 10, false));
|
||
expect_text(&b, "1234567890");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 18446744073709551615, 10, false));
|
||
expect_text(&b, "18446744073709551615");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 255, 16, false));
|
||
expect_text(&b, "ff");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 255, 16, true));
|
||
expect_text(&b, "FF");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 4096, 16, false));
|
||
expect_text(&b, "1000");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 170, 2, false));
|
||
expect_text(&b, "10101010");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 35, 36, false));
|
||
expect_text(&b, "z");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 18446744073709551615, 2, false));
|
||
expect_text(&b, "1111111111111111111111111111111111111111111111111111111111111111");
|
||
ByteBuffer.clear(&b);
|
||
}
|
||
|
||
fn test_signed() {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
|
||
expect_ok(append_i64(&b, 0, 10, false));
|
||
expect_text(&b, "0");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_i64(&b, -123, 10, false));
|
||
expect_text(&b, "-123");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_i64(&b, 9223372036854775807, 10, false));
|
||
expect_text(&b, "9223372036854775807");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_i64(&b, -9223372036854775808, 10, false));
|
||
expect_text(&b, "-9223372036854775808");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_isize(&b, -16, 16, false));
|
||
expect_text(&b, "-10");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_u64(&b, 48879, 16, true));
|
||
expect_text(&b, "BEEF");
|
||
ByteBuffer.clear(&b);
|
||
}
|
||
|
||
fn test_usize_bool_and_errors() {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
|
||
expect_ok(append_usize(&b, 1000 as usize, 10, false));
|
||
expect_text(&b, "1000");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_bool(&b, true));
|
||
expect_text(&b, "true");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(append_bool(&b, false));
|
||
expect_text(&b, "false");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_invalid(append_u64(&b, 1, 0, false));
|
||
expect_invalid(append_u64(&b, 1, 1, false));
|
||
expect_invalid(append_u64(&b, 1, 37, false));
|
||
expect_invalid(append_i64(&b, -1, 0, false));
|
||
@assert(ByteBuffer.size(b) == 0, "invalid radix writes nothing");
|
||
}
|
||
|
||
fn test_composition_and_growth() {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
|
||
expect_ok(ByteBuffer.append(&b, "x="));
|
||
expect_ok(append_i64(&b, -42, 10, false));
|
||
expect_ok(ByteBuffer.append(&b, " hex="));
|
||
expect_ok(append_u64(&b, 255, 16, false));
|
||
expect_ok(ByteBuffer.append(&b, " ok="));
|
||
expect_ok(append_bool(&b, true));
|
||
expect_text(&b, "x=-42 hex=ff ok=true");
|
||
ByteBuffer.clear(&b);
|
||
|
||
// 反复写入触发 ByteBuffer 扩容,校验累积内容。
|
||
var i: i32 = 0;
|
||
while i < 200 {
|
||
expect_ok(append_i64(&b, i as i64, 10, false));
|
||
expect_ok(ByteBuffer.append_byte(&b, ',')); // ','
|
||
i = i + 1;
|
||
}
|
||
expect_ok(append_i64(&b, 200, 10, false));
|
||
@assert(ByteBuffer.size(b) > 0, "growth content");
|
||
var s = ByteBuffer.as_slice(b);
|
||
@assert(s.ptr[0] == '0', "starts with 0");
|
||
@assert(s.ptr[1] == ',', "0 then comma");
|
||
}
|
||
|
||
fn main() {
|
||
test_unsigned();
|
||
test_signed();
|
||
test_usize_bool_and_errors();
|
||
test_composition_and_growth();
|
||
}
|