lib: char-literals for ascii/fnmatch/shlex magic decimals (Wave-1)

Replace magic ASCII decimals with char literals in ascii/fnmatch/shlex
predicates (e.g. `c < 48` → `c < '0'`). Byte-id-neutral: ascii params are
rune, so rune<rune emission is unchanged; fnmatch/shlex compare u8 against
value-preserving (<=126) rune constants. Range bounds (0/31/127), the ±32
case offset, the 128 high-bit mask, and fnmatch 0u8 sentinels stay decimal.

Regenerate the three combined.ww that embed ascii (w6c, wwdump, smoke).

Add functional rows pinning predicates reachable only via fnmatch ctype
classes / shlex split: [[:space:]]/[[:print:]]/[[:graph:]] + the '\t' arm
of [[:blank:]] (fnmatchtest), '\t'/'\n' split separators + issafe's
special-char set (shlextest) — so a wrong substitution would be caught.
This commit is contained in:
2026-06-02 19:24:40 +09:00
parent 90479fed68
commit d1310a03ad
8 changed files with 150 additions and 136 deletions

View File

@@ -8,20 +8,20 @@ package ascii;
import strings;
export fn isdigit(c: rune) bool = {
if (c < 48) { return false; };
if (c > 57) { return false; };
if (c < '0') { return false; };
if (c > '9') { return false; };
return true;
};
export fn isupper(c: rune) bool = {
if (c < 65) { return false; };
if (c > 90) { return false; };
if (c < 'A') { return false; };
if (c > 'Z') { return false; };
return true;
};
export fn islower(c: rune) bool = {
if (c < 97) { return false; };
if (c > 122) { return false; };
if (c < 'a') { return false; };
if (c > 'z') { return false; };
return true;
};
@@ -37,22 +37,22 @@ export fn isalnum(c: rune) bool = {
// isspace — the C/Hare set: space, tab, NL, VT, FF, CR.
export fn isspace(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == 10) { return true; }; // '\n'
if (c == 11) { return true; }; // '\v'
if (c == 12) { return true; }; // '\f'
if (c == 13) { return true; }; // '\r'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
if (c == '\n') { return true; };
if (c == '\v') { return true; };
if (c == '\f') { return true; };
if (c == '\r') { return true; };
return false;
};
export fn isxdigit(c: rune) bool = {
if (isdigit(c)) { return true; };
if (c >= 65) {
if (c <= 70) { return true; }; // 'A'..'F'
if (c >= 'A') {
if (c <= 'F') { return true; };
};
if (c >= 97) {
if (c <= 102) { return true; }; // 'a'..'f'
if (c >= 'a') {
if (c <= 'f') { return true; };
};
return false;
};
@@ -86,22 +86,22 @@ export fn iscntrl(c: rune) bool = {
// isblank — space and tab.
export fn isblank(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
return false;
};
// isprint — printable: space through '~'.
export fn isprint(c: rune) bool = {
if (c < 32) { return false; };
if (c > 126) { return false; };
if (c < ' ') { return false; };
if (c > '~') { return false; };
return true;
};
// isgraph — printable, non-space.
export fn isgraph(c: rune) bool = {
if (c < 33) { return false; };
if (c > 126) { return false; };
if (c < '!') { return false; };
if (c > '~') { return false; };
return true;
};

View File

@@ -113,17 +113,17 @@ fn pat_next(pat: str, pos: *i32, fl: flag) (u8 | star | question | bracket | end
if (*pos >= pat.len) { let e: endt; return e; };
let c: u8 = pat[*pos];
*pos += 1;
if (c == 42u8) { let r: star; return r; }; // '*'
if (c == 63u8) { let r: question; return r; }; // '?'
if (c == 91u8) { let r: bracket; return r; }; // '['
if (c == 92u8) { // '\\'
if (c == '*') { let r: star; return r; };
if (c == '?') { let r: question; return r; };
if (c == '[') { let r: bracket; return r; };
if (c == '\\') {
if (((fl as i32) & (flag.NOESCAPE as i32)) == 0) {
if (*pos >= pat.len) { let e: invalid; return e; };
let c2: u8 = pat[*pos];
*pos += 1;
return c2;
};
return 92u8;
return '\\';
};
return c;
};
@@ -161,7 +161,7 @@ fn match_ctype(pat: str, pos: *i32, c: u8) (bool | invalid) = {
let nameoff: i32 = *pos;
let i: i32 = 0;
let r: u8 = 0u8;
for (r != 58u8) { // ':'
for (r != ':') {
let rr = advance_or_err(pat, pos);
match (rr) {
case let e: invalid => return e;
@@ -176,7 +176,7 @@ fn match_ctype(pat: str, pos: *i32, c: u8) (bool | invalid) = {
match (rr) {
case let e: invalid => return e;
case let f: u8 => {
if (f != 93u8) { let e: invalid; return e; }; // ']'
if (f != ']') { let e: invalid; return e; };
};
};
let name: str;
@@ -212,9 +212,9 @@ fn match_bracket(pat: str, pos: *i32, c: u8) (bool | invalid) = {
case let e: invalid => return e;
case let f: u8 => { first = f; };
};
if (first == 94u8) { let e: invalid; return e; }; // '^' is Hare-invalid
if (first == '^') { let e: invalid; return e; }; // '^' is Hare-invalid
let inv: bool = false;
if (first == 33u8) { // '!'
if (first == '!') {
inv = true;
let r1 = advance_or_err(pat, pos);
match (r1) {
@@ -222,10 +222,10 @@ fn match_bracket(pat: str, pos: *i32, c: u8) (bool | invalid) = {
case let f: u8 => { first = f; };
};
};
let found: bool = (first != 91u8) && (first == c);
let found: bool = (first != '[') && (first == c);
let havelast: bool = true;
let last: u8 = first;
if (first == 93u8) { // ']' as first member
if (first == ']') { // ']' as first member
let r2 = advance_or_err(pat, pos);
match (r2) {
case let e: invalid => return e;
@@ -235,22 +235,22 @@ fn match_bracket(pat: str, pos: *i32, c: u8) (bool | invalid) = {
let r: u8 = first;
let loop: bool = true;
for (loop) {
if (r == 93u8) { // ']' closes
if (r == ']') { // ']' closes
loop = false;
} else { if (r == 45u8) { // '-' range
} else { if (r == '-') { // '-' range
let er = advance_or_err(pat, pos);
let endv: u8 = 0u8;
match (er) {
case let e: invalid => return e;
case let f: u8 => { endv = f; };
};
if (endv == 93u8) {
if (endv == ']') {
// Trailing '-' matches itself. Un-eat ']'
// so the next iteration sees it.
*pos -= 1;
last = 45u8;
last = '-';
havelast = true;
if (c == 45u8) { found = true; };
if (c == '-') { found = true; };
} else {
if (!havelast) { let e: invalid; return e; };
if ((last: u32) <= (c: u32) && (c: u32) <= (endv: u32)) {
@@ -258,16 +258,16 @@ fn match_bracket(pat: str, pos: *i32, c: u8) (bool | invalid) = {
};
havelast = false; // forbid `a-f-n`
};
} else { if (r == 91u8) { // '['
} else { if (r == '[') {
let nx = advance_or_err(pat, pos);
let nxv: u8 = 0u8;
match (nx) {
case let e: invalid => return e;
case let f: u8 => { nxv = f; };
};
if (nxv == 61u8) { let e: invalid; return e; }; // '='
if (nxv == 46u8) { let e: invalid; return e; }; // '.'
if (nxv == 58u8) { // ':' class
if (nxv == '=') { let e: invalid; return e; };
if (nxv == '.') { let e: invalid; return e; };
if (nxv == ':') { // ':' class
let t = match_ctype(pat, pos, c);
match (t) {
case let e: invalid => return e;
@@ -278,9 +278,9 @@ fn match_bracket(pat: str, pos: *i32, c: u8) (bool | invalid) = {
} else {
// Not a class — un-eat the byte after '['.
*pos -= 1;
if (c == 91u8) { found = true; };
if (c == '[') { found = true; };
};
last = 91u8;
last = '[';
havelast = true;
} else { // literal byte
if (c == r) { found = true; };
@@ -300,9 +300,9 @@ fn match_bracket(pat: str, pos: *i32, c: u8) (bool | invalid) = {
// have been an empty collating/equivalence/class).
let cnt: i32 = *pos - oldpos;
if (havelast && first == last && cnt >= 4) {
if (first == 61u8) { let e: invalid; return e; };
if (first == 46u8) { let e: invalid; return e; };
if (first == 58u8) { let e: invalid; return e; };
if (first == '=') { let e: invalid; return e; };
if (first == '.') { let e: invalid; return e; };
if (first == ':') { let e: invalid; return e; };
};
if (inv) { return !found; };
return found;
@@ -534,7 +534,7 @@ fn fnmatch_pathname(pattern: str, string: str, fl: flag) (bool | invalid) = {
case let e: invalid => return e;
case endt => { kind = 1; inner = false; };
case let r: u8 => {
if (r == 47u8) { kind = 0; inner = false; };
if (r == '/') { kind = 0; inner = false; };
};
case bracket => {
let mr = match_bracket(pattern, &pp, 0u8);
@@ -552,7 +552,7 @@ fn fnmatch_pathname(pattern: str, string: str, fl: flag) (bool | invalid) = {
if (donetokens) { return false; };
let segstart_s: i32 = sp;
for (sp < string.len) {
if (string[sp] == 47u8) { break; };
if (string[sp] == '/') { break; };
sp += 1;
};
let seg: str;

View File

@@ -134,6 +134,16 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check("[[alpha:]]", ":]", true, 0);
check("[[:alpha:]]", "a", true, 0);
check("[[:blank:]]", " ", true, 0);
// pin the ascii.is{blank,space,print,graph} predicates: their
// char-literal bounds are reachable only through these classes.
check("[[:blank:]]", "\t", true, 0);
check("[[:space:]]", " ", true, 0);
check("[[:space:]]", "\t", true, 0);
check("[[:space:]]", "x", false, 0);
check("[[:print:]]", "~", true, 0);
check("[[:print:]]", "\t", false, 0);
check("[[:graph:]]", "a", true, 0);
check("[[:graph:]]", " ", false, 0);
check("[[:alnum:]]a", "a]a", false, 0);
check("[[:alnum:][[:digit:]]", "a", true, 0);

View File

@@ -181,7 +181,7 @@ fn scan_backslash(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
if (*pos >= in.len) { let e: syntaxerr; return e; };
let r: u8 = in[*pos];
*pos += 1;
if (r == 10u8) { return; }; // '\n' deleted
if (r == '\n') { return; }; // deleted per POSIX
let _w = writebyte(out, r);
return;
};
@@ -195,8 +195,8 @@ fn scan_double(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
if (*pos >= in.len) { let e: syntaxerr; return e; };
let r: u8 = in[*pos];
*pos += 1;
if (r == 34u8) { loop = false; } // '"'
else { if (r == 92u8) { // '\\'
if (r == '"') { loop = false; }
else { if (r == '\\') {
let er = scan_backslash(out, in, pos);
match (er) {
case let e: syntaxerr => return e;
@@ -218,7 +218,7 @@ fn scan_single(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
if (*pos >= in.len) { let e: syntaxerr; return e; };
let r: u8 = in[*pos];
*pos += 1;
if (r == 39u8) { loop = false; } // "'"
if (r == '\'') { loop = false; }
else { let _w = writebyte(out, r); };
};
return;
@@ -254,14 +254,14 @@ export fn split(in: str) ([]str | syntaxerr) = {
pos += 1;
dirty = true;
if (r == 32u8 || r == 9u8 || r == 10u8) {
if (r == ' ' || r == '\t' || r == '\n') {
// Collapse a run of whitespace.
let inner: bool = true;
for (inner) {
if (pos >= in.len) { inner = false; }
else {
let r2: u8 = in[pos];
if (r2 != 32u8 && r2 != 9u8 && r2 != 10u8) {
if (r2 != ' ' && r2 != '\t' && r2 != '\n') {
inner = false;
} else {
pos += 1;
@@ -275,7 +275,7 @@ export fn split(in: str) ([]str | syntaxerr) = {
memio.reset(&st);
};
dirty = false;
} else { if (r == 92u8) { // '\\'
} else { if (r == '\\') {
let er = scan_backslash(snk, in, &pos);
match (er) {
case let e: syntaxerr => {
@@ -285,7 +285,7 @@ export fn split(in: str) ([]str | syntaxerr) = {
};
case void => {};
};
} else { if (r == 34u8) { // '"'
} else { if (r == '"') {
let er = scan_double(snk, in, &pos);
match (er) {
case let e: syntaxerr => {
@@ -295,7 +295,7 @@ export fn split(in: str) ([]str | syntaxerr) = {
};
case void => {};
};
} else { if (r == 39u8) { // "'"
} else { if (r == '\'') {
let er = scan_single(snk, in, &pos);
match (er) {
case let e: syntaxerr => {
@@ -332,15 +332,15 @@ fn issafe(s: str) bool = {
for (i < s.len) {
let c: u8 = s[i];
// Hare's switch list: '@', '%', '+', '=', ':', ',', '.', '/', '-'.
if (c == 64u8 || c == 37u8 || c == 43u8 || c == 61u8
|| c == 58u8 || c == 44u8 || c == 46u8 || c == 47u8
|| c == 45u8) {
if (c == '@' || c == '%' || c == '+' || c == '='
|| c == ':' || c == ',' || c == '.' || c == '/'
|| c == '-') {
i += 1;
} else {
let alnum: bool = false;
if (c >= 48u8 && c <= 57u8) { alnum = true; }; // 0-9
if (c >= 65u8 && c <= 90u8) { alnum = true; }; // A-Z
if (c >= 97u8 && c <= 122u8) { alnum = true; }; // a-z
if (c >= '0' && c <= '9') { alnum = true; };
if (c >= 'A' && c <= 'Z') { alnum = true; };
if (c >= 'a' && c <= 'z') { alnum = true; };
if (!alnum) { return false; };
i += 1;
};
@@ -361,7 +361,7 @@ fn issafe(s: str) bool = {
export fn quote(sink: io.stream, s: str) (size | io.error) = {
if (s.len == 0) {
let buf: [2]u8;
buf[0] = 39u8; buf[1] = 39u8;
buf[0] = '\''; buf[1] = '\'';
let r = io.write(sink, buf[0:2]);
match (r) {
case let n: size => return n;
@@ -380,7 +380,7 @@ export fn quote(sink: io.stream, s: str) (size | io.error) = {
};
let total: size = 0;
let r1 = writebyte(sink, 39u8); // "'"
let r1 = writebyte(sink, '\'');
match (r1) {
case let n: size => { total += n; };
case let e: io.error => return e;
@@ -388,10 +388,10 @@ export fn quote(sink: io.stream, s: str) (size | io.error) = {
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (c == 39u8) { // "'" → '"'"'
if (c == '\'') { // "'" → '"'"'
let pat: [5]u8;
pat[0] = 39u8; pat[1] = 34u8; pat[2] = 39u8;
pat[3] = 34u8; pat[4] = 39u8;
pat[0] = '\''; pat[1] = '"'; pat[2] = '\'';
pat[3] = '"'; pat[4] = '\'';
let rr = io.write(sink, pat[0:5]);
match (rr) {
case let n: size => { total += n; };
@@ -406,7 +406,7 @@ export fn quote(sink: io.stream, s: str) (size | io.error) = {
};
i += 1;
};
let r2 = writebyte(sink, 39u8); // "'"
let r2 = writebyte(sink, '\'');
match (r2) {
case let n: size => { total += n; };
case let e: io.error => return e;

View File

@@ -149,6 +149,8 @@ fn checkquote(in: str, expected: str) void = {
check3("with\\ backslashes 'single quoted' \"double quoted\"",
"with backslashes", "single quoted", "double quoted");
check2("'multiple spaces' 42", "multiple spaces", "42");
// pin the '\t'/'\n' arms of split's whitespace test.
check3("a\tb\nc", "a", "b", "c");
// Invalid
checkerr("\"dangling double quote");
@@ -172,6 +174,8 @@ fn checkquote(in: str, expected: str) void = {
checkquote("'hello' \"world\"", "''\"'\"'hello'\"'\"' \"world\"'");
checkquote("hello\\world", "'hello\\world'");
checkquote("", "''");
// pin issafe's special-char set: all safe → emitted raw, unquoted.
checkquote("@%+=:,./-", "@%+=:,./-");
};
// ---- quotestr ------------------------------------------------------

View File

@@ -4363,20 +4363,20 @@ package ascii;
import strings;
export fn isdigit(c: rune) bool = {
if (c < 48) { return false; };
if (c > 57) { return false; };
if (c < '0') { return false; };
if (c > '9') { return false; };
return true;
};
export fn isupper(c: rune) bool = {
if (c < 65) { return false; };
if (c > 90) { return false; };
if (c < 'A') { return false; };
if (c > 'Z') { return false; };
return true;
};
export fn islower(c: rune) bool = {
if (c < 97) { return false; };
if (c > 122) { return false; };
if (c < 'a') { return false; };
if (c > 'z') { return false; };
return true;
};
@@ -4392,22 +4392,22 @@ export fn isalnum(c: rune) bool = {
// isspace — the C/Hare set: space, tab, NL, VT, FF, CR.
export fn isspace(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == 10) { return true; }; // '\n'
if (c == 11) { return true; }; // '\v'
if (c == 12) { return true; }; // '\f'
if (c == 13) { return true; }; // '\r'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
if (c == '\n') { return true; };
if (c == '\v') { return true; };
if (c == '\f') { return true; };
if (c == '\r') { return true; };
return false;
};
export fn isxdigit(c: rune) bool = {
if (isdigit(c)) { return true; };
if (c >= 65) {
if (c <= 70) { return true; }; // 'A'..'F'
if (c >= 'A') {
if (c <= 'F') { return true; };
};
if (c >= 97) {
if (c <= 102) { return true; }; // 'a'..'f'
if (c >= 'a') {
if (c <= 'f') { return true; };
};
return false;
};
@@ -4441,22 +4441,22 @@ export fn iscntrl(c: rune) bool = {
// isblank — space and tab.
export fn isblank(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
return false;
};
// isprint — printable: space through '~'.
export fn isprint(c: rune) bool = {
if (c < 32) { return false; };
if (c > 126) { return false; };
if (c < ' ') { return false; };
if (c > '~') { return false; };
return true;
};
// isgraph — printable, non-space.
export fn isgraph(c: rune) bool = {
if (c < 33) { return false; };
if (c > 126) { return false; };
if (c < '!') { return false; };
if (c > '~') { return false; };
return true;
};

View File

@@ -4363,20 +4363,20 @@ package ascii;
import strings;
export fn isdigit(c: rune) bool = {
if (c < 48) { return false; };
if (c > 57) { return false; };
if (c < '0') { return false; };
if (c > '9') { return false; };
return true;
};
export fn isupper(c: rune) bool = {
if (c < 65) { return false; };
if (c > 90) { return false; };
if (c < 'A') { return false; };
if (c > 'Z') { return false; };
return true;
};
export fn islower(c: rune) bool = {
if (c < 97) { return false; };
if (c > 122) { return false; };
if (c < 'a') { return false; };
if (c > 'z') { return false; };
return true;
};
@@ -4392,22 +4392,22 @@ export fn isalnum(c: rune) bool = {
// isspace — the C/Hare set: space, tab, NL, VT, FF, CR.
export fn isspace(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == 10) { return true; }; // '\n'
if (c == 11) { return true; }; // '\v'
if (c == 12) { return true; }; // '\f'
if (c == 13) { return true; }; // '\r'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
if (c == '\n') { return true; };
if (c == '\v') { return true; };
if (c == '\f') { return true; };
if (c == '\r') { return true; };
return false;
};
export fn isxdigit(c: rune) bool = {
if (isdigit(c)) { return true; };
if (c >= 65) {
if (c <= 70) { return true; }; // 'A'..'F'
if (c >= 'A') {
if (c <= 'F') { return true; };
};
if (c >= 97) {
if (c <= 102) { return true; }; // 'a'..'f'
if (c >= 'a') {
if (c <= 'f') { return true; };
};
return false;
};
@@ -4441,22 +4441,22 @@ export fn iscntrl(c: rune) bool = {
// isblank — space and tab.
export fn isblank(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
return false;
};
// isprint — printable: space through '~'.
export fn isprint(c: rune) bool = {
if (c < 32) { return false; };
if (c > 126) { return false; };
if (c < ' ') { return false; };
if (c > '~') { return false; };
return true;
};
// isgraph — printable, non-space.
export fn isgraph(c: rune) bool = {
if (c < 33) { return false; };
if (c > 126) { return false; };
if (c < '!') { return false; };
if (c > '~') { return false; };
return true;
};

View File

@@ -4363,20 +4363,20 @@ package ascii;
import strings;
export fn isdigit(c: rune) bool = {
if (c < 48) { return false; };
if (c > 57) { return false; };
if (c < '0') { return false; };
if (c > '9') { return false; };
return true;
};
export fn isupper(c: rune) bool = {
if (c < 65) { return false; };
if (c > 90) { return false; };
if (c < 'A') { return false; };
if (c > 'Z') { return false; };
return true;
};
export fn islower(c: rune) bool = {
if (c < 97) { return false; };
if (c > 122) { return false; };
if (c < 'a') { return false; };
if (c > 'z') { return false; };
return true;
};
@@ -4392,22 +4392,22 @@ export fn isalnum(c: rune) bool = {
// isspace — the C/Hare set: space, tab, NL, VT, FF, CR.
export fn isspace(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == 10) { return true; }; // '\n'
if (c == 11) { return true; }; // '\v'
if (c == 12) { return true; }; // '\f'
if (c == 13) { return true; }; // '\r'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
if (c == '\n') { return true; };
if (c == '\v') { return true; };
if (c == '\f') { return true; };
if (c == '\r') { return true; };
return false;
};
export fn isxdigit(c: rune) bool = {
if (isdigit(c)) { return true; };
if (c >= 65) {
if (c <= 70) { return true; }; // 'A'..'F'
if (c >= 'A') {
if (c <= 'F') { return true; };
};
if (c >= 97) {
if (c <= 102) { return true; }; // 'a'..'f'
if (c >= 'a') {
if (c <= 'f') { return true; };
};
return false;
};
@@ -4441,22 +4441,22 @@ export fn iscntrl(c: rune) bool = {
// isblank — space and tab.
export fn isblank(c: rune) bool = {
if (c == 32) { return true; }; // ' '
if (c == 9) { return true; }; // '\t'
if (c == ' ') { return true; };
if (c == '\t') { return true; };
return false;
};
// isprint — printable: space through '~'.
export fn isprint(c: rune) bool = {
if (c < 32) { return false; };
if (c > 126) { return false; };
if (c < ' ') { return false; };
if (c > '~') { return false; };
return true;
};
// isgraph — printable, non-space.
export fn isgraph(c: rune) bool = {
if (c < 33) { return false; };
if (c > 126) { return false; };
if (c < '!') { return false; };
if (c > '~') { return false; };
return true;
};