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
168 , [ binary " & " Filter.And R.AssocLeft
170 , [ binary " + " Filter.Or R.AssocLeft
171 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
175 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
176 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
177 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
181 => String -> ParsecT s u m ()
182 test_bool_operator name =
185 >> R.notFollowedBy test_bool_operator_letter
189 test_bool_operator_letter
190 :: Stream s m Char => ParsecT s u m Char
191 test_bool_operator_letter =
192 R.oneOf ['-', '&', '+']
196 => [ParsecT s u m (ParsecT s u m t)]
197 -> ParsecT s u m (Test_Bool t)
198 test_bool_term terms = do
200 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
201 >> (return $ parens $
202 Data.Foldable.foldr Filter.And Filter.Any <$>
203 R.many (R.try (R.spaces >> expr)) ))
204 : map ((Filter.Bool <$>) <$>) terms
205 ) <* R.spaces <?> "boolean-expression")
208 R.lookAhead (R.try R.anyToken)
209 >> R.notFollowedBy (R.char ')')
218 (R.spaces >> R.char '(')
219 (R.spaces >> R.char ')')
221 bool :: Stream s m Char => ParsecT s u m Bool
236 -- ** Read Account.'Account.Name'
237 account_name :: Stream s m Char => ParsecT s u m Account.Name
240 R.many1 $ R.try account_name_char
242 account_name_char :: Stream s m Char => ParsecT s u m Char
243 account_name_char = do
246 -- _ | c == comment_begin -> R.parserZero
247 -- _ | c == account_section_sep -> R.parserZero
248 _ | R.is_space_horizontal c -> do
249 _ <- R.notFollowedBy $ R.space_horizontal
250 return c <* (R.lookAhead $ R.try $
251 ( R.try (R.char account_section_sep)
252 <|> account_name_char
254 _ | not (Data.Char.isSpace c) -> return c
257 -- ** Read 'Test_Account_Section'
260 => (String -> ParsecT s u m Test_Text)
261 -> ParsecT s u m Test_Account_Section
262 test_account_section make_test_text = do
265 <* R.lookAhead account_section_end
266 >> return Test_Account_Section_Any
267 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
268 >>= (liftM Test_Account_Section_Text . make_test_text)
269 , R.lookAhead account_section_end
270 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
271 >> return Test_Account_Section_Many
274 account_section_end =
276 [ R.char account_section_sep >> return ()
277 , R.space_horizontal >> return ()
281 -- ** Read 'Test_Account'
282 account_section_sep :: Char
283 account_section_sep = ':'
287 => ParsecT s u m Test_Account
289 R.notFollowedBy $ R.space_horizontal
290 make_test_text <- test_text
291 R.many1_separated (test_account_section make_test_text) $
292 R.char account_section_sep
294 -- ** Read 'Test_Date'
296 :: (Stream s (R.Error_State Error m) Char, Monad m)
297 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
301 (return $ read_date_pattern)
302 , test_ord >>= \tst ->
305 let (year, _, _) = Date.gregorian $ context_date ctx
306 Date.Read.date Error_Test_Date (Just year)
307 >>= return . Bool . Test_Date_UTC . tst
311 :: (Stream s (R.Error_State e m) Char, Monad m)
312 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
313 read_date_pattern = (do
314 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
315 n0 <- read_range $ R.many1 R.digit
316 n1 <- R.option Nothing $ R.try $ do
318 Just <$> read_range read2
319 n2 <- R.option Nothing $ R.try $ do
321 Just <$> read_range read2
322 let (year, month, dom) =
324 (Nothing, Nothing) ->
328 (Just d1, Nothing) ->
332 (Nothing, Just _d2) -> assert False undefined
333 (Just d1, Just d2) ->
334 ( R.integer_of_digits 10 <$> n0
337 (hour, minute, second) <-
338 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
339 R.skipMany1 $ R.space_horizontal
340 hour <- read_range read2
341 sep <- Date.Read.hour_separator
342 minute <- read_range read2
343 second <- R.option test_range_all $ R.try $ do
345 read_range $ R.many1 R.digit
346 -- tz <- R.option Time.utc $ R.try $ do
347 -- R.skipMany $ R.space_horizontal
348 -- Date.Read.time_zone
351 , of_digits <$> minute
352 , of_digits <$> second
357 [ just_when_bounded Test_Date_Year year
358 , just_when_bounded Test_Date_Month month
359 , just_when_bounded Test_Date_DoM dom
360 , just_when_bounded Test_Date_Hour hour
361 , just_when_bounded Test_Date_Minute minute
362 , just_when_bounded Test_Date_Second second
366 of_digits :: Num n => [Char] -> n
367 of_digits = fromInteger . R.integer_of_digits 10
368 just_when_bounded f x =
370 Test_Range_In Nothing Nothing -> Nothing
371 _ -> Just $ Bool $ f x
373 read_range :: Stream s m Char
375 -> ParsecT s u m (Test_Range a)
376 read_range read_digits = do
378 [ R.char '*' >> return Nothing
379 , Just <$> read_digits
383 (Test_Range_In a0 <$> R.choice_try
384 [ R.char '*' >> return Nothing
385 , Just <$> read_digits
387 , return $ maybe test_range_all Test_Range_Eq a0
392 => ParsecT s u m String
396 -- ** Read 'Test_Tag'
402 => ParsecT s u m Test_Tag
404 make_test_text <- test_text
407 <* R.lookAhead test_tag_name_end
408 >> return (Test_Tag_Name Test_Text_Any)
409 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
410 >>= (liftM Test_Tag_Name . make_test_text)
415 [ test_text_operator >> return ()
416 , R.space_horizontal >> return ()
421 => ParsecT s u m Test_Tag
423 make_test_text <- test_text
426 <* R.lookAhead test_tag_value_end
427 >> return (Test_Tag_Value Test_Text_Any)
428 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
429 >>= (liftM Test_Tag_Value . make_test_text)
434 [ R.space_horizontal >> return ()
440 => ParsecT s u m (Test_Bool Test_Tag)
444 [ R.lookAhead (R.try $ test_tag_operator)
445 >> And (Bool n) . Bool <$> test_tag_value
451 => ParsecT s u m String
455 -- ** Read 'Test_Posting'
457 :: (Stream s m Char, Filter.Posting t)
458 => ParsecT s Context m (Test_Bool (Test_Posting t))
460 Data.Foldable.foldr Filter.And Filter.Any <$>
463 >> R.lookAhead R.anyToken
464 >> test_bool test_posting_terms
467 :: (Stream s m Char, Filter.Posting t)
468 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
471 ( Filter.Test_Posting_Account
475 -- ** Read 'Test_Transaction'
477 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t)
478 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
480 Data.Foldable.foldr Filter.And Filter.Any <$>
483 >> R.lookAhead R.anyToken
484 >> test_bool test_transaction_terms
486 test_transaction_terms
487 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m)
488 => [ParsecT s Context (R.Error_State Error m)
489 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
490 test_transaction_terms =
491 -- , jump [ "account","acct" ] comp_text test_account
492 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
493 -- , jump [ "atag" ] comp_text parseFilterATag
494 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
495 -- , jump [ "code" ] comp_text parseFilterCode
496 [ jump [ "date" ] test_date_operator
497 (Filter.Test_Transaction_Date <$> test_date)
498 , jump [ "tag" ] test_tag_operator
499 (Filter.Test_Transaction_Tag <$> test_tag)
500 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
501 -- , jump [ "depth" ] comp_num parseFilterDepth
502 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
503 -- , jump [ "real" ] (R.char '=') parseFilterReal
504 -- , jump [ "status" ] (R.char '=') parseFilterStatus
505 -- , jump [ "sym" ] comp_text parseFilterSym
506 -- , R.lookAhead comp_num >> return parseFilterAmount
508 ( Filter.Test_Transaction_Posting
509 . Filter.Test_Posting_Account
513 jump :: Stream s m Char
518 jump prefixes next r =
520 (map (\s -> R.string s >> return r) prefixes)
521 <* R.lookAhead (R.try next)
523 -- ** Read 'Test_Balance'
525 :: (Stream s m Char, Filter.Balance t)
526 => ParsecT s Context m (Test_Bool (Test_Balance t))
528 Data.Foldable.foldr Filter.And Filter.Any <$>
531 >> R.lookAhead R.anyToken
532 >> test_bool test_balance_terms
535 :: (Stream s m Char, Filter.Balance t)
536 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
539 ( Filter.Test_Balance_Account
545 account :: Stream s m Char => ParsecT s Context m Filter
547 o <- R.optionMaybe comp_text
548 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
549 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
551 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
552 parseFilterAmount = do
558 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
561 liftM (uncurry (ATag c))
564 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
565 --parseFilterCode = do
573 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
576 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
577 parseFilterBalance = do
580 a <- parseAmount Nothing
581 return $ Bal (nc, absc) a
583 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
588 periodexprdatespan (qCtxDay ctx)
590 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
591 parseFilterDate2 = do
595 periodexprdatespan (qCtxDay ctx)
597 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
603 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
604 parseFilterDepth = do
606 liftM (Depth c . fromIntegral) $
609 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
614 -- | Read the boolean value part of a "status:" query, allowing "*" as
615 -- another way to spell True, similar to the journal file format.
616 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
617 parseFilterStatus = do
620 try (R.char '*' >> return True) <|> bool
622 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
623 --parseFilterSym = do
628 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
631 liftM (uncurry (Tag c))