feat: new String.Slice API (#10514)
This PR defines the new `String.Slice` API. Many of the core design principles of the API are taken over from Rust's [string library](https://doc.rust-lang.org/stable/std/string/struct.String.html).
This commit is contained in:
parent
5ef7b45afa
commit
5fd8c1b94d
11 changed files with 2287 additions and 0 deletions
|
|
@ -13,5 +13,7 @@ public import Init.Data.String.Extra
|
|||
public import Init.Data.String.Lemmas
|
||||
public import Init.Data.String.Repr
|
||||
public import Init.Data.String.Bootstrap
|
||||
public import Init.Data.String.Slice
|
||||
public import Init.Data.String.Pattern
|
||||
|
||||
public section
|
||||
|
|
|
|||
12
src/Init/Data/String/Pattern.lean
Normal file
12
src/Init/Data/String/Pattern.lean
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Henrik Böving
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Pattern.Basic
|
||||
public import Init.Data.String.Pattern.Char
|
||||
public import Init.Data.String.Pattern.String
|
||||
public import Init.Data.String.Pattern.Pred
|
||||
221
src/Init/Data/String/Pattern/Basic.lean
Normal file
221
src/Init/Data/String/Pattern/Basic.lean
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Henrik Böving
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Basic
|
||||
public import Init.Data.Iterators.Basic
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
/-!
|
||||
This module defines the notion of patterns which is central to the {name}`String.Slice` and
|
||||
{name}`String` API. All functions on {name}`String.Slice` and {name}`String` that
|
||||
"search for something" are polymorphic over a pattern instead of taking just one particular kind
|
||||
of pattern such as a {name}`Char`. The key components are:
|
||||
- {name (scope := "Init.Data.String.Pattern.Basic")}`ToForwardSearcher`
|
||||
- {name (scope := "Init.Data.String.Pattern.Basic")}`ForwardPattern`
|
||||
- {name (scope := "Init.Data.String.Pattern.Basic")}`ToBackwardSearcher`
|
||||
- {name (scope := "Init.Data.String.Pattern.Basic")}`SuffixPattern`
|
||||
-/
|
||||
|
||||
public section
|
||||
|
||||
namespace String.Slice.Pattern
|
||||
|
||||
/--
|
||||
A step taken during the traversal of a {name}`Slice` by a forward or backward searcher.
|
||||
-/
|
||||
inductive SearchStep (s : Slice) where
|
||||
/--
|
||||
The subslice starting at {name}`startPos` and ending at {name}`endPos` did not match the pattern.
|
||||
-/
|
||||
| rejected (startPos endPos : s.Pos)
|
||||
/--
|
||||
The subslice starting at {name}`startPos` and ending at {name}`endPos` did not match the pattern.
|
||||
-/
|
||||
| matched (startPos endPos : s.Pos)
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
Provides a conversion from a pattern to an iterator of {name}`SearchStep` searching for matches of
|
||||
the pattern from the start towards the end of a {name}`Slice`.
|
||||
-/
|
||||
class ToForwardSearcher (ρ : Type) (σ : outParam (Slice → Type)) where
|
||||
/--
|
||||
Build an iterator of {name}`SearchStep` corresponding to matches of {name}`pat` along the slice
|
||||
{name}`s`. The {name}`SearchStep`s returned by this iterator must contain ranges that are
|
||||
adjacent, non-overlapping and cover all of {name}`s`.
|
||||
-/
|
||||
toSearcher : (s : Slice) → (pat : ρ) → Std.Iter (α := σ s) (SearchStep s)
|
||||
|
||||
/--
|
||||
Provides simple pattern matching capabilities from the start of a {name}`Slice`.
|
||||
|
||||
While these operations can be implemented on top of {name}`ToForwardSearcher` some patterns allow
|
||||
for more efficient implementations so this class can be used to specialise for them. If there is no
|
||||
need to specialise in this fashion
|
||||
{name (scope := "Init.Data.String.Pattern.Basic")}`ForwardPattern.defaultImplementation` can be used
|
||||
to automatically derive an instance.
|
||||
-/
|
||||
class ForwardPattern (ρ : Type) where
|
||||
/--
|
||||
Checks whether the slice starts with the pattern.
|
||||
-/
|
||||
startsWith : Slice → ρ → Bool
|
||||
/--
|
||||
Checks whether the slice starts with the pattern, if it does return slice with the prefix removed,
|
||||
otherwise {name}`none`.
|
||||
-/
|
||||
dropPrefix? : Slice → ρ → Option Slice
|
||||
|
||||
namespace Internal
|
||||
|
||||
@[extern "lean_slice_memcmp"]
|
||||
def memcmp (lhs rhs : @& Slice) (lstart : @& String.Pos) (rstart : @& String.Pos)
|
||||
(len : @& String.Pos) (h1 : lstart + len ≤ lhs.utf8ByteSize)
|
||||
(h2 : rstart + len ≤ rhs.utf8ByteSize) : Bool :=
|
||||
go 0
|
||||
where
|
||||
go (curr : String.Pos) : Bool :=
|
||||
if h : curr < len then
|
||||
have hl := by
|
||||
simp [Pos.le_iff] at h h1 ⊢
|
||||
omega
|
||||
have hr := by
|
||||
simp [Pos.le_iff] at h h2 ⊢
|
||||
omega
|
||||
if lhs.getUtf8Byte (lstart + curr) hl == rhs.getUtf8Byte (rstart + curr) hr then
|
||||
go curr.inc
|
||||
else
|
||||
false
|
||||
else
|
||||
true
|
||||
termination_by len.byteIdx - curr.byteIdx
|
||||
decreasing_by
|
||||
simp at h ⊢
|
||||
omega
|
||||
|
||||
variable {ρ : Type} {σ : Slice → Type}
|
||||
variable [∀ s, Std.Iterators.Iterator (σ s) Id (SearchStep s)]
|
||||
variable [∀ s, Std.Iterators.Finite (σ s) Id]
|
||||
|
||||
/--
|
||||
Tries to skip the {name}`searcher` until the next {name}`SearchStep.matched` and return it. If no
|
||||
match is found until the end returns {name}`none`.
|
||||
-/
|
||||
@[inline]
|
||||
def nextMatch (searcher : Std.Iter (α := σ s) (SearchStep s)) :
|
||||
Option (Std.Iter (α := σ s) (SearchStep s) × s.Pos × s.Pos) :=
|
||||
go searcher
|
||||
where
|
||||
go [∀ s, Std.Iterators.Finite (σ s) Id] (searcher : Std.Iter (α := σ s) (SearchStep s)) :
|
||||
Option (Std.Iter (α := σ s) (SearchStep s) × s.Pos × s.Pos) :=
|
||||
match searcher.step with
|
||||
| .yield it (.matched startPos endPos) _ => some (it, startPos, endPos)
|
||||
| .yield it (.rejected ..) _ | .skip it .. => go it
|
||||
| .done .. => none
|
||||
termination_by Std.Iterators.Iter.finitelyManySteps searcher
|
||||
|
||||
/--
|
||||
Tries to skip the {name}`searcher` until the next {name}`SearchStep.rejected` and return it. If no
|
||||
reject is found until the end returns {name}`none`.
|
||||
-/
|
||||
@[inline]
|
||||
def nextReject (searcher : Std.Iter (α := σ s) (SearchStep s)) :
|
||||
Option (Std.Iter (α := σ s) (SearchStep s) × s.Pos × s.Pos) :=
|
||||
go searcher
|
||||
where
|
||||
go [∀ s, Std.Iterators.Finite (σ s) Id] (searcher : Std.Iter (α := σ s) (SearchStep s)) :
|
||||
Option (Std.Iter (α := σ s) (SearchStep s) × s.Pos × s.Pos) :=
|
||||
match searcher.step with
|
||||
| .yield it (.rejected startPos endPos) _ => some (it, startPos, endPos)
|
||||
| .yield it (.matched ..) _ | .skip it .. => go it
|
||||
| .done .. => none
|
||||
termination_by Std.Iterators.Iter.finitelyManySteps searcher
|
||||
|
||||
end Internal
|
||||
|
||||
namespace ForwardPattern
|
||||
|
||||
variable {ρ : Type} {σ : Slice → Type}
|
||||
variable [∀ s, Std.Iterators.Iterator (σ s) Id (SearchStep s)]
|
||||
variable [ToForwardSearcher ρ σ]
|
||||
|
||||
@[specialize pat]
|
||||
def defaultStartsWith (s : Slice) (pat : ρ) : Bool :=
|
||||
let searcher := ToForwardSearcher.toSearcher s pat
|
||||
match searcher.step with
|
||||
| .yield _ (.matched start ..) _ => s.startPos = start
|
||||
| _ => false
|
||||
|
||||
@[specialize pat]
|
||||
def defaultDropPrefix? (s : Slice) (pat : ρ) : Option Slice :=
|
||||
let searcher := ToForwardSearcher.toSearcher s pat
|
||||
match searcher.step with
|
||||
| .yield _ (.matched _ endPos) _ => some (s.replaceStart endPos)
|
||||
| _ => none
|
||||
|
||||
@[always_inline, inline]
|
||||
def defaultImplementation : ForwardPattern ρ where
|
||||
startsWith := defaultStartsWith
|
||||
dropPrefix? := defaultDropPrefix?
|
||||
|
||||
end ForwardPattern
|
||||
|
||||
/--
|
||||
Provides a conversion from a pattern to an iterator of {name}`SearchStep` searching for matches of
|
||||
the pattern from the end towards the start of a {name}`Slice`.
|
||||
-/
|
||||
class ToBackwardSearcher (ρ : Type) (σ : outParam (Slice → Type)) where
|
||||
/--
|
||||
Build an iterator of {name}`SearchStep` corresponding to matches of {lean}`pat` along the slice
|
||||
{name}`s`. The {name}`SearchStep`s returned by this iterator must contain ranges that are
|
||||
adjacent, non-overlapping and cover all of {name}`s`.
|
||||
-/
|
||||
toSearcher : (s : Slice) → (pat : ρ) → Std.Iter (α := σ s) (SearchStep s)
|
||||
|
||||
/--
|
||||
Provides simple pattern matching capabilities from the end of a {name}`Slice`.
|
||||
|
||||
While these operations can be implemented on top of {name}`ToBackwardSearcher` some patterns allow
|
||||
for more efficient implementations so this class can be used to specialise for them. If there is no
|
||||
need to specialise in this fashion
|
||||
{name (scope := "Init.Data.String.Pattern.Basic")}`BackwardPattern.defaultImplementation` can be
|
||||
used to automatically derive an instance.
|
||||
-/
|
||||
class BackwardPattern (ρ : Type) where
|
||||
endsWith : Slice → ρ → Bool
|
||||
dropSuffix? : Slice → ρ → Option Slice
|
||||
|
||||
namespace ToBackwardSearcher
|
||||
|
||||
variable {ρ : Type} {σ : Slice → Type}
|
||||
variable [∀ s, Std.Iterators.Iterator (σ s) Id (SearchStep s)]
|
||||
variable [ToBackwardSearcher ρ σ]
|
||||
|
||||
@[specialize pat]
|
||||
def defaultEndsWith (s : Slice) (pat : ρ) : Bool :=
|
||||
let searcher := ToBackwardSearcher.toSearcher s pat
|
||||
match searcher.step with
|
||||
| .yield _ (.matched _ endPos) _ => s.endPos = endPos
|
||||
| _ => false
|
||||
|
||||
@[specialize pat]
|
||||
def defaultDropSuffix? (s : Slice) (pat : ρ) : Option Slice :=
|
||||
let searcher := ToBackwardSearcher.toSearcher s pat
|
||||
match searcher.step with
|
||||
| .yield _ (.matched startPos _) _ => some (s.replaceEnd startPos)
|
||||
| _ => none
|
||||
|
||||
@[always_inline, inline]
|
||||
def defaultImplementation : BackwardPattern ρ where
|
||||
endsWith := defaultEndsWith
|
||||
dropSuffix? := defaultDropSuffix?
|
||||
|
||||
end ToBackwardSearcher
|
||||
|
||||
end String.Slice.Pattern
|
||||
160
src/Init/Data/String/Pattern/Char.lean
Normal file
160
src/Init/Data/String/Pattern/Char.lean
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Henrik Böving
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Pattern.Basic
|
||||
public import Init.Data.Iterators.Internal.Termination
|
||||
public import Init.Data.Iterators.Consumers.Monadic.Loop
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
/-!
|
||||
This module defines the necessary instances to register {name}`Char` with the pattern framework.
|
||||
-/
|
||||
|
||||
public section
|
||||
|
||||
namespace String.Slice.Pattern
|
||||
|
||||
structure ForwardCharSearcher (s : Slice) where
|
||||
currPos : s.Pos
|
||||
needle : Char
|
||||
deriving Inhabited
|
||||
|
||||
namespace ForwardCharSearcher
|
||||
|
||||
@[inline]
|
||||
def iter (s : Slice) (c : Char) : Std.Iter (α := ForwardCharSearcher s) (SearchStep s) :=
|
||||
{ internalState := { currPos := s.startPos, needle := c }}
|
||||
|
||||
instance (s : Slice) : Std.Iterators.Iterator (ForwardCharSearcher s) Id (SearchStep s) where
|
||||
IsPlausibleStep it
|
||||
| .yield it' out =>
|
||||
it.internalState.needle = it'.internalState.needle ∧
|
||||
∃ h1 : it.internalState.currPos ≠ s.endPos,
|
||||
it'.internalState.currPos = it.internalState.currPos.next h1 ∧
|
||||
match out with
|
||||
| .matched startPos endPos =>
|
||||
it.internalState.currPos = startPos ∧
|
||||
it'.internalState.currPos = endPos ∧
|
||||
it.internalState.currPos.get h1 = it.internalState.needle
|
||||
| .rejected startPos endPos =>
|
||||
it.internalState.currPos = startPos ∧
|
||||
it'.internalState.currPos = endPos ∧
|
||||
it.internalState.currPos.get h1 ≠ it.internalState.needle
|
||||
| .skip _ => False
|
||||
| .done => it.internalState.currPos = s.endPos
|
||||
step := fun ⟨currPos, needle⟩ =>
|
||||
if h1 : currPos = s.endPos then
|
||||
pure ⟨.done, by simp [h1]⟩
|
||||
else
|
||||
let nextPos := currPos.next h1
|
||||
let nextIt := ⟨nextPos, needle⟩
|
||||
if h2 : currPos.get h1 = needle then
|
||||
pure ⟨.yield nextIt (.matched currPos nextPos), by simp [h1, h2, nextIt, nextPos]⟩
|
||||
else
|
||||
pure ⟨.yield nextIt (.rejected currPos nextPos), by simp [h1, h2, nextIt, nextPos]⟩
|
||||
|
||||
def finitenessRelation : Std.Iterators.FinitenessRelation (ForwardCharSearcher s) Id where
|
||||
rel := InvImage WellFoundedRelation.rel
|
||||
(fun it => s.utf8ByteSize.byteIdx - it.internalState.currPos.offset.byteIdx)
|
||||
wf := InvImage.wf _ WellFoundedRelation.wf
|
||||
subrelation {it it'} h := by
|
||||
simp_wf
|
||||
obtain ⟨step, h, h'⟩ := h
|
||||
cases step
|
||||
· cases h
|
||||
obtain ⟨_, h1, h2, _⟩ := h'
|
||||
have h3 := Char.utf8Size_pos (it.internalState.currPos.get h1)
|
||||
have h4 := it.internalState.currPos.isValidForSlice.le_utf8ByteSize
|
||||
simp [Pos.ext_iff, String.Pos.ext_iff, Pos.le_iff] at h1 h2 h4
|
||||
omega
|
||||
· cases h'
|
||||
· cases h
|
||||
|
||||
instance : Std.Iterators.Finite (ForwardCharSearcher s) Id :=
|
||||
.of_finitenessRelation finitenessRelation
|
||||
|
||||
instance : Std.Iterators.IteratorLoop (ForwardCharSearcher s) Id Id :=
|
||||
.defaultImplementation
|
||||
|
||||
instance : ToForwardSearcher Char ForwardCharSearcher where
|
||||
toSearcher := iter
|
||||
|
||||
instance : ForwardPattern Char := .defaultImplementation
|
||||
|
||||
end ForwardCharSearcher
|
||||
|
||||
structure BackwardCharSearcher (s : Slice) where
|
||||
currPos : s.Pos
|
||||
needle : Char
|
||||
deriving Inhabited
|
||||
|
||||
namespace BackwardCharSearcher
|
||||
|
||||
@[inline]
|
||||
def iter (s : Slice) (c : Char) : Std.Iter (α := BackwardCharSearcher s) (SearchStep s) :=
|
||||
{ internalState := { currPos := s.endPos, needle := c }}
|
||||
|
||||
instance (s : Slice) : Std.Iterators.Iterator (BackwardCharSearcher s) Id (SearchStep s) where
|
||||
IsPlausibleStep it
|
||||
| .yield it' out =>
|
||||
it.internalState.needle = it'.internalState.needle ∧
|
||||
∃ h1 : it.internalState.currPos ≠ s.startPos,
|
||||
it'.internalState.currPos = it.internalState.currPos.prev h1 ∧
|
||||
match out with
|
||||
| .matched startPos endPos =>
|
||||
it.internalState.currPos = endPos ∧
|
||||
it'.internalState.currPos = startPos ∧
|
||||
(it.internalState.currPos.prev h1).get Pos.prev_ne_endPos = it.internalState.needle
|
||||
| .rejected startPos endPos =>
|
||||
it.internalState.currPos = endPos ∧
|
||||
it'.internalState.currPos = startPos ∧
|
||||
(it.internalState.currPos.prev h1).get Pos.prev_ne_endPos ≠ it.internalState.needle
|
||||
| .skip _ => False
|
||||
| .done => it.internalState.currPos = s.startPos
|
||||
step := fun ⟨currPos, needle⟩ =>
|
||||
if h1 : currPos = s.startPos then
|
||||
pure ⟨.done, by simp [h1]⟩
|
||||
else
|
||||
let nextPos := currPos.prev h1
|
||||
let nextIt := ⟨nextPos, needle⟩
|
||||
if h2 : nextPos.get Pos.prev_ne_endPos = needle then
|
||||
pure ⟨.yield nextIt (.matched nextPos currPos), by simp [h1, h2, nextIt, nextPos]⟩
|
||||
else
|
||||
pure ⟨.yield nextIt (.rejected nextPos currPos), by simp [h1, h2, nextIt, nextPos]⟩
|
||||
|
||||
def finitenessRelation : Std.Iterators.FinitenessRelation (BackwardCharSearcher s) Id where
|
||||
rel := InvImage WellFoundedRelation.rel
|
||||
(fun it => it.internalState.currPos.offset.byteIdx)
|
||||
wf := InvImage.wf _ WellFoundedRelation.wf
|
||||
subrelation {it it'} h := by
|
||||
simp_wf
|
||||
obtain ⟨step, h, h'⟩ := h
|
||||
cases step
|
||||
· cases h
|
||||
obtain ⟨_, h1, h2, _⟩ := h'
|
||||
have h3 := Pos.offset_prev_lt_offset (h := h1)
|
||||
simp [Pos.ext_iff, String.Pos.ext_iff] at h2 h3
|
||||
omega
|
||||
· cases h'
|
||||
· cases h
|
||||
|
||||
instance : Std.Iterators.Finite (BackwardCharSearcher s) Id :=
|
||||
.of_finitenessRelation finitenessRelation
|
||||
|
||||
instance : Std.Iterators.IteratorLoop (BackwardCharSearcher s) Id Id :=
|
||||
.defaultImplementation
|
||||
|
||||
instance : ToBackwardSearcher Char BackwardCharSearcher where
|
||||
toSearcher := iter
|
||||
|
||||
instance : BackwardPattern Char := ToBackwardSearcher.defaultImplementation
|
||||
|
||||
end BackwardCharSearcher
|
||||
|
||||
end String.Slice.Pattern
|
||||
162
src/Init/Data/String/Pattern/Pred.lean
Normal file
162
src/Init/Data/String/Pattern/Pred.lean
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Henrik Böving
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Pattern.Basic
|
||||
public import Init.Data.Iterators.Internal.Termination
|
||||
public import Init.Data.Iterators.Consumers.Monadic.Loop
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
/-!
|
||||
This module defines the necessary instances to register {lean}`Char → Bool` with the pattern
|
||||
framework.
|
||||
-/
|
||||
|
||||
public section
|
||||
|
||||
namespace String.Slice.Pattern
|
||||
|
||||
structure ForwardCharPredSearcher (s : Slice) where
|
||||
currPos : s.Pos
|
||||
needle : Char → Bool
|
||||
deriving Inhabited
|
||||
|
||||
namespace ForwardCharPredSearcher
|
||||
|
||||
@[inline]
|
||||
def iter (s : Slice) (p : Char → Bool) : Std.Iter (α := ForwardCharPredSearcher s) (SearchStep s) :=
|
||||
{ internalState := { currPos := s.startPos, needle := p }}
|
||||
|
||||
instance (s : Slice) : Std.Iterators.Iterator (ForwardCharPredSearcher s) Id (SearchStep s) where
|
||||
IsPlausibleStep it
|
||||
| .yield it' out =>
|
||||
it.internalState.needle = it'.internalState.needle ∧
|
||||
∃ h1 : it.internalState.currPos ≠ s.endPos,
|
||||
it'.internalState.currPos = it.internalState.currPos.next h1 ∧
|
||||
match out with
|
||||
| .matched startPos endPos =>
|
||||
it.internalState.currPos = startPos ∧
|
||||
it'.internalState.currPos = endPos ∧
|
||||
it.internalState.needle (it.internalState.currPos.get h1)
|
||||
| .rejected startPos endPos =>
|
||||
it.internalState.currPos = startPos ∧
|
||||
it'.internalState.currPos = endPos ∧
|
||||
¬ it.internalState.needle (it.internalState.currPos.get h1)
|
||||
| .skip _ => False
|
||||
| .done => it.internalState.currPos = s.endPos
|
||||
step := fun ⟨currPos, needle⟩ =>
|
||||
if h1 : currPos = s.endPos then
|
||||
pure ⟨.done, by simp [h1]⟩
|
||||
else
|
||||
let nextPos := currPos.next h1
|
||||
let nextIt := ⟨nextPos, needle⟩
|
||||
if h2 : needle <| currPos.get h1 then
|
||||
pure ⟨.yield nextIt (.matched currPos nextPos), by simp [h1, h2, nextPos, nextIt]⟩
|
||||
else
|
||||
pure ⟨.yield nextIt (.rejected currPos nextPos), by simp [h1, h2, nextPos, nextIt]⟩
|
||||
|
||||
|
||||
def finitenessRelation : Std.Iterators.FinitenessRelation (ForwardCharPredSearcher s) Id where
|
||||
rel := InvImage WellFoundedRelation.rel
|
||||
(fun it => s.utf8ByteSize.byteIdx - it.internalState.currPos.offset.byteIdx)
|
||||
wf := InvImage.wf _ WellFoundedRelation.wf
|
||||
subrelation {it it'} h := by
|
||||
simp_wf
|
||||
obtain ⟨step, h, h'⟩ := h
|
||||
cases step
|
||||
· cases h
|
||||
obtain ⟨_, h1, h2, _⟩ := h'
|
||||
have h3 := Char.utf8Size_pos (it.internalState.currPos.get h1)
|
||||
have h4 := it.internalState.currPos.isValidForSlice.le_utf8ByteSize
|
||||
simp [Pos.ext_iff, String.Pos.ext_iff, Pos.le_iff] at h1 h2 h4
|
||||
omega
|
||||
· cases h'
|
||||
· cases h
|
||||
|
||||
instance : Std.Iterators.Finite (ForwardCharPredSearcher s) Id :=
|
||||
.of_finitenessRelation finitenessRelation
|
||||
|
||||
instance : Std.Iterators.IteratorLoop (ForwardCharPredSearcher s) Id Id :=
|
||||
.defaultImplementation
|
||||
|
||||
instance : ToForwardSearcher (Char → Bool) ForwardCharPredSearcher where
|
||||
toSearcher := iter
|
||||
|
||||
instance : ForwardPattern (Char → Bool) := .defaultImplementation
|
||||
|
||||
end ForwardCharPredSearcher
|
||||
|
||||
structure BackwardCharPredSearcher (s : Slice) where
|
||||
currPos : s.Pos
|
||||
needle : Char → Bool
|
||||
deriving Inhabited
|
||||
|
||||
namespace BackwardCharPredSearcher
|
||||
|
||||
@[inline]
|
||||
def iter (s : Slice) (c : Char → Bool) : Std.Iter (α := BackwardCharPredSearcher s) (SearchStep s) :=
|
||||
{ internalState := { currPos := s.endPos, needle := c }}
|
||||
|
||||
instance (s : Slice) : Std.Iterators.Iterator (BackwardCharPredSearcher s) Id (SearchStep s) where
|
||||
IsPlausibleStep it
|
||||
| .yield it' out =>
|
||||
it.internalState.needle = it'.internalState.needle ∧
|
||||
∃ h1 : it.internalState.currPos ≠ s.startPos,
|
||||
it'.internalState.currPos = it.internalState.currPos.prev h1 ∧
|
||||
match out with
|
||||
| .matched startPos endPos =>
|
||||
it.internalState.currPos = endPos ∧
|
||||
it'.internalState.currPos = startPos ∧
|
||||
it.internalState.needle ((it.internalState.currPos.prev h1).get Pos.prev_ne_endPos)
|
||||
| .rejected startPos endPos =>
|
||||
it.internalState.currPos = endPos ∧
|
||||
it'.internalState.currPos = startPos ∧
|
||||
¬ it.internalState.needle ((it.internalState.currPos.prev h1).get Pos.prev_ne_endPos)
|
||||
| .skip _ => False
|
||||
| .done => it.internalState.currPos = s.startPos
|
||||
step := fun ⟨currPos, needle⟩ =>
|
||||
if h1 : currPos = s.startPos then
|
||||
pure ⟨.done, by simp [h1]⟩
|
||||
else
|
||||
let nextPos := currPos.prev h1
|
||||
let nextIt := ⟨nextPos, needle⟩
|
||||
if h2 : needle <| nextPos.get Pos.prev_ne_endPos then
|
||||
pure ⟨.yield nextIt (.matched nextPos currPos), by simp [h1, h2, nextIt, nextPos]⟩
|
||||
else
|
||||
pure ⟨.yield nextIt (.rejected nextPos currPos), by simp [h1, h2, nextIt, nextPos]⟩
|
||||
|
||||
def finitenessRelation : Std.Iterators.FinitenessRelation (BackwardCharPredSearcher s) Id where
|
||||
rel := InvImage WellFoundedRelation.rel
|
||||
(fun it => it.internalState.currPos.offset.byteIdx)
|
||||
wf := InvImage.wf _ WellFoundedRelation.wf
|
||||
subrelation {it it'} h := by
|
||||
simp_wf
|
||||
obtain ⟨step, h, h'⟩ := h
|
||||
cases step
|
||||
· cases h
|
||||
obtain ⟨_, h1, h2, _⟩ := h'
|
||||
have h3 := Pos.offset_prev_lt_offset (h := h1)
|
||||
simp [Pos.ext_iff, String.Pos.ext_iff] at h2 h3
|
||||
omega
|
||||
· cases h'
|
||||
· cases h
|
||||
|
||||
instance : Std.Iterators.Finite (BackwardCharPredSearcher s) Id :=
|
||||
.of_finitenessRelation finitenessRelation
|
||||
|
||||
instance : Std.Iterators.IteratorLoop (BackwardCharPredSearcher s) Id Id :=
|
||||
.defaultImplementation
|
||||
|
||||
instance : ToBackwardSearcher (Char → Bool) BackwardCharPredSearcher where
|
||||
toSearcher := iter
|
||||
|
||||
instance : BackwardPattern (Char → Bool) := ToBackwardSearcher.defaultImplementation
|
||||
|
||||
end BackwardCharPredSearcher
|
||||
|
||||
end String.Slice.Pattern
|
||||
273
src/Init/Data/String/Pattern/String.lean
Normal file
273
src/Init/Data/String/Pattern/String.lean
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Henrik Böving
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Pattern.Basic
|
||||
public import Init.Data.Iterators.Internal.Termination
|
||||
public import Init.Data.Iterators.Consumers.Monadic.Loop
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
/-!
|
||||
This module defines the necessary instances to register {name}`String` and {name}`String.Slice`
|
||||
with the pattern framework.
|
||||
-/
|
||||
|
||||
public section
|
||||
|
||||
namespace String.Slice.Pattern
|
||||
|
||||
inductive ForwardSliceSearcher (s : Slice) where
|
||||
| empty (pos : s.Pos)
|
||||
| proper (needle : Slice) (table : Array String.Pos) (stackPos : String.Pos) (needlePos : String.Pos)
|
||||
| atEnd
|
||||
deriving Inhabited
|
||||
|
||||
namespace ForwardSliceSearcher
|
||||
|
||||
partial def buildTable (pat : Slice) : Array String.Pos :=
|
||||
if pat.utf8ByteSize == 0 then
|
||||
#[]
|
||||
else
|
||||
let arr := Array.emptyWithCapacity pat.utf8ByteSize.byteIdx
|
||||
let arr := arr.push 0
|
||||
go ⟨1⟩ arr
|
||||
where
|
||||
go (pos : String.Pos) (table : Array String.Pos) :=
|
||||
if h : pos < pat.utf8ByteSize then
|
||||
let patByte := pat.getUtf8Byte pos h
|
||||
let distance := computeDistance table[table.size - 1]! patByte table
|
||||
let distance := if patByte = pat.getUtf8Byte! distance then distance.inc else distance
|
||||
go pos.inc (table.push distance)
|
||||
else
|
||||
table
|
||||
|
||||
computeDistance (distance : String.Pos) (patByte : UInt8) (table : Array String.Pos) :
|
||||
String.Pos :=
|
||||
if distance > 0 && patByte != pat.getUtf8Byte! distance then
|
||||
computeDistance table[distance.byteIdx - 1]! patByte table
|
||||
else
|
||||
distance
|
||||
|
||||
@[inline]
|
||||
def iter (s : Slice) (pat : Slice) : Std.Iter (α := ForwardSliceSearcher s) (SearchStep s) :=
|
||||
if pat.utf8ByteSize == 0 then
|
||||
{ internalState := .empty s.startPos }
|
||||
else
|
||||
{ internalState := .proper pat (buildTable pat) s.startPos.offset pat.startPos.offset }
|
||||
|
||||
partial def backtrackIfNecessary (pat : Slice) (table : Array String.Pos) (stackByte : UInt8)
|
||||
(needlePos : String.Pos) : String.Pos :=
|
||||
if needlePos != 0 && stackByte != pat.getUtf8Byte! needlePos then
|
||||
backtrackIfNecessary pat table stackByte table[needlePos.byteIdx - 1]!
|
||||
else
|
||||
needlePos
|
||||
|
||||
instance (s : Slice) : Std.Iterators.Iterator (ForwardSliceSearcher s) Id (SearchStep s) where
|
||||
IsPlausibleStep it
|
||||
| .yield it' out =>
|
||||
match it.internalState with
|
||||
| .empty pos =>
|
||||
(∃ newPos, pos.offset < newPos.offset ∧ it'.internalState = .empty newPos) ∨
|
||||
it'.internalState = .atEnd
|
||||
| .proper needle table stackPos needlePos =>
|
||||
(∃ newStackPos newNeedlePos,
|
||||
stackPos < newStackPos ∧
|
||||
newStackPos ≤ s.utf8ByteSize ∧
|
||||
it'.internalState = .proper needle table newStackPos newNeedlePos) ∨
|
||||
it'.internalState = .atEnd
|
||||
| .atEnd => False
|
||||
| .skip _ => False
|
||||
| .done => True
|
||||
step := fun ⟨iter⟩ =>
|
||||
match iter with
|
||||
| .empty pos =>
|
||||
let res := .matched pos pos
|
||||
if h : pos ≠ s.endPos then
|
||||
pure ⟨.yield ⟨.empty (pos.next h)⟩ res, by simp [Char.utf8Size_pos]⟩
|
||||
else
|
||||
pure ⟨.yield ⟨.atEnd⟩ res, by simp⟩
|
||||
| .proper needle table stackPos needlePos =>
|
||||
let rec findNext (startPos : String.Pos)
|
||||
(currStackPos : String.Pos) (needlePos : String.Pos) (h : stackPos ≤ currStackPos) :=
|
||||
if h1 : currStackPos < s.utf8ByteSize then
|
||||
let stackByte := s.getUtf8Byte currStackPos h1
|
||||
let needlePos := backtrackIfNecessary needle table stackByte needlePos
|
||||
let patByte := needle.getUtf8Byte! needlePos
|
||||
if stackByte != patByte then
|
||||
let nextStackPos := s.findNextPos currStackPos h1 |>.offset
|
||||
let res := .rejected (s.pos! startPos) (s.pos! nextStackPos)
|
||||
have hiter := by
|
||||
left
|
||||
exists nextStackPos
|
||||
have haux := lt_offset_findNextPos h1
|
||||
simp only [pos_lt_eq, proper.injEq, true_and, exists_and_left, exists_eq', and_true,
|
||||
nextStackPos]
|
||||
constructor
|
||||
· simp [String.Pos.le_iff] at h haux ⊢
|
||||
omega
|
||||
· apply Pos.IsValidForSlice.le_utf8ByteSize
|
||||
apply Pos.isValidForSlice
|
||||
⟨.yield ⟨.proper needle table nextStackPos needlePos⟩ res, hiter⟩
|
||||
else
|
||||
let needlePos := needlePos.inc
|
||||
if needlePos == needle.utf8ByteSize then
|
||||
let nextStackPos := currStackPos.inc
|
||||
let res := .matched (s.pos! startPos) (s.pos! nextStackPos)
|
||||
have hiter := by
|
||||
left
|
||||
exists nextStackPos
|
||||
simp only [pos_lt_eq, Pos.byteIdx_inc, proper.injEq, true_and, exists_and_left,
|
||||
exists_eq', and_true, nextStackPos]
|
||||
constructor
|
||||
· simp [String.Pos.le_iff] at h ⊢
|
||||
omega
|
||||
· simp [String.Pos.le_iff] at h1 ⊢
|
||||
omega
|
||||
⟨.yield ⟨.proper needle table nextStackPos 0⟩ res, hiter⟩
|
||||
else
|
||||
have hinv := by
|
||||
simp [String.Pos.le_iff] at h ⊢
|
||||
omega
|
||||
findNext startPos currStackPos.inc needlePos hinv
|
||||
else
|
||||
if startPos != s.utf8ByteSize then
|
||||
let res := .rejected (s.pos! startPos) (s.pos! currStackPos)
|
||||
⟨.yield ⟨.atEnd⟩ res, by simp⟩
|
||||
else
|
||||
⟨.done, by simp⟩
|
||||
termination_by s.utf8ByteSize.byteIdx - currStackPos.byteIdx
|
||||
decreasing_by
|
||||
simp at h1 ⊢
|
||||
omega
|
||||
|
||||
findNext stackPos stackPos needlePos (by simp)
|
||||
| .atEnd => pure ⟨.done, by simp⟩
|
||||
|
||||
private def toPair : ForwardSliceSearcher s → (Nat × Nat)
|
||||
| .empty pos => (1, s.utf8ByteSize.byteIdx - pos.offset.byteIdx)
|
||||
| .proper _ _ sp _ => (1, s.utf8ByteSize.byteIdx - sp.byteIdx)
|
||||
| .atEnd => (0, 0)
|
||||
|
||||
private instance : WellFoundedRelation (ForwardSliceSearcher s) where
|
||||
rel s1 s2 := Prod.Lex (· < ·) (· < ·) s1.toPair s2.toPair
|
||||
wf := by
|
||||
apply InvImage.wf
|
||||
apply (Prod.lex _ _).wf
|
||||
|
||||
private def finitenessRelation :
|
||||
Std.Iterators.FinitenessRelation (ForwardSliceSearcher s) Id where
|
||||
rel := InvImage WellFoundedRelation.rel (fun it => it.internalState)
|
||||
wf := InvImage.wf _ WellFoundedRelation.wf
|
||||
subrelation {it it'} h := by
|
||||
simp_wf
|
||||
obtain ⟨step, h, h'⟩ := h
|
||||
cases step
|
||||
· cases h
|
||||
simp only [Std.Iterators.IterM.IsPlausibleStep, Std.Iterators.Iterator.IsPlausibleStep] at h'
|
||||
split at h'
|
||||
· next heq =>
|
||||
rw [heq]
|
||||
rcases h' with ⟨np, h1', h2'⟩ | h'
|
||||
· rw [h2']
|
||||
apply Prod.Lex.right'
|
||||
· simp
|
||||
· have haux := np.isValidForSlice.le_utf8ByteSize
|
||||
simp [String.Pos.le_iff] at h1' haux ⊢
|
||||
omega
|
||||
· apply Prod.Lex.left
|
||||
simp [h']
|
||||
· next heq =>
|
||||
rw [heq]
|
||||
rcases h' with ⟨np, sp, h1', h2', h3'⟩ | h'
|
||||
· rw [h3']
|
||||
apply Prod.Lex.right'
|
||||
· simp
|
||||
· simp [String.Pos.le_iff] at h1' h2' ⊢
|
||||
omega
|
||||
· apply Prod.Lex.left
|
||||
simp [h']
|
||||
· contradiction
|
||||
· cases h'
|
||||
· cases h
|
||||
|
||||
@[no_expose]
|
||||
instance : Std.Iterators.Finite (ForwardSliceSearcher s) Id :=
|
||||
.of_finitenessRelation finitenessRelation
|
||||
|
||||
instance : Std.Iterators.IteratorLoop (ForwardSliceSearcher s) Id Id :=
|
||||
.defaultImplementation
|
||||
|
||||
instance : ToForwardSearcher Slice ForwardSliceSearcher where
|
||||
toSearcher := iter
|
||||
|
||||
@[inline]
|
||||
def startsWith (s : Slice) (pat : Slice) : Bool :=
|
||||
if h : pat.utf8ByteSize ≤ s.utf8ByteSize then
|
||||
have hs := by
|
||||
simp [Pos.le_iff] at h ⊢
|
||||
omega
|
||||
have hp := by
|
||||
simp [Pos.le_iff]
|
||||
Internal.memcmp s pat s.startPos.offset pat.startPos.offset pat.utf8ByteSize hs hp
|
||||
else
|
||||
false
|
||||
|
||||
@[inline]
|
||||
def dropPrefix? (s : Slice) (pat : Slice) : Option Slice :=
|
||||
if startsWith s pat then
|
||||
some <| s.replaceStart <| s.pos! <| s.startPos.offset + pat.utf8ByteSize
|
||||
else
|
||||
none
|
||||
|
||||
instance : ForwardPattern Slice where
|
||||
startsWith := startsWith
|
||||
dropPrefix? := dropPrefix?
|
||||
|
||||
instance : ToForwardSearcher String ForwardSliceSearcher where
|
||||
toSearcher slice pat := iter slice pat.toSlice
|
||||
|
||||
instance : ForwardPattern String where
|
||||
startsWith s pat := startsWith s pat.toSlice
|
||||
dropPrefix? s pat := dropPrefix? s pat.toSlice
|
||||
|
||||
end ForwardSliceSearcher
|
||||
|
||||
namespace BackwardSliceSearcher
|
||||
|
||||
@[inline]
|
||||
def endsWith (s : Slice) (pat : Slice) : Bool :=
|
||||
if h : pat.utf8ByteSize ≤ s.utf8ByteSize then
|
||||
let sStart := s.endPos.offset - pat.utf8ByteSize
|
||||
let patStart := pat.startPos.offset
|
||||
have hs := by
|
||||
simp [sStart, Pos.le_iff] at h ⊢
|
||||
omega
|
||||
have hp := by
|
||||
simp [patStart, Pos.le_iff] at h ⊢
|
||||
Internal.memcmp s pat sStart patStart pat.utf8ByteSize hs hp
|
||||
else
|
||||
false
|
||||
|
||||
@[inline]
|
||||
def dropSuffix? (s : Slice) (pat : Slice) : Option Slice :=
|
||||
if endsWith s pat then
|
||||
some <| s.replaceEnd <| s.pos! <| s.endPos.offset - pat.utf8ByteSize
|
||||
else
|
||||
none
|
||||
|
||||
instance : BackwardPattern Slice where
|
||||
endsWith := endsWith
|
||||
dropSuffix? := dropSuffix?
|
||||
|
||||
instance : BackwardPattern String where
|
||||
endsWith s pat := endsWith s pat.toSlice
|
||||
dropSuffix? s pat := dropSuffix? s pat.toSlice
|
||||
|
||||
end BackwardSliceSearcher
|
||||
|
||||
end String.Slice.Pattern
|
||||
1201
src/Init/Data/String/Slice.lean
Normal file
1201
src/Init/Data/String/Slice.lean
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -183,6 +183,14 @@ def Bool.toUInt8 (b : Bool) : UInt8 := if b then 1 else 0
|
|||
instance : Max UInt8 := maxOfLe
|
||||
instance : Min UInt8 := minOfLe
|
||||
|
||||
/--
|
||||
If `b` is the ASCII value of an uppercase character return the corresponding
|
||||
lowercase value, otherwise leave it untouched.
|
||||
-/
|
||||
@[inline]
|
||||
def UInt8.toAsciiLower (b : UInt8) : UInt8 :=
|
||||
if b >= 65 && b <= 90 then (b + 32) else b
|
||||
|
||||
/-- Converts a `Fin UInt16.size` into the corresponding `UInt16`. -/
|
||||
@[inline] def UInt16.ofFin (a : Fin UInt16.size) : UInt16 := ⟨⟨a⟩⟩
|
||||
|
||||
|
|
|
|||
|
|
@ -1155,6 +1155,9 @@ static inline uint8_t lean_string_dec_eq(b_lean_obj_arg s1, b_lean_obj_arg s2) {
|
|||
static inline uint8_t lean_string_dec_lt(b_lean_obj_arg s1, b_lean_obj_arg s2) { return lean_string_lt(s1, s2); }
|
||||
LEAN_EXPORT uint64_t lean_string_hash(b_lean_obj_arg);
|
||||
LEAN_EXPORT lean_obj_res lean_string_of_usize(size_t);
|
||||
LEAN_EXPORT uint8_t lean_slice_memcmp(b_lean_obj_arg s1, b_lean_obj_arg s2, b_lean_obj_arg lstart, b_lean_obj_arg rstart, b_lean_obj_arg len);
|
||||
LEAN_EXPORT uint64_t lean_slice_hash(b_lean_obj_arg);
|
||||
LEAN_EXPORT uint8_t lean_slice_dec_lt(b_lean_obj_arg s1, b_lean_obj_arg s2);
|
||||
|
||||
/* Thunks */
|
||||
|
||||
|
|
|
|||
|
|
@ -2331,6 +2331,45 @@ extern "C" LEAN_EXPORT obj_res lean_string_of_usize(size_t n) {
|
|||
return mk_ascii_string_unchecked(std::to_string(n));
|
||||
}
|
||||
|
||||
size_t lean_slice_size(b_obj_arg slice) {
|
||||
b_obj_res start = lean_ctor_get(slice, 1);
|
||||
lean_assert(lean_is_scalar(start));
|
||||
b_obj_res end = lean_ctor_get(slice, 2);
|
||||
lean_assert(lean_is_scalar(end));
|
||||
return lean_unbox(end) - lean_unbox(start);
|
||||
}
|
||||
|
||||
char const * lean_slice_base(b_obj_arg slice) {
|
||||
b_obj_res string = lean_ctor_get(slice, 0);
|
||||
b_obj_res offset = lean_ctor_get(slice, 1);
|
||||
lean_assert(lean_is_scalar(offset));
|
||||
return lean_string_cstr(string) + lean_unbox(offset);
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT uint8_t lean_slice_memcmp(b_obj_arg s1, b_obj_arg s2, b_obj_arg lstart, b_obj_arg rstart, b_obj_arg len) {
|
||||
// Thanks to the proof arguments we know that lstart, rstart and len are all scalars.
|
||||
lean_assert(lean_is_scalar(lstart));
|
||||
lean_assert(lean_is_scalar(rstart));
|
||||
lean_assert(lean_is_scalar(len));
|
||||
|
||||
char const * lbase = lean_slice_base(s1) + lean_unbox(lstart);
|
||||
char const * rbase = lean_slice_base(s2) + lean_unbox(rstart);
|
||||
return std::memcmp(lbase, rbase, lean_unbox(len)) == 0;
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT uint64_t lean_slice_hash(b_obj_arg s) {
|
||||
size_t sz = lean_slice_size(s);
|
||||
char const * str = lean_slice_base(s);
|
||||
return hash_str(sz, (unsigned char const *) str, 11);
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT uint8_t lean_slice_dec_lt(object * s1, object * s2) {
|
||||
size_t sz1 = lean_slice_size(s1);
|
||||
size_t sz2 = lean_slice_size(s2);
|
||||
int r = std::memcmp(lean_slice_base(s1), lean_slice_base(s2), std::min(sz1, sz2));
|
||||
return r < 0 || (r == 0 && sz1 < sz2);
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// ByteArray & FloatArray
|
||||
|
||||
|
|
|
|||
206
tests/lean/run/string_slice.lean
Normal file
206
tests/lean/run/string_slice.lean
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
module
|
||||
import Std.Data.HashSet.Basic
|
||||
|
||||
/-!
|
||||
Tests for `String.Slice` functions
|
||||
-/
|
||||
|
||||
#guard "".toSlice.isEmpty = true
|
||||
#guard " ".toSlice.isEmpty = false
|
||||
#guard (" ".toSlice.drop 1).isEmpty = true
|
||||
|
||||
#guard "abc".toSlice == "abc".toSlice
|
||||
#guard "abcdefg".toSlice.dropEnd 4 == "abc".toSlice
|
||||
#guard "abcdefg".toSlice.dropEnd 3 != "abc".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.startsWith "red" = true
|
||||
#guard "red green blue".toSlice.startsWith "green" = false
|
||||
#guard "red green blue".toSlice.startsWith "" = true
|
||||
#guard "red green blue".toSlice.startsWith 'r' = true
|
||||
#guard "red green blue".toSlice.startsWith Char.isLower = true
|
||||
|
||||
#guard ("coffee tea water".toSlice.split Char.isWhitespace).allowNontermination.toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]
|
||||
#guard ("coffee tea water".toSlice.split ' ').allowNontermination.toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]
|
||||
#guard ("coffee tea water".toSlice.split " tea ").allowNontermination.toList == ["coffee".toSlice, "water".toSlice]
|
||||
#guard ("baaab".toSlice.split "aa").allowNontermination.toList == ["b".toSlice, "ab".toSlice]
|
||||
|
||||
#guard ("coffee tea water".toSlice.splitInclusive Char.isWhitespace).allowNontermination.toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]
|
||||
#guard ("coffee tea water".toSlice.splitInclusive ' ').allowNontermination.toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]
|
||||
#guard ("coffee tea water".toSlice.splitInclusive " tea ").allowNontermination.toList == ["coffee tea ".toSlice, "water".toSlice]
|
||||
#guard ("a".toSlice.splitInclusive (fun (_ : Char) => true)).allowNontermination.toList == ["a".toSlice]
|
||||
#guard ("baaab".toSlice.splitInclusive "aa").allowNontermination.toList == ["baa".toSlice, "ab".toSlice]
|
||||
|
||||
#guard "red green blue".toSlice.drop 4 == "green blue".toSlice
|
||||
#guard "red green blue".toSlice.drop 10 == "blue".toSlice
|
||||
#guard "red green blue".toSlice.drop 50 == "".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.dropWhile Char.isLower == " green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropWhile 'r' == "ed green blue".toSlice
|
||||
#guard "red red green blue".toSlice.dropWhile "red " == "green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropWhile (fun (_ : Char) => true) == "".toSlice
|
||||
|
||||
#guard "abc".toSlice.trimAsciiStart == "abc".toSlice
|
||||
#guard " abc".toSlice.trimAsciiStart == "abc".toSlice
|
||||
#guard "abc \t ".toSlice.trimAsciiStart == "abc \t ".toSlice
|
||||
#guard " abc ".toSlice.trimAsciiStart == "abc ".toSlice
|
||||
#guard "abc\ndef\n".toSlice.trimAsciiStart == "abc\ndef\n".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.take 3 == "red".toSlice
|
||||
#guard "red green blue".toSlice.take 1 == "r".toSlice
|
||||
#guard "red green blue".toSlice.take 0 == "".toSlice
|
||||
#guard "red green blue".toSlice.take 100 == "red green blue".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.takeWhile Char.isLower == "red".toSlice
|
||||
#guard "red green blue".toSlice.takeWhile 'r' == "r".toSlice
|
||||
#guard "red red green blue".toSlice.takeWhile "red " == "red red ".toSlice
|
||||
#guard "red green blue".toSlice.takeWhile (fun (_ : Char) => true) == "red green blue".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.dropPrefix? "red " == some "green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropPrefix? "reed " == none
|
||||
#guard "red green blue".toSlice.dropPrefix? 'r' == some "ed green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropPrefix? Char.isLower == some "ed green blue".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.dropPrefix "red " == "green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropPrefix "reed " == "red green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropPrefix 'r' == "ed green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropPrefix Char.isLower == "ed green blue".toSlice
|
||||
|
||||
#guard ("coffee tea water".toSlice.find? Char.isWhitespace).map (·.get!) == some ' '
|
||||
#guard "tea".toSlice.find? (fun (c : Char) => c == 'X') == none
|
||||
#guard ("coffee tea water".toSlice.find? "tea").map (·.get!) == some 't'
|
||||
|
||||
#guard "coffee tea water".toSlice.contains Char.isWhitespace = true
|
||||
#guard "tea".toSlice.contains (fun (c : Char) => c == 'X') = false
|
||||
#guard "coffee tea water".toSlice.contains "tea" = true
|
||||
|
||||
#guard "brown".toSlice.all Char.isLower = true
|
||||
#guard "brown and orange".toSlice.all Char.isLower = false
|
||||
#guard "aaaaaa".toSlice.all 'a' = true
|
||||
#guard "aaaaaa".toSlice.all "aa" = true
|
||||
|
||||
#guard "red green blue".toSlice.endsWith "blue" = true
|
||||
#guard "red green blue".toSlice.endsWith "green" = false
|
||||
#guard "red green blue".toSlice.endsWith "" = true
|
||||
#guard "red green blue".toSlice.endsWith 'e' = true
|
||||
#guard "red green blue".toSlice.endsWith Char.isLower = true
|
||||
|
||||
#guard ("coffee tea water".toSlice.revSplit Char.isWhitespace).allowNontermination.toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]
|
||||
#guard ("coffee tea water".toSlice.revSplit ' ').allowNontermination.toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]
|
||||
|
||||
#guard "red green blue".toSlice.dropEnd 5 == "red green".toSlice
|
||||
#guard "red green blue".toSlice.dropEnd 11 == "red".toSlice
|
||||
#guard "red green blue".toSlice.dropEnd 50 == "".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.dropEndWhile Char.isLower == "red green ".toSlice
|
||||
#guard "red green blue".toSlice.dropEndWhile 'e' == "red green blu".toSlice
|
||||
#guard "red green blue".toSlice.dropEndWhile (fun (_ : Char) => true) == "".toSlice
|
||||
|
||||
#guard "abc".toSlice.trimAsciiEnd == "abc".toSlice
|
||||
#guard " abc".toSlice.trimAsciiEnd == " abc".toSlice
|
||||
#guard "abc \t ".toSlice.trimAsciiEnd == "abc".toSlice
|
||||
#guard " abc ".toSlice.trimAsciiEnd == " abc".toSlice
|
||||
#guard "abc\ndef\n".toSlice.trimAsciiEnd == "abc\ndef".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.takeEnd 4 == "blue".toSlice
|
||||
#guard "red green blue".toSlice.takeEnd 1 == "e".toSlice
|
||||
#guard "red green blue".toSlice.takeEnd 0 == "".toSlice
|
||||
#guard "red green blue".toSlice.takeEnd 100 == "red green blue".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.takeEndWhile Char.isLower == "blue".toSlice
|
||||
#guard "red green blue".toSlice.takeEndWhile 'e' == "e".toSlice
|
||||
#guard "red green blue".toSlice.takeEndWhile (fun (_ : Char) => true) == "red green blue".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.dropSuffix? " blue" == some "red green".toSlice
|
||||
#guard "red green blue".toSlice.dropSuffix? "bluu " == none
|
||||
#guard "red green blue".toSlice.dropSuffix? 'e' == some "red green blu".toSlice
|
||||
#guard "red green blue".toSlice.dropSuffix? Char.isLower == some "red green blu".toSlice
|
||||
|
||||
#guard "red green blue".toSlice.dropSuffix " blue" == "red green".toSlice
|
||||
#guard "red green blue".toSlice.dropSuffix "bluu " == "red green blue".toSlice
|
||||
#guard "red green blue".toSlice.dropSuffix 'e' == "red green blu".toSlice
|
||||
#guard "red green blue".toSlice.dropSuffix Char.isLower == "red green blu".toSlice
|
||||
|
||||
#guard "abc".toSlice.trimAscii == "abc".toSlice
|
||||
#guard " abc".toSlice.trimAscii == "abc".toSlice
|
||||
#guard "abc \t ".toSlice.trimAscii == "abc".toSlice
|
||||
#guard " abc ".toSlice.trimAscii == "abc".toSlice
|
||||
#guard "abc\ndef\n".toSlice.trimAscii == "abc\ndef".toSlice
|
||||
|
||||
|
||||
#guard ({} : Std.HashSet _).insert "abc".toSlice |>.contains "abc".toSlice
|
||||
#guard (({} : Std.HashSet _).insert "abc".toSlice |>.insert "abc".toSlice |>.size) == 1
|
||||
|
||||
#guard "abc".toSlice ≤ "abc".toSlice
|
||||
#guard "abc".toSlice < "abcd".toSlice
|
||||
#guard "abc".toSlice < "zbc".toSlice
|
||||
#guard "".toSlice < "zbc".toSlice
|
||||
#guard "".toSlice ≤ "".toSlice
|
||||
|
||||
#guard "abc".toSlice.chars.toList = ['a', 'b', 'c']
|
||||
#guard "ab∀c".toSlice.chars.toList = ['a', 'b', '∀', 'c']
|
||||
|
||||
#guard "abc".toSlice.revChars.toList = ['c', 'b', 'a']
|
||||
#guard "ab∀c".toSlice.revChars.toList = ['c', '∀', 'b', 'a']
|
||||
|
||||
#guard ("abc".toSlice.positions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['a', 'b', 'c']
|
||||
#guard ("abc".toSlice.positions.map (·.val.offset.byteIdx) |>.toList) = [0, 1, 2]
|
||||
#guard ("ab∀c".toSlice.positions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['a', 'b', '∀', 'c']
|
||||
#guard ("ab∀c".toSlice.positions.map (·.val.offset.byteIdx) |>.toList) = [0, 1, 2, 5]
|
||||
|
||||
#guard ("abc".toSlice.revPositions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['c', 'b', 'a']
|
||||
#guard ("abc".toSlice.revPositions.map (·.val.offset.byteIdx) |>.toList) = [2, 1, 0]
|
||||
#guard ("ab∀c".toSlice.revPositions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['c', '∀', 'b', 'a']
|
||||
#guard ("ab∀c".toSlice.revPositions.map (·.val.offset.byteIdx) |>.toList) = [5, 2, 1, 0]
|
||||
|
||||
#guard "abc".toSlice.bytes.toList = [97, 98, 99]
|
||||
#guard "ab∀c".toSlice.bytes.toList = [97, 98, 226, 136, 128, 99]
|
||||
|
||||
#guard "abc".toSlice.revBytes.toList = [99, 98, 97]
|
||||
#guard "ab∀c".toSlice.revBytes.toList = [99, 128, 136, 226, 98, 97]
|
||||
|
||||
#guard "foo\r\nbar\n\nbaz\n".toSlice.lines.allowNontermination.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]
|
||||
#guard "foo\r\nbar\n\nbaz".toSlice.lines.allowNontermination.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]
|
||||
#guard "foo\r\nbar\n\nbaz\r".toSlice.lines.allowNontermination.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz\r".toSlice]
|
||||
|
||||
#guard "coffee tea water".toSlice.foldl (fun n c => if c.isWhitespace then n + 1 else n) 0 = 2
|
||||
#guard "coffee tea and water".toSlice.foldl (fun n c => if c.isWhitespace then n + 1 else n) 0 = 3
|
||||
#guard "coffee tea water".toSlice.foldl (·.push ·) "" = "coffee tea water"
|
||||
|
||||
#guard "coffee tea water".toSlice.foldr (fun c n => if c.isWhitespace then n + 1 else n) 0 = 2
|
||||
#guard "coffee tea and water".toSlice.foldr (fun c n => if c.isWhitespace then n + 1 else n) 0 = 3
|
||||
#guard "coffee tea water".toSlice.foldr (fun c s => s.push c) "" = "retaw aet eeffoc"
|
||||
|
||||
#guard "".toSlice.isNat = false
|
||||
#guard "0".toSlice.isNat = true
|
||||
#guard "5".toSlice.isNat = true
|
||||
#guard "05".toSlice.isNat = true
|
||||
#guard "587".toSlice.isNat = true
|
||||
#guard "-587".toSlice.isNat = false
|
||||
#guard " 5".toSlice.isNat = false
|
||||
#guard "2+3".toSlice.isNat = false
|
||||
#guard "0xff".toSlice.isNat = false
|
||||
|
||||
#guard "".toSlice.toNat? = none
|
||||
#guard "0".toSlice.toNat? = some 0
|
||||
#guard "5".toSlice.toNat? = some 5
|
||||
#guard "587".toSlice.toNat? = some 587
|
||||
#guard "-587".toSlice.toNat? = none
|
||||
#guard " 5".toSlice.toNat? = none
|
||||
#guard "2+3".toSlice.toNat? = none
|
||||
#guard "0xff".toSlice.toNat? = none
|
||||
|
||||
#guard "0".toSlice.toNat! = 0
|
||||
#guard "5".toSlice.toNat! = 5
|
||||
#guard "587".toSlice.toNat! = 587
|
||||
|
||||
#guard "abc".toSlice.front? = some 'a'
|
||||
#guard "".toSlice.front? = none
|
||||
|
||||
#guard "abc".toSlice.front = 'a'
|
||||
#guard "".toSlice.front = (default : Char)
|
||||
|
||||
#guard "abc".toSlice.back? = some 'c'
|
||||
#guard "".toSlice.back? = none
|
||||
|
||||
#guard "abc".toSlice.back = 'c'
|
||||
#guard "".toSlice.back = (default : Char)
|
||||
Loading…
Add table
Reference in a new issue