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 => ParsecT s u m Test_Account_Section
276 test_account_section = do
279 <* R.lookAhead account_section_end
280 >> 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 . Test_Text_Regex) . Regex.of_StringM)
284 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
285 >>= (liftM (Test_Account_Section_Text . Test_Text_Exact) . return . Text.pack)
286 , R.lookAhead account_section_end
287 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
288 >> return Test_Account_Section_Many
291 account_section_end =
293 [ R.char account_section_sep >> return ()
294 , R.space_horizontal >> return ()
298 -- ** Read 'Test_Account'
299 account_section_sep :: Char
300 account_section_sep = ':'
304 => ParsecT s u m Test_Account
306 R.notFollowedBy $ R.space_horizontal
307 R.many1_separated test_account_section $
308 R.char account_section_sep
310 test_account_operator
312 => ParsecT s u m String
313 test_account_operator =
316 -- ** Read 'Test_Amount'
319 => ParsecT s u m (Test_Amount Amount)
321 R.notFollowedBy $ R.space_horizontal
325 amt <- Amount.Read.amount
327 (tst $ Amount.quantity amt) $
329 case Unit.text $ Amount.unit amt of
330 unit | Text.null unit -> Test_Text_Any
331 unit -> Test_Text_Exact unit)
334 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
335 return $ Test_Amount (Test_Ord_Any) (Test_Unit unit)
340 => ParsecT s u m String
341 test_amount_operator =
347 -- ** Read 'Test_Date'
349 :: (Stream s (R.Error_State Error m) Char, Monad m)
350 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
354 (return $ read_date_pattern)
355 , test_ord >>= \tst ->
358 let (year, _, _) = Date.gregorian $ context_date ctx
359 Date.Read.date Error_Test_Date (Just year)
360 >>= return . Bool . Test_Date_UTC . tst
364 :: (Stream s (R.Error_State e m) Char, Monad m)
365 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
366 read_date_pattern = (do
367 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
368 n0 <- read_range $ R.many1 R.digit
369 n1 <- R.option Nothing $ R.try $ do
371 Just <$> read_range read2
372 n2 <- R.option Nothing $ R.try $ do
374 Just <$> read_range read2
375 let (year, month, dom) =
377 (Nothing, Nothing) ->
381 (Just d1, Nothing) ->
385 (Nothing, Just _d2) -> assert False undefined
386 (Just d1, Just d2) ->
387 ( R.integer_of_digits 10 <$> n0
390 (hour, minute, second) <-
391 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
392 R.skipMany1 $ R.space_horizontal
393 hour <- read_range read2
394 sep <- Date.Read.hour_separator
395 minute <- read_range read2
396 second <- R.option test_range_all $ R.try $ do
398 read_range $ R.many1 R.digit
399 -- tz <- R.option Time.utc $ R.try $ do
400 -- R.skipMany $ R.space_horizontal
401 -- Date.Read.time_zone
404 , of_digits <$> minute
405 , of_digits <$> second
410 [ just_when_bounded Test_Date_Year year
411 , just_when_bounded Test_Date_Month month
412 , just_when_bounded Test_Date_DoM dom
413 , just_when_bounded Test_Date_Hour hour
414 , just_when_bounded Test_Date_Minute minute
415 , just_when_bounded Test_Date_Second second
419 of_digits :: Num n => [Char] -> n
420 of_digits = fromInteger . R.integer_of_digits 10
421 just_when_bounded f x =
423 Test_Range_In Nothing Nothing -> Nothing
424 _ -> Just $ Bool $ f x
426 read_range :: Stream s m Char
428 -> ParsecT s u m (Test_Range a)
429 read_range read_digits = do
431 [ R.char '*' >> return Nothing
432 , Just <$> read_digits
436 (Test_Range_In a0 <$> R.choice_try
437 [ R.char '*' >> return Nothing
438 , Just <$> read_digits
440 , return $ maybe test_range_all Test_Range_Eq a0
445 => ParsecT s u m String
449 -- ** Read 'Test_Tag'
455 => ParsecT s u m Test_Tag
457 make_test_text <- test_text
460 <* R.lookAhead test_tag_name_end
461 >> return (Test_Tag_Name Test_Text_Any)
462 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
463 >>= (liftM Test_Tag_Name . make_test_text)
468 [ test_text_operator >> return ()
469 , R.space_horizontal >> return ()
474 => ParsecT s u m Test_Tag
476 make_test_text <- test_text
479 <* R.lookAhead test_tag_value_end
480 >> return (Test_Tag_Value Test_Text_Any)
481 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
482 >>= (liftM Test_Tag_Value . make_test_text)
487 [ R.space_horizontal >> return ()
493 => ParsecT s u m (Test_Bool Test_Tag)
497 [ R.lookAhead (R.try $ test_tag_operator)
498 >> And (Bool n) . Bool <$> test_tag_value
504 => ParsecT s u m String
508 -- ** Read 'Test_Posting'
510 :: (Stream s m Char, Filter.Posting t)
511 => ParsecT s Context m (Test_Bool (Test_Posting t))
513 Data.Foldable.foldr Filter.And Filter.Any <$>
516 >> R.lookAhead R.anyToken
517 >> test_bool test_posting_terms
520 :: (Stream s m Char, Filter.Posting t)
521 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
524 ( Filter.Test_Posting_Account
528 -- ** Read 'Test_Transaction'
530 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t
531 , Posting_Amount (Transaction_Posting t) ~ Amount)
532 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
534 Data.Foldable.foldr Filter.And Filter.Any <$>
537 >> R.lookAhead R.anyToken
538 >> test_bool test_transaction_terms
540 test_transaction_terms
541 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m
542 , Posting_Amount (Transaction_Posting t) ~ Amount)
543 => [ParsecT s Context (R.Error_State Error m)
544 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
545 test_transaction_terms =
546 -- , jump [ "atag" ] comp_text parseFilterATag
547 -- , jump [ "code" ] comp_text parseFilterCode
548 [ jump [ "date" ] test_date_operator
549 (Filter.Test_Transaction_Date <$> test_date)
550 , jump [ "tag" ] test_tag_operator
551 (Filter.Test_Transaction_Tag <$> test_tag)
552 , jump [ "amount" ] test_amount_operator
553 (( Filter.Test_Transaction_Posting
554 . Filter.Test_Posting_Amount
556 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
557 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
558 -- , jump [ "real" ] (R.char '=') parseFilterReal
559 -- , jump [ "status" ] (R.char '=') parseFilterStatus
560 -- , jump [ "sym" ] comp_text parseFilterSym
561 -- , R.lookAhead comp_num >> return parseFilterAmount
563 ( Filter.Test_Transaction_Posting
564 . Filter.Test_Posting_Account
568 -- ** Read 'Test_Balance'
570 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
571 => ParsecT s Context m (Test_Bool (Test_Balance t))
573 Data.Foldable.foldr Filter.And Filter.Any <$>
576 >> R.lookAhead R.anyToken
577 >> test_bool test_balance_terms
580 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
581 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
583 [ jump [ "D" ] test_amount_operator
584 ( Filter.Test_Balance_Positive
586 , jump [ "C" ] test_amount_operator
587 ( Filter.Test_Balance_Negative
589 , jump [ "B", "" ] test_amount_operator
590 ( Filter.Test_Balance_Amount
593 ( Filter.Test_Balance_Account
599 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
600 => ParsecT s Context m (Test_Bool (Test_GL t))
602 Data.Foldable.foldr Filter.And Filter.Any <$>
605 >> R.lookAhead R.anyToken
606 >> test_bool test_gl_terms
609 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
610 => [ParsecT s Context m (ParsecT s Context m (Test_GL t))]
612 [ jump [ "D" ] test_amount_operator
613 ( Filter.Test_GL_Amount_Positive
615 , jump [ "C" ] test_amount_operator
616 ( Filter.Test_GL_Amount_Negative
618 , jump [ "B" ] test_amount_operator
619 ( Filter.Test_GL_Amount_Balance
621 , jump [ "TD" ] test_amount_operator
622 ( Filter.Test_GL_Sum_Positive
624 , jump [ "TC" ] test_amount_operator
625 ( Filter.Test_GL_Sum_Negative
627 , jump [ "TB" ] test_amount_operator
628 ( Filter.Test_GL_Sum_Balance
631 ( Filter.Test_GL_Account