1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE NamedFieldPuns #-}
3 module Hcompta.Amount.Read where
5 import Control.Monad (guard)
6 import Control.Applicative ((<$>), (<|>), (<*))
7 import qualified Data.Char
8 import qualified Data.Decimal
9 import qualified Data.List
10 import Data.String (fromString)
11 import Data.Typeable ()
12 import qualified Text.Parsec as R hiding
18 import Text.Parsec (Stream, ParsecT, (<?>))
20 import qualified Hcompta.Amount as Amount
21 import Hcompta.Amount (Amount, Unit)
22 import qualified Hcompta.Amount.Style as Style
23 import qualified Hcompta.Amount.Unit as Unit
24 import qualified Hcompta.Lib.Parsec as R
29 { integral :: [String]
30 , fractional :: [String]
31 , fractioning :: Maybe Style.Fractioning
32 , grouping_integral :: Maybe Style.Grouping
33 , grouping_fractional :: Maybe Style.Grouping
38 => Char -- ^ Integral grouping separator.
39 -> Char -- ^ Fractioning separator.
40 -> Char -- ^ Fractional grouping separator.
41 -> ParsecT s u m Quantity
42 quantity int_group_sep frac_sep frac_group_sep = do
43 (integral, grouping_integral) <- do
46 [] -> return ([], Nothing)
48 t <- R.many $ R.char int_group_sep >> R.many1 R.digit
50 return (digits, grouping_of_digits int_group_sep digits)
51 (fractional, fractioning, grouping_fractional) <-
54 _ -> R.option ([], Nothing, Nothing)) $ do
55 fractioning <- R.char frac_sep
57 t <- R.many $ R.char frac_group_sep >> R.many1 R.digit
59 return (digits, Just fractioning
60 , grouping_of_digits frac_group_sep $ reverse digits)
70 grouping_of_digits :: Char -> [String] -> Maybe Style.Grouping
71 grouping_of_digits group_sep digits =
76 Style.Grouping group_sep $
77 canonicalize_grouping $
79 canonicalize_grouping :: [Int] -> [Int]
80 canonicalize_grouping groups =
81 foldl -- NOTE: remove duplicates at beginning and reverse.
82 (\acc l0 -> case acc of
83 l1:_ -> if l0 == l1 then acc else l0:acc
85 case groups of -- NOTE: keep only longer at beginning.
86 l0:l1:t -> if l0 > l1 then groups else l1:t
90 unit :: Stream s m Char => ParsecT s u m Unit
92 (quoted <|> unquoted) <?> "unit"
94 unquoted :: Stream s m Char => ParsecT s u m Unit
99 case Data.Char.generalCategory c of
100 Data.Char.CurrencySymbol -> True
101 Data.Char.LowercaseLetter -> True
102 Data.Char.ModifierLetter -> True
103 Data.Char.OtherLetter -> True
104 Data.Char.TitlecaseLetter -> True
105 Data.Char.UppercaseLetter -> True
107 quoted :: Stream s m Char => ParsecT s u m Unit
110 R.between (R.char '"') (R.char '"') $
115 amount :: Stream s m Char => ParsecT s u m Amount
119 R.option Nothing $ do
121 s <- R.many $ R.space_horizontal
122 return $ Just $ (u, not $ null s)
123 (quantity_, style) <- do
130 , grouping_fractional
133 [ quantity '_' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
134 , quantity '_' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
135 , quantity ',' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
136 , quantity '.' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
138 let int = Data.List.concat integral
139 let frac_flat = Data.List.concat fractional
140 let frac = reverse $ Data.List.dropWhile (== '0') $ reverse $ frac_flat
141 let place = length frac
143 let mantissa = R.integer_of_digits 10 $ int ++ frac
145 ( Data.Decimal.Decimal
150 , Style.grouping_integral
151 , Style.grouping_fractional
152 , Style.precision = fromIntegral $ length frac_flat
155 (unit_, unit_side, unit_spaced) <-
158 return (u, Just Style.Side_Left, Just s)
160 R.option (Unit.nil, Nothing, Nothing) $ R.try $ do
161 s <- R.many $ R.space_horizontal
163 return $ (u, Just Style.Side_Right, Just $ not $ null s)
166 { Amount.quantity = left_signing $ quantity_
167 , Amount.style = style
171 , Amount.unit = unit_
174 -- | Parse either '-' into 'negate', or '+' or '' into 'id'.
175 sign :: (Stream s m Char, Num i) => ParsecT s u m (i -> i)
177 (R.char '-' >> return negate)
178 <|> (R.char '+' >> return id)