This PR introduces safe alternatives to `String.Pos` and `Substring` that can only represent valid positions/slices. Specifically, the PR - introduces the predicate `String.Pos.IsValid`; - proves several nontrivial equivalent conditions for `String.Pos.IsValid`; - introduces `String.ValidPos`, which is a `String.Pos` with an `IsValid` proof; - introduces `String.Slice`, which is like `Substring` but made from `String.ValidPos` instead of `Pos`; - introduces `String.Pos.IsValidForSlice`, which is like `String.Pos.IsValid` but for slices; - introduces `String.Slice.Pos`, which is like `String.ValidPos` but for slices; - introduces various functions for converting between the two types of positions. The API added in this PR is not complete. It will be expanded in future PRs with addional operations and verification.
128 lines
5.3 KiB
Text
128 lines
5.3 KiB
Text
/-
|
|
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
Authors: Leonardo de Moura and Sebastian Ullrich
|
|
-/
|
|
module
|
|
|
|
prelude
|
|
public import Init.Meta
|
|
public import Init.Data.String.Extra
|
|
|
|
/-!
|
|
Here we give the. implementation of `Name.toString`. There is also a private implementation in
|
|
`Init.Meta`, which we cannot import this implementation due to import hierarchy limitations.
|
|
|
|
The difference between the two versions is that the one in `Init.Meta` uses the `String.Internal.*`
|
|
functions, while the one here uses the public String functions. These differ in
|
|
that the latter versions have better inferred borrowing annotations, which is significant for an
|
|
inner-loop function like `Name.toString`.
|
|
-/
|
|
|
|
public section
|
|
|
|
namespace Lean.Name
|
|
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
private partial def needsNoEscapeAsciiRest (s : String) (i : Nat) : Bool :=
|
|
if h : i < s.utf8ByteSize then
|
|
let c := s.getUtf8Byte ⟨i⟩ h
|
|
isIdRestAscii c && needsNoEscapeAsciiRest s (i + 1)
|
|
else
|
|
true
|
|
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
@[inline] private def needsNoEscapeAscii (s : String) (h : s.utf8ByteSize > 0) : Bool :=
|
|
let c := s.getUtf8Byte 0 h
|
|
isIdFirstAscii c && needsNoEscapeAsciiRest s 1
|
|
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
@[inline] private def needsNoEscape (s : String) (h : s.utf8ByteSize > 0) : Bool :=
|
|
needsNoEscapeAscii s h || isIdFirst (s.get 0) && (s.toSubstring.drop 1).all isIdRest
|
|
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
@[inline] private def escape (s : String) : String :=
|
|
idBeginEscape.toString ++ s ++ idEndEscape.toString
|
|
|
|
/--
|
|
Creates a round-trippable string name component if possible, otherwise returns `none`.
|
|
Names that are valid identifiers are not escaped, and otherwise, if they do not contain `»`, they are escaped.
|
|
- If `force` is `true`, then even valid identifiers are escaped.
|
|
-/
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
@[inline]
|
|
def escapePart (s : String) (force : Bool := false) : Option String :=
|
|
if h : s.utf8ByteSize > 0 then
|
|
if !force && needsNoEscape s h then
|
|
some s
|
|
else if s.any isIdEndEscape then
|
|
none
|
|
else
|
|
some <| escape s
|
|
else
|
|
some <| escape s
|
|
|
|
variable (sep : String) (escape : Bool) in
|
|
/--
|
|
Uses the separator `sep` (usually `"."`) to combine the components of the `Name` into a string.
|
|
See the documentation for `Name.toStringWithToken` for an explanation of `escape` and `isToken`.
|
|
-/
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
@[specialize isToken] -- explicit annotation because isToken is overridden in recursive call
|
|
def toStringWithSep (n : Name) (isToken : String → Bool := fun _ => false) : String :=
|
|
match n with
|
|
| anonymous => "[anonymous]"
|
|
| str anonymous s => maybeEscape s (isToken s)
|
|
| num anonymous v => toString v
|
|
| str n s =>
|
|
-- Escape the last component if the identifier would otherwise be a token
|
|
let r := toStringWithSep n isToken
|
|
let r' := r ++ sep ++ (maybeEscape s false)
|
|
if escape && isToken r' then r ++ sep ++ (maybeEscape s true) else r'
|
|
| num n v => toStringWithSep n (isToken := fun _ => false) ++ sep ++ Nat.repr v
|
|
where
|
|
maybeEscape s force := if escape then escapePart s force |>.getD s else s
|
|
|
|
/--
|
|
Converts a name to a string.
|
|
|
|
- If `escape` is `true`, then escapes name components using `«` and `»` to ensure that
|
|
those names that can appear in source files round trip.
|
|
Names with number components, anonymous names, and names containing `»` might not round trip.
|
|
Furthermore, "pseudo-syntax" produced by the delaborator, such as `_`, `#0` or `?u`, is not escaped.
|
|
- The optional `isToken` function is used when `escape` is `true` to determine whether more
|
|
escaping is necessary to avoid parser tokens.
|
|
The insertion algorithm works so long as parser tokens do not themselves contain `«` or `»`.
|
|
-/
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
@[specialize]
|
|
def toStringWithToken (n : Name) (escape := true) (isToken : String → Bool) : String :=
|
|
-- never escape "prettified" inaccessible names or macro scopes or pseudo-syntax introduced by the delaborator
|
|
toStringWithSep "." (escape && !n.isInaccessibleUserName && !n.hasMacroScopes && !maybePseudoSyntax) n isToken
|
|
where
|
|
maybePseudoSyntax :=
|
|
if n == `_ then
|
|
-- output hole as is
|
|
true
|
|
else if let .str _ s := n.getRoot then
|
|
-- could be pseudo-syntax for loose bvar or universe mvar, output as is
|
|
"#".isPrefixOf s || "?".isPrefixOf s
|
|
else
|
|
false
|
|
|
|
/--
|
|
Converts a name to a string.
|
|
|
|
- If `escape` is `true`, then escapes name components using `«` and `»` to ensure that
|
|
those names that can appear in source files round trip.
|
|
Names with number components, anonymous names, and names containing `»` might not round trip.
|
|
Furthermore, "pseudo-syntax" produced by the delaborator, such as `_`, `#0` or `?u`, is not escaped.
|
|
-/
|
|
-- If you change this, also change the corresponding function in `Init.Meta`.
|
|
protected def toString (n : Name) (escape := true) : String :=
|
|
Name.toStringWithToken n escape (fun _ => false)
|
|
|
|
instance : ToString Name where
|
|
toString n := n.toString
|
|
|
|
end Lean.Name
|