chore: move some material out of Init.Data.String.Basic (#10893)

This PR splits some low-hanging fruit out of `Init.Data.String.Basic`:
basic material about `String.Pos.Raw`, `String.Substrig`, and
`String.Iterator`.

More splitting required and the remaining material is quite unorganized,
but it's a start.
This commit is contained in:
Markus Himmel 2025-10-22 18:31:08 +02:00 committed by GitHub
parent 08bc333705
commit b5dc11e8d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1456 additions and 1325 deletions

View file

@ -16,3 +16,6 @@ public import Init.Data.String.Bootstrap
public import Init.Data.String.Slice
public import Init.Data.String.Pattern
public import Init.Data.String.Stream
public import Init.Data.String.PosRaw
public import Init.Data.String.Substring
public import Init.Data.String.TakeDrop

File diff suppressed because it is too large Load diff

View file

@ -9,9 +9,24 @@ prelude
import all Init.Data.ByteArray.Basic
public import Init.Data.String.Basic
import all Init.Data.String.Basic
public import Init.Data.String.Iterator
import all Init.Data.String.Iterator
public import Init.Data.String.Substring
public section
namespace Substring
/--
Returns an iterator into the underlying string, at the substring's starting position. The ending
position is discarded, so the iterator alone cannot be used to determine whether its current
position is within the original substring.
-/
@[inline] def toIterator : Substring → String.Iterator
| ⟨s, b, _⟩ => ⟨s, b⟩
end Substring
namespace String
/--

View file

@ -0,0 +1,206 @@
/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura, Mario Carneiro
-/
module
prelude
public import Init.Data.String.Basic
/-!
# `String.Iterator`
This file contains `String.Iterator`, an outgoing API to be replaced by the iterator framework in
a future release.
-/
public section
namespace String
/--
An iterator over the characters (Unicode code points) in a `String`. Typically created by
`String.iter`.
String iterators pair a string with a valid byte index. This allows efficient character-by-character
processing of strings while avoiding the need to manually ensure that byte indices are used with the
correct strings.
An iterator is *valid* if the position `i` is *valid* for the string `s`, meaning `0 ≤ i ≤ s.rawEndPos`
and `i` lies on a UTF8 byte boundary. If `i = s.rawEndPos`, the iterator is at the end of the string.
Most operations on iterators return unspecified values if the iterator is not valid. The functions
in the `String.Iterator` API rule out the creation of invalid iterators, with two exceptions:
- `Iterator.next iter` is invalid if `iter` is already at the end of the string (`iter.atEnd` is
`true`), and
- `Iterator.forward iter n`/`Iterator.nextn iter n` is invalid if `n` is strictly greater than the
number of remaining characters.
-/
structure Iterator where
/-- The string being iterated over. -/
s : String
/-- The current UTF-8 byte position in the string `s`.
This position is not guaranteed to be valid for the string. If the position is not valid, then the
current character is `(default : Char)`, similar to `String.get` on an invalid position.
-/
i : Pos.Raw
deriving DecidableEq, Inhabited
/-- Creates an iterator at the beginning of the string. -/
@[inline] def mkIterator (s : String) : Iterator :=
⟨s, 0⟩
@[inherit_doc mkIterator]
abbrev iter := mkIterator
/--
The size of a string iterator is the number of bytes remaining.
Recursive functions that iterate towards the end of a string will typically decrease this measure.
-/
instance : SizeOf String.Iterator where
sizeOf i := i.1.utf8ByteSize - i.2.byteIdx
theorem Iterator.sizeOf_eq (i : String.Iterator) : sizeOf i = i.1.utf8ByteSize - i.2.byteIdx :=
rfl
namespace Iterator
@[inline, inherit_doc Iterator.s]
def toString := Iterator.s
/--
The number of UTF-8 bytes remaining in the iterator.
-/
@[inline] def remainingBytes : Iterator → Nat
| ⟨s, i⟩ => s.rawEndPos.byteIdx - i.byteIdx
@[inline, inherit_doc Iterator.i]
def pos := Iterator.i
/--
Gets the character at the iterator's current position.
A run-time bounds check is performed. Use `String.Iterator.curr'` to avoid redundant bounds checks.
If the position is invalid, returns `(default : Char)`.
-/
@[inline] def curr : Iterator → Char
| ⟨s, i⟩ => i.get s
/--
Moves the iterator's position forward by one character, unconditionally.
It is only valid to call this function if the iterator is not at the end of the string (i.e.
if `Iterator.atEnd` is `false`); otherwise, the resulting iterator will be invalid.
-/
@[inline] def next : Iterator → Iterator
| ⟨s, i⟩ => ⟨s, i.next s⟩
/--
Moves the iterator's position backward by one character, unconditionally.
The position is not changed if the iterator is at the beginning of the string.
-/
@[inline] def prev : Iterator → Iterator
| ⟨s, i⟩ => ⟨s, i.prev s⟩
/--
Checks whether the iterator is past its string's last character.
-/
@[inline] def atEnd : Iterator → Bool
| ⟨s, i⟩ => i.byteIdx ≥ s.rawEndPos.byteIdx
/--
Checks whether the iterator is at or before the string's last character.
-/
@[inline] def hasNext : Iterator → Bool
| ⟨s, i⟩ => i.byteIdx < s.rawEndPos.byteIdx
/--
Checks whether the iterator is after the beginning of the string.
-/
@[inline] def hasPrev : Iterator → Bool
| ⟨_, i⟩ => i.byteIdx > 0
/--
Gets the character at the iterator's current position.
The proof of `it.hasNext` ensures that there is, in fact, a character at the current position. This
function is faster that `String.Iterator.curr` due to avoiding a run-time bounds check.
-/
@[inline] def curr' (it : Iterator) (h : it.hasNext) : Char :=
match it with
| ⟨s, i⟩ => i.get' s (by simpa only [hasNext, rawEndPos, decide_eq_true_eq, Pos.Raw.atEnd, ge_iff_le, Nat.not_le] using h)
/--
Moves the iterator's position forward by one character, unconditionally.
The proof of `it.hasNext` ensures that there is, in fact, a position that's one character forwards.
This function is faster that `String.Iterator.next` due to avoiding a run-time bounds check.
-/
@[inline] def next' (it : Iterator) (h : it.hasNext) : Iterator :=
match it with
| ⟨s, i⟩ => ⟨s, i.next' s (by simpa only [hasNext, rawEndPos, decide_eq_true_eq, Pos.Raw.atEnd, ge_iff_le, Nat.not_le] using h)⟩
/--
Replaces the current character in the string.
Does nothing if the iterator is at the end of the string. If both the replacement character and the
replaced character are 7-bit ASCII characters and the string is not shared, then it is updated
in-place and not copied.
-/
@[inline] def setCurr : Iterator → Char → Iterator
| ⟨s, i⟩, c => ⟨i.set s c, i⟩
/--
Moves the iterator's position to the end of the string, just past the last character.
-/
@[inline] def toEnd : Iterator → Iterator
| ⟨s, _⟩ => ⟨s, s.rawEndPos⟩
/--
Extracts the substring between the positions of two iterators. The first iterator's position is the
start of the substring, and the second iterator's position is the end.
Returns the empty string if the iterators are for different strings, or if the position of the first
iterator is past the position of the second iterator.
-/
@[inline] def extract : Iterator → Iterator → String
| ⟨s₁, b⟩, ⟨s₂, e⟩ =>
if s₁ ≠ s₂ || b > e then ""
else b.extract s₁ e
/--
Moves the iterator's position forward by the specified number of characters.
The resulting iterator is only valid if the number of characters to skip is less than or equal
to the number of characters left in the iterator.
-/
def forward : Iterator → Nat → Iterator
| it, 0 => it
| it, n+1 => forward it.next n
/--
The remaining characters in an iterator, as a string.
-/
@[inline] def remainingToString : Iterator → String
| ⟨s, i⟩ => i.extract s s.rawEndPos
@[inherit_doc forward]
def nextn : Iterator → Nat → Iterator
| it, 0 => it
| it, i+1 => nextn it.next i
/--
Moves the iterator's position back by the specified number of characters, stopping at the beginning
of the string.
-/
def prevn : Iterator → Nat → Iterator
| it, 0 => it
| it, i+1 => prevn it.prev i
end Iterator
end String

View file

@ -0,0 +1,353 @@
/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura, Mario Carneiro
-/
module
prelude
public import Init.Data.String.Bootstrap
public import Init.Data.ByteArray.Basic
/-!
# Arithmetic of `String.Pos.Raw`
This file contains basic theory about which does not actually need to know anything about strings
and therefore does not depend on `Init.Data.String.Decode`.
-/
public section
namespace String
attribute [ext] String.Pos.Raw
instance : HSub String.Pos.Raw String String.Pos.Raw where
hSub p s := { byteIdx := p.byteIdx - s.utf8ByteSize }
instance : HSub String.Pos.Raw Char String.Pos.Raw where
hSub p c := { byteIdx := p.byteIdx - c.utf8Size }
@[export lean_string_pos_sub]
def Pos.Internal.subImpl : String.Pos.Raw → String.Pos.Raw → String.Pos.Raw :=
fun p₁ p₂ => ⟨p₁.byteIdx - p₂.byteIdx⟩
instance : HAdd String.Pos.Raw Char String.Pos.Raw where
hAdd p c := { byteIdx := p.byteIdx + c.utf8Size }
instance : HAdd Char String.Pos.Raw String.Pos.Raw where
hAdd c p := { byteIdx := c.utf8Size + p.byteIdx }
instance : HAdd String String.Pos.Raw String.Pos.Raw where
hAdd s p := { byteIdx := s.utf8ByteSize + p.byteIdx }
instance : HAdd String.Pos.Raw String String.Pos.Raw where
hAdd p s := { byteIdx := p.byteIdx + s.utf8ByteSize }
instance : LE String.Pos.Raw where
le p₁ p₂ := p₁.byteIdx ≤ p₂.byteIdx
instance : LT String.Pos.Raw where
lt p₁ p₂ := p₁.byteIdx < p₂.byteIdx
instance (p₁ p₂ : String.Pos.Raw) : Decidable (p₁ ≤ p₂) :=
inferInstanceAs (Decidable (p₁.byteIdx ≤ p₂.byteIdx))
instance (p₁ p₂ : String.Pos.Raw) : Decidable (p₁ < p₂) :=
inferInstanceAs (Decidable (p₁.byteIdx < p₂.byteIdx))
instance : Min String.Pos.Raw := minOfLe
instance : Max String.Pos.Raw := maxOfLe
@[simp]
theorem Pos.Raw.byteIdx_sub_char {p : Pos.Raw} {c : Char} : (p - c).byteIdx = p.byteIdx - c.utf8Size := rfl
@[simp]
theorem Pos.Raw.byteIdx_sub_string {p : Pos.Raw} {s : String} : (p - s).byteIdx = p.byteIdx - s.utf8ByteSize := rfl
@[simp]
theorem Pos.Raw.byteIdx_add_string {p : Pos.Raw} {s : String} : (p + s).byteIdx = p.byteIdx + s.utf8ByteSize := rfl
@[simp]
theorem Pos.Raw.byteIdx_string_add {s : String} {p : Pos.Raw} : (s + p).byteIdx = s.utf8ByteSize + p.byteIdx := rfl
@[simp]
theorem Pos.Raw.byteIdx_add_char {p : Pos.Raw} {c : Char} : (p + c).byteIdx = p.byteIdx + c.utf8Size := rfl
@[simp]
theorem Pos.Raw.byteIdx_char_add {c : Char} {p : Pos.Raw} : (c + p).byteIdx = c.utf8Size + p.byteIdx := rfl
theorem Pos.Raw.le_iff {i₁ i₂ : Pos.Raw} : i₁ ≤ i₂ ↔ i₁.byteIdx ≤ i₂.byteIdx := .rfl
theorem Pos.Raw.lt_iff {i₁ i₂ : Pos.Raw} : i₁ < i₂ ↔ i₁.byteIdx < i₂.byteIdx := .rfl
@[simp]
theorem Pos.Raw.byteIdx_zero : (0 : Pos.Raw).byteIdx = 0 := rfl
/--
Returns the size of the byte slice delineated by the positions `lo` and `hi`.
-/
@[expose, inline]
def Pos.Raw.byteDistance (lo hi : Pos.Raw) : Nat :=
hi.byteIdx - lo.byteIdx
theorem Pos.Raw.byteDistance_eq {lo hi : Pos.Raw} : lo.byteDistance hi = hi.byteIdx - lo.byteIdx :=
(rfl)
@[simp]
theorem rawEndPos_empty : "".rawEndPos = 0 := rfl
@[deprecated rawEndPos_empty (since := "2025-10-20")]
theorem endPos_empty : "".rawEndPos = 0 := rfl
/--
Accesses the indicated byte in the UTF-8 encoding of a string.
At runtime, this function is implemented by efficient, constant-time code.
-/
@[extern "lean_string_get_byte_fast", expose]
def getUTF8Byte (s : @& String) (p : Pos.Raw) (h : p < s.rawEndPos) : UInt8 :=
s.bytes[p.byteIdx]
@[deprecated getUTF8Byte (since := "2025-10-01"), extern "lean_string_get_byte_fast", expose]
abbrev getUtf8Byte (s : String) (p : Pos.Raw) (h : p < s.rawEndPos) : UInt8 :=
s.getUTF8Byte p h
protected theorem Pos.Raw.le_trans {a b c : Pos.Raw} : a ≤ b → b ≤ c → a ≤ c := by
simpa [le_iff] using Nat.le_trans
protected theorem Pos.Raw.lt_of_lt_of_le {a b c : Pos.Raw} : a < b → b ≤ c → a < c := by
simpa [le_iff, lt_iff] using Nat.lt_of_lt_of_le
/--
Offsets `p` by `offset` on the left. This is not an `HAdd` instance because it should be a
relatively rare operation, so we use a name to make accidental use less likely. To offset a position
by the size of a character character `c` or string `s`, you can use `c + p` resp. `s + p`.
This should be seen as an operation that converts relative positions into absolute positions.
See also `Pos.Raw.increaseBy`, which is an "advancing" operation.
-/
@[expose, inline]
def Pos.Raw.offsetBy (p : Pos.Raw) (offset : Pos.Raw) : Pos.Raw where
byteIdx := offset.byteIdx + p.byteIdx
@[simp]
theorem Pos.Raw.byteIdx_offsetBy {p : Pos.Raw} {offset : Pos.Raw} :
(p.offsetBy offset).byteIdx = offset.byteIdx + p.byteIdx := (rfl)
theorem Pos.Raw.offsetBy_assoc {p q r : Pos.Raw} :
(p.offsetBy q).offsetBy r = p.offsetBy (q.offsetBy r) := by
ext
simp [Nat.add_assoc]
@[simp]
theorem Pos.Raw.offsetBy_zero_left {p : Pos.Raw} : (0 : Pos.Raw).offsetBy p = p := by
ext
simp
/--
Decreases `p` by `offset`. This is not an `HSub` instance because it should be a relatively
rare operation, so we use a name to make accidental use less likely. To unoffset a position
by the size of a character `c` or string `s`, you can use `p - c` resp. `p - s`.
This should be seen as an operation that converts absolute positions into relative positions.
See also `Pos.Raw.decreaseBy`, which is an "unadvancing" operation.
-/
@[expose, inline]
def Pos.Raw.unoffsetBy (p : Pos.Raw) (offset : Pos.Raw) : Pos.Raw where
byteIdx := p.byteIdx - offset.byteIdx
@[simp]
theorem Pos.Raw.byteIdx_unoffsetBy {p : Pos.Raw} {offset : Pos.Raw} :
(p.unoffsetBy offset).byteIdx = p.byteIdx - offset.byteIdx := (rfl)
theorem Pos.Raw.offsetBy_unoffsetBy_of_le {p : Pos.Raw} {q : Pos.Raw} (h : q ≤ p) :
(p.unoffsetBy q).offsetBy q = p := by
ext
simp_all [le_iff]
@[simp]
theorem Pos.Raw.unoffsetBy_offsetBy {p q : Pos.Raw} : (p.offsetBy q).unoffsetBy q = p := by
ext
simp
@[simp]
theorem Pos.Raw.eq_zero_iff {p : Pos.Raw} : p = 0 ↔ p.byteIdx = 0 :=
Pos.Raw.ext_iff
/--
Advances `p` by `n` bytes. This is not an `HAdd` instance because it should be a relatively
rare operation, so we use a name to make accidental use less likely. To add the size of a
character `c` or string `s` to a raw position `p`, you can use `p + c` resp. `p + s`.
This should be seen as an "advance" or "skip".
See also `Pos.Raw.offsetBy`, which turns relative positions into absolute positions.
-/
@[expose, inline]
def Pos.Raw.increaseBy (p : Pos.Raw) (n : Nat) : Pos.Raw where
byteIdx := p.byteIdx + n
@[simp]
theorem Pos.Raw.byteIdx_increaseBy {p : Pos.Raw} {n : Nat} :
(p.increaseBy n).byteIdx = p.byteIdx + n := (rfl)
/--
Move the position `p` back by `n` bytes. This is not an `HSub` instance because it should be a
relatively rare operation, so we use a name to make accidental use less likely. To remove the size
of a character `c` or string `s` from a raw position `p`, you can use `p - c` resp. `p - s`.
This should be seen as the inverse of an "advance" or "skip".
See also `Pos.Raw.unoffsetBy`, which turns absolute positions into relative positions.
-/
@[expose, inline]
def Pos.Raw.decreaseBy (p : Pos.Raw) (n : Nat) : Pos.Raw where
byteIdx := p.byteIdx - n
@[simp]
theorem Pos.Raw.byteIdx_decreaseBy {p : Pos.Raw} {n : Nat} :
(p.decreaseBy n).byteIdx = p.byteIdx - n := (rfl)
theorem Pos.Raw.increaseBy_charUtf8Size {p : Pos.Raw} {c : Char} :
p.increaseBy c.utf8Size = p + c := by
simp [Pos.Raw.ext_iff]
/-- Increases the byte offset of the position by `1`. Not to be confused with `ValidPos.next`. -/
@[inline, expose]
def Pos.Raw.inc (p : Pos.Raw) : Pos.Raw :=
⟨p.byteIdx + 1⟩
@[simp]
theorem Pos.Raw.byteIdx_inc {p : Pos.Raw} : p.inc.byteIdx = p.byteIdx + 1 := (rfl)
/-- Decreases the byte offset of the position by `1`. Not to be confused with `ValidPos.prev`. -/
@[inline, expose]
def Pos.Raw.dec (p : Pos.Raw) : Pos.Raw :=
⟨p.byteIdx - 1⟩
@[simp]
theorem Pos.Raw.byteIdx_dec {p : Pos.Raw} : p.dec.byteIdx = p.byteIdx - 1 := (rfl)
@[simp]
theorem Pos.Raw.le_refl {p : Pos.Raw} : p ≤ p := by simp [le_iff]
theorem Pos.Raw.lt_inc {p : Pos.Raw} : p < p.inc := by simp [lt_iff]
theorem Pos.Raw.le_of_lt {p q : Pos.Raw} : p < q → p ≤ q := by simpa [lt_iff, le_iff] using Nat.le_of_lt
theorem Pos.Raw.inc_le {p q : Pos.Raw} : p.inc ≤ q ↔ p < q := by simpa [lt_iff, le_iff] using Nat.succ_le
theorem Pos.Raw.lt_of_le_of_lt {a b c : Pos.Raw} : a ≤ b → b < c → a < c := by
simpa [le_iff, lt_iff] using Nat.lt_of_le_of_lt
theorem Pos.Raw.ne_of_lt {a b : Pos.Raw} : a < b → a ≠ b := by
simpa [lt_iff, Pos.Raw.ext_iff] using Nat.ne_of_lt
@[deprecated Pos.Raw.lt_iff (since := "2025-10-10")]
theorem pos_lt_eq (p₁ p₂ : Pos.Raw) : (p₁ < p₂) = (p₁.1 < p₂.1) :=
propext Pos.Raw.lt_iff
@[deprecated Pos.Raw.byteIdx_add_char (since := "2025-10-10")]
theorem pos_add_char (p : Pos.Raw) (c : Char) : (p + c).byteIdx = p.byteIdx + c.utf8Size :=
Pos.Raw.byteIdx_add_char
protected theorem Pos.Raw.ne_zero_of_lt : {a b : Pos.Raw} → a < b → b ≠ 0
| _, _, hlt, rfl => Nat.not_lt_zero _ hlt
/--
Returns either `p₁` or `p₂`, whichever has the least byte index.
-/
protected abbrev Pos.Raw.min (p₁ p₂ : Pos.Raw) : Pos.Raw :=
{ byteIdx := p₁.byteIdx.min p₂.byteIdx }
@[export lean_string_pos_min]
def Pos.Raw.Internal.minImpl (p₁ p₂ : Pos.Raw) : Pos.Raw :=
Pos.Raw.min p₁ p₂
namespace Pos.Raw
theorem byteIdx_mk (n : Nat) : byteIdx ⟨n⟩ = n := rfl
@[simp] theorem mk_zero : ⟨0⟩ = (0 : Pos.Raw) := rfl
@[simp] theorem mk_byteIdx (p : Pos.Raw) : ⟨p.byteIdx⟩ = p := rfl
@[deprecated byteIdx_offsetBy (since := "2025-10-08")]
theorem add_byteIdx {p₁ p₂ : Pos.Raw} : (p₂.offsetBy p₁).byteIdx = p₁.byteIdx + p₂.byteIdx := by
simp
@[deprecated byteIdx_offsetBy (since := "2025-10-08")]
theorem add_eq {p₁ p₂ : Pos.Raw} : p₂.offsetBy p₁ = ⟨p₁.byteIdx + p₂.byteIdx⟩ := rfl
@[deprecated byteIdx_unoffsetBy (since := "2025-10-08")]
theorem sub_byteIdx (p₁ p₂ : Pos.Raw) : (p₁.unoffsetBy p₂).byteIdx = p₁.byteIdx - p₂.byteIdx := rfl
@[deprecated byteIdx_add_char (since := "2025-10-10")]
theorem addChar_byteIdx (p : Pos.Raw) (c : Char) : (p + c).byteIdx = p.byteIdx + c.utf8Size :=
byteIdx_add_char
theorem add_char_eq (p : Pos.Raw) (c : Char) : p + c = ⟨p.byteIdx + c.utf8Size⟩ := rfl
@[deprecated add_char_eq (since := "2025-10-10")]
theorem addChar_eq (p : Pos.Raw) (c : Char) : p + c = ⟨p.byteIdx + c.utf8Size⟩ :=
add_char_eq p c
theorem byteIdx_zero_add_char (c : Char) : ((0 : Pos.Raw) + c).byteIdx = c.utf8Size := by
simp only [byteIdx_add_char, byteIdx_zero, Nat.zero_add]
@[deprecated byteIdx_zero_add_char (since := "2025-10-10")]
theorem zero_addChar_byteIdx (c : Char) : ((0 : Pos.Raw) + c).byteIdx = c.utf8Size :=
byteIdx_zero_add_char c
theorem zero_add_char_eq (c : Char) : (0 : Pos.Raw) + c = ⟨c.utf8Size⟩ := by rw [← byteIdx_zero_add_char]
@[deprecated zero_add_char_eq (since := "2025-10-10")]
theorem zero_addChar_eq (c : Char) : (0 : Pos.Raw) + c = ⟨c.utf8Size⟩ :=
zero_add_char_eq c
theorem add_char_right_comm (p : Pos.Raw) (c₁ c₂ : Char) : p + c₁ + c₂ = p + c₂ + c₁ := by
simpa [Pos.Raw.ext_iff] using Nat.add_right_comm ..
@[deprecated add_char_right_comm (since := "2025-10-10")]
theorem addChar_right_comm (p : Pos.Raw) (c₁ c₂ : Char) : p + c₁ + c₂ = p + c₂ + c₁ :=
add_char_right_comm p c₁ c₂
theorem ne_of_gt {i₁ i₂ : Pos.Raw} (h : i₁ < i₂) : i₂ ≠ i₁ := (ne_of_lt h).symm
@[deprecated byteIdx_add_string (since := "2025-10-10")]
theorem byteIdx_addString (p : Pos.Raw) (s : String) :
(p + s).byteIdx = p.byteIdx + s.utf8ByteSize := byteIdx_add_string
@[deprecated byteIdx_addString (since := "2025-03-18")]
abbrev addString_byteIdx := @byteIdx_add_string
theorem addString_eq (p : Pos.Raw) (s : String) : p + s = ⟨p.byteIdx + s.utf8ByteSize⟩ := rfl
theorem byteIdx_zero_add_string (s : String) : ((0 : Pos.Raw) + s).byteIdx = s.utf8ByteSize := by
simp only [byteIdx_add_string, byteIdx_zero, Nat.zero_add]
@[deprecated byteIdx_zero_add_string (since := "2025-10-10")]
theorem byteIdx_zero_addString (s : String) : ((0 : Pos.Raw) + s).byteIdx = s.utf8ByteSize :=
byteIdx_zero_add_string s
@[deprecated byteIdx_zero_addString (since := "2025-03-18")]
abbrev zero_addString_byteIdx := @byteIdx_zero_add_string
theorem zero_add_string_eq (s : String) : (0 : Pos.Raw) + s = ⟨s.utf8ByteSize⟩ := by
rw [← byteIdx_zero_add_string]
@[deprecated zero_add_string_eq (since := "2025-10-10")]
theorem zero_addString_eq (s : String) : (0 : Pos.Raw) + s = ⟨s.utf8ByteSize⟩ :=
zero_add_string_eq s
@[simp] theorem mk_le_mk {i₁ i₂ : Nat} : Pos.Raw.mk i₁ ≤ Pos.Raw.mk i₂ ↔ i₁ ≤ i₂ := .rfl
@[simp] theorem mk_lt_mk {i₁ i₂ : Nat} : Pos.Raw.mk i₁ < Pos.Raw.mk i₂ ↔ i₁ < i₂ := .rfl
end Pos.Raw
end String

View file

@ -6,7 +6,8 @@ Author: Leonardo de Moura, Mario Carneiro
module
prelude
public import Init.Data.String.Basic
public import Init.Data.String.Substring
public import Init.Data.String.Iterator
public section

View file

@ -0,0 +1,521 @@
/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura, Mario Carneiro
-/
module
prelude
public import Init.Data.String.Basic
/-!
# The `Substring` type
This file contains API for `Substring` type, which is a legacy API that will be replaced by the
safer variant `String.Slice`.
-/
public section
namespace Substring
/--
Checks whether a substring is empty.
A substring is empty if its start and end positions are the same.
-/
@[inline] def isEmpty (ss : Substring) : Bool :=
ss.bsize == 0
@[export lean_substring_isempty]
def Internal.isEmptyImpl (ss : Substring) : Bool :=
Substring.isEmpty ss
/--
Copies the region of the underlying string pointed to by a substring into a fresh string.
-/
@[inline] def toString : Substring → String
| ⟨s, b, e⟩ => b.extract s e
@[export lean_substring_tostring]
def Internal.toStringImpl : Substring → String :=
Substring.toString
/--
Returns the character at the given position in the substring.
The position is relative to the substring, rather than the underlying string, and no bounds checking
is performed with respect to the substring's end position. If the relative position is not a valid
position in the underlying string, the fallback value `(default : Char)`, which is `'A'`, is
returned. Does not panic.
-/
@[inline] def get : Substring → String.Pos.Raw → Char
| ⟨s, b, _⟩, p => (p.offsetBy b).get s
@[export lean_substring_get]
def Internal.getImpl : Substring → String.Pos.Raw → Char :=
Substring.get
/--
Returns the next position in a substring after the given position. If the position is at the end of
the substring, it is returned unmodified.
Both the input position and the returned position are interpreted relative to the substring's start
position, not the underlying string.
-/
@[inline] def next : Substring → String.Pos.Raw → String.Pos.Raw
| ⟨s, b, e⟩, p =>
let absP := p.offsetBy b
if absP = e then p else { byteIdx := (absP.next s).byteIdx - b.byteIdx }
theorem lt_next (s : Substring) (i : String.Pos.Raw) (h : i.1 < s.bsize) :
i.1 < (s.next i).1 := by
simp [next]; rw [if_neg ?a]
case a =>
refine mt (congrArg String.Pos.Raw.byteIdx) (Nat.ne_of_lt ?_)
exact (Nat.add_comm .. ▸ Nat.add_lt_of_lt_sub h :)
apply Nat.lt_sub_of_add_lt
rw [Nat.add_comm]; apply String.Pos.Raw.lt_next
/--
Returns the previous position in a substring, just prior to the given position. If the position is
at the beginning of the substring, it is returned unmodified.
Both the input position and the returned position are interpreted relative to the substring's start
position, not the underlying string.
-/
@[inline] def prev : Substring → String.Pos.Raw → String.Pos.Raw
| ⟨s, b, _⟩, p =>
let absP := p.offsetBy b
if absP = b then p else { byteIdx := (absP.prev s).byteIdx - b.byteIdx }
@[export lean_substring_prev]
def Internal.prevImpl : Substring → String.Pos.Raw → String.Pos.Raw :=
Substring.prev
/--
Returns the position that's the specified number of characters forward from the given position in a
substring. If the end position of the substring is reached, it is returned.
Both the input position and the returned position are interpreted relative to the substring's start
position, not the underlying string.
-/
def nextn : Substring → Nat → String.Pos.Raw → String.Pos.Raw
| _, 0, p => p
| ss, i+1, p => ss.nextn i (ss.next p)
/--
Returns the position that's the specified number of characters prior to the given position in a
substring. If the start position of the substring is reached, it is returned.
Both the input position and the returned position are interpreted relative to the substring's start
position, not the underlying string.
-/
def prevn : Substring → Nat → String.Pos.Raw → String.Pos.Raw
| _, 0, p => p
| ss, i+1, p => ss.prevn i (ss.prev p)
/--
Returns the first character in the substring.
If the substring is empty, but the substring's start position is a valid position in the underlying
string, then the character at the start position is returned. If the substring's start position is
not a valid position in the string, the fallback value `(default : Char)`, which is `'A'`, is
returned. Does not panic.
-/
@[inline, expose] def front (s : Substring) : Char :=
s.get 0
@[export lean_substring_front]
def Internal.frontImpl : Substring → Char :=
Substring.front
/--
Returns the substring-relative position of the first occurrence of `c` in `s`, or `s.bsize` if `c`
doesn't occur.
-/
@[inline] def posOf (s : Substring) (c : Char) : String.Pos.Raw :=
match s with
| ⟨s, b, e⟩ => { byteIdx := (String.posOfAux s c e b).byteIdx - b.byteIdx }
/--
Removes the specified number of characters (Unicode code points) from the beginning of a substring
by advancing its start position.
If the substring's end position is reached, the start position is not advanced past it.
-/
@[inline] def drop : Substring → Nat → Substring
| ss@⟨s, b, e⟩, n => ⟨s, (ss.nextn n 0).offsetBy b, e⟩
@[export lean_substring_drop]
def Internal.dropImpl : Substring → Nat → Substring :=
Substring.drop
/--
Removes the specified number of characters (Unicode code points) from the end of a substring
by moving its end position towards its start position.
If the substring's start position is reached, the end position is not retracted past it.
-/
@[inline] def dropRight : Substring → Nat → Substring
| ss@⟨s, b, _⟩, n => ⟨s, b, (ss.prevn n ⟨ss.bsize⟩).offsetBy b⟩
/--
Retains only the specified number of characters (Unicode code points) at the beginning of a
substring, by moving its end position towards its start position.
If the substring's start position is reached, the end position is not retracted past it.
-/
@[inline] def take : Substring → Nat → Substring
| ss@⟨s, b, _⟩, n => ⟨s, b, (ss.nextn n 0).offsetBy b⟩
/--
Retains only the specified number of characters (Unicode code points) at the end of a substring, by
moving its start position towards its end position.
If the substring's end position is reached, the start position is not advanced past it.
-/
@[inline] def takeRight : Substring → Nat → Substring
| ss@⟨s, b, e⟩, n => ⟨s, (ss.prevn n ⟨ss.bsize⟩).offsetBy b, e⟩
/--
Checks whether a position in a substring is precisely equal to its ending position.
The position is understood relative to the substring's starting position, rather than the underlying
string's starting position.
-/
@[inline] def atEnd : Substring → String.Pos.Raw → Bool
| ⟨_, b, e⟩, p => p.offsetBy b == e
/--
Returns the region of the substring delimited by the provided start and stop positions, as a
substring. The positions are interpreted with respect to the substring's start position, rather than
the underlying string.
If the resulting substring is empty, then the resulting substring is a substring of the empty string
`""`. Otherwise, the underlying string is that of the input substring with the beginning and end
positions adjusted.
-/
@[inline] def extract : Substring → String.Pos.Raw → String.Pos.Raw → Substring
| ⟨s, b, e⟩, b', e' => if b' ≥ e' then ⟨"", 0, 0⟩ else ⟨s, e.min (b'.offsetBy b), e.min (e'.offsetBy b)⟩
@[export lean_substring_extract]
def Internal.extractImpl : Substring → String.Pos.Raw → String.Pos.Raw → Substring :=
Substring.extract
/--
Splits a substring `s` on occurrences of the separator string `sep`. The default separator is `" "`.
When `sep` is empty, the result is `[s]`. When `sep` occurs in overlapping patterns, the first match
is taken. There will always be exactly `n+1` elements in the returned list if there were `n`
non-overlapping matches of `sep` in the string. The separators are not included in the returned
substrings, which are all substrings of `s`'s string.
-/
def splitOn (s : Substring) (sep : String := " ") : List Substring :=
if sep == "" then
[s]
else
let rec loop (b i j : String.Pos.Raw) (r : List Substring) : List Substring :=
if h : i.byteIdx < s.bsize then
have := Nat.sub_lt_sub_left h (lt_next s i h)
if s.get i == j.get sep then
let i := s.next i
let j := j.next sep
if j.atEnd sep then
loop i i 0 (s.extract b (i.unoffsetBy j) :: r)
else
loop b i j r
else
loop b (s.next i) 0 r
else
let r := if j.atEnd sep then
"".toSubstring :: s.extract b (i.unoffsetBy j) :: r
else
s.extract b i :: r
r.reverse
termination_by s.bsize - i.1
loop 0 0 0 []
/--
Folds a function over a substring from the left, accumulating a value starting with `init`. The
accumulated value is combined with each character in order, using `f`.
-/
@[inline] def foldl {α : Type u} (f : α → Char → α) (init : α) (s : Substring) : α :=
match s with
| ⟨s, b, e⟩ => String.foldlAux f s e b init
/--
Folds a function over a substring from the right, accumulating a value starting with `init`. The
accumulated value is combined with each character in reverse order, using `f`.
-/
@[inline] def foldr {α : Type u} (f : Char → αα) (init : α) (s : Substring) : α :=
match s with
| ⟨s, b, e⟩ => String.foldrAux f init s e b
/--
Checks whether the Boolean predicate `p` returns `true` for any character in a substring.
Short-circuits at the first character for which `p` returns `true`.
-/
@[inline] def any (s : Substring) (p : Char → Bool) : Bool :=
match s with
| ⟨s, b, e⟩ => String.anyAux s e p b
/--
Checks whether the Boolean predicate `p` returns `true` for every character in a substring.
Short-circuits at the first character for which `p` returns `false`.
-/
@[inline] def all (s : Substring) (p : Char → Bool) : Bool :=
!s.any (fun c => !p c)
@[export lean_substring_all]
def Internal.allImpl (s : Substring) (p : Char → Bool) : Bool :=
Substring.all s p
/--
Checks whether a substring contains the specified character.
-/
@[inline] def contains (s : Substring) (c : Char) : Bool :=
s.any (fun a => a == c)
@[specialize] def takeWhileAux (s : String) (stopPos : String.Pos.Raw) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
if h : i < stopPos then
if p (i.get s) then
have := Nat.sub_lt_sub_left h (String.Pos.Raw.lt_next s i)
takeWhileAux s stopPos p (i.next s)
else i
else i
termination_by stopPos.1 - i.1
/--
Retains only the longest prefix of a substring in which a Boolean predicate returns `true` for all
characters by moving the substring's end position towards its start position.
-/
@[inline] def takeWhile : Substring → (Char → Bool) → Substring
| ⟨s, b, e⟩, p =>
let e := takeWhileAux s e p b;
⟨s, b, e⟩
@[export lean_substring_takewhile]
def Internal.takeWhileImpl : Substring → (Char → Bool) → Substring :=
Substring.takeWhile
/--
Removes the longest prefix of a substring in which a Boolean predicate returns `true` for all
characters by moving the substring's start position. The start position is moved to the position of
the first character for which the predicate returns `false`, or to the substring's end position if
the predicate always returns `true`.
-/
@[inline] def dropWhile : Substring → (Char → Bool) → Substring
| ⟨s, b, e⟩, p =>
let b := takeWhileAux s e p b;
⟨s, b, e⟩
@[specialize] def takeRightWhileAux (s : String) (begPos : String.Pos.Raw) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
if h : begPos < i then
have := String.Pos.Raw.prev_lt_of_pos s i <| mt (congrArg String.Pos.Raw.byteIdx) <|
Ne.symm <| Nat.ne_of_lt <| Nat.lt_of_le_of_lt (Nat.zero_le _) h
let i' := i.prev s
let c := i'.get s
if !p c then i
else takeRightWhileAux s begPos p i'
else i
termination_by i.1
/--
Retains only the longest suffix of a substring in which a Boolean predicate returns `true` for all
characters by moving the substring's start position towards its end position.
-/
@[inline] def takeRightWhile : Substring → (Char → Bool) → Substring
| ⟨s, b, e⟩, p =>
let b := takeRightWhileAux s b p e
⟨s, b, e⟩
/--
Removes the longest suffix of a substring in which a Boolean predicate returns `true` for all
characters by moving the substring's end position. The end position is moved just after the position
of the last character for which the predicate returns `false`, or to the substring's start position
if the predicate always returns `true`.
-/
@[inline] def dropRightWhile : Substring → (Char → Bool) → Substring
| ⟨s, b, e⟩, p =>
let e := takeRightWhileAux s b p e
⟨s, b, e⟩
/--
Removes leading whitespace from a substring by moving its start position to the first non-whitespace
character, or to its end position if there is no non-whitespace character.
“Whitespace” is defined as characters for which `Char.isWhitespace` returns `true`.
-/
@[inline] def trimLeft (s : Substring) : Substring :=
s.dropWhile Char.isWhitespace
/--
Removes trailing whitespace from a substring by moving its end position to the last non-whitespace
character, or to its start position if there is no non-whitespace character.
“Whitespace” is defined as characters for which `Char.isWhitespace` returns `true`.
-/
@[inline] def trimRight (s : Substring) : Substring :=
s.dropRightWhile Char.isWhitespace
/--
Removes leading and trailing whitespace from a substring by first moving its start position to the
first non-whitespace character, and then moving its end position to the last non-whitespace
character.
If the substring consists only of whitespace, then the resulting substring's start position is moved
to its end position.
“Whitespace” is defined as characters for which `Char.isWhitespace` returns `true`.
Examples:
* `" red green blue ".toSubstring.trim.toString = "red green blue"`
* `" red green blue ".toSubstring.trim.startPos = ⟨1⟩`
* `" red green blue ".toSubstring.trim.stopPos = ⟨15⟩`
* `" ".toSubstring.trim.startPos = ⟨5⟩`
-/
@[inline] def trim : Substring → Substring
| ⟨s, b, e⟩ =>
let b := takeWhileAux s e Char.isWhitespace b
let e := takeRightWhileAux s b Char.isWhitespace e
⟨s, b, e⟩
/--
Checks whether the substring can be interpreted as the decimal representation of a natural number.
A substring can be interpreted as a decimal natural number if it is not empty and all the characters
in it are digits.
Use `Substring.toNat?` to convert such a substring to a natural number.
-/
@[inline] def isNat (s : Substring) : Bool :=
!s.isEmpty && s.all fun c => c.isDigit
/--
Checks whether the substring can be interpreted as the decimal representation of a natural number,
returning the number if it can.
A substring can be interpreted as a decimal natural number if it is not empty and all the characters
in it are digits.
Use `Substring.isNat` to check whether the substring is such a substring.
-/
def toNat? (s : Substring) : Option Nat :=
if s.isNat then
some <| s.foldl (fun n c => n*10 + (c.toNat - '0'.toNat)) 0
else
none
/--
Given a `Substring`, returns another one which has valid endpoints
and represents the same substring according to `Substring.toString`.
(Note, the substring may still be inverted, i.e. beginning greater than end.)
-/
def repair : Substring → Substring
| ⟨s, b, e⟩ => ⟨s, if b.isValid s then b else s.rawEndPos, if e.isValid s then e else s.rawEndPos⟩
/--
Checks whether two substrings represent equal strings. Usually accessed via the `==` operator.
Two substrings do not need to have the same underlying string or the same start and end positions;
instead, they are equal if they contain the same sequence of characters.
-/
def beq (ss1 ss2 : Substring) : Bool :=
let ss1 := ss1.repair
let ss2 := ss2.repair
ss1.bsize == ss2.bsize && String.Pos.Raw.substrEq ss1.str ss1.startPos ss2.str ss2.startPos ss1.bsize
@[export lean_substring_beq]
def Internal.beqImpl (ss1 ss2 : Substring) : Bool :=
Substring.beq ss1 ss2
instance hasBeq : BEq Substring := ⟨beq⟩
/--
Checks whether two substrings have the same position and content.
The two substrings do not need to have the same underlying string for this check to succeed.
-/
def sameAs (ss1 ss2 : Substring) : Bool :=
ss1.startPos == ss2.startPos && ss1 == ss2
/--
Returns the longest common prefix of two substrings.
The returned substring uses the same underlying string as `s`.
-/
def commonPrefix (s t : Substring) : Substring :=
{ s with stopPos := loop s.startPos t.startPos }
where
/-- Returns the ending position of the common prefix, working up from `spos, tpos`. -/
loop spos tpos :=
if h : spos < s.stopPos ∧ tpos < t.stopPos then
if spos.get s.str == tpos.get t.str then
have := Nat.sub_lt_sub_left h.1 (String.Pos.Raw.lt_next s.str spos)
loop (spos.next s.str) (tpos.next t.str)
else
spos
else
spos
termination_by s.stopPos.byteIdx - spos.byteIdx
/--
Returns the longest common suffix of two substrings.
The returned substring uses the same underlying string as `s`.
-/
def commonSuffix (s t : Substring) : Substring :=
{ s with startPos := loop s.stopPos t.stopPos }
where
/-- Returns the starting position of the common prefix, working down from `spos, tpos`. -/
loop spos tpos :=
if h : s.startPos < spos ∧ t.startPos < tpos then
let spos' := spos.prev s.str
let tpos' := tpos.prev t.str
if spos'.get s.str == tpos'.get t.str then
have : spos' < spos := String.Pos.Raw.prev_lt_of_pos s.str spos (String.Pos.Raw.ne_zero_of_lt h.1)
loop spos' tpos'
else
spos
else
spos
termination_by spos.byteIdx
/--
If `pre` is a prefix of `s`, returns the remainder. Returns `none` otherwise.
The substring `pre` is a prefix of `s` if there exists a `t : Substring` such that
`s.toString = pre.toString ++ t.toString`. If so, the result is the substring of `s` without the
prefix.
-/
def dropPrefix? (s : Substring) (pre : Substring) : Option Substring :=
let t := s.commonPrefix pre
if t.bsize = pre.bsize then
some { s with startPos := t.stopPos }
else
none
/--
If `suff` is a suffix of `s`, returns the remainder. Returns `none` otherwise.
The substring `suff` is a suffix of `s` if there exists a `t : Substring` such that
`s.toString = t.toString ++ suff.toString`. If so, the result the substring of `s` without the
suffix.
-/
def dropSuffix? (s : Substring) (suff : Substring) : Option Substring :=
let t := s.commonSuffix suff
if t.bsize = suff.bsize then
some { s with stopPos := t.startPos }
else
none
@[simp] theorem prev_zero (s : Substring) : s.prev 0 = 0 := by simp [prev]
@[simp] theorem prevn_zero (s : Substring) : ∀ n, s.prevn n 0 = 0
| 0 => rfl
| n+1 => by simp [prevn, prevn_zero s n]
end Substring

View file

@ -0,0 +1,314 @@
/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura, Mario Carneiro
-/
module
prelude
public import Init.Data.String.Substring
/-!
# `String.take` and variants
This file contains the implementations of `String.take` and its variants. Currently, they are
implemented in terms of `Substring`; soon, they will be implemented in terms of `String.Slice`
instead.
-/
public section
namespace String
/--
Removes the specified number of characters (Unicode code points) from the start of the string.
If `n` is greater than `s.length`, returns `""`.
Examples:
* `"red green blue".drop 4 = "green blue"`
* `"red green blue".drop 10 = "blue"`
* `"red green blue".drop 50 = ""`
-/
@[inline] def drop (s : String) (n : Nat) : String :=
(s.toSubstring.drop n).toString
@[export lean_string_drop]
def Internal.dropImpl (s : String) (n : Nat) : String :=
String.drop s n
/--
Removes the specified number of characters (Unicode code points) from the end of the string.
If `n` is greater than `s.length`, returns `""`.
Examples:
* `"red green blue".dropRight 5 = "red green"`
* `"red green blue".dropRight 11 = "red"`
* `"red green blue".dropRight 50 = ""`
-/
@[inline] def dropRight (s : String) (n : Nat) : String :=
(s.toSubstring.dropRight n).toString
@[export lean_string_dropright]
def Internal.dropRightImpl (s : String) (n : Nat) : String :=
String.dropRight s n
/--
Creates a new string that contains the first `n` characters (Unicode code points) of `s`.
If `n` is greater than `s.length`, returns `s`.
Examples:
* `"red green blue".take 3 = "red"`
* `"red green blue".take 1 = "r"`
* `"red green blue".take 0 = ""`
* `"red green blue".take 100 = "red green blue"`
-/
@[inline] def take (s : String) (n : Nat) : String :=
(s.toSubstring.take n).toString
/--
Creates a new string that contains the last `n` characters (Unicode code points) of `s`.
If `n` is greater than `s.length`, returns `s`.
Examples:
* `"red green blue".takeRight 4 = "blue"`
* `"red green blue".takeRight 1 = "e"`
* `"red green blue".takeRight 0 = ""`
* `"red green blue".takeRight 100 = "red green blue"`
-/
@[inline] def takeRight (s : String) (n : Nat) : String :=
(s.toSubstring.takeRight n).toString
/--
Creates a new string that contains the longest prefix of `s` in which `p` returns `true` for all
characters.
Examples:
* `"red green blue".takeWhile (·.isLetter) = "red"`
* `"red green blue".takeWhile (· == 'r') = "r"`
* `"red green blue".takeWhile (· != 'n') = "red gree"`
* `"red green blue".takeWhile (fun _ => true) = "red green blue"`
-/
@[inline] def takeWhile (s : String) (p : Char → Bool) : String :=
(s.toSubstring.takeWhile p).toString
/--
Creates a new string by removing the longest prefix from `s` in which `p` returns `true` for all
characters.
Examples:
* `"red green blue".dropWhile (·.isLetter) = " green blue"`
* `"red green blue".dropWhile (· == 'r') = "ed green blue"`
* `"red green blue".dropWhile (· != 'n') = "n blue"`
* `"red green blue".dropWhile (fun _ => true) = ""`
-/
@[inline] def dropWhile (s : String) (p : Char → Bool) : String :=
(s.toSubstring.dropWhile p).toString
/--
Creates a new string that contains the longest suffix of `s` in which `p` returns `true` for all
characters.
Examples:
* `"red green blue".takeRightWhile (·.isLetter) = "blue"`
* `"red green blue".takeRightWhile (· == 'e') = "e"`
* `"red green blue".takeRightWhile (· != 'n') = " blue"`
* `"red green blue".takeRightWhile (fun _ => true) = "red green blue"`
-/
@[inline] def takeRightWhile (s : String) (p : Char → Bool) : String :=
(s.toSubstring.takeRightWhile p).toString
/--
Creates a new string by removing the longest suffix from `s` in which `p` returns `true` for all
characters.
Examples:
* `"red green blue".dropRightWhile (·.isLetter) = "red green "`
* `"red green blue".dropRightWhile (· == 'e') = "red green blu"`
* `"red green blue".dropRightWhile (· != 'n') = "red green"`
* `"red green blue".dropRightWhile (fun _ => true) = ""`
-/
@[inline] def dropRightWhile (s : String) (p : Char → Bool) : String :=
(s.toSubstring.dropRightWhile p).toString
/--
Checks whether the first string (`s`) begins with the second (`pre`).
`String.isPrefix` is a version that takes the potential prefix before the string.
Examples:
* `"red green blue".startsWith "red" = true`
* `"red green blue".startsWith "green" = false`
* `"red green blue".startsWith "" = true`
* `"red".startsWith "red" = true`
-/
@[inline] def startsWith (s pre : String) : Bool :=
s.toSubstring.take pre.length == pre.toSubstring
/--
Checks whether the first string (`s`) ends with the second (`post`).
Examples:
* `"red green blue".endsWith "blue" = true`
* `"red green blue".endsWith "green" = false`
* `"red green blue".endsWith "" = true`
* `"red".endsWith "red" = true`
-/
@[inline] def endsWith (s post : String) : Bool :=
s.toSubstring.takeRight post.length == post.toSubstring
/--
Removes trailing whitespace from a string.
“Whitespace” is defined as characters for which `Char.isWhitespace` returns `true`.
Examples:
* `"abc".trimRight = "abc"`
* `" abc".trimRight = " abc"`
* `"abc \t ".trimRight = "abc"`
* `" abc ".trimRight = " abc"`
* `"abc\ndef\n".trimRight = "abc\ndef"`
-/
@[inline] def trimRight (s : String) : String :=
s.toSubstring.trimRight.toString
/--
Removes leading whitespace from a string.
“Whitespace” is defined as characters for which `Char.isWhitespace` returns `true`.
Examples:
* `"abc".trimLeft = "abc"`
* `" abc".trimLeft = " abc"`
* `"abc \t ".trimLeft = "abc \t "`
* `" abc ".trimLeft = "abc "`
* `"abc\ndef\n".trimLeft = "abc\ndef\n"`
-/
@[inline] def trimLeft (s : String) : String :=
s.toSubstring.trimLeft.toString
/--
Removes leading and trailing whitespace from a string.
“Whitespace” is defined as characters for which `Char.isWhitespace` returns `true`.
Examples:
* `"abc".trim = "abc"`
* `" abc".trim = "abc"`
* `"abc \t ".trim = "abc"`
* `" abc ".trim = "abc"`
* `"abc\ndef\n".trim = "abc\ndef"`
-/
@[inline] def trim (s : String) : String :=
s.toSubstring.trim.toString
@[export lean_string_trim]
def Internal.trimImpl (s : String) : String :=
String.trim s
/--
Repeatedly increments a position in a string, as if by `String.next`, while the predicate `p`
returns `true` for the character at the position. Stops incrementing at the end of the string or
when `p` returns `false` for the current character.
Examples:
* `let s := " a "; s.get (s.nextWhile Char.isWhitespace 0) = 'a'`
* `let s := "a "; s.get (s.nextWhile Char.isWhitespace 0) = 'a'`
* `let s := "ba "; s.get (s.nextWhile Char.isWhitespace 0) = 'b'`
-/
@[inline] def Pos.Raw.nextWhile (s : String) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
Substring.takeWhileAux s s.rawEndPos p i
@[deprecated Pos.Raw.nextWhile (since := "2025-10-10")]
abbrev nextWhile (s : String) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
Pos.Raw.nextWhile s p i
@[export lean_string_nextwhile]
def Internal.nextWhileImpl (s : String) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
i.nextWhile s p
/--
Repeatedly increments a position in a string, as if by `String.next`, while the predicate `p`
returns `false` for the character at the position. Stops incrementing at the end of the string or
when `p` returns `true` for the current character.
Examples:
* `let s := " a "; s.get (s.nextUntil Char.isWhitespace 0) = ' '`
* `let s := " a "; s.get (s.nextUntil Char.isLetter 0) = 'a'`
* `let s := "a "; s.get (s.nextUntil Char.isWhitespace 0) = ' '`
-/
@[inline] def Pos.Raw.nextUntil (s : String) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
nextWhile s (fun c => !p c) i
@[deprecated Pos.Raw.nextUntil (since := "2025-10-10")]
def nextUntil (s : String) (p : Char → Bool) (i : String.Pos.Raw) : String.Pos.Raw :=
i.nextUntil s p
/--
If `pre` is a prefix of `s`, returns the remainder. Returns `none` otherwise.
The string `pre` is a prefix of `s` if there exists a `t : String` such that `s = pre ++ t`. If so,
the result is `some t`.
Use `String.stripPrefix` to return the string unchanged when `pre` is not a prefix.
Examples:
* `"red green blue".dropPrefix? "red " = some "green blue"`
* `"red green blue".dropPrefix? "reed " = none`
* `"red green blue".dropPrefix? "" = some "red green blue"`
-/
def dropPrefix? (s : String) (pre : String) : Option Substring :=
s.toSubstring.dropPrefix? pre.toSubstring
/--
If `suff` is a suffix of `s`, returns the remainder. Returns `none` otherwise.
The string `suff` is a suffix of `s` if there exists a `t : String` such that `s = t ++ suff`. If so,
the result is `some t`.
Use `String.stripSuffix` to return the string unchanged when `suff` is not a suffix.
Examples:
* `"red green blue".dropSuffix? " blue" = some "red green"`
* `"red green blue".dropSuffix? " blu " = none`
* `"red green blue".dropSuffix? "" = some "red green blue"`
-/
def dropSuffix? (s : String) (suff : String) : Option Substring :=
s.toSubstring.dropSuffix? suff.toSubstring
/--
If `pre` is a prefix of `s`, returns the remainder. Returns `s` unmodified otherwise.
The string `pre` is a prefix of `s` if there exists a `t : String` such that `s = pre ++ t`. If so,
the result is `t`. Otherwise, it is `s`.
Use `String.dropPrefix?` to return `none` when `pre` is not a prefix.
Examples:
* `"red green blue".stripPrefix "red " = "green blue"`
* `"red green blue".stripPrefix "reed " = "red green blue"`
* `"red green blue".stripPrefix "" = "red green blue"`
-/
def stripPrefix (s : String) (pre : String) : String :=
s.dropPrefix? pre |>.map Substring.toString |>.getD s
/--
If `suff` is a suffix of `s`, returns the remainder. Returns `s` unmodified otherwise.
The string `suff` is a suffix of `s` if there exists a `t : String` such that `s = t ++ suff`. If so,
the result is `t`. Otherwise, it is `s`.
Use `String.dropSuffix?` to return `none` when `suff` is not a suffix.
Examples:
* `"red green blue".stripSuffix " blue" = "red green"`
* `"red green blue".stripSuffix " blu " = "red green blue"`
* `"red green blue".stripSuffix "" = "red green blue"`
-/
def stripSuffix (s : String) (suff : String) : String :=
s.dropSuffix? suff |>.map Substring.toString |>.getD s
end String

View file

@ -6,7 +6,7 @@ Authors: Leonardo de Moura and Sebastian Ullrich
module
prelude
public import Init.Data.String.Basic
public import Init.Data.String.Substring
/-!
Here we give the. implementation of `Name.toString`. There is also a private implementation in

View file

@ -7,6 +7,7 @@ module
prelude
public import Init.Data.String.Basic
import Init.Data.String.Iterator
public section

View file

@ -9,6 +9,7 @@ prelude
public import Init.System.IOError
public import Init.System.FilePath
public import Init.Data.Ord.UInt
import Init.Data.String.TakeDrop
public section

View file

@ -8,6 +8,7 @@ module
prelude
public import Init.Data.String.Extra
public import Init.System.FilePath
import Init.Data.String.TakeDrop
public section

View file

@ -7,6 +7,7 @@ module
prelude
public import Lean.EnvExtension
import Init.Data.String.TakeDrop
public section

View file

@ -7,7 +7,7 @@ module
prelude
public import Lean.Data.Name
import Init.Data.String.Basic
import Init.Data.String.Iterator
public section

View file

@ -14,6 +14,7 @@ public import Std.Data.TreeMap.Raw.Basic
public import Init.Data.Ord.String
import Init.Data.Range.Polymorphic.Iterators
import Init.Data.Range.Polymorphic.Nat
import Init.Data.String.TakeDrop
public section

View file

@ -8,6 +8,7 @@ module
prelude
public import Lean.Data.JsonRpc
import Init.Data.String.TakeDrop
public section

View file

@ -7,6 +7,7 @@ module
prelude
public import Init.Data.Ord.Basic
import Init.Data.String.TakeDrop
import Init.Data.Ord.String
import Init.Data.Ord.UInt

View file

@ -8,6 +8,7 @@ module
prelude
public import Lean.Data.Json.FromToJson.Basic
public import Lean.ToExpr
import Init.Data.String.Iterator
public section

View file

@ -8,6 +8,8 @@ module
prelude
public import Lean.Syntax
import Init.Data.String.TakeDrop
import Init.Data.String.Iterator
public section

View file

@ -10,6 +10,8 @@ prelude
import Init.Data.Ord
public import Lean.DocString.Types
public import Init.Data.String.TakeDrop
import Init.Data.String.Iterator
set_option linter.missingDocs true

View file

@ -21,6 +21,7 @@ public import Lean.PrivateName
public import Lean.LoadDynlib
public import Init.Dynamic
import Init.Data.Slice
import Init.Data.String.TakeDrop
public section

View file

@ -10,6 +10,7 @@ prelude
public import Lean.Message
public import Lean.EnvExtension
public import Lean.DocString.Links
import Init.Data.String.TakeDrop
public section

View file

@ -7,6 +7,7 @@ module
prelude
public import Init.System.IO
import Init.Data.String.TakeDrop
public section

View file

@ -13,6 +13,7 @@ module
prelude
public import Init.System.IO
import Init.Data.ToString.Name
import Init.Data.String.TakeDrop
public section

View file

@ -7,7 +7,7 @@ module
prelude
public import Std.Internal.Parsec.Basic
public import Init.Data.String.Basic
public import Init.Data.String.Iterator
public section

View file

@ -7,6 +7,7 @@ module
prelude
public import Std.Time.Zoned
import Init.Data.String.TakeDrop
public section

View file

@ -7,6 +7,7 @@ module
prelude
public import Std.Time.Zoned.Database.Basic
import Init.Data.String.TakeDrop
public section

View file

@ -9,6 +9,7 @@ prelude
public import Init.Data.Order
import Lake.Util.Name
import Lake.Config.Kinds
import Init.Data.String.TakeDrop
namespace Lake
open Lean (Name)

View file

@ -7,6 +7,7 @@ module
prelude
public import Lean.Setup
public import Init.Data.String.TakeDrop
open Lean

View file

@ -10,6 +10,7 @@ public import Init.System.FilePath
public import Std.Data.TreeMap.Basic
public import Lean.Data.Name
import Lake.Util.Name
import Init.Data.String.TakeDrop
open System Lean

View file

@ -8,7 +8,7 @@ module
prelude
public import Lake.Util.Date
import Lake.Util.String
import Init.Data.String.Basic
import Init.Data.String.TakeDrop
/-!
# TOML Date-Time

View file

@ -10,7 +10,7 @@ public import Init.Data.Float
public import Lake.Toml.Data.Dict
public import Lake.Toml.Data.DateTime
import Lake.Util.String
import Init.Data.String.Basic
import Init.Data.String.TakeDrop
/-!
# TOML Value

View file

@ -7,7 +7,7 @@ module
prelude
import Init.Data.Array.Basic
public import Init.Data.String.Basic
public import Init.Data.String.TakeDrop
namespace Lake

View file

@ -7,6 +7,7 @@ module
prelude
public import Lean.Data.Json
import Init.Data.String.TakeDrop
open System Lean

View file

@ -8,6 +8,7 @@ module
prelude
public import Init.Data.ToString
public import Lake.Util.Proc
import Init.Data.String.TakeDrop
open System
namespace Lake

View file

@ -11,6 +11,7 @@ public import Lake.Util.Error
public import Lake.Util.EStateT
public import Lean.Message
public import Lake.Util.Lift
import Init.Data.String.TakeDrop
open Lean

View file

@ -7,6 +7,7 @@ module
prelude
public import Lake.Util.Log
import Init.Data.String.TakeDrop
namespace Lake

View file

@ -10,6 +10,7 @@ public import Lake.Util.Log
import all Init.Data.String.Extra
import Lake.Util.JsonObject
import Lake.Util.Proc
import Init.Data.String.TakeDrop
open Lean

View file

@ -8,6 +8,7 @@ module
prelude
public import Lean.Data.Json
public import Lake.Util.Date
import Init.Data.String.TakeDrop
/-! # Version

View file

@ -8,6 +8,7 @@ module
prelude
public import Init.Prelude
import Init.Data.ToString
import Init.Data.String.TakeDrop
namespace Lake