@@ -26,36 +26,57 @@ import Data.Char (isDigit, isHexDigit, ord)
2626import Data.Ratio ((%))
2727import Data.Text as T
2828
29−-- | Read some text, and if the read succeeds, return its value and
30−-- the remaining text.
29+-- | Read some text. If the read succeeds, return its value and the
30+-- remaining text, otherwise an error message.
3131type Reader a = Text -> Either String (a,Text)
3232
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+-- is reached.
3436--
3537-- This function does not handle leading sign characters. If you need
3638-- 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.
3743decimal :: Integral a => Reader a
3844{-# SPECIALIZE decimal :: Reader Int #-}
3945{-# SPECIALIZE decimal :: Reader Integer #-}
4046decimal txt
41− | T.null h = Left "no digits in input"
47+ | T.null h = Left "input does not start with a digit"
4248 | otherwise = Right (T.foldl' go 0 h, t)
4349 where (h,t) = T.spanBy isDigit txt
4450 go n d = (n * 10 + fromIntegral (digitToInt d))
4551
46−-- | Read a hexadecimal number, with optional leading @\"0x\"@. This
47−-- function is case insensitive.
52+-- | Read a hexadecimal number, 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
55+-- is case insensitive.
4856--
4957-- This function does not handle leading sign characters. If you need
5058-- to handle signed input, use @'signed' 'hexadecimal'@.
59+--
60+-- /Note/: For fixed-width integer types, this function does not
61+-- attempt to detect overflow, so a sufficiently long input may give
62+-- incorrect results.
5163hexadecimal :: Integral a => Reader a
52−{-# SPECIALIZE hex :: Reader Int #-}
53−{-# SPECIALIZE hex :: Reader Integer #-}
64+{-# SPECIALIZE hexadecimal :: Reader Int #-}
65+{-# SPECIALIZE hexadecimal :: Reader Integer #-}
5466hexadecimal txt
55− | T.toLower h == "0x" = hex t
56− | otherwise = hex txt
67+ | h == "0x" || h == "0X" = hex t
68+ | otherwise = hex txt
5769 where (h,t) = T.splitAt 2 txt
5870
71+hex :: Integral a => Reader a
72+{-# SPECIALIZE hex :: Reader Int #-}
73+{-# SPECIALIZE hex :: Reader Integer #-}
74+hex txt
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))
79+
5980hexDigitToInt :: Char -> Int
6081hexDigitToInt c
6182 | c >= '0' && c <= '9' = ord c - ord '0'
@@ -73,7 +94,24 @@ signed f = runP (signa (P f))
7394
7495-- | Read a rational number.
7596--
76−-- 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.
101+--
102+-- Examples:
103+--
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"
110+--
111+-- Examples of differences from 'read':
112+--
113+-- >rational "3.foo" == Right (3.0, ".foo")
114+-- >rational "3e" == Right (3.0, "e")
77115rational :: RealFloat a => Reader a
78116{-# SPECIALIZE rational :: Reader Double #-}
79117rational = floaty $ \real frac fracDenom -> fromRational $
@@ -81,7 +119,7 @@ rational = floaty $ \real frac fracDenom -> fromRational $
81119
82120-- | Read a rational number.
83121--
84−-- This function accepts an optional leading sign character.
122+-- The syntax accepted by this function is the same as for 'rational'.
85123--
86124-- /Note/: This function is almost ten times faster than 'rational',
87125-- but is slightly less accurate.
@@ -96,22 +134,6 @@ double = floaty $ \real frac fracDenom ->
96134 fromIntegral real +
97135 fromIntegral frac / fromIntegral fracDenom
98136
99−hex :: Integral a => Reader a
100−{-# SPECIALIZE hex :: Reader Int #-}
101−{-# SPECIALIZE hex :: Reader Integer #-}
102−hex txt
103− | T.null h = Left "no digits in input"
104− | otherwise = Right (T.foldl' go 0 h, t)
105− where (h,t) = T.spanBy isHexDigit txt
106− go n d = (n * 16 + fromIntegral (hexDigitToInt d))
107−
108−hexDigitToInt :: Char -> Int
109−hexDigitToInt c
110− | c >= '0' && c <= '9' = ord c - ord '0'
111− | c >= 'a' && c <= 'f' = ord c - (ord 'a' - 10)
112− | c >= 'A' && c <= 'F' = ord c - (ord 'A' - 10)
113− | otherwise = error "Data.Text.Lex.hexDigitToInt: bad input"
114−
115137signa :: Num a => Parser a -> Parser a
116138{-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
117139{-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
@@ -120,7 +142,7 @@ signa p = do
120142 if sign == '+' then p else negate `liftM` p
121143
122144newtype Parser a = P {
123− runP :: Text -> Either String (a,Text)
145+ runP :: Reader a
124146 }
125147
126148instance Monad Parser where
@@ -140,7 +162,7 @@ perhaps def m = P $ \t -> case runP m t of
140162char :: (Char -> Bool) -> Parser Char
141163char p = P $ \t -> case T.uncons t of
142164 Just (c,t') | p c -> Right (c,t')
143− _ -> Left "char"
165+ _ -> Left "character does not match"
144166
145167data T = T !Integer !Int
146168