{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}
module Commonmark.TokParsers
( satisfyTok
, satisfyWord
, anyTok
, anySymbol
, symbol
, whitespace
, lineEnd
, spaceTok
, oneOfToks
, noneOfToks
, gobbleSpaces
, gobbleUpToSpaces
, withRaw
, hasType
, textIs
, blankLine
, restOfLine
, isOneOfCI
, nonindentSpaces
, skipManyTill
, skipWhile
)
where
import Control.Monad (mzero, void)
import Data.Text (Text)
import qualified Data.Text as T
import Text.Parsec
import Text.Parsec.Pos (updatePosString)
import Commonmark.Tokens
satisfyTok :: Monad m => (Tok -> Bool) -> ParsecT [Tok] s m Tok
satisfyTok f = tokenPrim (T.unpack . tokContents) updatePos matcher
where matcher t | f t = Just t
| otherwise = Nothing
updatePos :: SourcePos -> Tok -> [Tok] -> SourcePos
updatePos _spos _ (Tok _ !pos _ : _) = pos
updatePos !spos (Tok _ _pos !t) [] =
updatePosString spos (T.unpack t)
{-# INLINE satisfyTok #-}
anyTok :: Monad m => ParsecT [Tok] s m Tok
anyTok = satisfyTok (const True)
{-# INLINE anyTok #-}
anySymbol :: Monad m => ParsecT [Tok] s m Tok
anySymbol = satisfyTok (\t -> case tokType t of
Symbol _ -> True
_ -> False)
{-# INLINE anySymbol #-}
symbol :: Monad m => Char -> ParsecT [Tok] s m Tok
symbol c = satisfyTok (hasType (Symbol c))
{-# INLINE symbol #-}
oneOfToks :: Monad m => [TokType] -> ParsecT [Tok] s m Tok
oneOfToks toktypes = satisfyTok (hasTypeIn toktypes)
{-# INLINE oneOfToks #-}
noneOfToks :: Monad m => [TokType] -> ParsecT [Tok] s m Tok
noneOfToks toktypes = satisfyTok (not . hasTypeIn toktypes)
{-# INLINE noneOfToks #-}
whitespace :: Monad m => ParsecT [Tok] s m [Tok]
whitespace = many1 $ satisfyTok (\t -> case tokType t of
Spaces -> True
LineEnd -> True
_ -> False)
{-# INLINE whitespace #-}
lineEnd :: Monad m => ParsecT [Tok] s m Tok
lineEnd = satisfyTok (hasType LineEnd)
{-# INLINE lineEnd #-}
spaceTok :: Monad m => ParsecT [Tok] s m Tok
spaceTok = satisfyTok (hasType Spaces)
{-# INLINE spaceTok #-}
satisfyWord :: Monad m => (Text -> Bool) -> ParsecT [Tok] s m Tok
satisfyWord f = satisfyTok (\t -> hasType WordChars t && textIs f t)
{-# INLINE satisfyWord #-}
gobbleSpaces :: Monad m => Int -> ParsecT [Tok] u m Int
gobbleSpaces 0 = return 0
gobbleSpaces n = try $ gobble' True n
{-# INLINE gobbleSpaces #-}
gobbleUpToSpaces :: Monad m => Int -> ParsecT [Tok] u m Int
gobbleUpToSpaces 0 = return 0
gobbleUpToSpaces n = gobble' False n
{-# INLINE gobbleUpToSpaces #-}
gobble' :: Monad m => Bool -> Int -> ParsecT [Tok] u m Int
gobble' requireAll numspaces
| numspaces >= 1 = (do
Tok Spaces pos _ <- satisfyTok (hasType Spaces)
pos' <- getPosition
case sourceColumn pos' - sourceColumn pos of
n | n < numspaces -> (+ n) <$> gobble' requireAll (numspaces - n)
| n == numspaces -> return $! n
| otherwise -> do
let newpos = incSourceColumn pos numspaces
let newtok = Tok Spaces newpos
(T.replicate (n - numspaces) " ")
getInput >>= setInput . (newtok:)
setPosition $ newpos
return $! numspaces)
<|> if requireAll
then mzero
else return 0
| otherwise = return 0
{-# INLINE gobble' #-}
withRaw :: Monad m => ParsecT [Tok] s m a -> ParsecT [Tok] s m (a, [Tok])
withRaw parser = do
toks <- getInput
res <- parser
newpos <- getPosition
let getrawtoks (t:ts)
| tokPos t < newpos = t : getrawtoks ts
getrawtoks _ = []
let rawtoks = getrawtoks toks
return (res, rawtoks)
{-# INLINE withRaw #-}
hasType :: TokType -> Tok -> Bool
hasType ty (Tok ty' _ _) = ty == ty'
{-# INLINE hasType #-}
hasTypeIn :: [TokType] -> Tok -> Bool
hasTypeIn tys (Tok ty' _ _) = ty' `elem` tys
textIs :: (Text -> Bool) -> Tok -> Bool
textIs f (Tok _ _ t) = f t
{-# INLINE textIs #-}
nonindentSpaces :: Monad m => ParsecT [Tok] u m ()
nonindentSpaces = void $ gobbleUpToSpaces 3
{-# INLINE nonindentSpaces #-}
isOneOfCI :: [Text] -> Text -> Bool
isOneOfCI ts t = T.toLower t `elem` ts
{-# INLINE isOneOfCI #-}
skipManyTill :: ParsecT s u m a -> ParsecT s u m b -> ParsecT s u m ()
skipManyTill p stop = scan
where scan = (() <$ stop) <|> (p >> scan)
{-# INLINE skipManyTill #-}
skipWhile :: Monad m => (Tok -> Bool) -> ParsecT [Tok] u m ()
skipWhile f = skipMany (satisfyTok f)
{-# INLINE skipWhile #-}
blankLine :: Monad m => ParsecT [Tok] s m ()
blankLine = try $ do
skipWhile (hasType Spaces)
void lineEnd
{-# INLINE blankLine #-}
restOfLine :: Monad m => ParsecT [Tok] s m [Tok]
restOfLine = go
where
go = option [] $ do
!tok <- anyTok
case tokType tok of
LineEnd -> return [tok]
_ -> (tok:) <$> go
{-# INLINE restOfLine #-}