-- |
-- Module      :  Text.Megaparsec.Pos
-- Copyright   :  © 2015 Megaparsec contributors
--                © 2007 Paolo Martini
--                © 1999–2001 Daan Leijen
-- License     :  BSD3
--
-- Maintainer  :  Mark Karpov <markkarpov@opmbx.org>
-- Stability   :  experimental
-- Portability :  portable
--
-- Textual source position.

module Text.Megaparsec.Pos
  ( SourcePos
  , sourceName
  , sourceLine
  , sourceColumn
  , incSourceLine
  , incSourceColumn
  , setSourceName
  , setSourceLine
  , setSourceColumn
  , newPos
  , initialPos
  , updatePosChar
  , updatePosString
  , defaultTabWidth )
where

import Data.Data (Data)
import Data.List (foldl')
import Data.Typeable (Typeable)

-- | The abstract data type @SourcePos@ represents source positions. It
-- contains the name of the source (i.e. file name), a line number and a
-- column number. @SourcePos@ is an instance of the 'Show', 'Eq' and 'Ord'
-- class.

data SourcePos = SourcePos
  { -- | Extract the name of the source from a source position.
    sourceName   :: String
    -- | Extract the line number from a source position.
  , sourceLine   :: !Int
    -- | Extract the column number from a source position.
  , sourceColumn :: !Int }
  deriving (Eq, Ord, Data, Typeable)

instance Show SourcePos where
  show (SourcePos n l c)
    | null n    = showLC
    | otherwise = n ++ ":" ++ showLC
      where showLC = show l ++ ":" ++ show c

-- | Create a new 'SourcePos' with the given source name, line number and
-- column number.

newPos :: String -> Int -> Int -> SourcePos
newPos = SourcePos

-- | Create a new 'SourcePos' with the given source name, and line number
-- and column number set to 1, the upper left.

initialPos :: String -> SourcePos
initialPos name = newPos name 1 1

-- | Increment the line number of a source position.

incSourceLine :: SourcePos -> Int -> SourcePos
incSourceLine (SourcePos n l c) d = SourcePos n (l + d) c

-- | Increments the column number of a source position.

incSourceColumn :: SourcePos -> Int -> SourcePos
incSourceColumn (SourcePos n l c) d = SourcePos n l (c + d)

-- | Set the name of the source.

setSourceName :: SourcePos -> String -> SourcePos
setSourceName (SourcePos _ l c) n = SourcePos n l c

-- | Set the line number of a source position.

setSourceLine :: SourcePos -> Int -> SourcePos
setSourceLine (SourcePos n _ c) l = SourcePos n l c

-- | Set the column number of a source position.

setSourceColumn :: SourcePos -> Int -> SourcePos
setSourceColumn (SourcePos n l _) = SourcePos n l

-- | Update a source position given a character. The first argument
-- specifies tab width. If the character is a newline (\'\\n\') the line
-- number is incremented by 1. If the character is a tab (\'\\t\') the
-- column number is incremented to the nearest tab position, i.e. @column +
-- width - ((column - 1) \`rem\` width)@. In all other cases, the column is
-- incremented by 1.
--
-- If given tab width is not positive, 'defaultTabWidth' will be used.

updatePosChar :: Int       -- ^ Tab width
              -> SourcePos -- ^ Initial position
              -> Char      -- ^ Character at the position
              -> SourcePos
updatePosChar width (SourcePos n l c) ch =
  case ch of
    '\n' -> SourcePos n (l + 1) 1
    '\t' -> let w = if width < 1 then defaultTabWidth else width
            in SourcePos n l (c + w - ((c - 1) `rem` w))
    _    -> SourcePos n l (c + 1)

-- | The expression @updatePosString pos s@ updates the source position
-- @pos@ by calling 'updatePosChar' on every character in @s@, i.e.
--
-- > updatePosString width = foldl (updatePosChar width)

updatePosString :: Int       -- ^ Tab width
                -> SourcePos -- ^ Initial position
                -> String    -- ^ String to process
                -> SourcePos
updatePosString w = foldl' (updatePosChar w)

-- | Value of tab width used by default. This is used as fall-back by
-- 'updatePosChar' and possibly in other cases. Always prefer this constant
-- when you want to refer to default tab width because actual value /may/
-- change in future. Current value is @8@.

defaultTabWidth :: Int
defaultTabWidth = 8