// 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(); }