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, join)
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")
211 R.lookAhead (R.try R.anyToken)
212 >> R.notFollowedBy (R.char ')')
219 lexeme p = p <* R.spaces
227 (R.spaces >> R.char '(')
228 (R.spaces >> R.char ')')
230 bool :: Stream s m Char => ParsecT s u m Bool
245 -- ** Read Account.'Account.Name'
246 account_name :: Stream s m Char => ParsecT s u m Account.Name
249 R.many1 $ R.try account_name_char
251 account_name_char :: Stream s m Char => ParsecT s u m Char
252 account_name_char = do
255 -- _ | c == comment_begin -> R.parserZero
256 -- _ | c == account_section_sep -> R.parserZero
257 _ | R.is_space_horizontal c -> do
258 _ <- R.notFollowedBy $ R.space_horizontal
259 return c <* (R.lookAhead $ R.try $
260 ( R.try (R.char account_section_sep)
261 <|> account_name_char
263 _ | not (Data.Char.isSpace c) -> return c
266 -- ** Read 'Test_Account_Section'
269 => (String -> ParsecT s u m Test_Text)
270 -> ParsecT s u m Test_Account_Section
271 test_account_section make_test_text = do
274 <* R.lookAhead account_section_end
275 >> return Test_Account_Section_Any
276 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
277 >>= (liftM Test_Account_Section_Text . make_test_text)
278 , R.lookAhead account_section_end
279 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
280 >> return Test_Account_Section_Many
283 account_section_end =
285 [ R.char account_section_sep >> return ()
286 , R.space_horizontal >> return ()
290 -- ** Read 'Test_Account'
291 account_section_sep :: Char
292 account_section_sep = ':'
296 => ParsecT s u m Test_Account
298 R.notFollowedBy $ R.space_horizontal
299 make_test_text <- test_text
300 R.many1_separated (test_account_section make_test_text) $
301 R.char account_section_sep
303 -- ** Read 'Test_Date'
305 :: (Stream s (R.Error_State Error m) Char, Monad m)
306 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
310 (return $ read_date_pattern)
311 , test_ord >>= \tst ->
314 let (year, _, _) = Date.gregorian $ context_date ctx
315 Date.Read.date Error_Test_Date (Just year)
316 >>= return . Bool . Test_Date_UTC . tst
320 :: (Stream s (R.Error_State e m) Char, Monad m)
321 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
322 read_date_pattern = (do
323 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
324 n0 <- read_range $ R.many1 R.digit
325 n1 <- R.option Nothing $ R.try $ do
327 Just <$> read_range read2
328 n2 <- R.option Nothing $ R.try $ do
330 Just <$> read_range read2
331 let (year, month, dom) =
333 (Nothing, Nothing) ->
337 (Just d1, Nothing) ->
341 (Nothing, Just _d2) -> assert False undefined
342 (Just d1, Just d2) ->
343 ( R.integer_of_digits 10 <$> n0
346 (hour, minute, second) <-
347 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
348 R.skipMany1 $ R.space_horizontal
349 hour <- read_range read2
350 sep <- Date.Read.hour_separator
351 minute <- read_range read2
352 second <- R.option test_range_all $ R.try $ do
354 read_range $ R.many1 R.digit
355 -- tz <- R.option Time.utc $ R.try $ do
356 -- R.skipMany $ R.space_horizontal
357 -- Date.Read.time_zone
360 , of_digits <$> minute
361 , of_digits <$> second
366 [ just_when_bounded Test_Date_Year year
367 , just_when_bounded Test_Date_Month month
368 , just_when_bounded Test_Date_DoM dom
369 , just_when_bounded Test_Date_Hour hour
370 , just_when_bounded Test_Date_Minute minute
371 , just_when_bounded Test_Date_Second second
375 of_digits :: Num n => [Char] -> n
376 of_digits = fromInteger . R.integer_of_digits 10
377 just_when_bounded f x =
379 Test_Range_In Nothing Nothing -> Nothing
380 _ -> Just $ Bool $ f x
382 read_range :: Stream s m Char
384 -> ParsecT s u m (Test_Range a)
385 read_range read_digits = do
387 [ R.char '*' >> return Nothing
388 , Just <$> read_digits
392 (Test_Range_In a0 <$> R.choice_try
393 [ R.char '*' >> return Nothing
394 , Just <$> read_digits
396 , return $ maybe test_range_all Test_Range_Eq a0
401 => ParsecT s u m String
405 -- ** Read 'Test_Tag'
411 => ParsecT s u m Test_Tag
413 make_test_text <- test_text
416 <* R.lookAhead test_tag_name_end
417 >> return (Test_Tag_Name Test_Text_Any)
418 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
419 >>= (liftM Test_Tag_Name . make_test_text)
424 [ test_text_operator >> return ()
425 , R.space_horizontal >> return ()
430 => ParsecT s u m Test_Tag
432 make_test_text <- test_text
435 <* R.lookAhead test_tag_value_end
436 >> return (Test_Tag_Value Test_Text_Any)
437 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
438 >>= (liftM Test_Tag_Value . make_test_text)
443 [ R.space_horizontal >> return ()
449 => ParsecT s u m (Test_Bool Test_Tag)
453 [ R.lookAhead (R.try $ test_tag_operator)
454 >> And (Bool n) . Bool <$> test_tag_value
460 => ParsecT s u m String
464 -- ** Read 'Test_Posting'
466 :: (Stream s m Char, Filter.Posting t)
467 => ParsecT s Context m (Test_Bool (Test_Posting t))
469 Data.Foldable.foldr Filter.And Filter.Any <$>
472 >> R.lookAhead R.anyToken
473 >> test_bool test_posting_terms
476 :: (Stream s m Char, Filter.Posting t)
477 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
480 ( Filter.Test_Posting_Account
484 -- ** Read 'Test_Transaction'
486 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t)
487 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
489 Data.Foldable.foldr Filter.And Filter.Any <$>
492 >> R.lookAhead R.anyToken
493 >> test_bool test_transaction_terms
495 test_transaction_terms
496 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m)
497 => [ParsecT s Context (R.Error_State Error m)
498 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
499 test_transaction_terms =
500 -- , jump [ "account","acct" ] comp_text test_account
501 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
502 -- , jump [ "atag" ] comp_text parseFilterATag
503 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
504 -- , jump [ "code" ] comp_text parseFilterCode
505 [ jump [ "date" ] test_date_operator
506 (Filter.Test_Transaction_Date <$> test_date)
507 , jump [ "tag" ] test_tag_operator
508 (Filter.Test_Transaction_Tag <$> test_tag)
509 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
510 -- , jump [ "depth" ] comp_num parseFilterDepth
511 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
512 -- , jump [ "real" ] (R.char '=') parseFilterReal
513 -- , jump [ "status" ] (R.char '=') parseFilterStatus
514 -- , jump [ "sym" ] comp_text parseFilterSym
515 -- , R.lookAhead comp_num >> return parseFilterAmount
517 ( Filter.Test_Transaction_Posting
518 . Filter.Test_Posting_Account
522 jump :: Stream s m Char
527 jump prefixes next r =
529 (map (\s -> R.string s >> return r) prefixes)
530 <* R.lookAhead (R.try next)
532 -- ** Read 'Test_Balance'
534 :: (Stream s m Char, Filter.Balance t)
535 => ParsecT s Context m (Test_Bool (Test_Balance t))
537 Data.Foldable.foldr Filter.And Filter.Any <$>
540 >> R.lookAhead R.anyToken
541 >> test_bool test_balance_terms
544 :: (Stream s m Char, Filter.Balance t)
545 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
548 ( Filter.Test_Balance_Account
554 account :: Stream s m Char => ParsecT s Context m Filter
556 o <- R.optionMaybe comp_text
557 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
558 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
560 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
561 parseFilterAmount = do
567 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
570 liftM (uncurry (ATag c))
573 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
574 --parseFilterCode = do
582 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
585 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
586 parseFilterBalance = do
589 a <- parseAmount Nothing
590 return $ Bal (nc, absc) a
592 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
597 periodexprdatespan (qCtxDay ctx)
599 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
600 parseFilterDate2 = do
604 periodexprdatespan (qCtxDay ctx)
606 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
612 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
613 parseFilterDepth = do
615 liftM (Depth c . fromIntegral) $
618 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
623 -- | Read the boolean value part of a "status:" query, allowing "*" as
624 -- another way to spell True, similar to the journal file format.
625 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
626 parseFilterStatus = do
629 try (R.char '*' >> return True) <|> bool
631 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
632 --parseFilterSym = do
637 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
640 liftM (uncurry (Tag c))