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)
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 qualified Hcompta.Lib.Regex as Regex
40 -- import Hcompta.Lib.Regex (Regex)
41 import qualified Hcompta.Account as Account
42 import qualified Hcompta.Amount as Amount
43 import Hcompta.Amount (Amount)
44 import qualified Hcompta.Amount.Read as Amount.Read
45 import qualified Hcompta.Amount.Unit as Unit
46 import qualified Hcompta.Date as Date
47 import Hcompta.Date (Date)
48 import qualified Hcompta.Date.Read as Date.Read
49 import qualified Hcompta.Filter as Filter
50 import Hcompta.Filter hiding (Amount)
51 import qualified Hcompta.Lib.Parsec as R
59 { context_date :: Date
60 } deriving (Data, Eq, Show, Typeable)
65 { context_date = Date.nil
72 | Error_Test_Date Date.Read.Error
78 ( Stream s (R.Error_State Error Identity) Char
81 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
82 -> s -> IO (Either [R.Error Error] (Test_Bool t))
84 context_date <- Time.getCurrentTime
86 R.runParser_with_Error t context{context_date} "" s
88 -- ** Read 'Test_Text'
90 :: (Stream s m Char, Monad r)
91 => ParsecT s u m (String -> r Test_Text)
94 [ R.char '~' >> return (\s -> Regex.of_StringM s >>= (return . Test_Text_Regex))
95 , R.char '=' >> return (\s -> return (Test_Text_Exact $ Text.pack s))
96 , return (\s -> return (Test_Text_Exact $ Text.pack s))
101 => ParsecT s u m String
108 -- ** Read 'Test_Ord'
110 :: (Stream s m Char, Ord o)
111 => ParsecT s u m (o -> Test_Ord o)
114 [ R.string "=" >> return Test_Ord_Eq
115 , R.string "<=" >> return Test_Ord_Le
116 , R.string ">=" >> return Test_Ord_Ge
117 , R.string "<" >> return Test_Ord_Lt
118 , R.string ">" >> return Test_Ord_Gt
123 => ParsecT s u m String
133 -- ** Read 'Test_Num_Abs'
135 :: (Stream s m Char, Num n)
136 => ParsecT s u m (Test_Ord n -> m (Either (Test_Ord n) (Test_Num_Abs n)))
139 [ R.char '+' >> return (return . Right . Test_Num_Abs)
140 , return (return . Left)
143 text :: Stream s m Char => String -> ParsecT s Context m Text
148 , R.many $ R.noneOf ("() " ++ none_of)
151 borders = R.between (R.char '(') (R.char ')')
152 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
153 preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
155 -- ** Read 'Test_Bool'
159 => [ParsecT s u m (ParsecT s u m t)]
160 -> ParsecT s u m (Test_Bool t)
162 R.buildExpressionParser
164 (test_bool_term terms)
169 => R.OperatorTable s u m (Filter.Test_Bool t)
170 test_bool_operators =
171 [ [ prefix "- " Filter.Not
173 , [ binary " & " Filter.And R.AssocLeft
175 , [ binary " + " Filter.Or R.AssocLeft
176 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
180 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
181 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
182 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
186 => String -> ParsecT s u m ()
187 test_bool_operator name =
190 >> R.notFollowedBy test_bool_operator_letter
194 test_bool_operator_letter
195 :: Stream s m Char => ParsecT s u m Char
196 test_bool_operator_letter =
197 R.oneOf ['-', '&', '+']
201 => [ParsecT s u m (ParsecT s u m t)]
202 -> ParsecT s u m (Test_Bool t)
203 test_bool_term terms = do
205 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
206 >> (return $ parens $
207 Data.Foldable.foldr Filter.And Filter.Any <$>
208 R.many (R.try (R.spaces >> expr)) ))
209 : map ((Filter.Bool <$>) <$>) terms
210 ) <* R.spaces <?> "boolean-expression")
213 R.lookAhead (R.try R.anyToken)
214 >> R.notFollowedBy (R.char ')')
223 (R.spaces >> R.char '(')
224 (R.spaces >> R.char ')')
226 bool :: Stream s m Char => ParsecT s u m Bool
241 jump :: Stream s m Char
246 jump prefixes next r =
248 (map (\s -> R.string s >> return r) prefixes)
249 <* R.lookAhead (R.try next)
251 -- ** Read Account.'Account.Name'
252 account_name :: Stream s m Char => ParsecT s u m Account.Name
255 R.many1 $ R.try account_name_char
257 account_name_char :: Stream s m Char => ParsecT s u m Char
258 account_name_char = do
261 -- _ | c == comment_begin -> R.parserZero
262 -- _ | c == account_section_sep -> R.parserZero
263 _ | R.is_space_horizontal c -> do
264 _ <- R.notFollowedBy $ R.space_horizontal
265 return c <* (R.lookAhead $ R.try $
266 ( R.try (R.char account_section_sep)
267 <|> account_name_char
269 _ | not (Data.Char.isSpace c) -> return c
272 -- ** Read 'Test_Account_Section'
275 => (String -> ParsecT s u m Test_Text)
276 -> ParsecT s u m Test_Account_Section
277 test_account_section make_test_text = do
280 <* R.lookAhead account_section_end
281 >> return Test_Account_Section_Any
282 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
283 >>= (liftM Test_Account_Section_Text . make_test_text)
284 , R.lookAhead account_section_end
285 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
286 >> return Test_Account_Section_Many
289 account_section_end =
291 [ R.char account_section_sep >> return ()
292 , R.space_horizontal >> return ()
296 -- ** Read 'Test_Account'
297 account_section_sep :: Char
298 account_section_sep = ':'
302 => ParsecT s u m Test_Account
304 R.notFollowedBy $ R.space_horizontal
305 make_test_text <- test_text
306 R.many1_separated (test_account_section make_test_text) $
307 R.char account_section_sep
309 test_account_operator
311 => ParsecT s u m String
312 test_account_operator =
315 -- ** Read 'Test_Amount'
318 => ParsecT s u m (Test_Amount Amount)
320 R.notFollowedBy $ R.space_horizontal
322 amt <- Amount.Read.amount
324 (tst $ Amount.quantity amt) $
326 case Unit.text $ Amount.unit amt of
327 unit | Text.null unit -> Test_Text_Any
328 unit -> Test_Text_Exact unit)
332 => ParsecT s u m String
333 test_amount_operator =
336 -- ** Read 'Test_Date'
338 :: (Stream s (R.Error_State Error m) Char, Monad m)
339 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
343 (return $ read_date_pattern)
344 , test_ord >>= \tst ->
347 let (year, _, _) = Date.gregorian $ context_date ctx
348 Date.Read.date Error_Test_Date (Just year)
349 >>= return . Bool . Test_Date_UTC . tst
353 :: (Stream s (R.Error_State e m) Char, Monad m)
354 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
355 read_date_pattern = (do
356 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
357 n0 <- read_range $ R.many1 R.digit
358 n1 <- R.option Nothing $ R.try $ do
360 Just <$> read_range read2
361 n2 <- R.option Nothing $ R.try $ do
363 Just <$> read_range read2
364 let (year, month, dom) =
366 (Nothing, Nothing) ->
370 (Just d1, Nothing) ->
374 (Nothing, Just _d2) -> assert False undefined
375 (Just d1, Just d2) ->
376 ( R.integer_of_digits 10 <$> n0
379 (hour, minute, second) <-
380 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
381 R.skipMany1 $ R.space_horizontal
382 hour <- read_range read2
383 sep <- Date.Read.hour_separator
384 minute <- read_range read2
385 second <- R.option test_range_all $ R.try $ do
387 read_range $ R.many1 R.digit
388 -- tz <- R.option Time.utc $ R.try $ do
389 -- R.skipMany $ R.space_horizontal
390 -- Date.Read.time_zone
393 , of_digits <$> minute
394 , of_digits <$> second
399 [ just_when_bounded Test_Date_Year year
400 , just_when_bounded Test_Date_Month month
401 , just_when_bounded Test_Date_DoM dom
402 , just_when_bounded Test_Date_Hour hour
403 , just_when_bounded Test_Date_Minute minute
404 , just_when_bounded Test_Date_Second second
408 of_digits :: Num n => [Char] -> n
409 of_digits = fromInteger . R.integer_of_digits 10
410 just_when_bounded f x =
412 Test_Range_In Nothing Nothing -> Nothing
413 _ -> Just $ Bool $ f x
415 read_range :: Stream s m Char
417 -> ParsecT s u m (Test_Range a)
418 read_range read_digits = do
420 [ R.char '*' >> return Nothing
421 , Just <$> read_digits
425 (Test_Range_In a0 <$> R.choice_try
426 [ R.char '*' >> return Nothing
427 , Just <$> read_digits
429 , return $ maybe test_range_all Test_Range_Eq a0
434 => ParsecT s u m String
438 -- ** Read 'Test_Tag'
444 => ParsecT s u m Test_Tag
446 make_test_text <- test_text
449 <* R.lookAhead test_tag_name_end
450 >> return (Test_Tag_Name Test_Text_Any)
451 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
452 >>= (liftM Test_Tag_Name . make_test_text)
457 [ test_text_operator >> return ()
458 , R.space_horizontal >> return ()
463 => ParsecT s u m Test_Tag
465 make_test_text <- test_text
468 <* R.lookAhead test_tag_value_end
469 >> return (Test_Tag_Value Test_Text_Any)
470 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
471 >>= (liftM Test_Tag_Value . make_test_text)
476 [ R.space_horizontal >> return ()
482 => ParsecT s u m (Test_Bool Test_Tag)
486 [ R.lookAhead (R.try $ test_tag_operator)
487 >> And (Bool n) . Bool <$> test_tag_value
493 => ParsecT s u m String
497 -- ** Read 'Test_Posting'
499 :: (Stream s m Char, Filter.Posting t)
500 => ParsecT s Context m (Test_Bool (Test_Posting t))
502 Data.Foldable.foldr Filter.And Filter.Any <$>
505 >> R.lookAhead R.anyToken
506 >> test_bool test_posting_terms
509 :: (Stream s m Char, Filter.Posting t)
510 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
513 ( Filter.Test_Posting_Account
517 -- ** Read 'Test_Transaction'
519 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t
520 , Posting_Amount (Transaction_Posting t) ~ Amount)
521 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
523 Data.Foldable.foldr Filter.And Filter.Any <$>
526 >> R.lookAhead R.anyToken
527 >> test_bool test_transaction_terms
529 test_transaction_terms
530 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m
531 , Posting_Amount (Transaction_Posting t) ~ Amount)
532 => [ParsecT s Context (R.Error_State Error m)
533 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
534 test_transaction_terms =
535 -- , jump [ "atag" ] comp_text parseFilterATag
536 -- , jump [ "code" ] comp_text parseFilterCode
537 [ jump [ "date" ] test_date_operator
538 (Filter.Test_Transaction_Date <$> test_date)
539 , jump [ "tag" ] test_tag_operator
540 (Filter.Test_Transaction_Tag <$> test_tag)
541 , jump [ "amount" ] test_amount_operator
542 (( Filter.Test_Transaction_Posting
543 . Filter.Test_Posting_Amount
545 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
546 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
547 -- , jump [ "real" ] (R.char '=') parseFilterReal
548 -- , jump [ "status" ] (R.char '=') parseFilterStatus
549 -- , jump [ "sym" ] comp_text parseFilterSym
550 -- , R.lookAhead comp_num >> return parseFilterAmount
552 ( Filter.Test_Transaction_Posting
553 . Filter.Test_Posting_Account
557 -- ** Read 'Test_Balance'
559 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
560 => ParsecT s Context m (Test_Bool (Test_Balance t))
562 Data.Foldable.foldr Filter.And Filter.Any <$>
565 >> R.lookAhead R.anyToken
566 >> test_bool test_balance_terms
569 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
570 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
572 [ jump [ "amount" ] test_amount_operator
573 ( Filter.Test_Balance_Amount
575 , jump [ "debit" ] test_amount_operator
576 ( Filter.Test_Balance_Positive
578 , jump [ "credit" ] test_amount_operator
579 ( Filter.Test_Balance_Negative
582 ( Filter.Test_Balance_Account
588 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
589 => ParsecT s Context m (Test_Bool (Test_GL t))
591 Data.Foldable.foldr Filter.And Filter.Any <$>
594 >> R.lookAhead R.anyToken
595 >> test_bool test_gl_terms
598 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
599 => [ParsecT s Context m (ParsecT s Context m (Test_GL t))]
601 [ jump [ "account" ] test_account_operator
602 ( Filter.Test_GL_Account
604 , jump [ "debit" ] test_amount_operator
605 ( Filter.Test_GL_Amount_Positive
607 , jump [ "credit" ] test_amount_operator
608 ( Filter.Test_GL_Amount_Negative
610 , jump [ "amount" ] test_amount_operator
611 ( Filter.Test_GL_Amount_Balance
613 , jump [ "total_debit" ] test_amount_operator
614 ( Filter.Test_GL_Sum_Positive
616 , jump [ "total_credit" ] test_amount_operator
617 ( Filter.Test_GL_Sum_Negative
619 , jump [ "total" ] test_amount_operator
620 ( Filter.Test_GL_Sum_Balance
623 ( Filter.Test_GL_Account