chore: reduce usage of String.length (#13681)
This PR reduces the usage of `String.length` in our codebase. This is just the first step of many towards eliminating `String.length`.
This commit is contained in:
parent
422920f643
commit
79659457fb
36 changed files with 180 additions and 134 deletions
|
|
@ -10,6 +10,7 @@ public import Init.Data.Repr
|
|||
public import Init.Data.Char.Basic
|
||||
public import Init.Data.ToString.Basic
|
||||
public import Init.Data.String.Basic
|
||||
public import Init.Data.String.Length
|
||||
import Init.NotationExtra
|
||||
import all Init.Data.Repr
|
||||
import Init.Omega
|
||||
|
|
|
|||
|
|
@ -31,3 +31,4 @@ public import Init.Data.String.Subslice
|
|||
public import Init.Data.String.Iter
|
||||
public import Init.Data.String.Iterate
|
||||
public import Init.Data.String.Hashable
|
||||
public import Init.Data.String.Length
|
||||
|
|
|
|||
|
|
@ -264,28 +264,6 @@ theorem String.toList_empty : "".toList = [] := by
|
|||
theorem String.data_empty : "".toList = [] :=
|
||||
toList_empty
|
||||
|
||||
/--
|
||||
Returns the length of a string in Unicode code points.
|
||||
|
||||
Examples:
|
||||
* `"".length = 0`
|
||||
* `"abc".length = 3`
|
||||
* `"L∃∀N".length = 4`
|
||||
-/
|
||||
@[extern "lean_string_length", expose, tagged_return]
|
||||
def String.length (b : @& String) : Nat :=
|
||||
b.toList.length
|
||||
|
||||
@[simp]
|
||||
theorem String.Internal.size_toArray {b : String} : (String.Internal.toArray b).size = b.length :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem String.length_toList {s : String} : s.toList.length = s.length := (rfl)
|
||||
|
||||
@[deprecated String.length_toList (since := "2025-10-30")]
|
||||
theorem String.length_data {b : String} : b.toList.length = b.length := (rfl)
|
||||
|
||||
private theorem ByteArray.utf8Decode?go_eq_utf8Decode?go_extract {b : ByteArray} {hi : i ≤ b.size} {acc : Array Char} :
|
||||
utf8Decode?.go b i acc hi = (utf8Decode?.go (b.extract i b.size) 0 #[] (by simp)).map (acc ++ ·) := by
|
||||
fun_cases utf8Decode?.go b i acc hi with
|
||||
|
|
@ -436,14 +414,6 @@ theorem String.ofList_eq_empty_iff {l : List Char} : String.ofList l = "" ↔ l
|
|||
theorem List.asString_eq_empty_iff {l : List Char} : String.ofList l = "" ↔ l = [] :=
|
||||
String.ofList_eq_empty_iff
|
||||
|
||||
@[simp]
|
||||
theorem String.length_ofList {l : List Char} : (String.ofList l).length = l.length := by
|
||||
rw [← String.length_toList, String.toList_ofList]
|
||||
|
||||
@[deprecated String.length_ofList (since := "2025-10-30")]
|
||||
theorem List.length_asString {l : List Char} : (String.ofList l).length = l.length :=
|
||||
String.length_ofList
|
||||
|
||||
end
|
||||
|
||||
namespace String
|
||||
|
|
@ -531,7 +501,7 @@ theorem Pos.Raw.isValid_ofList {l : List Char} {p : Pos.Raw} :
|
|||
rw [isValid_iff_exists_append]
|
||||
refine ⟨?_, ?_⟩
|
||||
· rintro ⟨t₁, t₂, ht, rfl⟩
|
||||
refine ⟨t₁.length, ?_⟩
|
||||
refine ⟨t₁.toList.length, ?_⟩
|
||||
have := congrArg String.toList ht
|
||||
simp only [String.toList_ofList, String.toList_append] at this
|
||||
simp [this]
|
||||
|
|
@ -3127,8 +3097,6 @@ namespace String
|
|||
theorem ext {s₁ s₂ : String} (h : s₁.toList = s₂.toList) : s₁ = s₂ :=
|
||||
toList_injective h
|
||||
|
||||
@[simp] theorem length_empty : "".length = 0 := by simp [← length_toList, toList_empty]
|
||||
|
||||
@[deprecated singleton_eq_ofList (since := "2025-10-30")]
|
||||
theorem singleton_eq {c : Char} : String.singleton c = ofList [c] :=
|
||||
singleton_eq_ofList
|
||||
|
|
@ -3140,10 +3108,6 @@ theorem singleton_eq {c : Char} : String.singleton c = ofList [c] :=
|
|||
theorem data_singleton (c : Char) : (String.singleton c).toList = [c] :=
|
||||
toList_singleton c
|
||||
|
||||
@[simp]
|
||||
theorem length_singleton {c : Char} : (String.singleton c).length = 1 := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp]
|
||||
theorem toList_push (c : Char) : (String.push s c).toList = s.toList ++ [c] := by
|
||||
simp [← append_singleton]
|
||||
|
|
@ -3152,15 +3116,6 @@ theorem toList_push (c : Char) : (String.push s c).toList = s.toList ++ [c] := b
|
|||
theorem data_push (c : Char) : (String.push s c).toList = s.toList ++ [c] :=
|
||||
toList_push c
|
||||
|
||||
@[simp] theorem length_push (c : Char) : (String.push s c).length = s.length + 1 := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp] theorem length_pushn (c : Char) (n : Nat) : (pushn s c n).length = s.length + n := by
|
||||
rw [pushn_eq_repeat_push]; induction n <;> simp [Nat.repeat, Nat.add_assoc, *]
|
||||
|
||||
@[simp] theorem length_append (s t : String) : (s ++ t).length = s.length + t.length := by
|
||||
simp [← length_toList]
|
||||
|
||||
theorem lt_iff {s t : String} : s < t ↔ s.toList < t.toList := .rfl
|
||||
|
||||
namespace Pos.Raw
|
||||
|
|
@ -3211,11 +3166,3 @@ theorem bytes_inj {s t : String} : s.toByteArray = t.toByteArray ↔ s = t :=
|
|||
toByteArray_inj
|
||||
|
||||
end String
|
||||
|
||||
namespace Char
|
||||
|
||||
@[deprecated String.length_singleton (since := "2026-02-12")]
|
||||
theorem length_toString (c : Char) : c.toString.length = 1 := by
|
||||
simp
|
||||
|
||||
end Char
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ public import Init.Data.String.Basic
|
|||
import all Init.Data.String.Basic
|
||||
import Init.Data.String.Search
|
||||
import Init.Data.String.Termination
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -21,3 +21,4 @@ public import Init.Data.String.Lemmas.Iter
|
|||
public import Init.Data.String.Lemmas.Hashable
|
||||
public import Init.Data.String.Lemmas.TakeDrop
|
||||
public import Init.Data.String.Lemmas.StringOrder
|
||||
public import Init.Data.String.Lemmas.Length
|
||||
|
|
|
|||
|
|
@ -81,11 +81,6 @@ theorem toList_join {l : List String} : (String.join l).toList = l.flatMap Strin
|
|||
theorem join_append {l m : List String} : String.join (l ++ m) = String.join l ++ String.join m := by
|
||||
simp [← toList_inj]
|
||||
|
||||
@[simp]
|
||||
theorem length_join {l : List String} : (String.join l).length = (l.map String.length).sum := by
|
||||
simp only [← length_toList, toList_join, List.length_flatMap]
|
||||
simp
|
||||
|
||||
namespace Slice
|
||||
|
||||
@[simp]
|
||||
|
|
|
|||
|
|
@ -162,10 +162,6 @@ theorem Slice.isEmpty_slice_eq_false_iff {s : Slice} {p₁ p₂ h} :
|
|||
(s.slice p₁ p₂ h).isEmpty = false ↔ p₁ ≠ p₂ := by
|
||||
rw [ne_eq, ← isEmpty_slice (h := h), Bool.not_eq_true]
|
||||
|
||||
@[simp]
|
||||
theorem length_eq_zero_iff {s : String} : s.length = 0 ↔ s = "" := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp]
|
||||
theorem toByteArray_eq_empty_iff {s : String} :
|
||||
s.toByteArray = ByteArray.empty ↔ s = "" := by
|
||||
|
|
|
|||
31
src/Init/Data/String/Lemmas/Length.lean
Normal file
31
src/Init/Data/String/Lemmas/Length.lean
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Length
|
||||
public import Init.Data.String.Modify
|
||||
import Init.Data.String.Lemmas.Modify
|
||||
import Init.Data.String.Lemmas.Intercalate
|
||||
|
||||
public section
|
||||
|
||||
namespace String
|
||||
|
||||
@[simp]
|
||||
theorem length_eq_zero_iff {s : String} : s.length = 0 ↔ s = "" := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp]
|
||||
theorem length_map {f : Char → Char} {s : String} : (s.map f).length = s.length := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp]
|
||||
theorem length_join {l : List String} : (String.join l).length = (l.map String.length).sum := by
|
||||
simp only [← length_toList, toList_join, List.length_flatMap]
|
||||
simp [length_toList]
|
||||
|
||||
end String
|
||||
|
|
@ -57,10 +57,6 @@ theorem map_eq_internal {f : Char → Char} {s : String} : s.map f = .ofList (s.
|
|||
apply String.toList_injective
|
||||
simp only [toList_map, toList_ofList]
|
||||
|
||||
@[simp]
|
||||
theorem length_map {f : Char → Char} {s : String} : (s.map f).length = s.length := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp]
|
||||
theorem map_eq_empty {f : Char → Char} {s : String} : s.map f = "" ↔ s = "" := by
|
||||
simp [← toList_eq_nil_iff]
|
||||
|
|
|
|||
|
|
@ -301,8 +301,8 @@ This implies that the notion of match and longest match coincide.
|
|||
class NoPrefixPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where
|
||||
eq_empty (s t) : PatternModel.Matches pat s → PatternModel.Matches pat (s ++ t) → t = ""
|
||||
|
||||
theorem NoPrefixPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
(h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) :
|
||||
theorem NoPrefixPatternModel.of_length_toList_eq {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
(h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.toList.length = t.toList.length) :
|
||||
NoPrefixPatternModel pat where
|
||||
eq_empty s t hs ht := by simpa using h s _ hs ht
|
||||
|
||||
|
|
@ -322,8 +322,8 @@ 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) :
|
||||
theorem NoSuffixPatternModel.of_length_toList_eq {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
(h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.toList.length = t.toList.length) :
|
||||
NoSuffixPatternModel pat where
|
||||
eq_empty s t hs ht := by simpa using h t _ hs ht
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ instance {c : Char} : StrictPatternModel c where
|
|||
not_matches_empty := by simp [PatternModel.Matches]
|
||||
|
||||
instance {c : Char} : NoPrefixPatternModel c :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {c : Char} : NoSuffixPatternModel c :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsMatch c pos ↔
|
||||
|
|
@ -186,12 +186,12 @@ theorem isLongestMatchAtChain_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
|
|||
|
||||
theorem isLongestMatchAtChain_iff_toList {c : Char} {s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestMatchAtChain c pos pos' ↔
|
||||
∃ (h : pos ≤ pos'), (s.slice pos pos' h).copy.toList = List.replicate (s.slice pos pos' h).copy.length c := by
|
||||
∃ (h : pos ≤ pos'), (s.slice pos pos' h).copy.toList = List.replicate (s.slice pos pos' h).copy.toList.length c := by
|
||||
simp [isLongestMatchAtChain_iff_isLongestMatchAtChain_beq, CharPred.isLongestMatchAtChain_iff_toList,
|
||||
List.eq_replicate_iff]
|
||||
|
||||
theorem isLongestMatchAtChain_startPos_endPos_iff_toList {c : Char} {s : Slice} :
|
||||
IsLongestMatchAtChain c s.startPos s.endPos ↔ s.copy.toList = List.replicate s.copy.length c := by
|
||||
IsLongestMatchAtChain c s.startPos s.endPos ↔ s.copy.toList = List.replicate s.copy.toList.length c := by
|
||||
simp [isLongestMatchAtChain_iff_isLongestMatchAtChain_beq,
|
||||
CharPred.isLongestMatchAtChain_startPos_endPos_iff_toList, List.eq_replicate_iff]
|
||||
|
||||
|
|
@ -216,12 +216,12 @@ theorem isLongestRevMatchAtChain_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
|
|||
|
||||
theorem isLongestRevMatchAtChain_iff_toList {c : Char} {s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestRevMatchAtChain c pos pos' ↔
|
||||
∃ (h : pos ≤ pos'), (s.slice pos pos' h).copy.toList = List.replicate (s.slice pos pos' h).copy.length c := by
|
||||
∃ (h : pos ≤ pos'), (s.slice pos pos' h).copy.toList = List.replicate (s.slice pos pos' h).copy.toList.length c := by
|
||||
simp [isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_beq, CharPred.isLongestRevMatchAtChain_iff_toList,
|
||||
List.eq_replicate_iff]
|
||||
|
||||
theorem isLongestRevMatchAtChain_startPos_endPos_iff_toList {c : Char} {s : Slice} :
|
||||
IsLongestRevMatchAtChain c s.startPos s.endPos ↔ s.copy.toList = List.replicate s.copy.length c := by
|
||||
IsLongestRevMatchAtChain c s.startPos s.endPos ↔ s.copy.toList = List.replicate s.copy.toList.length c := by
|
||||
simp [isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_beq,
|
||||
CharPred.isLongestRevMatchAtChain_startPos_endPos_iff_toList, List.eq_replicate_iff]
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ instance {p : Char → Bool} : StrictPatternModel p where
|
|||
not_matches_empty := by simp [PatternModel.Matches]
|
||||
|
||||
instance {p : Char → Bool} : NoPrefixPatternModel p :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {p : Char → Bool} : NoSuffixPatternModel p :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
IsMatch p pos ↔
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ theorem strictPatternModel {pat : Slice} (hpat : pat.isEmpty = false) : StrictPa
|
|||
not_matches_empty := by simpa [PatternModel.Matches]
|
||||
|
||||
instance {pat : Slice} : NoPrefixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {pat : Slice} : NoSuffixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff {pat s : Slice} {pos : s.Pos} :
|
||||
IsMatch pat pos ↔ (s.sliceTo pos).copy = pat.copy := by
|
||||
|
|
@ -287,10 +287,10 @@ theorem strictPatternModel {pat : String} (h : pat ≠ "") : StrictPatternModel
|
|||
not_matches_empty := by simpa [PatternModel.Matches]
|
||||
|
||||
instance {pat : String} : NoPrefixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {pat : String} : NoSuffixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
.of_length_toList_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
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ theorem get_eq_of_lt_skipPrefixWhile_char {c : Char} {s : Slice} {pos : s.Pos} (
|
|||
Pos.get_eq_of_lt_skipWhile_char (Pos.startPos_le _) (by rwa [skipPrefixWhile_eq_skipWhile_startPos] at h)
|
||||
|
||||
@[simp]
|
||||
theorem all_char_iff {c : Char} {s : Slice} : s.all c ↔ s.copy.toList = List.replicate s.copy.length c := by
|
||||
theorem all_char_iff {c : Char} {s : Slice} : s.all c ↔ s.copy.toList = List.replicate s.copy.toList.length c := by
|
||||
rw [Bool.eq_iff_iff]
|
||||
simp [Pattern.Model.all_eq_true_iff, Char.isLongestMatchAtChain_startPos_endPos_iff_toList]
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ theorem get_eq_of_skipSuffixWhile_le_char {c : Char} {s : Slice} {pos : s.Pos}
|
|||
Pos.get_eq_of_revSkipWhile_le_char h' (by rwa [skipSuffixWhile_eq_revSkipWhile_endPos] at h)
|
||||
|
||||
@[simp]
|
||||
theorem revAll_char_iff {c : Char} {s : Slice} : s.revAll c ↔ s.copy.toList = List.replicate s.copy.length c := by
|
||||
theorem revAll_char_iff {c : Char} {s : Slice} : s.revAll c ↔ s.copy.toList = List.replicate s.copy.toList.length c := by
|
||||
rw [Bool.eq_iff_iff]
|
||||
simp [Pattern.Model.revAll_eq_true_iff, Char.isLongestRevMatchAtChain_startPos_endPos_iff_toList]
|
||||
|
||||
|
|
|
|||
|
|
@ -760,9 +760,9 @@ theorem splits_nextn_startPos (s : String) (n : Nat) :
|
|||
simpa using s.splits_startPos.nextn n
|
||||
|
||||
theorem Slice.Pos.Splits.prevn {s : Slice} {t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
|
||||
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.length - n))) (String.ofList (t₁.toList.drop (t₁.length - n)) ++ t₂) := by
|
||||
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.toList.length - n))) (String.ofList (t₁.toList.drop (t₁.toList.length - n)) ++ t₂) := by
|
||||
induction n generalizing p t₁ t₂ with
|
||||
| zero => simpa [← String.length_toList]
|
||||
| zero => simpa
|
||||
| succ n ih =>
|
||||
rw [Pos.prevn_add_one]
|
||||
split
|
||||
|
|
@ -771,16 +771,16 @@ theorem Slice.Pos.Splits.prevn {s : Slice} {t₁ t₂ : String} {p : s.Pos} (h :
|
|||
simpa [Nat.add_sub_add_right, List.take_append, List.drop_append, ← append_assoc] using ih h.prev
|
||||
|
||||
theorem Slice.splits_prevn_endPos (s : Slice) (n : Nat) :
|
||||
(s.endPos.prevn n).Splits (String.ofList (s.copy.toList.take (s.copy.length - n)))
|
||||
(String.ofList (s.copy.toList.drop (s.copy.length - n))) := by
|
||||
(s.endPos.prevn n).Splits (String.ofList (s.copy.toList.take (s.copy.toList.length - n)))
|
||||
(String.ofList (s.copy.toList.drop (s.copy.toList.length - n))) := by
|
||||
simpa using s.splits_endPos.prevn n
|
||||
|
||||
theorem Pos.Splits.prevn {s t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
|
||||
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.length - n))) (String.ofList (t₁.toList.drop (t₁.length - n)) ++ t₂) := by
|
||||
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.toList.length - n))) (String.ofList (t₁.toList.drop (t₁.toList.length - n)) ++ t₂) := by
|
||||
simpa [← splits_toSlice_iff, toSlice_prevn] using h.toSlice.prevn n
|
||||
|
||||
theorem splits_prevn_endPos (s : String) (n : Nat) :
|
||||
(s.endPos.prevn n).Splits (String.ofList (s.toList.take (s.length - n))) (String.ofList (s.toList.drop (s.length - n))) := by
|
||||
(s.endPos.prevn n).Splits (String.ofList (s.toList.take (s.toList.length - n))) (String.ofList (s.toList.drop (s.toList.length - n))) := by
|
||||
simpa using s.splits_endPos.prevn n
|
||||
|
||||
@[simp]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ theorem dropEnd_eq_sliceTo {s : Slice} {n : Nat} : s.dropEnd n = s.sliceTo (s.en
|
|||
|
||||
@[simp]
|
||||
theorem toList_copy_dropEnd {s : Slice} {n : Nat} :
|
||||
(s.dropEnd n).copy.toList = s.copy.toList.take (s.copy.length - n) := by
|
||||
(s.dropEnd n).copy.toList = s.copy.toList.take (s.copy.toList.length - n) := by
|
||||
simp [dropEnd_eq_sliceTo, (s.splits_prevn_endPos n).copy_sliceTo_eq]
|
||||
|
||||
theorem take_eq_sliceTo {s : Slice} {n : Nat} : s.take n = s.sliceTo (s.startPos.nextn n) :=
|
||||
|
|
@ -44,7 +44,7 @@ theorem takeEnd_eq_sliceFrom {s : Slice} {n : Nat} : s.takeEnd n = s.sliceFrom (
|
|||
|
||||
@[simp]
|
||||
theorem toList_copy_takeEnd {s : Slice} {n : Nat} :
|
||||
(s.takeEnd n).copy.toList = s.copy.toList.drop (s.copy.length - n) := by
|
||||
(s.takeEnd n).copy.toList = s.copy.toList.drop (s.copy.toList.length - n) := by
|
||||
simp [takeEnd_eq_sliceFrom, (s.splits_prevn_endPos n).copy_sliceFrom_eq]
|
||||
|
||||
end Slice
|
||||
|
|
@ -63,7 +63,7 @@ theorem dropEnd_toSlice {s : String} {n : Nat} : s.toSlice.dropEnd n = s.dropEnd
|
|||
|
||||
@[simp]
|
||||
theorem toList_copy_dropEnd {s : String} {n : Nat} :
|
||||
(s.dropEnd n).copy.toList = s.toList.take (s.length - n) := by
|
||||
(s.dropEnd n).copy.toList = s.toList.take (s.toList.length - n) := by
|
||||
simp [← dropEnd_toSlice]
|
||||
|
||||
@[simp]
|
||||
|
|
@ -80,7 +80,7 @@ theorem takeEnd_toSlice {s : String} {n : Nat} : s.toSlice.takeEnd n = s.takeEnd
|
|||
|
||||
@[simp]
|
||||
theorem toList_copy_takeEnd {s : String} {n : Nat} :
|
||||
(s.takeEnd n).copy.toList = s.toList.drop (s.length - n) := by
|
||||
(s.takeEnd n).copy.toList = s.toList.drop (s.toList.length - n) := by
|
||||
simp [← takeEnd_toSlice]
|
||||
|
||||
end String
|
||||
|
|
|
|||
68
src/Init/Data/String/Length.lean
Normal file
68
src/Init/Data/String/Length.lean
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Julia Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Basic
|
||||
import Init.Data.Char.Lemmas
|
||||
|
||||
public section
|
||||
|
||||
namespace String
|
||||
|
||||
/--
|
||||
Returns the length of a string in Unicode code points.
|
||||
|
||||
Examples:
|
||||
* `"".length = 0`
|
||||
* `"abc".length = 3`
|
||||
* `"L∃∀N".length = 4`
|
||||
-/
|
||||
@[extern "lean_string_length", expose, tagged_return]
|
||||
def length (b : @& String) : Nat :=
|
||||
b.toList.length
|
||||
|
||||
@[simp]
|
||||
theorem Internal.size_toArray {b : String} : (String.Internal.toArray b).size = b.length :=
|
||||
(rfl)
|
||||
|
||||
theorem length_toList {s : String} : s.toList.length = s.length := (rfl)
|
||||
|
||||
@[deprecated String.length_toList (since := "2025-10-30")]
|
||||
theorem length_data {b : String} : b.toList.length = b.length := (rfl)
|
||||
|
||||
@[simp]
|
||||
theorem length_ofList {l : List Char} : (String.ofList l).length = l.length := by
|
||||
rw [← String.length_toList, String.toList_ofList]
|
||||
|
||||
@[deprecated String.length_ofList (since := "2025-10-30")]
|
||||
theorem _root_.List.length_asString {l : List Char} : (String.ofList l).length = l.length :=
|
||||
String.length_ofList
|
||||
|
||||
@[simp] theorem length_empty : "".length = 0 := by simp [← length_toList, toList_empty]
|
||||
|
||||
@[simp]
|
||||
theorem length_singleton {c : Char} : (String.singleton c).length = 1 := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp] theorem length_push (c : Char) : (String.push s c).length = s.length + 1 := by
|
||||
simp [← length_toList]
|
||||
|
||||
@[simp] theorem length_pushn (c : Char) (n : Nat) : (pushn s c n).length = s.length + n := by
|
||||
rw [pushn_eq_repeat_push]; induction n <;> simp [Nat.repeat, Nat.add_assoc, *]
|
||||
|
||||
@[simp] theorem length_append (s t : String) : (s ++ t).length = s.length + t.length := by
|
||||
simp [← length_toList]
|
||||
|
||||
end String
|
||||
|
||||
namespace Char
|
||||
|
||||
@[deprecated String.length_singleton (since := "2026-02-12")]
|
||||
theorem length_toString (c : Char) : c.toString.length = 1 := by
|
||||
simp
|
||||
|
||||
end Char
|
||||
|
|
@ -28,7 +28,7 @@ open Slice.Pattern
|
|||
Returns a {name}`String.Slice` obtained by removing the specified number of characters (Unicode code
|
||||
points) from the start of the string.
|
||||
|
||||
If {name}`n` is greater than {lean}`s.length`, returns an empty slice.
|
||||
If {name}`n` is greater than {lean}`s.toList.length`, returns an empty slice.
|
||||
|
||||
This is a cheap operation because it does not allocate a new string to hold the result.
|
||||
To convert the result into a string, use {name}`String.Slice.copy`.
|
||||
|
|
@ -50,7 +50,7 @@ def Internal.dropImpl (s : String) (n : Nat) : String :=
|
|||
Returns a {name}`String.Slice` obtained by removing the specified number of characters (Unicode code
|
||||
points) from the end of the string.
|
||||
|
||||
If {name}`n` is greater than {lean}`s.length`, returns an empty slice.
|
||||
If {name}`n` is greater than {lean}`s.toList.length`, returns an empty slice.
|
||||
|
||||
This is a cheap operation because it does not allocate a new string to hold the result.
|
||||
To convert the result into a string, use {name}`String.Slice.copy`.
|
||||
|
|
@ -80,7 +80,7 @@ def Internal.dropRightImpl (s : String) (n : Nat) : String :=
|
|||
Returns a {name}`String.Slice` that contains the first {name}`n` characters (Unicode code points) of
|
||||
{name}`s`.
|
||||
|
||||
If {name}`n` is greater than {lean}`s.length`, returns {lean}`s.toSlice`.
|
||||
If {name}`n` is greater than {lean}`s.toList.length`, returns {lean}`s.toSlice`.
|
||||
|
||||
This is a cheap operation because it does not allocate a new string to hold the result.
|
||||
To convert the result into a string, use {name}`String.Slice.copy`.
|
||||
|
|
@ -99,7 +99,7 @@ Examples:
|
|||
Returns a {name}`String.Slice` that contains the last {name}`n` characters (Unicode code points) of
|
||||
{name}`s`.
|
||||
|
||||
If {name}`n` is greater than {lean}`s.length`, returns {lean}`s.toSlice`.
|
||||
If {name}`n` is greater than {lean}`s.toList.length`, returns {lean}`s.toSlice`.
|
||||
|
||||
This is a cheap operation because it does not allocate a new string to hold the result.
|
||||
To convert the result into a string, use {name}`String.Slice.copy`.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Init.Data.String.Search
|
|||
public import Init.Data.ToString.Basic
|
||||
import Init.Data.Iterators.Consumers.Collect
|
||||
import Init.System.Platform
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -1026,7 +1026,7 @@ and/or return data.
|
|||
partial def Handle.lines (h : Handle) : IO (Array String) := do
|
||||
let rec read (lines : Array String) := do
|
||||
let line ← h.getLine
|
||||
if line.length == 0 then
|
||||
if line.isEmpty then
|
||||
pure lines
|
||||
else if line.back == '\n' then
|
||||
let line := line.dropEnd 1 |>.copy
|
||||
|
|
@ -1780,7 +1780,7 @@ and/or return data.
|
|||
partial def lines (s : Stream) : IO (Array String) := do
|
||||
let rec read (lines : Array String) := do
|
||||
let line ← s.getLine
|
||||
if line.length == 0 then
|
||||
if line.isEmpty then
|
||||
pure lines
|
||||
else if line.back == '\n' then
|
||||
let line := line.dropEnd 1 |>.copy
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import Init.Data.String.Search
|
|||
import Init.Omega
|
||||
import Init.System.Platform
|
||||
import Init.While
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import Init.Data.Nat.Order
|
|||
import Init.Data.Order.Lemmas
|
||||
import Init.Data.Range
|
||||
import Init.While
|
||||
import Init.Data.String.Length
|
||||
|
||||
set_option linter.missingDocs true
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Init.Data.String.TakeDrop
|
|||
import Init.Data.Ord.String
|
||||
import Init.Data.Ord.UInt
|
||||
import Init.Data.String.Search
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
namespace Lean
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import Init.Data.String.TakeDrop
|
|||
import Init.Data.String.Search
|
||||
import Init.Data.ToString.Macro
|
||||
import Init.While
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
@ -114,28 +115,28 @@ def rewriteManualLinksCore (s : String) : Id (Array (Lean.Syntax.Range × String
|
|||
let pre := iter
|
||||
iter := iter.next h
|
||||
|
||||
if !lookingAt scheme pre then
|
||||
match pre.skip? scheme with
|
||||
| none =>
|
||||
out := out.push c
|
||||
continue
|
||||
|
||||
let start := pre.nextn scheme.length
|
||||
let mut iter' := start
|
||||
while h' : ¬iter'.IsAtEnd do
|
||||
let c' := iter'.get h'
|
||||
let pre' := iter'
|
||||
iter' := iter'.next h'
|
||||
if urlChar c' && ¬iter'.IsAtEnd then
|
||||
continue
|
||||
match rw (s.extract start pre') with
|
||||
| .error err =>
|
||||
errors := errors.push (⟨pre.offset, pre'.offset⟩, err)
|
||||
out := out.push c
|
||||
break
|
||||
| .ok path =>
|
||||
out := out ++ manualRoot ++ path
|
||||
out := out.push c'
|
||||
iter := iter'
|
||||
break
|
||||
| some start =>
|
||||
let mut iter' := start
|
||||
while h' : ¬iter'.IsAtEnd do
|
||||
let c' := iter'.get h'
|
||||
let pre' := iter'
|
||||
iter' := iter'.next h'
|
||||
if urlChar c' && ¬iter'.IsAtEnd then
|
||||
continue
|
||||
match rw (s.extract start pre') with
|
||||
| .error err =>
|
||||
errors := errors.push (⟨pre.offset, pre'.offset⟩, err)
|
||||
out := out.push c
|
||||
break
|
||||
| .ok path =>
|
||||
out := out ++ manualRoot ++ path
|
||||
out := out.push c'
|
||||
iter := iter'
|
||||
break
|
||||
|
||||
pure (errors, out)
|
||||
|
||||
|
|
@ -154,12 +155,6 @@ where
|
|||
c == '!' || c == '$' || c == '&' || c == '\'' || /- c == '(' || c == ')' || -/ c == '*' ||
|
||||
c == '+' || c == ',' || c == ';' || c == '='
|
||||
|
||||
/--
|
||||
Returns `true` if `goal` is a prefix of the string at the position pointed to by `iter`.
|
||||
-/
|
||||
lookingAt (goal : String) {s : String} (iter : s.Pos) : Bool :=
|
||||
String.Pos.Raw.substrEq s iter.offset goal 0 goal.rawEndPos.byteIdx
|
||||
|
||||
/--
|
||||
Rewrites Lean reference manual links in `docstring` to point at the reference manual.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ prelude
|
|||
public import Lean.DocString.Types
|
||||
public import Init.Data.String.TakeDrop
|
||||
public import Init.Data.String.Search
|
||||
public import Init.Data.String.Length
|
||||
import Init.Data.ToString.Macro
|
||||
import Init.While
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public import Lean.Util.Sorry
|
|||
import Init.Data.String.Search
|
||||
import Init.Data.Format.Macro
|
||||
import Init.Data.Iterators.Consumers.Collect
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
@ -688,7 +689,7 @@ def inlineExpr (e : Expr) (maxInlineLength := 30) : MessageData :=
|
|||
(fun ctx => do
|
||||
let msg := MessageData.ofExpr e
|
||||
let render ← msg.formatExpensively ctx
|
||||
if render.length > maxInlineLength || render.any (· == '\n') then
|
||||
if render.positions.length > maxInlineLength || render.any (· == '\n') then
|
||||
return indentD msg ++ "\n"
|
||||
else
|
||||
return " `" ++ msg ++ "` ")
|
||||
|
|
@ -704,7 +705,7 @@ def inlineExprTrailing (e : Expr) (maxInlineLength := 30) : MessageData :=
|
|||
(fun ctx => do
|
||||
let msg := MessageData.ofExpr e
|
||||
let render ← msg.formatExpensively ctx
|
||||
if render.length > maxInlineLength || render.any (· == '\n') then
|
||||
if render.positions.length > maxInlineLength || render.any (· == '\n') then
|
||||
return indentD msg
|
||||
else
|
||||
return " `" ++ msg ++ "`")
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ def mkSpecTheoremName (env : Environment) (instName : Name) (privateSpecs : Bool
|
|||
if privateSpecs then mkPrivateName env thmName else thmName
|
||||
|
||||
def startsWithFollowedByNumber (s p : String) : Bool :=
|
||||
s.startsWith p && (s.drop p.length).isNat
|
||||
(s.dropPrefix? p).any (·.isNat)
|
||||
|
||||
def isSpecThmLikeSuffix (fieldName : Name) (s : String) : Bool :=
|
||||
s == s!"{fieldName}_spec" || startsWithFollowedByNumber s s!"{fieldName}_spec_"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Init.Data.String.TakeDrop
|
|||
import Init.Data.List.Monadic
|
||||
import Init.Data.Option.BasicAux
|
||||
import Init.Data.ToString.Macro
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module
|
|||
prelude
|
||||
public import Lean.Server.Rpc.Basic
|
||||
import Init.Data.Array.GetLit
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ public import Std.Net
|
|||
public import Std.Http.Internal
|
||||
public import Std.Http.Data.URI.Encoding
|
||||
public import Init.Data.String.Search
|
||||
public import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ prelude
|
|||
public import Std.Internal.Parsec.Basic
|
||||
public import Init.Data.String.Slice
|
||||
public import Init.Data.String.Termination
|
||||
import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module
|
|||
prelude
|
||||
public import Std.Time.Date
|
||||
public import Init.Data.String.Basic
|
||||
public import Init.Data.String.Length
|
||||
|
||||
public section
|
||||
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ inductive Modifier
|
|||
It takes a constructor function to build the `Modifier` and a classify function that maps the pattern length to a specific type.
|
||||
-/
|
||||
private def parseMod (constructor : α → Modifier) (classify : Nat → Option α) (p : String) : Parser Modifier :=
|
||||
let len := p.length
|
||||
let len := p.positions.length
|
||||
match classify len with
|
||||
| some res => pure (constructor res)
|
||||
| none => fail s!"invalid quantity of characters for '{p.front}'"
|
||||
|
|
@ -617,16 +617,16 @@ private def specParse (s : String) : Except String FormatString :=
|
|||
-- Pretty printer
|
||||
|
||||
private def leftPad (n : Nat) (a : Char) (s : String) : String :=
|
||||
"".pushn a (n - s.length) ++ s
|
||||
"".pushn a (n - s.positions.length) ++ s
|
||||
|
||||
private def rightPad (n : Nat) (a : Char) (s : String) : String :=
|
||||
s ++ "".pushn a (n - s.length)
|
||||
s ++ "".pushn a (n - s.positions.length)
|
||||
|
||||
private def pad (size : Nat) (n : Int) (cut : Bool := false) : String :=
|
||||
let (sign, n) := if n < 0 then ("-", -n) else ("", n)
|
||||
|
||||
let numStr := toString n
|
||||
if numStr.length > size then
|
||||
if numStr.positions.length > size then
|
||||
sign ++ if cut then numStr.drop (numStr.length - size) |>.copy else numStr
|
||||
else
|
||||
sign ++ leftPad size '0' numStr
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module
|
|||
prelude
|
||||
public import Init.Data.String.TakeDrop
|
||||
public import Init.Data.String.Search
|
||||
public import Init.Data.String.Length
|
||||
|
||||
namespace Lake
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public import Init.Data.ToString.Basic
|
|||
import Init.Data.UInt.Lemmas
|
||||
import Init.Data.String.Basic
|
||||
import Init.Data.Nat.Fold
|
||||
import Init.Data.String.Length
|
||||
|
||||
namespace Lake
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import Init.Data.String.TakeDrop
|
|||
import Lean.Data.Trie
|
||||
import Init.Data.String.Search
|
||||
import Init.Omega
|
||||
import Init.Data.String.Length
|
||||
|
||||
/-! # Version
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue