Constraints parsing peeled a body-leading map only when its first key was
literally :where; any other keyword fell through to the body, so a typo'd
key surfaced as a baffling error from inside what was meant as a predicate
and a stray map at body start compiled away silently. Any keyword-first map
is read as a constraint map now, but only when something follows it in the
body — a single-form map body is a real dyn value and not a discarded
statement, so that case is left alone.
An empty map literal still parses as a struct literal, (P {}) still meaning
the zero struct for a real struct name — the parser has no symbol table to
tell (take {}) apart from it at that point. check.ml now catches the case
where the name turns out to be a known function instead and says so, rather
than "unknown struct take".
flan_dyn.c's tag comment still said 6 and 7 were free; keywords and maps
took 4 and a kind field under BOX_OBJ, not new top-level tags, so 5, 6 and 7
are what is actually open for the interop handle. NEXT.md and json.flan both
still pointed at test/programs/arena-edn.flan, gone since edn/read stopped
taking an allocator; both now point at what replaced it.
flan_rt.c's flan_str_eq comment claimed the empty string literal was a
hypothetical null-pointer string; it isn't, its address is an interned
symbol's. The real case the zero-length guard exists for is a zero-length
container converted to a string. check.ml's ordering refusal said a string
has no comparison at all, which stopped being true when typed = and !=
grew strings in daed039 — split the message so an equality refusal and an
ordering refusal say the right noun, and updated the pinned rejects_check
rows to match. string-eq.flan gained the row the fast path most wants
tested, a slice against the prefix it was cut from sharing a base pointer at
different lengths, plus a != row at equal length with differing bytes;
acceptance now carries the real output, captured by running the program on
all three lanes. x86.ml's xor-1 comment now names the 0/1 return contract as
a requirement flan_str_eq must hold, not an incidental fact. SPIKE-DUPLICITY
now says plainly that its equality-and-ordering argument landed in daed039
and marks its transcript as the historical state that argument was made
against. FIX.org ticks M2 queue item 5.
And the acceptance runner: the tail check that turns a nonzero failure count
into exit 1 was already there and already fired — a fresh build with one row
broken already exited 1 before anything here changed. What wasn't proven is
that every path through the file's clang/wasmtime/raylib/lldb probes still
reaches that tail rather than skipping past rows that already failed. An
at_exit guard now closes that class regardless of which path the process
leaves by, flushing stdout first so a failing run's FAIL lines survive
Unix._exit rather than being dropped from the buffer. Verified both
directions with a deliberately broken row: dune test exits nonzero and the
log still carries the FAIL line and the failure count; restored, the same
run is exit 0 with nothing printed but green summaries. The other test
binaries were checked for the same gap and none have it — each gates its
own exit on a single failures ref that the tail already reads.
48 lines
2.3 KiB
Plaintext
48 lines
2.3 KiB
Plaintext
;;;; M2 queue item 5: typed = and != grow strings. Bytewise, with a
|
|
;;;; length-mismatch fast path and a same-pointer fast path ahead of the byte
|
|
;;;; loop (runtime/flan_rt.c, flan_str_eq). Ordering stays refused on a
|
|
;;;; string -- that half is tested in test_flan.ml, because a program that
|
|
;;;; wrote (< "a" "b") would not compile and so cannot be a row here.
|
|
|
|
(defn main [] i32
|
|
;; Same pointer: one local read twice is the same two words, ptr and len
|
|
;; both, and the fast path answers before a single byte is looked at.
|
|
(let [s "same"]
|
|
(println (= s s)) ; true
|
|
(println (!= s s))) ; false
|
|
|
|
;; Differing lengths: the length check alone settles it, and never reaches
|
|
;; the byte loop -- a common prefix would be no evidence otherwise.
|
|
(println (= "abc" "ab")) ; false
|
|
(println (!= "abc" "ab")) ; true
|
|
|
|
;; Equal contents, distinct pointers. "abc" the literal lives in the
|
|
;; read-only data section; to-lower of "ABC" is a fresh heap allocation,
|
|
;; so this pair shares no address and the same-pointer fast path cannot
|
|
;; fire -- what answers here is the byte loop, or the length check first
|
|
;; ruling nothing out since both are three bytes.
|
|
(let [heap (to-lower (bytes "ABC"))]
|
|
(let [h (string (as-slice heap))]
|
|
(println (= "abc" h)) ; true
|
|
(println (!= "abc" h)))
|
|
(free heap))
|
|
|
|
;; A one-byte difference at the end, so the length check cannot rule it
|
|
;; out and the byte loop has to run to the last byte before it can answer.
|
|
(println (= "abd" "abc")) ; false
|
|
(println (!= "abd" "abc")) ; true
|
|
|
|
;; Empty strings: the length check's zero case, which the runtime helper
|
|
;; also uses to skip a memcmp that would otherwise read through a null
|
|
;; pointer -- two empty string literals, and empty against non-empty.
|
|
(println (= "" "")) ; true
|
|
(println (= "" "a")) ; false
|
|
(println (= "a" "")) ; false
|
|
|
|
;; A slice and the prefix it was cut from: same base pointer, different
|
|
;; lengths -- the one pair the same-pointer fast path would answer wrong on
|
|
;; if it ran before the length check instead of after.
|
|
(let [s "abcd"]
|
|
(println (= s (string (slice (bytes s) 0 2))))) ; false
|
|
0)
|