lib/ascii: add strlower/strupper
Port ref/hare/ascii/string.ha strlower/strupper as the allocating entry points: byte-wise ASCII case fold, equivalent to Hare's rune fold since case-folding only touches bytes <0x80 and every UTF-8 multibyte byte is >=0x80 (passes through unchanged, length-preserving). nomem arises only from the allocation's `?`. strlower_buf/strupper_buf are deferred: ww has no nomem-value form or capacity-bounded static-append to express Hare's too-small-buffer path (#230); restore the two-tier delegation when those land. Divergence (rule 7): the empty-input fast path returns a nil/0 str because ww's alloc([], 0) routes through nomem, whereas Hare allocs a zero-length buffer and zero-loops; documented at the bypass site. Test vectors mirror Hare's @test (ABC/abc/[[[/こ/empty/aB1z). Adds lib/ascii/asciitest.ww + test/wcc/904_ascii_run.c (registered in the Makefile TESTS list and a build rule). Regenerates the ascii-embedding selfhost combined.ww amalgams (#110 freshness); the wwdump amalgam also reorders the ascii block after strings to satisfy the new import edge.
This commit is contained in:
7
Makefile
7
Makefile
@@ -405,7 +405,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_opaque_assign_cast_run \
|
||||
$(BIN)/test_sort_run \
|
||||
$(BIN)/test_bufio_run $(BIN)/test_random_run \
|
||||
$(BIN)/test_asserttyped_gap
|
||||
$(BIN)/test_asserttyped_gap \
|
||||
$(BIN)/test_ascii_run
|
||||
|
||||
$(BIN)/test_smoke: test/wcc/000_smoke.c $(LIB)/libwcc.a | $(BIN)
|
||||
$(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwcc
|
||||
@@ -1376,6 +1377,10 @@ $(BIN)/test_bytes_run: test/wcc/967_bytes_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_ascii_run: test/wcc/904_ascii_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_decimal_run: test/wcc/922_decimal_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package ascii;
|
||||
|
||||
import strings;
|
||||
|
||||
export fn isdigit(c: rune) bool = {
|
||||
if (c < 48) { return false; };
|
||||
if (c > 57) { return false; };
|
||||
@@ -134,3 +136,48 @@ export fn strcasecmp(a: str, b: str) i32 = {
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// strlower — ASCII-lowercased copy of s, newly allocated.
|
||||
// Byte-wise fold: ASCII case-fold only touches bytes <0x80; UTF-8
|
||||
// multibyte bytes are >=0x80 and pass through unchanged, so byte-wise
|
||||
// equals Hare's rune fold and is length-preserving.
|
||||
// _buf variants deferred — ww has no nomem-value form / static-append
|
||||
// builtin; restore Hare's two-tier delegation when they land (#230).
|
||||
// ref/hare/ascii/string.ha:11.
|
||||
export fn strlower(s: str) (str | nomem) = {
|
||||
// empty bypass: ww alloc([],0) routes through nomem; Hare allocs 0
|
||||
// and zero-loops (ref/hare/ascii/string.ha)
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = tolower(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strupper — ASCII-uppercased copy of s, newly allocated.
|
||||
// ref/hare/ascii/string.ha:33.
|
||||
export fn strupper(s: str) (str | nomem) = {
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = toupper(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
41
lib/ascii/asciitest.ww
Normal file
41
lib/ascii/asciitest.ww
Normal file
@@ -0,0 +1,41 @@
|
||||
// asciitest — exercises lib/ascii case folding. Run with
|
||||
// `ww run lib/ascii/asciitest.ww`. Same signalled-then-fail()-with-+10
|
||||
// pattern as bytestest: a non-zero exit pinpoints the failing scenario.
|
||||
//
|
||||
// Vectors mirror Hare's @test fn strcasecmp in ref/hare/ascii/string.ha.
|
||||
|
||||
package ascii;
|
||||
|
||||
import ascii;
|
||||
import os;
|
||||
|
||||
let signalled: i32 = 0;
|
||||
fn fail() void = { os.exit(signalled + 10); };
|
||||
|
||||
// checkfold — one table row: strlower(in) == lo and strupper(in) == up.
|
||||
fn checkfold(in: str, lo: str, up: str) void = {
|
||||
match (ascii.strlower(in)) {
|
||||
case let got: str => { if (got != lo) { fail(); }; };
|
||||
case nomem => { fail(); };
|
||||
};
|
||||
match (ascii.strupper(in)) {
|
||||
case let got: str => { if (got != up) { fail(); }; };
|
||||
case nomem => { fail(); };
|
||||
};
|
||||
};
|
||||
|
||||
// ref/hare/ascii/string.ha:70 case-fold vectors. The こ row pins that a
|
||||
// UTF-8 multibyte sequence (all bytes >=0x80) passes through unchanged.
|
||||
@test fn strfold_cases() void = {
|
||||
signalled = 100; checkfold("ABC", "abc", "ABC");
|
||||
signalled = 101; checkfold("abc", "abc", "ABC");
|
||||
signalled = 102; checkfold("[[[", "[[[", "[[[");
|
||||
signalled = 103; checkfold("こ", "こ", "こ");
|
||||
signalled = 104; checkfold("", "", "");
|
||||
signalled = 105; checkfold("aB1z", "ab1z", "AB1Z");
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; strfold_cases();
|
||||
return 0;
|
||||
};
|
||||
@@ -4290,6 +4290,8 @@ let POW5_TABLE: [26]u64 = [
|
||||
|
||||
package ascii;
|
||||
|
||||
import strings;
|
||||
|
||||
export fn isdigit(c: rune) bool = {
|
||||
if (c < 48) { return false; };
|
||||
if (c > 57) { return false; };
|
||||
@@ -4420,6 +4422,51 @@ export fn strcasecmp(a: str, b: str) i32 = {
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// strlower — ASCII-lowercased copy of s, newly allocated.
|
||||
// Byte-wise fold: ASCII case-fold only touches bytes <0x80; UTF-8
|
||||
// multibyte bytes are >=0x80 and pass through unchanged, so byte-wise
|
||||
// equals Hare's rune fold and is length-preserving.
|
||||
// _buf variants deferred — ww has no nomem-value form / static-append
|
||||
// builtin; restore Hare's two-tier delegation when they land (#230).
|
||||
// ref/hare/ascii/string.ha:11.
|
||||
export fn strlower(s: str) (str | nomem) = {
|
||||
// empty bypass: ww alloc([],0) routes through nomem; Hare allocs 0
|
||||
// and zero-loops (ref/hare/ascii/string.ha)
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = tolower(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strupper — ASCII-uppercased copy of s, newly allocated.
|
||||
// ref/hare/ascii/string.ha:33.
|
||||
export fn strupper(s: str) (str | nomem) = {
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = toupper(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
|
||||
@@ -2306,143 +2306,6 @@ let POW5_TABLE: [26]u64 = [
|
||||
59604644775390625u64, 298023223876953125u64,
|
||||
];
|
||||
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
// inline; they are expected to inline to a couple of compares.
|
||||
|
||||
package ascii;
|
||||
|
||||
export fn isdigit(c: rune) bool = {
|
||||
if (c < 48) { return false; };
|
||||
if (c > 57) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isupper(c: rune) bool = {
|
||||
if (c < 65) { return false; };
|
||||
if (c > 90) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn islower(c: rune) bool = {
|
||||
if (c < 97) { return false; };
|
||||
if (c > 122) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isalpha(c: rune) bool = {
|
||||
if (isupper(c)) { return true; };
|
||||
return islower(c);
|
||||
};
|
||||
|
||||
export fn isalnum(c: rune) bool = {
|
||||
if (isalpha(c)) { return true; };
|
||||
return isdigit(c);
|
||||
};
|
||||
|
||||
// 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'
|
||||
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 >= 97) {
|
||||
if (c <= 102) { return true; }; // 'a'..'f'
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
export fn toupper(c: rune) rune = {
|
||||
if (islower(c)) { return c - 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -4420,6 +4283,190 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
// inline; they are expected to inline to a couple of compares.
|
||||
|
||||
package ascii;
|
||||
|
||||
import strings;
|
||||
|
||||
export fn isdigit(c: rune) bool = {
|
||||
if (c < 48) { return false; };
|
||||
if (c > 57) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isupper(c: rune) bool = {
|
||||
if (c < 65) { return false; };
|
||||
if (c > 90) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn islower(c: rune) bool = {
|
||||
if (c < 97) { return false; };
|
||||
if (c > 122) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isalpha(c: rune) bool = {
|
||||
if (isupper(c)) { return true; };
|
||||
return islower(c);
|
||||
};
|
||||
|
||||
export fn isalnum(c: rune) bool = {
|
||||
if (isalpha(c)) { return true; };
|
||||
return isdigit(c);
|
||||
};
|
||||
|
||||
// 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'
|
||||
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 >= 97) {
|
||||
if (c <= 102) { return true; }; // 'a'..'f'
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
export fn toupper(c: rune) rune = {
|
||||
if (islower(c)) { return c - 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// strlower — ASCII-lowercased copy of s, newly allocated.
|
||||
// Byte-wise fold: ASCII case-fold only touches bytes <0x80; UTF-8
|
||||
// multibyte bytes are >=0x80 and pass through unchanged, so byte-wise
|
||||
// equals Hare's rune fold and is length-preserving.
|
||||
// _buf variants deferred — ww has no nomem-value form / static-append
|
||||
// builtin; restore Hare's two-tier delegation when they land (#230).
|
||||
// ref/hare/ascii/string.ha:11.
|
||||
export fn strlower(s: str) (str | nomem) = {
|
||||
// empty bypass: ww alloc([],0) routes through nomem; Hare allocs 0
|
||||
// and zero-loops (ref/hare/ascii/string.ha)
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = tolower(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strupper — ASCII-uppercased copy of s, newly allocated.
|
||||
// ref/hare/ascii/string.ha:33.
|
||||
export fn strupper(s: str) (str | nomem) = {
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = toupper(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
|
||||
@@ -2306,143 +2306,6 @@ let POW5_TABLE: [26]u64 = [
|
||||
59604644775390625u64, 298023223876953125u64,
|
||||
];
|
||||
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
// inline; they are expected to inline to a couple of compares.
|
||||
|
||||
package ascii;
|
||||
|
||||
export fn isdigit(c: rune) bool = {
|
||||
if (c < 48) { return false; };
|
||||
if (c > 57) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isupper(c: rune) bool = {
|
||||
if (c < 65) { return false; };
|
||||
if (c > 90) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn islower(c: rune) bool = {
|
||||
if (c < 97) { return false; };
|
||||
if (c > 122) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isalpha(c: rune) bool = {
|
||||
if (isupper(c)) { return true; };
|
||||
return islower(c);
|
||||
};
|
||||
|
||||
export fn isalnum(c: rune) bool = {
|
||||
if (isalpha(c)) { return true; };
|
||||
return isdigit(c);
|
||||
};
|
||||
|
||||
// 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'
|
||||
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 >= 97) {
|
||||
if (c <= 102) { return true; }; // 'a'..'f'
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
export fn toupper(c: rune) rune = {
|
||||
if (islower(c)) { return c - 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -4420,6 +4283,190 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
// inline; they are expected to inline to a couple of compares.
|
||||
|
||||
package ascii;
|
||||
|
||||
import strings;
|
||||
|
||||
export fn isdigit(c: rune) bool = {
|
||||
if (c < 48) { return false; };
|
||||
if (c > 57) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isupper(c: rune) bool = {
|
||||
if (c < 65) { return false; };
|
||||
if (c > 90) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn islower(c: rune) bool = {
|
||||
if (c < 97) { return false; };
|
||||
if (c > 122) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn isalpha(c: rune) bool = {
|
||||
if (isupper(c)) { return true; };
|
||||
return islower(c);
|
||||
};
|
||||
|
||||
export fn isalnum(c: rune) bool = {
|
||||
if (isalpha(c)) { return true; };
|
||||
return isdigit(c);
|
||||
};
|
||||
|
||||
// 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'
|
||||
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 >= 97) {
|
||||
if (c <= 102) { return true; }; // 'a'..'f'
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
export fn toupper(c: rune) rune = {
|
||||
if (islower(c)) { return c - 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// strlower — ASCII-lowercased copy of s, newly allocated.
|
||||
// Byte-wise fold: ASCII case-fold only touches bytes <0x80; UTF-8
|
||||
// multibyte bytes are >=0x80 and pass through unchanged, so byte-wise
|
||||
// equals Hare's rune fold and is length-preserving.
|
||||
// _buf variants deferred — ww has no nomem-value form / static-append
|
||||
// builtin; restore Hare's two-tier delegation when they land (#230).
|
||||
// ref/hare/ascii/string.ha:11.
|
||||
export fn strlower(s: str) (str | nomem) = {
|
||||
// empty bypass: ww alloc([],0) routes through nomem; Hare allocs 0
|
||||
// and zero-loops (ref/hare/ascii/string.ha)
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = tolower(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strupper — ASCII-uppercased copy of s, newly allocated.
|
||||
// ref/hare/ascii/string.ha:33.
|
||||
export fn strupper(s: str) (str | nomem) = {
|
||||
if (s.len == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let buf: []u8 = alloc([], s.len: u64)?;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
buf.ptr[i] = toupper(s[i]: rune): u8;
|
||||
i += 1;
|
||||
};
|
||||
buf.len = s.len;
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
|
||||
49
test/wcc/904_ascii_run.c
Normal file
49
test/wcc/904_ascii_run.c
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 904_ascii_run — execute the lib/ascii @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 967_bytes_run / 968_utf8_run:
|
||||
* asciitest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/ascii/asciitest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "ascii_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("ascii_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user