packages feed

alms-0.6.0: src/Data/Loc.hs

-- | Source locations
module Data.Loc (
  -- * Type and constructors
  Loc(..),
  initial, spanLocs, mkBogus, bogus,
  -- * Destructors
  isBogus, startOfLoc, endOfLoc,

  -- * Generic function for clearing source locations everywhere
  scrub, scrubWhen,

  -- ** Type class interface
  Locatable(..), Relocatable(..), (<<@),

  -- * Interface to 'Parsec' and 'TH' source positions
  toSourcePos, fromSourcePos, fromSourcePosSpan, fromTHLoc
) where

import Util.Bogus

import Data.Generics (Typeable, Data, everywhere, mkT)
import Text.ParserCombinators.Parsec.Pos
import qualified Language.Haskell.TH as TH

-- | Source locations
data Loc = Loc {
    file  :: !String,
    line1 :: !Int,
    col1  :: !Int,
    line2 :: !Int,
    col2  :: !Int
  }
  deriving (Eq, Ord, Typeable, Data)

-- | Construct a location spanning two locations; assumes the locations
--   are correctly ordered.
spanLocs :: Loc -> Loc -> Loc
spanLocs loc1 loc2
  | isBogus loc2 = loc1
  | isBogus loc1 = loc2
  | otherwise    =
      Loc (file loc1) (line1 loc1) (col1 loc1) (line2 loc2) (col2 loc2)

-- | Get a single-point location from the start of a span
startOfLoc :: Loc -> Loc
startOfLoc loc = Loc (file loc) (line1 loc) (col1 loc) (line1 loc) (col1 loc)

-- | Get a single-point location from the end of a span
endOfLoc :: Loc -> Loc
endOfLoc loc = Loc (file loc) (line2 loc) (col2 loc) (line2 loc) (col2 loc)

-- | Extract a 'Parsec' source position
toSourcePos :: Loc -> SourcePos
toSourcePos loc = newPos (file loc) (line1 loc) (col1 loc)

-- | Create from a 'Parsec' source position
fromSourcePos :: SourcePos -> Loc
fromSourcePos pos
  = Loc (sourceName pos) (sourceLine pos) (sourceColumn pos)
                         (sourceLine pos) (sourceColumn pos)

-- | Create a span from two 'Parsec' source positions
fromSourcePosSpan :: SourcePos -> SourcePos -> Loc
fromSourcePosSpan pos1 pos2
  = Loc (sourceName pos1) (sourceLine pos1) (sourceColumn pos1)
                          (sourceLine pos2) (sourceColumn pos2)

fromTHLoc :: TH.Loc -> Loc
fromTHLoc loc = Loc (TH.loc_filename loc)
                    (fst (TH.loc_start loc))
                    (snd (TH.loc_start loc))
                    (fst (TH.loc_end loc))
                    (snd (TH.loc_end loc))

-- | The initial location for a named source file
initial :: String -> Loc
initial = fromSourcePos . initialPos

-- | A named bogus location; useful to provide default locations
--   for generated code without losing real locations.
mkBogus :: String -> Loc
mkBogus s = Loc s (-1) (-1) (-1) (-1)

-- | The bogus location.
--   (Avoids need for @Maybe Loc@ and lifting)
instance Bogus Loc where
  bogus    = mkBogus "<bogus>"

instance IsBogus Loc where
  isBogus (Loc _ (-1) _ _ _) = True
  isBogus _                  = False

-- | Class for types that carry source locations
class Locatable a where
  getLoc   :: a -> Loc

-- | Class for types that can have their source locations updated
class Relocatable a where
  setLoc   :: a -> Loc -> a

instance Locatable Loc where
  getLoc   = id

instance Relocatable Loc where
  setLoc a b
    | isBogus b = a
    | otherwise = b

instance Locatable a => Locatable (Maybe a) where
  getLoc Nothing    = bogus
  getLoc (Just a)   = getLoc a

instance Relocatable a => Relocatable (Maybe a) where
  setLoc Nothing _  = Nothing
  setLoc (Just a) l = l `seq` a `seq` Just (setLoc a l)

instance Locatable a => Locatable [a] where
  getLoc = foldr spanLocs bogus . map getLoc

instance (Locatable a, Locatable b) => Locatable (Either a b) where
  getLoc (Left x)  = getLoc x
  getLoc (Right x) = getLoc x

instance (Relocatable a, Relocatable b) => Relocatable (Either a b) where
  setLoc (Left x)  l = Left (setLoc x l)
  setLoc (Right x) l = Right (setLoc x l)

instance (Locatable a, Locatable b) => Locatable (a, b) where
  getLoc (x, y) = getLoc x `spanLocs` getLoc y

instance (Locatable a, Locatable b, Locatable c) =>
         Locatable (a, b, c) where
  getLoc (x, y, z) = getLoc x `spanLocs` getLoc y `spanLocs` getLoc z

instance (Locatable a, Locatable b, Locatable c, Locatable d) =>
         Locatable (a, b, c, d) where
  getLoc (x, y, z, v) = getLoc x `spanLocs` getLoc y `spanLocs` getLoc z
                          `spanLocs` getLoc v

instance (Locatable a, Locatable b, Locatable c, Locatable d, Locatable e) =>
         Locatable (a, b, c, d, e) where
  getLoc (x, y, z, v, w) = getLoc x `spanLocs` getLoc y `spanLocs` getLoc z
                             `spanLocs` getLoc v `spanLocs` getLoc w

instance Relocatable b => Relocatable (a -> b) where
  setLoc f loc x = setLoc (f x) loc

-- | Copy the source location from the second operand to the first
(<<@)  :: (Relocatable a, Locatable b) => a -> b -> a
a <<@ b = setLoc a (getLoc b)

-- | Bogosify all source locations (as far as SYB can find them)
scrub :: Data a => a -> a
scrub = scrubWhen (const True)

-- | Bogosify all source locations satisfying a predicate
--   (as far as SYB can find them)
scrubWhen :: Data a => (Loc -> Bool) -> a -> a
scrubWhen p a = everywhere (mkT bogosify) a where
  bogosify loc | p loc     = bogus
               | otherwise = loc

instance Show Loc where
  showsPrec _ loc
    | isBogus loc = showString (showFile (file loc))
    | otherwise   =
        showString (showFile (file loc)) . showChar ':' . showCoords
    where
    showCoords =
      if line1 loc == line2 loc then
        shows (line1 loc) . showChar ':' . shows (col1 loc) .
        if col1 loc + 1 >= col2 loc then
          id
        else
          showChar '-' . shows (col2 loc)
      else
        shows (line1 loc) .
        showChar ':' . shows (col1 loc) .
        showChar '-' . shows (line2 loc) .
        showChar ':' . shows (col2 loc)
    showFile "-" = "<stdin>"
    showFile s   =
      let shown = show s
       in if shown == '"' : s ++ "\""
            then s
            else shown