|
Important
|
Slice landed: ASCII case-folding. Fourth slice of the variable-string backend wall
( |
| Builtin | resolve.ml | typecheck.ml | interp.ml | codegen.ml (wasm) |
|---|---|---|---|---|
|
✓ |
✓ |
✓ |
✓ (slice 3) |
|
✓ |
✓ |
✓ |
was missing → added |
|
✓ |
✓ |
✓ |
was missing → added |
Both failed at codegen (UnboundVariable) before this slice.
Interp oracle: String.lowercase_ascii / String.uppercase_ascii — shift only
the ASCII letters, leave everything else (digits, punctuation, bytes >= 128)
unchanged. Both builtins share one handler; the transform is selected by name:
lowercase c = c + 32 * ((c >= 'A') & (c <= 'Z')) ;; 'A'..'Z' -> +32 uppercase c = c - 32 * ((c >= 'a') & (c <= 'z')) ;; 'a'..'z' -> -32
The in-range test is a 0/1 product (I32And of two I32{Ge,Le}S) multiplied
by 32, then added/subtracted — branchless, so no per-byte If block inside
the loop. Bytes outside the letter range (incl. non-ASCII bytes >= 128, read
zero-extended via I32Load8U) get (in_range = 0) and pass through unchanged,
exactly matching the ASCII-only semantics.
Structure (same runtime-length alloc + Block/Loop copy as slice 3, with the
transform spliced into the loop body):
dst = heap; heap += 4 + slen dst[0] = slen for k in 0..slen: c = src[4 + k] ;; I32Load8U dst[4 + k] = c (+/-) 32*((c>=lo)&(c<=hi)) ;; I32Store8
Same inputs through both backends agree across folding, non-letter
passthrough, and the exclusive 'A'..'Z' boundary. Interp oracle confirmed
against the real library API; wasm executed under Node.
| Expression | interp | wasm |
|---|---|---|
|
97 |
97 |
|
99 |
99 |
|
65 |
65 |
|
51 |
51 |
|
64 |
64 |
|
91 |
91 |
|
5 |
5 |
(scca = string_char_code_at, the slice-1 reader.) The @/[ probes pin
the boundary: shifting either would mean the range test was off by one.
The packed fixture tests/codegen/string_case.affine returns 4285492; both
backends produce it.
-
test/test_e2e.ml— group "E2E String-wall slice 4 (case-fold)": seven interp-oracle cases (lower, upper, non-letter passthrough, both boundary bytes, length preservation). Runs underdune runtest. -
tests/codegen/string_case.affine+tests/codegen/test_string_case.mjs— executable wasm parity, run bytools/run_codegen_wasm_tests.sh(CI).
Full tools/run_codegen_wasm_tests.sh run: all codegen WASM tests pass
(slices 1-4 string harnesses), no sibling regressions.
Case-insensitive normalisation (a very common string-gated operation — keyed lookups, comparisons, command parsing) now lowers to wasm. More structurally: this proves the slice-3 idiom carries per-byte transforms, not just verbatim copies — the template for the remaining byte-wise ops.
-
Concatenation —
string_concat/` on strings (allocate `4 + la + lb`, two copy loops). Needs compile-time string-vs-list dispatch for the `operator (lists already lowerOpConcat). -
string_find— substring search returning an index (read-side nested scan, no allocation; builds on slice-1 indexing). -
trim— leading/trailing whitespace strip (scan for bounds, then a slice-3-style copy of the inner range). -
slice— the polymorphic (array|string) JS-semantics variant with negative-index normalisation.
Each slice: add the codegen arm, an interp-parity e2e group, and a
tests/codegen/*.mjs executable check, then re-run the census and drop the
gated count.