Ajout : Model.Filter.Read
authorJulien Moutinho <julm+hcompta@autogeree.net>
Fri, 15 May 2015 03:50:24 +0000 (05:50 +0200)
committerJulien Moutinho <julm+hcompta@autogeree.net>
Tue, 19 May 2015 05:33:11 +0000 (07:33 +0200)
lib/Hcompta/Model/Amount.hs
lib/Hcompta/Model/Filter.hs
lib/Hcompta/Model/Filter/Read.hs [new file with mode: 0644]

index ba22ff1741cdef5949d309ec0d62be053b79401a..37d8d60aa9a04e473c4f00827aac7ded431f871e 100644 (file)
@@ -239,7 +239,9 @@ usd q =
 
 -- ** Tests
 
--- | Return 'True' if and only if 'Amount'’s 'Quantity' is zero at 'Style'’s precision.
+-- | Return 'True' if and only if 'Amount'’s 'Quantity' is zero.
+--
+--  NOTE: the 'Amount'’s 'quantity' MUST be have been rounded at 'Amount'’s 'style'’s 'Style.precision'.
 is_zero :: Amount -> Bool
 is_zero = Quantity.is_zero . quantity
 
@@ -278,7 +280,7 @@ nil_By_Unit =
 
 -- ** Tests
 
--- | Return 'True' if and only if all 'Amount's are zero at their 'Style'’s precision.
+-- | Return 'True' if and only if all 'Amount's satisfy 'is_zero'.
 are_zero :: By_Unit -> Bool
 are_zero = Data.Foldable.all is_zero
 
index 4f5beed520842ba155b45a1dc3e0cc0a95cefe5d..2357b561bb59c6e5873bc4089292d0cfdfcd1daf 100644 (file)
@@ -14,38 +14,37 @@ import qualified Hcompta.Model.Transaction.Tag as Tag
 -- * The 'Filter' type
 
 data Filter
- = Account Comparison_String Account
- | Account_Tag Comparison_String String (Maybe (Comparison_String, String))
- | Amount (Comparison_Num, Comparison_Num_Absolute) Amount
+ = Account Comp_String Account
+ | Account_Tag Comp_String String (Maybe (Comp_String, String))
+ | Amount Comp_Num Comp_Num_Absolute Amount
  | And Filter Filter
  | Any
- | Balance (Comparison_Num, Comparison_Num_Absolute) Amount
- | Commodity Comparison_String String
- | Date  Date.Span
- | Date2 Date.Span
- | Depth Comparison_Num Int
- | Description Comparison_String String
+ | Balance Comp_Num Comp_Num_Absolute Amount
+ | Date Date.Span
+ | Depth Comp_Num Int
+ | Description Comp_String String
  | None
  | Not Filter
  | Or Filter Filter
  | Real Bool
  | Status Bool
- | Tag Comparison_String Tag.Name (Maybe (Comparison_String, Tag.Value))
+ | Tag Comp_String Tag.Name (Maybe (Comp_String, Tag.Value))
+ | Unit Comp_String String
  deriving (Data, Eq, Show, Typeable)
 
-data Comparison_Num
- =   Comparison_Num_Lt
- |   Comparison_Num_Lt_Eq
- |   Comparison_Num_Gt
- |   Comparison_Num_Gt_Eq
- |   Comparison_Num_Eq
+data Comp_Num
+ =   Comp_Num_Lt
+ |   Comp_Num_Lt_Eq
+ |   Comp_Num_Gt
+ |   Comp_Num_Gt_Eq
+ |   Comp_Num_Eq
  deriving (Data, Eq, Show, Typeable)
 
-type Comparison_Num_Absolute
+type Comp_Num_Absolute
  = Bool
 
-data Comparison_String
- =   Comparison_String_Eq
- |   Comparison_String_Regexp
+data Comp_String
+ =   Comp_String_Eq
+ |   Comp_String_Regexp
  deriving (Data, Eq, Show, Typeable)
 
