chore: expose more String.Slice functions on String (#11308)

This PR redefines `front` and `back` on `String` to go through
`String.Slice` and adds the new `String` functions `front?`, `back?`,
`positions`, `chars`, `revPositions`, `revChars`, `byteIterator`,
`revBytes`, `lines`.
This commit is contained in:
Markus Himmel 2025-11-23 16:33:16 +01:00 committed by GitHub
parent 4311237321
commit fba166eea0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 167 additions and 37 deletions

View file

@ -2429,37 +2429,6 @@ def Pos.Raw.prev : (@& String) → (@& Pos.Raw) → Pos.Raw
def prev : (@& String) → (@& Pos.Raw) → Pos.Raw
| s, p => Pos.Raw.utf8PrevAux s.toList 0 p
/--
Returns the first character in `s`. If `s = ""`, returns `(default : Char)`.
Examples:
* `"abc".front = 'a'`
* `"".front = (default : Char)`
-/
@[inline, expose] def front (s : String) : Char :=
Pos.Raw.get s 0
@[export lean_string_front]
def Internal.frontImpl (s : String) : Char :=
String.front s
theorem front_eq_get {s : String} : s.front = (0 : Pos.Raw).get s := rfl
/--
Returns the last character in `s`. If `s = ""`, returns `(default : Char)`.
Examples:
* `"abc".back = 'c'`
* `"".back = (default : Char)`
-/
@[inline, expose] def back (s : String) : Char :=
(s.rawEndPos.prev s).get s
theorem back_eq_get_prev_rawEndPos {s : String} : s.back = (s.rawEndPos.prev s).get s := rfl
@[deprecated back_eq_get_prev_rawEndPos (since := "2025-10-20")]
theorem back_eq_get_prev_endPos {s : String} : s.back = (s.rawEndPos.prev s).get s := rfl
/--
Returns `true` if a specified byte position is greater than or equal to the position which points to
the end of a string. Otherwise, returns `false`.

View file

@ -112,9 +112,25 @@ Examples:
* {lean}`("baaab".split "aa").toList == ["b".toSlice, "ab".toSlice]`
-/
@[inline]
def split (s : String) (pat : ρ) [ToForwardSearcher pat σ] :=
def split (s : String) (pat : ρ) [ToForwardSearcher pat σ] :=
(s.toSlice.split pat : Std.Iter String.Slice)
/--
Splits a string at each subslice that matches the pattern {name}`pat`. Unlike {name}`split` the
matched subslices are included at the end of each subslice.
This function is generic over all currently supported patterns.
Examples:
* {lean}`("coffee tea water".splitInclusive Char.isWhitespace).toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".splitInclusive ' ').toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]`
* {lean}`("coffee tea water".splitInclusive " tea ").toList == ["coffee tea ".toSlice, "water".toSlice]`
* {lean}`("baaab".splitInclusive "aa").toList == ["baa".toSlice, "ab".toSlice]`
-/
@[inline]
def splitInclusive (s : String) (pat : ρ) [ToForwardSearcher pat σ] :=
(s.toSlice.splitInclusive pat : Std.Iter String.Slice)
@[deprecated String.Slice.foldl (since := "2025-11-20")]
def foldlAux {α : Type u} (f : α → Char → α) (s : String) (stopPos : Pos.Raw) (i : Pos.Raw) (a : α) : α :=
s.slice! (s.pos! i) (s.pos! stopPos) |>.foldl f a
@ -200,6 +216,147 @@ Examples:
@[inline] def toNat! (s : String) : Nat :=
s.toSlice.toNat!
/--
Returns the first character in {name}`s`. If {name}`s` is empty, returns {name}`none`.
Examples:
* {lean}`"abc".front? = some 'a'`
* {lean}`"".front? = none`
-/
@[inline]
def front? (s : String) : Option Char :=
s.toSlice.front?
/--
Returns the first character in {name}`s`. If {lean}`s = ""`, returns {lean}`(default : Char)`.
Examples:
* {lean}`"abc".front = 'a'`
* {lean}`"".front = (default : Char)`
-/
@[inline, expose] def front (s : String) : Char :=
s.toSlice.front
@[export lean_string_front]
def Internal.frontImpl (s : String) : Char :=
String.front s
/--
Returns the last character in {name}`s`. If {name}`s` is empty, returns {name}`none`.
Examples:
* {lean}`"abc".back? = some 'c'`
* {lean}`"".back? = none`
-/
@[inline]
def back? (s : String) : Option Char :=
s.toSlice.back?
/--
Returns the last character in {name}`s`. If {lean}`s = ""`, returns {lean}`(default : Char)`.
Examples:
* {lean}`"abc".back = 'c'`
* {lean}`"".back = (default : Char)`
-/
@[inline, expose] def back (s : String) : Char :=
s.toSlice.back
theorem Slice.Pos.ofSlice_ne_endValidPos {s : String} {p : s.toSlice.Pos}
(h : p ≠ s.toSlice.endPos) : p.ofSlice ≠ s.endValidPos := by
rwa [ne_eq, ← ValidPos.toSlice_inj, toSlice_ofSlice, ← endPos_toSlice]
@[inline]
def Internal.toSliceWithProof {s : String} :
{ p : s.toSlice.Pos // p ≠ s.toSlice.endPos } → { p : s.ValidPos // p ≠ s.endValidPos } :=
fun ⟨p, h⟩ => ⟨p.ofSlice, Slice.Pos.ofSlice_ne_endValidPos h⟩
/--
Creates an iterator over all valid positions within {name}`s`.
Examples
* {lean}`("abc".positions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['a', 'b', 'c']`
* {lean}`("abc".positions.map (·.val.offset.byteIdx) |>.toList) = [0, 1, 2]`
* {lean}`("ab∀c".positions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['a', 'b', '∀', 'c']`
* {lean}`("ab∀c".positions.map (·.val.offset.byteIdx) |>.toList) = [0, 1, 2, 5]`
-/
@[inline]
def positions (s : String) :=
(s.toSlice.positions.map Internal.toSliceWithProof : Std.Iter { p : s.ValidPos // p ≠ s.endValidPos })
/--
Creates an iterator over all characters (Unicode code points) in {name}`s`.
Examples:
* {lean}`"abc".chars.toList = ['a', 'b', 'c']`
* {lean}`"ab∀c".chars.toList = ['a', 'b', '∀', 'c']`
-/
@[inline]
def chars (s : String) :=
(s.toSlice.chars : Std.Iter Char)
/--
Creates an iterator over all valid positions within {name}`s`, starting from the last valid
position and iterating towards the first one.
Examples
* {lean}`("abc".revPositions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['c', 'b', 'a']`
* {lean}`("abc".revPositions.map (·.val.offset.byteIdx) |>.toList) = [2, 1, 0]`
* {lean}`("ab∀c".revPositions.map (fun ⟨p, h⟩ => p.get h) |>.toList) = ['c', '∀', 'b', 'a']`
* {lean}`("ab∀c".toSlice.revPositions.map (·.val.offset.byteIdx) |>.toList) = [5, 2, 1, 0]`
-/
@[inline]
def revPositions (s : String) :=
(s.toSlice.revPositions.map Internal.toSliceWithProof : Std.Iter { p : s.ValidPos // p ≠ s.endValidPos })
/--
Creates an iterator over all characters (Unicode code points) in {name}`s`, starting from the end
of the slice and iterating towards the start.
Example:
* {lean}`"abc".revChars.toList = ['c', 'b', 'a']`
* {lean}`"ab∀c".revChars.toList = ['c', '∀', 'b', 'a']`
-/
@[inline]
def revChars (s : String) :=
(s.toSlice.revChars : Std.Iter Char)
/--
Creates an iterator over all bytes in {name}`s`.
Examples:
* {lean}`"abc".byteIterator.toList = [97, 98, 99]`
* {lean}`"ab∀c".byteIterator.toList = [97, 98, 226, 136, 128, 99]`
-/
@[inline]
def byteIterator (s : String) :=
(s.toSlice.bytes : Std.Iter UInt8)
/--
Creates an iterator over all bytes in {name}`s`, starting from the last one and iterating towards
the first one.
Examples:
* {lean}`"abc".revBytes.toList = [99, 98, 97]`
* {lean}`"ab∀c".revBytes.toList = [99, 128, 136, 226, 98, 97]`
-/
@[inline]
def revBytes (s : String) :=
(s.toSlice.revBytes : Std.Iter UInt8)
/--
Creates an iterator over all lines in {name}`s` with the line ending characters `\r\n` or `\n` being
stripped.
Examples:
* {lean}`"foo\r\nbar\n\nbaz\n".lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz".lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]`
* {lean}`"foo\r\nbar\n\nbaz\r".lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz\r".toSlice]`
-/
def lines (s : String) :=
s.toSlice.lines
end
end String

View file

@ -1303,13 +1303,13 @@ def toNat! (s : Slice) : Nat :=
panic! "Nat expected"
/--
Returns the first character in {name}`s`. If {name}`s` is empty, {name}`none`.
Returns the first character in {name}`s`. If {name}`s` is empty, returns {name}`none`.
Examples:
* {lean}`"abc".toSlice.front? = some 'a'`
* {lean}`"".toSlice.front? = none`
-/
@[inline]
@[inline, expose]
def front? (s : Slice) : Option Char :=
s.startPos.get?
@ -1320,7 +1320,7 @@ Examples:
* {lean}`"abc".toSlice.front = 'a'`
* {lean}`"".toSlice.front = (default : Char)`
-/
@[inline]
@[inline, expose]
def front (s : Slice) : Char :=
s.front?.getD default
@ -1331,7 +1331,7 @@ Examples:
* {lean}`"abc".toSlice.back? = some 'c'`
* {lean}`"".toSlice.back? = none`
-/
@[inline]
@[inline, expose]
def back? (s : Slice) : Option Char :=
s.endPos.prev? |>.bind (·.get?)
@ -1342,7 +1342,7 @@ Examples:
* {lean}`"abc".toSlice.back = 'c'`
* {lean}`"".toSlice.back = (default : Char)`
-/
@[inline]
@[inline, expose]
def back (s : Slice) : Char :=
s.back?.getD default

View file

@ -8,6 +8,7 @@ module
prelude
public import Init.Data.String.Substring
import Init.Data.String.TakeDrop
import Init.Data.String.Search
/-!
Here we give the. implementation of `Name.toString`. There is also a private implementation in

View file

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

View file

@ -10,6 +10,7 @@ prelude
public import Lean.Data.Lsp.Basic
meta import Lean.Data.Json
public import Lean.Expr
public import Init.Data.String.Search
public section

View file

@ -10,6 +10,7 @@ public import Init.Data.Ord.Basic
import Init.Data.String.TakeDrop
import Init.Data.Ord.String
import Init.Data.Ord.UInt
import Init.Data.String.Search
public section
namespace Lean