ADR 0018: Standard library toward C++ coverage
Status: accepted, 2026-10-03; second and third rounds 2026-10-04; fourth round 2026-10-07
Context
Phase 6’s exit criterion includes “Std library matches C++ coverage”, and doc 05 section 14 lists the scope. Before this change the builtins covered the core of <vector>, <map>, <optional> and <string>, with no sets, queues, heaps, file or clock access, float math beyond sqrt, or serialization. This ADR adds the missing pieces in both tiers (interpreter and native C) with identical results, in two rounds, and records what is still missing.
Decisions
| # | Decision | Reason |
|---|---|---|
| 1 | Builtins stay builtins: signatures live in crates/sspur-check/src/builtins.rs (STD_GLOBALS, STD_METHODS), the interpreter implements them in crates/sspur-eval/src/stdlib.rs, and native code in crates/sspur-native/src/cgen/stdlib.rs | One source of truth for types, and both tiers dispatch the same way existing builtins do. A library written in SSPUR would need module linking and could not reach the clock, files or argv |
| 2 | User definitions win: a user fn shadows a builtin global or method of the same name, and a user type Set, Heap or StrBuf shadows the builtin type. Builtin types are stored as #Set, #Heap, #StrBuf internally and printed without the # | Existing programs keep their meaning (the eval corpus defines type Json, and programs commonly define partition or gcd) |
| 3 | Set[T] is ordered and persistent. Native code reuses the Map[T, Unit] treap, so add remove has are O(log n); union inserts the smaller set into the larger, inter and diff filter the receiver; the interpreter uses BTreeSet. It prints as {1, 2} and compares as its sorted items | Same contract as Map; no new tree code |
| 4 | Heap[T] is a persistent min-heap: a leftist heap natively (push and pop O(log n), pop returns Opt[(min, rest)]), a sorted vector in the interpreter. It prints as heap[...] in sorted order and compares as its sorted items. Max-heaps and priorities use keys: push((-p, x)) | Persistence fits immutable values; leftist merges only walk right spines, and traversals are iterative, so deep left spines can’t overflow the stack |
| 5 | The deque is List: push_front is amortized O(1) natively (a front reservation in the buffer header, claimed like the existing end reservation), pop_front/pop_back and slice/take/drop are O(1) views | No new type to learn, and a queue loop (pop_front then push) runs 3M steps in milliseconds |
| 6 | List extras: binary_search lower_bound sort_with chunks windows group_by partition scan flatten index_of find_index slice push_front pop_front pop_back to_set to_heap, and range(start, end, step). sort_with takes an Int comparator and is the same stable top-down merge sort in both tiers (native uses an explicit stack), so comparator effects and traps happen in the same order. group_by keeps first-seen key order, like counts. scan returns the accumulator after each element | Identical comparison order is what makes tier parity hold even for inconsistent or effectful comparators |
| 7 | Strings: split_once index_of pad_left pad_right to_f64 bytes codes, globals from_bytes from_codes (strict UTF-8, none when invalid), F64.fmt(digits) (fixed point, 0..=20 digits, round half to even on the exact value in both tiers), and StrBuf (str_buf().add(s), .str, .byte_len), whose appends are amortized O(1) natively and whose .str is zero-copy. Indexes are in characters, like len, take and drop. to_f64 accepts [+-]digits[.digits][e[+-]digits] only, then converts with a correctly rounded parser | Str.find would collide with List.find, so the name is index_of; to_f64 mirrors to_int. A strict grammar avoids the differences between strtod and Rust’s parser (hex floats, inf, nan) |
| 8 | Math: F64 pow exp ln log2 log10 sin cos tan asin acos atan atan2 hypot ceil trunc is_nan is_finite, Int gcd lcm wrapping_add/sub/mul checked_add/sub/mul/div bnot popcount clz ctz (plus the existing band bor bxor shl shr), global clamp(x, lo, hi). ln instead of log because log is the logging effect. gcd/lcm trap on overflow; clz/ctz of 0 are 64 | Both tiers call the platform libm, so results are bit-identical on one machine (checked over 3,000 mixed inputs) |
| 9 | Random numbers are pure and seeded: rand(seed) -> (value, next) (non-negative), rand_int(seed, lo, hi) (half-open, traps if lo >= hi), rand_f64(seed) in [0, 1), all splitmix64 | Determinism and replay; no hidden global state |
| 10 | I/O is effects, which the checker requires in rows like any other: fs (read_file write_file append_file remove_file list_dir, all Res[_, Str]), io (read_line read_lines), time (now_ms mono_ns sleep_ms), env (env_var args). Errors are "{path}: not found" style with one errno table in both tiers; list_dir is sorted; files must be UTF-8; paths with NUL are rejected. Stdin is read unbuffered (one read(0) per byte) in both tiers, and the monotonic clock is clock_gettime in both | Tiers run in one process and call each other, so any buffer in one tier would hide input from the other, and the two must agree on clocks. sspur run file a b passes a b to args() |
| 11 | JSON is json.encode(v) -> Str and json.decode[T](s) -> Res[T, Str], using the deployer’s conventions (ADR 0016): records are objects in field order, nullary variants are "V", others {"tag": "V", ...}, Opt is the value or null, Map[Str, _] is an object and other maps are [[k, v], ...], Set/Heap/tuples are arrays, newtypes and Pii are their inner value, floats print shortest round-trip and non-finite as null. Decode errors read lines[0].qty: expected Int, found a string; syntax errors are invalid JSON | One wire format across services, files and tests. The checker records each call’s resolved type (CheckOutput.json_types), so encode is type-directed in both tiers (an empty Map[Int, _] is [], not {}); generic, function, secret and refined types are rejected (E_JSON) because decoding cannot check where clauses yet |
| 12 | Res gained native support (constructors, ok/err patterns, is_ok is_err get or map) and Res, Set, Heap values cross the interpreter/native boundary | Res is the result of every fallible std call, so without it those functions would stay in the interpreter |
| 13 | Native runtime helpers live in cgen/std_rt.c in named sections with dependencies and are emitted only when a program uses them | The generated C for every bench/native program is byte-identical to before this change, so benchmarks cannot get slower. A unit test compiles each section alone |
| 14 | Service endpoints may perform time and env; fs and io stay E_EP_EFFECT | Lambda has clocks and environment variables without extra IAM, but neither a durable filesystem nor stdin. deploy plan builds the bootstrap with the std runtime |
| 15 | Second round (2026-10-04), same rules: every new type is a builtin type that a user type of the same name shadows (HashMap HashSet Bits Time Duration BigInt Dec Regex), and its C runtime is a named section in std_rt.c emitted only on use. Pure algorithms that both tiers need for display or JSON (calendar math, bignum digits) live once in Rust (sspur-native/src/chrono.rs, bigint.rs) and once in C | A second implementation in C is the cost of native code; keeping the Rust side in one crate keeps the interpreter, JSON and the value boundary agreeing |
| 16 | HashMap[K, V] and HashSet[T] are persistent hash array mapped tries natively: 32-way nodes with separate data and child bitmaps, keys hashed with the type’s hash helper and compared with SSPUR equality, collision nodes after 64 bits, path copying on put/remove, singleton children folded back on delete. items keys values, display, comparison and JSON are in key order (sorted on demand), so the iteration order never depends on the hash. The interpreter stores them as its ordered Map/Set values | Lookups on 100k string keys are 5.5x faster than the treap Map (1M lookups: 38 ms against 211 ms); inserts are about 2x slower because every insert copies a wide path. Sorted iteration makes both tiers print the same thing |
| 17 | Regex is a Pike VM over code points (no backtracking, linear in the text): literals, ., classes with ranges and negation, \d \w \s (ASCII) and their negations, anchors ^ $ \b \B, groups, (?:...), alternation, greedy and lazy * + ? {n} {n,} {n,m}. Matching is leftmost-first like Perl; captures follow the automaton, so a repeated group that can match empty keeps its last non-empty capture (RE2 semantics). regex(p) returns Res[Regex, Str] with one of ten messages. Limits: repetition counts up to 1000, 100 groups, 20,000 instructions. Positions from span are in characters | Backtracking engines have exponential worst cases; one automaton design is small enough to write twice (Rust and C) with the same instruction set and thread priorities. 319 of 320 generated cases match Python’s re in ASCII mode; the exception is the empty-loop capture case above |
| 18 | Format specs follow Python’s mini-language: [[fill]align][sign][#][0][width][,][.precision][type] with d x X o b for Int, f e E % for F64, s for Str; negative hex is sign and magnitude; widths count characters; exponents have at least two digits. A bad spec traps with bad format spec '...' | Agents already know the syntax. Floats use snprintf in C and the exact formatter in Rust, which agree on rounding |
| 19 | Time and Duration are milliseconds in an i64, UTC only, with proleptic Gregorian calendar math (Hinnant’s algorithms), add_months clamping the day, ISO 8601 parse (`YYYY-MM-DD[THH:MM[:SS[.fff]]][Z | ±HH[:MM]]) and print, a strftime subset, and Go-style duration text (1h30m0s). Constructors that can be invalid return Opt`; overflow traps |
| 20 | BigInt is sign and 32-bit limbs with schoolbook multiply and Knuth’s algorithm D; division truncates like Int. Dec is a BigInt mantissa with a scale (0 to 10,000): add sub mul are exact, div(d, scale) and round(scale) round half to even. Equal values with different scales compare by value first, then by scale (1.5 < 1.50), as in Java’s BigDecimal. Results above 2^22 limbs trap out of memory. The constructor is decimal(s) because dec is a keyword | Exact money arithmetic without floats; any correct algorithm gives the same digits, and both tiers use the same one anyway |
| 21 | Bits is a fixed-size bitset (bits(n)), immutable like every SSPUR value, so set clear flip copy n/64 words. Out-of-range indexes and size mismatches trap. JSON is the C++ to_string form ("01001", bit 0 last) | Matches std::bitset semantics |
| 22 | Random distributions are pure functions of the seed like rand: rand_normal (Box-Muller from two uniform draws), rand_uniform, rand_exp, rand_bool, and List.shuffle(seed) (Fisher-Yates) and choice(seed). The C runtime splits a * b + c into separate statements so clang cannot fuse it into fma | Bit-identical results need identical rounding; contraction would change the last bit |
| 23 | range(a, b, step) is lazy where pipelines are fused: natively for x in range(...) is a counting loop and range(...).map(f).filter(g).sum (or .len, or collected) runs without building the range. Pipelines keep the 2^26-element limit so the interpreter, which materializes them, traps at the same size. Interpreter for loops over a..b and range(...) no longer build a list | Laziness that changes results would break tier parity; the loop forms are lazy in both tiers |
| 24 | New effects and calls: proc for run_cmd(prog, args, input) (PATH search, stdin, captured stdout and stderr, status or 128 + signal) and exit(code); fs gains read_bytes write_bytes mkdir mkdir_all remove_dir rename exists is_dir file_size modified_ms; io gains eprint. Errors reuse the errno table plus byte out of range and directory not empty. proc is not available to service endpoints | Child processes are an effect like any other I/O, so the checker shows them in signatures and IAM never has to guess |
| 25 | Math adds the rest of <cmath> that programs use: sinh cosh tanh asinh acosh atanh cbrt exp2 expm1 log1p erf erfc gamma lgamma fmod remainder copysign nextafter fdim fma is_inf, globals pi() euler() inf() nan(), and Int rotl rotr byteswap saturating_add/sub/mul. The interpreter calls these from libm through extern "C", as native code does | Same function on the same machine gives the same bits |
| 26 | Algorithms add upper_bound take_while drop_while rotate merge next_perm. <, <=, > and >= work on Time, Duration, BigInt and Dec | Closes the <algorithm> gaps (nth_element and partial_sort are sort plus take) |
| 27 | Third round: a std type that is plain data is a builtin record (STD_RECORDS: Rng Complex FlatMap MdSpan Zone File), stored as #Name and printed without the #. Layout, display, comparison, hashing, JSON and the tier boundary reuse the record code paths; only methods are new. Fields are readable, never writable (with on them is E_WITH_PATH), and field-name inference skips them, so user records keep their meaning | One new type costs its methods twice instead of a new value kind in eight places. Read-only fields mean values only come from the builtins, so methods can trust their invariants |
| 28 | Distributions: rand_binomial rand_poisson rand_geometric rand_gamma rand_beta rand_weighted, pure in the seed like rand. Gamma is Marsaglia-Tsang (with the u^(1/k) boost below 1), beta is two gammas, binomial and Poisson use Knuth’s beta and gamma splitting down to 16 then direct counting, so large n and means take O(log n) draws. Rng is a splittable SplitMix64 (state, gamma): rng(seed) reproduces the seed functions, split and stream(k) give independent streams, and every distribution is also a method returning (value, rng). Bad parameters trap | Pure and replayable like the existing rand; the C runtime turns off FP contraction in these functions so both tiers round identically |
| 29 | Complex is {re, im} with complex polar add sub mul div pow scale neg conj abs arg norm exp ln sqrt sin cos tan sinh cosh tanh asin acos atan asinh acosh atanh. Division is Smith’s algorithm, the inverse functions use the principal-branch log formulas, pow(0, 0) is 1 | Same formulas over libm in both tiers |
| 30 | FlatMap[K, V] is two sorted parallel lists (keys, values) with binary search: get has lower_bound between are O(log n) and between is an O(1) slice, put remove copy O(n), to_flat_map keeps the last value per key. MdSpan[T] is a view over a List with offset, shape and strides: mdspan(xs, shape) is row-major, get set slice sub transpose to_list rank size; slice, sub and transpose copy no elements. Bad indexes and shapes trap with the coordinate and extent | std::flat_map and std::mdspan semantics on persistent lists |
| 31 | View[T] is a lazy pipeline value: sources xs.view, iota(a, b), iterate(x, f, n); stages map filter take drop take_while drop_while enumerate zip chain; consumers to_list fold len sum first find any all and for. Stage functions must be pure (no effect row), so laziness is unobservable except for trap order, which both tiers fix to pull order. Natively a pipeline whose source and stages are visible at the consumer or for loop is one fused C loop with the lambdas inlined; a stored or passed view is a pull iterator over per-stage C functions. to_list keeps the 2^26-element limit. Views can’t be compared, encoded or cross the tier boundary | Views compose and travel like any value, and the common case costs no closures |
| 32 | Time zones: time_zone(name) (! time, Res[Zone, Str]) reads TZif v1 to v4 from $TZDIR or /usr/share/zoneinfo, including the POSIX TZ footer for instants after the last transition (M, J and day-of-year rules); UTC, Z and +HH[:MM] need no files, fixed_zone(minutes) is pure and local_zone() reads /etc/localtime. Zone has offset abbr is_dst local utc; utc(wall) is none in a gap and the earlier instant in an overlap; Time.format_in(z, pat) adds %z and %Z, iso_in writes the offset. Both tiers parse the same bytes with the same code shape; all 599 zones on macOS give identical results in both tiers and agree with Python’s zoneinfo on 6,000 sampled instants from 1900 to 2125 | The system database is the source of truth and needs no bundled data |
| 33 | File handles: open_file(path, mode) (r w a r+ w+ a+) and with_file(path, mode, f), which closes the handle when f returns or raises (a C cleanup attribute natively), all ! fs. Methods read(n) read_line read_all lines fold_lines write write_bytes seek tell size close return Res. There is no user-space buffer: read_line reads ahead with pread and seeks back, so a handle can move between tiers mid-file. Each handle keeps its device and inode, so using one after close (or after its descriptor is reused) is "{path}: file is closed" rather than touching another file | Effect-scoped like the rest of fs; the identity check makes stale handles safe without a handle table the two tiers would have to share |
| 34 | Locale-independent casing: upper and lower already use full Unicode mappings; fold_case (uppercase then lowercase, so ß matches ss and final sigma matches sigma) and compare_ci (code point order of the folded strings) are new. Locale-aware collation, number and date formatting stay out: they need CLDR data that differs between hosts (superseded by decision 37, which bundles the rules instead of reading host data) | Results never depend on the machine’s locale |
| 35 | Fourth round: Ratio is a builtin record {num: BigInt, den: BigInt}, always normalized (gcd 1, positive denominator, 0/1 for zero), so record equality, hashing and the tier boundary are value equality. Constructors ratio(n, d), ratio_big(n, d) and parse_ratio(s) ([+-]digits[/digits], none for a zero denominator); methods add sub mul div neg abs inv pow(Int) sign is_int floor ceil trunc round to_f64 to_dec(scale). < and sorting compare by cross-multiplication (the only std record with its own order), display is 3/4 or 5, round is half to even, to_f64 is correctly rounded (a 63-bit quotient with a sticky bit, then ldexp; values that land in the subnormal range can round twice). A zero denominator traps division by zero, oversized results out of memory. JSON encodes the fields; decoding is E_JSON because it could not check normalization | Exact rationals with the BigInt code both tiers already share; normalizing on every operation keeps == structural. C++’s std::ratio is compile-time only, so this is closer to Python’s Fraction |
| 36 | valarray is covered by pipelines, not a new type: elementwise binary ops are xs.view.zip(ys.view).map(...), scalar broadcast is xs.map(...), sum min max exist, masks are filter (or zip then filter), indirect arrays are idx.map(i => xs[i]), shift is drop plus concat, cshift is rotate, and slice/gslice are slice, strided range maps and MdSpan. Natively each of these is one fused loop (ADR 0008, decision 31). tests/programs/std_collections.ssp runs every idiom in both tiers | A second numeric array type would duplicate the fused paths for no speed gain |
| 37 | Locale is a builtin record {tag: Str} from locale(tag) (Opt), for exactly en-US en-GB de-DE fr-FR ja-JP hi-IN, passed explicitly; there is no ambient locale. Methods: compare(a, b) (-1, 0, 1), sort(xs), format_int(n), format_f64(x, digits) (fmt digits, then the locale’s separators), format_dec(d), format_date(t), format_date_long(t), format_time(t) (UTC fields; convert with a Zone first). The rules are bundled in crates/sspur-native/src/locale.rs: decimal and grouping separators (, ., . , in de-DE, U+202F and , in fr-FR, Indian 3-then-2 grouping in hi-IN), numeric, long and time patterns, month names and am/pm markers. The interpreter calls that module and the C runtime section locale reads tables generated from the same data at build time, so the tiers cannot drift | No system ICU or host locale data, so results are the same on every machine. Six locales cover the scripts the tests exercise (Latin with accents, CJK, Devanagari) |
| 38 | Collation is a UCA-style three-level comparison: primary weights for base letters, secondary for accents, tertiary for case (lowercase first), then code point order so only equal strings compare equal. Latin letters U+00C0 to U+017F decompose into a base letter and a mark whose secondary weight follows the DUCET order of the marks (acute, grave, breve, circumflex, caron, ring, diaeresis, double acute, tilde, dot, stroke, cedilla, ogonek, macron); combining marks U+0300 to U+036F get the same weights, so NFC and NFD text differ only at the last level; æ œ ß ij expand to two letters with a secondary difference. Spaces and punctuation sort before digits, digits before letters (Devanagari digits with the ASCII ones), and everything else after Latin in code point order. This is not full DUCET: ł đ ħ ŧ ø are their base letter plus a mark rather than separate letters, þ sorts after z, there is no tailoring (all six locales use these root rules; modern CLDR French and German standard order need none), kana and kanji are in code point order rather than JIS order, and Devanagari letters are in code point order | Correct for the Latin cases agents meet (accents, case, ligatures) with a table small enough to keep identical in two tiers; the limits are stated rather than hidden |
| 39 | fs gains copy_file(from, to) (contents and mode bits; same file when both name one file; a directory source is is a directory), symlink(target, link), read_link(path) (Res[Str, Str], not a symlink for other files), is_symlink(path) (does not follow), file_mode(path) (st_mode & 0o7777, following links) and set_mode(path, mode) (mode out of range outside 0..=0o7777). Errors name the path that failed and reuse the errno table | std::filesystem copy, symlink and permission calls with the same error style as the rest of fs |
Coverage against the C++ standard library
| Area (C++ headers) | SSPUR | Status |
|---|---|---|
Sequence containers (vector array deque list forward_list span) | List with O(1) views, amortized O(1) push and push_front, pop_front, pop_back, slice | Covered |
Ordered associative (map set multimap multiset) | Map, Set (persistent treaps) | Covered except multi-containers (use Map[K, List[V]]) |
Unordered associative (unordered_map unordered_set) | HashMap, HashSet (persistent HAMT natively) | Covered |
Adaptors (stack queue priority_queue) | List, Heap | Covered |
bitset, flat_map, mdspan | Bits, FlatMap, MdSpan (shape, strides, slices, transpose) | Covered |
Algorithms (<algorithm>, <numeric>) | sort, stable sort, comparator sort, sort_by, binary search, lower and upper bound, partition, unique, reverse, rotate, merge, next permutation, shuffle, min/max/clamp, find, any/all, take/drop while, fold/sum/scan, counts, group_by, chunks/windows, zip/enumerate, set algebra | Covered (nth_element, partial_sort as sort plus take) |
| Ranges and iterators | View values (map filter take drop take_while drop_while enumerate zip chain, iota, iterate) fused natively when visible, eager list pipelines, lazy range loops, generators (yield) | Covered except split/join/chunk views (eager on List) |
Utilities (optional variant tuple expected any functional) | Opt, sums, tuples, Res, lambdas and function values | Covered except any |
Strings (string string_view charconv) | Str methods, views via take/drop, to_int, to_f64, .str, StrBuf | Covered |
Formatting (format print) | Interpolation, .format(spec) on Int, F64, Str, fmt(digits), pad_left/pad_right | Covered |
Text encoding (codecvt, text_encoding) | UTF-8 bytes codes from_bytes from_codes, byte_len byte(i) | Covered for UTF-8 |
Regex (regex) | Regex Pike VM: match, find, span, find_all, captures, replace, split | Covered by a different model (no backreferences or lookaround, which need backtracking) |
Math (cmath numbers) | 44 F64 methods, pi euler inf nan | Covered except the C++17 special functions (Bessel, elliptic, Legendre) and frexp/ldexp |
Bits and integers (bit numeric, C++26 saturating) | Bitwise ops, popcount clz ctz rotl rotr byteswap gcd lcm, wrapping, checked and saturating ops | Covered |
Random (random) | Seeded SplitMix64 and a splittable Rng with independent streams: int, float, normal, uniform, exponential, Bernoulli, binomial, Poisson, geometric, gamma, beta, weighted, shuffle, choice | Covered except other engine families (mt19937) |
Big and exact numbers (complex valarray ratio, C++26 decimals) | Complex, BigInt, Dec, Ratio (runtime exact rationals), valarray operations as fused view and list pipelines | Covered (ratio at run time rather than compile time) |
Time (chrono) | Time, Duration, calendar parts, ISO 8601, month arithmetic, now, mono_ns, sleep_ms, IANA Zones with DST-aware conversion and formatting | Covered except leap-second clocks |
Files and streams (fstream filesystem iostream) | File handles (read, line folding, write, seek, tell, size, close, scoped with_file), whole-file text and binary I/O, list_dir mkdir mkdir_all remove_dir rename exists is_dir file_size modified_ms copy_file symlink read_link is_symlink file_mode set_mode, stdin lines, log, eprint | Covered |
Process and environment (cstdlib getenv, argv, system, exit) | env_var, args, run_cmd, exit | Covered |
Concurrency (thread atomic mutex future) | par, Atomic[Int], channels (ADR 0013) | Covered by a different model; no mutexes by design |
Memory (memory allocators, smart pointers) | GC values, res types, Ptr (ADR 0012) | Covered by a different model |
Errors (exception system_error stdexcept) | fail[E], Res, traps | Covered |
Locale (locale) | Locale-independent Unicode upper, lower, fold_case, compare_ci; explicit Locale values for en-US, en-GB, de-DE, fr-FR, ja-JP and hi-IN with bundled collation, number and date rules | Covered for the six bundled locales (no other locales, no host or ICU data; collation limits in decision 38) |
| Serialization (not in C++) | JSON encode and decode for every data type | Beyond C++ |
Summary: of 23 C++ areas, all 23 are covered (three by SSPUR’s own model, several with the exceptions listed). The fourth round closed the last partial area, locale, for six bundled locales, and the named gaps valarray, ratio, file copy, symlinks and permissions. None is missing.
Verification
- The library suite programs
tests/programs/std_collections.ssp,std_text.ssp,std_numbers.ssp,std_system.ssp,std_time.sspandjson.ssp(one section per library area of all four rounds, 333 tests in all) pass in both tiers with every function native. std_library_traps_are_identical_in_both_tiersandstd_extras_traps_are_identical_in_both_tiersincrates/sspur-cli/tests/suite.rscheck 20 trap messages in both tiers,std_round3_traps_are_identical_in_both_tiers19 more andstd_round4_traps_are_identical_in_both_tiers7 forRatioandLocale;exit_sets_the_status_in_both_tierschecksexit.sspur fuzz --differentialis clean on all 27 std programs, with and without--edge; the fuzzer generatesRng,Complex,FlatMap,MdSpan(sometimes transposed),View,Ratio(normalized),Locale(one of the six) andZone(13 IANA zones with odd rules, and fixed offsets). The second-round programs include generators that build format specs and regex patterns from integers, multi-limbBigIntdivision, and typed inputs (the fuzzer now generatesTime,Duration,Bits,HashMap,HashSet,BigIntandDec). Apostproperty over 3,000 cases checksq * d + r == nfordivmod, and a C harness under ASan and UBSan checks the HAMT against a reference with 200,000 random inserts and deletes under four weak hash functions, including full 64-bit collisions and persistence of old versions.- The eval corpus is unchanged: 199/199 programs identical, 270/270 functions native.
bench/nativegenerated C is byte-identical. The collation and separator rules also have unit tests incrates/sspur-native/src/locale.rs.
Not yet
Regex backreferences (by design), locales beyond the six bundled ones and full DUCET collation (decision 38), other random engines, and a builtin res file type for profile sys.