lean4-htt/src/Init/Data/String/Termination.lean
Markus Himmel 1ae680c5e2
chore: minor String API improvements (#11439)
This PR performs minor maintenance on the String API

- Rename `String.Pos.toCopy` to `String.Pos.copy` to adhere to the
naming convention
- Rename `String.Pos.extract` to `String.extract` to get sane dot
notation again
- Add `String.Slice.Pos.extract`
2025-12-01 11:44:14 +00:00

257 lines
9.3 KiB
Text

/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.String.Basic
public import Init.Data.String.Lemmas.Splits
/-!
# Helpers for termination arguments about functions operating on strings
-/
public section
namespace String
namespace Slice.Pos
/-- The number of bytes between `p` and the end position. This number decreases as `p` advances. -/
def remainingBytes {s : Slice} (p : s.Pos) : Nat :=
p.offset.byteDistance s.endPos.offset
theorem remainingBytes_eq_byteDistance {s : Slice} {p : s.Pos} :
p.remainingBytes = p.offset.byteDistance s.endPos.offset := (rfl)
theorem remainingBytes_eq {s : Slice} {p : s.Pos} :
p.remainingBytes = s.utf8ByteSize - p.offset.byteIdx := by
simp [remainingBytes_eq_byteDistance, Pos.Raw.byteDistance_eq]
theorem remainingBytes_inj {s : Slice} {p q : s.Pos} :
p.remainingBytes = q.remainingBytes ↔ p = q := by
have := p.isValidForSlice.le_utf8ByteSize
have := q.isValidForSlice.le_utf8ByteSize
simp only [remainingBytes_eq, Pos.ext_iff, Pos.Raw.ext_iff]
omega
theorem le_iff_remainingBytes_le {s : Slice} (p q : s.Pos) :
p ≤ q ↔ q.remainingBytes ≤ p.remainingBytes := by
have := p.isValidForSlice.le_utf8ByteSize
have := q.isValidForSlice.le_utf8ByteSize
simp only [remainingBytes_eq, Slice.Pos.le_iff, Pos.Raw.le_iff]
omega
theorem lt_iff_remainingBytes_lt {s : Slice} (p q : s.Pos) :
p < q ↔ q.remainingBytes < p.remainingBytes := by
have := p.isValidForSlice.le_utf8ByteSize
have := q.isValidForSlice.le_utf8ByteSize
simp only [remainingBytes_eq, Slice.Pos.lt_iff, Pos.Raw.lt_iff]
omega
theorem wellFounded_lt {s : Slice} : WellFounded (fun (p : s.Pos) q => p < q) := by
simpa [lt_iff, Pos.Raw.lt_iff] using
InvImage.wf (Pos.Raw.byteIdx ∘ Slice.Pos.offset) Nat.lt_wfRel.wf
theorem wellFounded_gt {s : Slice} : WellFounded (fun (p : s.Pos) q => q < p) := by
simpa [lt_iff_remainingBytes_lt] using
InvImage.wf Slice.Pos.remainingBytes Nat.lt_wfRel.wf
instance {s : Slice} : WellFoundedRelation s.Pos where
rel p q := q < p
wf := Slice.Pos.wellFounded_gt
/-- Type alias for `String.Slice.Pos` representing that the given position is expected to decrease
in recursive calls. -/
structure Down (s : Slice) : Type where
inner : s.Pos
/-- Use `termination_by pos.down` to signify that in a recursive call, the parameter `pos` is
expected to decrease. -/
def down {s : Slice} (p : s.Pos) : Pos.Down s where
inner := p
@[simp]
theorem inner_down {s : Slice} {p : s.Pos} : p.down.inner = p := (rfl)
instance {s : Slice} : WellFoundedRelation (Pos.Down s) where
rel p q := p.inner < q.inner
wf := InvImage.wf Down.inner wellFounded_lt
theorem ne_endPos_of_next?_eq_some {s : Slice} {p q : s.Pos} :
p.next? = some q → p ≠ s.endPos := by
simpa [next?] using fun h _ => h
theorem eq_next_of_next?_eq_some {s : Slice} {p q : s.Pos} :
(h : p.next? = some q) → q = p.next (ne_endPos_of_next?_eq_some h) := by
simpa [next?] using fun _ h => h.symm
theorem ne_startPos_of_prev?_eq_some {s : Slice} {p q : s.Pos} :
p.prev? = some q → p ≠ s.startPos := by
simpa [prev?] using fun h _ => h
theorem eq_prev_of_prev?_eq_some {s : Slice} {p q : s.Pos} :
(h : p.prev? = some q) → q = p.prev (ne_startPos_of_prev?_eq_some h) := by
simpa [prev?] using fun _ h => h.symm
@[simp]
theorem le_refl {s : Slice} (p : s.Pos) : p ≤ p := by
simp [le_iff]
theorem lt_trans {s : Slice} {p q r : s.Pos} : p < q → q < r → p < r := by
simpa [Pos.lt_iff, Pos.Raw.lt_iff] using Nat.lt_trans
@[simp]
theorem lt_next_next {s : Slice} {p : s.Pos} {h h'} : p < (p.next h).next h' :=
lt_trans p.lt_next (p.next h).lt_next
@[simp]
theorem prev_prev_lt {s : Slice} {p : s.Pos} {h h'} : (p.prev h).prev h' < p :=
lt_trans (p.prev h).prev_lt p.prev_lt
end Slice.Pos
namespace Pos
/-- The number of bytes between `p` and the end position. This number decreases as `p` advances. -/
def remainingBytes {s : String} (p : s.Pos) : Nat :=
p.toSlice.remainingBytes
@[simp]
theorem remainingBytes_toSlice {s : String} {p : s.Pos} :
p.toSlice.remainingBytes = p.remainingBytes := (rfl)
theorem remainingBytes_eq_byteDistance {s : String} {p : s.Pos} :
p.remainingBytes = p.offset.byteDistance s.endPos.offset := (rfl)
theorem remainingBytes_eq {s : String} {p : s.Pos} :
p.remainingBytes = s.utf8ByteSize - p.offset.byteIdx := by
simp [remainingBytes_eq_byteDistance, Pos.Raw.byteDistance_eq]
theorem remainingBytes_inj {s : String} {p q : s.Pos} :
p.remainingBytes = q.remainingBytes ↔ p = q := by
simp [← remainingBytes_toSlice, Pos.toSlice_inj, Slice.Pos.remainingBytes_inj]
theorem le_iff_remainingBytes_le {s : String} (p q : s.Pos) :
p ≤ q ↔ q.remainingBytes ≤ p.remainingBytes := by
simp [← remainingBytes_toSlice, ← Slice.Pos.le_iff_remainingBytes_le]
theorem lt_iff_remainingBytes_lt {s : String} (p q : s.Pos) :
p < q ↔ q.remainingBytes < p.remainingBytes := by
simp [← remainingBytes_toSlice, ← Slice.Pos.lt_iff_remainingBytes_lt]
theorem wellFounded_lt {s : String} : WellFounded (fun (p : s.Pos) q => p < q) := by
simpa [lt_iff, Pos.Raw.lt_iff] using
InvImage.wf (Pos.Raw.byteIdx ∘ Pos.offset) Nat.lt_wfRel.wf
theorem wellFounded_gt {s : String} : WellFounded (fun (p : s.Pos) q => q < p) := by
simpa [lt_iff_remainingBytes_lt] using
InvImage.wf Pos.remainingBytes Nat.lt_wfRel.wf
instance {s : String} : WellFoundedRelation s.Pos where
rel p q := q < p
wf := Pos.wellFounded_gt
/-- Type alias for `String.Pos` representing that the given position is expected to decrease
in recursive calls. -/
structure Down (s : String) : Type where
inner : s.Pos
/-- Use `termination_by pos.down` to signify that in a recursive call, the parameter `pos` is
expected to decrease. -/
def down {s : String} (p : s.Pos) : Pos.Down s where
inner := p
@[simp]
theorem inner_down {s : String} {p : s.Pos} : p.down.inner = p := (rfl)
instance {s : String} : WellFoundedRelation (Pos.Down s) where
rel p q := p.inner < q.inner
wf := InvImage.wf Pos.Down.inner Pos.wellFounded_lt
theorem map_toSlice_next? {s : String} {p : s.Pos} :
p.next?.map Pos.toSlice = p.toSlice.next? := by
simp [next?]
theorem map_toSlice_prev? {s : String} {p : s.Pos} :
p.prev?.map Pos.toSlice = p.toSlice.prev? := by
simp [prev?]
theorem ne_endPos_of_next?_eq_some {s : String} {p q : s.Pos}
(h : p.next? = some q) : p ≠ s.endPos :=
ne_of_apply_ne Pos.toSlice (Slice.Pos.ne_endPos_of_next?_eq_some
(by simpa only [Pos.map_toSlice_next?, Option.map_some] using congrArg (·.map toSlice) h))
theorem eq_next_of_next?_eq_some {s : String} {p q : s.Pos} (h : p.next? = some q) :
q = p.next (ne_endPos_of_next?_eq_some h) := by
simpa only [← toSlice_inj, toSlice_next] using Slice.Pos.eq_next_of_next?_eq_some
(by simpa [Pos.map_toSlice_next?] using congrArg (·.map toSlice) h)
theorem ne_startPos_of_prev?_eq_some {s : String} {p q : s.Pos}
(h : p.prev? = some q) : p ≠ s.startPos :=
ne_of_apply_ne Pos.toSlice (Slice.Pos.ne_startPos_of_prev?_eq_some
(by simpa only [Pos.map_toSlice_prev?, Option.map_some] using congrArg (·.map toSlice) h))
theorem eq_prev_of_prev?_eq_some {s : String} {p q : s.Pos} (h : p.prev? = some q) :
q = p.prev (ne_startPos_of_prev?_eq_some h) := by
simpa only [← toSlice_inj, toSlice_prev] using Slice.Pos.eq_prev_of_prev?_eq_some
(by simpa [Pos.map_toSlice_prev?] using congrArg (·.map toSlice) h)
@[simp]
theorem le_refl {s : String} (p : s.Pos) : p ≤ p := by
simp [Pos.le_iff]
theorem lt_trans {s : String} {p q r : s.Pos} : p < q → q < r → p < r := by
simpa [Pos.lt_iff, Pos.Raw.lt_iff] using Nat.lt_trans
@[simp]
theorem lt_next_next {s : String} {p : s.Pos} {h h'} : p < (p.next h).next h' :=
lt_trans p.lt_next (p.next h).lt_next
@[simp]
theorem prev_prev_lt {s : String} {p : s.Pos} {h h'} : (p.prev h).prev h' < p :=
lt_trans (p.prev h).prev_lt p.prev_lt
theorem Splits.remainingBytes_eq {s : String} {p : s.Pos} {t₁ t₂}
(h : p.Splits t₁ t₂) : p.remainingBytes = t₂.utf8ByteSize := by
simp [Pos.remainingBytes_eq, h.eq_append, h.offset_eq_rawEndPos]
end Pos
namespace Slice.Pos
@[simp]
theorem remainingBytes_copy {s : Slice} {p : s.Pos} :
p.copy.remainingBytes = p.remainingBytes := by
simp [remainingBytes_eq, String.Pos.remainingBytes_eq, Slice.utf8ByteSize_eq]
theorem Splits.remainingBytes_eq {s : Slice} {p : s.Pos} {t₁ t₂} (h : p.Splits t₁ t₂) :
p.remainingBytes = t₂.utf8ByteSize := by
simpa using h.copy.remainingBytes_eq
end Slice.Pos
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
(with_reducible change (_ : Slice.Pos _) < _
simp [
Slice.Pos.eq_next_of_next?_eq_some (by assumption),
]) <;> done)
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
(with_reducible change (_ : Slice.Pos _) < _
simp [
Slice.Pos.eq_prev_of_prev?_eq_some (by assumption),
]) <;> done)
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
(with_reducible change (_ : String.Pos _) < _
simp [
Pos.eq_next_of_next?_eq_some (by assumption),
]) <;> done)
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
(with_reducible change (_ : String.Pos _) < _
simp [
Pos.eq_prev_of_prev?_eq_some (by assumption),
]) <;> done)
end String