]> Git — Sourcephile - comptalang.git/blob - lib/Hcompta/Model/Filter/Read.hs
Polissage : utilise Control.Monad.join plutôt que >>= id
[comptalang.git] / lib / Hcompta / Model / Filter / Read.hs
1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 module Hcompta.Model.Filter.Read where
6
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
13 import Data.Data
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
20 ( char
21 , anyChar
22 , crlf
23 , newline
24 , noneOf
25 , oneOf
26 , satisfy
27 , space
28 , spaces
29 , string
30 )
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 ()
37
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
47
48 -- * Parsers' types
49
50 -- ** Type 'Context'
51
52 data Context
53 = Context
54 { context_date :: Date
55 } deriving (Data, Eq, Show, Typeable)
56
57 context :: Context
58 context =
59 Context
60 { context_date = Date.nil
61 }
62
63 -- ** Type 'Error'
64
65 data Error
66 = Error_Unknown
67 | Error_Test_Date Date.Read.Error
68 deriving (Show)
69
70 -- * Read
71
72 read ::
73 ( Stream s (R.Error_State Error Identity) Char
74 , Show t
75 )
76 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
77 -> s -> IO (Either [R.Error Error] (Test_Bool t))
78 read t s = do
79 context_date <- Time.getCurrentTime
80 return $
81 R.runParser_with_Error t context{context_date} "" s
82
83 -- ** Read 'Test_Text'
84 test_text
85 :: (Stream s m Char, Monad r)
86 => ParsecT s u m (String -> r Test_Text)
87 test_text =
88 R.choice_try
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))
92 ]
93
94 test_text_operator
95 :: Stream s m Char
96 => ParsecT s u m String
97 test_text_operator =
98 R.choice_try
99 [ R.string "="
100 , R.string "~"
101 ]
102
103 -- ** Read 'Test_Ord'
104 test_ord
105 :: (Stream s m Char, Ord o)
106 => ParsecT s u m (o -> Test_Ord o)
107 test_ord =
108 R.choice_try
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
114 ]
115
116 test_ord_operator
117 :: Stream s m Char
118 => ParsecT s u m String
119 test_ord_operator =
120 R.choice_try
121 [ R.string "="
122 , R.string "<="
123 , R.string ">="
124 , R.string "<"
125 , R.string ">"
126 ]
127
128 -- ** Read 'Test_Num_Abs'
129 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)))
132 test_num_abs =
133 R.choice_try
134 [ R.char '+' >> return (return . Right . Test_Num_Abs)
135 , return (return . Left)
136 ]
137
138 text :: Stream s m Char => String -> ParsecT s Context m Text
139 text none_of =
140 fromString <$>
141 R.choice_try
142 [ borders inside
143 , R.many $ R.noneOf ("() " ++ none_of)
144 ]
145 where
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++')':[]))
149
150 -- ** Read 'Test_Bool'
151
152 test_bool
153 :: (Stream s m Char)
154 => [ParsecT s u m (ParsecT s u m t)]
155 -> ParsecT s u m (Test_Bool t)
156 test_bool terms =
157 R.buildExpressionParser
158 test_bool_operators
159 (test_bool_term terms)
160 <?> "test_bool"
161
162 test_bool_operators
163 :: Stream s m Char
164 => R.OperatorTable s u m (Filter.Test_Bool t)
165 test_bool_operators =
166 [ [ prefix "- " Filter.Not
167 , prefix "not " Filter.Not
168 ]
169 , [ binary " & " Filter.And R.AssocLeft
170 , binary " and " Filter.And R.AssocLeft
171 , binary " - " (flip Filter.And . Filter.Not) R.AssocLeft
172 , binary " but " (flip Filter.And . Filter.Not) R.AssocLeft
173 ]
174 , [ binary " + " Filter.Or R.AssocLeft
175 , binary " or " Filter.Or R.AssocLeft
176 ]
177 ]
178 where
179 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
180 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
181 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
182
183 test_bool_operator
184 :: Stream s m Char
185 => String -> ParsecT s u m ()
186 test_bool_operator name =
187 lexeme $ R.try $
188 (R.string name
189 >> R.notFollowedBy test_bool_operator_letter
190 <?> ("end of " ++ show name))
191
192 test_bool_operator_letter
193 :: Stream s m Char => ParsecT s u m Char
194 test_bool_operator_letter =
195 R.oneOf ['+', '-', '&']
196
197 test_bool_term
198 :: Stream s m Char
199 => [ParsecT s u m (ParsecT s u m t)]
200 -> ParsecT s u m (Test_Bool t)
201 test_bool_term terms = do
202 join (R.choice_try
203 ( (R.lookAhead (R.try (R.spaces >> R.char '('))
204 >> (return $ parens $
205 Data.Foldable.foldr Filter.And Filter.Any <$>
206 R.many (R.try (R.spaces >> expr)) ))
207 : map ((Filter.Bool <$>) <$>) terms
208 ) <* R.spaces <?> "boolean-expression")
209 where
210 expr =
211 R.lookAhead (R.try R.anyToken)
212 >> R.notFollowedBy (R.char ')')
213 >> test_bool terms
214
215 lexeme
216 :: Stream s m Char
217 => ParsecT s u m a
218 -> ParsecT s u m a
219 lexeme p = p <* R.spaces
220
221 parens
222 :: Stream s m Char
223 => ParsecT s u m a
224 -> ParsecT s u m a
225 parens =
226 R.between
227 (R.spaces >> R.char '(')
228 (R.spaces >> R.char ')')
229
230 bool :: Stream s m Char => ParsecT s u m Bool
231 bool = do
232 R.choice_try
233 [ R.choice_try
234 [ R.string "1"
235 , R.string "true"
236 , R.string "t"
237 ] >> return True
238 , R.choice_try
239 [ R.string "0"
240 , R.string "false"
241 , R.string "f"
242 ] >> return False
243 ]
244
245 -- ** Read Account.'Account.Name'
246 account_name :: Stream s m Char => ParsecT s u m Account.Name
247 account_name = do
248 fromString <$> do
249 R.many1 $ R.try account_name_char
250 where
251 account_name_char :: Stream s m Char => ParsecT s u m Char
252 account_name_char = do
253 c <- R.anyChar
254 case c of
255 -- _ | c == comment_begin -> R.parserZero
256 -- _ | c == account_section_sep -> R.parserZero
257 _ | R.is_space_horizontal c -> do
258 _ <- R.notFollowedBy $ R.space_horizontal
259 return c <* (R.lookAhead $ R.try $
260 ( R.try (R.char account_section_sep)
261 <|> account_name_char
262 ))
263 _ | not (Data.Char.isSpace c) -> return c
264 _ -> R.parserZero
265
266 -- ** Read 'Test_Account_Section'
267 test_account_section
268 :: (Stream s m Char)
269 => (String -> ParsecT s u m Test_Text)
270 -> ParsecT s u m Test_Account_Section
271 test_account_section make_test_text = do
272 R.choice_try
273 [ R.char '*'
274 <* R.lookAhead account_section_end
275 >> return Test_Account_Section_Any
276 , R.many1 (R.satisfy (\c -> c /= account_section_sep && not (Data.Char.isSpace c)))
277 >>= (liftM Test_Account_Section_Text . make_test_text)
278 , R.lookAhead account_section_end
279 >> R.many (R.try (R.char account_section_sep >> R.lookAhead (R.try account_section_end)))
280 >> return Test_Account_Section_Many
281 ]
282 where
283 account_section_end =
284 R.choice_try
285 [ R.char account_section_sep >> return ()
286 , R.space_horizontal >> return ()
287 , R.eof
288 ]
289
290 -- ** Read 'Test_Account'
291 account_section_sep :: Char
292 account_section_sep = ':'
293
294 test_account
295 :: Stream s m Char
296 => ParsecT s u m Test_Account
297 test_account = do
298 R.notFollowedBy $ R.space_horizontal
299 make_test_text <- test_text
300 R.many1_separated (test_account_section make_test_text) $
301 R.char account_section_sep
302
303 -- ** Read 'Test_Date'
304 test_date
305 :: (Stream s (R.Error_State Error m) Char, Monad m)
306 => ParsecT s Context (R.Error_State Error m) (Test_Bool Test_Date)
307 test_date = do
308 join $ R.choice_try
309 [ R.char '=' >>
310 (return $ read_date_pattern)
311 , test_ord >>= \tst ->
312 return $ do
313 ctx <- R.getState
314 let (year, _, _) = Date.gregorian $ context_date ctx
315 Date.Read.date Error_Test_Date (Just year)
316 >>= return . Bool . Test_Date_UTC . tst
317 ]
318 where
319 read_date_pattern
320 :: (Stream s (R.Error_State e m) Char, Monad m)
321 => ParsecT s u (R.Error_State e m) (Test_Bool Test_Date)
322 read_date_pattern = (do
323 let read2 = R.try (R.count 2 R.digit) <|> R.count 1 R.digit
324 n0 <- read_range $ R.many1 R.digit
325 n1 <- R.option Nothing $ R.try $ do
326 _ <- R.char '/'
327 Just <$> read_range read2
328 n2 <- R.option Nothing $ R.try $ do
329 _ <- R.char '/'
330 Just <$> read_range read2
331 let (year, month, dom) =
332 case (n1, n2) of
333 (Nothing, Nothing) ->
334 ( test_range_all
335 , of_digits <$> n0
336 , test_range_all )
337 (Just d1, Nothing) ->
338 ( test_range_all
339 , of_digits <$> n0
340 , of_digits <$> d1 )
341 (Nothing, Just _d2) -> assert False undefined
342 (Just d1, Just d2) ->
343 ( R.integer_of_digits 10 <$> n0
344 , of_digits <$> d1
345 , of_digits <$> d2 )
346 (hour, minute, second) <-
347 R.option (test_range_all, test_range_all, test_range_all) $ R.try $ do
348 R.skipMany1 $ R.space_horizontal
349 hour <- read_range read2
350 sep <- Date.Read.hour_separator
351 minute <- read_range read2
352 second <- R.option test_range_all $ R.try $ do
353 _ <- R.char sep
354 read_range $ R.many1 R.digit
355 -- tz <- R.option Time.utc $ R.try $ do
356 -- R.skipMany $ R.space_horizontal
357 -- Date.Read.time_zone
358 return
359 ( of_digits <$> hour
360 , of_digits <$> minute
361 , of_digits <$> second
362 )
363 return $
364 foldr And Any $
365 catMaybes $
366 [ just_when_bounded Test_Date_Year year
367 , just_when_bounded Test_Date_Month month
368 , just_when_bounded Test_Date_DoM dom
369 , just_when_bounded Test_Date_Hour hour
370 , just_when_bounded Test_Date_Minute minute
371 , just_when_bounded Test_Date_Second second
372 ]
373 ) <?> "date-filter"
374 where
375 of_digits :: Num n => [Char] -> n
376 of_digits = fromInteger . R.integer_of_digits 10
377 just_when_bounded f x =
378 case x of
379 Test_Range_In Nothing Nothing -> Nothing
380 _ -> Just $ Bool $ f x
381
382 read_range :: Stream s m Char
383 => ParsecT s u m a
384 -> ParsecT s u m (Test_Range a)
385 read_range read_digits = do
386 a0 <- R.choice_try
387 [ R.char '*' >> return Nothing
388 , Just <$> read_digits
389 ]
390 R.choice_try
391 [ R.char '-' >>
392 (Test_Range_In a0 <$> R.choice_try
393 [ R.char '*' >> return Nothing
394 , Just <$> read_digits
395 ])
396 , return $ maybe test_range_all Test_Range_Eq a0
397 ]
398
399 test_date_operator
400 :: Stream s m Char
401 => ParsecT s u m String
402 test_date_operator =
403 test_ord_operator
404
405 -- ** Read 'Test_Tag'
406 tag_name_sep :: Char
407 tag_name_sep = ':'
408
409 test_tag_name
410 :: Stream s m Char
411 => ParsecT s u m Test_Tag
412 test_tag_name = do
413 make_test_text <- test_text
414 R.choice_try
415 [ R.char '*'
416 <* R.lookAhead test_tag_name_end
417 >> return (Test_Tag_Name Test_Text_Any)
418 , R.many1 (R.notFollowedBy (R.try test_tag_name_end) >> R.anyChar)
419 >>= (liftM Test_Tag_Name . make_test_text)
420 ]
421 where
422 test_tag_name_end =
423 R.choice_try
424 [ test_text_operator >> return ()
425 , R.space_horizontal >> return ()
426 , R.eof
427 ]
428 test_tag_value
429 :: Stream s m Char
430 => ParsecT s u m Test_Tag
431 test_tag_value = do
432 make_test_text <- test_text
433 R.choice_try
434 [ R.char '*'
435 <* R.lookAhead test_tag_value_end
436 >> return (Test_Tag_Value Test_Text_Any)
437 , R.many1 (R.notFollowedBy (R.try test_tag_value_end) >> R.anyChar)
438 >>= (liftM Test_Tag_Value . make_test_text)
439 ]
440 where
441 test_tag_value_end =
442 R.choice_try
443 [ R.space_horizontal >> return ()
444 , R.eof
445 ]
446
447 test_tag
448 :: Stream s m Char
449 => ParsecT s u m (Test_Bool Test_Tag)
450 test_tag = do
451 n <- test_tag_name
452 R.choice_try
453 [ R.lookAhead (R.try $ test_tag_operator)
454 >> And (Bool n) . Bool <$> test_tag_value
455 , return $ Bool n
456 ]
457
458 test_tag_operator
459 :: Stream s m Char
460 => ParsecT s u m String
461 test_tag_operator =
462 test_text_operator
463
464 -- ** Read 'Test_Posting'
465 test_posting
466 :: (Stream s m Char, Filter.Posting t)
467 => ParsecT s Context m (Test_Bool (Test_Posting t))
468 test_posting =
469 Data.Foldable.foldr Filter.And Filter.Any <$>
470 do R.many $
471 R.spaces
472 >> R.lookAhead R.anyToken
473 >> test_bool test_posting_terms
474
475 test_posting_terms
476 :: (Stream s m Char, Filter.Posting t)
477 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
478 test_posting_terms =
479 [ return
480 ( Filter.Test_Posting_Account
481 <$> test_account )
482 ]
483
484 -- ** Read 'Test_Transaction'
485 test_transaction
486 :: (Stream s (R.Error_State Error m) Char, Monad m, Filter.Transaction t)
487 => ParsecT s Context (R.Error_State Error m) (Test_Bool (Test_Transaction t))
488 test_transaction =
489 Data.Foldable.foldr Filter.And Filter.Any <$>
490 do R.many $
491 R.spaces
492 >> R.lookAhead R.anyToken
493 >> test_bool test_transaction_terms
494
495 test_transaction_terms
496 :: (Stream s (R.Error_State Error m) Char, Filter.Transaction t, Monad m)
497 => [ParsecT s Context (R.Error_State Error m)
498 (ParsecT s Context (R.Error_State Error m) (Test_Transaction t))]
499 test_transaction_terms =
500 -- , jump [ "account","acct" ] comp_text test_account
501 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
502 -- , jump [ "atag" ] comp_text parseFilterATag
503 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
504 -- , jump [ "code" ] comp_text parseFilterCode
505 [ jump [ "date" ] test_date_operator
506 (Filter.Test_Transaction_Date <$> test_date)
507 , jump [ "tag" ] test_tag_operator
508 (Filter.Test_Transaction_Tag <$> test_tag)
509 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
510 -- , jump [ "depth" ] comp_num parseFilterDepth
511 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
512 -- , jump [ "real" ] (R.char '=') parseFilterReal
513 -- , jump [ "status" ] (R.char '=') parseFilterStatus
514 -- , jump [ "sym" ] comp_text parseFilterSym
515 -- , R.lookAhead comp_num >> return parseFilterAmount
516 , return
517 ( Filter.Test_Transaction_Posting
518 . Filter.Test_Posting_Account
519 <$> test_account )
520 ]
521 where
522 jump :: Stream s m Char
523 => [String]
524 -> ParsecT s u m b
525 -> a
526 -> ParsecT s u m a
527 jump prefixes next r =
528 R.choice_try
529 (map (\s -> R.string s >> return r) prefixes)
530 <* R.lookAhead (R.try next)
531
532 -- ** Read 'Test_Balance'
533 test_balance
534 :: (Stream s m Char, Filter.Balance t)
535 => ParsecT s Context m (Test_Bool (Test_Balance t))
536 test_balance =
537 Data.Foldable.foldr Filter.And Filter.Any <$>
538 do R.many $
539 R.spaces
540 >> R.lookAhead R.anyToken
541 >> test_bool test_balance_terms
542
543 test_balance_terms
544 :: (Stream s m Char, Filter.Balance t)
545 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
546 test_balance_terms =
547 [ return
548 ( Filter.Test_Balance_Account
549 <$> test_account )
550 ]
551
552 {-
553
554 account :: Stream s m Char => ParsecT s Context m Filter
555 account = do
556 o <- R.optionMaybe comp_text
557 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
558 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
559
560 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
561 parseFilterAmount = do
562 Filter.Amount
563 <$> comp_num
564 <*> comp_num_abs
565 <*> amount
566
567 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
568 parseFilterATag = do
569 c <- comp_text
570 liftM (uncurry (ATag c))
571 parseTag
572
573 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
574 --parseFilterCode = do
575 -- string "code="
576 -- liftM Code $
577 -- try (do {
578 -- choice
579 -- [ inparen
580 -- , R.many nonspace
581 -- ]
582 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
583 -- })
584
585 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
586 parseFilterBalance = do
587 nc <- comp_num
588 absc <- comp_num_abs
589 a <- parseAmount Nothing
590 return $ Bal (nc, absc) a
591
592 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
593 parseFilterDate = do
594 R.char '='
595 ctx <- getState
596 liftM Date $
597 periodexprdatespan (qCtxDay ctx)
598
599 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
600 parseFilterDate2 = do
601 R.char '='
602 ctx <- getState
603 liftM Date2 $
604 periodexprdatespan (qCtxDay ctx)
605
606 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
607 parseFilterDesc = do
608 c <- comp_text
609 liftM (Desc c)
610 (string "")
611
612 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
613 parseFilterDepth = do
614 c <- comp_num
615 liftM (Depth c . fromIntegral) $
616 parseDecimal
617
618 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
619 parseFilterReal = do
620 R.char '='
621 liftM Real bool
622
623 -- | Read the boolean value part of a "status:" query, allowing "*" as
624 -- another way to spell True, similar to the journal file format.
625 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
626 parseFilterStatus = do
627 R.char '='
628 liftM Status $
629 try (R.char '*' >> return True) <|> bool
630
631 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
632 --parseFilterSym = do
633 -- string "cur="
634 -- liftM Sym
635 -- commoditysymbol
636
637 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
638 parseFilterTag = do
639 c <- comp_text
640 liftM (uncurry (Tag c))
641 parseTag
642 -}