218 lines
6.4 KiB
Plaintext
218 lines
6.4 KiB
Plaintext
// Expect retcode = 0
|
|
// ASCII 字符工具。输入和输出都是单个字节,不解释 UTF-8 或本地化规则。
|
|
|
|
type .{ .Error, .make } = @import("error.spl");
|
|
|
|
type DigitResult = enum {
|
|
Ok: u8,
|
|
Err: Error,
|
|
}
|
|
|
|
fn is_ascii(c: u8): bool {
|
|
ret c <= 127;
|
|
}
|
|
|
|
fn is_control(c: u8): bool {
|
|
ret c <= 31 || c == 127;
|
|
}
|
|
|
|
fn is_space(c: u8): bool {
|
|
ret c == ' ' || (c >= '\t' && c <= '\r');
|
|
}
|
|
|
|
fn is_blank(c: u8): bool {
|
|
ret c == ' ' || c == '\t';
|
|
}
|
|
|
|
fn is_digit(c: u8): bool {
|
|
ret c >= '0' && c <= '9';
|
|
}
|
|
|
|
fn is_lower(c: u8): bool {
|
|
ret c >= 'a' && c <= 'z';
|
|
}
|
|
|
|
fn is_upper(c: u8): bool {
|
|
ret c >= 'A' && c <= 'Z';
|
|
}
|
|
|
|
fn is_alpha(c: u8): bool {
|
|
ret is_lower(c) || is_upper(c);
|
|
}
|
|
|
|
fn is_alnum(c: u8): bool {
|
|
ret is_alpha(c) || is_digit(c);
|
|
}
|
|
|
|
fn is_hex_digit(c: u8): bool {
|
|
ret is_digit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
|
}
|
|
|
|
fn is_print(c: u8): bool {
|
|
ret c >= ' ' && c <= '~';
|
|
}
|
|
|
|
fn is_graph(c: u8): bool {
|
|
ret c >= '!' && c <= '~';
|
|
}
|
|
|
|
fn is_punct(c: u8): bool {
|
|
ret is_graph(c) && !is_alnum(c);
|
|
}
|
|
|
|
fn to_lower(c: u8): u8 {
|
|
if is_upper(c) { ret c + ('a' - 'A'); }
|
|
ret c;
|
|
}
|
|
|
|
fn to_upper(c: u8): u8 {
|
|
if is_lower(c) { ret c - ('a' - 'A'); }
|
|
ret c;
|
|
}
|
|
|
|
// radix 必须位于 2..36。大小写字母均表示 10..35。
|
|
fn digit_value(c: u8, radix: u8): DigitResult {
|
|
if radix < 2 || radix > 36 {
|
|
ret .Err(make(.InvalidArgument));
|
|
}
|
|
|
|
var value: u8 = 0;
|
|
if is_digit(c) {
|
|
value = c - '0';
|
|
} else if is_upper(c) {
|
|
value = c - 'A' + 10;
|
|
} else if is_lower(c) {
|
|
value = c - 'a' + 10;
|
|
} else {
|
|
ret .Err(make(.InvalidDigit));
|
|
}
|
|
|
|
if value >= radix { ret .Err(make(.InvalidDigit)); }
|
|
ret .Ok(value);
|
|
}
|
|
|
|
// value 必须位于 0..35。uppercase 只影响 10..35 的字母形式。
|
|
fn digit_char(value: u8, uppercase: bool): DigitResult {
|
|
if value >= 36 { ret .Err(make(.OutOfRange)); }
|
|
if value < 10 { ret .Ok(value + '0'); }
|
|
if uppercase { ret .Ok(value - 10 + 'A'); }
|
|
ret .Ok(value - 10 + 'a');
|
|
}
|
|
|
|
fn expect_digit(result: DigitResult, expected: u8) {
|
|
match result {
|
|
.Ok(value) => { @assert(value == expected, "digit value"); },
|
|
.Err(error) => { @assert(false, "unexpected digit error"); },
|
|
}
|
|
}
|
|
|
|
fn expect_error(result: DigitResult, expected_invalid_argument: bool) {
|
|
match result {
|
|
.Ok(value) => { @assert(false, "expected digit error"); },
|
|
.Err(error) => {
|
|
match error.kind {
|
|
.InvalidArgument => {
|
|
@assert(expected_invalid_argument, "unexpected invalid radix");
|
|
},
|
|
.InvalidDigit => {
|
|
@assert(!expected_invalid_argument, "unexpected invalid digit");
|
|
},
|
|
_ => { @assert(false, "unexpected digit error kind"); },
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn expect_range_error(result: DigitResult) {
|
|
match result {
|
|
.Ok(value) => { @assert(false, "expected digit range error"); },
|
|
.Err(error) => {
|
|
match error.kind {
|
|
.OutOfRange => {},
|
|
_ => { @assert(false, "unexpected digit range error kind"); },
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn test_all_bytes() {
|
|
var i: usize = 0;
|
|
while i < 256 {
|
|
var c = i as u8;
|
|
@assert(is_ascii(c) == (i <= 127), "ASCII boundary");
|
|
@assert(is_control(c) == (i <= 31 || i == 127), "control boundary");
|
|
@assert(is_print(c) == (c >= ' ' && c <= '~'), "print boundary");
|
|
@assert(is_graph(c) == (c >= '!' && c <= '~'), "graph boundary");
|
|
@assert(is_blank(c) == (c == '\t' || c == ' '), "blank set");
|
|
@assert(is_space(c) == (c == '\t' || c == '\n' || c == '\x0B' || c == '\x0C' ||
|
|
c == '\r' || c == ' '),
|
|
"space set");
|
|
@assert(is_digit(c) == (c >= '0' && c <= '9'), "digit set");
|
|
@assert(is_upper(c) == (c >= 'A' && c <= 'Z'), "upper set");
|
|
@assert(is_lower(c) == (c >= 'a' && c <= 'z'), "lower set");
|
|
@assert(is_hex_digit(c) == ((c >= '0' && c <= '9') ||
|
|
(c >= 'A' && c <= 'F') ||
|
|
(c >= 'a' && c <= 'f')),
|
|
"hex digit set");
|
|
@assert(is_alpha(c) == (is_upper(c) || is_lower(c)), "alpha relation");
|
|
@assert(is_alnum(c) == (is_alpha(c) || is_digit(c)), "alnum relation");
|
|
@assert(is_punct(c) == (is_graph(c) && !is_alnum(c)), "punct relation");
|
|
|
|
if is_upper(c) {
|
|
@assert(to_lower(c) == c + ('a' - 'A'), "upper to lower");
|
|
@assert(to_upper(to_lower(c)) == c, "case round trip");
|
|
} else if is_lower(c) {
|
|
@assert(to_upper(c) == c - ('a' - 'A'), "lower to upper");
|
|
@assert(to_lower(to_upper(c)) == c, "case round trip");
|
|
} else {
|
|
@assert(to_lower(c) == c, "lower preserves non-letter");
|
|
@assert(to_upper(c) == c, "upper preserves non-letter");
|
|
}
|
|
i = i + 1;
|
|
}
|
|
}
|
|
|
|
fn test_digits() {
|
|
var radix: usize = 2;
|
|
while radix <= 36 {
|
|
var value: usize = 0;
|
|
while value < radix {
|
|
var lower: u8 = 0;
|
|
match digit_char(value as u8, false) {
|
|
.Ok(c) => { lower = c; },
|
|
.Err(error) => { @assert(false, "lower digit generation"); },
|
|
}
|
|
expect_digit(digit_value(lower, radix as u8), value as u8);
|
|
|
|
var upper: u8 = 0;
|
|
match digit_char(value as u8, true) {
|
|
.Ok(c) => { upper = c; },
|
|
.Err(error) => { @assert(false, "upper digit generation"); },
|
|
}
|
|
expect_digit(digit_value(upper, radix as u8), value as u8);
|
|
if value < 10 {
|
|
@assert(lower == upper, "decimal digits ignore case");
|
|
} else {
|
|
@assert(lower == upper + ('a' - 'A'), "letter digit case distance");
|
|
}
|
|
value = value + 1;
|
|
}
|
|
radix = radix + 1;
|
|
}
|
|
|
|
expect_error(digit_value('0', 0), true);
|
|
expect_error(digit_value('0', 1), true);
|
|
expect_error(digit_value('0', 37), true);
|
|
expect_error(digit_value('/', 10), false);
|
|
expect_error(digit_value(':', 10), false);
|
|
expect_error(digit_value('2', 2), false);
|
|
expect_error(digit_value('g', 16), false);
|
|
expect_range_error(digit_char(36, false));
|
|
expect_range_error(digit_char(255, true));
|
|
}
|
|
|
|
fn main() {
|
|
test_all_bytes();
|
|
test_digits();
|
|
}
|