@@ -22,18 +22,24 @@ module Data.Text.Lazy.Read
24import Control.Monad (liftM)
24import Control.Monad (liftM)
25−import Data.Char (digitToInt, isDigit, isHexDigit, ord)
25+import Data.Char (isDigit, isHexDigit, ord)
26−import Data.Ratio
26+import Data.Ratio ((%))
27import Data.Text.Lazy as T
27import Data.Text.Lazy as T
29−-- | Read some text, and if the read succeeds, return its value and
29+-- | Read some text. If the read succeeds, return its value and the
30−-- the remaining text.
30+-- remaining text, otherwise an error message.
31type Reader a = Text -> Either String (a,Text)
31type Reader a = Text -> Either String (a,Text)
33−-- | Read a decimal integer.
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-- This function does not handle leading sign characters. If you need
37-- This function does not handle leading sign characters. If you need
36-- to handle signed input, use @'signed' 'decimal'@.
38-- to handle signed input, use @'signed' 'decimal'@.
40+-- /Note/: For fixed-width integer types, this function does not
41+-- attempt to detect overflow, so a sufficiently long input may give
37decimal :: Integral a => Reader a
43decimal :: Integral a => Reader a
38{-# SPECIALIZE decimal :: Reader Int #-}
44{-# SPECIALIZE decimal :: Reader Int #-}
39{-# SPECIALIZE decimal :: Reader Integer #-}
45{-# SPECIALIZE decimal :: Reader Integer #-}
@@ -43,28 +49,69 @@ decimal txt
43 where (h,t) = T.spanBy isDigit txt
49 where (h,t) = T.spanBy isDigit txt
44 go n d = (n * 10 + fromIntegral (digitToInt d))
50 go n d = (n * 10 + fromIntegral (digitToInt d))
46−-- | Read a hexadecimal number, with optional leading @\"0x\"@. This
52+-- | Read a hexadecimal integer, consisting of an optional leading
53+-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
54+-- until a non-hex-digit or end of string is reached. This function
47−-- function is case insensitive.
55+-- is case insensitive.
49-- This function does not handle leading sign characters. If you need
57-- This function does not handle leading sign characters. If you need
50-- to handle signed input, use @'signed' 'hexadecimal'@.
58-- to handle signed input, use @'signed' 'hexadecimal'@.
60+-- /Note/: For fixed-width integer types, this function does not
61+-- attempt to detect overflow, so a sufficiently long input may give
51hexadecimal :: Integral a => Reader a
63hexadecimal :: Integral a => Reader a
52−{-# SPECIALIZE hex :: Reader Int #-}
64+{-# SPECIALIZE hexadecimal :: Reader Int #-}
53−{-# SPECIALIZE hex :: Reader Integer #-}
65+{-# SPECIALIZE hexadecimal :: Reader Integer #-}
54hexadecimal txt
66hexadecimal txt
55− | T.toLower h == "0x" = hex t
67+ | h == "0x" || h == "0X" = hex t
56− | otherwise = hex txt
68+ | otherwise = hex txt
57 where (h,t) = T.splitAt 2 txt
69 where (h,t) = T.splitAt 2 txt
71+hex :: Integral a => Reader a
72+{-# SPECIALIZE hex :: Reader Int #-}
73+{-# SPECIALIZE hex :: Reader Integer #-}
75+ | T.null h = Left "input does not start with a hexadecimal digit"
76+ | otherwise = Right (T.foldl' go 0 h, t)
77+ where (h,t) = T.spanBy isHexDigit txt
78+ go n d = (n * 16 + fromIntegral (hexDigitToInt d))
80+hexDigitToInt :: Char -> Int
82+ | c >= '0' && c <= '9' = ord c - ord '0'
83+ | c >= 'a' && c <= 'f' = ord c - (ord 'a' - 10)
84+ | otherwise = ord c - (ord 'A' - 10)
86+digitToInt :: Char -> Int
87+digitToInt c = ord c - ord '0'
59−-- | Read a leading sign character (@\'-\'@ or @\'+\'@) and apply it
89+-- | Read an optional leading sign character (@\'-\'@ or @\'+\'@) and
60−-- to the result of applying the given reader.
90+-- apply it to the result of applying the given reader.
61signed :: Num a => Reader a -> Reader a
91signed :: Num a => Reader a -> Reader a
62{-# INLINE signed #-}
92{-# INLINE signed #-}
63signed f = runP (signa (P f))
93signed f = runP (signa (P f))
65-- | Read a rational number.
95-- | Read a rational number.
67−-- This function accepts an optional leading sign character.
97+-- This function accepts an optional leading sign character, followed
98+-- by at least one decimal digit. The syntax similar to that accepted
99+-- by the 'read' function, with the exception that a trailing @\'.\'@
100+-- or @\'e\'@ /not/ followed by a number is not consumed.
104+-- >rational "3" == Right (3.0, "")
105+-- >rational "3.1" == Right (3.1, "")
106+-- >rational "3e4" == Right (30000.0, "")
107+-- >rational "3.1e4" == Right (31000.0, "")
108+-- >rational ".3" == Left "input does not start with a digit"
109+-- >rational "e3" == Left "input does not start with a digit"
111+-- Examples of differences from 'read':
113+-- >rational "3.foo" == Right (3.0, ".foo")
114+-- >rational "3e" == Right (3.0, "e")
68rational :: RealFloat a => Reader a
115rational :: RealFloat a => Reader a
69{-# SPECIALIZE rational :: Reader Double #-}
116{-# SPECIALIZE rational :: Reader Double #-}
70rational = floaty $ \real frac fracDenom -> fromRational $
117rational = floaty $ \real frac fracDenom -> fromRational $
@@ -72,7 +119,7 @@ rational = floaty $ \real frac fracDenom -> fromRational $
73-- | Read a rational number.
120-- | Read a rational number.
75−-- This function accepts an optional leading sign character.
122+-- The syntax accepted by this function is the same as for 'rational'.
77-- /Note/: This function is almost ten times faster than 'rational',
124-- /Note/: This function is almost ten times faster than 'rational',
78-- but is slightly less accurate.
125-- but is slightly less accurate.
@@ -87,22 +134,6 @@ double = floaty $ \real frac fracDenom ->
87 fromIntegral real +
134 fromIntegral real +
88 fromIntegral frac / fromIntegral fracDenom
135 fromIntegral frac / fromIntegral fracDenom
90−hex :: Integral a => Reader a
91−{-# SPECIALIZE hex :: Reader Int #-}
92−{-# SPECIALIZE hex :: Reader Integer #-}
94− | T.null h = Left "input does not start with a hexadecimal digit"
95− | otherwise = Right (T.foldl' go 0 h, t)
96− where (h,t) = T.spanBy isHexDigit txt
97− go n d = (n * 16 + fromIntegral (hexDigitToInt d))
99−hexDigitToInt :: Char -> Int
101− | c >= '0' && c <= '9' = ord c - ord '0'
102− | c >= 'a' && c <= 'f' = ord c - (ord 'a' - 10)
103− | c >= 'A' && c <= 'F' = ord c - (ord 'A' - 10)
104− | otherwise = error "Data.Text.Lex.hexDigitToInt: bad input"
106signa :: Num a => Parser a -> Parser a
137signa :: Num a => Parser a -> Parser a
107{-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
138{-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
108{-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
139{-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
@@ -111,7 +142,7 @@ signa p = do
111 if sign == '+' then p else negate `liftM` p
142 if sign == '+' then p else negate `liftM` p
113newtype Parser a = P {
144newtype Parser a = P {
114− runP :: Text -> Either String (a,Text)
145+ runP :: Reader a
117instance Monad Parser where
148instance Monad Parser where