    {-# LANGUAGE DeriveDataTypeable, PatternGuards #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Language.C.Data.Position
    -- Copyright   :  (c) [1995..2000] Manuel M. T. Chakravarty
    -- License     :  BSD-style
    -- Maintainer  :  benedikt.huber@gmail.com
    -- Stability   :  experimental
    -- Portability :  ghc
    --
    -- Source code position
    -----------------------------------------------------------------------------
    module Language.C.Data.Position (
      --
      -- source text positions
      --
      Position(Position),initPos,
      posFile,posRow,posColumn,isSourcePos,
      nopos, isNoPos,
      builtinPos, isBuiltinPos,
      internalPos, isInternalPos,
      incPos, tabPos, retPos, adjustPos,
      Pos(..),
    ) where
    import Data.Generics

    -- | uniform representation of source file positions; the order of the arguments
    -- is important as it leads to the desired ordering of source positions
    data Position = Position String         -- file name
            {-# UNPACK #-}   !Int           -- row
            {-# UNPACK #-}   !Int           -- column
      deriving (Eq, Ord, Typeable, Data)

    instance Show Position where
      show pos@(Position fname row col)
        | isNoPos pos = "<no file>"
        | isBuiltinPos pos = "<builtin>"
        | isInternalPos pos = "<internal>"
        | otherwise = show (fname, row, col)
    instance Read Position where
        readsPrec p s = case s of
                           '<' : _  -> readInternal s
                           _        -> map (\((file,row,pos),r) -> (Position file row pos,r)) . readsPrec p $ s
    readInternal :: ReadS Position
    readInternal s | (Just rest) <- readString "<no file>" s = [(nopos,rest)]
                   | (Just rest) <- readString "<builtin>" s = [(builtinPos,rest)]
                   | (Just rest) <- readString "<internal>" s = [(internalPos,rest)]
                   | otherwise                             = []
        where readString [] r = return r
              readString (c:cs) (c':cs') | c == c'    = readString cs cs'
                                         | otherwise = Nothing
              readString (_:_) [] = Nothing

    -- | get the source file of the specified position. Fails unless @isSourcePos pos@.
    posFile :: Position -> String
    posFile (Position fname _ _) = fname

    -- | get the line number of the specified position. Fails unless @isSourcePos pos@
    posRow  :: Position -> Int
    posRow (Position _ row _) = row

    -- | get the column of the specified position. Fails unless @isSourcePos pos@
    posColumn :: Position -> Int
    posColumn (Position _ _ col) = col

    -- | class of type which aggregate a source code location
    class Pos a where
        posOf :: a -> Position

    -- | initialize a Position to the start of the translation unit starting in the given file
    initPos :: FilePath -> Position
    initPos file = Position file 1 1

    -- | returns @True@ if the given position refers to an actual source file
    isSourcePos :: Position -> Bool
    isSourcePos (Position _ row col) = row >= 0 && col >= 0

    -- | no position (for unknown position information)
    nopos :: Position
    nopos  = Position "<no file>" (-1) 0

    isNoPos :: Position -> Bool
    isNoPos (Position _ (-1) 0) = True
    isNoPos _                   = False

    -- | position attached to built-in objects
    --
    builtinPos :: Position
    builtinPos  = Position "<built into the parser>" (-1) 1

    -- | returns @True@ if the given position refers to a builtin definition
    isBuiltinPos :: Position -> Bool
    isBuiltinPos (Position _ (-1) 1) = True
    isBuiltinPos _                   = False

    -- | position used for internal errors
    internalPos :: Position
    internalPos = Position "<internal error>" (-1) 2

    -- | returns @True@ if the given position is internal
    isInternalPos :: Position -> Bool
    isInternalPos (Position _ (-1) 2) = True
    isInternalPos _                      = False

    {-# INLINE incPos #-}
    -- | advance column
    incPos :: Position -> Int -> Position
    incPos (Position fname row col) n = Position fname row (col + n)

    {-# DEPRECATED tabPos "Use 'incPos column-adjustment' instead" #-}
    -- | advance column to next tab positions (tabs are considered to be at every 8th column)
    tabPos :: Position -> Position
    tabPos (Position fname row col) =
            Position fname row (col + 8 - (col - 1) `mod` 8)

    {-# INLINE retPos #-}
    -- | advance to next line
    retPos :: Position -> Position
    retPos (Position fname row _col) = Position fname (row + 1) 1

    {-# INLINE adjustPos #-}
    -- | adjust position: change file and line number, reseting column to 1. This is usually
    --   used for #LINE pragmas.
    adjustPos :: FilePath -> Int -> Position -> Position
    adjustPos fname row (Position _ _ _) = Position fname row 1