(Re-)implement date expression parsing

This commit is contained in:
Joscha 2018-03-22 20:04:40 +00:00
parent 51eb270431
commit b8eecddc37
3 changed files with 278 additions and 35 deletions

View file

@ -21,24 +21,22 @@ description: Please see the README on Github at <https://github.com/Garm
dependencies: dependencies:
- base >= 4.7 && < 5 - base >= 4.7 && < 5
- brick
- ConfigFile
- megaparsec
- optparse-applicative
- sqlite-simple
- text - text
- time - time
- sqlite-simple
- brick
- vty
- optparse-applicative
- ConfigFile
- unix - unix
- vty
#- containers #- containers
#- unordered-containers #- unordered-containers
#- text
#- time
#- transformers #- transformers
#- async #- async
#- aeson #- aeson
#- bytestring #- bytestring
#- stm #- stm
#- megaparsec
library: library:
source-dirs: src source-dirs: src

View file

@ -17,7 +17,8 @@ import qualified TaskMachine.DateExpr as TM
data TaskRow = TaskRow data TaskRow = TaskRow
{ rowID :: Integer { rowID :: Integer
, rowDeadline :: Maybe Day , rowDeadline :: Maybe Day
, rowFormula :: Maybe TM.DateExpr , rowFormula :: Maybe TM.BoolExpr
, rowNumberFormula :: Maybe TM.IntExpr
, rowDescription :: T.Text , rowDescription :: T.Text
, rowDetails :: T.Text , rowDetails :: T.Text
, rowRepetitionsTotal :: Integer , rowRepetitionsTotal :: Integer
@ -29,6 +30,7 @@ instance DB.ToRow TaskRow where
( rowID ( rowID
, rowDeadline , rowDeadline
, rowFormula , rowFormula
, rowNumberFormula
, rowDescription , rowDescription
, rowDetails , rowDetails
, rowRepetitionsTotal , rowRepetitionsTotal
@ -37,14 +39,15 @@ instance DB.ToRow TaskRow where
instance DB.FromRow TaskRow where instance DB.FromRow TaskRow where
fromRow = do fromRow = do
(a,b,c,d,e,f,g) <- DB.fromRow (a,b,c,d,e,f,g,h) <- DB.fromRow
let rowID = a let rowID = a
rowDeadline = b rowDeadline = b
rowFormula = c rowFormula = c
rowDescription = d rowNumberFormula = d
rowDetails = e rowDescription = e
rowRepetitionsTotal = f rowDetails = f
rowRepetitionsDone = g rowRepetitionsTotal = g
rowRepetitionsDone = h
return TaskRow{..} return TaskRow{..}
-- TODO: Maybe put this in separate module and/or make less specific -- TODO: Maybe put this in separate module and/or make less specific
@ -65,6 +68,7 @@ initializeNewDB c = do
\ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\ \ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\
\ deadline TEXT,\ \ deadline TEXT,\
\ formula TEXT,\ \ formula TEXT,\
\ numberFormula TEXT,\
\ description TEXT NOT NULL,\ \ description TEXT NOT NULL,\
\ details TEXT NOT NULL DEFAULT \"\",\ \ details TEXT NOT NULL DEFAULT \"\",\
\ repetitions_total INTEGER NOT NULL DEFAULT 1,\ \ repetitions_total INTEGER NOT NULL DEFAULT 1,\

View file

@ -1,39 +1,280 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module TaskMachine.DateExpr module TaskMachine.DateExpr
( DateExpr ( BoolExpr
, parse , parseBoolExpr
, save , saveBoolExpr
, IntExpr
, parseIntExpr
, saveIntExpr
) where ) where
import Control.Exception import Control.Applicative
import Control.Monad
import Data.List
import Data.Maybe
import Data.Void
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.SQLite.Simple as DB import qualified Database.SQLite.Simple as DB
import qualified Database.SQLite.Simple.FromField as DB import qualified Database.SQLite.Simple.FromField as DB
import qualified Database.SQLite.Simple.Ok as DB import qualified Database.SQLite.Simple.Ok as DB
import qualified Database.SQLite.Simple.ToField as DB import qualified Database.SQLite.Simple.ToField as DB
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Megaparsec.Expr
data DateExpr = DummyValue data BoolExpr
= BValue Bool
parse :: T.Text -> Maybe DateExpr | BStatement DateStatement
parse = const (Just DummyValue) | BNot BoolExpr
| BAnd BoolExpr BoolExpr
save :: DateExpr -> T.Text | BOr BoolExpr BoolExpr
save = const "dummy string" | BSame BoolExpr BoolExpr
| BEqual IntExpr IntExpr
| BGreater IntExpr IntExpr
data DummyException = DummyException | BLess IntExpr IntExpr
deriving (Show) deriving (Show)
instance Exception DummyException data DateStatement
= IsLeapYear
| IsWeekend
| IsEaster -- same as: easter == 0
deriving (Show)
instance DB.ToField DateExpr where data IntExpr
toField = DB.SQLText . save = IValue Integer
| ISDate SpecialDate
| INegate IntExpr
| IAdd IntExpr IntExpr
| ISubtract IntExpr IntExpr -- same as (\a b -> IAdd a (INeg b))
| IMultiply IntExpr IntExpr
| IDivide IntExpr IntExpr -- div, not quot
| IModulo IntExpr IntExpr -- mod, not rem
deriving (Show)
instance DB.FromField DateExpr where data SpecialDate
= SJulianDay
| SYear
| SMonth
| SDay
| SDayOfYear
| SDayOfWeek
| SYearCount -- nth <day of week> of the year
| SMonthCount -- nth <day of week> of the month
| SEaster
deriving (Show)
parseBoolExpr :: String -> Maybe BoolExpr
parseBoolExpr = parseMaybe boolExpr
saveBoolExpr :: BoolExpr -> String
saveBoolExpr = bToString
parseIntExpr :: String -> Maybe IntExpr
parseIntExpr = parseMaybe intExpr
saveIntExpr :: IntExpr -> String
saveIntExpr = iToString
instance DB.ToField BoolExpr where
toField = DB.SQLText . T.pack . saveBoolExpr
instance DB.FromField BoolExpr where
fromField f = case DB.fromField f of fromField f = case DB.fromField f of
DB.Errors e -> DB.Errors e DB.Errors e -> DB.Errors e
DB.Ok text -> case parse text of DB.Ok text -> case parseBoolExpr (T.unpack text) of
Nothing -> DB.Errors [SomeException DummyException] -- TODO: Use proper exception Nothing -> DB.Errors [] -- TODO: Use proper exception?
Just expr -> DB.Ok expr Just expr -> DB.Ok expr
instance DB.ToField IntExpr where
toField = DB.SQLText . T.pack . saveIntExpr
instance DB.FromField IntExpr where
fromField f = case DB.fromField f of
DB.Errors e -> DB.Errors e
DB.Ok text -> case parseIntExpr (T.unpack text) of
Nothing -> DB.Errors [] -- TODO: Use proper exception?
Just expr -> DB.Ok expr
{-
- Evaluating expressions
-}
-- TODO
{-
- Converting to string
-}
iParenthesizeIf :: [(IntExpr -> Bool)] -> IntExpr -> String
iParenthesizeIf conditions expr =
if or (map ($expr) conditions)
then "(" ++ iToString expr ++ ")"
else iToString expr
iParenthesizeIfNot :: [(IntExpr -> Bool)] -> IntExpr -> String
iParenthesizeIfNot conditions expr =
if or (map ($expr) conditions)
then iToString expr
else "(" ++ iToString expr ++ ")"
isIAdd :: IntExpr -> Bool
isIAdd (IAdd _ _) = True
isIAdd _ = False
isISubtract :: IntExpr -> Bool
isISubtract (ISubtract _ _) = True
isISubtract _ = False
isINegate :: IntExpr -> Bool
isINegate (INegate _) = True
isINegate _ = False
isIValue :: IntExpr -> Bool
isIValue (IValue _) = True
isIValue _ = False
isISDate :: IntExpr -> Bool
isISDate (ISDate _) = True
isISDate _ = False
iToString :: IntExpr -> String
iToString (IValue a) = show a
iToString (ISDate a) = specialDateToString a
iToString (INegate a) = '-' : iParenthesizeIfNot [isIValue, isISDate] a
iToString (IAdd a b) = iToString a ++ " + " ++ iParenthesizeIf [isINegate] b
iToString (ISubtract a b) = iToString a ++ " - " ++ iParenthesizeIf [isINegate, isIAdd, isISubtract] b
iToString (IMultiply a b) = iParenthesizeIf [isIAdd, isISubtract] a ++ " * " ++ iParenthesizeIf [isIAdd, isISubtract, isINegate] b
iToString (IDivide a b) = iParenthesizeIf [isIAdd, isISubtract] a ++ " / " ++ iParenthesizeIf [isIAdd, isISubtract, isINegate] b
iToString (IModulo a b) = iParenthesizeIf [isIAdd, isISubtract] a ++ " % " ++ iParenthesizeIf [isIAdd, isISubtract, isINegate] b
specialDateToString :: SpecialDate -> String
specialDateToString SJulianDay = "julian"
specialDateToString SYear = "year"
specialDateToString SMonth = "month"
specialDateToString SDay = "day"
specialDateToString SDayOfYear = "yearday"
specialDateToString SDayOfWeek = "weekday"
specialDateToString SYearCount = "yearcount"
specialDateToString SMonthCount = "monthcount"
specialDateToString SEaster = "easter"
bToString :: BoolExpr -> String
bToString = undefined
dateStatementToString :: DateStatement -> String
dateStatementToString = undefined
{-
- Parsing
-}
type Parser = Parsec Void String
sc :: Parser ()
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 defining tables
--prefix :: String -> a -> Parser a
prefix name f = Prefix (f <$ symbol name)
--infixL :: String -> a -> Parser a
infixL name f = InfixL (f <$ symbol name)
-- Parse IntExpr
intExpr :: Parser IntExpr
intExpr = makeExprParser intTerm intTable
intTable :: [[Operator Parser IntExpr]]
intTable =
[ [ prefix "+" id
, prefix "-" INegate
]
, [ infixL "*" IMultiply
, infixL "/" IDivide
, infixL "%" IModulo
]
, [ infixL "+" IAdd
, infixL "-" ISubtract
]
]
intTerm :: Parser IntExpr
intTerm = parens intExpr
<|> IValue <$> integer
<|> ISDate <$> pSpecialDate
<?> "integer expression"
-- Parse BoolExpr
boolExpr :: Parser BoolExpr
boolExpr = makeExprParser boolTerm boolTable
boolTable :: [[Operator Parser BoolExpr]]
boolTable =
[ [ prefix "!" BNot
]
, [ infixL "&&" BAnd
, infixL "||" BOr
]
, [ infixL "==" BSame
, infixL "!=" (\a b -> BNot (BSame a b))
]
]
boolTerm :: Parser BoolExpr
boolTerm = parens boolExpr
<|> BValue <$> bool
<|> BStatement <$> pDateStatement
<|> relIntExpr
<?> "boolean expression"
relIntExpr :: Parser BoolExpr
relIntExpr = do
first <- intExpr
rel <- intRelation
second <- intExpr
return $ rel first second
intRelation :: Parser (IntExpr -> IntExpr -> BoolExpr)
intRelation = (BEqual <$ symbol "==")
<|> ((\a b -> BNot (BEqual a b)) <$ symbol "!=")
<|> ((\a b -> BNot (BLess a b)) <$ symbol ">=")
<|> ((\a b -> BNot (BGreater a b)) <$ symbol "<=")
<|> (BGreater <$ symbol ">")
<|> (BLess <$ symbol "<")
<?> "integer comparison"
pSpecialDate :: Parser SpecialDate
pSpecialDate = name SJulianDay "julian"
<|> name SYear "year"
<|> name SMonth "month"
<|> name SDay "day"
<|> name SDayOfYear "yearday"
<|> name SDayOfWeek "weekday"
<|> name SYearCount "yearcount"
<|> name SMonthCount "monthcount"
<|> name SEaster "easter"
where name a b = (a <$ symbol b)
pDateStatement :: Parser DateStatement
pDateStatement = name IsLeapYear "isleapyear"
<|> name IsWeekend "isweekend"
<|> name IsEaster "iseaster"
where name a b = (a <$ symbol b)