on builder help sign in
text/Data/Text/Read.hs
192 lines 6.8 kB history raw
1{-# LANGUAGE OverloadedStrings #-}
3-- |
4-- Module : Data.Text.Read
5-- Copyright : (c) 2010 Bryan O'Sullivan
6--
7-- License : BSD-style
8-- Maintainer : bos@serpentine.com, rtomharper@googlemail.com,
9-- duncan@haskell.org
10-- Stability : experimental
11-- Portability : GHC
12--
13-- Functions used frequently when reading textual data.
14module Data.Text.Read
15 (
16 Reader
17 , decimal
18 , hexadecimal
19 , signed
20 , rational
21 , double
22 ) where
24import Control.Monad (liftM)
25import Data.Char (isDigit, isHexDigit, ord)
26import Data.Ratio ((%))
27import Data.Text as T
29-- | Read some text. If the read succeeds, return its value and the
30-- remaining text, otherwise an error message.
31type Reader a = Text -> Either String (a,Text)
33-- | Read a decimal integer. The input must begin with at least one
34-- decimal digit, and is consumed until a non-digit or end of string
35-- is reached.
36--
37-- This function does not handle leading sign characters. If you need
38-- to handle signed input, use @'signed' 'decimal'@.
39--
40-- /Note/: For fixed-width integer types, this function does not
41-- attempt to detect overflow, so a sufficiently long input may give
42-- incorrect results. If you are worried about overflow, use
43-- 'Integer' for your result type.
44decimal :: Integral a => Reader a
45{-# SPECIALIZE decimal :: Reader Int #-}
46{-# SPECIALIZE decimal :: Reader Integer #-}
47decimal txt
48 | T.null h = Left "input does not start with a digit"
49 | otherwise = Right (T.foldl' go 0 h, t)
50 where (h,t) = T.span isDigit txt
51 go n d = (n * 10 + fromIntegral (digitToInt d))
53-- | Read a hexadecimal integer, consisting of an optional leading
54-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
55-- until a non-hex-digit or end of string is reached. This function
56-- is case insensitive.
57--
58-- This function does not handle leading sign characters. If you need
59-- to handle signed input, use @'signed' 'hexadecimal'@.
60--
61-- /Note/: For fixed-width integer types, this function does not
62-- attempt to detect overflow, so a sufficiently long input may give
63-- incorrect results. If you are worried about overflow, use
64-- 'Integer' for your result type.
65hexadecimal :: Integral a => Reader a
66{-# SPECIALIZE hexadecimal :: Reader Int #-}
67{-# SPECIALIZE hexadecimal :: Reader Integer #-}
68hexadecimal txt
69 | h == "0x" || h == "0X" = hex t
70 | otherwise = hex txt
71 where (h,t) = T.splitAt 2 txt
73hex :: Integral a => Reader a
74{-# SPECIALIZE hex :: Reader Int #-}
75{-# SPECIALIZE hex :: Reader Integer #-}
76hex txt
77 | T.null h = Left "input does not start with a hexadecimal digit"
78 | otherwise = Right (T.foldl' go 0 h, t)
79 where (h,t) = T.span isHexDigit txt
80 go n d = (n * 16 + fromIntegral (hexDigitToInt d))
82hexDigitToInt :: Char -> Int
83hexDigitToInt c
84 | c >= '0' && c <= '9' = ord c - ord '0'
85 | c >= 'a' && c <= 'f' = ord c - (ord 'a' - 10)
86 | otherwise = ord c - (ord 'A' - 10)
88digitToInt :: Char -> Int
89digitToInt c = ord c - ord '0'
91-- | Read an optional leading sign character (@\'-\'@ or @\'+\'@) and
92-- apply it to the result of applying the given reader.
93signed :: Num a => Reader a -> Reader a
94{-# INLINE signed #-}
95signed f = runP (signa (P f))
97-- | Read a rational number.
98--
99-- This function accepts an optional leading sign character, followed
100-- by at least one decimal digit. The syntax similar to that accepted
101-- by the 'read' function, with the exception that a trailing @\'.\'@
102-- or @\'e\'@ /not/ followed by a number is not consumed.
103--
104-- Examples (with behaviour identical to 'read'):
105--
106-- >rational "3" == Right (3.0, "")
107-- >rational "3.1" == Right (3.1, "")
108-- >rational "3e4" == Right (30000.0, "")
109-- >rational "3.1e4" == Right (31000.0, "")
110-- >rational ".3" == Left "input does not start with a digit"
111-- >rational "e3" == Left "input does not start with a digit"
112--
113-- Examples of differences from 'read':
114--
115-- >rational "3.foo" == Right (3.0, ".foo")
116-- >rational "3e" == Right (3.0, "e")
117rational :: RealFloat a => Reader a
118{-# SPECIALIZE rational :: Reader Double #-}
119rational = floaty $ \real frac fracDenom -> fromRational $
120 real % 1 + frac % fracDenom
122-- | Read a rational number.
123--
124-- The syntax accepted by this function is the same as for 'rational'.
125--
126-- /Note/: This function is almost ten times faster than 'rational',
127-- but is slightly less accurate.
128--
129-- The 'Double' type supports about 16 decimal places of accuracy.
130-- For 94.2% of numbers, this function and 'rational' give identical
131-- results, but for the remaining 5.8%, this function loses precision
132-- around the 15th decimal place. For 0.001% of numbers, this
133-- function will lose precision at the 13th or 14th decimal place.
134double :: Reader Double
135double = floaty $ \real frac fracDenom ->
136 fromIntegral real +
137 fromIntegral frac / fromIntegral fracDenom
139signa :: Num a => Parser a -> Parser a
140{-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
141{-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
142signa p = do
143 sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
144 if sign == '+' then p else negate `liftM` p
146newtype Parser a = P {
147 runP :: Reader a
148 }
150instance Monad Parser where
151 return a = P $ \t -> Right (a,t)
152 {-# INLINE return #-}
153 m >>= k = P $ \t -> case runP m t of
154 Left err -> Left err
155 Right (a,t') -> runP (k a) t'
156 {-# INLINE (>>=) #-}
157 fail msg = P $ \_ -> Left msg
159perhaps :: a -> Parser a -> Parser a
160perhaps def m = P $ \t -> case runP m t of
161 Left _ -> Right (def,t)
162 r@(Right _) -> r
164char :: (Char -> Bool) -> Parser Char
165char p = P $ \t -> case T.uncons t of
166 Just (c,t') | p c -> Right (c,t')
167 _ -> Left "character does not match"
169data T = T !Integer !Int
171floaty :: RealFloat a => (Integer -> Integer -> Integer -> a) -> Reader a
172{-# INLINE floaty #-}
173floaty f = runP $ do
174 sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
175 real <- P decimal
176 T fraction fracDigits <- perhaps (T 0 0) $ do
177 _ <- char (=='.')
178 digits <- P $ \t -> Right (T.length $ T.takeWhile isDigit t, t)
179 n <- P decimal
180 return $ T n digits
181 let e c = c == 'e' || c == 'E'
182 power <- perhaps 0 (char e >> signa (P decimal) :: Parser Int)
183 let n = if fracDigits == 0
184 then if power == 0
185 then fromIntegral real
186 else fromIntegral real * (10 ^^ power)
187 else if power == 0
188 then f real fraction (10 ^ fracDigits)
189 else f real fraction (10 ^ fracDigits) * (10 ^^ power)
190 return $! if sign == '+'
191 then n
192 else -n