/- Copyright (c) 2025 Lean FRO, LLC. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Henrik Böving, Markus Himmel -/ module prelude public import Init.Data.String.Pattern public import Init.Data.Ord.Basic public import Init.Data.Iterators.Combinators.FilterMap public import Init.Data.String.ToSlice public import Init.Data.String.Subslice public import Init.Data.String.Iter.Basic public import Init.Data.String.Iterate import Init.Data.Iterators.Consumers.Collect import Init.Data.Iterators.Consumers.Loop import Init.Data.Option.Lemmas import Init.Data.String.Termination import Init.Omega set_option doc.verso true /-! This module defines the programming API for {name}`String.Slice`. The API mostly consists of functionality for searching for various kinds of pattern matches in slices to iterate over them, provide subslices according to matches etc. The key design principles behind this module are: - Instead of providing one function per kind of pattern the API is generic over various kinds of patterns. Thus it only provides e.g. one kind of function for looking for the position of the first occurrence of a pattern. Currently the supported patterns are: - {name}`Char` - {lean}`Char → Bool` - {name}`String` and {name}`String.Slice` (partially: doing non trivial searches backwards is not supported yet) - Whenever a slice gets mutated a new slice is returned to allow for repeated chaining of functions with minimal allocations. If necessary the slice can ultimately be converted back to {name}`String` using {name}`String.Slice.copy` - Instead of allocating intermediate collections the operations that iterate over slices in various ways (characters, positions etc.) return iterators that can be collected into other collections if necessary. - When sensible the API provides functionality for searching both in a forward and backward manner -/ public section namespace String.Slice instance : HAppend String String.Slice String where -- This implementation performs an unnecessary copy which could be avoided by providing a custom -- C++ implementation for this instance. Note: if `String` had no custom runtime representation -- at all, then this would be very easy to get right from Lean using `ByteArray.copySlice`. hAppend s t := s ++ t.copy open Pattern /-- Checks whether {name}`s1` and {name}`s2` represent the same string, even if they are slices of different base strings or different slices within the same string. The implementation is an efficient equivalent of {lean}`s1.copy == s2.copy` -/ def beq (s1 s2 : Slice) : Bool := if h : s1.utf8ByteSize = s2.utf8ByteSize then have h1 := by simp have h2 := by simp [h, String.Pos.Raw.le_iff] Internal.memcmpSlice s1 s2 s1.startPos.offset s2.startPos.offset s1.rawEndPos h1 h2 else false instance : BEq Slice where beq := beq @[inherit_doc Slice.copy] def toString (s : Slice) : String := s.copy @[simp] theorem toString_eq : toString = copy := (rfl) instance : ToString String.Slice where toString := toString @[simp] theorem toStringToString_eq : ToString.toString = String.Slice.copy := (rfl) @[extern "lean_slice_hash"] protected def hash (s : @& Slice) : UInt64 := String.hash s.copy instance : Hashable Slice where hash := Slice.hash instance : LT Slice where lt x y := x.copy < y.copy @[extern "lean_slice_dec_lt"] instance (x y : @& Slice) : Decidable (x < y) := inferInstanceAs (Decidable (x.copy < y.copy)) instance : Ord Slice where compare x y := compareOfLessAndBEq x y instance : LE Slice where le x y := ¬x < y instance : DecidableLE Slice := fun x y => inferInstanceAs (Decidable (¬x < y)) section ForwardPatternUsers variable {ρ : Type} {σ : Slice → Type} variable [∀ s, Std.Iterator (σ s) Id (SearchStep s)] variable [∀ s, Std.Iterators.Finite (σ s) Id] variable [∀ s, Std.IteratorLoop (σ s) Id Id] /-- Checks whether the slice ({name}`s`) begins with the pattern ({name}`pat`). This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.startsWith "red" = true` * {lean}`"red green blue".toSlice.startsWith "green" = false` * {lean}`"red green blue".toSlice.startsWith "" = true` * {lean}`"red green blue".toSlice.startsWith 'r' = true` * {lean}`"red green blue".toSlice.startsWith Char.isLower = true` -/ @[inline] def startsWith (s : Slice) (pat : ρ) [ForwardPattern pat] : Bool := ForwardPattern.startsWith pat s inductive SplitIterator {ρ : Type} (pat : ρ) (s : Slice) [ToForwardSearcher pat σ] where | operating (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s)) | atEnd deriving Inhabited namespace SplitIterator variable {pat : ρ} [ToForwardSearcher pat σ] inductive PlausibleStep instance : Std.Iterator (SplitIterator pat s) Id s.Subslice where IsPlausibleStep | ⟨.operating _ s⟩, .yield ⟨.operating _ s'⟩ _ => s'.IsPlausibleSuccessorOf s | ⟨.operating _ s⟩, .yield ⟨.atEnd ..⟩ _ => True | ⟨.operating _ s⟩, .skip ⟨.operating _ s'⟩ => s'.IsPlausibleSuccessorOf s | ⟨.operating _ s⟩, .skip ⟨.atEnd ..⟩ => False | ⟨.operating _ s⟩, .done => True | ⟨.atEnd⟩, .yield .. => False | ⟨.atEnd⟩, .skip _ => False | ⟨.atEnd⟩, .done => True step | ⟨.operating currPos searcher⟩ => match h : searcher.step with | ⟨.yield searcher' (.matched startPos endPos), hps⟩ => let slice := s.subslice! currPos startPos let nextIt := ⟨.operating endPos searcher'⟩ pure (.deflate ⟨.yield nextIt slice, by simp [nextIt, hps.isPlausibleSuccessor_of_yield]⟩) | ⟨.yield searcher' (.rejected ..), hps⟩ => pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩, by simp [hps.isPlausibleSuccessor_of_yield]⟩) | ⟨.skip searcher', hps⟩ => pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩, by simp [hps.isPlausibleSuccessor_of_skip]⟩) | ⟨.done, _⟩ => let slice := s.subsliceFrom currPos pure (.deflate ⟨.yield ⟨.atEnd⟩ slice, by simp⟩) | ⟨.atEnd⟩ => pure (.deflate ⟨.done, by simp⟩) private def toOption : SplitIterator pat s → Option (Std.Iter (α := σ s) (SearchStep s)) | .operating _ s => some s | .atEnd => none private def finitenessRelation [Std.Iterators.Finite (σ s) Id] : Std.Iterators.FinitenessRelation (SplitIterator pat s) Id where Rel := InvImage (Option.lt Std.Iter.IsPlausibleSuccessorOf) (SplitIterator.toOption ∘ Std.IterM.internalState) wf := InvImage.wf _ (Option.wellFounded_lt Std.Iterators.Finite.wf_of_id) subrelation {it it'} h := by simp_wf obtain ⟨step, h, h'⟩ := h match step with | .yield it'' out | .skip it'' => obtain rfl : it' = it'' := by simpa using h.symm simp only [Std.IterM.IsPlausibleStep, Std.Iterator.IsPlausibleStep, instIteratorIdSubslice] at h' -- TODO revert h' match it, it' with | ⟨.operating _ searcher⟩, ⟨.operating _ searcher'⟩ => simp [SplitIterator.toOption, Option.lt] | ⟨.operating _ searcher⟩, ⟨.atEnd⟩ => simp [SplitIterator.toOption, Option.lt] | ⟨.atEnd⟩, _ => simp instance [Std.Iterators.Finite (σ s) Id] : Std.Iterators.Finite (SplitIterator pat s) Id := .of_finitenessRelation finitenessRelation instance [Monad n] : Std.IteratorLoop (SplitIterator pat s) Id n := .defaultImplementation end SplitIterator /-- Splits a slice at each subslice that matches the pattern {name}`pat`. The subslices that matched the pattern are not included in any of the resulting subslices. If multiple subslices in a row match the pattern, the resulting list will contain empty strings. This function is generic over all currently supported patterns. Examples: * {lean}`("coffee tea water".toSlice.splitToSubslice Char.isWhitespace).toStringList == ["coffee", "tea", "water"]` * {lean}`("coffee tea water".toSlice.splitToSubslice ' ').toStringList == ["coffee", "tea", "water"]` * {lean}`("coffee tea water".toSlice.splitToSubslice " tea ").toStringList == ["coffee", "water"]` * {lean}`("ababababa".toSlice.splitToSubslice "aba").toStringList == ["coffee", "water"]` * {lean}`("baaab".toSlice.splitToSubslice "aa").toStringList == ["b", "ab"]` -/ @[specialize pat, cbv_opaque] def splitToSubslice (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] : Std.Iter (α := SplitIterator pat s) s.Subslice := { internalState := .operating s.startPos (ToForwardSearcher.toSearcher pat s) } /-- Splits a slice at each subslice that matches the pattern {name}`pat`. The subslices that matched the pattern are not included in any of the resulting subslices. If multiple subslices in a row match the pattern, the resulting list will contain empty strings. This function is generic over all currently supported patterns. Examples: * {lean}`("coffee tea water".toSlice.split Char.isWhitespace).toStringList == ["coffee", "tea", "water"]` * {lean}`("coffee tea water".toSlice.split ' ').toStringList == ["coffee", "tea", "water"]` * {lean}`("coffee tea water".toSlice.split " tea ").toStringList == ["coffee", "water"]` * {lean}`("ababababa".toSlice.split "aba").toStringList == ["coffee", "water"]` * {lean}`("baaab".toSlice.split "aa").toStringList == ["b", "ab"]` -/ @[inline] def split (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] := (s.splitToSubslice pat |>.map Subslice.toSlice : Std.Iter Slice) inductive SplitInclusiveIterator {ρ : Type} (pat : ρ) (s : Slice) [ToForwardSearcher pat σ] where | operating (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s)) | atEnd deriving Inhabited namespace SplitInclusiveIterator variable {pat : ρ} [ToForwardSearcher pat σ] instance : Std.Iterator (SplitInclusiveIterator pat s) Id Slice where IsPlausibleStep | ⟨.operating _ s⟩, .yield ⟨.operating _ s'⟩ _ => s'.IsPlausibleSuccessorOf s | ⟨.operating _ s⟩, .yield ⟨.atEnd ..⟩ _ => True | ⟨.operating _ s⟩, .skip ⟨.operating _ s'⟩ => s'.IsPlausibleSuccessorOf s | ⟨.operating _ s⟩, .skip ⟨.atEnd ..⟩ => False | ⟨.operating _ s⟩, .done => True | ⟨.atEnd⟩, .yield .. => False | ⟨.atEnd⟩, .skip _ => False | ⟨.atEnd⟩, .done => True step | ⟨.operating currPos searcher⟩ => match h : searcher.step with | ⟨.yield searcher' (.matched _ endPos), hps⟩ => let slice := s.slice! currPos endPos let nextIt := ⟨.operating endPos searcher'⟩ pure (.deflate ⟨.yield nextIt slice, by simp [nextIt, hps.isPlausibleSuccessor_of_yield]⟩) | ⟨.yield searcher' (.rejected ..), hps⟩ => pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩, by simp [hps.isPlausibleSuccessor_of_yield]⟩) | ⟨.skip searcher', hps⟩ => pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩, by simp [hps.isPlausibleSuccessor_of_skip]⟩) | ⟨.done, _⟩ => if currPos != s.endPos then let slice := s.sliceFrom currPos pure (.deflate ⟨.yield ⟨.atEnd⟩ slice, by simp⟩) else pure (.deflate ⟨.done, by simp⟩) | ⟨.atEnd⟩ => pure (.deflate ⟨.done, by simp⟩) private def toOption : SplitInclusiveIterator pat s → Option (Std.Iter (α := σ s) (SearchStep s)) | .operating _ s => some s | .atEnd => none private def finitenessRelation [Std.Iterators.Finite (σ s) Id] : Std.Iterators.FinitenessRelation (SplitInclusiveIterator pat s) Id where Rel := InvImage (Option.lt Std.Iter.IsPlausibleSuccessorOf) (SplitInclusiveIterator.toOption ∘ Std.IterM.internalState) wf := InvImage.wf _ (Option.wellFounded_lt Std.Iterators.Finite.wf_of_id) subrelation {it it'} h := by simp_wf obtain ⟨step, h, h'⟩ := h match step with | .yield it'' out | .skip it'' => obtain rfl : it' = it'' := by simpa using h.symm simp only [Std.IterM.IsPlausibleStep, Std.Iterator.IsPlausibleStep, instIteratorId] at h' -- TODO revert h' match it, it' with | ⟨.operating _ searcher⟩, ⟨.operating _ searcher'⟩ => simp [SplitInclusiveIterator.toOption, Option.lt] | ⟨.operating _ searcher⟩, ⟨.atEnd⟩ => simp [SplitInclusiveIterator.toOption, Option.lt] | ⟨.atEnd⟩, _ => simp instance [Std.Iterators.Finite (σ s) Id] : Std.Iterators.Finite (SplitInclusiveIterator pat s) Id := .of_finitenessRelation finitenessRelation instance [Monad n] {s} : Std.IteratorLoop (SplitInclusiveIterator pat s) Id n := .defaultImplementation end SplitInclusiveIterator /-- Splits a slice 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".toSlice.splitInclusive Char.isWhitespace).toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]` * {lean}`("coffee tea water".toSlice.splitInclusive ' ').toList == ["coffee ".toSlice, "tea ".toSlice, "water".toSlice]` * {lean}`("coffee tea water".toSlice.splitInclusive " tea ").toList == ["coffee tea ".toSlice, "water".toSlice]` * {lean}`("baaab".toSlice.splitInclusive "aa").toList == ["baa".toSlice, "ab".toSlice]` -/ @[specialize pat] def splitInclusive (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] : Std.Iter (α := SplitInclusiveIterator pat s) Slice := { internalState := .operating s.startPos (ToForwardSearcher.toSearcher pat s) } /-- If {name}`pat` matches a prefix of {name}`s`, returns the position at the start of the remainder. Returns {name}`none` otherwise. This function is generic over all currently supported patterns. -/ @[inline] def skipPrefix? (s : Slice) (pat : ρ) [ForwardPattern pat] : Option s.Pos := ForwardPattern.skipPrefix? pat s /-- If {name}`pat` matches a prefix at {name}`pos`, returns the position after the end of the match. Returns {name}`none` otherwise. This function is generic over all currently supported patterns. -/ @[inline] def Pos.skip? {s : Slice} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos := ((s.sliceFrom pos).skipPrefix? pat).map Pos.ofSliceFrom /-- If {name}`pat` matches a prefix of {name}`s`, returns the remainder. Returns {name}`none` otherwise. Use {name (scope := "Init.Data.String.Slice")}`String.Slice.dropPrefix` to return the slice unchanged when {name}`pat` does not match a prefix. This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.dropPrefix? "red " == some "green blue".toSlice` * {lean}`"red green blue".toSlice.dropPrefix? "reed " == none` * {lean}`"red green blue".toSlice.dropPrefix? 'r' == some "ed green blue".toSlice` * {lean}`"red green blue".toSlice.dropPrefix? Char.isLower == some "ed green blue".toSlice` -/ @[inline] def dropPrefix? (s : Slice) (pat : ρ) [ForwardPattern pat] : Option Slice := (s.skipPrefix? pat).map s.sliceFrom /-- If {name}`pat` matches a prefix of {name}`s`, returns the remainder. Returns {name}`s` unmodified otherwise. Use {name}`String.Slice.dropPrefix?` to return {name}`none` when {name}`pat` does not match a prefix. This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.dropPrefix "red " == "green blue".toSlice` * {lean}`"red green blue".toSlice.dropPrefix "reed " == "red green blue".toSlice` * {lean}`"red green blue".toSlice.dropPrefix 'r' == "ed green blue".toSlice` * {lean}`"red green blue".toSlice.dropPrefix Char.isLower == "ed green blue".toSlice` -/ @[specialize pat] def dropPrefix (s : Slice) (pat : ρ) [ForwardPattern pat] : Slice := dropPrefix? s pat |>.getD s /-- Constructs a new string obtained by replacing all occurrences of {name}`pattern` with {name}`replacement` in {name}`s`. This function is generic over all currently supported patterns. The replacement may be a {name}`String` or a {name}`String.Slice`. Examples: * {lean}`"red green blue".toSlice.replace 'e' "" = "rd grn blu"` * {lean}`"red green blue".toSlice.replace (fun c => c == 'u' || c == 'e') "" = "rd grn bl"` * {lean}`"red green blue".toSlice.replace "e" "" = "rd grn blu"` * {lean}`"red green blue".toSlice.replace "ee" "E" = "red grEn blue"` * {lean}`"red green blue".toSlice.replace "e" "E" = "rEd grEEn bluE"` * {lean}`"aaaaa".toSlice.replace "aa" "b" = "bba"` * {lean}`"abc".toSlice.replace "" "k" = "kakbkck"` -/ def replace [ToSlice α] (s : Slice) (pattern : ρ) [ToForwardSearcher pattern σ] (replacement : α) : String := (ToForwardSearcher.toSearcher pattern s).fold (init := "") (fun | sofar, .matched .. => sofar ++ ToSlice.toSlice replacement | sofar, .rejected start stop => sofar ++ s.slice! start stop) /-- Removes the specified number of characters (Unicode code points) from the start of the slice. If {name}`n` is greater than the amount of characters in {name}`s`, returns an empty slice. Examples: * {lean}`"red green blue".toSlice.drop 4 == "green blue".toSlice` * {lean}`"red green blue".toSlice.drop 10 == "blue".toSlice` * {lean}`"red green blue".toSlice.drop 50 == "".toSlice` -/ @[inline] def drop (s : Slice) (n : Nat) : Slice := s.sliceFrom (s.startPos.nextn n) /-- Advances {name}`pos` as long as {name}`pat` matches. -/ @[specialize pat] def Pos.skipWhile {s : Slice} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : s.Pos := match pos.skip? pat with | some nextCurr => if pos < nextCurr then skipWhile nextCurr pat else pos | none => pos termination_by pos /-- Returns the position after the longest prefix of {name}`s` for which {name}`pat` matches (potentially repeatedly). -/ @[inline] def skipPrefixWhile (s : Slice) (pat : ρ) [ForwardPattern pat] : s.Pos := s.startPos.skipWhile pat /-- Creates a new slice that contains the longest prefix of {name}`s` for which {name}`pat` matched (potentially repeatedly). Examples: * {lean}`"red green blue".toSlice.dropWhile Char.isLower == " green blue".toSlice` * {lean}`"red green blue".toSlice.dropWhile 'r' == "ed green blue".toSlice` * {lean}`"red red green blue".toSlice.dropWhile "red " == "green blue".toSlice` * {lean}`"red green blue".toSlice.dropWhile (fun (_ : Char) => true) == "".toSlice` -/ @[inline] def dropWhile (s : Slice) (pat : ρ) [ForwardPattern pat] : Slice := s.sliceFrom (s.skipPrefixWhile pat) /-- Removes leading whitespace from a slice 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 {name}`Char.isWhitespace` returns {name}`true`. Examples: * {lean}`"abc".toSlice.trimAsciiStart == "abc".toSlice` * {lean}`" abc".toSlice.trimAsciiStart == "abc".toSlice` * {lean}`"abc \t ".toSlice.trimAsciiStart == "abc \t ".toSlice` * {lean}`" abc ".toSlice.trimAsciiStart == "abc ".toSlice` * {lean}`"abc\ndef\n".toSlice.trimAsciiStart == "abc\ndef\n".toSlice` -/ @[inline] def trimAsciiStart (s : Slice) : Slice := -- If we want to optimize this can be pushed further by specialising for ASCII dropWhile s Char.isWhitespace /-- Creates a new slice that contains the first {name}`n` characters (Unicode code points) of {name}`s`. If {name}`n` is greater than the amount of characters in {name}`s`, returns {name}`s`. Examples: * {lean}`"red green blue".toSlice.take 3 == "red".toSlice` * {lean}`"red green blue".toSlice.take 1 == "r".toSlice` * {lean}`"red green blue".toSlice.take 0 == "".toSlice` * {lean}`"red green blue".toSlice.take 100 == "red green blue".toSlice` -/ @[inline] def take (s : Slice) (n : Nat) : Slice := s.sliceTo (s.startPos.nextn n) /-- Creates a new slice that contains the longest prefix of {name}`s` for which {name}`pat` matched (potentially repeatedly). This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.takeWhile Char.isLower == "red".toSlice` * {lean}`"red green blue".toSlice.takeWhile 'r' == "r".toSlice` * {lean}`"red red green blue".toSlice.takeWhile "red " == "red red ".toSlice` * {lean}`"red green blue".toSlice.takeWhile (fun (_ : Char) => true) == "red green blue".toSlice` -/ @[inline] def takeWhile (s : Slice) (pat : ρ) [ForwardPattern pat] : Slice := s.sliceTo (s.skipPrefixWhile pat) /-- Finds the position of the first match of the pattern {name}`pat` in a slice {name}`s`. If there is no match {name}`none` is returned. This function is generic over all currently supported patterns. Examples: * {lean}`("coffee tea water".toSlice.find? Char.isWhitespace).map (·.get!) == some ' '` * {lean}`"tea".toSlice.find? (fun (c : Char) => c == 'X') == none` * {lean}`("coffee tea water".toSlice.find? "tea").map (·.get!) == some 't'` -/ @[inline] def find? (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] : Option s.Pos := let searcher := ToForwardSearcher.toSearcher pat s searcher.findSome? (fun | .matched startPos _ => some startPos | .rejected .. => none) /-- Finds the position of the first match of the pattern {name}`pat` in a slice {name}`s`. If there is no match {lean}`s.endPos` is returned. This function is generic over all currently supported patterns. Examples: * {lean}`("coffee tea water".toSlice.find Char.isWhitespace).get! == ' '` * {lean}`"tea".toSlice.find (fun (c : Char) => c == 'X') == "tea".toSlice.endPos` * {lean}`("coffee tea water".toSlice.find "tea").get! == 't'` -/ @[inline] def find (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] : s.Pos := s.find? pat |>.getD s.endPos /-- Checks whether a slice has a match of the pattern {name}`pat` anywhere. This function is generic over all currently supported patterns. Examples: * {lean}`"coffee tea water".toSlice.contains Char.isWhitespace = true` * {lean}`"tea".toSlice.contains (fun (c : Char) => c == 'X') = false` * {lean}`"coffee tea water".toSlice.contains "tea" = true` -/ @[specialize pat, suggest_for String.Slice.some] def contains (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] : Bool := let searcher := ToForwardSearcher.toSearcher pat s searcher.any (· matches .matched ..) @[inline, inherit_doc contains] def any (s : Slice) (pat : ρ) [ToForwardSearcher pat σ] : Bool := s.contains pat /-- Checks whether a slice only consists of matches of the pattern {name}`pat`. Short-circuits at the first pattern mis-match. This function is generic over all currently supported patterns. Examples: * {lean}`"brown".toSlice.all Char.isLower = true` * {lean}`"brown and orange".toSlice.all Char.isLower = false` * {lean}`"aaaaaa".toSlice.all 'a' = true` * {lean}`"aaaaaa".toSlice.all "aa" = true` * {lean}`"aaaaaaa".toSlice.all "aa" = false` -/ @[inline] def all (s : Slice) (pat : ρ) [ForwardPattern pat] : Bool := s.skipPrefixWhile pat == s.endPos end ForwardPatternUsers section BackwardPatternUsers variable {σ : Slice → Type} variable [∀ s, Std.Iterator (σ s) Id (SearchStep s)] variable [∀ s, Std.Iterators.Finite (σ s) Id] variable [∀ s, Std.IteratorLoop (σ s) Id Id] /-- Checks whether the slice ({name}`s`) ends with the pattern ({name}`pat`). This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.endsWith "blue" = true` * {lean}`"red green blue".toSlice.endsWith "green" = false` * {lean}`"red green blue".toSlice.endsWith "" = true` * {lean}`"red green blue".toSlice.endsWith 'e' = true` * {lean}`"red green blue".toSlice.endsWith Char.isLower = true` -/ @[inline] def endsWith (s : Slice) (pat : ρ) [BackwardPattern pat] : Bool := BackwardPattern.endsWith pat s inductive RevSplitIterator {ρ : Type} (pat : ρ) (s : Slice) [ToBackwardSearcher pat σ] where | operating (currPos : s.Pos) (searcher : Std.Iter (α := σ s) (SearchStep s)) | atEnd deriving Inhabited namespace RevSplitIterator variable [ToBackwardSearcher ρ σ] instance [Pure m] : Std.Iterator (RevSplitIterator ρ s) m Slice where IsPlausibleStep | ⟨.operating _ s⟩, .yield ⟨.operating _ s'⟩ _ => s'.IsPlausibleSuccessorOf s | ⟨.operating _ s⟩, .yield ⟨.atEnd ..⟩ _ => True | ⟨.operating _ s⟩, .skip ⟨.operating _ s'⟩ => s'.IsPlausibleSuccessorOf s | ⟨.operating _ s⟩, .skip ⟨.atEnd ..⟩ => False | ⟨.operating _ s⟩, .done => True | ⟨.atEnd⟩, .yield .. => False | ⟨.atEnd⟩, .skip _ => False | ⟨.atEnd⟩, .done => True step | ⟨.operating currPos searcher⟩ => match h : searcher.step with | ⟨.yield searcher' (.matched startPos endPos), hps⟩ => let slice := s.slice! endPos currPos let nextIt := ⟨.operating startPos searcher'⟩ pure (.deflate ⟨.yield nextIt slice, by simp [nextIt, hps.isPlausibleSuccessor_of_yield]⟩) | ⟨.yield searcher' (.rejected ..), hps⟩ => pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩, by simp [hps.isPlausibleSuccessor_of_yield]⟩) | ⟨.skip searcher', hps⟩ => pure (.deflate ⟨.skip ⟨.operating currPos searcher'⟩, by simp [hps.isPlausibleSuccessor_of_skip]⟩) | ⟨.done, _⟩ => if currPos ≠ s.startPos then let slice := s.sliceTo currPos pure (.deflate ⟨.yield ⟨.atEnd⟩ slice, by simp⟩) else pure (.deflate ⟨.done, by simp⟩) | ⟨.atEnd⟩ => pure (.deflate ⟨.done, by simp⟩) private def toOption : RevSplitIterator ρ s → Option (Std.Iter (α := σ s) (SearchStep s)) | .operating _ s => some s | .atEnd => none private def finitenessRelation [Std.Iterators.Finite (σ s) Id] : Std.Iterators.FinitenessRelation (RevSplitIterator ρ s) Id where Rel := InvImage (Option.lt Std.Iter.IsPlausibleSuccessorOf) (RevSplitIterator.toOption ∘ Std.IterM.internalState) wf := InvImage.wf _ (Option.wellFounded_lt Std.Iterators.Finite.wf_of_id) subrelation {it it'} h := by simp_wf obtain ⟨step, h, h'⟩ := h match step with | .yield it'' out | .skip it'' => obtain rfl : it' = it'' := by simpa using h.symm simp only [Std.IterM.IsPlausibleStep, Std.Iterator.IsPlausibleStep, instIteratorOfPure] at h' -- TODO revert h' match it, it' with | ⟨.operating _ searcher⟩, ⟨.operating _ searcher'⟩ => simp [RevSplitIterator.toOption, Option.lt] | ⟨.operating _ searcher⟩, ⟨.atEnd⟩ => simp [RevSplitIterator.toOption, Option.lt] | ⟨.atEnd⟩, _ => simp instance [Std.Iterators.Finite (σ s) Id] : Std.Iterators.Finite (RevSplitIterator ρ s) Id := .of_finitenessRelation finitenessRelation instance [Monad m] [Monad n] : Std.IteratorLoop (RevSplitIterator ρ s) m n := .defaultImplementation end RevSplitIterator /-- Splits a slice at each subslice that matches the pattern {name}`pat`, starting from the end of the slice and traversing towards the start. The subslices that matched the pattern are not included in any of the resulting subslices. If multiple subslices in a row match the pattern, the resulting list will contain empty slices. This function is generic over all currently supported patterns except {name}`String`/{name}`String.Slice`. Examples: * {lean}`("coffee tea water".toSlice.revSplit Char.isWhitespace).toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]` * {lean}`("coffee tea water".toSlice.revSplit ' ').toList == ["water".toSlice, "tea".toSlice, "coffee".toSlice]` -/ @[specialize pat] def revSplit (s : Slice) (pat : ρ) [ToBackwardSearcher pat σ] : Std.Iter (α := RevSplitIterator pat s) Slice := { internalState := .operating s.endPos (ToBackwardSearcher.toSearcher pat s) } /-- If {name}`pat` matches a suffix of {name}`s`, returns the position at the beginning of the suffix. Returns {name}`none` otherwise. This function is generic over all currently supported patterns. -/ @[inline] def skipSuffix? (s : Slice) (pat : ρ) [BackwardPattern pat] : Option s.Pos := BackwardPattern.skipSuffix? pat s /-- If {name}`pat` matches a suffix at {name}`pos`, returns the position at the beginning of the match. Returns {name}`none` otherwise. This function is generic over all currently supported patterns. -/ @[inline] def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos := ((s.sliceTo pos).skipSuffix? pat).map Pos.ofSliceTo /-- If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise. Use {name (scope := "Init.Data.String.Slice")}`String.Slice.dropSuffix` to return the slice unchanged when {name}`pat` does not match a suffix. This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.dropSuffix? " blue" == some "red green".toSlice` * {lean}`"red green blue".toSlice.dropSuffix? "bluu " == none` * {lean}`"red green blue".toSlice.dropSuffix? 'e' == some "red green blu".toSlice` * {lean}`"red green blue".toSlice.dropSuffix? Char.isLower == some "red green blu".toSlice` -/ @[inline] def dropSuffix? (s : Slice) (pat : ρ) [BackwardPattern pat] : Option Slice := (s.skipSuffix? pat).map s.sliceTo /-- If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`s` unmodified otherwise. Use {name}`String.Slice.dropSuffix?` to return {name}`none` when {name}`pat` does not match a prefix. This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.dropSuffix " blue" == "red green".toSlice` * {lean}`"red green blue".toSlice.dropSuffix "bluu " == "red green blue".toSlice` * {lean}`"red green blue".toSlice.dropSuffix 'e' == "red green blu".toSlice` * {lean}`"red green blue".toSlice.dropSuffix Char.isLower == "red green blu".toSlice` -/ @[specialize pat] def dropSuffix (s : Slice) (pat : ρ) [BackwardPattern pat] : Slice := dropSuffix? s pat |>.getD s /-- Removes the specified number of characters (Unicode code points) from the end of the slice. If {name}`n` is greater than the amount of characters in {name}`s`, returns an empty slice. Examples: * {lean}`"red green blue".toSlice.dropEnd 5 == "red green".toSlice` * {lean}`"red green blue".toSlice.dropEnd 11 == "red".toSlice` * {lean}`"red green blue".toSlice.dropEnd 50 == "".toSlice` -/ @[inline] def dropEnd (s : Slice) (n : Nat) : Slice := s.sliceTo (s.endPos.prevn n) /-- Rewinds {name}`pos` as long as {name}`pat` matches. -/ @[specialize pat] def Pos.revSkipWhile {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : s.Pos := match pos.revSkip? pat with | some nextCurr => if nextCurr < pos then revSkipWhile nextCurr pat else pos | none => pos termination_by pos.down /-- Returns the position at the start of the longest suffix of {name}`s` for which {name}`pat` matches (potentially repeatedly). -/ @[inline] def skipSuffixWhile (s : Slice) (pat : ρ) [BackwardPattern pat] : s.Pos := s.endPos.revSkipWhile pat /-- Checks whether a slice only consists of matches of the pattern {name}`pat`, starting from the back of the string. Short-circuits at the first pattern mis-match. This function is generic over all currently supported patterns. For many types of patterns, this function can be expected to return the same result as {name}`Slice.all`. If mismatches are expected to occur close to the end of the string, this function might be more efficient. For some types of patterns, this function will return a different result than {name}`Slice.all`. Consider, for example, a pattern that matches the longest string at the given position that matches the regular expression {lean}`"a|aa|ab"`. Then, given the input string {lean}`"aab"`, performing {name}`Slice.all` will greedily match the prefix {lean}`"aa"` and then get stuck on the remainder {lean}`"b"`, causing it to return {lean}`false`. On the other hand, {name}`Slice.revAll` will match the suffix {lean}`"ab"` and then match the remainder {lean}`"a"`, so it will return {lean}`true`. Examples: * {lean}`"brown".toSlice.revAll Char.isLower = true` * {lean}`"brown and orange".toSlice.revAll Char.isLower = false` * {lean}`"aaaaaa".toSlice.revAll 'a' = true` * {lean}`"aaaaaa".toSlice.revAll "aa" = true` * {lean}`"aaaaaaa".toSlice.revAll "aa" = false` -/ @[inline] def revAll (s : Slice) (pat : ρ) [BackwardPattern pat] : Bool := s.skipSuffixWhile pat == s.startPos /-- Creates a new slice that contains the longest suffix of {name}`s` for which {name}`pat` matched (potentially repeatedly). Examples: * {lean}`"red green blue".toSlice.dropEndWhile Char.isLower == "red green ".toSlice` * {lean}`"red green blue".toSlice.dropEndWhile 'e' == "red green blu".toSlice` * {lean}`"red green blue".toSlice.dropEndWhile (fun (_ : Char) => true) == "".toSlice` -/ @[inline] def dropEndWhile (s : Slice) (pat : ρ) [BackwardPattern pat] : Slice := s.sliceTo (s.skipSuffixWhile pat) /-- Removes trailing whitespace from a slice 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 {name}`Char.isWhitespace` returns {name}`true`. Examples: * {lean}`"abc".toSlice.trimAsciiEnd == "abc".toSlice` * {lean}`" abc".toSlice.trimAsciiEnd == " abc".toSlice` * {lean}`"abc \t ".toSlice.trimAsciiEnd == "abc".toSlice` * {lean}`" abc ".toSlice.trimAsciiEnd == " abc".toSlice` * {lean}`"abc\ndef\n".toSlice.trimAsciiEnd == "abc\ndef".toSlice` -/ @[inline] def trimAsciiEnd (s : Slice) : Slice := -- If we want to optimize this can be pushed further by specialising for ASCII dropEndWhile s Char.isWhitespace /-- Creates a new slice that contains the last {name}`n` characters (Unicode code points) of {name}`s`. If {name}`n` is greater than the amount of characters in {name}`s`, returns {name}`s`. Examples: * {lean}`"red green blue".toSlice.takeEnd 4 == "blue".toSlice` * {lean}`"red green blue".toSlice.takeEnd 1 == "e".toSlice` * {lean}`"red green blue".toSlice.takeEnd 0 == "".toSlice` * {lean}`"red green blue".toSlice.takeEnd 100 == "red green blue".toSlice` -/ @[inline] def takeEnd (s : Slice) (n : Nat) : Slice := s.sliceFrom (s.endPos.prevn n) /-- Creates a new slice that contains the suffix prefix of {name}`s` for which {name}`pat` matched (potentially repeatedly). This function is generic over all currently supported patterns. Examples: * {lean}`"red green blue".toSlice.takeEndWhile Char.isLower == "blue".toSlice` * {lean}`"red green blue".toSlice.takeEndWhile 'e' == "e".toSlice` * {lean}`"red green blue".toSlice.takeEndWhile (fun (_ : Char) => true) == "red green blue".toSlice` -/ @[inline] def takeEndWhile (s : Slice) (pat : ρ) [BackwardPattern pat] : Slice := s.sliceFrom (s.skipSuffixWhile pat) /-- Finds the position of the first match of the pattern {name}`pat` in a slice, starting from the end of the slice and traversing towards the start. If there is no match {name}`none` is returned. This function is generic over all currently supported patterns except {name}`String`/{name}`String.Slice`. Examples: * {lean}`("coffee tea water".toSlice.revFind? Char.isWhitespace).map (·.get!) == some ' '` * {lean}`"tea".toSlice.revFind? (fun (c : Char) => c == 'X') == none` -/ @[specialize pat] def revFind? (s : Slice) (pat : ρ) [ToBackwardSearcher pat σ] : Option s.Pos := let searcher := ToBackwardSearcher.toSearcher pat s searcher.findSome? (fun | .matched startPos _ => some startPos | .rejected .. => none) end BackwardPatternUsers /-- Removes leading and trailing whitespace from a slice. “Whitespace” is defined as characters for which {name}`Char.isWhitespace` returns {name}`true`. Examples: * {lean}`"abc".toSlice.trimAscii == "abc".toSlice` * {lean}`" abc".toSlice.trimAscii == "abc".toSlice` * {lean}`"abc \t ".toSlice.trimAscii == "abc".toSlice` * {lean}`" abc ".toSlice.trimAscii == "abc".toSlice` * {lean}`"abc\ndef\n".toSlice.trimAscii == "abc\ndef".toSlice` -/ def trimAscii (s : Slice) : Slice := s.trimAsciiStart.trimAsciiEnd /-- Checks whether {lean}`s1 == s2` if ASCII upper/lowercase are ignored. -/ def eqIgnoreAsciiCase (s1 s2 : Slice) : Bool := s1.utf8ByteSize == s2.utf8ByteSize && go s1 s1.startPos.offset s2 s2.startPos.offset where go (s1 : Slice) (s1Curr : String.Pos.Raw) (s2 : Slice) (s2Curr : String.Pos.Raw) : Bool := if h : s1Curr < s1.rawEndPos ∧ s2Curr < s2.rawEndPos then let c1 := (s1.getUTF8Byte s1Curr h.left).toAsciiLower let c2 := (s2.getUTF8Byte s2Curr h.right).toAsciiLower if c1 == c2 then go s1 s1Curr.inc s2 s2Curr.inc else false else s1Curr == s1.rawEndPos && s2Curr == s2.rawEndPos termination_by s1.endPos.offset.byteIdx - s1Curr.byteIdx decreasing_by simp [String.Pos.Raw.lt_iff] at h ⊢ omega def lines.lineMap (s : Slice) : Slice := if let some s := s.dropSuffix? '\n' then if let some s := s.dropSuffix? '\r' then s else s else s /-- 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".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]` * {lean}`"foo\r\nbar\n\nbaz".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz".toSlice]` * {lean}`"foo\r\nbar\n\nbaz\r".toSlice.lines.toList == ["foo".toSlice, "bar".toSlice, "".toSlice, "baz\r".toSlice]` -/ def lines (s : Slice) := s.splitInclusive '\n' |>.map lines.lineMap /-- Checks whether the slice can be interpreted as the decimal representation of a natural number. A slice can be interpreted as a decimal natural number if it is not empty and all the characters in it are digits. Underscores ({lit}`_`) are allowed as digit separators for readability, but cannot appear at the start, at the end, or consecutively. Use {name (scope := "Init.Data.String.Slice")}`toNat?` or {name (scope := "Init.Data.String.Slice")}`toNat!` to convert such a slice to a natural number. Examples: * {lean}`"".toSlice.isNat = false` * {lean}`"0".toSlice.isNat = true` * {lean}`"5".toSlice.isNat = true` * {lean}`"05".toSlice.isNat = true` * {lean}`"587".toSlice.isNat = true` * {lean}`"1_000".toSlice.isNat = true` * {lean}`"100_000_000".toSlice.isNat = true` * {lean}`"-587".toSlice.isNat = false` * {lean}`" 5".toSlice.isNat = false` * {lean}`"2+3".toSlice.isNat = false` * {lean}`"0xff".toSlice.isNat = false` * {lean}`"_123".toSlice.isNat = false` * {lean}`"123_".toSlice.isNat = false` * {lean}`"12__34".toSlice.isNat = false` -/ def isNat (s : Slice) : Bool := Id.run do let mut lastWasDigit := false for c in s do if c = '_' then if !lastWasDigit then return false lastWasDigit := false else if c.isDigit then lastWasDigit := true else return false return lastWasDigit /-- Interprets a slice as the decimal representation of a natural number, returning it. Returns {name}`none` if the slice does not contain a decimal natural number. A slice can be interpreted as a decimal natural number if it is not empty and all the characters in it are digits. Underscores ({lit}`_`) are allowed as digit separators and are ignored during parsing. Use {name}`isNat` to check whether {name}`toNat?` would return {name}`some`. {name (scope := "Init.Data.String.Slice")}`toNat!` is an alternative that panics instead of returning {name}`none` when the slice is not a natural number. Examples: * {lean}`"".toSlice.toNat? = none` * {lean}`"0".toSlice.toNat? = some 0` * {lean}`"5".toSlice.toNat? = some 5` * {lean}`"587".toSlice.toNat? = some 587` * {lean}`"1_000".toSlice.toNat? = some 1000` * {lean}`"100_000_000".toSlice.toNat? = some 100000000` * {lean}`"-587".toSlice.toNat? = none` * {lean}`" 5".toSlice.toNat? = none` * {lean}`"2+3".toSlice.toNat? = none` * {lean}`"0xff".toSlice.toNat? = none` -/ def toNat? (s : Slice) : Option Nat := if s.isNat then some <| s.foldl (fun n c => if c = '_' then n else n * 10 + (c.toNat - '0'.toNat)) 0 else none /-- Interprets a slice as the decimal representation of a natural number, returning it. Panics if the slice does not contain a decimal natural number. A slice can be interpreted as a decimal natural number if it is not empty and all the characters in it are digits. Underscores ({lit}`_`) are allowed as digit separators and are ignored during parsing. Use {name}`isNat` to check whether {name}`toNat!` would return a value. {name}`toNat?` is a safer alternative that returns {name}`none` instead of panicking when the string is not a natural number. Examples: * {lean}`"0".toSlice.toNat! = 0` * {lean}`"5".toSlice.toNat! = 5` * {lean}`"587".toSlice.toNat! = 587` * {lean}`"1_000".toSlice.toNat! = 1000` -/ def toNat! (s : Slice) : Nat := if s.isNat then s.foldl (fun n c => if c = '_' then n else n * 10 + (c.toNat - '0'.toNat)) 0 else panic! "Nat expected" /-- 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, expose] def front? (s : Slice) : Option Char := s.startPos.get? /-- Returns the first character in {name}`s`. If {name}`s` is empty, returns {lean}`(default : Char)`. Examples: * {lean}`"abc".toSlice.front = 'a'` * {lean}`"".toSlice.front = (default : Char)` -/ @[inline, expose] def front (s : Slice) : Char := s.front?.getD default /-- Checks whether the slice can be interpreted as the decimal representation of an integer. A slice can be interpreted as a decimal integer if it only consists of at least one decimal digit and optionally {lit}`-` in front. Leading {lit}`+` characters are not allowed. Use {name (scope := "Init.Data.String.Slice")}`String.Slice.toInt?` or {name (scope := "Init.Data.String.Slice")}`String.toInt!` to convert such a string to an integer. Examples: * {lean}`"".toSlice.isInt = false` * {lean}`"-".toSlice.isInt = false` * {lean}`"0".toSlice.isInt = true` * {lean}`"-0".toSlice.isInt = true` * {lean}`"5".toSlice.isInt = true` * {lean}`"587".toSlice.isInt = true` * {lean}`"-587".toSlice.isInt = true` * {lean}`"+587".toSlice.isInt = false` * {lean}`" 5".toSlice.isInt = false` * {lean}`"2-3".toSlice.isInt = false` * {lean}`"0xff".toSlice.isInt = false` * {lean}`"-0_1".toSlice.isInt = true` * {lean}`"-_1".toSlice.isInt = false` -/ def isInt (s : Slice) : Bool := match s.dropPrefix? '-' with | some rest => rest.isNat | none => s.isNat /-- Interprets a slice as the decimal representation of an integer, returning it. Returns {lean}`none` if the string does not contain a decimal integer. A string can be interpreted as a decimal integer if it only consists of at least one decimal digit and optionally {lit}`-` in front. Leading {lit}`+` characters are not allowed. Use {name}`Slice.isInt` to check whether {name}`Slice.toInt?` would return {lean}`some`. {name (scope := "Init.Data.String.Slice")}`Slice.toInt!` is an alternative that panics instead of returning {lean}`none` when the string is not an integer. Examples: * {lean}`"".toSlice.toInt? = none` * {lean}`"-".toSlice.toInt? = none` * {lean}`"0".toSlice.toInt? = some 0` * {lean}`"5".toSlice.toInt? = some 5` * {lean}`"-5".toSlice.toInt? = some (-5)` * {lean}`"587".toSlice.toInt? = some 587` * {lean}`"-587".toSlice.toInt? = some (-587)` * {lean}`" 5".toSlice.toInt? = none` * {lean}`"2-3".toSlice.toInt? = none` * {lean}`"0xff".toSlice.toInt? = none` * {lean}`"-0_1".toSlice.toInt? = some (-1)` * {lean}`"-_1".toSlice.toInt? = none` -/ def toInt? (s : Slice) : Option Int := match s.dropPrefix? '-' with | some rest => rest.toNat?.map Int.negOfNat | none => s.toNat?.map Int.ofNat /-- Interprets a string as the decimal representation of an integer, returning it. Panics if the string does not contain a decimal integer. A string can be interpreted as a decimal integer if it only consists of at least one decimal digit and optionally {lit}`-` in front. Leading `+` characters are not allowed. Use {name}`Slice.isInt` to check whether {name}`Slice.toInt!` would return a value. {name}`Slice.toInt?` is a safer alternative that returns {lean}`none` instead of panicking when the string is not an integer. Examples: * {lean}`"0".toSlice.toInt! = 0` * {lean}`"5".toSlice.toInt! = 5` * {lean}`"587".toSlice.toInt! = 587` * {lean}`"-587".toSlice.toInt! = -587` -/ @[inline] def toInt! (s : Slice) : Int := match s.toInt? with | some v => v | none => panic "Int expected" /-- Returns the last character in {name}`s`. If {name}`s` is empty, returns {name}`none`. Examples: * {lean}`"abc".toSlice.back? = some 'c'` * {lean}`"".toSlice.back? = none` -/ @[inline, expose] def back? (s : Slice) : Option Char := s.endPos.prev? |>.bind (·.get?) /-- Returns the last character in {name}`s`. If {name}`s` is empty, returns {lean}`(default : Char)`. Examples: * {lean}`"abc".toSlice.back = 'c'` * {lean}`"".toSlice.back = (default : Char)` -/ @[inline, expose] def back (s : Slice) : Char := s.back?.getD default /-- Appends the slices in a list of slices, placing the separator {name}`s` between each pair. Examples: * {lean}`", ".toSlice.intercalate ["red".toSlice, "green".toSlice, "blue".toSlice] = "red, green, blue"` * {lean}`" and ".toSlice.intercalate ["tea".toSlice, "coffee".toSlice] = "tea and coffee"` * {lean}`" | ".toSlice.intercalate ["M".toSlice, "".toSlice, "N".toSlice] = "M | | N"` -/ def intercalate (s : Slice) : List Slice → String | [] => "" | a :: as => go a.copy s as where go (acc : String) (s : Slice) : List Slice → String | a :: as => go (acc ++ s ++ a) s as | [] => acc /-- Appends all the slices in a list of slices, in order. Use {name}`String.Slice.intercalate` to place a separator string between the strings in a list. Examples: * {lean}`String.Slice.join ["gr", "ee", "n"] = "green"` * {lean}`String.Slice.join ["b", "", "l", "", "ue"] = "blue"` * {lean}`String.Slice.join [] = ""` -/ def join (l : List String.Slice) : String := l.foldl (fun (r : String) (s : String.Slice) => r ++ s) "" /-- Converts a string to the Lean compiler's representation of names. The resulting name is hierarchical, and the string is split at the dots ({lean}`'.'`). {lean}`"a.b".toSlice.toName` is the name {lit}`a.b`, not {lit}`«a.b»`. For the latter, use `Name.mkSimple`. -/ def toName (s : Slice) : Lean.Name := s.toString.toName instance : Std.ToFormat String.Slice where format s := Std.ToFormat.format s.copy end String.Slice