]> Git — Sourcephile - comptalang.git/blob - lib/Hcompta/Filter/Read.hs
Modif : CLI.Command.{Print => Journal}.
[comptalang.git] / lib / Hcompta / Filter / Read.hs
1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE ScopedTypeVariables #-}
5 {-# LANGUAGE TypeFamilies #-}
6 module Hcompta.Filter.Read where
7
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
14 import Data.Data
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
21 ( char
22 , anyChar
23 , crlf
24 , newline
25 , noneOf
26 , oneOf
27 , satisfy
28 , space
29 , spaces
30 , string
31 )
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 ()
38
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
52
53 -- * Parsers' types
54
55 -- ** Type 'Context'
56
57 data Context
58 = Context
59 { context_date :: Date
60 } deriving (Data, Eq, Show, Typeable)
61
62 context :: Context
63 context =
64 Context
65 { context_date = Date.nil
66 }
67
68 -- ** Type 'Error'
69
70 data Error
71 = Error_Unknown
72 | Error_Test_Date Date.Read.Error
73 deriving (Show)
74
75 -- * Read
76
77 read ::
78 ( Stream s (R.Error_State Error Identity) Char
79 , Show t
80 )
81 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
82 -> s -> IO (Either [R.Error Error] (Test_Bool t))
83 read t s = do
84 context_date <- Time.getCurrentTime
85 return $
86 R.runParser_with_Error t context{context_date} "" s
87
88 -- ** Read 'Test_Text'
89 test_text
90 :: (Stream s m Char, Monad r)
91 => ParsecT s u m (String -> r Test_Text)
92 test_text =
93 R.choice_try
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))
97 ]
98
99 test_text_operator
100 :: Stream s m Char
101 => ParsecT s u m String
102 test_text_operator =
103 R.choice_try
104 [ R.string "="
105 , R.string "~"
106 ]
107
108 -- ** Read 'Test_Ord'
109 test_ord
110 :: (Stream s m Char, Ord o)
111 => ParsecT s u m (o -> Test_Ord o)
112 test_ord =
113 R.choice_try
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
119 ]
120
121 test_ord_operator
122 :: Stream s m Char
123 => ParsecT s u m String
124 test_ord_operator =
125 R.choice_try
126 [ R.string "="
127 , R.string "<="
128 , R.string ">="
129 , R.string "<"
130 , R.string ">"
131 ]
132
133 -- ** Read 'Test_Num_Abs'
134 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)))
137 test_num_abs =
138 R.choice_try
139 [ R.char '+' >> return (return . Right . Test_Num_Abs)
140 , return (return . Left)
141 ]
142
143 text :: Stream s m Char => String -> ParsecT s Context m Text
144 text none_of =
145 fromString <$>
146 R.choice_try
147 [ borders inside
148 , R.many $ R.noneOf ("() " ++ none_of)
149 ]
150 where
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++')':[]))
154
155 -- ** Read 'Test_Bool'
156
157 test_bool
158 :: (Stream s m Char)
159 => [ParsecT s u m (ParsecT s u m t)]
160 -> ParsecT s u m (Test_Bool t)
161 test_bool terms =
162 R.buildExpressionParser
163 test_bool_operators
164 (test_bool_term terms)
165 <?> "test_bool"
166
167 test_bool_operators
168 :: Stream s m Char
169 => R.OperatorTable s u m (Filter.Test_Bool t)
170 test_bool_operators =
171 [ [ prefix "- " Filter.Not
172 ]
173 , [ binary " & " Filter.And R.AssocLeft
174 ]
175 , [ binary " + " Filter.Or R.AssocLeft
176 , binary " - " (\x -> Filter.And x . Filter.Not) R.AssocLeft
177 ]
178 ]
179 where
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)
183
184 test_bool_operator
185 :: Stream s m Char
186 => String -> ParsecT s u m ()
187 test_bool_operator name =
188 R.try $
189 (R.string name
190 >> R.notFollowedBy test_bool_operator_letter
191 -- <* R.spaces
192 <?> name)
193
194 test_bool_operator_letter
195 :: Stream s m Char => ParsecT s u m Char
196 test_bool_operator_letter =
197 R.oneOf ['-', '&', '+']
198
199 test_bool_term
200 :: Stream s m Char
201 => [ParsecT s u m (ParsecT s u m t)]
202 -> ParsecT s u m (Test_Bool t)
203 test_bool_term terms = do
204 join (R.choice_try
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")
211 where
212 expr =
213 R.lookAhead (R.try R.anyToken)
214 >> R.notFollowedBy (R.char ')')
215 >> test_bool terms
216
217 parens
218 :: Stream s m Char
219 => ParsecT s u m a
220 -> ParsecT s u m a
221 parens =
222 R.between
223 (R.spaces >> R.char '(')
224 (R.spaces >> R.char ')')
225
226 bool :: Stream s m Char => ParsecT s u m Bool
227 bool = do
228 R.choice_try
229 [ R.choice_try
230 [ R.string "1"
231 , R.string "true"
232 , R.string "t"
233 ] >> return True
234 , R.choice_try
235 [ R.string "0"
236 , R.string "false"
237 , R.string "f"
238 ] >> return False
239 ]
240
241 jump :: Stream s m Char
242 => [String]
243 -> ParsecT s u m b
244 -> a
245 -> ParsecT s u m a
246 jump prefixes next r =
247 R.choice_try
248 (map (\s -> R.string s >> return r) prefixes)
249 <* R.lookAhead (R.try next)
250
251 -- ** Read Account.'Account.Name'
252 account_name :: Stream s m Char => ParsecT s u m Account.Name
253 account_name = do
254 fromString <$> do
255 R.many1 $ R.try account_name_char
256 where
257 account_name_char :: Stream s m Char => ParsecT s u m Char
258 account_name_char = do
259 c <- R.anyChar
260 case c of
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
268 ))
269 _ | not (Data.Char.isSpace c) -> return c
270 _ -> R.parserZero
271
272 -- ** Read 'Test_Account_Section'
273 test_account_section
274 :: (Stream s m Char)
275 => ParsecT s u m Test_Account_Section
276 test_account_section = do
277 R.choice_try
278 [ R.char '*'
279 <* R.lookAhead account_section_end
280 >> return Test_Account_Section_Any
281 , R.char '~'
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
289 ]
290 where
291 account_section_end =
292 R.choice_try
293 [ R.char account_section_sep >> return ()
294 , R.space_horizontal >> return ()
295 , R.eof
296 ]
297
298 -- ** Read 'Test_Account'
299 account_section_sep :: Char
300 account_section_sep = ':'
301
302 test_account
303 :: Stream s m Char
304 => ParsecT s u m Test_Account
305 test_account = do
306 R.notFollowedBy $ R.space_horizontal
307 R.many1_separated test_account_section $
308 R.char account_section_sep
309
310 test_account_operator
311 :: Stream s m Char
312 => ParsecT s u m String
313 test_account_operator =
314 test_text_operator
315
316 -- ** Read 'Test_Amount'
317 test_amount
318 :: Stream s m Char
319 => ParsecT s u m (Test_Amount Amount)
320 test_amount = do
321 R.notFollowedBy $ R.space_horizontal
322 R.choice_try
323 [ test_ord
324 >>= \tst -> do
325 amt <- Amount.Read.amount
326 return $ Test_Amount
327 (tst $ Amount.quantity amt) $
328 (Test_Unit $
329 case Unit.text $ Amount.unit amt of
330 unit | Text.null unit -> Test_Text_Any
331 unit -> Test_Text_Exact unit)
332 , test_text
333 >>= \tst -> do
334 unit <- Amount.Read.unit >>= tst . Text.unpack . Unit.text
335 return $ Test_Amount (Test_Ord_Any) (Test_Unit unit)
336 ]
337
338 test_amount_operator
339 :: Stream s m Char
340 => ParsecT s u m String
341 test_amount_operator =
342 R.choice_try
343 [ test_ord_operator
344 , test_text_operator
345 ]
346
347 -- ** Read 'Test_Date'
348 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)
351 test_date = do
352 join $ R.choice_try
353 [ R.char '=' >>
354 (return $ read_date_pattern)
355 , test_ord >>= \tst ->
356 return $ do
357 ctx <- R.getState
358 let (year, _, _) = Date.gregorian $ context_date ctx
359 Date.Read.date Error_Test_Date (Just year)
360 >>= return . Bool . Test_Date_UTC . tst
361 ]
362 where
363 read_date_pattern
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
370 _ <- R.char '/'
371 Just <$> read_range read2
372 n2 <- R.option Nothing $ R.try $ do
373 _ <- R.char '/'
374 Just <$> read_range read2
375 let (year, month, dom) =
376 case (n1, n2) of
377 (Nothing, Nothing) ->
378 ( test_range_all
379 , of_digits <$> n0
380 , test_range_all )
381 (Just d1, Nothing) ->
382 ( test_range_all
383 , of_digits <$> n0
384 , of_digits <$> d1 )
385 (Nothing, Just _d2) -> assert False undefined
386 (Just d1, Just d2) ->
387 ( R.integer_of_digits 10 <$> n0
388 , of_digits <$> d1
389 , of_digits <$> d2 )
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
397 _ <- R.char sep
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
402 return
403 ( of_digits <$> hour
404 , of_digits <$> minute
405 , of_digits <$> second
406 )
407 return $
408 foldr And Any $
409 catMaybes $
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
416 ]
417 ) <?> "date-filter"
418 where
419 of_digits :: Num n => [Char] -> n
420 of_digits = fromInteger . R.integer_of_digits 10
421 just_when_bounded f x =
422 case x of
423 Test_Range_In Nothing Nothing -> Nothing
424 _ -> Just $ Bool $ f x
425
426 read_range :: Stream s m Char
427 => ParsecT s u m a
428 -> ParsecT s u m (Test_Range a)
429 read_range read_digits = do
430 a0 <- R.choice_try
431 [ R.char '*' >> return Nothing
432 , Just <$> read_digits
433 ]
434 R.choice_try
435 [ R.char '-' >>
436 (Test_Range_In a0 <$> R.choice_try
437 [ R.char '*' >> return Nothing
438 , Just <$> read_digits
439 ])
440 , return $ maybe test_range_all Test_Range_Eq a0
441 ]
442
443 test_date_operator
444 :: Stream s m Char
445 => ParsecT s u m String
446 test_date_operator =
447 test_ord_operator
448
449 -- ** Read 'Test_Tag'
450 tag_name_sep :: Char
451 tag_name_sep = ':'
452
453 test_tag_name
454 :: Stream s m Char
455 => ParsecT s u m Test_Tag
456 test_tag_name = do
457 make_test_text <- test_text
458 R.choice_try
459 [ R.char '*'
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)
464 ]
465 where
466 test_tag_name_end =
467 R.choice_try
468 [ test_text_operator >> return ()
469 , R.space_horizontal >> return ()
470 , R.eof
471 ]
472 test_tag_value
473 :: Stream s m Char
474 => ParsecT s u m Test_Tag
475 test_tag_value = do
476 make_test_text <- test_text
477 R.choice_try
478 [ R.char '*'
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)
483 ]
484 where
485 test_tag_value_end =
486 R.choice_try
487 [ R.space_horizontal >> return ()
488 , R.eof
489 ]
490
491 test_tag
492 :: Stream s m Char
493 => ParsecT s u m (Test_Bool Test_Tag)
494 test_tag = do
495 n <- test_tag_name
496 R.choice_try
497 [ R.lookAhead (R.try $ test_tag_operator)
498 >> And (Bool n) . Bool <$> test_tag_value
499 , return $ Bool n
500 ]
501
502 test_tag_operator
503 :: Stream s m Char
504 => ParsecT s u m String
505 test_tag_operator =
506 test_text_operator
507
508 -- ** Read 'Test_Posting'
509 test_posting
510 :: (Stream s m Char, Filter.Posting t)
511 => ParsecT s Context m (Test_Bool (Test_Posting t))
512 test_posting =
513 Data.Foldable.foldr Filter.And Filter.Any <$>
514 do R.many $
515 R.spaces
516 >> R.lookAhead R.anyToken
517 >> test_bool test_posting_terms
518
519 test_posting_terms
520 :: (Stream s m Char, Filter.Posting t)
521 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
522 test_posting_terms =
523 [ return
524 ( Filter.Test_Posting_Account
525 <$> test_account )
526 ]
527
528 -- ** Read 'Test_Transaction'
529 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))
533 test_transaction =
534 Data.Foldable.foldr Filter.And Filter.Any <$>
535 do R.many $
536 R.spaces
537 >> R.lookAhead R.anyToken
538 >> test_bool test_transaction_terms
539
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
555 ) <$> test_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
562 , return
563 ( Filter.Test_Transaction_Posting
564 . Filter.Test_Posting_Account
565 <$> test_account )
566 ]
567
568 -- ** Read 'Test_Balance'
569 test_balance
570 :: (Stream s m Char, Filter.Balance t, Balance_Amount t ~ Amount)
571 => ParsecT s Context m (Test_Bool (Test_Balance t))
572 test_balance =
573 Data.Foldable.foldr Filter.And Filter.Any <$>
574 do R.many $
575 R.spaces
576 >> R.lookAhead R.anyToken
577 >> test_bool test_balance_terms
578
579 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))]
582 test_balance_terms =
583 [ jump [ "D" ] test_amount_operator
584 ( Filter.Test_Balance_Positive
585 <$> test_amount )
586 , jump [ "C" ] test_amount_operator
587 ( Filter.Test_Balance_Negative
588 <$> test_amount )
589 , jump [ "B", "" ] test_amount_operator
590 ( Filter.Test_Balance_Amount
591 <$> test_amount )
592 , return
593 ( Filter.Test_Balance_Account
594 <$> test_account )
595 ]
596
597 -- ** Read 'Test_GL'
598 test_gl
599 :: (Stream s m Char, Filter.GL t, GL_Amount t ~ Amount)
600 => ParsecT s Context m (Test_Bool (Test_GL t))
601 test_gl =
602 Data.Foldable.foldr Filter.And Filter.Any <$>
603 do R.many $
604 R.spaces
605 >> R.lookAhead R.anyToken
606 >> test_bool test_gl_terms
607
608 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))]
611 test_gl_terms =
612 [ jump [ "D" ] test_amount_operator
613 ( Filter.Test_GL_Amount_Positive
614 <$> test_amount )
615 , jump [ "C" ] test_amount_operator
616 ( Filter.Test_GL_Amount_Negative
617 <$> test_amount )
618 , jump [ "B" ] test_amount_operator
619 ( Filter.Test_GL_Amount_Balance
620 <$> test_amount )
621 , jump [ "TD" ] test_amount_operator
622 ( Filter.Test_GL_Sum_Positive
623 <$> test_amount )
624 , jump [ "TC" ] test_amount_operator
625 ( Filter.Test_GL_Sum_Negative
626 <$> test_amount )
627 , jump [ "TB" ] test_amount_operator
628 ( Filter.Test_GL_Sum_Balance
629 <$> test_amount )
630 , return
631 ( Filter.Test_GL_Account
632 <$> test_account )
633 ]