ivory-0.1.0.3: src/Ivory/Language/Syntax/Concrete/Location.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
--
-- Ivory concrete syntax locations.
--
-- Copyright (C) 2014, Galois, Inc.
-- All rights reserved.
--
module Ivory.Language.Syntax.Concrete.Location where
import Prelude ()
import Prelude.Compat
import Control.Monad (mplus)
import Data.Function (on)
import Data.List (foldl')
import Data.Maybe (maybeToList)
import Data.Monoid.Compat ((<>))
import qualified Text.PrettyPrint as P
import Ivory.Language.Syntax.Concrete.Pretty
-- Located Things --------------------------------------------------------------
class HasLocation a where
getLoc :: a -> SrcLoc
stripLoc :: a -> a
instance HasLocation a => HasLocation [a] where
{-# INLINE getLoc #-}
getLoc = foldMap getLoc
{-# INLINE stripLoc #-}
stripLoc = fmap stripLoc
instance (HasLocation a, HasLocation b) => HasLocation (a,b) where
{-# INLINE getLoc #-}
getLoc (a,b) = getLoc a `mappend` getLoc b
{-# INLINE stripLoc #-}
stripLoc (a,b) = (stripLoc a, stripLoc b)
instance HasLocation a => HasLocation (Maybe a) where
{-# INLINE getLoc #-}
getLoc = foldMap getLoc
{-# INLINE stripLoc #-}
stripLoc = fmap stripLoc
locStart :: HasLocation a => a -> Position
locStart a = srcStart (getLoc a)
locEnd :: HasLocation a => a -> Position
locEnd a = srcEnd (getLoc a)
-- | Things that carry a range in the source syntax.
data Located a = Located
{ locRange :: !SrcLoc
, locValue :: a
} deriving (Show,Read,Functor,Ord,Eq,Foldable,Traversable)
instance HasLocation (Located a) where
{-# INLINE getLoc #-}
getLoc = locRange
{-# INLINE stripLoc #-}
stripLoc l = l { locRange = NoLoc }
-- | Attach no location information to a value.
noLoc :: a -> Located a
noLoc a = Located
{ locRange = NoLoc
, locValue = a
}
-- | Attach location information to a value.
at :: HasLocation loc => a -> loc -> Located a
at a loc = Located
{ locRange = getLoc loc
, locValue = a
}
-- | `at` helper for binary operators.
atBin :: (HasLocation loc0, HasLocation loc1)
=> a -> loc0 -> loc1 -> Located a
atBin a l0 l1 = a `at` (getLoc l0 <> getLoc l1)
-- | `at` helper for list args.
atList :: a -> [SrcLoc] -> Located a
atList a locs = a `at` mconcat locs
-- | Strip off location information.
unLoc :: Located a -> a
unLoc = locValue
-- | Extend the range of a located thing.
extendLoc :: SrcLoc -> Located a -> Located a
extendLoc r loc = loc { locRange = locRange loc `mappend` r }
-- Source Locations ------------------------------------------------------------
-- | Source locations.
type Source = Maybe String
-- | A range in the program source.
data SrcLoc = NoLoc | SrcLoc !Range Source
deriving (Show,Read,Ord,Eq)
instance Pretty SrcLoc where
pretty NoLoc = P.lbrack P.<+> P.text "No location available" P.<+> P.rbrack
pretty (SrcLoc rng msrc) =
case msrc of
Nothing -> pretty rng
Just src -> pretty src P.<> P.colon P.<> pretty rng
instance HasLocation SrcLoc where
{-# INLINE getLoc #-}
getLoc = id
{-# INLINE stripLoc #-}
stripLoc _ = NoLoc
instance Monoid SrcLoc where
mempty = NoLoc
-- widen source ranges, and prefer source names from the left
mappend (SrcLoc lr ls) (SrcLoc rr rs) = SrcLoc (mappend lr rr) (mplus ls rs)
mappend NoLoc r = r
mappend l NoLoc = l
-- | Get info to build a line pragma from a 'SrcLoc'. Returns a value only if
-- there is a valid range. Returns the starting line number.
srcLoclinePragma :: SrcLoc -> Maybe (Int, String)
srcLoclinePragma srcloc = case srcloc of
NoLoc -> Nothing
SrcLoc _ src -> Just ( posLine (srcStart srcloc)
, concat (maybeToList src))
srcRange :: SrcLoc -> Range
srcRange loc = case loc of
SrcLoc r _ -> r
NoLoc -> mempty
-- | Starting Position of a 'SrcLoc'.
srcStart :: SrcLoc -> Position
srcStart loc = case loc of
SrcLoc r _ -> rangeStart r
NoLoc -> zeroPosition
-- | Ending Position of a 'SrcLoc'.
srcEnd :: SrcLoc -> Position
srcEnd loc = case loc of
SrcLoc r _ -> rangeStart r
NoLoc -> zeroPosition
-- Ranges ----------------------------------------------------------------------
-- | The region between to source positions.
data Range = Range
{ rangeStart :: !Position
, rangeEnd :: !Position
} deriving (Show,Read,Eq,Ord)
instance Monoid Range where
mempty = Range zeroPosition zeroPosition
-- widen the range
mappend (Range ls le) (Range rs re) = Range (smallerOf ls rs) (largerOf le re)
instance Pretty Range where
pretty (Range st end) =
if st == end
then pretty st
else pretty st P.<+> P.char '-' P.<+> pretty end
-- Positions -------------------------------------------------------------------
-- | Position information within a source.
data Position = Position
{ posOff :: !Int
, posLine :: !Int
, posCol :: !Int
} deriving (Show,Read,Eq)
-- | This only compares offset, assuming that the positions come from the same
-- source.
instance Ord Position where
compare = compare `on` posOff
instance Pretty Position where
pretty (Position _ ln col) =
pretty ln P.<> P.colon P.<> pretty col
-- | Starting position.
zeroPosition :: Position
zeroPosition = Position
{ posOff = 0
, posLine = 1
, posCol = 1
}
-- | Return smaller of the two positions, taking care to not allow the zero
-- position to dominate.
smallerOf :: Position -> Position -> Position
smallerOf l r
| l < r && l /= zeroPosition = l
| otherwise = r
-- | Return the larger of the two positions.
largerOf :: Position -> Position -> Position
largerOf l r
| l > r = l
| otherwise = r
-- | Given a character, increment a position.
movePos :: Position -> Char -> Position
movePos (Position off line col) c =
case c of
'\t' -> Position (off+1) line (col+8)
'\n' -> Position (off+1) (line+1) 1
_ -> Position (off+1) line (col+1)
-- | Move many characters at once.
movesPos :: Position -> String -> Position
movesPos pos = foldl' movePos pos