1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 {-# LANGUAGE TypeFamilies #-}
6 module Hcompta.Filter.Read where
8 import Prelude hiding (filter)
9 -- import Control.Applicative ((<$>), (<*))
10 import Control.Exception (assert)
11 import Control.Monad (liftM, join, when, (>=>), void)
12 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
13 import qualified Data.Char
15 import qualified Data.Foldable
16 import Data.Functor.Identity (Identity)
17 import Data.Maybe (catMaybes)
18 import qualified Data.Time.Clock as Time
19 import qualified Text.Parsec.Expr as R
20 import qualified Text.Parsec as R hiding
32 -- import qualified Text.Parsec.Expr as R
33 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
34 import Data.String (fromString)
35 import qualified Data.Text as Text
36 import Data.Text (Text)
37 import Data.Typeable ()
39 import Hcompta.Lib.Interval (Interval)
40 import qualified Hcompta.Lib.Interval as Interval
41 import qualified Hcompta.Lib.Regex as Regex
42 -- import Hcompta.Lib.Regex (Regex)
43 import qualified Hcompta.Account as Account
44 import qualified Hcompta.Amount as Amount
45 import Hcompta.Amount (Amount)
46 import qualified Hcompta.Amount.Read as Amount.Read
47 import qualified Hcompta.Amount.Unit as Unit
48 import qualified Hcompta.Date as Date
49 import Hcompta.Date (Date)
50 import qualified Hcompta.Date.Read as Date.Read
51 import qualified Hcompta.Filter as Filter
52 import Hcompta.Filter hiding (Amount)
53 import qualified Hcompta.Lib.Parsec as R
61 { context_date :: Date
62 } deriving (Data, Eq, Show, Typeable)
67 { context_date = Date.nil
74 | Error_Filter_Date Date.Read.Error
75 | Error_Filter_Date_Interval (Integer, Integer)
81 ( Stream s (R.Error_State Error Identity) Char
84 => ParsecT s Context (R.Error_State Error Identity) (Filter_Bool t)
85 -> s -> IO (Either [R.Error Error] (Filter_Bool t))
87 context_date <- Time.getCurrentTime
89 R.runParser_with_Error t context{context_date} "" s
91 -- ** Read 'Filter_Text'
93 :: (Stream s m Char, Monad r)
94 => ParsecT s u m (String -> r Filter_Text)
97 [ R.char '~' >> return (Regex.of_StringM >=> (return . Filter_Text_Regex))
98 , R.char '=' >> return (\s -> return (Filter_Text_Exact $ Text.pack s))
99 , return (\s -> return (Filter_Text_Exact $ Text.pack s))
104 => ParsecT s u m String
105 filter_text_operator =
111 -- ** Read 'Filter_Ord'
113 :: (Stream s m Char, Ord o)
114 => ParsecT s u m (o -> Filter_Ord o)
117 [ R.string "=" >> return (Filter_Ord Eq)
118 , R.string "<=" >> return (Filter_Ord Le) -- NOTE: before "<"
119 , R.string ">=" >> return (Filter_Ord Ge) -- NOTE: before ">"
120 , R.string "<" >> return (Filter_Ord Lt)
121 , R.string ">" >> return (Filter_Ord Gt)
126 => ParsecT s u m String
127 filter_ord_operator =
136 -- ** Read 'Filter_Num_Abs'
138 :: (Stream s m Char, Num n)
139 => ParsecT s u m (Filter_Ord n -> m (Either (Filter_Ord n) (Filter_Num_Abs n)))
142 [ R.char '+' >> return (return . Right . Filter_Num_Abs)
143 , return (return . Left)
146 text :: Stream s m Char => String -> ParsecT s Context m Text
151 , R.many $ R.noneOf ("() " ++ none_of)
154 borders = R.between (R.char '(') (R.char ')')
155 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
156 preserve_inside = inside >>= (\x -> return $ '(':(x++[')']))
158 -- ** Read 'Filter_Bool'
162 => [ParsecT s u m (ParsecT s u m t)]
163 -> ParsecT s u m (Filter_Bool t)
165 R.buildExpressionParser
166 filter_bool_operators
167 (filter_bool_term terms)
170 filter_bool_operators
172 => R.OperatorTable s u m (Filter.Filter_Bool t)
173 filter_bool_operators =
174 [ [ prefix "- " Filter.Not
176 , [ binary " & " Filter.And R.AssocLeft
178 , [ binary " + " Filter.Or R.AssocLeft
179 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
183 binary name fun = R.Infix (filter_bool_operator name >> return fun)
184 prefix name fun = R.Prefix (filter_bool_operator name >> return fun)
185 -- postfix name fun = Text.Parsec.Expr.Postfix (filter_bool_operator name >> return fun)
189 => String -> ParsecT s u m ()
190 filter_bool_operator name =
193 >> R.notFollowedBy filter_bool_operator_letter
197 filter_bool_operator_letter
198 :: Stream s m Char => ParsecT s u m Char
199 filter_bool_operator_letter =
200 R.oneOf ['-', '&', '+']
204 => [ParsecT s u m (ParsecT s u m t)]
205 -> ParsecT s u m (Filter_Bool t)
206 filter_bool_term terms = do
208 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
209 >> (return $ parens $
210 Data.Foldable.foldr Filter.And Filter.Any <$>
211 R.many (R.try (R.spaces >> expr)) ))
212 : map ((Filter.Bool <$>) <$>) terms
213 ) <* R.spaces <?> "boolean-expression")
216 R.lookAhead (R.try R.anyToken)
217 >> R.notFollowedBy (R.char ')')
226 (R.spaces >> R.char '(')
227 (R.spaces >> R.char ')')
229 bool :: Stream s m Char => ParsecT s u m Bool
244 jump :: Stream s m Char
249 jump prefixes next r =
251 (map (\s -> R.string s >> return r) prefixes)
252 <* R.lookAhead (R.try next)
254 -- ** Read Account.'Account.Name'
255 account_name :: Stream s m Char => ParsecT s u m Account.Name
258 R.many1 $ R.try account_name_char
260 account_name_char :: Stream s m Char => ParsecT s u m Char
261 account_name_char = do
264 -- _ | c == comment_begin -> R.parserZero
265 -- _ | c == account_section_sep -> R.parserZero
266 _ | R.is_space_horizontal c -> do
267 _ <- R.notFollowedBy $ R.space_horizontal
268 return c <* (R.lookAhead $ R.try $
269 ( R.try (R.char account_section_sep)
270 <|> account_name_char
272 _ | not (Data.Char.isSpace c) -> return c
275 -- ** Read 'Filter_Account_Section'
276 filter_account_section
278 => ParsecT s u m Filter_Account_Section
279 filter_account_section = do
282 <* R.lookAhead account_section_end
283 >> return Filter_Account_Section_Any
285 >> R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
286 >>= (liftM (Filter_Account_Section_Text . Filter_Text_Regex) . Regex.of_StringM)
287 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
288 >>= (liftM (Filter_Account_Section_Text . Filter_Text_Exact) . return . Text.pack)
289 , R.lookAhead account_section_end
290 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
291 >> return Filter_Account_Section_Many
294 account_section_end =
296 [ void $ R.char account_section_sep
297 , void $ R.space_horizontal
301 -- ** Read 'Filter_Account'
302 account_section_sep :: Char
303 account_section_sep = ':'
307 => ParsecT s u m Filter_Account
309 R.notFollowedBy $ R.space_horizontal
311 R.option (Filter_Ord Eq ()) $ R.try $
312 (\f -> f ()) <$> filter_ord
313 fmap (Filter_Account o) $
314 R.many1_separated filter_account_section $
315 R.char account_section_sep
317 filter_account_operator
319 => ParsecT s u m String
320 filter_account_operator =
323 -- ** Read 'Filter_Amount'
326 => ParsecT s u m (Filter_Amount Amount)
328 R.notFollowedBy $ R.space_horizontal
332 amt <- Amount.Read.amount
334 (Filter_Amount_Section_Quantity (tst $ Amount.quantity amt))
335 : case Unit.text $ Amount.unit amt of
336 unit | Text.null unit -> []
337 unit -> [Filter_Amount_Section_Unit (Filter_Unit (Filter_Text_Exact unit))]
340 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
341 return $ [Filter_Amount_Section_Unit (Filter_Unit unit)]
344 filter_amount_operator
346 => ParsecT s u m String
347 filter_amount_operator =
349 [ filter_ord_operator
350 , filter_text_operator
353 -- ** Read 'Filter_Date'
355 :: (Stream s (R.Error_State Error m) Char, Monad m)
356 => ParsecT s Context (R.Error_State Error m) (Filter_Bool Filter_Date)
360 (return $ read_date_pattern)
361 , filter_ord >>= \tst ->
364 let (year, _, _) = Date.gregorian $ context_date ctx
365 liftM (Bool . Filter_Date_UTC . tst) $
366 Date.Read.date Error_Filter_Date (Just year)
370 :: (Stream s (R.Error_State Error m) Char, Monad m)
371 => ParsecT s u (R.Error_State Error m) (Filter_Bool Filter_Date)
372 read_date_pattern = (do
373 let read2 = of_digits <$> (R.try (R.count 2 R.digit) <|> R.count 1 R.digit)
374 n0 <- read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
375 n1 <- R.option Nothing $ R.try $ do
377 Just <$> read_interval Error_Filter_Date_Interval read2
378 n2 <- R.option Nothing $ R.try $ do
380 Just <$> read_interval Error_Filter_Date_Interval read2
381 let (year, month, dom) =
383 (Nothing, Nothing) ->
386 , Interval.unlimited )
387 (Just d1, Nothing) ->
391 (Nothing, Just _d2) -> assert False undefined
392 (Just d1, Just d2) ->
396 (hour, minute, second) <-
397 R.option (Interval.unlimited, Interval.unlimited, Interval.unlimited) $ R.try $ do
399 hour <- read_interval Error_Filter_Date_Interval read2
400 sep <- Date.Read.hour_separator
401 minute <- read_interval Error_Filter_Date_Interval read2
402 second <- R.option Interval.unlimited $ R.try $ do
404 read_interval Error_Filter_Date_Interval $ of_digits <$> R.many1 R.digit
405 -- tz <- R.option Time.utc $ R.try $ do
406 -- -- R.skipMany $ R.space_horizontal
407 -- Date.Read.time_zone
416 [ just_when_limited (Filter_Date_Year . Filter_Interval_In) year
417 , just_when_limited (Filter_Date_Month . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) month)
418 , just_when_limited (Filter_Date_DoM . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) dom)
419 , just_when_limited (Filter_Date_Hour . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) hour)
420 , just_when_limited (Filter_Date_Minute . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) minute)
421 , just_when_limited (Filter_Date_Second . Filter_Interval_In) (Interval.fmap_unsafe (fmap fromInteger) second)
425 of_digits :: Num n => [Char] -> n
426 of_digits = fromInteger . R.integer_of_digits 10
427 just_when_limited f x =
428 if x == Interval.unlimited
430 else Just $ Bool $ f x
433 :: (Stream s (R.Error_State e m) Char, Monad m, Ord x)
435 -> ParsecT s u (R.Error_State e m) x
436 -> ParsecT s u (R.Error_State e m) (Interval (Interval.Unlimitable x))
437 read_interval err read_digits = do
439 [ R.string ".." >> return Interval.Unlimited_low
440 , Interval.Limited <$> read_digits
443 [ when (l /= Interval.Unlimited_low)
444 (void $ R.string "..") >> do
446 [ Interval.Limited <$> read_digits
447 , return Interval.Unlimited_high
449 case (Interval.<=..<=) l h of
450 Nothing -> R.fail_with "interval" (err $ (Interval.limited l, Interval.limited h))
454 Interval.Limited _ -> Interval.point l
455 _ -> Interval.unlimited
460 => ParsecT s u m String
461 filter_date_operator =
464 -- ** Read 'Filter_Tag'
470 => ParsecT s u m Filter_Tag
472 make_filter_text <- filter_text
475 <* R.lookAhead filter_tag_name_end
476 >> return (Filter_Tag_Name Filter_Text_Any)
477 , R.many1 (R.notFollowedBy (R.try filter_tag_name_end) >> R.anyChar)
478 >>= (liftM Filter_Tag_Name . make_filter_text)
481 filter_tag_name_end =
483 [ void $ filter_text_operator
484 , void $ R.space_horizontal
489 => ParsecT s u m Filter_Tag
490 filter_tag_value = do
491 make_filter_text <- filter_text
494 <* R.lookAhead filter_tag_value_end
495 >> return (Filter_Tag_Value Filter_Text_Any)
496 , R.many1 (R.notFollowedBy (R.try filter_tag_value_end) >> R.anyChar)
497 >>= (liftM Filter_Tag_Value . make_filter_text)
500 filter_tag_value_end =
502 [ void $ R.space_horizontal
508 => ParsecT s u m (Filter_Bool Filter_Tag)
512 [ R.lookAhead (R.try $ filter_tag_operator)
513 >> And (Bool n) . Bool <$> filter_tag_value
519 => ParsecT s u m String
520 filter_tag_operator =
523 -- ** Read 'Filter_Posting'
525 :: (Stream s m Char, Filter.Posting t)
526 => ParsecT s Context m (Filter_Bool (Filter_Posting t))
528 Data.Foldable.foldr Filter.And Filter.Any <$>
531 >> R.lookAhead R.anyToken
532 >> filter_bool filter_posting_terms
535 :: (Stream s m Char, Filter.Posting t)
536 => [ParsecT s Context m (ParsecT s Context m (Filter_Posting t))]
537 filter_posting_terms =
539 ( Filter.Filter_Posting_Account
543 -- ** Read 'Filter_Transaction'
545 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t
546 , Posting_Amount (Transaction_Posting t) ~ Amount)
547 => ParsecT s Context (R.Error_State Error m) (Filter_Bool (Filter_Transaction t))
549 Data.Foldable.foldr Filter.And Filter.Any <$>
552 >> R.lookAhead R.anyToken
553 >> filter_bool filter_transaction_terms
555 filter_transaction_terms
556 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m
557 , Posting_Amount (Transaction_Posting t) ~ Amount)
558 => [ParsecT s Context (R.Error_State Error m)
559 (ParsecT s Context (R.Error_State Error m) (Filter_Transaction t))]
560 filter_transaction_terms =
561 -- , jump [ "atag" ] comp_text parseFilterATag
562 -- , jump [ "code" ] comp_text parseFilterCode
563 [ jump [ "date" ] filter_date_operator
564 (Filter.Filter_Transaction_Date <$> filter_date)
565 , jump [ "tag" ] filter_tag_operator
566 (Filter.Filter_Transaction_Tag <$> filter_tag)
567 , jump [ "amount" ] filter_amount_operator
568 (( Filter.Filter_Transaction_Posting
569 . Filter.Filter_Posting_Amount
571 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
572 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
573 -- , jump [ "real" ] (R.char '=') parseFilterReal
574 -- , jump [ "status" ] (R.char '=') parseFilterStatus
575 -- , jump [ "sym" ] comp_text parseFilterSym
576 -- , R.lookAhead comp_num >> return parseFilterAmount
578 ( Filter.Filter_Transaction_Posting
579 . Filter.Filter_Posting_Account
583 -- ** Read 'Filter_Balance'
585 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
586 => ParsecT s Context m (Filter_Bool (Filter_Balance t))
588 Data.Foldable.foldr Filter.And Filter.Any <$>
591 >> R.lookAhead R.anyToken
592 >> filter_bool filter_balance_terms
595 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
596 => [ParsecT s Context m (ParsecT s Context m (Filter_Balance t))]
597 filter_balance_terms =
598 [ jump [ "D" ] filter_amount_operator
599 ( Filter.Filter_Balance_Positive
601 , jump [ "C" ] filter_amount_operator
602 ( Filter.Filter_Balance_Negative
604 , jump [ "B" ] filter_amount_operator
605 ( Filter.Filter_Balance_Amount
608 ( Filter.Filter_Balance_Account
612 -- ** Read 'Filter_GL'
614 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
615 => ParsecT s Context m (Filter_Bool (Filter_GL t))
617 Data.Foldable.foldr Filter.And Filter.Any <$>
620 >> R.lookAhead R.anyToken
621 >> filter_bool filter_gl_terms
624 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
625 => [ParsecT s Context m (ParsecT s Context m (Filter_GL t))]
627 [ jump [ "D" ] filter_amount_operator
628 ( Filter.Filter_GL_Amount_Positive
630 , jump [ "C" ] filter_amount_operator
631 ( Filter.Filter_GL_Amount_Negative
633 , jump [ "B" ] filter_amount_operator
634 ( Filter.Filter_GL_Amount_Balance
636 , jump [ "RD" ] filter_amount_operator
637 ( Filter.Filter_GL_Sum_Positive
639 , jump [ "RC" ] filter_amount_operator
640 ( Filter.Filter_GL_Sum_Negative
642 , jump [ "RB" ] filter_amount_operator
643 ( Filter.Filter_GL_Sum_Balance
646 ( Filter.Filter_GL_Account