-- | Handling simple submission conditions operating on tags and parameters module Data.SubmissionConditions where import Import hiding (many, try) import Control.Monad (void) import Data.Void import Text.Megaparsec import Text.Megaparsec.Char import Text.Megaparsec.Expr import qualified Text.Megaparsec.Char.Lexer as L import Data.Text as T hiding (empty) import Text.Read (readMaybe) import qualified Data.Map as Map data VariantEntry = VariantEntry { variantEntryTags :: [Tag], variantEntryParams :: [Parameter] } type VariantEntryMap = Map Text Text entryToMap :: VariantEntry -> VariantEntryMap entryToMap entry = Map.fromList ((Import.map (\t -> (tagName t, "TAG")) tags) ++ (Import.map (\p -> (parameterName p, parameterValue p)) params)) where tags = variantEntryTags entry params = variantEntryParams entry data SubmissionCondition = Simple SubmissionSimpleCondition | Neg SubmissionCondition | BBinary BBinaryOp SubmissionCondition SubmissionCondition deriving (Show, Eq) data BBinaryOp = And | Or deriving (Show, Eq) data SubmissionSimpleCondition = Existence ValueHolder | SBinary Atom SBinaryOp Atom deriving (Show, Eq) data SBinaryOp = Equal | NotEqual | Less | Greater | LessOrEqual | GreaterOrEqual deriving (Show, Eq) data Atom = ReadFrom ValueHolder | IntegerAtom Integer | DoubleAtom Double | StringAtom Text deriving (Show, Eq) data ValueHolder = ValueHolder Text deriving (Show, Eq) type Parser = Parsec Void Text spaceConsumer = L.space space1 empty empty symbol :: Text -> Parser Text symbol = L.symbol spaceConsumer lexeme :: Parser a -> Parser a lexeme = L.lexeme spaceConsumer parens :: Parser a -> Parser a parens = between (symbol "(") (symbol ")") conditionP :: Parser SubmissionCondition conditionP = makeExprParser bTerm bOperators bTerm = (parens conditionP) <|> (Simple <$> simpleConditionP) bOperators :: [[Operator Parser SubmissionCondition]] bOperators = [ [Prefix (Neg <$ symbol "!") ] , [InfixL (BBinary And <$ symbol "&&") , InfixL (BBinary Or <$ symbol "||") ] ] simpleConditionP :: Parser SubmissionSimpleCondition simpleConditionP = (try (SBinary <$> valP <*> sopP <*> valP)) <|> (Existence <$> valueHolderP) sopP :: Parser SBinaryOp sopP = (try (symbol "==" *> pure Equal)) <|> symbol "=" *> pure Equal <|> symbol "!=" *> pure NotEqual <|> symbol "/=" *> pure NotEqual <|> (try (symbol "<>" *> pure NotEqual)) <|> (try (symbol "<=" *> pure LessOrEqual)) <|> (try (symbol ">=" *> pure GreaterOrEqual)) <|> symbol "<" *> pure Less <|> symbol ">" *> pure Greater valP :: Parser Atom valP = (ReadFrom <$> valueHolderP) <|> (try $ lexeme $ DoubleAtom <$> L.signed spaceConsumer L.float) <|> (lexeme $ IntegerAtom <$> L.signed spaceConsumer L.decimal) <|> (StringAtom <$> literalP) literalP :: Parser Text literalP = quotedP '"' <|> quotedP '\'' quotedP :: Char -> Parser Text quotedP q = lexeme (T.pack <$> ((char q) *> (many $ notChar q) <* (char q))) valueHolderP :: Parser ValueHolder valueHolderP = lexeme $ ValueHolder <$> T.pack <$> ((:) <$> letterChar <*> many (alphaNumChar <|> char '-')) parseCondition :: Text -> Maybe (SubmissionCondition) parseCondition = parseMaybe (spaceConsumer *> conditionP) checkCondition :: Maybe SubmissionCondition -> VariantEntry -> Bool checkCondition mCondition = checkCondition' mCondition . entryToMap checkCondition' :: Maybe SubmissionCondition -> VariantEntryMap -> Bool checkCondition' Nothing _ = False checkCondition' (Just condition) entry = checkCondition'' condition entry checkCondition'' :: SubmissionCondition -> VariantEntryMap -> Bool checkCondition'' (Simple simpleCondition) entry = checkSimpleCondition simpleCondition entry checkCondition'' (Neg condition) entry = not (checkCondition'' (condition) entry) checkCondition'' (BBinary op condA condB) entry = (rfy op) (checkCondition'' condA entry) (checkCondition'' condB entry) where rfy And = (&&) rfy Or = (||) data Val = IntegerVal Integer | DoubleVal Double | StringVal Text | NoVal checkSimpleCondition :: SubmissionSimpleCondition -> VariantEntryMap -> Bool checkSimpleCondition (Existence (ValueHolder holder)) entry = Map.member holder entry checkSimpleCondition (SBinary valA op valB) entry = rop (getValue valA entry) (getValue valB entry) where rop NoVal _ = False -- no value always brings false value rop _ NoVal = False -- therefore (x < y) is not equal to !(x >= y) rop (IntegerVal x) (IntegerVal y) = (rfy op) x y rop (IntegerVal x) (DoubleVal y) = (rfy op) (fromIntegral x) y rop (DoubleVal x) (IntegerVal y) = (rfy op) x (fromIntegral y) rop (DoubleVal x) (DoubleVal y) = (rfy op) x y rop (StringVal x) (StringVal y) = (rfy op) x y rop _ _ = False rfy :: Ord a => SBinaryOp -> a -> a -> Bool rfy Equal = (==) rfy NotEqual = (/=) rfy Less = (<) rfy Greater = (>) rfy LessOrEqual = (<=) rfy GreaterOrEqual = (>=) getValue :: Atom -> VariantEntryMap -> Val getValue (IntegerAtom i) _ = IntegerVal i getValue (DoubleAtom d) _ = DoubleVal d getValue (StringAtom t) _ = StringVal t getValue (ReadFrom (ValueHolder holder)) entry = case Map.lookup holder entry of Just v -> let s = T.unpack v in case readMaybe s of Just i -> IntegerVal i Nothing -> case readMaybe s of Just d -> DoubleVal d Nothing -> StringVal v Nothing -> NoVal