|
7 | 7 |
|
8 | 8 | prelude |
9 | 9 | public import Lean.Meta.WHNF |
| 10 | +import Init.Data.String.Bootstrap |
| 11 | +import Init.Data.UInt.Lemmas |
10 | 12 |
|
11 | 13 | public section |
12 | 14 |
|
| 15 | +/-- |
| 16 | +Returns `true` if the characters of `a` occur in `b` in order (not necessarily contiguously), |
| 17 | +compared case-insensitively via `Char.toLower`. |
| 18 | +
|
| 19 | +This function assumes that `a` and `b` have size at most `USize.size`, which should be a safe |
| 20 | +assumption. |
| 21 | +-/ |
13 | 22 | partial def Lean.String.charactersIn (a b : String) : Bool := |
14 | | - goFastScalar ⟨0⟩ ⟨0⟩ |
| 23 | + goFastScalar a.utf8ByteSize.toUSize b.utf8ByteSize.toUSize 0 0 rfl rfl |
15 | 24 | where |
16 | 25 | /- |
17 | | - This function is the ASCII fast path for `go` |
| 26 | + This function is the ASCII fast path for `go`. It caches the two case variants of the current |
| 27 | + pattern byte in `lower`/`upper` (via `scan`) so that the hot loop scanning `b` only performs a |
| 28 | + bounds check, a byte load, and two comparisons per byte. |
18 | 29 | -/ |
19 | | - goFastScalar (aPos bPos : String.Pos.Raw) : Bool := |
20 | | - if ha : ¬aPos < a.rawEndPos then |
21 | | - true |
22 | | - else if hb : ¬bPos < b.rawEndPos then |
23 | | - false |
| 30 | + goFastScalar (aSize bSize aPos bPos : USize) |
| 31 | + (ha : aSize = a.utf8ByteSize.toUSize) (hb : bSize = b.utf8ByteSize.toUSize) : Bool := |
| 32 | + if haPos : aPos < aSize then |
| 33 | + let aByte := String.Internal.ugetUTF8Byte a aPos |
| 34 | + (Std.lt_of_lt_of_le (USize.lt_iff_toNat_lt.1 haPos) (ha ▸ USize.toNat_ofNat_le)) |
| 35 | + -- If `a` contains a non-ASCII byte we give up on the fast path |
| 36 | + if aByte &&& 0x80 != 0 then |
| 37 | + go ⟨aPos.toNat⟩ ⟨bPos.toNat⟩ |
| 38 | + else |
| 39 | + scan aByte.toAsciiLower aByte.toAsciiUpper aSize bSize aPos bPos ha hb |
24 | 40 | else |
25 | | - let aByte := a.getUTF8Byte aPos (by simpa using ha) |
26 | | - let bByte := b.getUTF8Byte bPos (by simpa using hb) |
27 | | - -- If a or b are not UTF-8 bytes we give up on the fast path |
28 | | - if (aByte &&& 0x80 != 0) || (bByte &&& 0x80 != 0) then |
29 | | - go aPos bPos |
| 41 | + true |
| 42 | + |
| 43 | + scan (lower upper : UInt8) (aSize bSize aPos bPos : USize) |
| 44 | + (ha : aSize = a.utf8ByteSize.toUSize) (hb : bSize = b.utf8ByteSize.toUSize) : Bool := |
| 45 | + if hbPos : bPos < bSize then |
| 46 | + let bByte := String.Internal.ugetUTF8Byte b bPos |
| 47 | + (Std.lt_of_lt_of_le (USize.lt_iff_toNat_lt.1 hbPos) (hb ▸ USize.toNat_ofNat_le)) |
| 48 | + -- `lower` and `upper` are ASCII, so a non-ASCII byte can never match and it is fine to |
| 49 | + -- check for a match first |
| 50 | + if bByte == lower || bByte == upper then |
| 51 | + goFastScalar aSize bSize (aPos + 1) (bPos + 1) ha hb |
| 52 | + -- If `b` contains a non-ASCII byte we give up on the fast path |
| 53 | + else if bByte &&& 0x80 != 0 then |
| 54 | + go ⟨aPos.toNat⟩ ⟨bPos.toNat⟩ |
30 | 55 | else |
31 | | - let bPos := ⟨bPos.byteIdx + 1⟩ |
32 | | - if aByte.toAsciiLower == bByte.toAsciiLower then |
33 | | - let aPos := ⟨aPos.byteIdx + 1⟩ |
34 | | - goFastScalar aPos bPos |
35 | | - else |
36 | | - goFastScalar aPos bPos |
| 56 | + scan lower upper aSize bSize aPos (bPos + 1) ha hb |
| 57 | + else |
| 58 | + false |
37 | 59 |
|
38 | 60 | go (aPos bPos : String.Pos.Raw) : Bool := |
39 | 61 | if ha : aPos.atEnd a then |
|
0 commit comments