From f4bd723d929e53475f7990374dca7b7dfcbbdc42 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sun, 1 Nov 2026 10:33:55 -0800 Subject: Add Chapter 29 for tech debt cleanup with the parser split Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks. --- chapter29/src/main/smp/sys/adt.smp | 3 ++ chapter29/src/main/smp/sys/adt/bitset.smp | 57 ++++++++++++++++++++ chapter29/src/main/smp/sys/ary.smp | 24 +++++++++ chapter29/src/main/smp/sys/aryi64.smp | 82 +++++++++++++++++++++++++++++ chapter29/src/main/smp/sys/aryu8.smp | 87 +++++++++++++++++++++++++++++++ chapter29/src/main/smp/sys/char.smp | 8 +++ chapter29/src/main/smp/sys/io.smp | 64 +++++++++++++++++++++++ chapter29/src/main/smp/sys/libc.smp | 44 ++++++++++++++++ chapter29/src/main/smp/sys/scan.smp | 39 ++++++++++++++ 9 files changed, 408 insertions(+) create mode 100644 chapter29/src/main/smp/sys/adt.smp create mode 100644 chapter29/src/main/smp/sys/adt/bitset.smp create mode 100644 chapter29/src/main/smp/sys/ary.smp create mode 100644 chapter29/src/main/smp/sys/aryi64.smp create mode 100644 chapter29/src/main/smp/sys/aryu8.smp create mode 100644 chapter29/src/main/smp/sys/char.smp create mode 100644 chapter29/src/main/smp/sys/io.smp create mode 100644 chapter29/src/main/smp/sys/libc.smp create mode 100644 chapter29/src/main/smp/sys/scan.smp (limited to 'chapter29/src/main/smp/sys') diff --git a/chapter29/src/main/smp/sys/adt.smp b/chapter29/src/main/smp/sys/adt.smp new file mode 100644 index 00000000..a4fe40bf --- /dev/null +++ b/chapter29/src/main/smp/sys/adt.smp @@ -0,0 +1,3 @@ + +// Abstract data types. +val bitset = bitset; diff --git a/chapter29/src/main/smp/sys/adt/bitset.smp b/chapter29/src/main/smp/sys/adt/bitset.smp new file mode 100644 index 00000000..d02cd2fe --- /dev/null +++ b/chapter29/src/main/smp/sys/adt/bitset.smp @@ -0,0 +1,57 @@ + +// A set of non-negative integers, represented one bit per value. +struct bitset { + i64[] !_bits; + + new bitset = { -> + _bits = new i64[1]; + }; + + // Return 0 for missing, non-zero for true + val get = { int bit -> + if( (bit >> 6) >= _bits# ) return false; + i64 mask = 1 << (bit & 63); + return (_bits[bit >> 6] & mask); + }; + + val _grow = { int bit -> + int x = bit >> 6; + int len = _bits#; + if( x < len ) return self; + while( x >= len ) len *= 2; + var bits = new i64[len]; + for( int i=0; i<_bits#; i++ ) + bits[i] = _bits[i]; + _bits = bits; + return self; + }; + + // Set + val set = { int bit -> + i64 mask = 1 << (bit & 63); + _grow(bit)._bits[bit >> 6] |= mask; + return self; + }; + + // Test and set + val tset = { int bit -> + int idx = bit >> 6; + i64 mask = 1 << (bit & 63); + if( _grow(bit)._bits[idx] & mask ) + return true; // Already set + _bits[idx] |= mask; + return false; // Was false (and now set) + }; + + val clr = { int bit -> + if( (bit >> 6) >= _bits# ) return self; + i64 mask = 1 << (bit & 63); + _bits[bit >> 6] &= (mask ^ -1); + return self; + }; + + val clear = { -> + for( int i=0; i<_bits#; i++ ) _bits[i] = 0; + return self; + }; +}; diff --git a/chapter29/src/main/smp/sys/ary.smp b/chapter29/src/main/smp/sys/ary.smp new file mode 100644 index 00000000..8312e94c --- /dev/null +++ b/chapter29/src/main/smp/sys/ary.smp @@ -0,0 +1,24 @@ + +// Array utilities + +// Copy byte array contents. Copies the shorter of the buffers and len +val copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> + if( len > dst# - doff ) len = dst# - doff; + if( len > src# - soff ) len = src# - soff; + for( int i=0; i0 for first non-matching byte. Returns <0 or >0 for +// the larger array after that, or 0 if both arrays are fully equal. +val cmp = { u8[~] src1, u32 off1, u8[~] src2, u32 off2, u32 len -> + u32 len1 = len > src1# - off1 ? src1# - off1 : len; + u32 len2 = len > src2# - off2 ? src2# - off2 : len; + u32 max = len1 > len2 ? len2 : len1; + for( int i=0; i + buf = b; + }; + + // Grow as needed + val _grow = { int len2 -> + if( len+len2 < buf# ) return self; + int newlen = buf# * 2; + while( len+len2 >= newlen ) newlen *= 2; + i64[] !buf2 = new i64[newlen]; + for( int i=0; i + _grow(1).buf[len++] = c; + self; + }; + + val get = { int index -> return buf[index]; }; + + // Write the bracketed array-list form to aryu8. + val str = { -> + // TODO: clunky default constructor syntax + var str = new aryu8().add('['); + for( int i=0; i + i64 tmp = buf[i]; + buf[i] = buf[j]; + buf[j] = tmp; + self; + }; +}; + + + +// Parse a comma-separated list of numbers, wrapped with square brackets, +// with optional whitespace e.g. "[ -17, 0,99,12456789 ] ". +// parseAryI64 & aryi64.str are bijections modulo whitespace. +// Returns null if the parse failed, without giving a clue as to why. +val parseAryI64 = { u8[~] str -> + !scan !s = new scan(str); + if( !s.match('[') ) return null; + // Array of parsed results + var nums = new aryi64(new i64[1]); + + // Parse a list of numbers + if( s.match(']') ) // Handle the empty list + return nums; + + // Parse number commas "123, " until something else + while( s.x < s.buf# ) { + nums.add(s.number()); + if( s.bad ) return null; + if( !s.match(',') ) + break; + }; + // Close the list + if( !s.match(']') ) return null; + s.skip(); + return s.x == s.buf# ? nums : null; +}; + +val copy = { i64[] dst, int doff, i64[~] src, int soff, int len -> + for( int i=0; i buf = new u8[1]; }; + + // Grow as needed + val _grow = { int len2 -> + if( len+len2 < buf# ) return self; + int newlen = buf# * 2; + while( len+len2 >= newlen ) newlen *= 2; + u8[] !buf2 = new u8[newlen]; + ary.copy(buf2,0,buf,0,buf#); + buf = buf2; + self; + }; + + val clear = { -> len = 0; self; }; + + // Add a character to aryu8 + val add = { u8 c -> + _grow(1).buf[len++] = c; + self; + }; + + // Add all of a plain array to the aryu8 + val addAry = { u8[~] xs -> + _grow(xs#); + ary.copy(buf,len,xs,0,xs#); + len += xs#; + self; + }; + + // Remove x bytes from the tail. + val undo = { int x -> + len = len >= x ? len-x : 0; + self; + }; + + // Print ASCII number into aryu8 + val num = { i64 x -> + if( !x ) { add('0'); return self; } + if( x < 0 ) { add('-'); x = -x; } + i64 ten = 1, log = 0; + while( ten <= x ) { ten *= 10; log++; } + for( int i=0; i + i64 ptr = buf; // Cast buffer pointer to i64 for native C call + libc.write(fd,ptr,len); + }; +}; + + +// Parse one integer, ignore trailing data; returns 0 with an empty string. +// If you want a more robust parsing, use the `scan` class +val atoi = { u8[~] str -> + int x = 0; + while( x < str# && str[x] <= ' ' ) x++; + if( x == str# ) return 0; + bool neg = false; + if( str[x]=='-' ) { neg=true; x++; } + i64 n = 0; + while( x < str# && '0' <= str[x] <= '9' ) + n = n*10 + str[x++]-'0'; + return neg ? -n : n; +}; + +// Zero-terminate string for libc strings. No error checking, just copies and +// appends a zero. +{ u8[~] -> u8[] } str0 = { u8[~] str -> + u8[] !strz = new u8[str#+1]; + ary.copy(strz,0,str,0,str#); + strz[str#] = '\0'; + strz; +}; diff --git a/chapter29/src/main/smp/sys/char.smp b/chapter29/src/main/smp/sys/char.smp new file mode 100644 index 00000000..22ca4213 --- /dev/null +++ b/chapter29/src/main/smp/sys/char.smp @@ -0,0 +1,8 @@ + +val toupper = { u8 c -> + return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c; +}; + +val tolower = { u8 c -> + return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; +}; diff --git a/chapter29/src/main/smp/sys/io.smp b/chapter29/src/main/smp/sys/io.smp new file mode 100644 index 00000000..d6263ffe --- /dev/null +++ b/chapter29/src/main/smp/sys/io.smp @@ -0,0 +1,64 @@ + +// Simple I/O Library + +// Write byte array to stdout +val p = { u8[~] str -> write(1,str); }; +val p_noInline = { u8[~] str -> write(1,str); }; + +// Return fd contents as a u8[]. +val read = { i32 fd -> + var buf = new u8[8*1024]; + i64 ptr = buf; + int len = 0; + while( true ) { + i32 rez = libc.read(fd,ptr,buf#); + if( rez > buf# ) libc._exit(libc.errno()); + len += rez; + if( rez < buf# ) + break; + libc._exit(-2); // Need to implement for more than 8K input + } + var out = new u8[len]; + ary.copy(out,0,buf,0,len); +}; + +// Write all of str to fd. +val write = { i32 fd, u8[~] str -> + i64 ptr = str; // Convert array pointer to a i64 for the native call + libc.write(fd,ptr,str#); +}; + +// Write all of str to fd. +val writeOff = { i32 fd, u8[~] str, u32 off, u32 len -> + i64 ptr = str; // Convert array pointer to a i64 for the native call + ptr += off; // Skip offset + // Cap at available length + if( len > str# - off ) len = str# - off; + // Do the write + libc.write(fd,ptr,len); +}; + +// Return stdin as a u8[]. +val stdin = { -> read(0); }; + +// Create file for writing. Return fd, or -1 and errno has the error code +val create = { u8[~] str -> + i64 ptr = sys.aryu8.str0(str); + i32 perms = 6*64 + 6*8 + 6; // 0666 + return libc.creat(ptr, perms); +}; + +// Open file for reading. Return fd, or -1 and errno has the error code +val open = { u8[~] str -> + i64 ptr = sys.aryu8.str0(str); + return libc.open(ptr, sys.libc.O_RDONLY, 0); +}; + +// Close an open fd. Return 0, or -1 and errno has the error code +val close = { i32 fd -> return libc.close(fd); }; + +// Remove a file. Return 0, or -1 and errno has the error code +val remove = { u8[~] str -> + i64 ptr = sys.aryu8.str0(str); + return libc.unlink(ptr); +}; diff --git a/chapter29/src/main/smp/sys/libc.smp b/chapter29/src/main/smp/sys/libc.smp new file mode 100644 index 00000000..17ee65cb --- /dev/null +++ b/chapter29/src/main/smp/sys/libc.smp @@ -0,0 +1,44 @@ + +// libc bindings: +// https://www.man7.org/linux/man-pages/man2/.2.html + +// The buffers are int-versions of zero-terminated strings. +// int buf = aryu8.str0("some_file"); +// buf mode perm-> fd +{ i64 i32 i32 -> i32 } open = "C"; +// buf perm -> fd +{ i64 i32 -> i32 } creat = "C"; + +// fd buf len -> len +{ i32 i64 u32 -> i32 } write = "C"; +// fd buf len -> len +{ i32 i64 u32 -> i32 } read = "C"; +// fd -> err +{ i32 -> i32 } close = "C"; +// path -> err +{ i64 -> i32 } unlink = "C"; + +i32 O_RDONLY = 0; +i32 O_WRONLY = 1; +i32 O_RDWR = 2; +i32 O_CREAT = 512; // 02000 on x86_64-pc-cygwin +i32 O_TRUNC = 1024; // 04000 on x86_64-pc-cygwin + +i32 STDIN = 0; +i32 STDOUT = 1; +i32 STDERR = 2; + +// status +{ i32 -> i32 } _exit = "C"; +// errno is not an ordinary C data symbol; read it through the native runtime. +{ i32 } simple_errno = "C"; +val errno = { -> simple_errno(); }; +// addr len prot flags fd off -> void* +{ i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C"; +// mmap flags +i32 PROT_EXEC = 4; +i32 PROT_READ = 1; +i32 PROT_WRITE= 2; +i32 PROT_NONE = 0; +i32 MAP_PRIVATE = 2; +i32 MAP_ANON = 32; diff --git a/chapter29/src/main/smp/sys/scan.smp b/chapter29/src/main/smp/sys/scan.smp new file mode 100644 index 00000000..44bedaaa --- /dev/null +++ b/chapter29/src/main/smp/sys/scan.smp @@ -0,0 +1,39 @@ + +// Scanner; a simple string parser. +struct scan { + int !x; // Cursor + bool !bad; // True after a malformed token + u8[~] buf; // Buffer to parse + + new scan = { u8[~] b -> buf = b; }; + + // Skip whitespace + val skip = { -> + while( x < buf# && buf[x] <= ' ' ) + x++; + return self; + }; + + // True if find required character, and skip it; else False. + val match = { u8 c -> + skip(); + if( x >= buf# ) return false; + if( buf[x] != c ) return false; + x++; + return true; + }; + + // Parse a simple number + val number = { -> + skip(); + bool neg = match('-'); + if( x >= buf# || buf[x] < '0' || '9' < buf[x] ) { + bad = true; + return 0; + } + i64 n = 0; + while( x < buf# && '0' <= buf[x] & buf[x] <= '9' ) + n = n*10 + (buf[x++] - '0'); + return neg ? -n : n; + }; +}; -- cgit v1.2.3