packages feed

pro-source-0.1.0.0: src/ProSource/LineMap.hs

{- |
Description: Binary-search tree for finding the position of new lines.
Copyright: © 2019 James Alexander Feldman-Crough
License: MPL-2.0
-}
module ProSource.LineMap
    ( LineMap
    , lineOffsets
    , lineToOffset
    , offsetToLine
    , fromOffsets
    ) where

import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as V

import ProSource.Units

-- | A dense vector containing offsets poiting to the start of each line. That is, the starting position of the third line of a file can be found at position 2.
newtype LineMap = LineMap (Vector Offset)
    deriving stock (Eq, Generic)
    deriving newtype (Show, NFData)

instance Hashable LineMap where
    hashWithSalt salt (LineMap v) = V.foldl' hashWithSalt salt v

fromOffsets :: Foldable f => f Offset -> LineMap
fromOffsets = LineMap . V.fromList . sort . toList

-- | Convert a 'LineMap' into a list of 'Offset's, corresponding to the first character of a line. Note that the initial offset is omitted-- the offset at index 0 will be the offset of the /second/ line.
lineOffsets :: LineMap -> [Offset]
lineOffsets (LineMap v) = V.toList v

-- | Fetch the 'Offset' for the given 'Line'. Evaluates to 'Nothing' if the given 'Line' does not appear in the LineMap
lineToOffset :: Line -> LineMap -> Maybe Offset
lineToOffset = \case
    Line 0 -> \_ -> Just $ Offset 0
    Line nth -> \(LineMap xs) -> xs V.!? fromIntegral (pred nth)

-- | Fetch the 'Line' number for a given 'Offset'. Newlines will be attributed the line that they terminate, rather than the line started immediately afterwards.
offsetToLine :: Offset -> LineMap -> Line
offsetToLine offset (LineMap xs) = Line $ fromIntegral $ go Nothing 0 (V.length xs)
  where
    go result min max | min >= max = maybe 0 succ result
    go result min max = case compare nthOffset offset of
        EQ -> succ nthIndex
        LT -> go (Just nthIndex) (nthIndex + 1) max
        GT -> go result min nthIndex
      where
        nthIndex  = ((max - min) `div` 2) + min
        nthOffset = xs V.! nthIndex