1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 module Hcompta.Model.Filter.Read where
7 import Prelude hiding (filter)
8 import Control.Applicative ((<$>), (<*))
9 import Control.Exception (assert)
10 import Control.Monad (liftM)
11 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
12 import qualified Data.Char
14 import qualified Data.Foldable
15 import Data.Functor.Identity (Identity)
16 import Data.Maybe (catMaybes)
17 import qualified Data.Time.Clock as Time
18 import qualified Text.Parsec.Expr as R
19 import qualified Text.Parsec as R hiding
31 -- import qualified Text.Parsec.Expr as R
32 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
33 import Data.String (fromString)
34 import qualified Data.Text as Text
35 import Data.Text (Text)
36 import Data.Typeable ()
38 import qualified Hcompta.Lib.Regex as Regex
39 -- import Hcompta.Lib.Regex (Regex)
40 import qualified Hcompta.Model.Account as Account
41 import qualified Hcompta.Model.Date as Date
42 import Hcompta.Model.Date (Date)
43 import qualified Hcompta.Model.Date.Read as Date.Read
44 import qualified Hcompta.Model.Filter as Filter
45 import Hcompta.Model.Filter
46 import qualified Hcompta.Lib.Parsec as R
54 { context_date :: Date
55 } deriving (Data, Eq, Show, Typeable)
60 { context_date = Date.nil
67 | Error_Test_Date Date.Read.Error
73 ( Stream s (R.Error_State Error Identity) Char
76 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
77 -> s -> IO (Either [R.Error Error] (Test_Bool t))
79 context_date <- Time.getCurrentTime
81 R.runParser_with_Error t context{context_date} "" s
83 -- ** Read 'Test_Text'
85 :: (Stream s m Char, Monad r)
86 => ParsecT s u m (String -> r Test_Text)
89 [ R.char '~' >> return (\s -> Regex.of_StringM s >>= (return . Test_Text_Regex))
90 , R.char '=' >> return (\s -> return (Test_Text_Exact $ Text.pack s))
91 , return (\s -> return (Test_Text_Exact $ Text.pack s))
96 => ParsecT s u m String
103 -- ** Read 'Test_Ord'
105 :: (Stream s m Char, Ord o)
106 => ParsecT s u m (o -> Test_Ord o)
109 [ R.string "=" >> return Test_Ord_Eq
110 , R.string "<=" >> return Test_Ord_Le
111 , R.string ">=" >> return Test_Ord_Ge
112 , R.string "<" >> return Test_Ord_Lt
113 , R.string ">" >> return Test_Ord_Gt
118 => ParsecT s u m String
128 -- ** Read 'Test_Num_Abs'
130 :: (Stream s m Char, Num n)
131 => ParsecT s u m (Test_Ord n -> m (Either (Test_Ord n) (Test_Num_Abs n)))
134 [ R.char '+' >> return (return . Right . Test_Num_Abs)
135 , return (return . Left)
138 text :: Stream s m Char => String -> ParsecT s Context m Text
143 , R.many $ R.noneOf ("() " ++ none_of)
146 borders = R.between (R.char '(') (R.char ')')
147 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
148 preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
150 -- ** Read 'Test_Bool'
154 => [ParsecT s u m (ParsecT s u m t)]
155 -> ParsecT s u m (Test_Bool t)
157 R.buildExpressionParser
159 (test_bool_term terms)
164 => R.OperatorTable s u m (Filter.Test_Bool t)
165 test_bool_operators =
166 [ [ prefix "- " Filter.Not
167 , prefix "not " Filter.Not
169 , [ binary " & " Filter.And R.AssocLeft
170 , binary " and " Filter.And R.AssocLeft
171 , binary " - " (flip Filter.And . Filter.Not) R.AssocLeft
172 , binary " but " (flip Filter.And . Filter.Not) R.AssocLeft
174 , [ binary " + " Filter.Or R.AssocLeft
175 , binary " or " Filter.Or R.AssocLeft
179 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
180 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
181 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
185 => String -> ParsecT s u m ()
186 test_bool_operator name =
189 >> R.notFollowedBy test_bool_operator_letter
190 <?> ("end of " ++ show name))
192 test_bool_operator_letter
193 :: Stream s m Char => ParsecT s u m Char
194 test_bool_operator_letter =
195 R.oneOf ['+', '-', '&']
199 => [ParsecT s u m (ParsecT s u m t)]
200 -> ParsecT s u m (Test_Bool t)
201 test_bool_term terms = do
203 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
204 >> (return $ parens $
205 Data.Foldable.foldr Filter.And Filter.Any <$>
206 R.many (R.try (R.spaces >> expr)) ))
207 : map ((Filter.Bool <$>) <$>) terms
208 ) <* R.spaces <?> "boolean-expression"
212 R.lookAhead (R.try R.anyToken)
213 >> R.notFollowedBy (R.char ')')
220 lexeme p = p <* R.spaces
228 (R.spaces >> R.char '(')
229 (R.spaces >> R.char ')')
231 bool :: Stream s m Char => ParsecT s u m Bool
246 -- ** Read Account.'Account.Name'
247 account_name :: Stream s m Char => ParsecT s u m Account.Name
250 R.many1 $ R.try account_name_char
252 account_name_char :: Stream s m Char => ParsecT s u m Char
253 account_name_char = do
256 -- _ | c == comment_begin -> R.parserZero
257 -- _ | c == account_section_sep -> R.parserZero
258 _ | R.is_space_horizontal c -> do
259 _ <- R.notFollowedBy $ R.space_horizontal
260 return c <* (R.lookAhead $ R.try $
261 ( R.try (R.char account_section_sep)
262 <|> account_name_char
264 _ | not (Data.Char.isSpace c) -> return c
267 -- ** Read 'Test_Account_Section'
270 => (String -> ParsecT s u m Test_Text)
271 -> ParsecT s u m Test_Account_Section
272 test_account_section make_test_text = do
275 <* R.lookAhead account_section_end
276 >> return Test_Account_Section_Any
277 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
278 >>= (liftM Test_Account_Section_Text . make_test_text)
279 , R.lookAhead account_section_end
280 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
281 >> return Test_Account_Section_Many
284 account_section_end =
286 [ R.char account_section_sep >> return ()
287 , R.space_horizontal >> return ()
291 -- ** Read 'Test_Account'
292 account_section_sep :: Char
293 account_section_sep = ':'
297 => ParsecT s u m Test_Account
299 R.notFollowedBy $ R.space_horizontal
300 make_test_text <- test_text
301 R.many1_separated (test_account_section make_test_text) $
302 R.char account_section_sep
304 -- ** Read 'Test_Date'
306 :: (Stream s (R.Error_State Error m) Char, Monad m)
307 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
311 (return $ read_date_pattern)
312 , test_ord >>= \tst ->
315 let (year, _, _) = Date.gregorian $ context_date ctx
316 Date.Read.date Error_Test_Date (Just year)
317 >>= return . Bool . Test_Date_UTC . tst
321 :: (Stream s (R.Error_State e m) Char, Monad m)
322 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
323 read_date_pattern = (do
324 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
325 n0 <- read_range $ R.many1 R.digit
326 n1 <- R.option Nothing $ R.try $ do
328 Just <$> read_range read2
329 n2 <- R.option Nothing $ R.try $ do
331 Just <$> read_range read2
332 let (year, month, dom) =
334 (Nothing, Nothing) ->
338 (Just d1, Nothing) ->
342 (Nothing, Just _d2) -> assert False undefined
343 (Just d1, Just d2) ->
344 ( R.integer_of_digits 10 <$> n0
347 (hour, minute, second) <-
348 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
349 R.skipMany1 $ R.space_horizontal
350 hour <- read_range read2
351 sep <- Date.Read.hour_separator
352 minute <- read_range read2
353 second <- R.option test_range_all $ R.try $ do
355 read_range $ R.many1 R.digit
356 -- tz <- R.option Time.utc $ R.try $ do
357 -- R.skipMany $ R.space_horizontal
358 -- Date.Read.time_zone
361 , of_digits <$> minute
362 , of_digits <$> second
367 [ just_when_bounded Test_Date_Year year
368 , just_when_bounded Test_Date_Month month
369 , just_when_bounded Test_Date_DoM dom
370 , just_when_bounded Test_Date_Hour hour
371 , just_when_bounded Test_Date_Minute minute
372 , just_when_bounded Test_Date_Second second
376 of_digits :: Num n => [Char] -> n
377 of_digits = fromInteger . R.integer_of_digits 10
378 just_when_bounded f x =
380 Test_Range_In Nothing Nothing -> Nothing
381 _ -> Just $ Bool $ f x
383 read_range :: Stream s m Char
385 -> ParsecT s u m (Test_Range a)
386 read_range read_digits = do
388 [ R.char '*' >> return Nothing
389 , Just <$> read_digits
393 (Test_Range_In a0 <$> R.choice_try
394 [ R.char '*' >> return Nothing
395 , Just <$> read_digits
397 , return $ maybe test_range_all Test_Range_Eq a0
402 => ParsecT s u m String
406 -- ** Read 'Test_Tag'
412 => ParsecT s u m Test_Tag
414 make_test_text <- test_text
417 <* R.lookAhead test_tag_name_end
418 >> return (Test_Tag_Name Test_Text_Any)
419 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
420 >>= (liftM Test_Tag_Name . make_test_text)
425 [ test_text_operator >> return ()
426 , R.space_horizontal >> return ()
431 => ParsecT s u m Test_Tag
433 make_test_text <- test_text
436 <* R.lookAhead test_tag_value_end
437 >> return (Test_Tag_Value Test_Text_Any)
438 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
439 >>= (liftM Test_Tag_Value . make_test_text)
444 [ R.space_horizontal >> return ()
450 => ParsecT s u m (Test_Bool Test_Tag)
454 [ R.lookAhead (R.try $ test_tag_operator)
455 >> And (Bool n) . Bool <$> test_tag_value
461 => ParsecT s u m String
465 -- ** Read 'Test_Posting'
467 :: (Stream s m Char, Filter.Posting t)
468 => ParsecT s Context m (Test_Bool (Test_Posting t))
470 Data.Foldable.foldr Filter.And Filter.Any <$>
473 >> R.lookAhead R.anyToken
474 >> test_bool test_posting_terms
477 :: (Stream s m Char, Filter.Posting t)
478 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
481 ( Filter.Test_Posting_Account
485 -- ** Read 'Test_Transaction'
487 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t)
488 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
490 Data.Foldable.foldr Filter.And Filter.Any <$>
493 >> R.lookAhead R.anyToken
494 >> test_bool test_transaction_terms
496 test_transaction_terms
497 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m)
498 => [ParsecT s Context (R.Error_State Error m)
499 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
500 test_transaction_terms =
501 -- , jump [ "account","acct" ] comp_text test_account
502 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
503 -- , jump [ "atag" ] comp_text parseFilterATag
504 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
505 -- , jump [ "code" ] comp_text parseFilterCode
506 [ jump [ "date" ] test_date_operator
507 (Filter.Test_Transaction_Date <$> test_date)
508 , jump [ "tag" ] test_tag_operator
509 (Filter.Test_Transaction_Tag <$> test_tag)
510 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
511 -- , jump [ "depth" ] comp_num parseFilterDepth
512 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
513 -- , jump [ "real" ] (R.char '=') parseFilterReal
514 -- , jump [ "status" ] (R.char '=') parseFilterStatus
515 -- , jump [ "sym" ] comp_text parseFilterSym
516 -- , R.lookAhead comp_num >> return parseFilterAmount
518 ( Filter.Test_Transaction_Posting
519 . Filter.Test_Posting_Account
523 jump :: Stream s m Char
528 jump prefixes next r =
530 (map (\s -> R.string s >> return r) prefixes)
531 <* R.lookAhead (R.try next)
533 -- ** Read 'Test_Balance'
535 :: (Stream s m Char, Filter.Balance t)
536 => ParsecT s Context m (Test_Bool (Test_Balance t))
538 Data.Foldable.foldr Filter.And Filter.Any <$>
541 >> R.lookAhead R.anyToken
542 >> test_bool test_balance_terms
545 :: (Stream s m Char, Filter.Balance t)
546 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
549 ( Filter.Test_Balance_Account
555 account :: Stream s m Char => ParsecT s Context m Filter
557 o <- R.optionMaybe comp_text
558 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
559 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
561 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
562 parseFilterAmount = do
568 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
571 liftM (uncurry (ATag c))
574 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
575 --parseFilterCode = do
583 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
586 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
587 parseFilterBalance = do
590 a <- parseAmount Nothing
591 return $ Bal (nc, absc) a
593 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
598 periodexprdatespan (qCtxDay ctx)
600 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
601 parseFilterDate2 = do
605 periodexprdatespan (qCtxDay ctx)
607 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
613 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
614 parseFilterDepth = do
616 liftM (Depth c . fromIntegral) $
619 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
624 -- | Read the boolean value part of a "status:" query, allowing "*" as
625 -- another way to spell True, similar to the journal file format.
626 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
627 parseFilterStatus = do
630 try (R.char '*' >> return True) <|> bool
632 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
633 --parseFilterSym = do
638 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
641 liftM (uncurry (Tag c))