510 lines
16 KiB
Plaintext
510 lines
16 KiB
Plaintext
// Expect retcode = 0
|
||
|
||
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
|
||
// 整数转换统一复用 MPA。先在临时缓冲区完成转换,再一次性追加,失败不产生部分输出。
|
||
|
||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||
type .{ .ByteView } = @import("byte_view.spl");
|
||
type .{ .Value, .int, .uint, .char, .str, .addr } = @import("value.spl");
|
||
type .{ .BigInt, .Bits } = @import("mp.spl");
|
||
|
||
// radix 边界直接用字面量:顶层 const 不参与运行时常量折叠。
|
||
|
||
fn valid_radix(radix: u8): bool {
|
||
ret radix >= 2 && radix <= 36;
|
||
}
|
||
|
||
fn append_big_int(out: *ByteBuffer, value: BigInt,
|
||
radix: u8, uppercase: bool): Status {
|
||
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
|
||
var text = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&text); }
|
||
match BigInt.format(&text, value, radix, uppercase) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret ByteBuffer.append(out, ByteBuffer.as_slice(text));
|
||
}
|
||
|
||
fn append_bits(out: *ByteBuffer, value: Bits, signed: bool,
|
||
radix: u8, uppercase: bool): Status {
|
||
var integer = BigInt.zero();
|
||
defer { BigInt.drop(&integer); }
|
||
match Bits.to_big(&integer, value, signed) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret append_big_int(out, integer, radix, uppercase);
|
||
}
|
||
|
||
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
|
||
var integer = BigInt.zero();
|
||
defer { BigInt.drop(&integer); }
|
||
match BigInt.from_u64(&integer, value) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret append_big_int(out, integer, radix, uppercase);
|
||
}
|
||
|
||
fn append_i64(out: *ByteBuffer, value: i64, radix: u8, uppercase: bool): Status {
|
||
var integer = BigInt.zero();
|
||
defer { BigInt.drop(&integer); }
|
||
match BigInt.from_i64(&integer, value) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret append_big_int(out, integer, 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");
|
||
}
|
||
|
||
|
||
// Int/Uint 落到整数;signed 仅对 Int 的十进制生效,其余按无符号位模式输出。
|
||
fn append_value_int(out: *ByteBuffer, value: Value, radix: u8, uppercase: bool,
|
||
signed: bool): Status {
|
||
match value {
|
||
.Int(v) => {
|
||
if signed && radix == 10 { ret append_i64(out, v, radix, uppercase); }
|
||
ret append_u64(out, v as u64, radix, uppercase);
|
||
},
|
||
.Uint(v) => { ret append_u64(out, v, radix, uppercase); },
|
||
_ => { ret .Err(make(.InvalidFormat)); },
|
||
}
|
||
}
|
||
|
||
fn append_value_char(out: *ByteBuffer, value: Value): Status {
|
||
match value {
|
||
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
|
||
_ => { ret .Err(make(.InvalidFormat)); },
|
||
}
|
||
}
|
||
|
||
fn append_value_str(out: *ByteBuffer, value: Value): Status {
|
||
match value {
|
||
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
|
||
_ => { ret .Err(make(.InvalidFormat)); },
|
||
}
|
||
}
|
||
|
||
fn append_value_ptr(out: *ByteBuffer, value: Value): Status {
|
||
match value {
|
||
.Ptr(p) => {
|
||
match ByteBuffer.append(out, "0x") {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret append_u64(out, p as usize, 16, false);
|
||
},
|
||
_ => { ret .Err(make(.InvalidFormat)); },
|
||
}
|
||
}
|
||
|
||
// {} 自动格式化:按 Value 变体选择默认表示。
|
||
fn append_value_auto(out: *ByteBuffer, value: Value): Status {
|
||
match value {
|
||
.Int(v) => { ret append_i64(out, v, 10, false); },
|
||
.Uint(v) => { ret append_u64(out, v, 10, false); },
|
||
.Bool(v) => { ret append_bool(out, v); },
|
||
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
|
||
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
|
||
.Ptr(p) => {
|
||
match ByteBuffer.append(out, "0x") {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
ret append_u64(out, p as usize, 16, false);
|
||
},
|
||
}
|
||
ret .Ok;
|
||
}
|
||
|
||
// {} 风格:{} 自动;{:d} {:i} {:u} {:x} {:X} {:o} {:b} {:c} {:s} {:p} 指定。
|
||
// {{ 与 }} 输出字面量括号。参数按序从 args 消费;数量不足、类型不符、未知转换
|
||
// 或括号不配返回 Err(.InvalidFormat)。失败不产生部分输出:先在临时缓冲完成。
|
||
fn format(out: *ByteBuffer, fmt: []u8, args: []Value): Status {
|
||
var temp = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&temp); }
|
||
|
||
var argi: usize = 0;
|
||
var i: usize = 0;
|
||
while i < fmt.len {
|
||
var c = fmt.ptr[i];
|
||
|
||
if c == '{' {
|
||
if i + 1 < fmt.len && fmt.ptr[i + 1] == '{' {
|
||
match ByteBuffer.append_byte(&temp, '{') {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
i = i + 2;
|
||
continue;
|
||
}
|
||
|
||
var j = i + 1;
|
||
if j < fmt.len && fmt.ptr[j] == ':' { j = j + 1; }
|
||
var has_spec = false;
|
||
var spec: u8 = 0;
|
||
if j < fmt.len && fmt.ptr[j] != '}' {
|
||
has_spec = true;
|
||
spec = fmt.ptr[j];
|
||
j = j + 1;
|
||
}
|
||
if j >= fmt.len || fmt.ptr[j] != '}' { ret .Err(make(.InvalidFormat)); }
|
||
i = j + 1;
|
||
|
||
if argi >= args.len { ret .Err(make(.InvalidFormat)); }
|
||
var value = args.ptr[argi];
|
||
argi = argi + 1;
|
||
|
||
var step: Status = .Ok;
|
||
if !has_spec {
|
||
step = append_value_auto(&temp, value);
|
||
} else if spec == 'd' || spec == 'i' {
|
||
step = append_value_int(&temp, value, 10, false, true);
|
||
} else if spec == 'u' {
|
||
step = append_value_int(&temp, value, 10, false, false);
|
||
} else if spec == 'x' {
|
||
step = append_value_int(&temp, value, 16, false, false);
|
||
} else if spec == 'X' {
|
||
step = append_value_int(&temp, value, 16, true, false);
|
||
} else if spec == 'o' {
|
||
step = append_value_int(&temp, value, 8, false, false);
|
||
} else if spec == 'b' {
|
||
step = append_value_int(&temp, value, 2, false, false);
|
||
} else if spec == 'c' {
|
||
step = append_value_char(&temp, value);
|
||
} else if spec == 's' {
|
||
step = append_value_str(&temp, value);
|
||
} else if spec == 'p' {
|
||
step = append_value_ptr(&temp, value);
|
||
} else {
|
||
ret .Err(make(.InvalidFormat));
|
||
}
|
||
match step {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if c == '}' {
|
||
if i + 1 < fmt.len && fmt.ptr[i + 1] == '}' {
|
||
match ByteBuffer.append_byte(&temp, '}') {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
i = i + 2;
|
||
continue;
|
||
}
|
||
ret .Err(make(.InvalidFormat));
|
||
}
|
||
|
||
match ByteBuffer.append_byte(&temp, c) {
|
||
.Ok => {},
|
||
.Err(error) => { ret .Err(error); },
|
||
}
|
||
i = i + 1;
|
||
}
|
||
|
||
ret ByteBuffer.append(out, ByteBuffer.as_slice(temp));
|
||
}
|
||
|
||
|
||
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");
|
||
|
||
expect_ok(ByteBuffer.append(&b, "prefix"));
|
||
expect_invalid(append_i64(&b, -123, 1, false));
|
||
expect_text(&b, "prefix");
|
||
}
|
||
|
||
fn test_mpa_integer_formatting() {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
|
||
var huge = BigInt.zero();
|
||
defer { BigInt.drop(&huge); }
|
||
expect_ok(BigInt.parse(&huge, "1234567890123456789012345678901234567890", 10));
|
||
expect_ok(append_big_int(&b, huge, 10, false));
|
||
expect_text(&b, "1234567890123456789012345678901234567890");
|
||
ByteBuffer.clear(&b);
|
||
|
||
expect_ok(BigInt.parse(&huge, "-ffffffffffffffffffffffffffffffff", 16));
|
||
expect_ok(append_big_int(&b, huge, 16, true));
|
||
expect_text(&b, "-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
|
||
ByteBuffer.clear(&b);
|
||
|
||
var raw = BigInt.zero();
|
||
defer { BigInt.drop(&raw); }
|
||
expect_ok(BigInt.from_u64(&raw, 255));
|
||
var bits: Bits = undefined;
|
||
match Bits.new(8) {
|
||
.Ok(value) => { bits = value; },
|
||
.Err(error) => { @assert(false, "Bits construction"); },
|
||
}
|
||
defer { Bits.drop(&bits); }
|
||
expect_ok(Bits.from_big_checked(&bits, raw, 8, false));
|
||
|
||
expect_ok(append_bits(&b, bits, false, 10, false));
|
||
expect_text(&b, "255");
|
||
ByteBuffer.clear(&b);
|
||
expect_ok(append_bits(&b, bits, true, 10, false));
|
||
expect_text(&b, "-1");
|
||
}
|
||
|
||
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 expect_format(fmt: []u8, args: []Value, expected: []u8) {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
expect_ok(format(&b, fmt, args));
|
||
expect_text(&b, expected);
|
||
}
|
||
|
||
fn expect_invalid_format(fmt: []u8, args: []Value) {
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
match format(&b, fmt, args) {
|
||
.Ok => { @assert(false, "expected invalid format"); },
|
||
.Err(error) => {
|
||
match error.kind {
|
||
.InvalidFormat => {},
|
||
_ => { @assert(false, "expected InvalidFormat kind"); },
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
fn test_format_specs() {
|
||
var one: [1]Value = .[int(-5)];
|
||
expect_format("{}", one[0..1], "-5");
|
||
expect_format("{d}", one[0..1], "-5");
|
||
expect_format("{i}", one[0..1], "-5");
|
||
|
||
var u: [1]Value = .[uint(7)];
|
||
expect_format("{}", u[0..1], "7");
|
||
expect_format("{u}", u[0..1], "7");
|
||
expect_format("{x}", u[0..1], "7");
|
||
expect_format("{o}", u[0..1], "7");
|
||
expect_format("{b}", u[0..1], "111");
|
||
|
||
var hex: [1]Value = .[uint(255)];
|
||
expect_format("{x}", hex[0..1], "ff");
|
||
expect_format("{X}", hex[0..1], "FF");
|
||
|
||
var oct: [1]Value = .[uint(8)];
|
||
expect_format("{o}", oct[0..1], "10");
|
||
|
||
var bits: [1]Value = .[uint(5)];
|
||
expect_format("{b}", bits[0..1], "101");
|
||
|
||
var ch: [1]Value = .[char('A')];
|
||
expect_format("{}", ch[0..1], "A");
|
||
expect_format("{c}", ch[0..1], "A");
|
||
|
||
var text: [1]Value = .[str("hi")];
|
||
expect_format("{}", text[0..1], "hi");
|
||
expect_format("{s}", text[0..1], "hi");
|
||
|
||
var nul: [1]Value = .[addr(null)];
|
||
expect_format("{p}", nul[0..1], "0x0");
|
||
|
||
var none: [1]Value = .[int(0)];
|
||
expect_format("100%", none[0..0], "100%");
|
||
expect_format("{{}}", none[0..0], "{}");
|
||
expect_format("plain", none[0..0], "plain");
|
||
}
|
||
|
||
fn test_format_mixed_and_failure() {
|
||
var args: [3]Value = .[int(42), str("x"), char('!')];
|
||
expect_format("n={} s={} c={}", args[0..3], "n=42 s=x c=!");
|
||
expect_format("n={} s={s} c={c}", args[0..3], "n=42 s=x c=!");
|
||
|
||
// 失败不改动输出,且不产生部分结果:前两个转换成功、第三个参数不足。
|
||
var b = ByteBuffer.new();
|
||
defer { ByteBuffer.drop(&b); }
|
||
expect_ok(ByteBuffer.append(&b, "prefix"));
|
||
var two: [2]Value = .[int(1), int(2)];
|
||
expect_invalid_format("a={} b={} c={}", two[0..2]);
|
||
expect_text(&b, "prefix");
|
||
|
||
var only_int: [1]Value = .[int(1)];
|
||
expect_invalid_format("{} {}", only_int[0..1]);
|
||
expect_invalid_format("{}", only_int[0..0]);
|
||
expect_invalid_format("trailing {", only_int[0..1]);
|
||
expect_invalid_format("dangling }", only_int[0..1]);
|
||
expect_invalid_format("{q}", only_int[0..1]);
|
||
expect_invalid_format("{s}", only_int[0..1]);
|
||
expect_invalid_format("{c}", only_int[0..1]);
|
||
}
|
||
|
||
fn main() {
|
||
test_unsigned();
|
||
test_signed();
|
||
test_usize_bool_and_errors();
|
||
test_mpa_integer_formatting();
|
||
test_composition_and_growth();
|
||
test_format_specs();
|
||
test_format_mixed_and_failure();
|
||
}
|