module Test.Tasty.Patterns.Parser
( Parser
, runParser
, ParseResult(..)
, expr
)
where
import Prelude hiding (Ordering(..))
import Text.ParserCombinators.ReadP hiding (many, optional)
import Text.ParserCombinators.ReadPrec (readPrec_to_P, minPrec)
import Text.Read (readPrec)
import Data.Functor
import Data.Char
import Control.Applicative
import Control.Monad
import Test.Tasty.Patterns.Types
import Test.Tasty.Patterns.Expr
type Token = ReadP
newtype Parser a = Parser (ReadP a)
deriving (Functor, Applicative, Alternative, Monad, MonadPlus)
#if !MIN_VERSION_base(4,6,0)
instance Applicative ReadP where
pure = return
(<*>) = ap
instance Alternative ReadP where
empty = mzero
(<|>) = mplus
#endif
data ParseResult a = Success a | Invalid | Ambiguous [a]
deriving Show
token :: Token a -> Parser a
token a = Parser (a <* skipSpaces)
sym :: Char -> Parser ()
sym = void . token . char
str :: String -> Parser ()
str = void . token . string
runParser
:: Parser a
-> String
-> ParseResult a
runParser (Parser p) s =
case filter (null . snd) $ readP_to_S (skipSpaces *> p) s of
[(a, _)] -> Success a
[] -> Invalid
as -> Ambiguous (fst <$> as)
intP :: Parser Int
intP = token $
read <$> munch1 isDigit
strP :: Parser String
strP = token $ readPrec_to_P readPrec minPrec
patP :: Parser String
patP = token $ char '/' *> many ch <* char '/'
where
ch =
satisfy (`notElem` "/\\") <|>
(char '\\' *> satisfy (`elem` "/\\"))
nfP :: Parser ()
nfP = token $ void $ string "NF"
builtin :: Parser Expr
builtin = msum
[ fn "length" $ LengthFn <$> optional expr
, fn "toupper" $ ToUpperFn <$> expr
, fn "tolower" $ ToLowerFn <$> expr
, fn "match" $ MatchFn <$> expr <* sym ',' <*> patP
, fn "substr" $ SubstrFn <$> expr <* sym ',' <*> expr <*>
optional (sym ',' *> expr)
]
where
fn :: String -> Parser a -> Parser a
fn name args = token (string name) *> sym '(' *> args <* sym ')'
expr0 :: Parser Expr
expr0 =
(sym '(' *> expr <* sym ')') <|>
(IntLit <$> intP) <|>
(StringLit <$> strP) <|>
(ERE <$> patP) <|>
(NF <$ nfP) <|>
builtin
expr1 :: Parser Expr
expr1 = makeExprParser expr0
[ [ Prefix (Field <$ sym '$') ] ]
data Unary = Unary | NonUnary
expr2 :: Unary -> Parser Expr
expr2 unary = makeExprParser expr1
[ [ Prefix (Not <$ sym '!') ] ++
(case unary of
Unary -> [ Prefix (Neg <$ sym '-') ]
NonUnary -> []
)
, [ InfixL (Add <$ sym '+')
, InfixL (Sub <$ sym '-')
]
]
expr3 :: Parser Expr
expr3 = concatExpr <|> expr2 Unary
where
concatExpr = Concat <$> nonUnary <*> (nonUnary <|> concatExpr)
nonUnary = expr2 NonUnary
expr4 :: Parser Expr
expr4 = makeExprParser expr3
[ [ InfixN (LT <$ sym '<')
, InfixN (GT <$ sym '>')
, InfixN (LE <$ str "<=")
, InfixN (GE <$ str ">=")
, InfixN (EQ <$ str "==")
, InfixN (NE <$ str "!=")
]
, [ Postfix (flip Match <$ sym '~' <*> patP)
, Postfix (flip NoMatch <$ str "!~" <*> patP)
]
, [ InfixL (And <$ str "&&") ]
, [ InfixL (Or <$ str "||") ]
, [ TernR ((If <$ sym ':') <$ sym '?') ]
]
expr :: Parser Expr
expr = expr4