1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 module Hcompta.Model.Filter.Read where
5 import Prelude hiding (filter)
6 import Control.Applicative ((<$>){-, (<*>)-}, (<*))
7 import Control.Monad (liftM)
8 -- import Control.Monad.Trans.Except (ExceptT(..), throwE)
9 import qualified Data.Char
11 import qualified Data.Foldable
12 import Data.Functor.Identity (Identity)
13 import qualified Text.Parsec.Expr as R
14 import qualified Text.Parsec as R hiding
26 -- import qualified Text.Parsec.Expr as R
27 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
28 import Data.String (fromString)
29 import qualified Data.Text as Text
30 import Data.Text (Text)
31 import Data.Typeable ()
33 import qualified Hcompta.Lib.Regex as Regex
34 -- import Hcompta.Lib.Regex (Regex)
35 import qualified Hcompta.Model.Account as Account
36 import qualified Hcompta.Model.Filter as Filter
37 import Hcompta.Model.Filter
40 , Test_Account_Section(..)
46 , Test_Transaction(..)
49 import qualified Hcompta.Lib.Parsec as R
58 } deriving (Data, Eq, Show, Typeable)
71 -- ** Parse 'Test_Text'
73 :: (Stream s m Char, Monad r)
74 => ParsecT s u m (String -> r Test_Text)
77 [ R.char '~' >> return (\s -> Regex.of_StringM s >>= (return . Test_Text_Regex))
78 , R.char '=' >> return (\s -> return (Test_Text_Exact $ Text.pack s))
79 , return (\s -> return (Test_Text_Exact $ Text.pack s))
82 -- ** Parse 'Test_Ord'
84 :: (Stream s m Char, Ord o)
85 => ParsecT s u m (o -> m (Test_Ord o))
88 [ R.string "=" >> return (return . Test_Ord_Eq)
89 , R.string "<=" >> return (return . Test_Ord_Lt_Eq)
90 , R.string ">=" >> return (return . Test_Ord_Gt_Eq)
91 , R.string "<" >> return (return . Test_Ord_Lt)
92 , R.string ">" >> return (return . Test_Ord_Gt)
95 -- ** Parse 'Test_Num_Abs'
97 :: (Stream s m Char, Num n)
98 => ParsecT s u m (Test_Ord n -> m (Either (Test_Ord n) (Test_Num_Abs n)))
101 [ R.char '+' >> return (return . Right . Test_Num_Abs)
102 , return (return . Left)
105 text :: Stream s m Char => String -> ParsecT s Context m Text
110 , R.many $ R.noneOf ("() " ++ none_of)
113 borders = R.between (R.char '(') (R.char ')')
114 inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, R.many1 $ R.noneOf "()"])
115 preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
117 -- ** Parse 'Test_Bool'
121 => [ParsecT s Context m (ParsecT s Context m t)]
122 -> ParsecT s Context m (Test_Bool t)
124 R.buildExpressionParser
126 (test_bool_term terms)
131 => R.OperatorTable s u m (Filter.Test_Bool t)
132 test_bool_operators =
133 [ [ prefix "- " Filter.Not
134 , prefix "not " Filter.Not
136 , [ binary " & " Filter.And R.AssocLeft
137 , binary " and " Filter.And R.AssocLeft
138 , binary " - " (flip Filter.And . Filter.Not) R.AssocLeft
139 , binary " but " (flip Filter.And . Filter.Not) R.AssocLeft
141 , [ binary " + " Filter.Or R.AssocLeft
142 , binary " or " Filter.Or R.AssocLeft
146 binary name fun assoc = R.Infix (test_bool_operator name >> return fun) assoc
147 prefix name fun = R.Prefix (test_bool_operator name >> return fun)
148 -- postfix name fun = Text.Parsec.Expr.Postfix (test_bool_operator name >> return fun)
152 => String -> ParsecT s u m ()
153 test_bool_operator name =
156 >> R.notFollowedBy test_bool_operator_letter
157 <?> ("end of " ++ show name))
159 test_bool_operator_letter
160 :: Stream s m Char => ParsecT s u m Char
161 test_bool_operator_letter =
162 R.oneOf ['+', '-', '&']
166 => [ParsecT s Context m (ParsecT s Context m t)]
167 -> ParsecT s Context m (Test_Bool t)
168 test_bool_term terms = do
170 ( (R.lookAhead (R.try $ R.char '(')
171 >> (return $ parens $
172 Data.Foldable.foldr Filter.And Filter.Any <$>
173 R.many (R.spaces >> expr) ))
174 : map ((Filter.Bool <$>) <$>) terms
175 ) <* R.spaces <?> "filter expression"
179 R.lookAhead (R.try R.anyToken)
180 >> R.notFollowedBy (R.char ')')
185 => ParsecT s u m a -> ParsecT s u m a
186 lexeme p = p <* R.spaces
190 => ParsecT s u m a -> ParsecT s u m a
191 parens = R.between (lexeme $ R.char '(') (lexeme $ R.char ')')
193 bool :: Stream s m Char => ParsecT s u m Bool
208 -- ** Parse Account.'Account.Name'
209 account_name :: Stream s m Char => ParsecT s u m Account.Name
212 R.many1 $ R.try account_name_char
214 account_name_char :: Stream s m Char => ParsecT s u m Char
215 account_name_char = do
218 -- _ | c == comment_begin -> R.parserZero
219 -- _ | c == account_name_sep -> R.parserZero
220 _ | R.is_space_horizontal c -> do
221 _ <- R.notFollowedBy $ R.space_horizontal
222 return c <* (R.lookAhead $ R.try $
223 ( R.try (R.char account_name_sep)
224 <|> account_name_char
226 _ | not (Data.Char.isSpace c) -> return c
229 -- ** Parse 'Test_Account_Section'
232 => (String -> ParsecT s u m Test_Text)
233 -> ParsecT s u m Test_Account_Section
234 test_account_section make_test_text = do
237 <* R.lookAhead account_name_sep_or_eof
238 >> return Test_Account_Section_Any
239 , R.many1 (R.satisfy (\c -> c /= account_name_sep && not (Data.Char.isSpace c)))
240 >>= (liftM Test_Account_Section_Text . make_test_text)
241 , R.lookAhead account_name_sep_or_eof
242 >> R.many (R.try (R.char account_name_sep
243 >> R.lookAhead account_name_sep_or_eof))
244 >> return Test_Account_Section_Skip
247 account_name_sep_or_eof =
248 (R.try (R.char account_name_sep) >> return ())
251 -- ** Parse 'Test_Account'
252 account_name_sep :: Char
253 account_name_sep = ':'
257 => ParsecT s u m Test_Account
259 R.notFollowedBy $ R.space_horizontal
260 make_test_text <- test_text
261 R.many1_separated (test_account_section make_test_text) $
262 R.char account_name_sep
264 -- ** Parse 'Test_Posting'
266 :: (Stream s m Char, Filter.Posting t)
267 => ParsecT s Context m (Test_Bool (Test_Posting t))
269 Data.Foldable.foldr Filter.And Filter.Any <$>
272 >> R.lookAhead R.anyToken
273 >> test_bool test_posting_terms
276 :: (Stream s m Char, Filter.Posting t)
277 => [ParsecT s Context m (ParsecT s Context m (Test_Posting t))]
280 ( Filter.Test_Posting_Account
284 -- ** Parse 'Test_Transaction'
286 :: (Stream s m Char, Filter.Transaction t)
287 => ParsecT s Context m (Test_Bool (Test_Transaction t))
289 Data.Foldable.foldr Filter.And Filter.Any <$>
292 >> R.lookAhead R.anyToken
293 >> test_bool test_transaction_terms
295 test_transaction_terms
296 :: (Stream s m Char, Filter.Transaction t)
297 => [ParsecT s Context m (ParsecT s Context m (Test_Transaction t))]
298 test_transaction_terms =
300 ( Filter.Test_Transaction_Posting
301 . Filter.Test_Posting_Account
303 -- , jump [ "account","acct" ] comp_text test_account
304 -- , jump [ "amount", "amt" ] comp_num parseFilterAmount
305 -- , jump [ "atag" ] comp_text parseFilterATag
306 -- , jump [ "balance", "bal" ] comp_num parseFilterBalance
307 -- , jump [ "code" ] comp_text parseFilterCode
308 -- , jump [ "date" ] (R.char '=') parseFilterDate
309 -- , jump [ "date2", "edate" ] (R.char '=') parseFilterDate2
310 -- , jump [ "depth" ] comp_num parseFilterDepth
311 -- , jump [ "description","descr","desc" ] comp_text parseFilterDesc
312 -- , jump [ "real" ] (R.char '=') parseFilterReal
313 -- , jump [ "status" ] (R.char '=') parseFilterStatus
314 -- , jump [ "sym" ] comp_text parseFilterSym
315 -- , jump [ "tag" ] comp_text parseFilterTag
316 -- , R.lookAhead comp_num >> return parseFilterAmount
319 -- jump :: Stream s m Char => [String] -> ParsecT s u m b -> a -> ParsecT s u m a
322 -- (map (\s -> R.string s >> return r) l)
323 -- <* R.lookAhead next
325 -- ** Parse 'Test_Balance'
327 :: (Stream s m Char, Filter.Balance t)
328 => ParsecT s Context m (Test_Bool (Test_Balance t))
330 Data.Foldable.foldr Filter.And Filter.Any <$>
333 >> R.lookAhead R.anyToken
334 >> test_bool test_balance_terms
337 :: (Stream s m Char, Filter.Balance t)
338 => [ParsecT s Context m (ParsecT s Context m (Test_Balance t))]
341 ( Filter.Test_Balance_Account
347 ( Stream s (R.Error_State Error Identity) Char
350 => ParsecT s Context (R.Error_State Error Identity) (Test_Bool t)
351 -> s -> Either [R.Error Error] (Test_Bool t)
353 R.runParser_with_Error t context "" s
357 account :: Stream s m Char => ParsecT s Context m Filter
359 o <- R.optionMaybe comp_text
360 liftM (Filter.Account $ fromMaybe Comp_Text_Exact o)
361 (liftM (accountNameComponents) $ string (" \t"++"+-&"))
363 parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
364 parseFilterAmount = do
370 parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
373 liftM (uncurry (ATag c))
376 --parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
377 --parseFilterCode = do
385 -- return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
388 parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
389 parseFilterBalance = do
392 a <- parseAmount Nothing
393 return $ Bal (nc, absc) a
395 parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
400 periodexprdatespan (qCtxDay ctx)
402 parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
403 parseFilterDate2 = do
407 periodexprdatespan (qCtxDay ctx)
409 parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
415 parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
416 parseFilterDepth = do
418 liftM (Depth c . fromIntegral) $
421 parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
426 -- | Parse the boolean value part of a "status:" query, allowing "*" as
427 -- another way to spell True, similar to the journal file format.
428 parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
429 parseFilterStatus = do
432 try (R.char '*' >> return True) <|> bool
434 --parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
435 --parseFilterSym = do
440 parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
443 liftM (uncurry (Tag c))