1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE NamedFieldPuns #-}
3 module Hcompta.Amount.Read where
5 import Control.Monad (guard)
6 -- import Control.Applicative ((<$>), (<|>), (<*))
7 import Control.Applicative ((<|>))
8 import qualified Data.Char
9 import qualified Data.Decimal
10 import qualified Data.List
11 import Data.String (fromString)
12 import Data.Typeable ()
13 import qualified Text.Parsec as R hiding
19 import Text.Parsec (Stream, ParsecT, (<?>))
21 import qualified Hcompta.Amount as Amount
22 import Hcompta.Amount (Amount, Unit)
23 import qualified Hcompta.Amount.Style as Style
24 import qualified Hcompta.Amount.Unit as Unit
25 import qualified Hcompta.Lib.Parsec as R
30 { integral :: [String]
31 , fractional :: [String]
32 , fractioning :: Maybe Style.Fractioning
33 , grouping_integral :: Maybe Style.Grouping
34 , grouping_fractional :: Maybe Style.Grouping
39 => Char -- ^ Integral grouping separator.
40 -> Char -- ^ Fractioning separator.
41 -> Char -- ^ Fractional grouping separator.
42 -> ParsecT s u m Quantity
43 quantity int_group_sep frac_sep frac_group_sep = do
44 (integral, grouping_integral) <- do
47 [] -> return ([], Nothing)
49 t <- R.many $ R.char int_group_sep >> R.many1 R.digit
51 return (digits, grouping_of_digits int_group_sep digits)
52 (fractional, fractioning, grouping_fractional) <-
55 _ -> R.option ([], Nothing, Nothing)) $ do
56 fractioning <- R.char frac_sep
58 t <- R.many $ R.char frac_group_sep >> R.many1 R.digit
60 return (digits, Just fractioning
61 , grouping_of_digits frac_group_sep $ reverse digits)
71 grouping_of_digits :: Char -> [String] -> Maybe Style.Grouping
72 grouping_of_digits group_sep digits =
77 Style.Grouping group_sep $
78 canonicalize_grouping $
80 canonicalize_grouping :: [Int] -> [Int]
81 canonicalize_grouping groups =
82 foldl -- NOTE: remove duplicates at beginning and reverse.
83 (\acc l0 -> case acc of
84 l1:_ -> if l0 == l1 then acc else l0:acc
86 case groups of -- NOTE: keep only longer at beginning.
87 l0:l1:t -> if l0 > l1 then groups else l1:t
91 unit :: Stream s m Char => ParsecT s u m Unit
93 (quoted <|> unquoted) <?> "unit"
95 unquoted :: Stream s m Char => ParsecT s u m Unit
100 case Data.Char.generalCategory c of
101 Data.Char.CurrencySymbol -> True
102 Data.Char.LowercaseLetter -> True
103 Data.Char.ModifierLetter -> True
104 Data.Char.OtherLetter -> True
105 Data.Char.TitlecaseLetter -> True
106 Data.Char.UppercaseLetter -> True
108 quoted :: Stream s m Char => ParsecT s u m Unit
111 R.between (R.char '"') (R.char '"') $
116 amount :: Stream s m Char => ParsecT s u m Amount
120 R.option Nothing $ do
122 s <- R.many $ R.space_horizontal
123 return $ Just $ (u, not $ null s)
124 (quantity_, style) <- do
131 , grouping_fractional
134 [ quantity '_' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
135 , quantity '_' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
136 , quantity ',' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
137 , quantity '.' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
139 let int = Data.List.concat integral
140 let frac_flat = Data.List.concat fractional
141 let frac = reverse $ Data.List.dropWhile (== '0') $ reverse $ frac_flat
142 let place = length frac
144 let mantissa = R.integer_of_digits 10 $ int ++ frac
146 ( Data.Decimal.Decimal
151 , Style.grouping_integral
152 , Style.grouping_fractional
153 , Style.precision = fromIntegral $ length frac_flat
156 (unit_, unit_side, unit_spaced) <-
159 return (u, Just Style.Side_Left, Just s)
161 R.option (Unit.nil, Nothing, Nothing) $ R.try $ do
162 s <- R.many $ R.space_horizontal
164 return $ (u, Just Style.Side_Right, Just $ not $ null s)
167 { Amount.quantity = left_signing $ quantity_
168 , Amount.style = style
172 , Amount.unit = unit_
175 -- | Parse either '-' into 'negate', or '+' or '' into 'id'.
176 sign :: (Stream s m Char, Num i) => ParsecT s u m (i -> i)
178 (R.char '-' >> return negate)
179 <|> (R.char '+' >> return id)