1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE NamedFieldPuns #-}
3 module Hcompta.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(..))
13 import Data.Maybe (Maybe(..))
14 import Data.Ord (Ord(..))
15 import Data.String (String, fromString)
16 import Data.Typeable ()
17 import Prelude (($), Int, fromIntegral, Num(..), id)
18 import qualified Text.Parsec as R hiding
24 import Text.Parsec (Stream, ParsecT, (<?>))
26 import qualified Hcompta.Amount as Amount
27 import Hcompta.Amount (Amount, Unit)
28 import qualified Hcompta.Amount.Style as Style
29 import qualified Hcompta.Amount.Unit as Unit
30 import qualified Hcompta.Lib.Parsec as R
35 { integral :: [String]
36 , fractional :: [String]
37 , fractioning :: Maybe Style.Fractioning
38 , grouping_integral :: Maybe Style.Grouping
39 , grouping_fractional :: Maybe Style.Grouping
44 => Char -- ^ Integral grouping separator.
45 -> Char -- ^ Fractioning separator.
46 -> Char -- ^ Fractional grouping separator.
47 -> ParsecT s u m Quantity
48 quantity int_group_sep frac_sep frac_group_sep = do
49 (integral, grouping_integral) <- do
52 [] -> return ([], Nothing)
54 t <- R.many $ R.char int_group_sep >> R.many1 R.digit
56 return (digits, grouping_of_digits int_group_sep digits)
57 (fractional, fractioning, grouping_fractional) <-
60 _ -> R.option ([], Nothing, Nothing)) $ do
61 fractioning <- R.char frac_sep
63 t <- R.many $ R.char frac_group_sep >> R.many1 R.digit
65 return (digits, Just fractioning
66 , grouping_of_digits frac_group_sep $ reverse digits)
76 grouping_of_digits :: Char -> [String] -> Maybe Style.Grouping
77 grouping_of_digits group_sep digits =
82 Style.Grouping group_sep $
83 canonicalize_grouping $
85 canonicalize_grouping :: [Int] -> [Int]
86 canonicalize_grouping groups =
87 foldl' -- NOTE: remove duplicates at beginning and reverse.
88 (\acc l0 -> case acc of
89 l1:_ -> if l0 == l1 then acc else l0:acc
91 case groups of -- NOTE: keep only longer at beginning.
92 l0:l1:t -> if l0 > l1 then groups else l1:t
96 unit :: Stream s m Char => ParsecT s u m Unit
98 (quoted <|> unquoted) <?> "unit"
100 unquoted :: Stream s m Char => ParsecT s u m Unit
105 case Data.Char.generalCategory c of
106 Data.Char.CurrencySymbol -> True
107 Data.Char.LowercaseLetter -> True
108 Data.Char.ModifierLetter -> True
109 Data.Char.OtherLetter -> True
110 Data.Char.TitlecaseLetter -> True
111 Data.Char.UppercaseLetter -> True
113 quoted :: Stream s m Char => ParsecT s u m Unit
116 R.between (R.char '"') (R.char '"') $
121 amount :: Stream s m Char => ParsecT s u m Amount
125 R.option Nothing $ do
127 s <- R.many $ R.space_horizontal
128 return $ Just $ (u, not $ null s)
129 (quantity_, style) <- do
136 , grouping_fractional
139 [ quantity '_' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
140 , quantity '_' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
141 , quantity ',' '.' '_' <* (R.notFollowedBy $ R.oneOf ",._")
142 , quantity '.' ',' '_' <* (R.notFollowedBy $ R.oneOf ",._")
144 let int = Data.List.concat integral
145 let frac_flat = Data.List.concat fractional
146 let frac = reverse $ Data.List.dropWhile (== '0') $ reverse $ frac_flat
147 let place = length frac
149 let mantissa = R.integer_of_digits 10 $ int ++ frac
151 ( Data.Decimal.Decimal
156 , Style.grouping_integral
157 , Style.grouping_fractional
158 , Style.precision = fromIntegral $ length frac_flat
161 (unit_, unit_side, unit_spaced) <-
164 return (u, Just Style.Side_Left, Just s)
166 R.option (Unit.nil, Nothing, Nothing) $ R.try $ do
167 s <- R.many $ R.space_horizontal
169 return $ (u, Just Style.Side_Right, Just $ not $ null s)
172 { Amount.quantity = left_signing $ quantity_
173 , Amount.style = style
177 , Amount.unit = unit_
180 -- | Parse either '-' into 'negate', or '+' or '' into 'id'.
181 sign :: (Stream s m Char, Num i) => ParsecT s u m (i -> i)
183 (R.char '-' >> return negate)
184 <|> (R.char '+' >> return id)