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