ascii

tape
import ascii from "ascii";

ascii answers questions about single bytes: is this a digit, a letter, whitespace? It also converts letters between upper and lower case and gives the value of a digit. It is a pure t0 module: it allocates nothing, has no state, and works in every profile.

Every function takes a u8 and accepts all 256 values. Bytes from 128 up are never in any class and are never changed by the case functions, so you can run them over UTF-8 text byte by byte without damaging multi-byte characters.

tape has no character literals, so the examples write bytes as numbers: 48 is '0', 65 is 'A', 97 is 'a'.

Classifying bytes

FunctionTrue for
is_digit(c: u8) -> bool0 to 9
is_hex_digit(c: u8) -> bool0 to 9, a to f, A to F
is_upper(c: u8) -> boolA to Z
is_lower(c: u8) -> boola to z
is_alpha(c: u8) -> boola letter, either case
is_alnum(c: u8) -> boola letter or a digit
is_space(c: u8) -> boolspace, tab, newline, vertical tab (11), form feed (12), carriage return
is_print(c: u8) -> boola printable character: space (32) through ~ (126)
tape
let a: bool = ascii.is_digit(55);        // true: '7'
let b: bool = ascii.is_hex_digit(71);    // false: 'G'
let c: bool = ascii.is_alnum(95);        // false: '_'
let d: bool = ascii.is_space(9);         // true: tab
let e: bool = ascii.is_print(127);       // false: DEL

// Count the bytes that aren't whitespace
let words: []const u8 = "  tape  ";
var count: i64 = 0;
for (i in 0..words.len) {
    if (!ascii.is_space(words[i])) { count = count + 1; }
}
// count == 4

Changing case

FunctionReturns
to_upper(c: u8) -> u8the upper-case letter for a to z; any other byte unchanged
to_lower(c: u8) -> u8the lower-case letter for A to Z; any other byte unchanged
tape
let up: u8 = ascii.to_upper(97);         // 65: 'a' -> 'A'
let low: u8 = ascii.to_lower(90);        // 122: 'Z' -> 'z'
let same: u8 = ascii.to_upper(49);       // 49: '1' is not a letter

Digit values

tape
pub fn digit_value(c: u8) -> ?u8;

The value of c as a digit: 0 to 9 give 0 to 9, and letters give 10 to 35 (a and A are 10, z and Z are 35), which covers every base up to 36. Any other byte gives null. Whether the byte is a digit of your base is a check on the value: below 16 for hexadecimal, below 10 for decimal.

tape
let f: ?u8 = ascii.digit_value(102);     // 15: 'f'
let none: ?u8 = ascii.digit_value(33);   // null: '!'

// Parse a hexadecimal number; null if it is empty or not hex
fn parse_hex(s: []const u8) -> ?u64 {
    if (s.len == 0) { return null; }
    var v: u64 = 0;
    for (i in 0..s.len) {
        let d: u8 = ascii.digit_value(s[i]) or { return null; };
        if (d >= 16) { return null; }
        v = (v << 4) | (d as u64);
    }
    return v;
}
// parse_hex("ff") is 255, parse_hex("fg") is null

Last modified: