1 module Hcompta.Model.Filter.Read where
3 import Control.Monad (liftM)
4 import Control.Applicative ((<$>))
5 import Data.Maybe (isJust)
6 import qualified Text.Parsec as R
7 import qualified Text.Parsec.Expr as R
8 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
10 import qualified Hcompta.Model.Filter as Filter
11 import Hcompta.Model.Filter (Comp_Number(..), Comp_String(..))
13 -- * The 'Context' type
18 } deriving (Data, Eq, Show, Typeable)
22 comp_string :: Stream s m Char => ParsecT s u m Comp_String
25 [ R.string "=" >> return Comp_String_Eq
26 , R.string "~" >> return Comp_String_Regexp
29 comp_number :: Stream s m Char => ParsecT s u m Comp_Number
32 [ R.string "=" >> return Comp_Number_Eq
33 , R.string "<=" >> return Comp_Number_Lt_Eq
34 , R.string ">=" >> return Comp_Number_Gt_Eq
35 , R.string "<" >> return Comp_Number_Lt
36 , R.string ">" >> return Comp_Number_Gt
39 comp_number_absolute :: Stream s m Char => ParsecT s u m Comp_Num_Absolute
40 comp_number_absolute =
41 liftM isJust $ R.optionMaybe (char '+')
43 string :: Stream s m Char => String -> ParsecT s Context m String
45 R.choice_try [borders inside, R.many $ R.noneOf ("() " ++ none_of)]
47 borders inside = between (char '(') (char ')') inside
48 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, many1 $ R.noneOf "()"])
49 preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
51 parseBool :: Stream s m Char => ParsecT s u m Bool
66 account :: Stream s m Char => ParsecT s Context m Filter
68 o <- R.optionMaybe comp_string
69 liftM (Filter.Account $ fromMaybe Comp_String_Eq o)
70 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
72 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
73 parseFilterAmount = do
76 <*> comp_number_absolute
79 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
82 liftM (uncurry (ATag c))
85 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
86 --parseFilterCode = do
94 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
97 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
98 parseFilterBalance = do
100 absc <- comp_number_absolute
101 a <- parseAmount Nothing
102 return $ Bal (nc, absc) a
104 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
109 periodexprdatespan (qCtxDay ctx)
111 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
112 parseFilterDate2 = do
116 periodexprdatespan (qCtxDay ctx)
118 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
124 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
125 parseFilterDepth = do
127 liftM (Depth c . fromIntegral) $
130 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
136 -- | Parse the boolean value part of a "status:" query, allowing "*" as
137 -- another way to spell True, similar to the journal file format.
138 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
139 parseFilterStatus = do
142 try (char '*' >> return True) <|> parseBool
144 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
145 --parseFilterSym = do
150 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
153 liftM (uncurry (Tag c))
156 parens :: Stream s m Char => ParsecT s u m a -> ParsecT s u m a
157 parens = between (parseLexeme $ char '(') (parseLexeme $ char ')')
159 operators :: Stream s m Char => R.OperatorTable s u m Filter
161 [ [ prefix "- " Filter.Not
162 , prefix "not " Filter.Not
164 , [ binary " & " Filter.And R.AssocLeft
165 , binary " and " Filter.And R.AssocLeft
166 , binary " - " (\x y -> Filter.And x (Filter.Not y)) R.AssocLeft
167 , binary " but " (\x y -> Filter.And x (Filter.Not y)) R.AssocLeft
169 , [ binary " + " Filter.Or R.AssocLeft
170 , binary " or " Filter.Or R.AssocLeft
174 binary name fun assoc = R.Infix (do{ operator name; return fun }) assoc
175 prefix name fun = R.Prefix (do{ operator name; return fun })
176 -- postfix name fun = Text.Parsec.Expr.Postfix (do{ operator name; return fun })
178 operator_letter :: Stream s m Char => ParsecT s u m Char
179 operator_letter = oneOf ['+', '-', '&']
181 operator :: Stream s m Char => String -> ParsecT s u m ()
185 ; R.notFollowedBy operator_letter <?> ("end of " ++ show name)
188 parseFilterTerm :: Stream s m Char => ParsecT s Context m Filter
191 [ R.lookAhead (char '(')
193 parens (liftM (foldl (\acc x -> case acc of { Any -> x; _ -> And acc x }) Any) $
194 R.many $ (parseWhiteSpace >> R.lookAhead R.anyToken >> R.notFollowedBy (char ')') >> parseFilterExpr)))
195 , jump [ "account","acct" ] comp_string parseFilterAccount
196 , jump [ "amount", "amt" ] comp_number parseFilterAmount
197 , jump [ "atag" ] comp_string parseFilterATag
198 , jump [ "balance", "bal" ] comp_number parseFilterBalance
199 -- , jump [ "code" ] comp_string parseFilterCode
200 , jump [ "date" ] (char '=') parseFilterDate
201 , jump [ "date2", "edate" ] (char '=') parseFilterDate2
202 , jump [ "depth" ] comp_number parseFilterDepth
203 , jump [ "description","descr","desc" ] comp_string parseFilterDesc
204 , jump [ "real" ] (char '=') parseFilterReal
205 , jump [ "status" ] (char '=') parseFilterStatus
206 -- , jump [ "sym" ] comp_string parseFilterSym
207 , jump [ "tag" ] comp_string parseFilterTag
208 , R.lookAhead comp_number >> return parseFilterAmount
209 , return parseFilterAccount
210 ] <* parseWhiteSpace <?> "query expression"
213 jump l next r = R.choice_try (map (\s -> R.string s >> return r) l) <* R.lookAhead next
215 expr :: Stream s m Char => ParsecT s Context m Filter
216 expr = R.buildExpressionParser operators parseFilterTerm <?> "query"
218 filter :: Stream s m Char => ParsecT s Context m Filter
220 liftM (foldl (\acc x -> case acc of { Any -> x; _ -> And acc x }) Any) $
221 R.many $ (parseWhiteSpace >> R.lookAhead R.anyToken >> parseFilterExpr)
225 query :: Day -> String -> Either ParseError Filter
226 query d s = runParser (parseFilter <* eof) QCtx{qCtxDay=d} "" s
228 simplifyFilter :: Filter -> Filter
230 -- let q' = simplify q
231 -- in if q' == q then q else simplifyFilter q'
232 simplify q -- NOTE: should be stable without recurring
234 simplify (And None _) = None
235 simplify (And _ None) = None
236 simplify (And Any Any) = Any
237 simplify (And q q'@(Date _)) = simplify (And q' q) -- XXX: useful?
238 simplify (And q q') | q == q' = simplify q
239 | otherwise = case (simplify q, simplify q') of
245 simplify (Or None q') = simplify q'
246 simplify (Or q None) = simplify q
247 simplify (Or Any _) = Any
248 simplify (Or _ Any) = Any
249 -- simplify (Or q q'@(Date _)) = simplify (Or q' q)
250 simplify (Or q q') | q == q' = simplify q
251 | otherwise = case (simplify q, simplify q') of
257 simplify (Date (DateSpan Nothing Nothing)) = Any
258 simplify (Date2 (DateSpan Nothing Nothing)) = Any