Parse expressions

Switched to Megaparsec from Parsec.
Also switched eval[...] functions to return Maybe when things like
division by zero would otherwise happen. Wouldn't want any runtime
crashes :P
This commit is contained in:
Joscha 2017-12-17 00:52:41 +00:00
parent cf6424b186
commit 76378b0add

View file

@ -1,16 +1,25 @@
module DateExpr module DateExpr
( BoolExpr ( DateExpr
, parseBoolExpr , parseDateExpr
, firstMatchingDay , firstMatchingDay
, evalBoolExpr , evalDateExpr
) where ) where
import Control.Applicative
import Control.Monad
import Data.List import Data.List
import Data.Maybe
import Data.Time.Calendar import Data.Time.Calendar
import Data.Time.Calendar.WeekDate import Data.Time.Calendar.WeekDate
import Data.Time.Calendar.MonthDay import Data.Time.Calendar.MonthDay
import Data.Time.Calendar.Easter import Data.Time.Calendar.Easter
import Text.Parsec import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Megaparsec.Expr
type DateExpr = BoolExpr
data BoolExpr = BValue Bool data BoolExpr = BValue Bool
| BStatement DateStatement | BStatement DateStatement
@ -21,6 +30,7 @@ data BoolExpr = BValue Bool
| BEq IntExpr IntExpr | BEq IntExpr IntExpr
| BGt IntExpr IntExpr | BGt IntExpr IntExpr
| BLt IntExpr IntExpr | BLt IntExpr IntExpr
deriving (Show)
data IntExpr = IValue Integer data IntExpr = IValue Integer
| IDate SpecialDate | IDate SpecialDate
@ -29,9 +39,12 @@ data IntExpr = IValue Integer
| IMul IntExpr IntExpr | IMul IntExpr IntExpr
| IDiv IntExpr IntExpr -- div, not quot! | IDiv IntExpr IntExpr -- div, not quot!
| IMod IntExpr IntExpr -- mod, not rem! | IMod IntExpr IntExpr -- mod, not rem!
deriving (Show)
data DateStatement = IsLeapYear data DateStatement = IsLeapYear
| IsWeekend | IsWeekend
deriving (Show)
-- possible additions: IsEaster
data SpecialDate = SJulianDay data SpecialDate = SJulianDay
| SYear | SMonth | SDay | SYear | SMonth | SDay
@ -40,32 +53,45 @@ data SpecialDate = SJulianDay
| SYearCount | SYearCount
| SMonthCount | SMonthCount
| SEaster | SEaster
deriving (Show)
{- {-
- Evaluating expressions - Evaluating expressions
-} -}
firstMatchingDay :: BoolExpr -> Int -> Day -> Maybe Day evalDateExpr :: DateExpr -> Day -> Bool
firstMatchingDay expr duration = find (evalBoolExpr expr) . take duration . iterate (addDays 1) evalDateExpr expr = fromMaybe False . evalBoolExpr expr
evalBoolExpr :: BoolExpr -> Day -> Bool firstMatchingDay :: DateExpr -> Int -> Day -> Maybe Day
evalBoolExpr (BValue v) _ = v firstMatchingDay expr duration = find (evalDateExpr expr)
evalBoolExpr (BStatement s) d = evalDateStatement s d . take duration . iterate (addDays 1)
evalBoolExpr (BNot a) d = not $ evalBoolExpr a d
evalBoolExpr (BAnd a b) d = evalBoolExpr a d && evalBoolExpr b d
evalBoolExpr (BOr a b) d = evalBoolExpr a d || evalBoolExpr b d
evalBoolExpr (BEq a b) d = evalIntExpr a d == evalIntExpr b d
evalBoolExpr (BGt a b) d = evalIntExpr a d > evalIntExpr b d
evalBoolExpr (BLt a b) d = evalIntExpr a d < evalIntExpr b d
evalIntExpr :: IntExpr -> Day -> Integer evalBoolExpr :: BoolExpr -> Day -> Maybe Bool
evalIntExpr (IValue v) _ = v evalBoolExpr (BValue v) _ = Just v
evalIntExpr (IDate s) d = evalSpecialDate s d evalBoolExpr (BStatement s) d = Just $ evalDateStatement s d
evalIntExpr (INeg a) d = - evalIntExpr a d evalBoolExpr (BNot a) d = not <$> evalBoolExpr a d
evalIntExpr (IAdd a b) d = evalIntExpr a d + evalIntExpr b d evalBoolExpr (BAnd a b) d = liftA2 (&&) (evalBoolExpr a d) (evalBoolExpr b d)
evalIntExpr (IMul a b) d = evalIntExpr a d * evalIntExpr b d evalBoolExpr (BOr a b) d = liftA2 (||) (evalBoolExpr a d) (evalBoolExpr b d)
evalIntExpr (IDiv a b) d = evalIntExpr a d `div` evalIntExpr b d evalBoolExpr (BEq a b) d = liftA2 (==) (evalIntExpr a d) (evalIntExpr b d)
evalIntExpr (IMod a b) d = evalIntExpr a d `mod` evalIntExpr b d evalBoolExpr (BGt a b) d = liftA2 (>) (evalIntExpr a d) (evalIntExpr b d)
evalBoolExpr (BLt a b) d = liftA2 (<) (evalIntExpr a d) (evalIntExpr b d)
evalIntExpr :: IntExpr -> Day -> Maybe Integer
evalIntExpr (IValue v) _ = Just v
evalIntExpr (IDate s) d = Just $ evalSpecialDate s d
evalIntExpr (INeg a) d = negate <$> evalIntExpr a d
evalIntExpr (IAdd a b) d = liftA2 (+) (evalIntExpr a d) (evalIntExpr b d)
evalIntExpr (IMul a b) d = liftA2 (*) (evalIntExpr a d) (evalIntExpr b d)
evalIntExpr (IDiv a b) d = do
x <- evalIntExpr a d
y <- evalIntExpr b d
guard $ y /= 0
return $ x `div` y
evalIntExpr (IMod a b) d = do
x <- evalIntExpr a d
y <- evalIntExpr b d
guard $ y /= 0
return $ x `mod` y
evalDateStatement :: DateStatement -> Day -> Bool evalDateStatement :: DateStatement -> Day -> Bool
evalDateStatement IsLeapYear d = isLeapYear $ year d evalDateStatement IsLeapYear d = isLeapYear $ year d
@ -107,8 +133,94 @@ yearday day = let (y,m,d) = toGregorian day
in toInteger dayofyear in toInteger dayofyear
{- {-
- Parsing BoolExpr - Parsing DateExpr
-} -}
parseBoolExpr :: String -> Either ParseError BoolExpr -- error ↓ ↓ input
parseBoolExpr = undefined type Parser = Parsec Void String
parseDateExpr :: Parser DateExpr
parseDateExpr = boolExpr
sc :: Parser () -- oddly necessary
sc = L.space space1 empty empty
symbol :: String -> Parser String
symbol = L.symbol sc
lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc
parens :: Parser a -> Parser a
parens = between (symbol "(") (symbol ")")
integer :: Parser Integer
integer = lexeme L.decimal
bool :: Parser Bool
bool = (True <$ symbol "true") <|> (False <$ symbol "false")
-- helper functions for creating tables
prefix name f = Prefix (f <$ symbol name)
infixL name f = InfixL (f <$ symbol name)
-- parse IntExpr
intExpr :: Parser IntExpr
intExpr = makeExprParser intTerm intTable
intTable :: [[Operator Parser IntExpr]]
intTable = [ [prefix "+" id, prefix "-" INeg]
, [infixL "*" IMul, infixL "/" IDiv, infixL "%" IMod]
, [infixL "+" IAdd, infixL "-" (IAdd . INeg)]
]
intTerm :: Parser IntExpr
intTerm = parens intExpr
<|> IValue <$> integer
<|> IDate <$> pSpecialDate
<?> "integer expression"
-- parse BoolExpr
boolExpr :: Parser BoolExpr
boolExpr = makeExprParser boolTerm boolTable
boolTable :: [[Operator Parser BoolExpr]]
boolTable = [ [prefix "!" BNot]
, [infixL "&&" BAnd, infixL "||" BOr]
]
boolTerm :: Parser BoolExpr
boolTerm = parens boolExpr
<|> BValue <$> bool
<|> BStatement <$> pDateStatement
<|> relExpr
<?> "boolean expression"
-- comparison (==, <, >, <=, >=)
relExpr = do
a <- intExpr
b <- relation
c <- intExpr
return $ b a c
relation = (BEq <$ symbol "==")
<|> ((\a b -> BNot (BEq a b)) <$ symbol "!=")
<|> ((\a b -> BNot (BLt a b)) <$ symbol ">=")
<|> ((\a b -> BNot (BGt a b)) <$ symbol "<=")
<|> (BGt <$ symbol ">")
<|> (BLt <$ symbol "<")
pSpecialDate = (SJulianDay <$ symbol "julian")
<|> (SDayOfYear <$ symbol "yearday")
<|> (SYearCount <$ symbol "yearcount")
<|> (SYear <$ symbol "year")
<|> (SMonthCount <$ symbol "monthcount")
<|> (SMonth <$ symbol "month")
<|> (SDay <$ symbol "day")
<|> (SDayOfWeek <$ symbol "weekday")
<|> (SEaster <$ symbol "easter")
<?> "special date" -- necessary?
pDateStatement = (IsWeekend <$ symbol "isweekend")
<|> (IsLeapYear <$ symbol "isleapyear")
<?> "date statement" -- necessary?