1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 {-# LANGUAGE TypeFamilies #-}
6 module Hcompta.Model.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.Model.Account as Account
42 import qualified Hcompta.Model.Amount as Amount
43 import Hcompta.Model.Amount (Amount)
44 import qualified Hcompta.Model.Amount.Read as Amount.Read
45 import qualified Hcompta.Model.Amount.Unit as Unit
46 import qualified Hcompta.Model.Date as Date
47 import Hcompta.Model.Date (Date)
48 import qualified Hcompta.Model.Date.Read as Date.Read
49 import qualified Hcompta.Model.Filter as Filter
50 import Hcompta.Model.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)
319 (Test_Unit $ Test_Text_Exact $ Unit.text $ Amount.unit amt)
323 => ParsecT s u m String
324 test_amount_operator =
327 -- ** Read 'Test_Date'
329 :: (Stream s (R.Error_State Error m) Char, Monad m)
330 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
334 (return $ read_date_pattern)
335 , test_ord >>= \tst ->
338 let (year, _, _) = Date.gregorian $ context_date ctx
339 Date.Read.date Error_Test_Date (Just year)
340 >>= return . Bool . Test_Date_UTC . tst
344 :: (Stream s (R.Error_State e m) Char, Monad m)
345 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
346 read_date_pattern = (do
347 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
348 n0 <- read_range $ R.many1 R.digit
349 n1 <- R.option Nothing $ R.try $ do
351 Just <$> read_range read2
352 n2 <- R.option Nothing $ R.try $ do
354 Just <$> read_range read2
355 let (year, month, dom) =
357 (Nothing, Nothing) ->
361 (Just d1, Nothing) ->
365 (Nothing, Just _d2) -> assert False undefined
366 (Just d1, Just d2) ->
367 ( R.integer_of_digits 10 <$> n0
370 (hour, minute, second) <-
371 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
372 R.skipMany1 $ R.space_horizontal
373 hour <- read_range read2
374 sep <- Date.Read.hour_separator
375 minute <- read_range read2
376 second <- R.option test_range_all $ R.try $ do
378 read_range $ R.many1 R.digit
379 -- tz <- R.option Time.utc $ R.try $ do
380 -- R.skipMany $ R.space_horizontal
381 -- Date.Read.time_zone
384 , of_digits <$> minute
385 , of_digits <$> second
390 [ just_when_bounded Test_Date_Year year
391 , just_when_bounded Test_Date_Month month
392 , just_when_bounded Test_Date_DoM dom
393 , just_when_bounded Test_Date_Hour hour
394 , just_when_bounded Test_Date_Minute minute
395 , just_when_bounded Test_Date_Second second
399 of_digits :: Num n => [Char] -> n
400 of_digits = fromInteger . R.integer_of_digits 10
401 just_when_bounded f x =
403 Test_Range_In Nothing Nothing -> Nothing
404 _ -> Just $ Bool $ f x
406 read_range :: Stream s m Char
408 -> ParsecT s u m (Test_Range a)
409 read_range read_digits = do
411 [ R.char '*' >> return Nothing
412 , Just <$> read_digits
416 (Test_Range_In a0 <$> R.choice_try
417 [ R.char '*' >> return Nothing
418 , Just <$> read_digits
420 , return $ maybe test_range_all Test_Range_Eq a0
425 => ParsecT s u m String
429 -- ** Read 'Test_Tag'
435 => ParsecT s u m Test_Tag
437 make_test_text <- test_text
440 <* R.lookAhead test_tag_name_end
441 >> return (Test_Tag_Name Test_Text_Any)
442 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
443 >>= (liftM Test_Tag_Name . make_test_text)
448 [ test_text_operator >> return ()
449 , R.space_horizontal >> return ()
454 => ParsecT s u m Test_Tag
456 make_test_text <- test_text
459 <* R.lookAhead test_tag_value_end
460 >> return (Test_Tag_Value Test_Text_Any)
461 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
462 >>= (liftM Test_Tag_Value . make_test_text)
467 [ R.space_horizontal >> return ()
473 => ParsecT s u m (Test_Bool Test_Tag)
477 [ R.lookAhead (R.try $ test_tag_operator)
478 >> And (Bool n) . Bool <$> test_tag_value
484 => ParsecT s u m String
488 -- ** Read 'Test_Posting'
490 :: (Stream s m Char, Filter.Posting t)
491 => ParsecT s Context m (Test_Bool (Test_Posting t))
493 Data.Foldable.foldr Filter.And Filter.Any <$>
496 >> R.lookAhead R.anyToken
497 >> test_bool test_posting_terms
500 :: (Stream s m Char, Filter.Posting t)
501 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
504 ( Filter.Test_Posting_Account
508 -- ** Read 'Test_Transaction'
510 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t
511 , Posting_Amount (Transaction_Posting t) ~ Amount)
512 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
514 Data.Foldable.foldr Filter.And Filter.Any <$>
517 >> R.lookAhead R.anyToken
518 >> test_bool test_transaction_terms
520 test_transaction_terms
521 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m
522 , Posting_Amount (Transaction_Posting t) ~ Amount)
523 => [ParsecT s Context (R.Error_State Error m)
524 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
525 test_transaction_terms =
526 -- , jump [ "atag" ] comp_text parseFilterATag
527 -- , jump [ "code" ] comp_text parseFilterCode
528 [ jump [ "date" ] test_date_operator
529 (Filter.Test_Transaction_Date <$> test_date)
530 , jump [ "tag" ] test_tag_operator
531 (Filter.Test_Transaction_Tag <$> test_tag)
532 , jump [ "amount" ] test_amount_operator
533 (( Filter.Test_Transaction_Posting
534 . Filter.Test_Posting_Amount
536 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
537 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
538 -- , jump [ "real" ] (R.char '=') parseFilterReal
539 -- , jump [ "status" ] (R.char '=') parseFilterStatus
540 -- , jump [ "sym" ] comp_text parseFilterSym
541 -- , R.lookAhead comp_num >> return parseFilterAmount
543 ( Filter.Test_Transaction_Posting
544 . Filter.Test_Posting_Account
548 -- ** Read 'Test_Balance'
550 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
551 => ParsecT s Context m (Test_Bool (Test_Balance t))
553 Data.Foldable.foldr Filter.And Filter.Any <$>
556 >> R.lookAhead R.anyToken
557 >> test_bool test_balance_terms
560 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
561 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
563 [ jump [ "amount" ] test_amount_operator
564 ( Filter.Test_Balance_Amount
566 , jump [ "debit" ] test_amount_operator
567 ( Filter.Test_Balance_Positive
569 , jump [ "credit" ] test_amount_operator
570 ( Filter.Test_Balance_Negative
573 ( Filter.Test_Balance_Account