diff --git a/lib/Hcompta/Model/Filter/Read.hs b/lib/Hcompta/Model/Filter/Read.hs
new file mode 100644 (file)
index 0000000..3485b6a
--- /dev/null
@@ -0,0 +1,260 @@
+module Hcompta.Model.Filter.Read where
+
+import           Control.Monad (liftM)
+import           Control.Applicative ((<$>))
+import           Data.Maybe (isJust)
+import qualified Text.Parsec as R
+import qualified Text.Parsec.Expr as R
+import           Text.Parsec (Stream, ParsecT, (<|>), (<?>))
+
+import qualified Hcompta.Model.Filter as Filter
+import           Hcompta.Model.Filter (Comp_Number(..), Comp_String(..))
+
+-- * The 'Context' type
+
+data Context
+ =   Context
+ { context_day :: !Day
+ } deriving (Data, Eq, Show, Typeable)
+
+-- * Comparing
+
+comp_string :: Stream s m Char => ParsecT s u m Comp_String
+comp_string =
+       R.choice_try
+        [ R.string "=" >> return Comp_String_Eq
+        , R.string "~" >> return Comp_String_Regexp
+        ]
+
+comp_number :: Stream s m Char => ParsecT s u m Comp_Number
+comp_number =
+       R.choice_try
+        [ R.string "="  >> return Comp_Number_Eq
+        , R.string "<=" >> return Comp_Number_Lt_Eq
+        , R.string ">=" >> return Comp_Number_Gt_Eq
+        , R.string "<"  >> return Comp_Number_Lt
+        , R.string ">"  >> return Comp_Number_Gt
+        ]
+
+comp_number_absolute :: Stream s m Char => ParsecT s u m Comp_Num_Absolute
+comp_number_absolute =
+       liftM isJust $ R.optionMaybe (char '+')
+
+string :: Stream s m Char => String -> ParsecT s Context m String
+string none_of =
+       R.choice_try [borders inside, R.many $ R.noneOf ("() " ++ none_of)]
+       where
+               borders inside = between (char '(') (char ')') inside
+               inside = liftM concat $ R.many (R.choice_try [borders preserve_inside, many1 $ R.noneOf "()"])
+               preserve_inside = inside >>= (\x -> return $ '(':(x++')':[]))
+
+parseBool :: Stream s m Char => ParsecT s u m Bool
+parseBool = do
+       R.choice_try
+        [ R.choice_try
+                [ R.string "1"
+                , R.string "true"
+                , R.string "t"
+                ] >> return True
+        , R.choice_try
+                [ R.string "0"
+                , R.string "false"
+                , R.string "f"
+                ] >> return False
+        ]
+
+account :: Stream s m Char => ParsecT s Context m Filter
+account = do
+       o <- R.optionMaybe comp_string
+       liftM (Filter.Account $ fromMaybe Comp_String_Eq o)
+        (liftM (accountNameComponents) $ string (" \t"++"+-&"))
+
+parseFilterAmount :: Stream s m Char => ParsecT s Context m Filter
+parseFilterAmount = do
+       Filter.Amount
+        <$> comp_number
+        <*> comp_number_absolute
+        <*> amount
+
+parseFilterATag :: Stream s m Char => ParsecT s Context m Filter
+parseFilterATag = do
+       c <- comp_string
+       liftM (uncurry (ATag c))
+        parseTag
+
+--parseFilterCode :: Stream s m Char => ParsecT s Context m Filter
+--parseFilterCode = do
+--     string "code="
+--     liftM Code $
+--      try (do {
+               --              choice
+               --               [ inparen
+               --               , R.many nonspace
+               --               ]
+               --              return $ strip $ reverse $ dropWhile (==',') $ reverse $ strip v
+               --       })
+
+parseFilterBalance :: Stream s m Char => ParsecT s Context m Filter
+parseFilterBalance = do
+       nc <- comp_number
+       absc <- comp_number_absolute
+       a <- parseAmount Nothing
+       return $ Bal (nc, absc) a
+
+parseFilterDate :: Stream s m Char => ParsecT s Context m Filter
+parseFilterDate = do
+       char '='
+       ctx <- getState
+       liftM Date $
+        periodexprdatespan (qCtxDay ctx)
+
+parseFilterDate2 :: Stream s m Char => ParsecT s Context m Filter
+parseFilterDate2 = do
+       char '='
+       ctx <- getState
+       liftM Date2 $
+        periodexprdatespan (qCtxDay ctx)
+
+parseFilterDesc :: Stream s m Char => ParsecT s Context m Filter
+parseFilterDesc = do
+       c <- comp_string
+       liftM (Desc c)
+        (string "")
+
+parseFilterDepth :: Stream s m Char => ParsecT s Context m Filter
+parseFilterDepth = do
+       c <- comp_number
+       liftM (Depth c . fromIntegral) $
+        parseDecimal
+
+parseFilterReal :: Stream s m Char => ParsecT s Context m Filter
+parseFilterReal = do
+       char '='
+       liftM Real
+        parseBool
+
+-- | Parse the boolean value part of a "status:" query, allowing "*" as
+-- another way to spell True, similar to the journal file format.
+parseFilterStatus :: Stream s m Char => ParsecT s Context m Filter
+parseFilterStatus = do
+       char '='
+       liftM Status $
+        try (char '*' >> return True) <|> parseBool
+
+--parseFilterSym :: Stream s m Char => ParsecT s Context m Filter
+--parseFilterSym = do
+--     string "cur="
+--     liftM Sym
+--      commoditysymbol
+
+parseFilterTag :: Stream s m Char => ParsecT s Context m Filter
+parseFilterTag = do
+       c <- comp_string
+       liftM (uncurry (Tag c))
+        parseTag
+
+parens :: Stream s m Char => ParsecT s u m a -> ParsecT s u m a
+parens = between (parseLexeme $ char '(') (parseLexeme $ char ')')
+
+operators :: Stream s m Char => R.OperatorTable s u m Filter
+operators =
+       [ [ prefix "- "   Filter.Not
+         , prefix "not " Filter.Not
+         ]
+       , [ binary " & "   Filter.And R.AssocLeft
+         , binary " and " Filter.And R.AssocLeft
+         , binary " - "   (\x y -> Filter.And x (Filter.Not y)) R.AssocLeft
+         , binary " but " (\x y -> Filter.And x (Filter.Not y)) R.AssocLeft
+         ]
+       , [ binary " + "  Filter.Or R.AssocLeft
+         , binary " or " Filter.Or R.AssocLeft
+         ]
+       ]
+       where
+               binary  name fun assoc = R.Infix  (do{ operator name; return fun }) assoc
+               prefix  name fun       = R.Prefix (do{ operator name; return fun })
+               -- postfix name fun       = Text.Parsec.Expr.Postfix (do{ operator name; return fun })
+
+operator_letter :: Stream s m Char => ParsecT s u m Char
+operator_letter = oneOf ['+', '-', '&']
+
+operator :: Stream s m Char => String -> ParsecT s u m ()
+operator name =
+       parseLexeme $ try $
+       do{ R.string name
+         ; R.notFollowedBy operator_letter <?> ("end of " ++ show name)
+         }
+
+parseFilterTerm :: Stream s m Char => ParsecT s Context m Filter
+parseFilterTerm = do
+       r <- R.choice_try
+        [ R.lookAhead (char '(')
+               >> (return $
+                       parens (liftM (foldl (\acc x -> case acc of { Any -> x; _ -> And acc x }) Any) $
+                       R.many $ (parseWhiteSpace >> R.lookAhead R.anyToken >> R.notFollowedBy (char ')') >> parseFilterExpr)))
+        , jump [ "account","acct" ] comp_string parseFilterAccount
+        , jump [ "amount", "amt" ] comp_number parseFilterAmount
+        , jump [ "atag" ] comp_string parseFilterATag
+        , jump [ "balance", "bal" ] comp_number parseFilterBalance
+        -- , jump [ "code" ] comp_string parseFilterCode
+        , jump [ "date" ] (char '=') parseFilterDate
+        , jump [ "date2", "edate" ] (char '=') parseFilterDate2
+        , jump [ "depth" ] comp_number parseFilterDepth
+        , jump [ "description","descr","desc" ] comp_string parseFilterDesc
+        , jump [ "real" ] (char '=') parseFilterReal
+        , jump [ "status" ] (char '=') parseFilterStatus
+        -- , jump [ "sym" ] comp_string parseFilterSym
+        , jump [ "tag" ] comp_string parseFilterTag
+        , R.lookAhead comp_number >> return parseFilterAmount
+        , return parseFilterAccount
+        ] <* parseWhiteSpace <?> "query expression"
+       r
+       where
+               jump l next r = R.choice_try (map (\s -> R.string s >> return r) l) <* R.lookAhead next
+
+expr :: Stream s m Char => ParsecT s Context m Filter
+expr = R.buildExpressionParser operators parseFilterTerm <?> "query"
+
+filter :: Stream s m Char => ParsecT s Context m Filter
+filter =
+       liftM (foldl (\acc x -> case acc of { Any -> x; _ -> And acc x }) Any) $
+               R.many $ (parseWhiteSpace >> R.lookAhead R.anyToken >> parseFilterExpr)
+
+{-
+
+query :: Day -> String -> Either ParseError Filter
+query d s = runParser (parseFilter <* eof) QCtx{qCtxDay=d} "" s
+
+simplifyFilter :: Filter -> Filter
+simplifyFilter q =
+  -- let q' = simplify q
+  -- in if q' == q then q else simplifyFilter q'
+  simplify q -- NOTE: should be stable without recurring
+  where
+    simplify (And None _) = None
+    simplify (And _ None) = None
+    simplify (And Any Any) = Any
+    simplify (And q q'@(Date _)) = simplify (And q' q) -- XXX: useful?
+    simplify (And q q') | q == q' = simplify q
+                        | otherwise = case (simplify q, simplify q') of
+                                       (None, _) -> None
+                                       (_, None) -> None
+                                       (Any, q') -> q'
+                                       (q, Any) -> q
+                                       (q, q') -> And q q'
+    simplify (Or None q') = simplify q'
+    simplify (Or q None) = simplify q
+    simplify (Or Any _) = Any
+    simplify (Or _ Any) = Any
+    -- simplify (Or q q'@(Date _)) = simplify (Or q' q)
+    simplify (Or q q') | q == q' = simplify q
+                       | otherwise = case (simplify q, simplify q') of
+                                      (None, q') -> q'
+                                      (q, None) -> q
+                                      (Any, _) -> q'
+                                      (_, Any) -> q
+                                      (q, q') -> Or q q'
+    simplify (Date (DateSpan Nothing Nothing)) = Any
+    simplify (Date2 (DateSpan Nothing Nothing)) = Any
+    simplify q = q
+-}