{-| Module : Language.Rust.Data.Position Description : Positions and spans in files Copyright : (c) Alec Theriault, 2017-2018 License : BSD-style Maintainer : alec.theriault@gmail.com Stability : experimental Portability : GHC Everything to do with describing a position or a contiguous region in a file. -} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveAnyClass #-} module Language.Rust.Data.Position ( -- * Positions in files Position(..), prettyPosition, maxPos, minPos, initPos, incPos, retPos, incOffset, -- * Spans in files Span(..), unspan, prettySpan, subsetOf, (#), Spanned(..), Located(..), ) where import GHC.Generics ( Generic ) import Control.DeepSeq ( NFData ) import Data.Data ( Data ) import Data.Typeable ( Typeable ) import Data.List.NonEmpty ( NonEmpty(..) ) import Data.Monoid ( Monoid(..) ) import Data.Semigroup ( Semigroup(..) ) -- | A position in a source file. The row and column information is kept only for its convenience -- and human-readability. Analogous to the information encoded in a cursor. data Position = Position { absoluteOffset :: {-# UNPACK #-} !Int, -- ^ absolute offset the source file. row :: {-# UNPACK #-} !Int, -- ^ row (line) in the source file. col :: {-# UNPACK #-} !Int -- ^ column in the source file. } | NoPosition deriving (Eq, Ord, Data, Typeable, Generic, NFData) -- | Field names are not shown instance Show Position where showsPrec _ NoPosition = showString "NoPosition" showsPrec p (Position a r c) = showParen (p >= 11) ( showString "Position" . showString " " . showsPrec 11 a . showString " " . showsPrec 11 r . showString " " . showsPrec 11 c ) -- | Pretty print a 'Position' prettyPosition :: Position -> String prettyPosition NoPosition = "???" prettyPosition (Position _ r c) = show r ++ ":" ++ show c -- | Maximum of two positions, bias for actual positions. -- -- >>> maxPos (Position 30 5 8) (Position 37 5 15) -- Position 37 5 15 -- -- >>> maxPos NoPosition (Position 30 5 8) -- Position 30 5 8 -- {-# INLINE maxPos #-} maxPos :: Position -> Position -> Position maxPos NoPosition p2 = p2 maxPos p1 NoPosition = p1 maxPos p1@(Position a1 _ _) p2@(Position a2 _ _) = if a1 > a2 then p1 else p2 -- | Maximum and minimum positions, bias for actual positions. -- -- >>> minPos (Position 30 5 8) (Position 37 5 15) -- Position 30 5 8 -- -- >>> minPos NoPosition (Position 30 5 8) -- Position 30 5 8 -- {-# INLINE minPos #-} minPos :: Position -> Position -> Position minPos NoPosition p2 = p2 minPos p1 NoPosition = p1 minPos p1@(Position a1 _ _) p2@(Position a2 _ _) = if a1 < a2 then p1 else p2 -- | Starting position in a file. {-# INLINE initPos #-} initPos :: Position initPos = Position 0 1 0 -- | Advance column a certain number of times. {-# INLINE incPos #-} incPos :: Position -> Int -> Position incPos NoPosition _ = NoPosition incPos p@Position{ absoluteOffset = a, col = c } offset = p { absoluteOffset = a + offset, col = c + offset } -- | Advance to the next line. {-# INLINE retPos #-} retPos :: Position -> Position retPos NoPosition = NoPosition retPos (Position a r _) = Position { absoluteOffset = a + 1, row = r + 1, col = 1 } -- | Advance only the absolute offset, not the row and column information. Only use this if you -- know what you are doing! {-# INLINE incOffset #-} incOffset :: Position -> Int -> Position incOffset NoPosition _ = NoPosition incOffset p@Position{ absoluteOffset = a } offset = p { absoluteOffset = a + offset } -- | Spans represent a contiguous region of code, delimited by two 'Position's. The endpoints are -- inclusive. Analogous to the information encoded in a selection. data Span = Span { lo, hi :: !Position } deriving (Eq, Ord, Data, Typeable, Generic, NFData) -- | Field names are not shown instance Show Span where showsPrec p (Span l h) = showParen (p >= 11) ( showString "Span" . showString " " . showsPrec 11 l . showString " " . showsPrec 11 h ) -- | Check if a span is a subset of another span subsetOf :: Span -> Span -> Bool Span l1 h1 `subsetOf` Span l2 h2 = minPos l1 l2 == l1 && maxPos h1 h2 == h2 -- | Convenience function lifting '<>' to work on all 'Located' things {-# INLINE (#) #-} (#) :: (Located a, Located b) => a -> b -> Span left # right = spanOf left <> spanOf right -- | smallest covering 'Span' instance Semigroup Span where {-# INLINE (<>) #-} Span l1 h1 <> Span l2 h2 = Span (l1 `minPos` l2) (h1 `maxPos` h2) instance Monoid Span where {-# INLINE mempty #-} mempty = Span NoPosition NoPosition {-# INLINE mappend #-} mappend = (<>) -- | Pretty print a 'Span' prettySpan :: Span -> String prettySpan (Span lo' hi') = show lo' ++ " - " ++ show hi' -- | A "tagging" of something with a 'Span' that describes its extent. data Spanned a = Spanned a {-# UNPACK #-} !Span deriving (Eq, Ord, Data, Typeable, Generic, NFData) -- | Extract the wrapped value from 'Spanned' {-# INLINE unspan #-} unspan :: Spanned a -> a unspan (Spanned x _) = x instance Functor Spanned where {-# INLINE fmap #-} fmap f (Spanned x s) = Spanned (f x) s instance Applicative Spanned where {-# INLINE pure #-} pure x = Spanned x mempty {-# INLINE (<*>) #-} Spanned f s1 <*> Spanned x s2 = Spanned (f x) (s1 <> s2) instance Monad Spanned where return = pure Spanned x s1 >>= f = let Spanned y s2 = f x in Spanned y (s1 <> s2) instance Show a => Show (Spanned a) where show = show . unspan -- | Describes nodes that can be located - their span can be extracted from them. In general, we -- expect that for a value constructed as @Con x y z@ where @Con@ is an arbitrary constructor -- -- prop> (spanOf x <> spanOf y <> spanOf z) `subsetOf` spanOf (Con x y z) == True -- class Located a where spanOf :: a -> Span instance Located Span where {-# INLINE spanOf #-} spanOf = id instance Located (Spanned a) where {-# INLINE spanOf #-} spanOf (Spanned _ s) = s instance Located a => Located (Maybe a) where {-# INLINE spanOf #-} spanOf = foldMap spanOf -- | /O(n)/ time complexity instance Located a => Located [a] where {-# INLINE spanOf #-} spanOf = foldMap spanOf -- | /O(n)/ time complexity instance Located a => Located (NonEmpty a) where {-# INLINE spanOf #-} spanOf = foldMap spanOf