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 -- ** Read 'Test_Amount'
312 => ParsecT s u m (Test_Amount Amount)
314 R.notFollowedBy $ R.space_horizontal
316 amt <- Amount.Read.amount
318 (tst $ Amount.quantity amt) $
320 case Unit.text $ Amount.unit amt of
321 unit | Text.null unit -> Test_Text_Any
322 unit -> Test_Text_Exact unit)
326 => ParsecT s u m String
327 test_amount_operator =
330 -- ** Read 'Test_Date'
332 :: (Stream s (R.Error_State Error m) Char, Monad m)
333 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
337 (return $ read_date_pattern)
338 , test_ord >>= \tst ->
341 let (year, _, _) = Date.gregorian $ context_date ctx
342 Date.Read.date Error_Test_Date (Just year)
343 >>= return . Bool . Test_Date_UTC . tst
347 :: (Stream s (R.Error_State e m) Char, Monad m)
348 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
349 read_date_pattern = (do
350 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
351 n0 <- read_range $ R.many1 R.digit
352 n1 <- R.option Nothing $ R.try $ do
354 Just <$> read_range read2
355 n2 <- R.option Nothing $ R.try $ do
357 Just <$> read_range read2
358 let (year, month, dom) =
360 (Nothing, Nothing) ->
364 (Just d1, Nothing) ->
368 (Nothing, Just _d2) -> assert False undefined
369 (Just d1, Just d2) ->
370 ( R.integer_of_digits 10 <$> n0
373 (hour, minute, second) <-
374 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
375 R.skipMany1 $ R.space_horizontal
376 hour <- read_range read2
377 sep <- Date.Read.hour_separator
378 minute <- read_range read2
379 second <- R.option test_range_all $ R.try $ do
381 read_range $ R.many1 R.digit
382 -- tz <- R.option Time.utc $ R.try $ do
383 -- R.skipMany $ R.space_horizontal
384 -- Date.Read.time_zone
387 , of_digits <$> minute
388 , of_digits <$> second
393 [ just_when_bounded Test_Date_Year year
394 , just_when_bounded Test_Date_Month month
395 , just_when_bounded Test_Date_DoM dom
396 , just_when_bounded Test_Date_Hour hour
397 , just_when_bounded Test_Date_Minute minute
398 , just_when_bounded Test_Date_Second second
402 of_digits :: Num n => [Char] -> n
403 of_digits = fromInteger . R.integer_of_digits 10
404 just_when_bounded f x =
406 Test_Range_In Nothing Nothing -> Nothing
407 _ -> Just $ Bool $ f x
409 read_range :: Stream s m Char
411 -> ParsecT s u m (Test_Range a)
412 read_range read_digits = do
414 [ R.char '*' >> return Nothing
415 , Just <$> read_digits
419 (Test_Range_In a0 <$> R.choice_try
420 [ R.char '*' >> return Nothing
421 , Just <$> read_digits
423 , return $ maybe test_range_all Test_Range_Eq a0
428 => ParsecT s u m String
432 -- ** Read 'Test_Tag'
438 => ParsecT s u m Test_Tag
440 make_test_text <- test_text
443 <* R.lookAhead test_tag_name_end
444 >> return (Test_Tag_Name Test_Text_Any)
445 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
446 >>= (liftM Test_Tag_Name . make_test_text)
451 [ test_text_operator >> return ()
452 , R.space_horizontal >> return ()
457 => ParsecT s u m Test_Tag
459 make_test_text <- test_text
462 <* R.lookAhead test_tag_value_end
463 >> return (Test_Tag_Value Test_Text_Any)
464 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
465 >>= (liftM Test_Tag_Value . make_test_text)
470 [ R.space_horizontal >> return ()
476 => ParsecT s u m (Test_Bool Test_Tag)
480 [ R.lookAhead (R.try $ test_tag_operator)
481 >> And (Bool n) . Bool <$> test_tag_value
487 => ParsecT s u m String
491 -- ** Read 'Test_Posting'
493 :: (Stream s m Char, Filter.Posting t)
494 => ParsecT s Context m (Test_Bool (Test_Posting t))
496 Data.Foldable.foldr Filter.And Filter.Any <$>
499 >> R.lookAhead R.anyToken
500 >> test_bool test_posting_terms
503 :: (Stream s m Char, Filter.Posting t)
504 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
507 ( Filter.Test_Posting_Account
511 -- ** Read 'Test_Transaction'
513 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t
514 , Posting_Amount (Transaction_Posting t) ~ Amount)
515 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
517 Data.Foldable.foldr Filter.And Filter.Any <$>
520 >> R.lookAhead R.anyToken
521 >> test_bool test_transaction_terms
523 test_transaction_terms
524 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m
525 , Posting_Amount (Transaction_Posting t) ~ Amount)
526 => [ParsecT s Context (R.Error_State Error m)
527 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
528 test_transaction_terms =
529 -- , jump [ "atag" ] comp_text parseFilterATag
530 -- , jump [ "code" ] comp_text parseFilterCode
531 [ jump [ "date" ] test_date_operator
532 (Filter.Test_Transaction_Date <$> test_date)
533 , jump [ "tag" ] test_tag_operator
534 (Filter.Test_Transaction_Tag <$> test_tag)
535 , jump [ "amount" ] test_amount_operator
536 (( Filter.Test_Transaction_Posting
537 . Filter.Test_Posting_Amount
539 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
540 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
541 -- , jump [ "real" ] (R.char '=') parseFilterReal
542 -- , jump [ "status" ] (R.char '=') parseFilterStatus
543 -- , jump [ "sym" ] comp_text parseFilterSym
544 -- , R.lookAhead comp_num >> return parseFilterAmount
546 ( Filter.Test_Transaction_Posting
547 . Filter.Test_Posting_Account
551 -- ** Read 'Test_Balance'
553 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
554 => ParsecT s Context m (Test_Bool (Test_Balance t))
556 Data.Foldable.foldr Filter.And Filter.Any <$>
559 >> R.lookAhead R.anyToken
560 >> test_bool test_balance_terms
563 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
564 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
566 [ jump [ "amount" ] test_amount_operator
567 ( Filter.Test_Balance_Amount
569 , jump [ "debit" ] test_amount_operator
570 ( Filter.Test_Balance_Positive
572 , jump [ "credit" ] test_amount_operator
573 ( Filter.Test_Balance_Negative
576 ( Filter.Test_Balance_Account