Types.is_equatable splits from is_comparable: a string answers equal? now, bytewise, but still answers no to ordered? — there is no collation the language has picked, so < and friends keep the refusal they had. The comparison itself is one new runtime entry point, flan_str_eq (runtime/flan_rt.c), length-mismatch and same-pointer fast paths ahead of the memcmp, called identically from both backends: emit.ml pulls a string's ptr and length out of the %slice SSA value and calls it directly in the Eq/Ne arm; x86.ml adds an arm ahead of the generic scalar comparison that reaches it through call_native, flipping the answer for != the same way Not already flips a bool. test_flan.ml covers the checker side directly and through a generic instantiated at string, including the two different ways ordered? and equal? fail at that type. test/programs/string-eq.flan is the survey program — same pointer, differing lengths, equal content at distinct addresses (a literal against a fresh heap string), a difference in the last byte, and the empty-string cases — with acceptance rows for LLVM, -O0 and --x86 in test_acceptance.ml.
41 lines
1.9 KiB
Plaintext
41 lines
1.9 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
|
|
|
|
;; 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
|
|
0)
|