feat: split and splitInclusive iterators are finite (#10820)

This PR shows that the iterators returned by `String.Slice.split` and
`String.Slice.splitInclusive` are finite as long as the forward matcher
iterator for the pattern is finite (which we already know for all of our
patterns).

At actually also completely redefines the iterators to avoid the inner
loop in `Internal.nextMatch` which generates inefficient code. Instead,
when encountering a mismach from the matcher, we `skip` the split
iterator.
This commit is contained in:
Markus Himmel 2025-10-20 12:21:21 +02:00 committed by GitHub
parent 71f1a6c164
commit c981ebc546
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 248 additions and 139 deletions

View file

@ -241,6 +241,16 @@ def IterStep.successor : IterStep α β → Option α
| .skip it => some it
| .done => none
@[simp]
theorem IterStep.successor_yield {it : α} {out : β} :
(IterStep.yield it out).successor = some it := rfl
@[simp]
theorem IterStep.successor_skip {it : α} : (IterStep.skip (β := β) it).successor = some it := rfl
@[simp]
theorem IterStep.successor_done : (IterStep.done (α := α) (β := β)).successor = none := rfl
/--
If present, applies `f` to the iterator of an `IterStep` and replaces the iterator
with the result of the application of `f`.
@ -543,6 +553,10 @@ def Iter.IsPlausibleSuccessorOf {α : Type w} {β : Type w} [Iterator α Id β]
(it' it : Iter (α := α) β) : Prop :=
it'.toIterM.IsPlausibleSuccessorOf it.toIterM
theorem Iter.isPlausibleSuccessorOf_eq_invImage {α : Type w} {β : Type w} [Iterator α Id β] :
IsPlausibleSuccessorOf (α := α) (β := β) =
InvImage (IterM.IsPlausibleSuccessorOf (α := α) (β := β) (m := Id)) Iter.toIterM := rfl
theorem Iter.isPlausibleSuccessorOf_iff_exists {α : Type w} {β : Type w} [Iterator α Id β]
{it' it : Iter (α := α) β} :
it'.IsPlausibleSuccessorOf it ↔ ∃ step, step.successor = some it' ∧ it.IsPlausibleStep step := by
@ -555,6 +569,16 @@ theorem Iter.isPlausibleSuccessorOf_iff_exists {α : Type w} {β : Type w} [Iter
exact ⟨step.mapIterator Iter.toIterM,
by cases step <;> simp_all [IterStep.successor, Iter.IsPlausibleStep]⟩
theorem Iter.IsPlausibleStep.isPlausibleSuccessor_of_yield {α : Type w} {β : Type w}
[Iterator α Id β] {it' it : Iter (α := α) β} {out : β}
(h : it.IsPlausibleStep (.yield it' out)) : it'.IsPlausibleSuccessorOf it := by
simpa [isPlausibleSuccessorOf_iff_exists] using ⟨.yield it' out, by simp [h]⟩
theorem Iter.IsPlausibleStep.isPlausibleSuccessor_of_skip {α : Type w} {β : Type w}
[Iterator α Id β] {it' it : Iter (α := α) β} (h : it.IsPlausibleStep (.skip it')) :
it'.IsPlausibleSuccessorOf it := by
simpa [isPlausibleSuccessorOf_iff_exists] using ⟨.skip it', by simp [h]⟩
/--
Asserts that a certain iterator `it` could plausibly yield the value `out` after an arbitrary
number of steps.
@ -656,6 +680,10 @@ Given this typeclass, termination proofs for well-founded recursion over an iter
class Finite (α : Type w) (m : Type w → Type w') {β : Type w} [Iterator α m β] : Prop where
wf : WellFounded (IterM.IsPlausibleSuccessorOf (α := α) (m := m))
theorem Finite.wf_of_id {α : Type w} {β : Type w} [Iterator α Id β] [Finite α Id] :
WellFounded (Iter.IsPlausibleSuccessorOf (α := α)) := by
simpa [Iter.isPlausibleSuccessorOf_eq_invImage] using InvImage.wf _ Finite.wf
/--
This type is a wrapper around `IterM` so that it becomes a useful termination measure for
recursion over finite iterators. See also `IterM.finitelyManySteps` and `Iter.finitelyManySteps`.
@ -705,6 +733,12 @@ theorem IterM.TerminationMeasures.Finite.rel_of_skip
Rel ⟨it'⟩ ⟨it⟩ := by
exact .single ⟨_, rfl, h⟩
theorem IterM.TerminationMeasures.Finite.rel_trans
{α : Type w} {m : Type w → Type w'} {β : Type w} [Iterator α m β]
{it it' it'' : TerminationMeasures.Finite α m} :
it.Rel it' → it'.Rel it'' → it.Rel it'' :=
.trans
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
first
| exact IterM.TerminationMeasures.Finite.rel_of_yield _

View file

@ -94,7 +94,7 @@ it.filterMapWithPostcondition ---a'-----c'-------⊥
**Termination properties:**
* `Finite` instance: only if `it` is finite
* `Productive` instance: only if `it` is finite`
* `Productive` instance: only if `it` is finite
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
no `Finite` (or `Productive`) instance is provided. For example, if `f` never returns `none`, then

View file

@ -35,7 +35,7 @@ inductive SearchStep (s : Slice) where
-/
| rejected (startPos endPos : s.Pos)
/--
The subslice starting at {name}`startPos` and ending at {name}`endPos` did not match the pattern.
The subslice starting at {name}`startPos` and ending at {name}`endPos` matches the pattern.
-/
| matched (startPos endPos : s.Pos)
deriving Inhabited
@ -99,44 +99,6 @@ where
simp [Pos.Raw.lt_iff] 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

View file

@ -112,8 +112,8 @@ Examples:
def startsWith [ForwardPattern ρ] (s : Slice) (pat : ρ) : Bool :=
ForwardPattern.startsWith s pat
inductive SplitIterator (ρ : Type) [ToForwardSearcher ρ σ] where
| operating (s : Slice) (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s))
inductive SplitIterator (ρ : Type) (s : Slice) [ToForwardSearcher ρ σ] where
| operating (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s))
| atEnd
deriving Inhabited
@ -121,33 +121,72 @@ namespace SplitIterator
variable [ToForwardSearcher ρ σ]
instance [Pure m] : Std.Iterators.Iterator (SplitIterator ρ) m Slice where
IsPlausibleStep := fun _ _ => True
step := fun ⟨iter⟩ =>
match iter with
| .operating s currPos searcher =>
match Internal.nextMatch searcher with
| some (searcher, startPos, endPos) =>
inductive PlausibleStep
instance : Std.Iterators.Iterator (SplitIterator ρ s) Id Slice where
IsPlausibleStep
| ⟨.operating _ s⟩, .yield ⟨.operating _ s'⟩ _ => s'.IsPlausibleSuccessorOf s
| ⟨.operating _ s⟩, .yield ⟨.atEnd ..⟩ _ => True
| ⟨.operating _ s⟩, .skip ⟨.operating _ s'⟩ => s'.IsPlausibleSuccessorOf s
| ⟨.operating _ s⟩, .skip ⟨.atEnd ..⟩ => False
| ⟨.operating _ s⟩, .done => True
| ⟨.atEnd⟩, .yield .. => False
| ⟨.atEnd⟩, .skip _ => False
| ⟨.atEnd⟩, .done => True
step
| ⟨.operating currPos searcher⟩ =>
match h : searcher.step with
| ⟨.yield searcher' (.matched startPos endPos), hps⟩ =>
let slice := s.replaceStartEnd! currPos startPos
let nextIt := ⟨.operating s endPos searcher⟩
pure (.deflate ⟨.yield nextIt slice, by simp⟩)
| none =>
let nextIt := ⟨.operating endPos searcher'⟩
pure (.deflate ⟨.yield nextIt slice, by simp [nextIt, hps.isPlausibleSuccessor_of_yield]⟩)
| ⟨.yield searcher' (.rejected ..), hps⟩ =>
pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩,
by simp [hps.isPlausibleSuccessor_of_yield]⟩)
| ⟨.skip searcher', hps⟩ =>
pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩,
by simp [hps.isPlausibleSuccessor_of_skip]⟩)
| ⟨.done, _⟩ =>
let slice := s.replaceStart currPos
pure (.deflate ⟨.yield ⟨.atEnd⟩ slice, by simp⟩)
| .atEnd => pure (.deflate ⟨.done, by simp⟩)
| .atEnd => pure (.deflate ⟨.done, by simp⟩)
-- TODO: Finiteness after we have a notion of lawful searcher
private def toOption : SplitIterator ρ s → Option (Std.Iter (α := σ s) (SearchStep s))
| .operating _ s => some s
| .atEnd => none
instance [Monad m] [Monad n] : Std.Iterators.IteratorCollect (SplitIterator ρ) m n :=
private def finitenessRelation [Std.Iterators.Finite (σ s) Id] :
Std.Iterators.FinitenessRelation (SplitIterator ρ s) Id where
rel := InvImage (Option.lt Std.Iterators.Iter.IsPlausibleSuccessorOf)
(SplitIterator.toOption ∘ Std.Iterators.IterM.internalState)
wf := InvImage.wf _ (Option.wellFounded_lt Std.Iterators.Finite.wf_of_id)
subrelation {it it'} h := by
simp_wf
obtain ⟨step, h, h'⟩ := h
match step with
| .yield it'' out | .skip it'' =>
obtain rfl : it' = it'' := by simpa using h.symm
simp only [Std.Iterators.IterM.IsPlausibleStep, Std.Iterators.Iterator.IsPlausibleStep] at h'
revert h'
match it, it' with
| ⟨.operating _ searcher⟩, ⟨.operating _ searcher'⟩ => simp [SplitIterator.toOption, Option.lt]
| ⟨.operating _ searcher⟩, ⟨.atEnd⟩ => simp [SplitIterator.toOption, Option.lt]
| ⟨.atEnd⟩, _ => simp
@[no_expose]
instance [Std.Iterators.Finite (σ s) Id] : Std.Iterators.Finite (SplitIterator ρ s) Id :=
.of_finitenessRelation finitenessRelation
instance [Monad n] : Std.Iterators.IteratorCollect (SplitIterator ρ s) Id n :=
.defaultImplementation
instance [Monad m] [Monad n] : Std.Iterators.IteratorCollectPartial (SplitIterator ρ) m n :=
instance [Monad n] : Std.Iterators.IteratorCollectPartial (SplitIterator ρ s) Id n :=
.defaultImplementation
instance [Monad m] [Monad n] : Std.Iterators.IteratorLoop (SplitIterator ρ) m n :=
instance [Monad n] : Std.Iterators.IteratorLoop (SplitIterator ρ s) Id n :=
.defaultImplementation
instance [Monad m] [Monad n] : Std.Iterators.IteratorLoopPartial (SplitIterator ρ) m n :=
instance [Monad n] : Std.Iterators.IteratorLoopPartial (SplitIterator ρ s) Id n :=
.defaultImplementation
end SplitIterator
@ -161,18 +200,18 @@ multiple subslices in a row match the pattern, the resulting list will contain e
This function is generic over all currently supported patterns.
Examples:
* {lean}`("coffee tea water".toSlice.split Char.isWhitespace).allowNontermination.toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.split ' ').allowNontermination.toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.split " tea ").allowNontermination.toList == ["coffee".toSlice, "water".toSlice]`
* {lean}`("ababababa".toSlice.split "aba").allowNontermination.toList == ["coffee".toSlice, "water".toSlice]`
* {lean}`("baaab".toSlice.split "aa").allowNontermination.toList == ["b".toSlice, "ab".toSlice]`
* {lean}`("coffee tea water".toSlice.split Char.isWhitespace).toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.split ' ').toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.split " tea ").toList == ["coffee".toSlice, "water".toSlice]`
* {lean}`("ababababa".toSlice.split "aba").toList == ["coffee".toSlice, "water".toSlice]`
* {lean}`("baaab".toSlice.split "aa").toList == ["b".toSlice, "ab".toSlice]`
-/
@[specialize pat]
def split [ToForwardSearcher ρ σ] (s : Slice) (pat : ρ) : Std.Iter (α := SplitIterator ρ) Slice :=
{ internalState := .operating s s.startPos (ToForwardSearcher.toSearcher s pat) }
def split [ToForwardSearcher ρ σ] (s : Slice) (pat : ρ) : Std.Iter (α := SplitIterator ρ s) Slice :=
{ internalState := .operating s.startPos (ToForwardSearcher.toSearcher s pat) }
inductive SplitInclusiveIterator (ρ : Type) [ToForwardSearcher ρ σ] where
| operating (s : Slice) (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s))
inductive SplitInclusiveIterator (ρ : Type) (s : Slice) [ToForwardSearcher ρ σ] where
| operating (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s))
| atEnd
deriving Inhabited
@ -180,40 +219,79 @@ namespace SplitInclusiveIterator
variable [ToForwardSearcher ρ σ]
instance [Pure m] : Std.Iterators.Iterator (SplitInclusiveIterator ρ) m Slice where
IsPlausibleStep := fun _ _ => True
step := fun ⟨iter⟩ =>
match iter with
| .operating s currPos searcher =>
match Internal.nextMatch searcher with
| some (searcher, _, endPos) =>
instance : Std.Iterators.Iterator (SplitInclusiveIterator ρ s) Id Slice where
IsPlausibleStep
| ⟨.operating _ s⟩, .yield ⟨.operating _ s'⟩ _ => s'.IsPlausibleSuccessorOf s
| ⟨.operating _ s⟩, .yield ⟨.atEnd ..⟩ _ => True
| ⟨.operating _ s⟩, .skip ⟨.operating _ s'⟩ => s'.IsPlausibleSuccessorOf s
| ⟨.operating _ s⟩, .skip ⟨.atEnd ..⟩ => False
| ⟨.operating _ s⟩, .done => True
| ⟨.atEnd⟩, .yield .. => False
| ⟨.atEnd⟩, .skip _ => False
| ⟨.atEnd⟩, .done => True
step
| ⟨.operating currPos searcher⟩ =>
match h : searcher.step with
| ⟨.yield searcher' (.matched _ endPos), hps⟩ =>
let slice := s.replaceStartEnd! currPos endPos
let nextIt := ⟨.operating s endPos searcher⟩
pure (.deflate ⟨.yield nextIt slice, by simp⟩)
| none =>
let nextIt := ⟨.operating endPos searcher'⟩
pure (.deflate ⟨.yield nextIt slice,
by simp [nextIt, hps.isPlausibleSuccessor_of_yield]⟩)
| ⟨.yield searcher' (.rejected ..), hps⟩ =>
pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩,
by simp [hps.isPlausibleSuccessor_of_yield]⟩)
| ⟨.skip searcher', hps⟩ =>
pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩,
by simp [hps.isPlausibleSuccessor_of_skip]⟩)
| ⟨.done, _⟩ =>
if currPos != s.endPos then
let slice := s.replaceStart currPos
pure (.deflate ⟨.yield ⟨.atEnd⟩ slice, by simp⟩)
else
pure (.deflate ⟨.done, by simp⟩)
| .atEnd => pure (.deflate ⟨.done, by simp⟩)
| .atEnd => pure (.deflate ⟨.done, by simp⟩)
-- TODO: Finiteness after we have a notion of lawful searcher
private def toOption : SplitInclusiveIterator ρ s → Option (Std.Iter (α := σ s) (SearchStep s))
| .operating _ s => some s
| .atEnd => none
instance [Monad m] [Monad n] :
Std.Iterators.IteratorCollect (SplitInclusiveIterator ρ) m n :=
private def finitenessRelation [Std.Iterators.Finite (σ s) Id] :
Std.Iterators.FinitenessRelation (SplitInclusiveIterator ρ s) Id where
rel := InvImage (Option.lt Std.Iterators.Iter.IsPlausibleSuccessorOf)
(SplitInclusiveIterator.toOption ∘ Std.Iterators.IterM.internalState)
wf := InvImage.wf _ (Option.wellFounded_lt Std.Iterators.Finite.wf_of_id)
subrelation {it it'} h := by
simp_wf
obtain ⟨step, h, h'⟩ := h
match step with
| .yield it'' out | .skip it'' =>
obtain rfl : it' = it'' := by simpa using h.symm
simp only [Std.Iterators.IterM.IsPlausibleStep, Std.Iterators.Iterator.IsPlausibleStep] at h'
revert h'
match it, it' with
| ⟨.operating _ searcher⟩, ⟨.operating _ searcher'⟩ => simp [SplitInclusiveIterator.toOption, Option.lt]
| ⟨.operating _ searcher⟩, ⟨.atEnd⟩ => simp [SplitInclusiveIterator.toOption, Option.lt]
| ⟨.atEnd⟩, _ => simp
@[no_expose]
instance [Std.Iterators.Finite (σ s) Id] :
Std.Iterators.Finite (SplitInclusiveIterator ρ s) Id :=
.of_finitenessRelation finitenessRelation
instance [Monad n] {s} :
Std.Iterators.IteratorCollect (SplitInclusiveIterator ρ s) Id n :=
.defaultImplementation
instance [Monad m] [Monad n] :
Std.Iterators.IteratorCollectPartial (SplitInclusiveIterator ρ) m n :=
instance [Monad n] {s} :
Std.Iterators.IteratorCollectPartial (SplitInclusiveIterator ρ s) Id n :=
.defaultImplementation
instance [Monad m] [Monad n] :
Std.Iterators.IteratorLoop (SplitInclusiveIterator ρ) m n :=
instance [Monad n] {s} :
Std.Iterators.IteratorLoop (SplitInclusiveIterator ρ s) Id n :=
.defaultImplementation
instance [Monad m] [Monad n] :
Std.Iterators.IteratorLoopPartial (SplitInclusiveIterator ρ) m n :=
instance [Monad n] {s} :
Std.Iterators.IteratorLoopPartial (SplitInclusiveIterator ρ s) Id n :=
.defaultImplementation
end SplitInclusiveIterator
@ -225,15 +303,15 @@ matched subslices are included at the end of each subslice.
This function is generic over all currently supported patterns.
Examples:
* {lean}`("coffee tea water".toSlice.splitInclusive Char.isWhitespace).allowNontermination.toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.splitInclusive ' ').allowNontermination.toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.splitInclusive " tea ").allowNontermination.toList == ["coffee tea ".toSlice, "water".toSlice]`
* {lean}`("baaab".toSlice.splitInclusive "aa").allowNontermination.toList == ["baa".toSlice, "ab".toSlice]`
* {lean}`("coffee tea water".toSlice.splitInclusive Char.isWhitespace).toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.splitInclusive ' ').toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".toSlice.splitInclusive " tea ").toList == ["coffee tea ".toSlice, "water".toSlice]`
* {lean}`("baaab".toSlice.splitInclusive "aa").toList == ["baa".toSlice, "ab".toSlice]`
-/
@[specialize pat]
def splitInclusive [ToForwardSearcher ρ σ] (s : Slice) (pat : ρ) :
Std.Iter (α := SplitInclusiveIterator ρ) Slice :=
{ internalState := .operating s s.startPos (ToForwardSearcher.toSearcher s pat) }
Std.Iter (α := SplitInclusiveIterator ρ s) Slice :=
{ internalState := .operating s.startPos (ToForwardSearcher.toSearcher s pat) }
/--
If {name}`pat` matches a prefix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
@ -384,9 +462,7 @@ Examples:
@[specialize pat]
def find? [ToForwardSearcher ρ σ] (s : Slice) (pat : ρ) : Option s.Pos :=
let searcher := ToForwardSearcher.toSearcher s pat
match Internal.nextMatch searcher with
| some (_, startPos, _) => some startPos
| none => none
searcher.findSome? (fun | .matched startPos _ => some startPos | .rejected .. => none)
/--
Checks whether a slice has a match of the pattern {name}`pat` anywhere.
@ -401,7 +477,7 @@ Examples:
@[specialize pat]
def contains [ToForwardSearcher ρ σ] (s : Slice) (pat : ρ) : Bool :=
let searcher := ToForwardSearcher.toSearcher s pat
Internal.nextMatch searcher |>.isSome
searcher.any (· matches .matched ..)
/--
Checks whether a slice only consists of matches of the pattern {name}`pat` anywhere.
@ -415,6 +491,7 @@ Examples:
* {lean}`"brown and orange".toSlice.all Char.isLower = false`
* {lean}`"aaaaaa".toSlice.all 'a' = true`
* {lean}`"aaaaaa".toSlice.all "aa" = true`
* {lean}`"aaaaaaa".toSlice.all "aa" = false`
-/
@[inline]
def all [ForwardPattern ρ] (s : Slice) (pat : ρ) : Bool :=
@ -445,8 +522,8 @@ Examples:
def endsWith [BackwardPattern ρ] (s : Slice) (pat : ρ) : Bool :=
BackwardPattern.endsWith s pat
inductive RevSplitIterator (ρ : Type) [ToBackwardSearcher ρ σ] where
| operating (s : Slice) (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s))
inductive RevSplitIterator (ρ : Type) (s : Slice) [ToBackwardSearcher ρ σ] where
| operating (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s))
| atEnd
deriving Inhabited
@ -454,37 +531,74 @@ namespace RevSplitIterator
variable [ToBackwardSearcher ρ σ]
instance [Pure m] : Std.Iterators.Iterator (RevSplitIterator ρ) m Slice where
IsPlausibleStep := fun _ _ => True
step := fun ⟨iter⟩ =>
match iter with
| .operating s currPos searcher =>
match Internal.nextMatch searcher with
| some (searcher, startPos, endPos) =>
instance [Pure m] : Std.Iterators.Iterator (RevSplitIterator ρ s) m Slice where
IsPlausibleStep
| ⟨.operating _ s⟩, .yield ⟨.operating _ s'⟩ _ => s'.IsPlausibleSuccessorOf s
| ⟨.operating _ s⟩, .yield ⟨.atEnd ..⟩ _ => True
| ⟨.operating _ s⟩, .skip ⟨.operating _ s'⟩ => s'.IsPlausibleSuccessorOf s
| ⟨.operating _ s⟩, .skip ⟨.atEnd ..⟩ => False
| ⟨.operating _ s⟩, .done => True
| ⟨.atEnd⟩, .yield .. => False
| ⟨.atEnd⟩, .skip _ => False
| ⟨.atEnd⟩, .done => True
step
| ⟨.operating currPos searcher⟩ =>
match h : searcher.step with
| ⟨.yield searcher' (.matched startPos endPos), hps⟩ =>
let slice := s.replaceStartEnd! endPos currPos
let nextIt := ⟨.operating s startPos searcher⟩
pure (.deflate ⟨.yield nextIt slice, by simp⟩)
| none =>
let nextIt := ⟨.operating startPos searcher'⟩
pure (.deflate ⟨.yield nextIt slice, by simp [nextIt, hps.isPlausibleSuccessor_of_yield]⟩)
| ⟨.yield searcher' (.rejected ..), hps⟩ =>
pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩,
by simp [hps.isPlausibleSuccessor_of_yield]⟩)
| ⟨.skip searcher', hps⟩ =>
pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩,
by simp [hps.isPlausibleSuccessor_of_skip]⟩)
| ⟨.done, _⟩ =>
if currPos ≠ s.startPos then
let slice := s.replaceEnd currPos
pure (.deflate ⟨.yield ⟨.atEnd⟩ slice, by simp⟩)
else
pure (.deflate ⟨.done, by simp⟩)
| .atEnd => pure (.deflate ⟨.done, by simp⟩)
| .atEnd => pure (.deflate ⟨.done, by simp⟩)
-- TODO: Finiteness after we have a notion of lawful searcher
private def toOption : RevSplitIterator ρ s → Option (Std.Iter (α := σ s) (SearchStep s))
| .operating _ s => some s
| .atEnd => none
instance [Monad m] [Monad n] : Std.Iterators.IteratorCollect (RevSplitIterator ρ) m n :=
private def finitenessRelation [Std.Iterators.Finite (σ s) Id] :
Std.Iterators.FinitenessRelation (RevSplitIterator ρ s) Id where
rel := InvImage (Option.lt Std.Iterators.Iter.IsPlausibleSuccessorOf)
(RevSplitIterator.toOption ∘ Std.Iterators.IterM.internalState)
wf := InvImage.wf _ (Option.wellFounded_lt Std.Iterators.Finite.wf_of_id)
subrelation {it it'} h := by
simp_wf
obtain ⟨step, h, h'⟩ := h
match step with
| .yield it'' out | .skip it'' =>
obtain rfl : it' = it'' := by simpa using h.symm
simp only [Std.Iterators.IterM.IsPlausibleStep, Std.Iterators.Iterator.IsPlausibleStep] at h'
revert h'
match it, it' with
| ⟨.operating _ searcher⟩, ⟨.operating _ searcher'⟩ => simp [RevSplitIterator.toOption, Option.lt]
| ⟨.operating _ searcher⟩, ⟨.atEnd⟩ => simp [RevSplitIterator.toOption, Option.lt]
| ⟨.atEnd⟩, _ => simp
@[no_expose]
instance [Std.Iterators.Finite (σ s) Id] : Std.Iterators.Finite (RevSplitIterator ρ s) Id :=
.of_finitenessRelation finitenessRelation
instance [Monad m] [Monad n] : Std.Iterators.IteratorCollect (RevSplitIterator ρ s) m n :=
.defaultImplementation
instance [Monad m] [Monad n] :
Std.Iterators.IteratorCollectPartial (RevSplitIterator ρ) m n :=
Std.Iterators.IteratorCollectPartial (RevSplitIterator ρ s) m n :=
.defaultImplementation
instance [Monad m] [Monad n] : Std.Iterators.IteratorLoop (RevSplitIterator ρ) m n :=
instance [Monad m] [Monad n] : Std.Iterators.IteratorLoop (RevSplitIterator ρ s) m n :=
.defaultImplementation
instance [Monad m] [Monad n] : Std.Iterators.IteratorLoopPartial (RevSplitIterator ρ) m n :=
instance [Monad m] [Monad n] : Std.Iterators.IteratorLoopPartial (RevSplitIterator ρ s) m n :=
.defaultImplementation
end RevSplitIterator
@ -500,13 +614,13 @@ This function is generic over all currently supported patterns except
{name}`String`/{name}`String.Slice`.
Examples:
* {lean}`("coffee tea water".toSlice.revSplit Char.isWhitespace).allowNontermination.toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]`
* {lean}`("coffee tea water".toSlice.revSplit ' ').allowNontermination.toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]`
* {lean}`("coffee tea water".toSlice.revSplit Char.isWhitespace).toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]`
* {lean}`("coffee tea water".toSlice.revSplit ' ').toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]`
-/
@[specialize pat]
def revSplit [ToBackwardSearcher ρ σ] (s : Slice) (pat : ρ) :
Std.Iter (α := RevSplitIterator ρ) Slice :=
{ internalState := .operating s s.endPos (ToBackwardSearcher.toSearcher s pat) }
Std.Iter (α := RevSplitIterator ρ s) Slice :=
{ internalState := .operating s.endPos (ToBackwardSearcher.toSearcher s pat) }
/--
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
@ -658,9 +772,7 @@ Examples:
@[specialize pat]
def revFind? [ToBackwardSearcher ρ σ] (s : Slice) (pat : ρ) : Option s.Pos :=
let searcher := ToBackwardSearcher.toSearcher s pat
match Internal.nextMatch searcher with
| some (_, startPos, _) => some startPos
| none => none
searcher.findSome? (fun | .matched startPos _ => some startPos | .rejected .. => none)
end BackwardPatternUsers
@ -1048,9 +1160,9 @@ Creates an iterator over all lines in {name}`s` with the line ending characters
stripped.
Examples:
* {lean}`"foo\r\nbar\n\nbaz\n".toSlice.lines.allowNontermination.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz".toSlice.lines.allowNontermination.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz\r".toSlice.lines.allowNontermination.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz\r".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz\n".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz\r".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz\r".toSlice]`
-/
def lines (s : Slice) :=
s.splitInclusive '\n' |>.map lines.lineMap

View file

@ -19,16 +19,16 @@ Tests for `String.Slice` functions
#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.split Char.isWhitespace).toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]
#guard ("coffee tea water".toSlice.split ' ').toList == ["coffee".toSlice, "tea".toSlice, "water".toSlice]
#guard ("coffee tea water".toSlice.split " tea ").toList == ["coffee".toSlice, "water".toSlice]
#guard ("baaab".toSlice.split "aa").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 ("coffee tea water".toSlice.splitInclusive Char.isWhitespace).toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]
#guard ("coffee tea water".toSlice.splitInclusive ' ').toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]
#guard ("coffee tea water".toSlice.splitInclusive " tea ").toList == ["coffee tea ".toSlice, "water".toSlice]
#guard ("a".toSlice.splitInclusive (fun (_ : Char) => true)).toList == ["a".toSlice]
#guard ("baaab".toSlice.splitInclusive "aa").toList == ["baa".toSlice, "ab".toSlice]
#guard "red green blue".toSlice.drop 4 == "green blue".toSlice
#guard "red green blue".toSlice.drop 10 == "blue".toSlice
@ -77,6 +77,7 @@ Tests for `String.Slice` functions
#guard "brown and orange".toSlice.all Char.isLower = false
#guard "aaaaaa".toSlice.all 'a' = true
#guard "aaaaaa".toSlice.all "aa" = true
#guard "aaaaaaa".toSlice.all "aa" = false
#guard "red green blue".toSlice.endsWith "blue" = true
#guard "red green blue".toSlice.endsWith "green" = false
@ -84,8 +85,8 @@ Tests for `String.Slice` functions
#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 ("coffee tea water".toSlice.revSplit Char.isWhitespace).toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]
#guard ("coffee tea water".toSlice.revSplit ' ').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
@ -158,9 +159,9 @@ Tests for `String.Slice` functions
#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 "foo\r\nbar\n\nbaz\n".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]
#guard "foo\r\nbar\n\nbaz".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]
#guard "foo\r\nbar\n\nbaz\r".toSlice.lines.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