From b83bc7024bd4052ebb70b0b44e0f0cf13365b0de Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:51:12 +0000 Subject: [PATCH 1/9] LawfulForwardPattern instance more generally --- .../String/Lemmas/Pattern/String/ForwardPattern.lean | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean b/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean index c39830f6f220..1396ee619d60 100644 --- a/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean +++ b/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean @@ -76,10 +76,12 @@ namespace Model.ForwardSliceSearcher open Pattern.ForwardSliceSearcher +public instance {pat : Slice} : LawfulForwardPattern pat where + skipPrefixOfNonempty?_eq _ := rfl + startsWith_eq _ := isSome_skipPrefix?.symm + public theorem lawfulForwardPatternModel {pat : Slice} (hpat : pat.isEmpty = false) : LawfulForwardPatternModel pat where - skipPrefixOfNonempty?_eq h := rfl - startsWith_eq s := isSome_skipPrefix?.symm skipPrefix?_eq_some_iff pos := by simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat] @@ -89,10 +91,12 @@ namespace Model.ForwardStringSearcher open Pattern.ForwardSliceSearcher +public instance {pat : String} : LawfulForwardPattern pat where + skipPrefixOfNonempty?_eq _ := rfl + startsWith_eq _ := isSome_skipPrefix?.symm + public theorem lawfulForwardPatternModel {pat : String} (hpat : pat ≠ "") : LawfulForwardPatternModel pat where - skipPrefixOfNonempty?_eq h := rfl - startsWith_eq s := isSome_skipPrefix?.symm skipPrefix?_eq_some_iff pos := by simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat] From c94dfc41b7a74a8c211b1d80e9140c9381466935 Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:58:57 +0000 Subject: [PATCH 2/9] Rename ForwardPatternModel to PatternModel --- .../Data/String/Lemmas/Pattern/Basic.lean | 107 +++++++++--------- src/Init/Data/String/Lemmas/Pattern/Char.lean | 6 +- .../String/Lemmas/Pattern/Find/Basic.lean | 12 +- src/Init/Data/String/Lemmas/Pattern/Pred.lean | 12 +- .../String/Lemmas/Pattern/Split/Basic.lean | 22 ++-- .../String/Lemmas/Pattern/String/Basic.lean | 12 +- .../String/Lemmas/Pattern/TakeDrop/Basic.lean | 12 +- .../String/Lemmas/Pattern/TakeDrop/Char.lean | 2 +- .../String/Lemmas/Pattern/TakeDrop/Pred.lean | 2 +- .../Lemmas/Pattern/TakeDrop/String.lean | 2 +- 10 files changed, 95 insertions(+), 94 deletions(-) diff --git a/src/Init/Data/String/Lemmas/Pattern/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/Basic.lean index 1c3811602d70..de3342165c4f 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Basic.lean @@ -31,19 +31,20 @@ This file develops basic theory around searching in strings. We provide a typeclass for providing semantics to a pattern and then define the relevant notions of matching a pattern that let us state compatibility typeclasses for {name}`ForwardPattern` and -{name}`ToForwardSearcher`. These typeclasses can then be required by correctness results for -string functions which are implemented using the pattern framework. +{name}`ToForwardSearcher` as well as their backwards variants. These typeclasses can then be +required by correctness results for string functions which are implemented using the pattern +framework. -/ /-- This data-carrying typeclass is used to give semantics to a pattern type that implements {name}`ForwardPattern` and/or {name}`ToForwardSearcher` by providing an abstract, not necessarily -decidable {name}`ForwardPatternModel.Matches` predicate that implementates of {name}`ForwardPattern` +decidable {name}`PatternModel.Matches` predicate that implementates of {name}`ForwardPattern` and {name}`ToForwardSearcher` can be validated against. Correctness results for generic functions relying on the pattern infrastructure, for example the correctness result for {name (scope := "Init.Data.String.Slice")}`String.Slice.split`, are then -stated in terms of {name}`ForwardPatternModel.Matches`, and can be specialized to specific patterns +stated in terms of {name}`PatternModel.Matches`, and can be specialized to specific patterns from there. The corresponding compatibility typeclasses are @@ -59,7 +60,7 @@ searching. This means that pattern types that allow searching for the empty string will have to special-case the empty string in their correctness statements. -/ -class ForwardPatternModel {ρ : Type} (pat : ρ) : Type where +class PatternModel {ρ : Type} (pat : ρ) : Type where /-- The predicate that says which strings match the pattern. -/ Matches : String → Prop not_matches_empty : ¬ Matches "" @@ -69,21 +70,21 @@ Predicate stating that the region between the start of the slice {name}`s` and t {name}`endPos` matches the pattern {name}`pat`. Note that there might be a longer match, see {name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.IsLongestMatch`. -/ -structure IsMatch (pat : ρ) [ForwardPatternModel pat] {s : Slice} (endPos : s.Pos) : Prop where - matches_copy : ForwardPatternModel.Matches pat (s.sliceTo endPos).copy +structure IsMatch (pat : ρ) [PatternModel pat] {s : Slice} (endPos : s.Pos) : Prop where + matches_copy : PatternModel.Matches pat (s.sliceTo endPos).copy -theorem IsMatch.ne_startPos {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} +theorem IsMatch.ne_startPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} (h : IsMatch pat pos) : pos ≠ s.startPos := by intro hc - apply ForwardPatternModel.not_matches_empty (pat := pat) + apply PatternModel.not_matches_empty (pat := pat) simpa [hc] using h.matches_copy -theorem isMatch_iff {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} : - IsMatch pat pos ↔ ForwardPatternModel.Matches pat (s.sliceTo pos).copy := +theorem isMatch_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : + IsMatch pat pos ↔ PatternModel.Matches pat (s.sliceTo pos).copy := ⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩ -theorem isMatch_iff_exists_splits {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} : - IsMatch pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ t₂ ∧ ForwardPatternModel.Matches pat t₁ := by +theorem isMatch_iff_exists_splits {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : + IsMatch pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ t₂ ∧ PatternModel.Matches pat t₁ := by rw [isMatch_iff] refine ⟨fun h => ⟨_, _, pos.splits, h⟩, fun ⟨t₁, t₂, h₁, h₂⟩ => ?_⟩ rwa [h₁.eq_left pos.splits] at h₂ @@ -96,22 +97,22 @@ beginning of the slice. This is what a correct matcher should match. In some cases, being a match and being a longest match will coincide, see {name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixForwardPatternModel`. -/ -structure IsLongestMatch (pat : ρ) [ForwardPatternModel pat] {s : Slice} (pos : s.Pos) where +structure IsLongestMatch (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) where isMatch : IsMatch pat pos not_isMatch : ∀ pos', pos < pos' → ¬ IsMatch pat pos' -theorem IsLongestMatch.ne_startPos {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} +theorem IsLongestMatch.ne_startPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} (h : IsLongestMatch pat pos) : pos ≠ s.startPos := h.isMatch.ne_startPos -theorem IsLongestMatch.eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos pos' : s.Pos} +theorem IsLongestMatch.eq {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos} (h : IsLongestMatch pat pos) (h' : IsLongestMatch pat pos') : pos = pos' := by apply Std.le_antisymm · exact Std.not_lt.1 (fun hlt => h'.not_isMatch _ hlt h.isMatch) · exact Std.not_lt.1 (fun hlt => h.not_isMatch _ hlt h'.isMatch) open Classical in -theorem IsMatch.exists_isLongestMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} : +theorem IsMatch.exists_isLongestMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : IsMatch pat pos → ∃ (pos' : s.Pos), IsLongestMatch pat pos' := by induction pos using WellFounded.induction Pos.wellFounded_gt with | h pos ih intro h₁ @@ -120,7 +121,7 @@ theorem IsMatch.exists_isLongestMatch {pat : ρ} [ForwardPatternModel pat] {s : exact ih _ hp₁ hp₂ · exact ⟨pos, ⟨h₁, fun p' hp₁ hp₂ => h₂ ⟨_, hp₁, hp₂⟩⟩⟩ -theorem IsLongestMatch.le_of_isMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos pos' : s.Pos} +theorem IsLongestMatch.le_of_isMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos} (h : IsLongestMatch pat pos) (h' : IsMatch pat pos') : pos' ≤ pos := Std.not_lt.1 (fun hlt => h.not_isMatch _ hlt h') @@ -129,15 +130,15 @@ Predicate stating that a match for a given pattern is never a proper prefix of a This implies that the notion of match and longest match coincide. -/ -class NoPrefixForwardPatternModel {ρ : Type} (pat : ρ) [ForwardPatternModel pat] : Prop where - eq_empty (s t) : ForwardPatternModel.Matches pat s → ForwardPatternModel.Matches pat (s ++ t) → t = "" +class NoPrefixForwardPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where + eq_empty (s t) : PatternModel.Matches pat s → PatternModel.Matches pat (s ++ t) → t = "" -theorem NoPrefixForwardPatternModel.of_length_eq {ρ : Type} {pat : ρ} [ForwardPatternModel pat] - (h : ∀ s t, ForwardPatternModel.Matches pat s → ForwardPatternModel.Matches pat t → s.length = t.length) : +theorem NoPrefixForwardPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat] + (h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) : NoPrefixForwardPatternModel pat where eq_empty s t hs ht := by simpa using h s _ hs ht -theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [ForwardPatternModel pat] [NoPrefixForwardPatternModel pat] +theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoPrefixForwardPatternModel pat] {s : Slice} {pos : s.Pos} : IsLongestMatch pat pos ↔ IsMatch pat pos := by refine ⟨fun h => h.isMatch, fun h => ⟨h, fun pos' hpos' hm => ?_⟩⟩ obtain ⟨t₁, t₂, ht₁, ht₂⟩ := isMatch_iff_exists_splits.1 h @@ -149,32 +150,32 @@ theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [ForwardPatternModel p Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match of {name}`pat` in {name}`s` and it is longest among matches starting at {name}`startPos`. -/ -structure IsLongestMatchAt (pat : ρ) [ForwardPatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where +structure IsLongestMatchAt (pat : ρ) [PatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where le : startPos ≤ endPos isLongestMatch_sliceFrom : IsLongestMatch pat (Slice.Pos.sliceFrom _ _ le) -theorem isLongestMatchAt_iff {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos₁ pos₂ : s.Pos} : +theorem isLongestMatchAt_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos₁ pos₂ : s.Pos} : IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ (h : pos₁ ≤ pos₂), IsLongestMatch pat (Slice.Pos.sliceFrom _ _ h) := ⟨fun ⟨h, h'⟩ => ⟨h, h'⟩, fun ⟨h, h'⟩ => ⟨h, h'⟩⟩ -theorem IsLongestMatchAt.lt {pat : ρ} [ForwardPatternModel pat] {s : Slice} {startPos endPos : s.Pos} +theorem IsLongestMatchAt.lt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos} (h : IsLongestMatchAt pat startPos endPos) : startPos < endPos := by have := h.isLongestMatch_sliceFrom.ne_startPos rw [← Pos.startPos_lt_iff, ← Slice.Pos.ofSliceFrom_lt_ofSliceFrom_iff] at this simpa -theorem IsLongestMatchAt.eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} {startPos endPos endPos' : s.Pos} +theorem IsLongestMatchAt.eq {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos endPos' : s.Pos} (h : IsLongestMatchAt pat startPos endPos) (h' : IsLongestMatchAt pat startPos endPos') : endPos = endPos' := by simpa using h.isLongestMatch_sliceFrom.eq h'.isLongestMatch_sliceFrom -private theorem isLongestMatch_of_eq {pat : ρ} [ForwardPatternModel pat] {s t : Slice} +private theorem isLongestMatch_of_eq {pat : ρ} [PatternModel pat] {s t : Slice} {pos : s.Pos} {pos' : t.Pos} (h_eq : s = t) (h_pos : pos.offset = pos'.offset) (hm : IsLongestMatch pat pos) : IsLongestMatch pat pos' := by subst h_eq; exact (Slice.Pos.ext h_pos) ▸ hm -theorem isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat] +theorem isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos} {startPos endPos : (s.sliceFrom base).Pos} : IsLongestMatchAt pat startPos endPos ↔ IsLongestMatchAt pat (Pos.ofSliceFrom startPos) (Pos.ofSliceFrom endPos) := by constructor @@ -187,14 +188,14 @@ theorem isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom {pat : ρ} [ForwardPat exact isLongestMatch_of_eq Slice.sliceFrom_sliceFrom.symm (by simp [Pos.Raw.ext_iff]; omega) h.isLongestMatch_sliceFrom -theorem IsLongestMatch.isLongestMatchAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem IsLongestMatch.isLongestMatchAt_ofSliceFrom {pat : ρ} [PatternModel pat] {s : Slice} {p₀ : s.Pos} {pos : (s.sliceFrom p₀).Pos} (h : IsLongestMatch pat pos) : IsLongestMatchAt pat p₀ (Slice.Pos.ofSliceFrom pos) where le := Slice.Pos.le_ofSliceFrom isLongestMatch_sliceFrom := by simpa @[simp] -theorem isLongestMatchAt_startPos_iff {pat : ρ} [ForwardPatternModel pat] {s : Slice} {endPos : s.Pos} : +theorem isLongestMatchAt_startPos_iff {pat : ρ} [PatternModel pat] {s : Slice} {endPos : s.Pos} : IsLongestMatchAt pat s.startPos endPos ↔ IsLongestMatch pat endPos := by simpa [isLongestMatchAt_iff] using ⟨fun h => isLongestMatch_of_eq (by simp) (by simp) h, @@ -203,19 +204,19 @@ theorem isLongestMatchAt_startPos_iff {pat : ρ} [ForwardPatternModel pat] {s : /-- Predicate stating that there is a (longest) match starting at the given position. -/ -structure MatchesAt (pat : ρ) [ForwardPatternModel pat] {s : Slice} (pos : s.Pos) : Prop where +structure MatchesAt (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) : Prop where exists_isLongestMatchAt : ∃ endPos, IsLongestMatchAt pat pos endPos -theorem matchesAt_iff_exists_isLongestMatchAt {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem matchesAt_iff_exists_isLongestMatchAt {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : MatchesAt pat pos ↔ ∃ endPos, IsLongestMatchAt pat pos endPos := ⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩ -theorem matchesAt_iff_exists_isLongestMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem matchesAt_iff_exists_isLongestMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : MatchesAt pat pos ↔ ∃ (endPos : s.Pos), ∃ h, IsLongestMatch pat (pos.sliceFrom endPos h) := ⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestMatch_sliceFrom⟩, fun ⟨p, h₁, h₂⟩ => ⟨p, ⟨h₁, h₂⟩⟩⟩ -theorem matchesAt_iff_exists_isMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem matchesAt_iff_exists_isMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : MatchesAt pat pos ↔ ∃ (endPos : s.Pos), ∃ h, IsMatch pat (pos.sliceFrom endPos h) := by refine ⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestMatch_sliceFrom.isMatch⟩, fun ⟨p, h₁, h₂⟩ => ?_⟩ @@ -225,13 +226,13 @@ theorem matchesAt_iff_exists_isMatch {pat : ρ} [ForwardPatternModel pat] {s : S by simpa using hq⟩⟩ @[simp] -theorem not_matchesAt_endPos {pat : ρ} [ForwardPatternModel pat] {s : Slice} : +theorem not_matchesAt_endPos {pat : ρ} [PatternModel pat] {s : Slice} : ¬ MatchesAt pat s.endPos := by simp only [matchesAt_iff_exists_isMatch, Pos.endPos_le, exists_prop_eq] intro h simpa [← Pos.ofSliceFrom_inj] using h.ne_startPos -theorem matchesAt_iff_matchesAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat] {s : Slice} {base : s.Pos} +theorem matchesAt_iff_matchesAt_ofSliceFrom {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos} {pos : (s.sliceFrom base).Pos} : MatchesAt pat pos ↔ MatchesAt pat (Pos.ofSliceFrom pos) := by simp only [matchesAt_iff_exists_isLongestMatchAt] constructor @@ -241,7 +242,7 @@ theorem matchesAt_iff_matchesAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat] exact ⟨base.sliceFrom endPos (Std.le_trans Slice.Pos.le_ofSliceFrom h.le), isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom.mpr (by simpa using h)⟩ -theorem IsLongestMatchAt.matchesAt {pat : ρ} [ForwardPatternModel pat] {s : Slice} {startPos endPos : s.Pos} +theorem IsLongestMatchAt.matchesAt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos} (h : IsLongestMatchAt pat startPos endPos) : MatchesAt pat startPos where exists_isLongestMatchAt := ⟨_, h⟩ @@ -250,12 +251,12 @@ open Classical in Noncomputable model function returning the end point of the longest match starting at the given position, or {lean}`none` if there is no match. -/ -noncomputable def matchAt? {ρ : Type} (pat : ρ) [ForwardPatternModel pat] +noncomputable def matchAt? {ρ : Type} (pat : ρ) [PatternModel pat] {s : Slice} (startPos : s.Pos) : Option s.Pos := if h : ∃ endPos, IsLongestMatchAt pat startPos endPos then some h.choose else none @[simp] -theorem matchAt?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] +theorem matchAt?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos} : matchAt? pat startPos = some endPos ↔ IsLongestMatchAt pat startPos endPos := by fun_cases matchAt? with @@ -263,7 +264,7 @@ theorem matchAt?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] | case2 => simp_all @[simp] -theorem matchAt?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] +theorem matchAt?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice} {startPos : s.Pos} : matchAt? pat startPos = none ↔ ¬ MatchesAt pat startPos := by fun_cases matchAt? with @@ -271,18 +272,18 @@ theorem matchAt?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] | case2 h => simpa using fun ⟨h'⟩ => h h' /-- -Predicate stating compatibility between {name}`ForwardPatternModel` and {name}`ForwardPattern`. +Predicate stating compatibility between {name}`PatternModel` and {name}`ForwardPattern`. This extends {name}`LawfulForwardPattern`, but it is much stronger because it forces the {name}`ForwardPattern` to match the longest prefix of the given slice that matches the property -supplied by the {name}`ForwardPatternModel` instance. +supplied by the {name}`PatternModel` instance. -/ class LawfulForwardPatternModel {ρ : Type} (pat : ρ) [ForwardPattern pat] - [ForwardPatternModel pat] : Prop extends LawfulForwardPattern pat where + [PatternModel pat] : Prop extends LawfulForwardPattern pat where skipPrefix?_eq_some_iff (pos) : ForwardPattern.skipPrefix? pat s = some pos ↔ IsLongestMatch pat pos open Classical in -theorem LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [ForwardPatternModel pat] +theorem LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [PatternModel pat] [LawfulForwardPatternModel pat] {s : Slice} {p₀ : s.Pos} : ForwardPattern.skipPrefix? pat (s.sliceFrom p₀) = none ↔ ¬ MatchesAt pat p₀ := by rw [← Decidable.not_iff_not] @@ -291,7 +292,7 @@ theorem LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff {ρ : Type} · exact ⟨Slice.Pos.ofSliceFrom p, hp.isLongestMatchAt_ofSliceFrom⟩ · exact ⟨p₀.sliceFrom p hp.le, hp.isLongestMatch_sliceFrom⟩ -theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [ForwardPatternModel pat] +theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [PatternModel pat] [LawfulForwardPatternModel pat] {s : Slice} : ForwardPattern.skipPrefix? pat s = none ↔ ¬ MatchesAt pat s.startPos := by conv => lhs; rw [← sliceFrom_startPos (s := s)] @@ -306,7 +307,7 @@ matches. Hence, this predicate determines the list of search steps up to grouping of rejections. -/ -inductive IsValidSearchFrom (pat : ρ) [ForwardPatternModel pat] {s : Slice} : +inductive IsValidSearchFrom (pat : ρ) [PatternModel pat] {s : Slice} : s.Pos → List (SearchStep s) → Prop where | endPos : IsValidSearchFrom pat s.endPos [] | matched {startPos endPos : s.Pos} : @@ -316,14 +317,14 @@ inductive IsValidSearchFrom (pat : ρ) [ForwardPatternModel pat] {s : Slice} : (∀ pos, startPos ≤ pos → pos < endPos → ¬ MatchesAt pat pos) → IsValidSearchFrom pat endPos l → IsValidSearchFrom pat startPos (.rejected startPos endPos :: l) -theorem IsValidSearchFrom.matched_of_eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem IsValidSearchFrom.matched_of_eq {pat : ρ} [PatternModel pat] {s : Slice} {startPos startPos' endPos : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidSearchFrom pat endPos l) (h₂ : IsLongestMatchAt pat startPos' endPos) (h₃ : startPos = startPos') : IsValidSearchFrom pat startPos' (.matched startPos endPos :: l) := by cases h₃ exact IsValidSearchFrom.matched h₂ h₁ -theorem IsValidSearchFrom.mismatched_of_eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem IsValidSearchFrom.mismatched_of_eq {pat : ρ} [PatternModel pat] {s : Slice} {startPos startPos' endPos : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidSearchFrom pat endPos l) (h₀ : startPos' < endPos) (h₂ : ∀ pos, startPos' ≤ pos → pos < endPos → ¬ MatchesAt pat pos) (h₃ : startPos = startPos') : @@ -331,7 +332,7 @@ theorem IsValidSearchFrom.mismatched_of_eq {pat : ρ} [ForwardPatternModel pat] cases h₃ exact IsValidSearchFrom.mismatched h₀ h₂ h₁ -theorem IsValidSearchFrom.endPos_of_eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} +theorem IsValidSearchFrom.endPos_of_eq {pat : ρ} [PatternModel pat] {s : Slice} {p : s.Pos} {l : List (SearchStep s)} (hp : p = s.endPos) (hl : l = []) : IsValidSearchFrom pat p l := by cases hp @@ -339,18 +340,18 @@ theorem IsValidSearchFrom.endPos_of_eq {pat : ρ} [ForwardPatternModel pat] {s : exact IsValidSearchFrom.endPos /-- -Predicate stating compatibility between {name}`ForwardPatternModel` and {name}`ToForwardSearcher`. +Predicate stating compatibility between {name}`PatternModel` and {name}`ToForwardSearcher`. We require the searcher to always match the longest match at the first position where the pattern matches; see {name}`IsValidSearchFrom`. -/ -class LawfulToForwardSearcherModel {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type} +class LawfulToForwardSearcherModel {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} [ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)] [∀ s, Std.Iterators.Finite (σ s) Id] : Prop where isValidSearchFrom_toList (s) : IsValidSearchFrom pat s.startPos (ToForwardSearcher.toSearcher pat s).toList theorem LawfulToForwardSearcherModel.defaultImplementation {pat : ρ} [ForwardPattern pat] [StrictForwardPattern pat] - [ForwardPatternModel pat] [LawfulForwardPatternModel pat] : + [PatternModel pat] [LawfulForwardPatternModel pat] : letI : ToForwardSearcher pat (ToForwardSearcher.DefaultForwardSearcher pat) := .defaultImplementation LawfulToForwardSearcherModel pat := by let inst : ToForwardSearcher pat (ToForwardSearcher.DefaultForwardSearcher pat) := .defaultImplementation diff --git a/src/Init/Data/String/Lemmas/Pattern/Char.lean b/src/Init/Data/String/Lemmas/Pattern/Char.lean index f7ba07640098..ed7159769226 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Char.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Char.lean @@ -25,17 +25,17 @@ public section namespace String.Slice.Pattern.Model.Char -instance {c : Char} : ForwardPatternModel c where +instance {c : Char} : PatternModel c where Matches s := s = String.singleton c not_matches_empty := by simp instance {c : Char} : NoPrefixForwardPatternModel c := - .of_length_eq (by simp +contextual [ForwardPatternModel.Matches]) + .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : IsMatch c pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ s.startPos.get h = c := by - simp only [Model.isMatch_iff, ForwardPatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] + simp only [Model.isMatch_iff, PatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] refine ⟨?_, ?_⟩ · simp only [splits_singleton_iff] exact fun ⟨t₂, h, h₁, h₂, h₃⟩ => ⟨h, h₁, h₂⟩ diff --git a/src/Init/Data/String/Lemmas/Pattern/Find/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/Find/Basic.lean index 0df4b1df2eb0..a3c81b1f94d6 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Find/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Find/Basic.lean @@ -23,7 +23,7 @@ open Std String.Slice Pattern Pattern.Model namespace String.Slice -theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type} +theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} [∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id] [∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id] [ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} {pos : s.Pos} : @@ -40,7 +40,7 @@ theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [ForwardPatternMo | matched h₁ _ _ => have := h₁.matchesAt; grind | mismatched => grind -theorem Pattern.Model.find?_eq_none_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type} +theorem Pattern.Model.find?_eq_none_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} [∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id] [∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id] [ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} : @@ -65,14 +65,14 @@ theorem find?_eq_none_iff {ρ : Type} (pat : ρ) {σ : Slice → Type} [ToForwardSearcher pat σ] {s : Slice} : s.find? pat = none ↔ s.contains pat = false := by rw [← Option.isNone_iff_eq_none, ← Option.isSome_eq_false_iff, isSome_find?] -theorem Pattern.Model.contains_eq_false_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type} +theorem Pattern.Model.contains_eq_false_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} [∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id] [∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id] [ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} : s.contains pat = false ↔ ∀ (pos : s.Pos), ¬ MatchesAt pat pos := by rw [← find?_eq_none_iff, Slice.find?_eq_none_iff] -theorem Pattern.Model.contains_eq_true_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type} +theorem Pattern.Model.contains_eq_true_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} [∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id] [∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id] [ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} : @@ -85,7 +85,7 @@ theorem Pos.find?_eq_find?_sliceFrom {ρ : Type} {pat : ρ} {σ : Slice → Type p.find? pat = ((s.sliceFrom p).find? pat).map Pos.ofSliceFrom := (rfl) -theorem Pattern.Model.posFind?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {σ : Slice → Type} +theorem Pattern.Model.posFind?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] {σ : Slice → Type} [∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id] [∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id] [ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} {pos pos' : s.Pos} : @@ -100,7 +100,7 @@ theorem Pattern.Model.posFind?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatter refine ⟨Pos.sliceFrom _ _ h₁, ⟨by simpa using h₂, fun p hp₁ hp₂ => ?_⟩, by simp⟩ exact h₃ (Pos.ofSliceFrom p) Slice.Pos.le_ofSliceFrom (Pos.lt_sliceFrom_iff.1 hp₁) hp₂ -theorem Pattern.Model.posFind?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {σ : Slice → Type} +theorem Pattern.Model.posFind?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] {σ : Slice → Type} [∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id] [∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id] [ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} {pos : s.Pos} : diff --git a/src/Init/Data/String/Lemmas/Pattern/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/Pred.lean index 163c0b3da1cf..0b51fe6176e7 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Pred.lean @@ -24,18 +24,18 @@ public section namespace String.Slice.Pattern.Model.CharPred -instance {p : Char → Bool} : ForwardPatternModel p where +instance {p : Char → Bool} : PatternModel p where Matches s := ∃ c, s = singleton c ∧ p c not_matches_empty := by simp instance {p : Char → Bool} : NoPrefixForwardPatternModel p := - .of_length_eq (by simp +contextual [ForwardPatternModel.Matches]) + .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : IsMatch p pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by - simp only [Model.isMatch_iff, ForwardPatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] + simp only [Model.isMatch_iff, PatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] refine ⟨?_, ?_⟩ · simp only [splits_singleton_iff] refine fun ⟨c, ⟨t₂, h, h₁, h₂, h₃⟩, hc⟩ => ⟨h, h₁, h₂ ▸ hc⟩ @@ -78,9 +78,9 @@ theorem matchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Bool} : namespace Decidable -instance {p : Char → Prop} [DecidablePred p] : ForwardPatternModel p where - Matches := ForwardPatternModel.Matches (decide <| p ·) - not_matches_empty := ForwardPatternModel.not_matches_empty (pat := (decide <| p ·)) +instance {p : Char → Prop} [DecidablePred p] : PatternModel p where + Matches := PatternModel.Matches (decide <| p ·) + not_matches_empty := PatternModel.not_matches_empty (pat := (decide <| p ·)) instance {p : Char → Prop} [DecidablePred p] : NoPrefixForwardPatternModel p where eq_empty := NoPrefixForwardPatternModel.eq_empty (pat := (decide <| p ·)) diff --git a/src/Init/Data/String/Lemmas/Pattern/Split/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/Split/Basic.lean index b6a585449bb7..51c4a94785a1 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Split/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Split/Basic.lean @@ -28,7 +28,7 @@ set_option doc.verso true # Verification of {name}`String.Slice.splitToSubslice` This PR verifies the {name}`String.Slice.splitToSubslice` function by relating it to a model -implementation based on the {name}`String.Slice.Pattern.Model.ForwardPatternModel` class. +implementation based on the {name}`String.Slice.Pattern.Model.PatternModel` class. This gives a low-level correctness proof from which higher-level API lemmas can be derived. -/ @@ -36,7 +36,7 @@ This gives a low-level correctness proof from which higher-level API lemmas can namespace String.Slice.Pattern.Model @[cbv_opaque] -public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {s : Slice} +public protected noncomputable def split {ρ : Type} (pat : ρ) [PatternModel pat] {s : Slice} (firstRejected curr : s.Pos) (hle : firstRejected ≤ curr) : List s.Subslice := if h : curr = s.endPos then [s.subslice _ _ hle] @@ -49,12 +49,12 @@ public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternM termination_by curr @[simp] -public theorem split_endPos {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {s : Slice} +public theorem split_endPos {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice} {firstRejected : s.Pos} : Model.split (s := s) pat firstRejected s.endPos (by simp) = [s.subslice firstRejected s.endPos (by simp)] := by simp [Model.split] -public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat] +public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice} {firstRejected start stop : s.Pos} {hle} (h : IsLongestMatchAt pat start stop) : Model.split pat firstRejected start hle = s.subslice _ _ hle :: Model.split pat stop stop (by exact Std.le_refl _) := by @@ -63,7 +63,7 @@ public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [ForwardPatte · congr <;> exact (matchAt?_eq_some_iff.1 ‹_›).eq h · simp [matchAt?_eq_some_iff.2 ‹_›] at * -public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat] +public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice} {firstRejected start} (stop : s.Pos) (h₀ : start ≤ stop) {hle} (h : ∀ p, start ≤ p → p < stop → ¬ MatchesAt pat p) : Model.split pat firstRejected start hle = @@ -80,7 +80,7 @@ public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternM · obtain rfl : start = stop := Std.le_antisymm h₀ (Std.not_lt.1 h') simp -public theorem split_eq_next_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat] +public theorem split_eq_next_of_not_matchesAt {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice} {firstRejected start} {hle} (hs : start ≠ s.endPos) (h : ¬ MatchesAt pat start) : Model.split pat firstRejected start hle = Model.split pat firstRejected (start.next hs) (by exact Std.le_trans hle (by simp)) := by @@ -103,7 +103,7 @@ def splitFromSteps {s : Slice} (currPos : s.Pos) (l : List (SearchStep s)) : Lis | .matched p q :: l => s.subslice! currPos p :: splitFromSteps q l theorem IsValidSearchFrom.splitFromSteps_eq_extend_split {ρ : Type} (pat : ρ) - [ForwardPatternModel pat] (l : List (SearchStep s)) (pos pos' : s.Pos) (h₀ : pos ≤ pos') + [PatternModel pat] (l : List (SearchStep s)) (pos pos' : s.Pos) (h₀ : pos ≤ pos') (h' : ∀ p, pos ≤ p → p < pos' → ¬ MatchesAt pat p) (h : IsValidSearchFrom pat pos' l) : splitFromSteps pos l = Model.split pat pos pos' h₀ := by @@ -155,7 +155,7 @@ end Model open Model @[cbv_eval] -public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [ForwardPatternModel pat] +public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} [ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)] [∀ s, Std.Iterators.Finite (σ s) Id] [LawfulToForwardSearcherModel pat] (s : Slice) : (s.splitToSubslice pat).toList = Model.split pat s.startPos s.startPos (by exact Std.le_refl _) := by @@ -168,7 +168,7 @@ end Pattern open Pattern public theorem toList_splitToSubslice_of_isEmpty {ρ : Type} (pat : ρ) - [Model.ForwardPatternModel pat] {σ : Slice → Type} + [Model.PatternModel pat] {σ : Slice → Type} [ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)] [∀ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] {s : Slice} (h : s.isEmpty = true) : @@ -182,7 +182,7 @@ public theorem toList_split_eq_splitToSubslice {ρ : Type} (pat : ρ) {σ : Slic simp [split, Std.Iter.toList_map] public theorem toList_split_of_isEmpty {ρ : Type} (pat : ρ) - [Model.ForwardPatternModel pat] {σ : Slice → Type} + [Model.PatternModel pat] {σ : Slice → Type} [ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)] [∀ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] {s : Slice} (h : s.isEmpty = true) : @@ -200,7 +200,7 @@ public theorem split_eq_split_toSlice {ρ : Type} {pat : ρ} {σ : Slice → Typ @[simp] public theorem toList_split_empty {ρ : Type} (pat : ρ) - [Model.ForwardPatternModel pat] {σ : Slice → Type} + [Model.PatternModel pat] {σ : Slice → Type} [ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)] [∀ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] : ("".split pat).toList.map Slice.copy = [""] := by diff --git a/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean index b72333d61b8b..7237ddb993c7 100644 --- a/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean @@ -19,7 +19,7 @@ namespace String.Slice.Pattern.Model namespace ForwardSliceSearcher -instance {pat : Slice} : ForwardPatternModel pat where +instance {pat : Slice} : PatternModel pat where /- See the docstring of `ForwardPatternModel` for an explanation about why we disallow matching the empty string. @@ -33,11 +33,11 @@ instance {pat : Slice} : ForwardPatternModel pat where not_matches_empty := by simp instance {pat : Slice} : NoPrefixForwardPatternModel pat := - .of_length_eq (by simp +contextual [ForwardPatternModel.Matches]) + .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : IsMatch pat pos ↔ (s.sliceTo pos).copy = pat.copy := by - simp only [Model.isMatch_iff, ForwardPatternModel.Matches, ne_eq, copy_eq_empty_iff, + simp only [Model.isMatch_iff, PatternModel.Matches, ne_eq, copy_eq_empty_iff, Bool.not_eq_true, and_iff_right_iff_imp] intro h' rw [← isEmpty_copy (s := s.sliceTo pos), h', isEmpty_copy, h] @@ -146,16 +146,16 @@ end ForwardSliceSearcher namespace ForwardStringSearcher -instance {pat : String} : ForwardPatternModel pat where +instance {pat : String} : PatternModel pat where Matches s := s ≠ "" ∧ s = pat not_matches_empty := by simp instance {pat : String} : NoPrefixForwardPatternModel pat := - .of_length_eq (by simp +contextual [ForwardPatternModel.Matches]) + .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff_slice {pat : String} {s : Slice} {pos : s.Pos} : IsMatch (ρ := String) pat pos ↔ IsMatch (ρ := Slice) pat.toSlice pos := by - simp only [Model.isMatch_iff, ForwardPatternModel.Matches, copy_toSlice] + simp only [Model.isMatch_iff, PatternModel.Matches, copy_toSlice] theorem isLongestMatch_iff_isLongestMatch_toSlice {pat : String} {s : Slice} {pos : s.Pos} : IsLongestMatch (ρ := String) pat pos ↔ IsLongestMatch (ρ := Slice) pat.toSlice pos where diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean index d9202aae70b1..4bef45b6f533 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean @@ -29,12 +29,12 @@ theorem startsWith_eq_forwardPatternStartsWith {ρ : Type} {pat : ρ} [ForwardPa theorem dropPrefix?_eq_map_skipPrefix? {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : Slice} : s.dropPrefix? pat = (s.skipPrefix? pat).map s.sliceFrom := (rfl) -theorem Pattern.Model.skipPrefix?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat] +theorem Pattern.Model.skipPrefix?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat] [LawfulForwardPatternModel pat] {s : Slice} {pos : s.Pos} : s.skipPrefix? pat = some pos ↔ IsLongestMatch pat pos := by rw [skipPrefix?_eq_forwardPatternSkipPrefix?, LawfulForwardPatternModel.skipPrefix?_eq_some_iff] -theorem Pattern.Model.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat] +theorem Pattern.Model.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat] [LawfulForwardPatternModel pat] {s : Slice} : s.skipPrefix? pat = none ↔ ¬ MatchesAt pat s.startPos := by rw [skipPrefix?_eq_forwardPatternSkipPrefix?, LawfulForwardPatternModel.skipPrefix?_eq_none_iff] @@ -44,13 +44,13 @@ theorem isSome_skipPrefix? {ρ : Type} {pat : ρ} [ForwardPattern pat] [LawfulFo (s.skipPrefix? pat).isSome = s.startsWith pat := by rw [startsWith_eq_forwardPatternStartsWith, skipPrefix?, LawfulForwardPattern.startsWith_eq] -theorem Pattern.Model.startsWith_eq_false_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat] +theorem Pattern.Model.startsWith_eq_false_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat] [LawfulForwardPatternModel pat] {s : Slice} : s.startsWith pat = false ↔ ¬ MatchesAt pat s.startPos := by rw [← Pattern.Model.skipPrefix?_eq_none_iff, ← Option.isNone_iff_eq_none, ← isSome_skipPrefix?, Option.isSome_eq_false_iff] -theorem Pattern.Model.startsWith_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat] +theorem Pattern.Model.startsWith_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat] [LawfulForwardPatternModel pat] {s : Slice} : s.startsWith pat = true ↔ MatchesAt pat s.startPos := by rw [← Bool.not_eq_false, startsWith_eq_false_iff, Classical.not_not] @@ -65,9 +65,9 @@ theorem dropPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [Law {s : Slice} : s.dropPrefix? pat = none ↔ s.startsWith pat = false := by simp [dropPrefix?_eq_map_skipPrefix?] -theorem Pattern.Model.eq_append_of_dropPrefix?_eq_some {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat] +theorem Pattern.Model.eq_append_of_dropPrefix?_eq_some {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat] [LawfulForwardPatternModel pat] {s res : Slice} (h : s.dropPrefix? pat = some res) : - ∃ t, ForwardPatternModel.Matches pat t ∧ s.copy = t ++ res.copy := by + ∃ t, PatternModel.Matches pat t ∧ s.copy = t ++ res.copy := by simp only [dropPrefix?_eq_map_skipPrefix?, Option.map_eq_some_iff, skipPrefix?_eq_some_iff] at h obtain ⟨pos, h₁, h₂⟩ := h exact ⟨(s.sliceTo pos).copy, h₁.isMatch.matches_copy, by simp [← h₂, ← copy_eq_copy_sliceTo]⟩ diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean index 762138f3c6d7..8c8cbac7b1e5 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean @@ -52,7 +52,7 @@ theorem startsWith_char_eq_false_iff_forall_append {c : Char} {s : Slice} : theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s res : Slice} (h : s.dropPrefix? c = some res) : s.copy = singleton c ++ res.copy := by - simpa [ForwardPatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h + simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h end Slice diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean index 10691d4fd3c8..77533eb8e1bc 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean @@ -45,7 +45,7 @@ theorem startsWith_bool_eq_head? {p : Char → Bool} {s : Slice} : theorem eq_append_of_dropPrefix?_bool_eq_some {p : Char → Bool} {s res : Slice} (h : s.dropPrefix? p = some res) : ∃ c, s.copy = singleton c ++ res.copy ∧ p c = true := by - obtain ⟨_, ⟨c, ⟨rfl, h₁⟩⟩, h₂⟩ := by simpa [ForwardPatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h + obtain ⟨_, ⟨c, ⟨rfl, h₁⟩⟩, h₂⟩ := by simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h exact ⟨_, h₂, h₁⟩ theorem skipPrefix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} : diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean index eac68168d1fa..7115c813df33 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean @@ -67,7 +67,7 @@ theorem eq_append_of_dropPrefix?_slice_eq_some {pat s res : Slice} (h : s.dropPr | false => have := ForwardSliceSearcher.lawfulForwardPatternModel hpat have := Pattern.Model.eq_append_of_dropPrefix?_eq_some h - simp only [ForwardPatternModel.Matches] at this + simp only [PatternModel.Matches] at this obtain ⟨_, ⟨-, rfl⟩, h⟩ := this exact h | true => simp [Option.some.inj (h ▸ dropPrefix?_slice_of_isEmpty hpat), (show pat.copy = "" by simpa)] From b40dac180ed996d57a1849d49d3952da30192ba8 Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:20:33 +0000 Subject: [PATCH 3/9] Basic pattern theory backwards --- .../Data/String/Lemmas/Pattern/Basic.lean | 340 +++++++++++++++++- src/Init/Data/String/Lemmas/Pattern/Char.lean | 2 +- src/Init/Data/String/Lemmas/Pattern/Pred.lean | 6 +- .../String/Lemmas/Pattern/String/Basic.lean | 4 +- src/Init/Data/String/Pattern/Basic.lean | 2 +- 5 files changed, 339 insertions(+), 15 deletions(-) diff --git a/src/Init/Data/String/Lemmas/Pattern/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/Basic.lean index de3342165c4f..f4f519f411c2 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Basic.lean @@ -19,6 +19,7 @@ import Init.Data.Order.Lemmas import Init.ByCases import Init.Data.Option.Lemmas import Init.Data.Iterators.Lemmas.Consumers.Collect +import Init.Data.String.Lemmas.FindPos set_option doc.verso true @@ -89,13 +90,36 @@ theorem isMatch_iff_exists_splits {pat : ρ} [PatternModel pat] {s : Slice} {pos refine ⟨fun h => ⟨_, _, pos.splits, h⟩, fun ⟨t₁, t₂, h₁, h₂⟩ => ?_⟩ rwa [h₁.eq_left pos.splits] at h₂ +/-- +Predicate stating that the region between the position {name}`startPos` and the end of the slice +{name}`s` matches the pattern {name}`pat`. Note that there might be a longer match. +-/ +structure IsRevMatch (pat : ρ) [PatternModel pat] {s : Slice} (startPos : s.Pos) : Prop where + matches_copy : PatternModel.Matches pat (s.sliceFrom startPos).copy + +theorem IsRevMatch.ne_endPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} + (h : IsRevMatch pat pos) : pos ≠ s.endPos := by + intro hc + apply PatternModel.not_matches_empty (pat := pat) + simpa [hc] using h.matches_copy + +theorem isRevMatch_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : + IsRevMatch pat pos ↔ PatternModel.Matches pat (s.sliceFrom pos).copy := + ⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩ + +theorem isRevMatch_iff_exists_splits {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : + IsRevMatch pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ t₂ ∧ PatternModel.Matches pat t₂ := by + rw [isRevMatch_iff] + refine ⟨fun h => ⟨_, _, pos.splits, h⟩, fun ⟨t₁, t₂, h₁, h₂⟩ => ?_⟩ + rwa [h₁.eq_right pos.splits] at h₂ + /-- Predicate stating that the region between the start of the slice {name}`s` and the position -{name}`endPos` matches that pattern {name}`pat`, and that there is no longer match starting at the +{name}`pos` matches the pattern {name}`pat`, and that there is no longer match starting at the beginning of the slice. This is what a correct matcher should match. In some cases, being a match and being a longest match will coincide, see -{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixForwardPatternModel`. +{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixPatternModel`. -/ structure IsLongestMatch (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) where isMatch : IsMatch pat pos @@ -125,26 +149,83 @@ theorem IsLongestMatch.le_of_isMatch {pat : ρ} [PatternModel pat] {s : Slice} { (h : IsLongestMatch pat pos) (h' : IsMatch pat pos') : pos' ≤ pos := Std.not_lt.1 (fun hlt => h.not_isMatch _ hlt h') +/-- +Predicate stating that the region between the start of the slice {name}`s` and the position +{name}`pos` matches the patten {name}`pat`, and that there is no longer match starting at the +beginning of the slice. This is what a correct matcher should match. + +In some cases, being a match and being a longest match will coincide, see +{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixPatternModel`. +-/ +structure IsLongestRevMatch (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) where + isRevMatch : IsRevMatch pat pos + not_isRevMatch : ∀ pos', pos' < pos → ¬ IsRevMatch pat pos' + +theorem IsLongestRevMatch.ne_endPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} + (h : IsLongestRevMatch pat pos) : pos ≠ s.endPos := + h.isRevMatch.ne_endPos + +theorem IsLongestRevMatch.eq {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos} + (h : IsLongestRevMatch pat pos) (h' : IsLongestRevMatch pat pos') : pos = pos' := by + apply Std.le_antisymm + · exact Std.not_lt.1 (fun hlt => h.not_isRevMatch _ hlt h'.isRevMatch) + · exact Std.not_lt.1 (fun hlt => h'.not_isRevMatch _ hlt h.isRevMatch) + +open Classical in +theorem IsRevMatch.exists_isLongestRevMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} : + IsRevMatch pat pos → ∃ (pos' : s.Pos), IsLongestRevMatch pat pos' := by + induction pos using WellFounded.induction Pos.wellFounded_lt with | h pos ih + intro h₁ + by_cases h₂ : ∃ pos', pos' < pos ∧ IsRevMatch pat pos' + · obtain ⟨pos', hp₁, hp₂⟩ := h₂ + exact ih _ hp₁ hp₂ + · exact ⟨pos, ⟨h₁, fun p' hp₁ hp₂ => h₂ ⟨_, hp₁, hp₂⟩⟩⟩ + +theorem IsLongestRevMatch.le_of_isRevMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos} + (h : IsLongestRevMatch pat pos) (h' : IsRevMatch pat pos') : pos ≤ pos' := + Std.not_lt.1 (fun hlt => h.not_isRevMatch _ hlt h') + /-- Predicate stating that a match for a given pattern is never a proper prefix of another match. This implies that the notion of match and longest match coincide. -/ -class NoPrefixForwardPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where +class NoPrefixPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where eq_empty (s t) : PatternModel.Matches pat s → PatternModel.Matches pat (s ++ t) → t = "" -theorem NoPrefixForwardPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat] +theorem NoPrefixPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat] (h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) : - NoPrefixForwardPatternModel pat where + NoPrefixPatternModel pat where eq_empty s t hs ht := by simpa using h s _ hs ht -theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoPrefixForwardPatternModel pat] +theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoPrefixPatternModel pat] {s : Slice} {pos : s.Pos} : IsLongestMatch pat pos ↔ IsMatch pat pos := by refine ⟨fun h => h.isMatch, fun h => ⟨h, fun pos' hpos' hm => ?_⟩⟩ obtain ⟨t₁, t₂, ht₁, ht₂⟩ := isMatch_iff_exists_splits.1 h obtain ⟨t₁', t₂', ht₁', ht₂'⟩ := isMatch_iff_exists_splits.1 hm obtain ⟨t₅, ht₅, ht₅', ht₅''⟩ := (ht₁.lt_iff_exists_eq_append ht₁').1 hpos' - exact ht₅ (NoPrefixForwardPatternModel.eq_empty _ _ ht₂ (ht₅' ▸ ht₂')) + exact ht₅ (NoPrefixPatternModel.eq_empty _ _ ht₂ (ht₅' ▸ ht₂')) + +/-- +Predicate stating that a match for a given pattern is never a proper suffix of another match. + +This implies that the notion of reverse match and longest reverse match coincide. +-/ +class NoSuffixPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where + eq_empty (s t) : PatternModel.Matches pat t → PatternModel.Matches pat (s ++ t) → s = "" + +theorem NoSuffixPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat] + (h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) : + NoPrefixPatternModel pat where + eq_empty s t hs ht := by simpa using h s _ hs ht + +theorem isLongestRevMatch_iff_isRevMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoSuffixPatternModel pat] + {s : Slice} {pos : s.Pos} : IsLongestRevMatch pat pos ↔ IsRevMatch pat pos := by + refine ⟨fun h => h.isRevMatch, fun h => ⟨h, fun pos' hpos' hm => ?_⟩⟩ + obtain ⟨t₁, t₂, ht₁, ht₂⟩ := isRevMatch_iff_exists_splits.1 h + obtain ⟨t₁', t₂', ht₁', ht₂'⟩ := isRevMatch_iff_exists_splits.1 hm + obtain ⟨t₅, ht₅, ht₅', ht₅''⟩ := (ht₁'.lt_iff_exists_eq_append ht₁).1 hpos' + exact ht₅ (NoSuffixPatternModel.eq_empty _ _ ht₂ (ht₅'' ▸ ht₂')) /-- Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match @@ -201,6 +282,59 @@ theorem isLongestMatchAt_startPos_iff {pat : ρ} [PatternModel pat] {s : Slice} ⟨fun h => isLongestMatch_of_eq (by simp) (by simp) h, fun h => isLongestMatch_of_eq (by simp) (by simp) h⟩ +/-- +Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match +of {name}`pat` in {name}`s` and it is longest among matches ending at {name}`endPos`. +-/ +structure IsLongestRevMatchAt (pat : ρ) [PatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where + le : startPos ≤ endPos + isLongestRevMatch_sliceTo : IsLongestRevMatch pat (Slice.Pos.sliceTo _ _ le) + +theorem isLongestRevMatchAt_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos₁ pos₂ : s.Pos} : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ + ∃ (h : pos₁ ≤ pos₂), IsLongestRevMatch pat (Slice.Pos.sliceTo _ _ h) := + ⟨fun ⟨h, h'⟩ => ⟨h, h'⟩, fun ⟨h, h'⟩ => ⟨h, h'⟩⟩ + +theorem IsLongestRevMatchAt.lt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos} + (h : IsLongestRevMatchAt pat startPos endPos) : startPos < endPos := by + have := h.isLongestRevMatch_sliceTo.ne_endPos + rw [← Pos.lt_endPos_iff, ← Slice.Pos.ofSliceTo_lt_ofSliceTo_iff] at this + simpa + +theorem IsLongestRevMatchAt.eq {pat : ρ} [PatternModel pat] {s : Slice} {startPos startPos' endPos : s.Pos} + (h : IsLongestRevMatchAt pat startPos endPos) (h' : IsLongestRevMatchAt pat startPos' endPos) : + startPos = startPos' := by + simpa using h.isLongestRevMatch_sliceTo.eq h'.isLongestRevMatch_sliceTo + +private theorem isLongestRevMatch_of_eq {pat : ρ} [PatternModel pat] {s t : Slice} + {pos : s.Pos} {pos' : t.Pos} (h_eq : s = t) (h_pos : pos.offset = pos'.offset) + (hm : IsLongestRevMatch pat pos) : IsLongestRevMatch pat pos' := by + subst h_eq; exact (Slice.Pos.ext h_pos) ▸ hm + +theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_ofSliceTo {pat : ρ} [PatternModel pat] + {s : Slice} {base : s.Pos} {startPos endPos : (s.sliceTo base).Pos} : + IsLongestRevMatchAt pat startPos endPos ↔ IsLongestRevMatchAt pat (Pos.ofSliceTo startPos) (Pos.ofSliceTo endPos) := by + constructor + · intro h + refine ⟨Slice.Pos.ofSliceTo_le_ofSliceTo_iff.mpr h.le, ?_⟩ + exact isLongestRevMatch_of_eq Slice.sliceTo_sliceTo (by simp) h.isLongestRevMatch_sliceTo + · intro h + refine ⟨Slice.Pos.ofSliceTo_le_ofSliceTo_iff.mp h.le, ?_⟩ + exact isLongestRevMatch_of_eq Slice.sliceTo_sliceTo.symm (by simp) h.isLongestRevMatch_sliceTo + +theorem IsLongestRevMatch.isLongestRevMatchAt_ofSliceTo {pat : ρ} [PatternModel pat] {s : Slice} + {p₀ : s.Pos} {pos : (s.sliceTo p₀).Pos} (h : IsLongestRevMatch pat pos) : + IsLongestRevMatchAt pat (Slice.Pos.ofSliceTo pos) p₀ where + le := Slice.Pos.ofSliceTo_le + isLongestRevMatch_sliceTo := by simpa + +@[simp] +theorem isLongestRevMatchAt_endPos_iff {pat : ρ} [PatternModel pat] {s : Slice} {startPos : s.Pos} : + IsLongestRevMatchAt pat startPos s.endPos ↔ IsLongestRevMatch pat startPos := by + simpa [isLongestRevMatchAt_iff] using + ⟨fun h => isLongestRevMatch_of_eq (by simp) (by simp) h, + fun h => isLongestRevMatch_of_eq (by simp) (by simp) h⟩ + /-- Predicate stating that there is a (longest) match starting at the given position. -/ @@ -246,6 +380,51 @@ theorem IsLongestMatchAt.matchesAt {pat : ρ} [PatternModel pat] {s : Slice} {st (h : IsLongestMatchAt pat startPos endPos) : MatchesAt pat startPos where exists_isLongestMatchAt := ⟨_, h⟩ +/-- +Predicate stating that there is a (longest) match ending at the given position. +-/ +structure RevMatchesAt (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) : Prop where + exists_isLongestRevMatchAt : ∃ startPos, IsLongestRevMatchAt pat startPos pos + +theorem revMatchesAt_iff_exists_isLongestRevMatchAt {pat : ρ} [PatternModel pat] {s : Slice} + {pos : s.Pos} : RevMatchesAt pat pos ↔ ∃ startPos, IsLongestRevMatchAt pat startPos pos := + ⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩ + +theorem revMatchesAt_iff_exists_isLongestRevMatch {pat : ρ} [PatternModel pat] {s : Slice} + {pos : s.Pos} : + RevMatchesAt pat pos ↔ ∃ (startPos : s.Pos), ∃ h, IsLongestRevMatch pat (pos.sliceTo startPos h) := + ⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestRevMatch_sliceTo⟩, fun ⟨p, h₁, h₂⟩ => ⟨p, ⟨h₁, h₂⟩⟩⟩ + +theorem revMatchesAt_iff_exists_isRevMatch {pat : ρ} [PatternModel pat] {s : Slice} + {pos : s.Pos} : + RevMatchesAt pat pos ↔ ∃ (startPos : s.Pos), ∃ h, IsRevMatch pat (pos.sliceTo startPos h) := by + refine ⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestRevMatch_sliceTo.isRevMatch⟩, fun ⟨p, h₁, h₂⟩ => ?_⟩ + obtain ⟨q, hq⟩ := h₂.exists_isLongestRevMatch + exact ⟨Pos.ofSliceTo q, + ⟨Std.le_trans (by simpa [← Pos.ofSliceTo_le_ofSliceTo_iff] using hq.le_of_isRevMatch h₂) h₁, + by simpa using hq⟩⟩ + +@[simp] +theorem not_revMatchesAt_startPos {pat : ρ} [PatternModel pat] {s : Slice} : + ¬ RevMatchesAt pat s.startPos := by + simp only [revMatchesAt_iff_exists_isRevMatch, Pos.le_startPos, exists_prop_eq] + intro h + simpa [← Pos.ofSliceTo_inj] using h.ne_endPos + +theorem revMatchesAt_iff_revMatchesAt_ofSliceto {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos} + {pos : (s.sliceTo base).Pos} : RevMatchesAt pat pos ↔ RevMatchesAt pat (Pos.ofSliceTo pos) := by + simp only [revMatchesAt_iff_exists_isLongestRevMatchAt] + constructor + · rintro ⟨startPos, h⟩ + exact ⟨Pos.ofSliceTo startPos, isLongestRevMatchAt_iff_isLongestRevMatchAt_ofSliceTo.mp h⟩ + · rintro ⟨startPos, h⟩ + exact ⟨base.sliceTo startPos (Std.le_trans h.le Slice.Pos.ofSliceTo_le), + isLongestRevMatchAt_iff_isLongestRevMatchAt_ofSliceTo.mpr (by simpa using h)⟩ + +theorem IsLongestRevMatchAt.revMatchesAt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos} + (h : IsLongestRevMatchAt pat startPos endPos) : RevMatchesAt pat endPos where + exists_isLongestRevMatchAt := ⟨_, h⟩ + open Classical in /-- Noncomputable model function returning the end point of the longest match starting at the given @@ -271,6 +450,31 @@ theorem matchAt?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] | case1 h => simpa using ⟨h⟩ | case2 h => simpa using fun ⟨h'⟩ => h h' +open Classical in +/-- +Noncomputable model function returning the start point of the longest match ending at the given +position, or {lean}`none` if there is no match. +-/ +noncomputable def revMatchAt? {ρ : Type} (pat : ρ) [PatternModel pat] + {s : Slice} (endPos : s.Pos) : Option s.Pos := + if h : ∃ startPos, IsLongestRevMatchAt pat startPos endPos then some h.choose else none + +@[simp] +theorem revMatchAt?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] + {s : Slice} {startPos endPos : s.Pos} : + revMatchAt? pat endPos = some startPos ↔ IsLongestRevMatchAt pat startPos endPos := by + fun_cases revMatchAt? with + | case1 h => simpa using ⟨by rintro rfl; exact h.choose_spec, fun h' => h.choose_spec.eq h'⟩ + | case2 => simp_all + +@[simp] +theorem revMatchAt?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] + {s : Slice} {endPos : s.Pos} : + revMatchAt? pat endPos = none ↔ ¬ RevMatchesAt pat endPos := by + fun_cases revMatchAt? with + | case1 h => simpa using ⟨h⟩ + | case2 h => simpa using fun ⟨h'⟩ => h h' + /-- Predicate stating compatibility between {name}`PatternModel` and {name}`ForwardPattern`. @@ -282,10 +486,10 @@ class LawfulForwardPatternModel {ρ : Type} (pat : ρ) [ForwardPattern pat] [PatternModel pat] : Prop extends LawfulForwardPattern pat where skipPrefix?_eq_some_iff (pos) : ForwardPattern.skipPrefix? pat s = some pos ↔ IsLongestMatch pat pos -open Classical in theorem LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [PatternModel pat] [LawfulForwardPatternModel pat] {s : Slice} {p₀ : s.Pos} : ForwardPattern.skipPrefix? pat (s.sliceFrom p₀) = none ↔ ¬ MatchesAt pat p₀ := by + classical rw [← Decidable.not_iff_not] simp [Option.ne_none_iff_exists', LawfulForwardPatternModel.skipPrefix?_eq_some_iff] refine ⟨fun ⟨p, hp⟩ => ?_, fun ⟨p, hp⟩ => ?_⟩ @@ -298,6 +502,33 @@ theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} conv => lhs; rw [← sliceFrom_startPos (s := s)] simp [skipPrefix?_sliceFrom_eq_none_iff] +/-- +Predicate stating compatibility between {name}`PatternModel` and {name}`BackwardPattern`. + +This extends {name}`LawfulForwardPattern`, but it is much stronger because it forces the +{name}`ForwardPattern` to match the longest prefix of the given slice that matches the property +supplied by the {name}`PatternModel` instance. +-/ +class LawfulBackwardPatternModel {ρ : Type} (pat : ρ) [BackwardPattern pat] + [PatternModel pat] : Prop extends LawfulBackwardPattern pat where + skipSuffix?_eq_some_iff (pos) : BackwardPattern.skipSuffix? pat s = some pos ↔ IsLongestRevMatch pat pos + +theorem LawfulBackwardPatternModel.skipSuffix?_sliceTo_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [PatternModel pat] + [LawfulBackwardPatternModel pat] {s : Slice} {p₀ : s.Pos} : + BackwardPattern.skipSuffix? pat (s.sliceTo p₀) = none ↔ ¬ RevMatchesAt pat p₀ := by + classical + rw [← Decidable.not_iff_not] + simp [Option.ne_none_iff_exists', LawfulBackwardPatternModel.skipSuffix?_eq_some_iff] + refine ⟨fun ⟨p, hp⟩ => ?_, fun ⟨p, hp⟩ => ?_⟩ + · exact ⟨Slice.Pos.ofSliceTo p, hp.isLongestRevMatchAt_ofSliceTo⟩ + · exact ⟨p₀.sliceTo p hp.le, hp.isLongestRevMatch_sliceTo⟩ + +theorem LawfulBackwardPatternModel.skipSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [PatternModel pat] + [LawfulBackwardPatternModel pat] {s : Slice} : + BackwardPattern.skipSuffix? pat s = none ↔ ¬ RevMatchesAt pat s.endPos := by + conv => lhs; rw [← sliceTo_endPos (s := s)] + simp [skipSuffix?_sliceTo_eq_none_iff] + /-- Inductive predicate stating that a list of search steps represents a valid search from a given position in a slice. @@ -391,4 +622,97 @@ theorem LawfulToForwardSearcherModel.defaultImplementation {pat : ρ} [ForwardPa · split at heq <;> simp at heq · split at heq <;> simp at heq +/-- +Inductive predicate stating that a list of search steps represents a valid backwards search from a +given position in a slice. + +"Searching" here means always taking the longest match at the first position where the pattern +matches. + +Hence, this predicate determines the list of search steps up to grouping of rejections. +-/ +inductive IsValidRevSearchFrom (pat : ρ) [PatternModel pat] {s : Slice} : + s.Pos → List (SearchStep s) → Prop where + | startPos : IsValidRevSearchFrom pat s.startPos [] + | matched {startPos endPos : s.Pos} : + IsLongestRevMatchAt pat startPos endPos → IsValidRevSearchFrom pat startPos l → + IsValidRevSearchFrom pat endPos (.matched startPos endPos :: l) + | mismatched {startPos endPos : s.Pos} : startPos < endPos → + (∀ pos, startPos < pos → pos ≤ endPos → ¬ RevMatchesAt pat pos) → + IsValidRevSearchFrom pat startPos l → IsValidRevSearchFrom pat endPos (.rejected startPos endPos :: l) + +theorem IsValidRevSearchFrom.matched_of_eq {pat : ρ} [PatternModel pat] {s : Slice} + {startPos endPos endPos' : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidRevSearchFrom pat startPos l) + (h₂ : IsLongestRevMatchAt pat startPos endPos') + (h₃ : endPos = endPos') : IsValidRevSearchFrom pat endPos' (.matched startPos endPos :: l) := by + cases h₃ + exact IsValidRevSearchFrom.matched h₂ h₁ + +theorem IsValidRevSearchFrom.mismatched_of_eq {pat : ρ} [PatternModel pat] {s : Slice} + {startPos endPos endPos' : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidRevSearchFrom pat startPos l) + (h₀ : startPos < endPos') + (h₂ : ∀ pos, startPos < pos → pos ≤ endPos' → ¬ RevMatchesAt pat pos) (h₃ : endPos = endPos') : + IsValidRevSearchFrom pat endPos' (.rejected startPos endPos :: l) := by + cases h₃ + exact IsValidRevSearchFrom.mismatched h₀ h₂ h₁ + +theorem IsValidRevSearchFrom.startPos_of_eq {pat : ρ} [PatternModel pat] {s : Slice} + {p : s.Pos} {l : List (SearchStep s)} (hp : p = s.startPos) (hl : l = []) : + IsValidRevSearchFrom pat p l := by + cases hp + cases hl + exact IsValidRevSearchFrom.startPos + +/-- +Predicate stating compatibility between {name}`PatternModel` and {name}`ToBackwardSearcher`. + +We require the searcher to always match the longest match at the first position where the pattern +matches; see {name}`IsValidRevSearchFrom`. +-/ +class LawfulToBackwardSearcherModel {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type} + [ToBackwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)] + [∀ s, Std.Iterators.Finite (σ s) Id] : Prop where + isValidRevSearchFrom_toList (s) : IsValidRevSearchFrom pat s.endPos (ToBackwardSearcher.toSearcher pat s).toList + +theorem LawfulToBackSearcherModel.defaultImplementation {pat : ρ} [BackwardPattern pat] [StrictBackwardPattern pat] + [PatternModel pat] [LawfulBackwardPatternModel pat] : + letI : ToBackwardSearcher pat (ToBackwardSearcher.DefaultBackwardSearcher pat) := .defaultImplementation + LawfulToBackwardSearcherModel pat := by + let inst : ToBackwardSearcher pat (ToBackwardSearcher.DefaultBackwardSearcher pat) := .defaultImplementation + refine ⟨fun s => ?_⟩ + suffices ∀ (pos : s.Pos), + IsValidRevSearchFrom pat pos (Std.Iter.mk (α := ToBackwardSearcher.DefaultBackwardSearcher pat s) ⟨pos⟩).toList from + this s.endPos + intro pos + induction pos using WellFounded.induction Slice.Pos.wellFounded_lt with | h pos ih + rw [Std.Iter.toList_eq_match_step, Std.Iter.step_eq] + simp only [Std.Iter.toIterM, ne_eq] + by_cases h : pos = s.startPos + · simpa [h] using IsValidRevSearchFrom.startPos + · simp only [h, ↓reduceDIte] + split <;> rename_i heq + · split at heq <;> rename_i pos' heq' + · simp only [Id.run_pure, Std.Shrink.inflate_deflate, Std.IterM.Step.toPure_yield, + Std.PlausibleIterStep.yield, Std.IterStep.yield.injEq] at heq + rw [← heq.1, ← heq.2] + apply IsValidRevSearchFrom.matched + · rw [LawfulBackwardPattern.skipSuffixOfNonempty?_eq, + LawfulBackwardPatternModel.skipSuffix?_eq_some_iff] at heq' + exact heq'.isLongestRevMatchAt_ofSliceTo + · simp only [Std.IterM.toIter] + apply ih + refine Std.lt_of_lt_of_le (Slice.Pos.ofSliceTo_lt_ofSliceTo_iff.2 ?_) + (Slice.Pos.ofSliceTo_le (pos := Slice.endPos _)) + simpa using StrictBackwardPattern.ne_endPos _ _ heq' + · simp only [Id.run_pure, Std.Shrink.inflate_deflate, Std.IterM.Step.toPure_yield, + Std.PlausibleIterStep.yield, Std.IterStep.yield.injEq] at heq + rw [← heq.1, ← heq.2] + apply IsValidRevSearchFrom.mismatched (by simp) _ (ih _ (by simp)) + intro p' hp' hp'' + obtain rfl : pos = p' := Std.le_antisymm (by simpa using hp') hp'' + rwa [LawfulBackwardPattern.skipSuffixOfNonempty?_eq, + LawfulBackwardPatternModel.skipSuffix?_sliceTo_eq_none_iff] at heq' + · split at heq <;> simp at heq + · split at heq <;> simp at heq + end String.Slice.Pattern.Model diff --git a/src/Init/Data/String/Lemmas/Pattern/Char.lean b/src/Init/Data/String/Lemmas/Pattern/Char.lean index ed7159769226..1b95972af73e 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Char.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Char.lean @@ -29,7 +29,7 @@ instance {c : Char} : PatternModel c where Matches s := s = String.singleton c not_matches_empty := by simp -instance {c : Char} : NoPrefixForwardPatternModel c := +instance {c : Char} : NoPrefixPatternModel c := .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : diff --git a/src/Init/Data/String/Lemmas/Pattern/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/Pred.lean index 0b51fe6176e7..44ebf64f8d19 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Pred.lean @@ -29,7 +29,7 @@ instance {p : Char → Bool} : PatternModel p where not_matches_empty := by simp -instance {p : Char → Bool} : NoPrefixForwardPatternModel p := +instance {p : Char → Bool} : NoPrefixPatternModel p := .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : @@ -82,8 +82,8 @@ instance {p : Char → Prop} [DecidablePred p] : PatternModel p where Matches := PatternModel.Matches (decide <| p ·) not_matches_empty := PatternModel.not_matches_empty (pat := (decide <| p ·)) -instance {p : Char → Prop} [DecidablePred p] : NoPrefixForwardPatternModel p where - eq_empty := NoPrefixForwardPatternModel.eq_empty (pat := (decide <| p ·)) +instance {p : Char → Prop} [DecidablePred p] : NoPrefixPatternModel p where + eq_empty := NoPrefixPatternModel.eq_empty (pat := (decide <| p ·)) theorem isMatch_iff_isMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : IsMatch p pos ↔ IsMatch (decide <| p ·) pos := diff --git a/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean index 7237ddb993c7..36d1c5beb2d2 100644 --- a/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean @@ -32,7 +32,7 @@ instance {pat : Slice} : PatternModel pat where Matches s := s ≠ "" ∧ s = pat.copy not_matches_empty := by simp -instance {pat : Slice} : NoPrefixForwardPatternModel pat := +instance {pat : Slice} : NoPrefixPatternModel pat := .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : @@ -150,7 +150,7 @@ instance {pat : String} : PatternModel pat where Matches s := s ≠ "" ∧ s = pat not_matches_empty := by simp -instance {pat : String} : NoPrefixForwardPatternModel pat := +instance {pat : String} : NoPrefixPatternModel pat := .of_length_eq (by simp +contextual [PatternModel.Matches]) theorem isMatch_iff_slice {pat : String} {s : Slice} {pos : s.Pos} : diff --git a/src/Init/Data/String/Pattern/Basic.lean b/src/Init/Data/String/Pattern/Basic.lean index 021f585f0b23..171e16dbd7c1 100644 --- a/src/Init/Data/String/Pattern/Basic.lean +++ b/src/Init/Data/String/Pattern/Basic.lean @@ -117,7 +117,7 @@ class ForwardPattern {ρ : Type} (pat : ρ) where -/ startsWith : (s : Slice) → Bool := fun s => (skipPrefix? s).isSome -@[deprecated ForwardPattern.dropPrefix? (since := "2026-03-19")] +@[deprecated ForwardPattern.skipPrefix? (since := "2026-03-19")] def ForwardPattern.dropPrefix? {ρ : Type} (pat : ρ) [ForwardPattern pat] (s : Slice) : Option s.Pos := ForwardPattern.skipPrefix? pat s From 8d0b2db1f9b4af262011b8d4fd55cccad3ad3bf6 Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:56:55 +0000 Subject: [PATCH 4/9] Working --- src/Init/Data/String/Lemmas/FindPos.lean | 54 ++++++++ src/Init/Data/String/Lemmas/IsEmpty.lean | 2 +- src/Init/Data/String/Lemmas/Order.lean | 115 +++++++++++++++++- .../Data/String/Lemmas/Pattern/Basic.lean | 6 +- src/Init/Data/String/Lemmas/Pattern/Char.lean | 16 ++- src/Init/Data/String/Lemmas/Pattern/Pred.lean | 50 +++++++- src/Init/Data/String/Lemmas/Splits.lean | 54 +++++++- 7 files changed, 284 insertions(+), 13 deletions(-) diff --git a/src/Init/Data/String/Lemmas/FindPos.lean b/src/Init/Data/String/Lemmas/FindPos.lean index 98be6ea31b57..b5b129477c03 100644 --- a/src/Init/Data/String/Lemmas/FindPos.lean +++ b/src/Init/Data/String/Lemmas/FindPos.lean @@ -201,6 +201,10 @@ theorem Pos.prev_eq_iff {s : Slice} {p q : s.Pos} {h} : theorem Pos.prev_lt {s : Slice} {p : s.Pos} {h} : p.prev h < p := by simp +@[simp] +theorem Pos.prev_le {s : Slice} {p : s.Pos} {h} : p.prev h ≤ p := + Std.le_of_lt (by simp) + @[simp] theorem Pos.prev_ne_endPos {s : Slice} {p : s.Pos} {h} : p.prev h ≠ s.endPos := ne_endPos_of_lt prev_lt @@ -211,6 +215,29 @@ theorem Pos.prevn_le {s : Slice} {p : s.Pos} {n : Nat} : p.prevn n ≤ p := by | case2 p n h ih => exact Std.le_of_lt (by simpa using ih) | case3 => simp +theorem Pos.ofSliceTo_prev {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} : + Pos.ofSliceTo (p.prev h) = (Pos.ofSliceTo p).prev (by simpa [← Pos.ofSliceTo_inj] using h) := by + rw [eq_comm, Pos.prev_eq_iff] + simp only [Pos.ofSliceTo_lt_ofSliceTo_iff, Pos.le_ofSliceTo_iff] + simp [Pos.lt_ofSliceTo_iff] + +theorem Pos.prev_ofSliceTo {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} : + (Pos.ofSliceTo p).prev h = Pos.ofSliceTo (p.prev (by simpa [← Pos.ofSliceTo_inj])) := by + simp [ofSliceTo_prev] + +theorem Pos.ofSliceFrom_prev {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} : + Pos.ofSliceFrom (p.prev h) = (Pos.ofSliceFrom p).prev (by exact ofSliceFrom_ne_startPos h) := by + rw [eq_comm, Pos.prev_eq_iff] + simp only [Pos.ofSliceFrom_lt_ofSliceFrom_iff, Pos.le_ofSliceFrom_iff] + simp [Pos.lt_ofSliceFrom_iff] + +theorem Pos.ofSlice_prev {s : Slice} {p₀ p₁ : s.Pos} {h} + {p : (s.slice p₀ p₁ h).Pos} {h'} : + Pos.ofSlice (p.prev h') = (Pos.ofSlice p).prev (by exact ofSlice_ne_startPos h') := by + rw [eq_comm, Pos.prev_eq_iff] + simp only [ofSlice_lt_ofSlice_iff, le_ofSlice_iff] + simpa +contextual [← ofSlice_lt_ofSlice_iff] using fun q hq => Std.le_of_lt (Std.lt_of_lt_of_le hq ofSlice_le) + @[simp] theorem Pos.prev_next {s : Slice} {p : s.Pos} {h} : (p.next h).prev (by simp) = p := prev_eq_iff.2 (by simp) @@ -439,6 +466,10 @@ theorem Pos.prev_eq_iff {s : String} {p q : s.Pos} {h} : theorem Pos.prev_lt {s : String} {p : s.Pos} {h} : p.prev h < p := by simp +@[simp] +theorem Pos.prev_le {s : String} {p : s.Pos} {h} : p.prev h ≤ p := + Std.le_of_lt (by simp) + @[simp] theorem Pos.prev_ne_endPos {s : String} {p : s.Pos} {h} : p.prev h ≠ s.endPos := ne_endPos_of_lt prev_lt @@ -463,6 +494,29 @@ theorem Pos.prevn_le {s : String} {p : s.Pos} {n : Nat} : p.prevn n ≤ p := by simpa [Pos.le_iff, ← offset_toSlice] using Slice.Pos.prevn_le +theorem Pos.ofSliceTo_prev {s : String} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} : + Pos.ofSliceTo (p.prev h) = (Pos.ofSliceTo p).prev (by simpa [← Pos.ofSliceTo_inj] using h) := by + rw [eq_comm, Pos.prev_eq_iff] + simp only [Pos.ofSliceTo_lt_ofSliceTo_iff, Pos.le_ofSliceTo_iff] + simp [Pos.lt_ofSliceTo_iff] + +theorem Pos.prev_ofSliceTo {s : String} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} : + (Pos.ofSliceTo p).prev h = Pos.ofSliceTo (p.prev (by simpa [← Pos.ofSliceTo_inj])) := by + simp [ofSliceTo_prev] + +theorem Pos.ofSliceFrom_prev {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} : + Pos.ofSliceFrom (p.prev h) = (Pos.ofSliceFrom p).prev (by exact ofSliceFrom_ne_startPos h) := by + rw [eq_comm, Pos.prev_eq_iff] + simp only [Pos.ofSliceFrom_lt_ofSliceFrom_iff, Pos.le_ofSliceFrom_iff] + simp [Pos.lt_ofSliceFrom_iff] + +theorem Pos.ofSlice_prev {s : String} {p₀ p₁ : s.Pos} {h} + {p : (s.slice p₀ p₁ h).Pos} {h'} : + Pos.ofSlice (p.prev h') = (Pos.ofSlice p).prev (by exact ofSlice_ne_startPos h') := by + rw [eq_comm, Pos.prev_eq_iff] + simp only [ofSlice_lt_ofSlice_iff, le_ofSlice_iff] + simpa +contextual [← ofSlice_lt_ofSlice_iff] using fun q hq => Std.le_of_lt (Std.lt_of_lt_of_le hq ofSlice_le) + @[simp] theorem Pos.prev_next {s : String} {p : s.Pos} {h} : (p.next h).prev (by simp) = p := prev_eq_iff.2 (by simp) diff --git a/src/Init/Data/String/Lemmas/IsEmpty.lean b/src/Init/Data/String/Lemmas/IsEmpty.lean index 09dad094752c..1c92a169d0f7 100644 --- a/src/Init/Data/String/Lemmas/IsEmpty.lean +++ b/src/Init/Data/String/Lemmas/IsEmpty.lean @@ -204,7 +204,7 @@ theorem Slice.copy_sliceTo_startPos {s : Slice} : (s.sliceTo s.startPos).copy = simp @[simp] -theorem Slice.copy_sliceFrom_startPos {s : Slice} : (s.sliceFrom s.endPos).copy = "" := by +theorem Slice.copy_sliceFrom_endPos {s : Slice} : (s.sliceFrom s.endPos).copy = "" := by simp end CopyEqEmpty diff --git a/src/Init/Data/String/Lemmas/Order.lean b/src/Init/Data/String/Lemmas/Order.lean index f4a050b68edc..b1e28a252668 100644 --- a/src/Init/Data/String/Lemmas/Order.lean +++ b/src/Init/Data/String/Lemmas/Order.lean @@ -11,6 +11,7 @@ import Init.Data.String.OrderInstances import Init.Data.String.Lemmas.Basic import Init.Data.Order.Lemmas import Init.Omega +import Init.ByCases public section @@ -70,7 +71,7 @@ theorem Pos.le_startPos {s : String} (p : s.Pos) : p ≤ s.startPos ↔ p = s.st ⟨fun h => Std.le_antisymm h (startPos_le _), by simp +contextual⟩ @[simp] -theorem Pos.startPos_lt_iff {s : String} {p : s.Pos} : s.startPos < p ↔ p ≠ s.startPos := by +theorem Pos.startPos_lt_iff {s : String} (p : s.Pos) : s.startPos < p ↔ p ≠ s.startPos := by simp [← le_startPos, Std.not_le] @[simp] @@ -235,6 +236,10 @@ theorem Slice.Pos.ofSliceFrom_next {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom Pos.next_le_iff_lt, true_and] simp [Pos.ofSliceFrom_lt_iff] +theorem Slice.Pos.next_ofSliceFrom {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} : + (Pos.ofSliceFrom p).next h = Pos.ofSliceFrom (p.next (by simpa [← Pos.ofSliceFrom_inj])) := by + simp [ofSliceFrom_next] + theorem Pos.ofSliceFrom_next {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} : Pos.ofSliceFrom (p.next h) = (Pos.ofSliceFrom p).next (by simpa [← Pos.ofSliceFrom_inj] using h) := by rw [eq_comm, Pos.next_eq_iff] @@ -242,6 +247,10 @@ theorem Pos.ofSliceFrom_next {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀) Slice.Pos.next_le_iff_lt, true_and] simp [Pos.ofSliceFrom_lt_iff] +theorem Pos.next_ofSliceFrom {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} : + (Pos.ofSliceFrom p).next h = Pos.ofSliceFrom (p.next (by simpa [← Pos.ofSliceFrom_inj])) := by + simp [Pos.ofSliceFrom_next] + theorem Slice.Pos.le_ofSliceTo_iff {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {q : s.Pos} : q ≤ Pos.ofSliceTo p ↔ ∃ h, Slice.Pos.sliceTo p₀ q h ≤ p := by refine ⟨fun h => ⟨Slice.Pos.le_trans h Pos.ofSliceTo_le, ?_⟩, fun ⟨h, h'⟩ => ?_⟩ @@ -359,11 +368,21 @@ theorem Slice.Pos.ofSliceTo_ne_endPos {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₀)) simpa [← lt_endPos_iff, ← ofSliceTo_lt_ofSliceTo_iff] using h +theorem Slice.Pos.ofSliceFrom_ne_startPos {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} + (h : p ≠ (s.sliceFrom p₀).startPos) : Pos.ofSliceFrom p ≠ s.startPos := by + refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_) + simpa [← startPos_lt_iff, ← ofSliceFrom_lt_ofSliceFrom_iff] using h + theorem Pos.ofSliceTo_ne_endPos {s : String} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} (h : p ≠ (s.sliceTo p₀).endPos) : Pos.ofSliceTo p ≠ s.endPos := by refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₀)) simpa [← Slice.Pos.lt_endPos_iff, ← ofSliceTo_lt_ofSliceTo_iff] using h +theorem Pos.ofSliceFrom_ne_startPos {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} + (h : p ≠ (s.sliceFrom p₀).startPos) : Pos.ofSliceFrom p ≠ s.startPos := by + refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_) + simpa [← Slice.Pos.startPos_lt_iff, ← ofSliceFrom_lt_ofSliceFrom_iff] using h + theorem Slice.Pos.ofSliceTo_next {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} : Pos.ofSliceTo (p.next h) = (Pos.ofSliceTo p).next (ofSliceTo_ne_endPos h) := by rw [eq_comm, Pos.next_eq_iff] @@ -406,16 +425,110 @@ theorem Pos.slice_le_slice_iff {s : String} {p₀ p₁ : s.Pos} {q r : s.Pos} simp [Slice.Pos.le_iff, Pos.le_iff, Pos.Raw.le_iff] at h₁ h₁' ⊢ omega +theorem Slice.Pos.le_ofSlice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + q ≤ Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Slice.Pos.slice q p₀ p₁ h₀ h₁ ≤ p := by + refine ⟨fun h => ⟨Std.le_trans h ofSlice_le, fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩ + · simp only [← Slice.Pos.slice_ofSlice (pos := p), slice_le_slice_iff] + simpa + · by_cases h₀ : p₀ ≤ q + · simpa only [← Slice.Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_le_ofSlice_iff] using h h₀ + · exact Std.le_of_lt (Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice) + +theorem Slice.Pos.ofSlice_lt_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + Pos.ofSlice p < q ↔ ∀ h₁, ∃ h₀, p < Slice.Pos.slice q p₀ p₁ h₀ h₁ := by + simp [← Std.not_le, le_ofSlice_iff] + +theorem Slice.Pos.lt_ofSlice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + q < Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Slice.Pos.slice q p₀ p₁ h₀ h₁ < p := by + refine ⟨fun h => ⟨Std.le_of_lt (Std.lt_of_lt_of_le h ofSlice_le), fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩ + · simp only [← Slice.Pos.slice_ofSlice (pos := p), slice_lt_slice_iff] + simpa + · by_cases h₀ : p₀ ≤ q + · simpa only [← Slice.Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_lt_ofSlice_iff] using h h₀ + · exact Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice + +theorem Slice.Pos.ofSlice_le_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + Pos.ofSlice p ≤ q ↔ ∀ h₁, ∃ h₀, p ≤ Slice.Pos.slice q p₀ p₁ h₀ h₁ := by + simp [← Std.not_lt, lt_ofSlice_iff] + +theorem Pos.le_ofSlice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + q ≤ Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Pos.slice q p₀ p₁ h₀ h₁ ≤ p := by + refine ⟨fun h => ⟨Std.le_trans h ofSlice_le, fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩ + · simp only [← Pos.slice_ofSlice (pos := p), slice_le_slice_iff] + simpa + · by_cases h₀ : p₀ ≤ q + · simpa only [← Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_le_ofSlice_iff] using h h₀ + · exact Std.le_of_lt (Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice) + +theorem Pos.ofSlice_lt_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + Pos.ofSlice p < q ↔ ∀ h₁, ∃ h₀, p < Pos.slice q p₀ p₁ h₀ h₁ := by + simp [← Std.not_le, le_ofSlice_iff] + +theorem Pos.lt_ofSlice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + q < Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Pos.slice q p₀ p₁ h₀ h₁ < p := by + refine ⟨fun h => ⟨Std.le_of_lt (Std.lt_of_lt_of_le h ofSlice_le), fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩ + · simp only [← Pos.slice_ofSlice (pos := p), slice_lt_slice_iff] + simpa + · by_cases h₀ : p₀ ≤ q + · simpa only [← Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_lt_ofSlice_iff] using h h₀ + · exact Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice + +theorem Pos.ofSlice_le_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} : + Pos.ofSlice p ≤ q ↔ ∀ h₁, ∃ h₀, p ≤ Pos.slice q p₀ p₁ h₀ h₁ := by + simp [← Std.not_lt, lt_ofSlice_iff] + +theorem Slice.Pos.slice_le_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + Slice.Pos.slice q p₀ p₁ h₀ h₁ ≤ p ↔ q ≤ Pos.ofSlice p := by + simp [le_ofSlice_iff, h₀, h₁] + +theorem Slice.Pos.lt_slice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + p < Slice.Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p < q := by + simp [ofSlice_lt_iff, h₀, h₁] + +theorem Slice.Pos.slice_lt_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + Slice.Pos.slice q p₀ p₁ h₀ h₁ < p ↔ q < Pos.ofSlice p := by + simp [lt_ofSlice_iff, h₀, h₁] + +theorem Slice.Pos.le_slice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + p ≤ Slice.Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p ≤ q := by + simp [ofSlice_le_iff, h₀, h₁] + +theorem Pos.slice_le_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + Pos.slice q p₀ p₁ h₀ h₁ ≤ p ↔ q ≤ Pos.ofSlice p := by + simp [le_ofSlice_iff, h₀, h₁] + +theorem Pos.lt_slice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + p < Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p < q := by + simp [ofSlice_lt_iff, h₀, h₁] + +theorem Pos.slice_lt_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + Pos.slice q p₀ p₁ h₀ h₁ < p ↔ q < Pos.ofSlice p := by + simp [lt_ofSlice_iff, h₀, h₁] + +theorem Pos.le_slice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} : + p ≤ Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p ≤ q := by + simp [ofSlice_le_iff, h₀, h₁] + theorem Slice.Pos.ofSlice_ne_endPos {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} (h : p ≠ (s.slice p₀ p₁ h).endPos) : Pos.ofSlice p ≠ s.endPos := by refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₁)) simpa [← lt_endPos_iff, ← ofSlice_lt_ofSlice_iff] using h +theorem Slice.Pos.ofSlice_ne_startPos {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} + (h : p ≠ (s.slice p₀ p₁ h).startPos) : Pos.ofSlice p ≠ s.startPos := by + refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_) + simpa [← startPos_lt_iff, ← ofSlice_lt_ofSlice_iff] using h + theorem Pos.ofSlice_ne_endPos {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} (h : p ≠ (s.slice p₀ p₁ h).endPos) : Pos.ofSlice p ≠ s.endPos := by refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₁)) simpa [← Slice.Pos.lt_endPos_iff, ← ofSlice_lt_ofSlice_iff] using h +theorem Pos.ofSlice_ne_startPos {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} + (h : p ≠ (s.slice p₀ p₁ h).startPos) : Pos.ofSlice p ≠ s.startPos := by + refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_) + simpa [← Slice.Pos.startPos_lt_iff, ← ofSlice_lt_ofSlice_iff] using h + @[simp] theorem Slice.Pos.offset_le_rawEndPos {s : Slice} {p : s.Pos} : p.offset ≤ s.rawEndPos := diff --git a/src/Init/Data/String/Lemmas/Pattern/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/Basic.lean index f4f519f411c2..221d351bdec1 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Basic.lean @@ -216,8 +216,8 @@ class NoSuffixPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop wher theorem NoSuffixPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat] (h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) : - NoPrefixPatternModel pat where - eq_empty s t hs ht := by simpa using h s _ hs ht + NoSuffixPatternModel pat where + eq_empty s t hs ht := by simpa using h t _ hs ht theorem isLongestRevMatch_iff_isRevMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoSuffixPatternModel pat] {s : Slice} {pos : s.Pos} : IsLongestRevMatch pat pos ↔ IsRevMatch pat pos := by @@ -674,7 +674,7 @@ class LawfulToBackwardSearcherModel {ρ : Type} (pat : ρ) [PatternModel pat] { [∀ s, Std.Iterators.Finite (σ s) Id] : Prop where isValidRevSearchFrom_toList (s) : IsValidRevSearchFrom pat s.endPos (ToBackwardSearcher.toSearcher pat s).toList -theorem LawfulToBackSearcherModel.defaultImplementation {pat : ρ} [BackwardPattern pat] [StrictBackwardPattern pat] +theorem LawfulToBackwardSearcherModel.defaultImplementation {pat : ρ} [BackwardPattern pat] [StrictBackwardPattern pat] [PatternModel pat] [LawfulBackwardPatternModel pat] : letI : ToBackwardSearcher pat (ToBackwardSearcher.DefaultBackwardSearcher pat) := .defaultImplementation LawfulToBackwardSearcherModel pat := by diff --git a/src/Init/Data/String/Lemmas/Pattern/Char.lean b/src/Init/Data/String/Lemmas/Pattern/Char.lean index 1b95972af73e..cdcb5e9274c4 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Char.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Char.lean @@ -20,6 +20,7 @@ import Init.Data.String.Lemmas.Order import Init.Data.Order.Lemmas import Init.Data.String.OrderInstances import Init.Omega +import Init.Data.String.Lemmas.FindPos public section @@ -32,16 +33,29 @@ instance {c : Char} : PatternModel c where instance {c : Char} : NoPrefixPatternModel c := .of_length_eq (by simp +contextual [PatternModel.Matches]) +instance {c : Char} : NoSuffixPatternModel c := + .of_length_eq (by simp +contextual [PatternModel.Matches]) + theorem isMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : IsMatch c pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ s.startPos.get h = c := by - simp only [Model.isMatch_iff, PatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] + simp only [Model.isMatch_iff, PatternModel.Matches, copy_sliceTo_eq_iff_exists_splits] refine ⟨?_, ?_⟩ · simp only [splits_singleton_iff] exact fun ⟨t₂, h, h₁, h₂, h₃⟩ => ⟨h, h₁, h₂⟩ · rintro ⟨h, rfl, rfl⟩ exact ⟨_, Slice.splits_next_startPos⟩ +theorem isRevMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : + IsRevMatch c pos ↔ + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos ∧ (s.endPos.prev h).get (by simp) = c := by + simp only [Model.isRevMatch_iff, PatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] + refine ⟨?_, ?_⟩ + · simp only [splits_singleton_iff] + exact fun ⟨t₂, h, h₁, h₂, h₃⟩ => ⟨h, h₁, h₂⟩ + · rintro ⟨h, rfl, rfl⟩ + exact ⟨_, Slice.splits_prev_endPos⟩ + theorem isLongestMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : IsLongestMatch c pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ s.startPos.get h = c := by diff --git a/src/Init/Data/String/Lemmas/Pattern/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/Pred.lean index 44ebf64f8d19..2f78a828bb29 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Pred.lean @@ -19,6 +19,7 @@ import Init.Data.String.Lemmas.Order import Init.Data.Order.Lemmas import Init.Data.String.OrderInstances import Init.Omega +import Init.Data.String.Lemmas.FindPos public section @@ -32,50 +33,97 @@ instance {p : Char → Bool} : PatternModel p where instance {p : Char → Bool} : NoPrefixPatternModel p := .of_length_eq (by simp +contextual [PatternModel.Matches]) +instance {p : Char → Bool} : NoSuffixPatternModel p := + .of_length_eq (by simp +contextual [PatternModel.Matches]) + theorem isMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : IsMatch p pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by - simp only [Model.isMatch_iff, PatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] + simp only [Model.isMatch_iff, PatternModel.Matches, copy_sliceTo_eq_iff_exists_splits] refine ⟨?_, ?_⟩ · simp only [splits_singleton_iff] refine fun ⟨c, ⟨t₂, h, h₁, h₂, h₃⟩, hc⟩ => ⟨h, h₁, h₂ ▸ hc⟩ · rintro ⟨h, rfl, h'⟩ exact ⟨s.startPos.get h, ⟨_, Slice.splits_next_startPos⟩, h'⟩ +theorem isRevMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : + IsRevMatch p pos ↔ + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by + simp only [Model.isRevMatch_iff, PatternModel.Matches, copy_sliceFrom_eq_iff_exists_splits] + refine ⟨?_, ?_⟩ + · simp only [splits_singleton_right_iff] + refine fun ⟨c, ⟨t₂, h, h₁, h₂, h₃⟩, hc⟩ => ⟨h, h₁, h₂ ▸ hc⟩ + · rintro ⟨h, rfl, h'⟩ + exact ⟨(s.endPos.prev h).get (by simp), ⟨_, Slice.splits_prev_endPos⟩, h'⟩ + theorem isLongestMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : IsLongestMatch p pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by rw [isLongestMatch_iff_isMatch, isMatch_iff] +theorem isLongestRevMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : + IsLongestRevMatch p pos ↔ + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by + rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff] + theorem isLongestMatchAt_iff {p : Char → Bool} {s : Slice} {pos pos' : s.Pos} : IsLongestMatchAt p pos pos' ↔ ∃ h, pos' = pos.next h ∧ p (pos.get h) := by simp +contextual [Model.isLongestMatchAt_iff, isLongestMatch_iff, ← Pos.ofSliceFrom_inj, Pos.get_eq_get_ofSliceFrom, Pos.ofSliceFrom_next] +theorem isLongestRevMatchAt_iff {p : Char → Bool} {s : Slice} {pos pos' : s.Pos} : + IsLongestRevMatchAt p pos pos' ↔ ∃ h, pos = pos'.prev h ∧ p ((pos'.prev h).get (by simp)) := by + simp +contextual [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff, ← Pos.ofSliceTo_inj, + Pos.get_eq_get_ofSliceTo, Pos.ofSliceTo_prev] + theorem isLongestMatchAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} (hc : p (pos.get h)) : IsLongestMatchAt p pos (pos.next h) := isLongestMatchAt_iff.2 ⟨h, by simp [hc]⟩ +theorem isLongestRevMatchAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos} + (hc : p ((pos.prev h).get (by simp))) : IsLongestRevMatchAt p (pos.prev h) pos := + isLongestRevMatchAt_iff.2 ⟨h, by simp [hc]⟩ + instance {p : Char → Bool} : LawfulForwardPatternModel p where skipPrefix?_eq_some_iff {s} pos := by simp [isLongestMatch_iff, ForwardPattern.skipPrefix?, and_comm, eq_comm (b := pos)] +instance {p : Char → Bool} : LawfulBackwardPatternModel p where + skipSuffix?_eq_some_iff {s} pos := by + simp [isLongestRevMatch_iff, BackwardPattern.skipSuffix?, and_comm, eq_comm (b := pos)] + instance {p : Char → Bool} : LawfulToForwardSearcherModel p := .defaultImplementation +instance {p : Char → Bool} : LawfulToBackwardSearcherModel p := + .defaultImplementation + theorem matchesAt_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : MatchesAt p pos ↔ ∃ (h : pos ≠ s.endPos), p (pos.get h) := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm] +theorem revMatchesAt_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : + RevMatchesAt p pos ↔ ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm] + theorem not_matchesAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} (hc : p (pos.get h) = false) : ¬ MatchesAt p pos := by simp [matchesAt_iff, hc] +theorem not_revMatchesAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos} + (hc : p ((pos.prev h).get (by simp)) = false) : ¬ RevMatchesAt p pos := by + simp [revMatchesAt_iff, hc] + theorem matchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Bool} : matchAt? p pos = if h₀ : ∃ (h : pos ≠ s.endPos), p (pos.get h) then some (pos.next h₀.1) else none := by split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff] +theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Bool} : + revMatchAt? p pos = + if h₀ : ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) then some (pos.prev h₀.1) else none := by + split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff] + namespace Decidable instance {p : Char → Prop} [DecidablePred p] : PatternModel p where diff --git a/src/Init/Data/String/Lemmas/Splits.lean b/src/Init/Data/String/Lemmas/Splits.lean index 2edd7eba7201..fd62c3fa220f 100644 --- a/src/Init/Data/String/Lemmas/Splits.lean +++ b/src/Init/Data/String/Lemmas/Splits.lean @@ -367,7 +367,7 @@ theorem Slice.Pos.Splits.of_prev {s : Slice} {p : s.Pos} {hp} obtain ⟨rfl, rfl, rfl⟩ := by simpa using h.eq (splits_prev p hp) exact splits_prev_right p hp -theorem Slice.sliceTo_copy_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : String} : +theorem Slice.copy_sliceTo_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : String} : (s.sliceTo p).copy = t₁ ↔ ∃ t₂, p.Splits t₁ t₂ := by refine ⟨?_, ?_⟩ · rintro rfl @@ -375,13 +375,21 @@ theorem Slice.sliceTo_copy_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : · rintro ⟨t₂, h⟩ exact p.splits.eq_left h -theorem sliceTo_copy_eq_iff_exists_splits {s : String} {p : s.Pos} {t₁ : String} : - (s.sliceTo p).copy = t₁ ↔ ∃ t₂, p.Splits t₁ t₂ := by +theorem Slice.copy_sliceFrom_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₂ : String} : + (s.sliceFrom p).copy = t₂ ↔ ∃ t₁, p.Splits t₁ t₂ := by refine ⟨?_, ?_⟩ · rintro rfl exact ⟨_, p.splits⟩ · rintro ⟨t₂, h⟩ - exact p.splits.eq_left h + exact p.splits.eq_right h + +theorem copy_sliceTo_eq_iff_exists_splits {s : String} {p : s.Pos} {t₁ : String} : + (s.sliceTo p).copy = t₁ ↔ ∃ t₂, p.Splits t₁ t₂ := by + simp [← Pos.splits_toSlice_iff, ← Slice.copy_sliceTo_eq_iff_exists_splits] + +theorem copy_sliceFrom_eq_iff_exists_splits {s : String} {p : s.Pos} {t₂ : String} : + (s.sliceFrom p).copy = t₂ ↔ ∃ t₁, p.Splits t₁ t₂ := by + simp [← Pos.splits_toSlice_iff, ← Slice.copy_sliceFrom_eq_iff_exists_splits] theorem Pos.Splits.offset_eq_decreaseBy {s : String} {p : s.Pos} (h : p.Splits t₁ t₂) : p.offset = s.rawEndPos.decreaseBy t₂.utf8ByteSize := by @@ -427,8 +435,7 @@ theorem Slice.splits_singleton_iff {s : Slice} {p : s.Pos} {c : Char} {t : Strin simp [startPos_ne_endPos_iff, ← copy_ne_empty_iff, h.eq_append] have spl : (s.startPos.next this).Splits (singleton c) t := by rw [← empty_append (s := singleton c)] - apply Pos.Splits.next - simp [h.eq_append] + exact Pos.Splits.next (by simp [h.eq_append]) refine ⟨this, ⟨h.pos_eq spl, ?_, h.eq_append⟩⟩ rw [← empty_append (s := singleton c)] at spl exact spl.get_eq_of_singleton @@ -442,6 +449,27 @@ theorem splits_singleton_iff {s : String} {p : s.Pos} {c : Char} {t : String} : rw [← Pos.splits_toSlice_iff, Slice.splits_singleton_iff] simp [← Pos.ofToSlice_inj] +theorem Slice.splits_singleton_right_iff {s : Slice} {p : s.Pos} {c : Char} {t : String} : + p.Splits t (singleton c) ↔ + ∃ h, p = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c ∧ s.copy = t ++ singleton c := by + refine ⟨fun h => ?_, ?_⟩ + · have : s.endPos ≠ s.startPos := by + simp [ne_comm (a := s.endPos), startPos_ne_endPos_iff, ← copy_ne_empty_iff, h.eq_append] + have spl : (s.endPos.prev this).Splits t (singleton c) := by + rw [← append_empty (s := singleton c)] + exact Pos.Splits.prev (by simp [h.eq_append]) + refine ⟨this, ⟨h.pos_eq spl, ?_, h.eq_append⟩⟩ + exact (h.eq_append ▸ Pos.next_prev (h := this) ▸ s.splits_endPos).get_eq_of_singleton + · rintro ⟨h, ⟨rfl, rfl, h'⟩⟩ + rw [← String.append_empty (s := singleton _)] + exact Pos.Splits.prev (by simp [h']) + +theorem splits_singleton_right_iff {s : String} {p : s.Pos} {c : Char} {t : String} : + p.Splits t (singleton c) ↔ + ∃ h, p = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c ∧ s = t ++ singleton c := by + rw [← Pos.splits_toSlice_iff, Slice.splits_singleton_right_iff] + simp [← Pos.ofToSlice_inj, Pos.prev_toSlice] + theorem Slice.splits_next_startPos {s : Slice} {h : s.startPos ≠ s.endPos} : (s.startPos.next h).Splits (singleton (s.startPos.get h)) (s.sliceFrom (s.startPos.next h)).copy := by @@ -456,6 +484,20 @@ theorem splits_next_startPos {s : String} {h : s.startPos ≠ s.endPos} : rw [← Pos.splits_toSlice_iff] apply (Slice.splits_next_startPos).of_eq <;> simp [String.Pos.next_toSlice] +theorem Slice.splits_prev_endPos {s : Slice} {h : s.endPos ≠ s.startPos} : + (s.endPos.prev h).Splits + (s.sliceTo (s.endPos.prev h)).copy (singleton ((s.endPos.prev h).get (by simp))) := by + rw [← String.append_empty (s := singleton _)] + apply Slice.Pos.Splits.prev + have := Slice.Pos.splits_prev_right s.endPos h + rwa [copy_sliceFrom_endPos] at this + +theorem splits_prev_endPos {s : String} {h : s.endPos ≠ s.startPos} : + (s.endPos.prev h).Splits + (s.sliceTo (s.endPos.prev h)).copy (singleton ((s.endPos.prev h).get (by simp))) := by + rw [← Pos.splits_toSlice_iff] + apply (Slice.splits_prev_endPos).of_eq <;> simp [String.Pos.prev_toSlice, h] + theorem Slice.Pos.Splits.toByteArray_eq_left {s : Slice} {p : s.Pos} {t₁ t₂ : String} (h : p.Splits t₁ t₂) : t₁.toByteArray = s.copy.toByteArray.extract 0 p.offset.byteIdx := by rw [h.eq_left p.splits] From 2c2ea3691dc834f3d6fda00b8f11b67ccd5b4ce3 Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:04:51 +0000 Subject: [PATCH 5/9] WIP --- src/Init/Data/String/Lemmas/Pattern/Pred.lean | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Init/Data/String/Lemmas/Pattern/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/Pred.lean index 2f78a828bb29..00e315e272db 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Pred.lean @@ -133,29 +133,55 @@ instance {p : Char → Prop} [DecidablePred p] : PatternModel p where instance {p : Char → Prop} [DecidablePred p] : NoPrefixPatternModel p where eq_empty := NoPrefixPatternModel.eq_empty (pat := (decide <| p ·)) +instance {p : Char → Prop} [DecidablePred p] : NoSuffixPatternModel p where + eq_empty := NoSuffixPatternModel.eq_empty (pat := (decide <| p ·)) + theorem isMatch_iff_isMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : IsMatch p pos ↔ IsMatch (decide <| p ·) pos := ⟨fun ⟨h⟩ => ⟨h⟩, fun ⟨h⟩ => ⟨h⟩⟩ +theorem isRevMatch_iff_isRevMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : + IsRevMatch p pos ↔ IsRevMatch (decide <| p ·) pos := + ⟨fun ⟨h⟩ => ⟨h⟩, fun ⟨h⟩ => ⟨h⟩⟩ + theorem isMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : IsMatch p pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by simp [isMatch_iff_isMatch_decide, CharPred.isMatch_iff] +theorem isRevMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : + IsRevMatch p pos ↔ + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by + simp [isRevMatch_iff_isRevMatch_decide, CharPred.isRevMatch_iff] + theorem isLongestMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : IsLongestMatch p pos ↔ ∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by rw [isLongestMatch_iff_isMatch, isMatch_iff] +theorem isLongestRevMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : + IsLongestRevMatch p pos ↔ + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by + simp [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff] + theorem isLongestMatch_iff_isLongestMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : IsLongestMatch p pos ↔ IsLongestMatch (decide <| p ·) pos := by simp [isLongestMatch_iff_isMatch, isMatch_iff_isMatch_decide] +theorem isLongestRevMatch_iff_isLongestRevMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} + {pos : s.Pos} : IsLongestRevMatch p pos ↔ IsLongestRevMatch (decide <| p ·) pos := by + simp [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff_isRevMatch_decide] + theorem isLongestMatchAt_iff_isLongestMatchAt_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos pos' : s.Pos} : IsLongestMatchAt p pos pos' ↔ IsLongestMatchAt (decide <| p ·) pos pos' := by simp [Model.isLongestMatchAt_iff, isLongestMatch_iff_isLongestMatch_decide] +theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_decide {p : Char → Prop} [DecidablePred p] + {s : Slice} {pos pos' : s.Pos} : + IsLongestRevMatchAt p pos pos' ↔ IsLongestRevMatchAt (decide <| p ·) pos pos' := by + simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff_isLongestRevMatch_decide] + theorem isLongestMatchAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos pos' : s.Pos} : IsLongestMatchAt p pos pos' ↔ ∃ h, pos' = pos.next h ∧ p (pos.get h) := by From 2f653d2a0018c750744e726a8f88a145ce1b683b Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:24:19 +0000 Subject: [PATCH 6/9] Char/Pred --- src/Init/Data/String/Lemmas/Pattern/Char.lean | 92 ++++++++++++++++++- src/Init/Data/String/Lemmas/Pattern/Pred.lean | 61 ++++++++++++ src/Init/Data/String/Pattern/Char.lean | 4 +- src/Init/Data/String/Pattern/Pred.lean | 5 +- 4 files changed, 155 insertions(+), 7 deletions(-) diff --git a/src/Init/Data/String/Lemmas/Pattern/Char.lean b/src/Init/Data/String/Lemmas/Pattern/Char.lean index cdcb5e9274c4..31a2c13f8cce 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Char.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Char.lean @@ -48,10 +48,10 @@ theorem isMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : theorem isRevMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : IsRevMatch c pos ↔ - ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos ∧ (s.endPos.prev h).get (by simp) = c := by - simp only [Model.isRevMatch_iff, PatternModel.Matches, sliceTo_copy_eq_iff_exists_splits] + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by + simp only [Model.isRevMatch_iff, PatternModel.Matches, copy_sliceFrom_eq_iff_exists_splits] refine ⟨?_, ?_⟩ - · simp only [splits_singleton_iff] + · simp only [splits_singleton_right_iff] exact fun ⟨t₂, h, h₁, h₂, h₃⟩ => ⟨h, h₁, h₂⟩ · rintro ⟨h, rfl, rfl⟩ exact ⟨_, Slice.splits_prev_endPos⟩ @@ -66,21 +66,46 @@ theorem isLongestMatchAt_iff {c : Char} {s : Slice} {pos pos' : s.Pos} : simp +contextual [Model.isLongestMatchAt_iff, isLongestMatch_iff, ← Pos.ofSliceFrom_inj, Pos.get_eq_get_ofSliceFrom, Pos.ofSliceFrom_next] +theorem isLongestRevMatch_iff {c : Char} {s : Slice} {pos : s.Pos} : + IsLongestRevMatch c pos ↔ + ∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by + rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff] + +theorem isLongestRevMatchAt_iff {c : Char} {s : Slice} {pos pos' : s.Pos} : + IsLongestRevMatchAt c pos pos' ↔ ∃ h, pos = pos'.prev h ∧ (pos'.prev h).get (by simp) = c := by + simp +contextual [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff, ← Pos.ofSliceTo_inj, + Pos.get_eq_get_ofSliceTo, Pos.ofSliceTo_prev] + theorem isLongestMatchAt_of_get_eq {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} (hc : pos.get h = c) : IsLongestMatchAt c pos (pos.next h) := isLongestMatchAt_iff.2 ⟨h, by simp [hc]⟩ +theorem isLongestRevMatchAt_of_get_eq {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos} + (hc : (pos.prev h).get (by simp) = c) : IsLongestRevMatchAt c (pos.prev h) pos := + isLongestRevMatchAt_iff.2 ⟨h, by simp [hc]⟩ + instance {c : Char} : LawfulForwardPatternModel c where skipPrefix?_eq_some_iff {s} pos := by simp [isLongestMatch_iff, ForwardPattern.skipPrefix?, and_comm, eq_comm (b := pos)] +instance {c : Char} : LawfulBackwardPatternModel c where + skipSuffix?_eq_some_iff {s} pos := by + simp [isLongestRevMatch_iff, BackwardPattern.skipSuffix?, and_comm, eq_comm (b := pos)] + theorem toSearcher_eq {c : Char} {s : Slice} : ToForwardSearcher.toSearcher c s = ToForwardSearcher.toSearcher (· == c) s := (rfl) +theorem toBackwardSearcher_eq {c : Char} {s : Slice} : + ToBackwardSearcher.toSearcher c s = ToBackwardSearcher.toSearcher (· == c) s := (rfl) + theorem matchesAt_iff {c : Char} {s : Slice} {pos : s.Pos} : MatchesAt c pos ↔ ∃ (h : pos ≠ s.endPos), pos.get h = c := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm] +theorem revMatchesAt_iff {c : Char} {s : Slice} {pos : s.Pos} : + RevMatchesAt c pos ↔ ∃ (h : pos ≠ s.startPos), (pos.prev h).get (by simp) = c := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm] + theorem matchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} : MatchesAt c pos ↔ ∃ t₁ t₂, pos.Splits t₁ (singleton c ++ t₂) := by rw [matchesAt_iff] @@ -91,37 +116,81 @@ theorem matchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} : have hne := hs.ne_endPos_of_singleton exact ⟨hne, (singleton_append_inj.mp (hs.eq_right (pos.splits_next_right hne))).1.symm⟩ +theorem revMatchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} : + RevMatchesAt c pos ↔ ∃ t₁ t₂, pos.Splits (t₁ ++ singleton c) t₂ := by + rw [revMatchesAt_iff] + refine ⟨?_, ?_⟩ + · rintro ⟨h, rfl⟩ + exact ⟨_, _, pos.splits_prev_right h⟩ + · rintro ⟨t₁, t₂, hs⟩ + have hne := hs.ne_startPos_of_singleton + refine ⟨hne, ?_⟩ + have := hs.eq_left (pos.splits_prev_right hne) + simp only [append_singleton, push_inj] at this + exact this.2.symm + theorem not_matchesAt_of_get_ne {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} (hc : pos.get h ≠ c) : ¬ MatchesAt c pos := by simp [matchesAt_iff, hc] +theorem not_revMatchesAt_of_get_ne {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos} + (hc : (pos.prev h).get (by simp) ≠ c) : ¬ RevMatchesAt c pos := by + simp [revMatchesAt_iff, hc] + theorem matchAt?_eq {s : Slice} {pos : s.Pos} {c : Char} : matchAt? c pos = if h₀ : ∃ (h : pos ≠ s.endPos), pos.get h = c then some (pos.next h₀.1) else none := by split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff] +theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {c : Char} : + revMatchAt? c pos = + if h₀ : ∃ (h : pos ≠ s.startPos), (pos.prev h).get (by simp) = c then some (pos.prev h₀.1) else none := by + split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff] + theorem isMatch_iff_isMatch_beq {c : Char} {s : Slice} {pos : s.Pos} : IsMatch c pos ↔ IsMatch (· == c) pos := by simp [isMatch_iff, CharPred.isMatch_iff, beq_iff_eq] +theorem isRevMatch_iff_isRevMatch_beq {c : Char} {s : Slice} {pos : s.Pos} : + IsRevMatch c pos ↔ IsRevMatch (· == c) pos := by + simp [isRevMatch_iff, CharPred.isRevMatch_iff, beq_iff_eq] + theorem isLongestMatch_iff_isLongestMatch_beq {c : Char} {s : Slice} {pos : s.Pos} : IsLongestMatch c pos ↔ IsLongestMatch (· == c) pos := by simp [isLongestMatch_iff_isMatch, isMatch_iff_isMatch_beq] +theorem isLongestRevMatch_iff_isLongestRevMatch_beq {c : Char} {s : Slice} {pos : s.Pos} : + IsLongestRevMatch c pos ↔ IsLongestRevMatch (· == c) pos := by + simp [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff_isRevMatch_beq] + theorem isLongestMatchAt_iff_isLongestMatchAt_beq {c : Char} {s : Slice} {pos pos' : s.Pos} : IsLongestMatchAt c pos pos' ↔ IsLongestMatchAt (· == c) pos pos' := by simp [Model.isLongestMatchAt_iff, isLongestMatch_iff_isLongestMatch_beq] +theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_beq {c : Char} {s : Slice} + {pos pos' : s.Pos} : + IsLongestRevMatchAt c pos pos' ↔ IsLongestRevMatchAt (· == c) pos pos' := by + simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff_isLongestRevMatch_beq] + theorem matchesAt_iff_matchesAt_beq {c : Char} {s : Slice} {pos : s.Pos} : MatchesAt c pos ↔ MatchesAt (· == c) pos := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_beq] +theorem revMatchesAt_iff_revMatchesAt_beq {c : Char} {s : Slice} {pos : s.Pos} : + RevMatchesAt c pos ↔ RevMatchesAt (· == c) pos := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq] + theorem matchAt?_eq_matchAt?_beq {c : Char} {s : Slice} {pos : s.Pos} : matchAt? c pos = matchAt? (· == c) pos := by refine Option.ext (fun pos' => ?_) simp [matchAt?_eq_some_iff, isLongestMatchAt_iff_isLongestMatchAt_beq] +theorem revMatchAt?_eq_revMatchAt?_beq {c : Char} {s : Slice} {pos : s.Pos} : + revMatchAt? c pos = revMatchAt? (· == c) pos := by + refine Option.ext (fun pos' => ?_) + simp [revMatchAt?_eq_some_iff, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq] + theorem isValidSearchFrom_iff_isValidSearchFrom_beq {c : Char} {s : Slice} {p : s.Pos} {l : List (SearchStep s)} : IsValidSearchFrom c p l ↔ IsValidSearchFrom (· == c) p l := by refine ⟨fun h => ?_, fun h => ?_⟩ @@ -134,11 +203,28 @@ theorem isValidSearchFrom_iff_isValidSearchFrom_beq {c : Char} {s : Slice} {p : | matched => simp_all [IsValidSearchFrom.matched, isLongestMatchAt_iff_isLongestMatchAt_beq] | mismatched => simp_all [IsValidSearchFrom.mismatched, matchesAt_iff_matchesAt_beq] +theorem isValidRevSearchFrom_iff_isValidRevSearchFrom_beq {c : Char} {s : Slice} {p : s.Pos} + {l : List (SearchStep s)} : IsValidRevSearchFrom c p l ↔ IsValidRevSearchFrom (· == c) p l := by + refine ⟨fun h => ?_, fun h => ?_⟩ + · induction h with + | startPos => simpa using IsValidRevSearchFrom.startPos + | matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq] + | mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_beq] + · induction h with + | startPos => simpa using IsValidRevSearchFrom.startPos + | matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq] + | mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_beq] + instance {c : Char} : LawfulToForwardSearcherModel c where isValidSearchFrom_toList s := by simpa [toSearcher_eq, isValidSearchFrom_iff_isValidSearchFrom_beq] using LawfulToForwardSearcherModel.isValidSearchFrom_toList (pat := (· == c)) (s := s) +instance {c : Char} : LawfulToBackwardSearcherModel c where + isValidRevSearchFrom_toList s := by + simpa [toBackwardSearcher_eq, isValidRevSearchFrom_iff_isValidRevSearchFrom_beq] using + LawfulToBackwardSearcherModel.isValidRevSearchFrom_toList (pat := (· == c)) (s := s) + end Pattern.Model.Char theorem startsWith_char_eq_startsWith_beq {c : Char} {s : Slice} : diff --git a/src/Init/Data/String/Lemmas/Pattern/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/Pred.lean index 00e315e272db..8d5f5cffe4c7 100644 --- a/src/Init/Data/String/Lemmas/Pattern/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/Pred.lean @@ -187,30 +187,60 @@ theorem isLongestMatchAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} IsLongestMatchAt p pos pos' ↔ ∃ h, pos' = pos.next h ∧ p (pos.get h) := by simp [isLongestMatchAt_iff_isLongestMatchAt_decide, CharPred.isLongestMatchAt_iff] +theorem isLongestRevMatchAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} + {pos pos' : s.Pos} : + IsLongestRevMatchAt p pos pos' ↔ ∃ h, pos = pos'.prev h ∧ p ((pos'.prev h).get (by simp)) := by + simp [isLongestRevMatchAt_iff_isLongestRevMatchAt_decide, CharPred.isLongestRevMatchAt_iff] + theorem isLongestMatchAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} (hc : p (pos.get h)) : IsLongestMatchAt p pos (pos.next h) := isLongestMatchAt_iff.2 ⟨h, by simp [hc]⟩ +theorem isLongestRevMatchAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} + {h : pos ≠ s.startPos} (hc : p ((pos.prev h).get (by simp))) : + IsLongestRevMatchAt p (pos.prev h) pos := + isLongestRevMatchAt_iff.2 ⟨h, by simp [hc]⟩ + theorem matchesAt_iff_matchesAt_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : MatchesAt p pos ↔ MatchesAt (decide <| p ·) pos := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_decide] +theorem revMatchesAt_iff_revMatchesAt_decide {p : Char → Prop} [DecidablePred p] {s : Slice} + {pos : s.Pos} : RevMatchesAt p pos ↔ RevMatchesAt (decide <| p ·) pos := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide] + theorem matchAt?_eq_matchAt?_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : matchAt? p pos = matchAt? (decide <| p ·) pos := by ext endPos simp [isLongestMatchAt_iff_isLongestMatchAt_decide] +theorem revMatchAt?_eq_revMatchAt?_decide {p : Char → Prop} [DecidablePred p] {s : Slice} + {pos : s.Pos} : revMatchAt? p pos = revMatchAt? (decide <| p ·) pos := by + ext startPos + simp [isLongestRevMatchAt_iff_isLongestRevMatchAt_decide] + theorem skipPrefix?_eq_skipPrefix?_decide {p : Char → Prop} [DecidablePred p] : ForwardPattern.skipPrefix? p = ForwardPattern.skipPrefix? (decide <| p ·) := rfl +theorem skipSuffix?_eq_skipSuffix?_decide {p : Char → Prop} [DecidablePred p] : + BackwardPattern.skipSuffix? p = BackwardPattern.skipSuffix? (decide <| p ·) := rfl + instance {p : Char → Prop} [DecidablePred p] : LawfulForwardPatternModel p where skipPrefix?_eq_some_iff {s} pos := by rw [skipPrefix?_eq_skipPrefix?_decide, isLongestMatch_iff_isLongestMatch_decide] exact LawfulForwardPatternModel.skipPrefix?_eq_some_iff .. +instance {p : Char → Prop} [DecidablePred p] : LawfulBackwardPatternModel p where + skipSuffix?_eq_some_iff {s} pos := by + rw [skipSuffix?_eq_skipSuffix?_decide, isLongestRevMatch_iff_isLongestRevMatch_decide] + exact LawfulBackwardPatternModel.skipSuffix?_eq_some_iff .. + theorem toSearcher_eq {p : Char → Prop} [DecidablePred p] {s : Slice} : ToForwardSearcher.toSearcher p s = ToForwardSearcher.toSearcher (decide <| p ·) s := (rfl) +theorem toBackwardSearcher_eq {p : Char → Prop} [DecidablePred p] {s : Slice} : + ToBackwardSearcher.toSearcher p s = ToBackwardSearcher.toSearcher (decide <| p ·) s := (rfl) + theorem isValidSearchFrom_iff_isValidSearchFrom_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} {l : List (SearchStep s)} : IsValidSearchFrom p pos l ↔ IsValidSearchFrom (decide <| p ·) pos l := by @@ -224,24 +254,55 @@ theorem isValidSearchFrom_iff_isValidSearchFrom_decide {p : Char → Prop} [Deci | matched => simp_all [IsValidSearchFrom.matched, isLongestMatchAt_iff_isLongestMatchAt_decide] | mismatched => simp_all [IsValidSearchFrom.mismatched, matchesAt_iff_matchesAt_decide] +theorem isValidRevSearchFrom_iff_isValidRevSearchFrom_decide {p : Char → Prop} [DecidablePred p] + {s : Slice} {pos : s.Pos} {l : List (SearchStep s)} : + IsValidRevSearchFrom p pos l ↔ IsValidRevSearchFrom (decide <| p ·) pos l := by + refine ⟨fun h => ?_, fun h => ?_⟩ + · induction h with + | startPos => simpa using IsValidRevSearchFrom.startPos + | matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide] + | mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_decide] + · induction h with + | startPos => simpa using IsValidRevSearchFrom.startPos + | matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide] + | mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_decide] + instance {p : Char → Prop} [DecidablePred p] : LawfulToForwardSearcherModel p where isValidSearchFrom_toList s := by simpa [toSearcher_eq, isValidSearchFrom_iff_isValidSearchFrom_decide] using LawfulToForwardSearcherModel.isValidSearchFrom_toList (pat := (decide <| p ·)) (s := s) +instance {p : Char → Prop} [DecidablePred p] : LawfulToBackwardSearcherModel p where + isValidRevSearchFrom_toList s := by + simpa [toBackwardSearcher_eq, isValidRevSearchFrom_iff_isValidRevSearchFrom_decide] using + LawfulToBackwardSearcherModel.isValidRevSearchFrom_toList (pat := (decide <| p ·)) (s := s) + theorem matchesAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : MatchesAt p pos ↔ ∃ (h : pos ≠ s.endPos), p (pos.get h) := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm] +theorem revMatchesAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} : + RevMatchesAt p pos ↔ ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm] + theorem not_matchesAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos} (hc : ¬ p (pos.get h)) : ¬ MatchesAt p pos := by simp [matchesAt_iff, hc] +theorem not_revMatchesAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} + {h : pos ≠ s.startPos} (hc : ¬ p ((pos.prev h).get (by simp))) : ¬ RevMatchesAt p pos := by + simp [revMatchesAt_iff, hc] + theorem matchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Prop} [DecidablePred p] : matchAt? p pos = if h₀ : ∃ (h : pos ≠ s.endPos), p (pos.get h) then some (pos.next h₀.1) else none := by split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff] +theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Prop} [DecidablePred p] : + revMatchAt? p pos = + if h₀ : ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) then some (pos.prev h₀.1) else none := by + split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff] + end Decidable end Pattern.Model.CharPred diff --git a/src/Init/Data/String/Pattern/Char.lean b/src/Init/Data/String/Pattern/Char.lean index eb8276e01d6a..a125a6fc3675 100644 --- a/src/Init/Data/String/Pattern/Char.lean +++ b/src/Init/Data/String/Pattern/Char.lean @@ -47,8 +47,8 @@ instance {c : Char} : LawfulBackwardPattern c where skipSuffixOfNonempty?_eq h := LawfulBackwardPattern.skipSuffixOfNonempty?_eq (pat := (· == c)) h endsWith_eq s := LawfulBackwardPattern.endsWith_eq (pat := (· == c)) s -instance {c : Char} : ToBackwardSearcher c (ToBackwardSearcher.DefaultBackwardSearcher c) := - .defaultImplementation +instance {c : Char} : ToBackwardSearcher c (ToBackwardSearcher.DefaultBackwardSearcher (· == c)) where + toSearcher s := ToBackwardSearcher.toSearcher (· == c) s end Char diff --git a/src/Init/Data/String/Pattern/Pred.lean b/src/Init/Data/String/Pattern/Pred.lean index 7438699ce613..6920111dfce9 100644 --- a/src/Init/Data/String/Pattern/Pred.lean +++ b/src/Init/Data/String/Pattern/Pred.lean @@ -139,8 +139,9 @@ instance {p : Char → Prop} [DecidablePred p] : LawfulBackwardPattern p where skipSuffixOfNonempty?_eq h := LawfulBackwardPattern.skipSuffixOfNonempty?_eq (pat := (decide <| p ·)) h endsWith_eq s := LawfulBackwardPattern.endsWith_eq (pat := (decide <| p ·)) s -instance {p : Char → Prop} [DecidablePred p] : ToBackwardSearcher p (ToBackwardSearcher.DefaultBackwardSearcher p) := - .defaultImplementation +instance {p : Char → Prop} [DecidablePred p] : + ToBackwardSearcher p (ToBackwardSearcher.DefaultBackwardSearcher (decide <| p ·)) where + toSearcher s := ToBackwardSearcher.toSearcher (decide <| p ·) s end Decidable From 206229d1d1fb5034577f94e0dabe89704d310d80 Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:38:31 +0000 Subject: [PATCH 7/9] rev versions of strins/basic --- .../String/Lemmas/Pattern/String/Basic.lean | 203 +++++++++++++++++- 1 file changed, 198 insertions(+), 5 deletions(-) diff --git a/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean index 36d1c5beb2d2..a521246e292b 100644 --- a/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/String/Basic.lean @@ -21,10 +21,10 @@ namespace ForwardSliceSearcher instance {pat : Slice} : PatternModel pat where /- - See the docstring of `ForwardPatternModel` for an explanation about why we disallow matching the + See the docstring of `PatternModel` for an explanation about why we disallow matching the empty string. - Requiring `s ≠ ""` is a trick that allows us to give a `ForwardPatternModel` instance + Requiring `s ≠ ""` is a trick that allows us to give a `PatternModel` instance unconditionally, without forcing `pat.copy` to be non-empty (which would make it very awkward to state theorems about the instance). It does not change anything about the fact that all lemmas about this instance require `pat.isEmpty = false`. @@ -35,6 +35,9 @@ instance {pat : Slice} : PatternModel pat where instance {pat : Slice} : NoPrefixPatternModel pat := .of_length_eq (by simp +contextual [PatternModel.Matches]) +instance {pat : Slice} : NoSuffixPatternModel pat := + .of_length_eq (by simp +contextual [PatternModel.Matches]) + theorem isMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : IsMatch pat pos ↔ (s.sliceTo pos).copy = pat.copy := by simp only [Model.isMatch_iff, PatternModel.Matches, ne_eq, copy_eq_empty_iff, @@ -42,24 +45,47 @@ theorem isMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : intro h' rw [← isEmpty_copy (s := s.sliceTo pos), h', isEmpty_copy, h] +theorem isRevMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : + IsRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat.copy := by + simp only [Model.isRevMatch_iff, PatternModel.Matches, ne_eq, copy_eq_empty_iff, + Bool.not_eq_true, and_iff_right_iff_imp] + intro h' + rw [← isEmpty_copy (s := s.sliceFrom pos), h', isEmpty_copy, h] + theorem isLongestMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : IsLongestMatch pat pos ↔ (s.sliceTo pos).copy = pat.copy := by rw [isLongestMatch_iff_isMatch, isMatch_iff h] +theorem isLongestRevMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : + IsLongestRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat.copy := by + rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff h] + theorem isLongestMatchAt_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) : IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat.copy := by simp [Model.isLongestMatchAt_iff, isLongestMatch_iff h] +theorem isLongestRevMatchAt_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat.copy := by + simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff h] + theorem isLongestMatchAt_iff_splits {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) : IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ t₁ t₂, pos₁.Splits t₁ (pat.copy ++ t₂) ∧ pos₂.Splits (t₁ ++ pat.copy) t₂ := by simp only [isLongestMatchAt_iff h, copy_slice_eq_iff_splits] +theorem isLongestRevMatchAt_iff_splits {pat s : Slice} {pos₁ pos₂ : s.Pos} + (h : pat.isEmpty = false) : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ ∃ t₁ t₂, pos₁.Splits t₁ (pat.copy ++ t₂) ∧ + pos₂.Splits (t₁ ++ pat.copy) t₂ := by + simp only [isLongestRevMatchAt_iff h, copy_slice_eq_iff_splits] + theorem isLongestMatch_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : IsLongestMatch pat pos ↔ ∃ t, pos.Splits pat.copy t := by - simp only [← isLongestMatchAt_startPos_iff, isLongestMatchAt_iff_splits h, splits_startPos_iff, - and_assoc, exists_and_left, exists_eq_left, empty_append] - exact ⟨fun ⟨h, _, h'⟩ => ⟨h, h'⟩, fun ⟨h, h'⟩ => ⟨h, h'.eq_append.symm, h'⟩⟩ + rw [isLongestMatch_iff h, copy_sliceTo_eq_iff_exists_splits] + +theorem isLongestRevMatch_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : + IsLongestRevMatch pat pos ↔ ∃ t, pos.Splits t pat.copy := by + rw [isLongestRevMatch_iff h, copy_sliceFrom_eq_iff_exists_splits] theorem isLongestMatchAt_iff_extract {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) : IsLongestMatchAt pat pos₁ pos₂ ↔ @@ -71,6 +97,18 @@ theorem isLongestMatchAt_iff_extract {pat s : Slice} {pos₁ pos₂ : s.Pos} (h exact ⟨by simp [Pos.le_iff, Pos.Raw.le_iff]; omega, by simp [← h', ← toByteArray_inj, toByteArray_copy_slice]⟩ +theorem isLongestRevMatchAt_iff_extract {pat s : Slice} {pos₁ pos₂ : s.Pos} + (h : pat.isEmpty = false) : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ + s.copy.toByteArray.extract pos₁.offset.byteIdx pos₂.offset.byteIdx = + pat.copy.toByteArray := by + rw [isLongestRevMatchAt_iff h] + refine ⟨fun ⟨h, h'⟩ => ?_, fun h' => ?_⟩ + · simp [← h', toByteArray_copy_slice] + · rw [← Slice.toByteArray_copy_ne_empty_iff, ← h', ne_eq, ByteArray.extract_eq_empty_iff] at h + exact ⟨by simp [Pos.le_iff, Pos.Raw.le_iff]; omega, + by simp [← h', ← toByteArray_inj, toByteArray_copy_slice]⟩ + theorem offset_of_isLongestMatchAt {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) (h' : IsLongestMatchAt pat pos₁ pos₂) : pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by simp only [Pos.Raw.ext_iff, Pos.Raw.byteIdx_increaseBy] @@ -81,12 +119,29 @@ theorem offset_of_isLongestMatchAt {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : suffices pos₂.offset.byteIdx ≤ s.utf8ByteSize by omega simpa [Pos.le_iff, Pos.Raw.le_iff] using pos₂.le_endPos +theorem offset_of_isLongestRevMatchAt {pat s : Slice} {pos₁ pos₂ : s.Pos} + (h : pat.isEmpty = false) (h' : IsLongestRevMatchAt pat pos₁ pos₂) : + pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by + simp only [Pos.Raw.ext_iff, Pos.Raw.byteIdx_increaseBy] + rw [isLongestRevMatchAt_iff_extract h] at h' + rw [← Slice.toByteArray_copy_ne_empty_iff, ← h', ne_eq, ByteArray.extract_eq_empty_iff] at h + replace h' := congrArg ByteArray.size h' + simp only [ByteArray.size_extract, size_toByteArray, utf8ByteSize_copy] at h' + suffices pos₂.offset.byteIdx ≤ s.utf8ByteSize by omega + simpa [Pos.le_iff, Pos.Raw.le_iff] using pos₂.le_endPos + theorem matchesAt_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : MatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ (pat.copy ++ t₂) := by simp only [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_splits h] exact ⟨fun ⟨e, t₁, t₂, ht₁, ht₂⟩ => ⟨t₁, t₂, ht₁⟩, fun ⟨t₁, t₂, ht⟩ => ⟨ht.rotateRight, t₁, t₂, ht, ht.splits_rotateRight⟩⟩ +theorem revMatchesAt_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : + RevMatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits (t₁ ++ pat.copy) t₂ := by + simp only [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_splits h] + exact ⟨fun ⟨e, t₁, t₂, ht₁, ht₂⟩ => ⟨t₁, t₂, ht₂⟩, + fun ⟨t₁, t₂, ht⟩ => ⟨ht.rotateLeft, t₁, t₂, ht.splits_rotateLeft, ht⟩⟩ + theorem exists_matchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false) : (∃ (pos : s.Pos), MatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat.copy ++ t₂ := by simp only [matchesAt_iff_splits h] @@ -99,6 +154,18 @@ theorem exists_matchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false) ⟨t₁, pat.copy ++ t₂, by rw [← append_assoc]; exact heq, rfl⟩ exact ⟨s.pos _ hvalid, t₁, t₂, ⟨by rw [← append_assoc]; exact heq, by simp⟩⟩ +theorem exists_revMatchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false) : + (∃ (pos : s.Pos), RevMatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat.copy ++ t₂ := by + simp only [revMatchesAt_iff_splits h] + constructor + · rintro ⟨pos, t₁, t₂, hsplit⟩ + exact ⟨t₁, t₂, by rw [hsplit.eq_append, append_assoc]⟩ + · rintro ⟨t₁, t₂, heq⟩ + have hvalid : (t₁ ++ pat.copy).rawEndPos.IsValidForSlice s := + Pos.Raw.isValidForSlice_iff_exists_append.mpr + ⟨t₁ ++ pat.copy, t₂, heq, rfl⟩ + exact ⟨s.pos _ hvalid, t₁, t₂, ⟨heq, by simp⟩⟩ + theorem matchesAt_iff_isLongestMatchAt {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : MatchesAt pat pos ↔ ∃ (h : (pos.offset.increaseBy pat.utf8ByteSize).IsValidForSlice s), IsLongestMatchAt pat pos (s.pos _ h) := by @@ -108,6 +175,25 @@ theorem matchesAt_iff_isLongestMatchAt {pat s : Slice} {pos : s.Pos} (h : pat.is obtain rfl : p = s.pos _ this := by simpa [Pos.ext_iff] using offset_of_isLongestMatchAt h h' exact h' +theorem revMatchesAt_iff_isLongestRevMatchAt {pat s : Slice} {pos : s.Pos} + (h : pat.isEmpty = false) : + RevMatchesAt pat pos ↔ + ∃ (h : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s), + IsLongestRevMatchAt pat (s.pos _ h) pos := by + refine ⟨fun ⟨⟨p, h'⟩⟩ => ?_, fun ⟨_, h⟩ => ⟨⟨_, h⟩⟩⟩ + have hoff := offset_of_isLongestRevMatchAt h h' + have hvalid : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s := by + rw [show pos.offset.decreaseBy pat.utf8ByteSize = p.offset from by + simp [Pos.Raw.ext_iff, Pos.Raw.byteIdx_decreaseBy, Pos.Raw.byteIdx_increaseBy] at hoff ⊢ + omega] + exact p.isValidForSlice + refine ⟨hvalid, ?_⟩ + obtain rfl : p = s.pos _ hvalid := by + simp only [Pos.ext_iff, offset_pos] + simp [Pos.Raw.ext_iff, Pos.Raw.byteIdx_decreaseBy, Pos.Raw.byteIdx_increaseBy] at hoff ⊢ + omega + exact h' + theorem matchesAt_iff_getElem {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) : MatchesAt pat pos ↔ ∃ (h : pos.offset.byteIdx + pat.copy.toByteArray.size ≤ s.copy.toByteArray.size), @@ -153,24 +239,49 @@ instance {pat : String} : PatternModel pat where instance {pat : String} : NoPrefixPatternModel pat := .of_length_eq (by simp +contextual [PatternModel.Matches]) +instance {pat : String} : NoSuffixPatternModel pat := + .of_length_eq (by simp +contextual [PatternModel.Matches]) + theorem isMatch_iff_slice {pat : String} {s : Slice} {pos : s.Pos} : IsMatch (ρ := String) pat pos ↔ IsMatch (ρ := Slice) pat.toSlice pos := by simp only [Model.isMatch_iff, PatternModel.Matches, copy_toSlice] +theorem isRevMatch_iff_slice {pat : String} {s : Slice} {pos : s.Pos} : + IsRevMatch (ρ := String) pat pos ↔ IsRevMatch (ρ := Slice) pat.toSlice pos := by + simp only [Model.isRevMatch_iff, PatternModel.Matches, copy_toSlice] + theorem isLongestMatch_iff_isLongestMatch_toSlice {pat : String} {s : Slice} {pos : s.Pos} : IsLongestMatch (ρ := String) pat pos ↔ IsLongestMatch (ρ := Slice) pat.toSlice pos where mp h := ⟨isMatch_iff_slice.1 h.isMatch, fun p hp hm => h.not_isMatch p hp (isMatch_iff_slice.2 hm)⟩ mpr h := ⟨isMatch_iff_slice.2 h.isMatch, fun p hp hm => h.not_isMatch p hp (isMatch_iff_slice.1 hm)⟩ +theorem isLongestRevMatch_iff_isLongestRevMatch_toSlice {pat : String} {s : Slice} {pos : s.Pos} : + IsLongestRevMatch (ρ := String) pat pos ↔ IsLongestRevMatch (ρ := Slice) pat.toSlice pos where + mp h := ⟨isRevMatch_iff_slice.1 h.isRevMatch, + fun p hp hm => h.not_isRevMatch p hp (isRevMatch_iff_slice.2 hm)⟩ + mpr h := ⟨isRevMatch_iff_slice.2 h.isRevMatch, + fun p hp hm => h.not_isRevMatch p hp (isRevMatch_iff_slice.1 hm)⟩ + theorem isLongestMatchAt_iff_isLongestMatchAt_toSlice {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} : IsLongestMatchAt (ρ := String) pat pos₁ pos₂ ↔ IsLongestMatchAt (ρ := Slice) pat.toSlice pos₁ pos₂ := by simp [Model.isLongestMatchAt_iff, isLongestMatch_iff_isLongestMatch_toSlice] +theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice {pat : String} {s : Slice} + {pos₁ pos₂ : s.Pos} : + IsLongestRevMatchAt (ρ := String) pat pos₁ pos₂ ↔ + IsLongestRevMatchAt (ρ := Slice) pat.toSlice pos₁ pos₂ := by + simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff_isLongestRevMatch_toSlice] + theorem matchesAt_iff_toSlice {pat : String} {s : Slice} {pos : s.Pos} : MatchesAt (ρ := String) pat pos ↔ MatchesAt (ρ := Slice) pat.toSlice pos := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_toSlice] +theorem revMatchesAt_iff_toSlice {pat : String} {s : Slice} {pos : s.Pos} : + RevMatchesAt (ρ := String) pat pos ↔ RevMatchesAt (ρ := Slice) pat.toSlice pos := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, + isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice] + private theorem toSlice_isEmpty (h : pat ≠ "") : pat.toSlice.isEmpty = false := by rwa [isEmpty_toSlice, isEmpty_eq_false_iff] @@ -179,16 +290,31 @@ theorem isMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : rw [isMatch_iff_slice, ForwardSliceSearcher.isMatch_iff (toSlice_isEmpty h)] simp +theorem isRevMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : + IsRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat := by + rw [isRevMatch_iff_slice, ForwardSliceSearcher.isRevMatch_iff (toSlice_isEmpty h)] + simp + theorem isLongestMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : IsLongestMatch pat pos ↔ (s.sliceTo pos).copy = pat := by rw [isLongestMatch_iff_isMatch, isMatch_iff h] +theorem isLongestRevMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : + IsLongestRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat := by + rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff h] + theorem isLongestMatchAt_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") : IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat := by rw [isLongestMatchAt_iff_isLongestMatchAt_toSlice, ForwardSliceSearcher.isLongestMatchAt_iff (toSlice_isEmpty h)] simp +theorem isLongestRevMatchAt_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat := by + rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice, + ForwardSliceSearcher.isLongestRevMatchAt_iff (toSlice_isEmpty h)] + simp + theorem isLongestMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") : IsLongestMatchAt pat pos₁ pos₂ ↔ @@ -197,6 +323,14 @@ theorem isLongestMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : ForwardSliceSearcher.isLongestMatchAt_iff_splits (toSlice_isEmpty h)] simp +theorem isLongestRevMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} + (h : pat ≠ "") : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ + ∃ t₁ t₂, pos₁.Splits t₁ (pat ++ t₂) ∧ pos₂.Splits (t₁ ++ pat) t₂ := by + rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice, + ForwardSliceSearcher.isLongestRevMatchAt_iff_splits (toSlice_isEmpty h)] + simp + theorem isLongestMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") : IsLongestMatchAt pat pos₁ pos₂ ↔ @@ -205,6 +339,14 @@ theorem isLongestMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : ForwardSliceSearcher.isLongestMatchAt_iff_extract (toSlice_isEmpty h)] simp +theorem isLongestRevMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} + (h : pat ≠ "") : + IsLongestRevMatchAt pat pos₁ pos₂ ↔ + s.copy.toByteArray.extract pos₁.offset.byteIdx pos₂.offset.byteIdx = pat.toByteArray := by + rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice, + ForwardSliceSearcher.isLongestRevMatchAt_iff_extract (toSlice_isEmpty h)] + simp + theorem offset_of_isLongestMatchAt {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") (h' : IsLongestMatchAt pat pos₁ pos₂) : pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by @@ -212,12 +354,25 @@ theorem offset_of_isLongestMatchAt {pat : String} {s : Slice} {pos₁ pos₂ : s exact ForwardSliceSearcher.offset_of_isLongestMatchAt (toSlice_isEmpty h) (isLongestMatchAt_iff_isLongestMatchAt_toSlice.1 h') +theorem offset_of_isLongestRevMatchAt {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} + (h : pat ≠ "") (h' : IsLongestRevMatchAt pat pos₁ pos₂) : + pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by + rw [show pat.utf8ByteSize = pat.toSlice.utf8ByteSize from utf8ByteSize_toSlice.symm] + exact ForwardSliceSearcher.offset_of_isLongestRevMatchAt (toSlice_isEmpty h) + (isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice.1 h') + theorem matchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : MatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ (pat ++ t₂) := by rw [matchesAt_iff_toSlice, ForwardSliceSearcher.matchesAt_iff_splits (toSlice_isEmpty h)] simp +theorem revMatchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : + RevMatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits (t₁ ++ pat) t₂ := by + rw [revMatchesAt_iff_toSlice, + ForwardSliceSearcher.revMatchesAt_iff_splits (toSlice_isEmpty h)] + simp + theorem exists_matchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat ≠ "") : (∃ (pos : s.Pos), MatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat ++ t₂ := by simp only [matchesAt_iff_splits h] @@ -230,6 +385,14 @@ theorem exists_matchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat ≠ " ⟨t₁, pat ++ t₂, by rw [← append_assoc]; exact heq, rfl⟩ exact ⟨s.pos _ hvalid, t₁, t₂, ⟨by rw [← append_assoc]; exact heq, by simp⟩⟩ +theorem exists_revMatchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat ≠ "") : + (∃ (pos : s.Pos), RevMatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat ++ t₂ := by + rw [show (∃ (pos : s.Pos), RevMatchesAt (ρ := String) pat pos) ↔ + (∃ (pos : s.Pos), RevMatchesAt (ρ := Slice) pat.toSlice pos) from by + simp [revMatchesAt_iff_toSlice], + ForwardSliceSearcher.exists_revMatchesAt_iff_eq_append (toSlice_isEmpty h)] + simp + theorem matchesAt_iff_isLongestMatchAt {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : MatchesAt pat pos ↔ ∃ (h : (pos.offset.increaseBy pat.utf8ByteSize).IsValidForSlice s), @@ -239,6 +402,16 @@ theorem matchesAt_iff_isLongestMatchAt {pat : String} {s : Slice} {pos : s.Pos} simp only [utf8ByteSize_toSlice, ← isLongestMatchAt_iff_isLongestMatchAt_toSlice] at key rwa [matchesAt_iff_toSlice] +theorem revMatchesAt_iff_isLongestRevMatchAt {pat : String} {s : Slice} {pos : s.Pos} + (h : pat ≠ "") : + RevMatchesAt pat pos ↔ + ∃ (h : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s), + IsLongestRevMatchAt pat (s.pos _ h) pos := by + have key := ForwardSliceSearcher.revMatchesAt_iff_isLongestRevMatchAt (pat := pat.toSlice) + (toSlice_isEmpty h) (pos := pos) + simp only [utf8ByteSize_toSlice, ← isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice] at key + rwa [revMatchesAt_iff_toSlice] + theorem matchesAt_iff_getElem {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") : MatchesAt pat pos ↔ ∃ (h : pos.offset.byteIdx + pat.toByteArray.size ≤ s.copy.toByteArray.size), @@ -259,6 +432,11 @@ theorem matchesAt_iff_matchesAt_toSlice {pat : String} {s : Slice} {pos : s.Pos} : MatchesAt pat pos ↔ MatchesAt pat.toSlice pos := by simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_toSlice] +theorem revMatchesAt_iff_revMatchesAt_toSlice {pat : String} {s : Slice} + {pos : s.Pos} : RevMatchesAt pat pos ↔ RevMatchesAt pat.toSlice pos := by + simp [revMatchesAt_iff_exists_isLongestRevMatchAt, + isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice] + theorem toSearcher_eq {pat : String} {s : Slice} : ToForwardSearcher.toSearcher pat s = ToForwardSearcher.toSearcher pat.toSlice s := (rfl) @@ -275,6 +453,21 @@ theorem isValidSearchFrom_iff_isValidSearchFrom_toSlice {pat : String} | matched => simp_all [IsValidSearchFrom.matched, isLongestMatchAt_iff_isLongestMatchAt_toSlice] | mismatched => simp_all [IsValidSearchFrom.mismatched, matchesAt_iff_matchesAt_toSlice] +theorem isValidRevSearchFrom_iff_isValidRevSearchFrom_toSlice {pat : String} + {s : Slice} {pos : s.Pos} {l : List (SearchStep s)} : + IsValidRevSearchFrom pat pos l ↔ IsValidRevSearchFrom pat.toSlice pos l := by + refine ⟨fun h => ?_, fun h => ?_⟩ + · induction h with + | startPos => simpa using IsValidRevSearchFrom.startPos + | matched => simp_all [IsValidRevSearchFrom.matched, + isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice] + | mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_toSlice] + · induction h with + | startPos => simpa using IsValidRevSearchFrom.startPos + | matched => simp_all [IsValidRevSearchFrom.matched, + isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice] + | mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_toSlice] + end ForwardStringSearcher end String.Slice.Pattern.Model From 88adb3e1fd8eb81d1a17983b566dea4ba2a217ee Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Thu, 26 Mar 2026 07:54:57 +0000 Subject: [PATCH 8/9] string backward pattern --- .../Lemmas/Pattern/String/ForwardPattern.lean | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean b/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean index 1396ee619d60..6fe25f716011 100644 --- a/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean +++ b/src/Init/Data/String/Lemmas/Pattern/String/ForwardPattern.lean @@ -102,6 +102,105 @@ public theorem lawfulForwardPatternModel {pat : String} (hpat : pat ≠ "") : end Model.ForwardStringSearcher +namespace BackwardSliceSearcher + +theorem endsWith_iff {pat s : Slice} : endsWith pat s ↔ ∃ t, s.copy = t ++ pat.copy := by + rw [endsWith] + simp [Internal.memcmpSlice_eq_true_iff, utf8ByteSize_eq_size_toByteArray_copy, -size_toByteArray] + generalize pat.copy = pat + generalize s.copy = s + refine ⟨fun ⟨h₁, h₂⟩ => ?_, ?_⟩ + · rw [Nat.sub_add_cancel h₁] at h₂ + suffices (s.rawEndPos.unoffsetBy pat.rawEndPos).IsValid s by + have h₃ : (s.sliceFrom (s.pos _ this)).copy = pat := by + rw [← toByteArray_inj, (s.pos _ this).splits.toByteArray_right_eq] + simpa [offset_pos, Pos.Raw.byteIdx_unoffsetBy, byteIdx_rawEndPos] + have := (s.pos _ this).splits + rw [h₃] at this + exact ⟨_, this.eq_append⟩ + rw [Pos.Raw.isValid_iff_isValidUTF8_extract_utf8ByteSize] + refine ⟨by simp [Pos.Raw.le_iff, Pos.Raw.byteIdx_unoffsetBy], ?_⟩ + simp only [size_toByteArray] at h₂ + simpa [Pos.Raw.byteIdx_unoffsetBy, byteIdx_rawEndPos, h₂] using pat.isValidUTF8 + · rintro ⟨t, rfl⟩ + exact ⟨by simp, by rw [Nat.sub_add_cancel (by simp)]; exact + ByteArray.extract_append_eq_right (by simp) (by simp)⟩ + +theorem skipSuffix?_eq_some_iff {pat s : Slice} {pos : s.Pos} : + skipSuffix? pat s = some pos ↔ (s.sliceFrom pos).copy = pat.copy := by + fun_cases skipSuffix? with + | case1 h => + simp only [Option.some.injEq] + obtain ⟨t, ht⟩ := endsWith_iff.1 h + have hpc : pat.copy.utf8ByteSize = pat.utf8ByteSize := Slice.utf8ByteSize_copy + have hsz : s.utf8ByteSize = t.utf8ByteSize + pat.utf8ByteSize := by + have := congrArg String.utf8ByteSize ht + simp only [utf8ByteSize_append, Slice.utf8ByteSize_copy] at this + exact this + have hoff : (s.endPos.offset.unoffsetBy pat.rawEndPos) = t.rawEndPos := by + ext + simp only [offset_endPos, Pos.Raw.byteIdx_unoffsetBy, byteIdx_rawEndPos, + String.byteIdx_rawEndPos] + omega + have hval : (s.endPos.offset.unoffsetBy pat.rawEndPos).IsValidForSlice s := + Pos.Raw.isValidForSlice_iff_exists_append.mpr ⟨t, pat.copy, ht, hoff⟩ + have hsp : (s.pos _ hval).Splits t pat.copy := ⟨ht, hoff⟩ + rw [Slice.pos!_eq_pos hval] + exact ⟨(· ▸ hsp.copy_sliceFrom_eq), + fun h => hsp.pos_eq_of_eq_right (h ▸ pos.splits)⟩ + | case2 h => + simp only [endsWith_iff, not_exists] at h + simp only [reduceCtorEq, false_iff] + intro heq + have := h (s.sliceTo pos).copy + simp [← heq, pos.splits.eq_append] at this + +theorem isSome_skipSuffix? {pat s : Slice} : (skipSuffix? pat s).isSome = endsWith pat s := by + fun_cases skipSuffix? <;> simp_all + +public theorem endsWith_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) : + BackwardPattern.endsWith pat s = true := by + suffices pat.copy = "" by simp [BackwardPattern.endsWith, endsWith_iff, this] + simpa + +public theorem skipSuffix?_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) : + BackwardPattern.skipSuffix? pat s = some s.endPos := by + simpa [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff] + +end BackwardSliceSearcher + +namespace Model.BackwardSliceSearcher + +open Pattern.BackwardSliceSearcher + +public instance {pat : Slice} : LawfulBackwardPattern pat where + skipSuffixOfNonempty?_eq _ := rfl + endsWith_eq _ := isSome_skipSuffix?.symm + +public theorem lawfulBackwardPatternModel {pat : Slice} (hpat : pat.isEmpty = false) : + LawfulBackwardPatternModel pat where + skipSuffix?_eq_some_iff pos := by + simp [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff, + ForwardSliceSearcher.isLongestRevMatch_iff hpat] + +end Model.BackwardSliceSearcher + +namespace Model.BackwardStringSearcher + +open Pattern.BackwardSliceSearcher + +public instance {pat : String} : LawfulBackwardPattern pat where + skipSuffixOfNonempty?_eq _ := rfl + endsWith_eq _ := isSome_skipSuffix?.symm + +public theorem lawfulBackwardPatternModel {pat : String} (hpat : pat ≠ "") : + LawfulBackwardPatternModel pat where + skipSuffix?_eq_some_iff pos := by + simp [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff, + ForwardStringSearcher.isLongestRevMatch_iff hpat] + +end Model.BackwardStringSearcher + end Pattern public theorem startsWith_string_eq_startsWith_toSlice {pat : String} {s : Slice} : From 7b7ee4adbd3a6879ca0ee9754b04383e4e42959c Mon Sep 17 00:00:00 2001 From: Julia Markus Himmel <2065352+TwoFX@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:25:52 +0000 Subject: [PATCH 9/9] skipSuffix?, endsWith, dropSuffix? --- .../String/Lemmas/Pattern/TakeDrop/Basic.lean | 61 ++++++++++++ .../String/Lemmas/Pattern/TakeDrop/Char.lean | 67 +++++++++++++ .../String/Lemmas/Pattern/TakeDrop/Pred.lean | 93 +++++++++++++++++++ .../Lemmas/Pattern/TakeDrop/String.lean | 81 ++++++++++++++++ 4 files changed, 302 insertions(+) diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean index 4bef45b6f533..c66b96a2944d 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Basic.lean @@ -72,6 +72,58 @@ theorem Pattern.Model.eq_append_of_dropPrefix?_eq_some {ρ : Type} {pat : ρ} [P obtain ⟨pos, h₁, h₂⟩ := h exact ⟨(s.sliceTo pos).copy, h₁.isMatch.matches_copy, by simp [← h₂, ← copy_eq_copy_sliceTo]⟩ +theorem skipSuffix?_eq_backwardPatternSkipSuffix? {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : Slice} : + s.skipSuffix? pat = BackwardPattern.skipSuffix? pat s := (rfl) + +theorem endsWith_eq_backwardPatternEndsWith {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : Slice} : + s.endsWith pat = BackwardPattern.endsWith pat s := (rfl) + +theorem dropSuffix?_eq_map_skipSuffix? {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : Slice} : + s.dropSuffix? pat = (s.skipSuffix? pat).map s.sliceTo := (rfl) + +theorem Pattern.Model.skipSuffix?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat] + [LawfulBackwardPatternModel pat] {s : Slice} {pos : s.Pos} : + s.skipSuffix? pat = some pos ↔ IsLongestRevMatch pat pos := by + rw [skipSuffix?_eq_backwardPatternSkipSuffix?, LawfulBackwardPatternModel.skipSuffix?_eq_some_iff] + +theorem Pattern.Model.skipSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat] + [LawfulBackwardPatternModel pat] {s : Slice} : + s.skipSuffix? pat = none ↔ ¬ RevMatchesAt pat s.endPos := by + rw [skipSuffix?_eq_backwardPatternSkipSuffix?, LawfulBackwardPatternModel.skipSuffix?_eq_none_iff] + +@[simp] +theorem isSome_skipSuffix? {ρ : Type} {pat : ρ} [BackwardPattern pat] [LawfulBackwardPattern pat] {s : Slice} : + (s.skipSuffix? pat).isSome = s.endsWith pat := by + rw [endsWith_eq_backwardPatternEndsWith, skipSuffix?, LawfulBackwardPattern.endsWith_eq] + +theorem Pattern.Model.endsWith_eq_false_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat] + [LawfulBackwardPatternModel pat] {s : Slice} : + s.endsWith pat = false ↔ ¬ RevMatchesAt pat s.endPos := by + rw [← Pattern.Model.skipSuffix?_eq_none_iff, ← Option.isNone_iff_eq_none, + ← isSome_skipSuffix?, Option.isSome_eq_false_iff] + +theorem Pattern.Model.endsWith_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat] + [LawfulBackwardPatternModel pat] {s : Slice} : + s.endsWith pat = true ↔ RevMatchesAt pat s.endPos := by + rw [← Bool.not_eq_false, endsWith_eq_false_iff, Classical.not_not] + +@[simp] +theorem skipSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [LawfulBackwardPattern pat] + {s : Slice} : s.skipSuffix? pat = none ↔ s.endsWith pat = false := by + rw [← Option.isNone_iff_eq_none, ← Option.isSome_eq_false_iff, isSome_skipSuffix?] + +@[simp] +theorem dropSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [LawfulBackwardPattern pat] + {s : Slice} : s.dropSuffix? pat = none ↔ s.endsWith pat = false := by + simp [dropSuffix?_eq_map_skipSuffix?] + +theorem Pattern.Model.eq_append_of_dropSuffix?_eq_some {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat] + [LawfulBackwardPatternModel pat] {s res : Slice} (h : s.dropSuffix? pat = some res) : + ∃ t, PatternModel.Matches pat t ∧ s.copy = res.copy ++ t := by + simp only [dropSuffix?_eq_map_skipSuffix?, Option.map_eq_some_iff, skipSuffix?_eq_some_iff] at h + obtain ⟨pos, h₁, h₂⟩ := h + exact ⟨(s.sliceFrom pos).copy, h₁.isRevMatch.matches_copy, by simp [← h₂, ← copy_eq_copy_sliceTo]⟩ + end Slice theorem skipPrefix?_eq_skipPrefix?_toSlice {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : String} : @@ -83,4 +135,13 @@ theorem startsWith_eq_startsWith_toSlice {ρ : Type} {pat : ρ} [ForwardPattern theorem dropPrefix?_eq_dropPrefix?_toSlice {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : String} : s.dropPrefix? pat = s.toSlice.dropPrefix? pat := (rfl) +theorem skipSuffix?_eq_skipSuffix?_toSlice {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : String} : + s.skipSuffix? pat = (s.toSlice.skipSuffix? pat).map Pos.ofToSlice := (rfl) + +theorem endsWith_eq_endsWith_toSlice {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : String} : + s.endsWith pat = s.toSlice.endsWith pat := (rfl) + +theorem dropSuffix?_eq_dropSuffix?_toSlice {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : String} : + s.dropSuffix? pat = s.toSlice.dropSuffix? pat := (rfl) + end String diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean index 8c8cbac7b1e5..0304b10f88b2 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Char.lean @@ -11,6 +11,8 @@ public import Init.Data.String.TakeDrop import Init.Data.String.Lemmas.Pattern.TakeDrop.Basic import Init.Data.String.Lemmas.Pattern.Char import Init.Data.Option.Lemmas +import Init.Data.String.Lemmas.FindPos +import Init.Data.List.Sublist public section @@ -54,6 +56,41 @@ theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s res : Slice} (h : s. s.copy = singleton c ++ res.copy := by simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h +theorem skipSuffix?_char_eq_some_iff {c : Char} {s : Slice} {pos : s.Pos} : + s.skipSuffix? c = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by + rw [Pattern.Model.skipSuffix?_eq_some_iff, Char.isLongestRevMatch_iff] + +theorem endsWith_char_iff_get {c : Char} {s : Slice} : + s.endsWith c ↔ ∃ h, (s.endPos.prev h).get (by simp) = c := by + simp [Pattern.Model.endsWith_iff, Char.revMatchesAt_iff] + +theorem endsWith_char_eq_false_iff_get {c : Char} {s : Slice} : + s.endsWith c = false ↔ ∀ h, (s.endPos.prev h).get (by simp) ≠ c := by + simp [Pattern.Model.endsWith_eq_false_iff, Char.revMatchesAt_iff] + +theorem endsWith_char_iff_exists_append {c : Char} {s : Slice} : + s.endsWith c ↔ ∃ t, s.copy = t ++ singleton c := by + rw [Pattern.Model.endsWith_iff, Char.revMatchesAt_iff_splits] + simp only [splits_endPos_iff, exists_eq_right, eq_comm (a := s.copy)] + +theorem endsWith_char_eq_getLast? {c : Char} {s : Slice} : + s.endsWith c = (s.copy.toList.getLast? == some c) := by + rw [Bool.eq_iff_iff, endsWith_char_iff_exists_append, beq_iff_eq, + ← List.singleton_suffix_iff_getLast?_eq_some, List.suffix_iff_exists_eq_append] + constructor + · rintro ⟨t, ht⟩ + exact ⟨t.toList, by rw [ht, toList_append, toList_singleton]⟩ + · rintro ⟨l, hl⟩ + exact ⟨ofList l, by rw [← toList_inj, toList_append, toList_singleton, toList_ofList]; exact hl⟩ + +theorem endsWith_char_eq_false_iff_forall_append {c : Char} {s : Slice} : + s.endsWith c = false ↔ ∀ t, s.copy ≠ t ++ singleton c := by + simp [← Bool.not_eq_true, endsWith_char_iff_exists_append] + +theorem eq_append_of_dropSuffix?_char_eq_some {c : Char} {s res : Slice} (h : s.dropSuffix? c = some res) : + s.copy = res.copy ++ singleton c := by + simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropSuffix?_eq_some h + end Slice theorem skipPrefix?_char_eq_some_iff {c : Char} {s : String} {pos : s.Pos} : @@ -86,4 +123,34 @@ theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s : String} {res : Sli rw [dropPrefix?_eq_dropPrefix?_toSlice] at h simpa using Slice.eq_append_of_dropPrefix?_char_eq_some h +theorem skipSuffix?_char_eq_some_iff {c : Char} {s : String} {pos : s.Pos} : + s.skipSuffix? c = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by + simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_char_eq_some_iff, ← Pos.toSlice_inj, + Pos.prev_toSlice] + +theorem endsWith_char_iff_get {c : Char} {s : String} : + s.endsWith c ↔ ∃ h, (s.endPos.prev h).get (by simp) = c := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_iff_get, Pos.prev_toSlice] + +theorem endsWith_char_eq_false_iff_get {c : Char} {s : String} : + s.endsWith c = false ↔ ∀ h, (s.endPos.prev h).get (by simp) ≠ c := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_eq_false_iff_get, Pos.prev_toSlice] + +theorem endsWith_char_eq_getLast? {c : Char} {s : String} : + s.endsWith c = (s.toList.getLast? == some c) := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_eq_getLast?] + +theorem endsWith_char_iff_exists_append {c : Char} {s : String} : + s.endsWith c ↔ ∃ t, s = t ++ singleton c := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_iff_exists_append] + +theorem endsWith_char_eq_false_iff_forall_append {c : Char} {s : String} : + s.endsWith c = false ↔ ∀ t, s ≠ t ++ singleton c := by + simp [← Bool.not_eq_true, endsWith_char_iff_exists_append] + +theorem eq_append_of_dropSuffix?_char_eq_some {c : Char} {s : String} {res : Slice} (h : s.dropSuffix? c = some res) : + s = res.copy ++ singleton c := by + rw [dropSuffix?_eq_dropSuffix?_toSlice] at h + simpa using Slice.eq_append_of_dropSuffix?_char_eq_some h + end String diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean index 77533eb8e1bc..acdc42f29c2d 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/Pred.lean @@ -11,6 +11,7 @@ public import Init.Data.String.TakeDrop import Init.Data.String.Lemmas.Pattern.TakeDrop.Basic import Init.Data.String.Lemmas.Pattern.Pred import Init.Data.Option.Lemmas +import Init.Data.String.Lemmas.FindPos import Init.ByCases public section @@ -69,6 +70,54 @@ theorem eq_append_of_dropPrefix_prop_eq_some {P : Char → Prop} [DecidablePred rw [dropPrefix?_prop_eq_dropPrefix?_decide] at h simpa using eq_append_of_dropPrefix?_bool_eq_some h +theorem skipSuffix?_bool_eq_some_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} : + s.skipSuffix? p = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) = true := by + rw [Pattern.Model.skipSuffix?_eq_some_iff, CharPred.isLongestRevMatch_iff] + +theorem endsWith_bool_iff_get {p : Char → Bool} {s : Slice} : + s.endsWith p ↔ ∃ h, p ((s.endPos.prev h).get (by simp)) = true := by + simp [Pattern.Model.endsWith_iff, CharPred.revMatchesAt_iff] + +theorem endsWith_bool_eq_false_iff_get {p : Char → Bool} {s : Slice} : + s.endsWith p = false ↔ ∀ h, p ((s.endPos.prev h).get (by simp)) = false := by + simp [Pattern.Model.endsWith_eq_false_iff, CharPred.revMatchesAt_iff] + +theorem endsWith_bool_eq_getLast? {p : Char → Bool} {s : Slice} : + s.endsWith p = s.copy.toList.getLast?.any p := by + rw [Bool.eq_iff_iff, Pattern.Model.endsWith_iff, CharPred.revMatchesAt_iff] + by_cases h : s.endPos = s.startPos + · refine ⟨fun ⟨h', _⟩ => by simp_all, ?_⟩ + have : s.copy = "" := by simp_all [Slice.startPos_eq_endPos_iff.mp h.symm] + simp [this] + · obtain ⟨t, ht⟩ := s.splits_endPos.exists_eq_append_singleton_of_ne_startPos h + simp [h, ht] + +theorem eq_append_of_dropSuffix?_bool_eq_some {p : Char → Bool} {s res : Slice} (h : s.dropSuffix? p = some res) : + ∃ c, s.copy = res.copy ++ singleton c ∧ p c = true := by + obtain ⟨_, ⟨c, ⟨rfl, h₁⟩⟩, h₂⟩ := by simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropSuffix?_eq_some h + exact ⟨_, h₂, h₁⟩ + +theorem skipSuffix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} : + s.skipSuffix? P = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ P ((s.endPos.prev h).get (by simp)) := by + simp [skipSuffix?_prop_eq_skipSuffix?_decide, skipSuffix?_bool_eq_some_iff] + +theorem endsWith_prop_iff_get {P : Char → Prop} [DecidablePred P] {s : Slice} : + s.endsWith P ↔ ∃ h, P ((s.endPos.prev h).get (by simp)) := by + simp [endsWith_prop_eq_endsWith_decide, endsWith_bool_iff_get] + +theorem endsWith_prop_eq_false_iff_get {P : Char → Prop} [DecidablePred P] {s : Slice} : + s.endsWith P = false ↔ ∀ h, ¬ P ((s.endPos.prev h).get (by simp)) := by + simp [endsWith_prop_eq_endsWith_decide, endsWith_bool_eq_false_iff_get] + +theorem endsWith_prop_eq_getLast? {P : Char → Prop} [DecidablePred P] {s : Slice} : + s.endsWith P = s.copy.toList.getLast?.any (decide <| P ·) := by + simp [endsWith_prop_eq_endsWith_decide, endsWith_bool_eq_getLast?] + +theorem eq_append_of_dropSuffix?_prop_eq_some {P : Char → Prop} [DecidablePred P] {s res : Slice} (h : s.dropSuffix? P = some res) : + ∃ c, s.copy = res.copy ++ singleton c ∧ P c := by + rw [dropSuffix?_prop_eq_dropSuffix?_decide] at h + simpa using eq_append_of_dropSuffix?_bool_eq_some h + end Slice theorem skipPrefix?_bool_eq_some_iff {p : Char → Bool} {s : String} {pos : s.Pos} : @@ -115,4 +164,48 @@ theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char → Prop} [DecidablePred rw [dropPrefix?_eq_dropPrefix?_toSlice] at h simpa using Slice.eq_append_of_dropPrefix_prop_eq_some h +theorem skipSuffix?_bool_eq_some_iff {p : Char → Bool} {s : String} {pos : s.Pos} : + s.skipSuffix? p = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) = true := by + simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_bool_eq_some_iff, ← Pos.toSlice_inj, + Pos.prev_toSlice] + +theorem endsWith_bool_iff_get {p : Char → Bool} {s : String} : + s.endsWith p ↔ ∃ h, p ((s.endPos.prev h).get (by simp)) = true := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_bool_iff_get, Pos.prev_toSlice] + +theorem endsWith_bool_eq_false_iff_get {p : Char → Bool} {s : String} : + s.endsWith p = false ↔ ∀ h, p ((s.endPos.prev h).get (by simp)) = false := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_bool_eq_false_iff_get, Pos.prev_toSlice] + +theorem endsWith_bool_eq_getLast? {p : Char → Bool} {s : String} : + s.endsWith p = s.toList.getLast?.any p := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_bool_eq_getLast?] + +theorem eq_append_of_dropSuffix?_bool_eq_some {p : Char → Bool} {s : String} {res : Slice} (h : s.dropSuffix? p = some res) : + ∃ c, s = res.copy ++ singleton c ∧ p c = true := by + rw [dropSuffix?_eq_dropSuffix?_toSlice] at h + simpa using Slice.eq_append_of_dropSuffix?_bool_eq_some h + +theorem skipSuffix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s : String} {pos : s.Pos} : + s.skipSuffix? P = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ P ((s.endPos.prev h).get (by simp)) := by + simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_prop_eq_some_iff, ← Pos.toSlice_inj, + Pos.prev_toSlice] + +theorem endsWith_prop_iff_get {P : Char → Prop} [DecidablePred P] {s : String} : + s.endsWith P ↔ ∃ h, P ((s.endPos.prev h).get (by simp)) := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_prop_iff_get, Pos.prev_toSlice] + +theorem endsWith_prop_eq_false_iff_get {P : Char → Prop} [DecidablePred P] {s : String} : + s.endsWith P = false ↔ ∀ h, ¬ P ((s.endPos.prev h).get (by simp)) := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_prop_eq_false_iff_get, Pos.prev_toSlice] + +theorem endsWith_prop_eq_getLast? {P : Char → Prop} [DecidablePred P] {s : String} : + s.endsWith P = s.toList.getLast?.any (decide <| P ·) := by + simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_prop_eq_getLast?] + +theorem eq_append_of_dropSuffix?_prop_eq_some {P : Char → Prop} [DecidablePred P] {s : String} {res : Slice} + (h : s.dropSuffix? P = some res) : ∃ c, s = res.copy ++ singleton c ∧ P c := by + rw [dropSuffix?_eq_dropSuffix?_toSlice] at h + simpa using Slice.eq_append_of_dropSuffix?_prop_eq_some h + end String diff --git a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean index 7115c813df33..e3e2336f3482 100644 --- a/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean +++ b/src/Init/Data/String/Lemmas/Pattern/TakeDrop/String.lean @@ -104,6 +104,87 @@ theorem eq_append_of_dropPrefix?_string_eq_some {pat : String} {s res : Slice} ( rw [dropPrefix?_string_eq_dropPrefix?_toSlice] at h simpa using eq_append_of_dropPrefix?_slice_eq_some h +theorem skipSuffix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) : + s.skipSuffix? pat = some s.endPos := by + rw [skipSuffix?_eq_backwardPatternSkipSuffix?, BackwardSliceSearcher.skipSuffix?_of_isEmpty hpat] + +@[simp] +theorem skipSuffix?_slice_eq_some_iff {pat s : Slice} {pos : s.Pos} : + s.skipSuffix? pat = some pos ↔ ∃ t, pos.Splits t pat.copy := by + match h : pat.isEmpty with + | false => + have := BackwardSliceSearcher.lawfulBackwardPatternModel h + rw [Pattern.Model.skipSuffix?_eq_some_iff, ForwardSliceSearcher.isLongestRevMatch_iff_splits h] + | true => simp [skipSuffix?_slice_of_isEmpty h, (show pat.copy = "" by simpa), eq_comm] + +theorem endsWith_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) : + s.endsWith pat = true := by + rw [endsWith_eq_backwardPatternEndsWith, BackwardSliceSearcher.endsWith_of_isEmpty hpat] + +@[simp] +theorem endsWith_slice_iff {pat s : Slice} : + s.endsWith pat ↔ pat.copy.toList <:+ s.copy.toList := by + match h : pat.isEmpty with + | false => + have := BackwardSliceSearcher.lawfulBackwardPatternModel h + simp only [Model.endsWith_iff, ForwardSliceSearcher.revMatchesAt_iff_splits h, + splits_endPos_iff, exists_eq_right] + simp only [← toList_inj, toList_append, List.suffix_iff_exists_append_eq] + exact ⟨fun ⟨t, ht⟩ => ⟨t.toList, by simp [ht]⟩, fun ⟨t, ht⟩ => ⟨String.ofList t, by simp [← ht]⟩⟩ + | true => simp [endsWith_slice_of_isEmpty h, (show pat.copy = "" by simpa)] + +@[simp] +theorem endsWith_slice_eq_false_iff {pat s : Slice} : + s.endsWith pat = false ↔ ¬ (pat.copy.toList <:+ s.copy.toList) := by + simp [← Bool.not_eq_true, endsWith_slice_iff] + +theorem dropSuffix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) : + s.dropSuffix? pat = some s := by + simp [dropSuffix?_eq_map_skipSuffix?, skipSuffix?_slice_of_isEmpty hpat] + +theorem eq_append_of_dropSuffix?_slice_eq_some {pat s res : Slice} (h : s.dropSuffix? pat = some res) : + s.copy = res.copy ++ pat.copy := by + match hpat : pat.isEmpty with + | false => + have := BackwardSliceSearcher.lawfulBackwardPatternModel hpat + have := Pattern.Model.eq_append_of_dropSuffix?_eq_some h + simp only [PatternModel.Matches] at this + obtain ⟨_, ⟨-, rfl⟩, h⟩ := this + exact h + | true => simp [Option.some.inj (h ▸ dropSuffix?_slice_of_isEmpty hpat), (show pat.copy = "" by simpa)] + +@[simp] +theorem skipSuffix?_string_eq_some_iff' {pat : String} {s : Slice} {pos : s.Pos} : + s.skipSuffix? pat = some pos ↔ ∃ t, pos.Splits t pat := by + simp [skipSuffix?_string_eq_skipSuffix?_toSlice] + +@[simp] +theorem skipSuffix?_string_empty {s : Slice} : s.skipSuffix? "" = some s.endPos := by + simp + +@[simp] +theorem endsWith_string_iff {pat : String} {s : Slice} : + s.endsWith pat ↔ pat.toList <:+ s.copy.toList := by + simp [endsWith_string_eq_endsWith_toSlice] + +@[simp] +theorem endsWith_string_empty {s : Slice} : s.endsWith "" = true := by + simp + +@[simp] +theorem endsWith_string_eq_false_iff {pat : String} {s : Slice} : + s.endsWith pat = false ↔ ¬ (pat.toList <:+ s.copy.toList) := by + simp [endsWith_string_eq_endsWith_toSlice] + +@[simp] +theorem dropSuffix?_string_empty {s : Slice} : s.dropSuffix? "" = some s := by + simpa [dropSuffix?_string_eq_dropSuffix?_toSlice] using dropSuffix?_slice_of_isEmpty (by simp) + +theorem eq_append_of_dropSuffix?_string_eq_some {pat : String} {s res : Slice} (h : s.dropSuffix? pat = some res) : + s.copy = res.copy ++ pat := by + rw [dropSuffix?_string_eq_dropSuffix?_toSlice] at h + simpa using eq_append_of_dropSuffix?_slice_eq_some h + end Slice theorem skipPrefix?_slice_of_isEmpty {pat : Slice} {s : String} (hpat : pat.isEmpty = true) :