1{-# LANGUAGE OverloadedStrings #-}
4-- Module : Data.Text.Read
5-- Copyright : (c) 2010 Bryan O'Sullivan
8-- Maintainer : bos@serpentine.com, rtomharper@googlemail.com,
10-- Stability : experimental
13-- Functions used frequently when reading textual data.
24import Control.Monad (liftM)
25import Data.Char (isDigit, isHexDigit, ord)
26import Data.Ratio ((%))
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
37-- This function does not handle leading sign characters. If you need
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
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 #-}
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.
58-- This function does not handle leading sign characters. If you need
59-- to handle signed input, use @'signed' 'hexadecimal'@.
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 #-}
69 | h == "0x" || h == "0X" = hex t
71 where (h,t) = T.splitAt 2 txt
73hex :: Integral a => Reader a
74{-# SPECIALIZE hex :: Reader Int #-}
75{-# SPECIALIZE hex :: Reader Integer #-}
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
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
95signed f = runP (signa (P f))
97-- | Read a rational number.
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.
104-- Examples (with behaviour identical to 'read'):
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"
113-- Examples of differences from 'read':
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.
124-- The syntax accepted by this function is the same as for 'rational'.
126-- /Note/: This function is almost ten times faster than 'rational',
127-- but is slightly less accurate.
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 ->
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 #-}
143 sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
144 if sign == '+' then p else negate `liftM` p
146newtype Parser a = P {
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
155 Right (a,t') -> runP (k a) t'
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)
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
174 sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
176 T fraction fracDigits <- perhaps (T 0 0) $ do
178 digits <- P $ \t -> Right (T.length $ T.takeWhile isDigit t, t)
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
185 then fromIntegral real
186 else fromIntegral real * (10 ^^ power)
188 then f real fraction (10 ^ fracDigits)
189 else f real fraction (10 ^ fracDigits) * (10 ^^ power)
190 return $! if sign == '+'
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list