gonito/Data/SubmissionConditions.hs

184 lines
5.4 KiB
Haskell

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