phino-0.0.0.53: src/Condition.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE RecordWildCards #-}
-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT
module Condition (parseCondition, parseConditionThrows) where
import Control.Exception (Exception)
import Control.Exception.Base (throwIO)
import Data.Void (Void)
import Parser (PhiParser (..), phiParser)
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Printf (printf)
import qualified Yaml as Y
newtype ConditionException = CouldNotParseCondition {message :: String}
deriving (Exception)
instance Show ConditionException where
show CouldNotParseCondition{..} = printf "Couldn't parse given condition, cause: %s" message
type Parser = Parsec Void String
-- White space consumer
whiteSpace :: Parser ()
whiteSpace = L.space hspace1 empty empty
-- Lexeme that ignores white spaces after
lexeme :: Parser a -> Parser a
lexeme = L.lexeme whiteSpace
-- Strict symbol (or sequence of symbols) with ignored white spaces after
symbol :: String -> Parser String
symbol = L.symbol whiteSpace
lparen :: Parser String
lparen = symbol "("
rparen :: Parser String
rparen = symbol ")"
comma :: Parser String
comma = symbol ","
number :: Parser Y.Number
number =
choice
[ do
_ <- symbol "ordinal" >> lparen
attr <- _attribute phiParser
_ <- rparen
return (Y.Ordinal attr)
, do
_ <- symbol "length" >> lparen
bd <- _binding phiParser
_ <- rparen
return (Y.Length bd)
, do
sign <- optional (choice [char '-', char '+'])
unsigned <- lexeme L.decimal
return
( Y.Literal
( case sign of
Just '-' -> negate unsigned
_ -> unsigned
)
)
]
comparable :: Parser Y.Comparable
comparable =
choice
[ try $ Y.CmpNum <$> number
, try $ Y.CmpAttr <$> _attribute phiParser
, Y.CmpExpr <$> _expression phiParser
]
condition :: Parser Y.Condition
condition =
choice
[ do
_ <- symbol "and" >> lparen
args <- condition `sepBy1` comma
_ <- rparen
return (Y.And args)
, do
_ <- symbol "or" >> lparen
args <- condition `sepBy1` comma
_ <- rparen
return (Y.Or args)
, do
_ <- symbol "in" >> lparen
attr <- _attribute phiParser
_ <- comma
bd <- _binding phiParser
_ <- rparen
return (Y.In attr bd)
, do
_ <- symbol "not" >> lparen
cond <- condition
_ <- rparen
return (Y.Not cond)
, do
_ <- symbol "alpha" >> lparen
attr <- _attribute phiParser
_ <- rparen
return (Y.Alpha attr)
, do
_ <- symbol "eq" >> lparen
left <- comparable
_ <- comma
right <- comparable
_ <- rparen
return (Y.Eq left right)
, do
_ <- symbol "nf" >> lparen
expr <- _expression phiParser
_ <- rparen
return (Y.NF expr)
, do
_ <- symbol "xi" >> lparen
expr <- _expression phiParser
_ <- rparen
return (Y.XI expr)
, do
_ <- symbol "matches" >> lparen
ptn <- _string phiParser
_ <- comma
expr <- _expression phiParser
_ <- rparen
return (Y.Matches ptn expr)
, do
_ <- symbol "part-of" >> lparen
expr <- _expression phiParser
_ <- comma
bd <- _binding phiParser
_ <- rparen
return (Y.PartOf expr bd)
]
parseCondition :: String -> Either String Y.Condition
parseCondition input = do
let parsed =
runParser
( do
_ <- whiteSpace
p <- condition
_ <- eof
return p
)
"condition"
input
case parsed of
Right parsed' -> Right parsed'
Left err -> Left (errorBundlePretty err)
parseConditionThrows :: String -> IO Y.Condition
parseConditionThrows cnd = case parseCondition cnd of
Right condition -> pure condition
Left err -> throwIO (CouldNotParseCondition err)