content: -----------------------------------------------------------------------------
  -- |
  -- Module      :  Position
  -- Copyright   :  2000-2004 Malcolm Wallace
  -- Licence     :  LGPL
  --
  -- Maintainer  :  Malcolm Wallace <Malcolm.Wallace@cs.york.ac.uk>
  -- Stability   :  experimental
  -- Portability :  All
  --
  -- Simple file position information, with recursive inclusion points.
  -----------------------------------------------------------------------------
  
  module Language.Preprocessor.Cpphs.Position
    ( Posn(..)
    , newfile
    , addcol, newline, tab, newlines, newpos
    , cppline, haskline, cpp2hask
    , filename, lineno, directory
    , cleanPath
    ) where
  
  import Data.List (isPrefixOf)
  
  -- | Source positions contain a filename, line, column, and an
  --   inclusion point, which is itself another source position,
  --   recursively.
  data Posn = Pn String !Int !Int (Maybe Posn)
          deriving (Eq)
  
  instance Show Posn where
        showsPrec _ (Pn f l c i) = showString f .
                                   showString "  at line " . shows l .
                                   showString " col " . shows c .
                                   ( case i of
                                      Nothing -> id
                                      Just p  -> showString "\n    used by  " .
                                                 shows p )
  
  -- | Constructor.  Argument is filename.
  newfile :: String -> Posn
  newfile name = Pn (cleanPath name) 1 1 Nothing
  
  -- | Increment column number by given quantity.
  addcol :: Int -> Posn -> Posn
  addcol n (Pn f r c i) = Pn f r (c+n) i
  
  -- | Increment row number, reset column to 1.
  newline :: Posn -> Posn
  --newline (Pn f r _ i) = Pn f (r+1) 1 i
  newline (Pn f r _ i) = let r' = r+1 in r' `seq` Pn f r' 1 i
  
  -- | Increment column number, tab stops are every 8 chars.
  tab     :: Posn -> Posn
  tab     (Pn f r c i) = Pn f r (((c`div`8)+1)*8) i
  
  -- | Increment row number by given quantity.
  newlines :: Int -> Posn -> Posn
  newlines n (Pn f r _ i) = Pn f (r+n) 1 i
  
  -- | Update position with a new row, and possible filename.
  newpos :: Int -> Maybe String -> Posn -> Posn
  newpos r Nothing  (Pn f _ c i) = Pn f r c i
  newpos r (Just ('"':f)) (Pn _ _ c i) = Pn (init f) r c i
  newpos r (Just f)       (Pn _ _ c i) = Pn f r c i
  
  -- | Project the line number.
  lineno    :: Posn -> Int
  -- | Project the filename.
  filename  :: Posn -> String
  -- | Project the directory of the filename.
  directory :: Posn -> FilePath
  
  lineno    (Pn _ r _ _) = r
  filename  (Pn f _ _ _) = f
  directory (Pn f _ _ _) = dirname f
  
  
  -- | cpp-style printing of file position
  cppline :: Posn -> String
  cppline (Pn f r _ _) = "#line "++show r++" "++show f
  
  -- | haskell-style printing of file position
  haskline :: Posn -> String
  haskline (Pn f r _ _) = "{-# LINE "++show r++" "++show f++" #-}"
  
  -- | Conversion from a cpp-style "#line" to haskell-style pragma.
  cpp2hask :: String -> String
  cpp2hask line | "#line" `isPrefixOf` line = "{-# LINE "
                                              ++unwords (tail (words line))
                                              ++" #-}"
                | otherwise = line
  
  -- | Strip non-directory suffix from file name (analogous to the shell
  --   command of the same name).
  dirname :: String -> String
  dirname  = reverse . safetail . dropWhile (not.(`elem`"\\/")) . reverse
    where safetail [] = []
          safetail (_:x) = x
  
  -- | Sigh.  Mixing Windows filepaths with unix is bad.  Make sure there is a
  --   canonical path separator.
  cleanPath :: FilePath -> FilePath
  cleanPath [] = []
  cleanPath ('\\':cs) = '/': cleanPath cs
  cleanPath (c:cs)    = c:   cleanPath cs
  