content: {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE FunctionalDependencies #-}
  module Text.PariPari.Internal.Class (
    Parser(..)
    , Alternative(..)
    , MonadPlus
    , Pos(..)
    , Error(..)
    , showError
    , expectedEnd
    , unexpectedEnd
    , string
  ) where
  
  import Control.Applicative (Alternative(empty, (<|>)))
  import Control.Monad (MonadPlus(..))
  import Data.List (intercalate)
  import Data.String (IsString)
  import Data.Word (Word8)
  import GHC.Generics (Generic)
  import Text.PariPari.Internal.Chunk
  import qualified Control.Monad.Fail as Fail
  
  -- | Parsing errors
  data Error
    = EInvalidUtf8
    | EExpected         [String]
    | EUnexpected       String
    | EFail             String
    | EIndentNotAligned {-#UNPACK#-}!Int {-#UNPACK#-}!Int
    | EIndentOverLine   {-#UNPACK#-}!Int {-#UNPACK#-}!Int
    | ENotEnoughIndent  {-#UNPACK#-}!Int {-#UNPACK#-}!Int
    deriving (Eq, Ord, Show, Generic)
  
  -- | Line and column position starting at (1,1)
  data Pos = Pos
    { _posLine   :: {-#UNPACK#-}!Int
    , _posCol :: {-#UNPACK#-}!Int
    } deriving (Eq, Show, Generic)
  
  infixl 3 <!>
  
  -- | Parser class, which specifies the necessary
  -- primitives for parsing. All other parser combinators
  -- rely on these primitives.
  class (Fail.MonadFail p, MonadPlus p, Chunk k, IsString (p k)) => Parser k p | p -> k where
    -- | Get file name associated with current parser
    getFile :: p FilePath
  
    -- | Get current position of the parser
    getPos :: p Pos
  
    -- | Get reference position used for indentation-sensitive parsing
    getRefPos :: p Pos
  
    -- | Update reference position with current position
    withRefPos :: p a -> p a
  
    -- | Parser which succeeds when the given parser fails
    notFollowedBy :: Show a => p a -> p ()
  
    -- | Look ahead and return result of the given parser
    -- The current position stays the same.
    lookAhead :: p a -> p a
  
    -- | Parser failure with detailled 'Error'
    failWith :: Error -> p a
  
    -- | Parser which succeeds at the end of file
    eof :: p ()
  
    -- | Annotate the given parser with a label used for error reporting.
    --
    -- __Note__: This function has zero cost in the 'Acceptor'. You can
    -- use it to improve the error reports without slowing
    -- down the fast path of your parser.
    label :: String -> p a -> p a
  
    -- | Hide errors occurring within the given parser
    -- from the error report. Based on the given
    -- labels an 'Error' is constructed instead.
    --
    -- __Note__: This function has zero cost in the 'Acceptor'. You can
    -- use it to improve the error reports without slowing
    -- down the fast path of your parser.
    hidden :: p a -> p a
  
    -- | Reset position if parser fails
    try :: p a -> p a
  
    -- | Alternative which does not backtrack.
    (<!>) :: p a -> p a -> p a
  
    -- | Parse with error recovery.
    -- If the parser p fails in `recover p r`
    -- the parser r continues at the position where p failed.
    -- If the recovering parser r fails too, the whole
    -- parser fails. The errors reported by the recovering
    -- parser are ignored in any case.
    -- Error recovery support is only available
    -- in the 'Reporter' instance.
    --
    -- __Note__: This function has zero cost in the 'Acceptor'. You can
    -- use it to improve the error reports without slowing
    -- down the fast path of your parser.
    recover :: p a -> p a -> p a
  
    -- | Parse a chunk of elements. The chunk must not
    -- contain multiple lines, otherwise the position information
    -- will be invalid.
    chunk :: k -> p k
  
    -- | Run the given parser and return the
    -- result as buffer
    asChunk :: p () -> p k
  
    -- | Parse a single character
    --
    -- __Note__: The character '\0' cannot be parsed using this combinator
    -- since it is used as decoding sentinel. Use 'element' instead.
    char :: Char -> p Char
  
    -- | Scan a single character
    --
    -- __Note__: The character '\0' cannot be parsed using this combinator
    -- since it is used as decoding sentinel. Use 'elementScan' instead.
    scan :: (Char -> Maybe a) -> p a
  
    -- | Parse a single character within the ASCII charset
    --
    -- __Note__: The character '\0' cannot be parsed using this combinator
    -- since it is used as decoding sentinel. Use 'element' instead.
    asciiByte :: Word8 -> p Word8
  
    -- | Scan a single character within the ASCII charset
    --
    -- __Note__: The character '\0' cannot be parsed using this combinator
    -- since it is used as decoding sentinel. Use 'elementScan' instead.
    asciiScan :: (Word8 -> Maybe a) -> p a
  
  -- | Pretty string representation of 'Error'
  showError :: Error -> String
  showError EInvalidUtf8             = "Invalid UTF-8 character found"
  showError (EExpected tokens)       = "Expected " <> intercalate ", " tokens
  showError (EUnexpected token)      = "Unexpected " <> token
  showError (EFail msg)              = msg
  showError (EIndentNotAligned rc c) = "Invalid alignment, expected column " <> show rc <> " expected, got " <> show c
  showError (EIndentOverLine   rl l) = "Indentation over line, expected line " <> show rl <> ", got " <> show l
  showError (ENotEnoughIndent  rc c) = "Must be indented deeper than column " <> show rc <> ", got column " <> show c
  
  expectedEnd :: Error
  expectedEnd = EExpected ["end of file"]
  
  unexpectedEnd :: Error
  unexpectedEnd = EUnexpected "end of file"
  
  -- | Parse a string
  string :: Parser k p => String -> p k
  string t = chunk (stringToChunk t)
  {-# INLINE string #-}
  