1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE NamedFieldPuns #-}
3 module Hcompta.Format.Ledger.Amount.Read where
5 import Control.Applicative ((<$>), (<*))
6 import Control.Applicative ((<|>))
7 import Control.Monad (Monad(..), guard)
10 import qualified Data.Decimal
11 import Data.Eq (Eq(..))
12 import qualified Data.List as List
13 import Data.Maybe (Maybe(..))
14 import Data.Monoid (Monoid(..))
15 import Data.Ord (Ord(..))
16 import Data.String (String, fromString)
17 import Data.Typeable ()
18 import Prelude (($), Int, fromIntegral, Num(..), id)
19 import qualified Text.Parsec as R hiding
25 import Text.Parsec (Stream, ParsecT, (<?>))
27 import qualified Hcompta.Unit as Unit
28 import Hcompta.Format.Ledger (Amount(..), Unit(..))
29 import qualified Hcompta.Format.Ledger.Amount.Style as Style
30 import qualified Hcompta.Lib.Parsec as R
36 => Char -- ^ Integral grouping separator.
37 -> Char -- ^ Fractioning separator.
38 -> Char -- ^ Fractional grouping separator.
40 ( [String] -- integral
41 , [String] -- fractional
42 , Maybe Style.Fractioning -- fractioning
43 , Maybe Style.Grouping -- grouping_integral
44 , Maybe Style.Grouping -- grouping_fractional
46 quantity int_group_sep frac_sep frac_group_sep = do
47 (integral, grouping_integral) <- do
50 [] -> return ([], Nothing)
52 t <- R.many $ R.char int_group_sep >> R.many1 R.digit
54 return (digits, grouping_of_digits int_group_sep digits)
55 (fractional, fractioning, grouping_fractional) <-
58 _ -> R.option ([], Nothing, Nothing)) $ do
59 fractioning <- R.char frac_sep
61 t <- R.many $ R.char frac_group_sep >> R.many1 R.digit
63 return (digits, Just fractioning
64 , grouping_of_digits frac_group_sep $ List.reverse digits)
73 grouping_of_digits :: Char -> [String] -> Maybe Style.Grouping
74 grouping_of_digits group_sep digits =
79 Style.Grouping group_sep $
80 canonicalize_grouping $
81 List.map List.length $ digits
82 canonicalize_grouping :: [Int] -> [Int]
83 canonicalize_grouping groups =
84 List.foldl' -- NOTE: remove duplicates at beginning and reverse.
85 (\acc l0 -> case acc of
86 l1:_ -> if l0 == l1 then acc else l0:acc
88 case groups of -- NOTE: keep only longer at beginning.
89 l0:l1:t -> if l0 > l1 then groups else l1:t
93 unit :: Stream s m Char => ParsecT s u m Unit
95 (quoted <|> unquoted) <?> "unit"
97 unquoted :: Stream s m Char => ParsecT s u m Unit
102 case Data.Char.generalCategory c of
103 Data.Char.CurrencySymbol -> True
104 Data.Char.LowercaseLetter -> True
105 Data.Char.ModifierLetter -> True
106 Data.Char.OtherLetter -> True
107 Data.Char.TitlecaseLetter -> True
108 Data.Char.UppercaseLetter -> True
110 quoted :: Stream s m Char => ParsecT s u m Unit
113 R.between (R.char '"') (R.char '"') $
118 amount :: Stream s m Char => ParsecT s u m (Style.Styled Amount)
122 R.option Nothing $ do
124 s <- R.many $ R.space_horizontal
125 return $ Just $ (u, not $ List.null s)
132 , grouping_fractional
135 [ quantity '_' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
136 , quantity '_' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
137 , quantity ',' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
138 , quantity '.' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
140 let int = List.concat integral
141 let frac = List.concat fractional
142 let precision = List.length frac
143 guard (precision <= 255)
144 let mantissa = R.integer_of_digits 10 $ int `mappend` frac
146 ( Data.Decimal.Decimal
147 (fromIntegral precision)
151 , Style.grouping_integral
152 , Style.grouping_fractional
155 (amount_unit, unit_side, unit_spaced) <-
158 return (u, Just Style.Side_Left, Just s)
160 R.option (Unit.unit_empty, Nothing, Nothing) $ R.try $ do
161 s <- R.many R.space_horizontal
163 return $ (u, Just Style.Side_Right, Just $ not $ List.null s)
171 { amount_quantity = left_signing qty
176 -- | Parse either '-' into 'negate', or '+' or '' into 'id'.
177 sign :: (Stream s m Char, Num i) => ParsecT s u m (i -> i)
179 (R.char '-' >> return negate)
180 <|> (R.char '+' >> return id)