# 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 #

 | Parsing errors
#UNPACK#
#UNPACK#
#UNPACK#
#UNPACK#
#UNPACK#
#UNPACK#
#UNPACK#
#UNPACK#
 | Parser class, which specifies the necessary
 primitives for parsing. All other parser combinators
 rely on these primitives.
 | Get file name associated with current parser
 | Get current position of the parser
 | Get reference position used for indentation-sensitive parsing
 | Update reference position with current position
 | Parser which succeeds when the given parser fails
 | Look ahead and return result of the given parser
 The current position stays the same.
 | Parser failure with detailled 'Error'
 | Parser which succeeds at the end of file
 | Annotate the given parser with a label used for error reporting.

 use it to improve the error reports without slowing
 down the fast path of your parser.
 | Hide errors occurring within the given parser
 from the error report. Based on the given
 labels an 'Error' is constructed instead.

 use it to improve the error reports without slowing
 down the fast path of your parser.
 | Reset position if parser fails
 | Alternative which does not backtrack.
 | Parse with error recovery.
 If the parser p fails in `recover p r`
 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.

 use it to improve the error reports without slowing
 down the fast path of your parser.
 | Parse a chunk of elements. The chunk must not
 contain multiple lines, otherwise the position information
 will be invalid.
 | Run the given parser and return the
 result as buffer
 | Parse a single character

 __Note__: The character '\0' cannot be parsed using this combinator
 since it is used as decoding sentinel. Use 'element' instead.
 | Scan a single character

 __Note__: The character '\0' cannot be parsed using this combinator
 since it is used as decoding sentinel. Use 'elementScan' instead.
 | 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.
 | 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.
 | Pretty string representation of 'Error'
 | Parse a string
# 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

data Error
  = EInvalidUtf8
  | EExpected         [String]
  | EUnexpected       String
  | EFail             String
  deriving (Eq, Ord, Show, Generic)

  | Line and column position starting at ( 1,1 )
data Pos = Pos
  } deriving (Eq, Show, Generic)

infixl 3 <!>

class (Fail.MonadFail p, MonadPlus p, Chunk k, IsString (p k)) => Parser k p | p -> k where
  getFile :: p FilePath

  getPos :: p Pos

  getRefPos :: p Pos

  withRefPos :: p a -> p a

  notFollowedBy :: Show a => p a -> p ()

  lookAhead :: p a -> p a

  failWith :: Error -> p a

  eof :: p ()

  _ _ Note _ _ : This function has zero cost in the ' Acceptor ' . You can
  label :: String -> p a -> p a

  _ _ Note _ _ : This function has zero cost in the ' Acceptor ' . You can
  hidden :: p a -> p a

  try :: p a -> p a

  (<!>) :: p a -> p a -> p a

  the parser r continues at the position where p failed .
  _ _ Note _ _ : This function has zero cost in the ' Acceptor ' . You can
  recover :: p a -> p a -> p a

  chunk :: k -> p k

  asChunk :: p () -> p k

  char :: Char -> p Char

  scan :: (Char -> Maybe a) -> p a

  asciiByte :: Word8 -> p Word8

  asciiScan :: (Word8 -> Maybe a) -> p a

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"

string :: Parser k p => String -> p k
string t = chunk (stringToChunk t)
# INLINE string #
