lean4-htt/src/Lean/Data/Position.lean
Markus Himmel b5dc11e8d3
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.
2025-10-22 16:31:08 +00:00

124 lines
4 KiB
Text

/-
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
module
prelude
public import Lean.Data.Json.FromToJson.Basic
public import Lean.ToExpr
import Init.Data.String.Iterator
public section
namespace Lean
structure Position where
line : Nat
column : Nat
deriving Inhabited, DecidableEq, Repr, ToJson, FromJson
namespace Position
protected def lt : Position → Position → Bool
| ⟨l₁, c₁⟩, ⟨l₂, c₂⟩ => Prod.lexLt (l₁, c₁) (l₂, c₂)
instance : ToFormat Position :=
⟨fun ⟨l, c⟩ => "⟨" ++ format l ++ ", " ++ format c ++ "⟩"⟩
instance : ToString Position :=
⟨fun ⟨l, c⟩ => "⟨" ++ toString l ++ ", " ++ toString c ++ "⟩"⟩
instance : ToExpr Position where
toExpr p := mkAppN (mkConst ``Position.mk) #[toExpr p.line, toExpr p.column]
toTypeExpr := mkConst ``Position
end Position
/-- Content of a file together with precalculated positions of newlines. -/
structure FileMap where
/-- The content of the file. -/
source : String
/-- The positions of newline characters.
The first entry is always `0` and the last always the index of the last character.
In particular, if the last character is a newline, that index will appear twice. -/
positions : Array String.Pos.Raw
deriving Inhabited
class MonadFileMap (m : Type → Type) where
getFileMap : m FileMap
export MonadFileMap (getFileMap)
namespace FileMap
/-- The last line should always be `positions.size - 1`. -/
def getLastLine (fmap : FileMap) : Nat :=
fmap.positions.size - 1
/-- The line numbers associated with the `positions` of the `FileMap`.
`fmap.getLine i` is the iᵗʰ entry of `#[1, 2, …, n-1, n-1]`
where `n` is the `size` of `positions`. -/
def getLine (fmap : FileMap) (x : Nat) : Nat :=
min (x + 1) fmap.getLastLine
partial def ofString (s : String) : FileMap :=
let rec loop (i : String.Pos.Raw) (line : Nat) (ps : Array String.Pos.Raw) : FileMap :=
if i.atEnd s then { source := s, positions := ps.push i }
else
let c := i.get s
let i := i.next s
if c == '\n' then loop i (line+1) (ps.push i)
else loop i line ps
loop 0 1 #[0]
partial def toPosition (fmap : FileMap) (pos : String.Pos.Raw) : Position :=
match fmap with
| { source := str, positions := ps } =>
if ps.size >= 2 && pos <= ps.back! then
let rec toColumn (i : String.Pos.Raw) (c : Nat) : Nat :=
if i == pos || i.atEnd str then c
else toColumn (i.next str) (c+1)
let rec loop (b e : Nat) :=
let posB := ps[b]!
if e == b + 1 then { line := fmap.getLine b, column := toColumn posB 0 }
else
let m := (b + e) / 2;
let posM := ps[m]!
if pos == posM then { line := fmap.getLine m, column := 0 }
else if pos > posM then loop m e
else loop b m
loop 0 (ps.size - 1)
else if ps.isEmpty then
⟨0, 0⟩
else
-- Some systems like the delaborator use synthetic positions without an input file,
-- which would violate `toPositionAux`'s invariant.
-- Can also happen with EOF errors, which are not strictly inside the file.
⟨fmap.getLastLine, ps.back!.byteDistance pos⟩
/-- Convert a `Lean.Position` to a `String.Pos`. -/
def ofPosition (text : FileMap) (pos : Position) : String.Pos.Raw :=
let colPos :=
if h : pos.line - 1 < text.positions.size then
text.positions[pos.line - 1]
else if text.positions.isEmpty then
0
else
text.positions.back!
String.Iterator.nextn ⟨text.source, colPos⟩ pos.column |>.pos
/--
Returns the position of the start of (1-based) line `line`.
This gives the same result as `map.ofPosition ⟨line, 0⟩`, but is more efficient.
-/
def lineStart (map : FileMap) (line : Nat) : String.Pos.Raw :=
if h : line - 1 < map.positions.size then
map.positions[line - 1]
else map.positions.back?.getD 0
end FileMap
end Lean
def String.toFileMap (s : String) : Lean.FileMap :=
Lean.FileMap.ofString s