1{-# LANGUAGE OverloadedStrings, UnboxedTuples #-}
4-- Module : Data.Text.Read
5-- Copyright : (c) 2010, 2011 Bryan O'Sullivan
8-- Maintainer : bos@serpentine.com
9-- Stability : experimental
12-- Functions used frequently when reading textual data.
23import Control.Monad (liftM)
24import Data.Char (isDigit, isHexDigit, ord)
25import Data.Int (Int8, Int16, Int32, Int64)
26import Data.Ratio ((%))
28import Data.Text.Private (span_)
29import Data.Word (Word, Word8, Word16, Word32, Word64)
31-- | Read some text. If the read succeeds, return its value and the
32-- remaining text, otherwise an error message.
33type Reader a = Text -> Either String (a,Text)
35-- | Read a decimal integer. The input must begin with at least one
36-- decimal digit, and is consumed until a non-digit or end of string
39-- This function does not handle leading sign characters. If you need
40-- to handle signed input, use @'signed' 'decimal'@.
42-- /Note/: For fixed-width integer types, this function does not
43-- attempt to detect overflow, so a sufficiently long input may give
44-- incorrect results. If you are worried about overflow, use
45-- 'Integer' for your result type.
46decimal :: Integral a => Reader a
47{-# SPECIALIZE decimal :: Reader Int #-}
48{-# SPECIALIZE decimal :: Reader Int8 #-}
49{-# SPECIALIZE decimal :: Reader Int16 #-}
50{-# SPECIALIZE decimal :: Reader Int32 #-}
51{-# SPECIALIZE decimal :: Reader Int64 #-}
52{-# SPECIALIZE decimal :: Reader Integer #-}
53{-# SPECIALIZE decimal :: Reader Word #-}
54{-# SPECIALIZE decimal :: Reader Word8 #-}
55{-# SPECIALIZE decimal :: Reader Word16 #-}
56{-# SPECIALIZE decimal :: Reader Word32 #-}
57{-# SPECIALIZE decimal :: Reader Word64 #-}
59 | T.null h = Left "input does not start with a digit"
60 | otherwise = Right (T.foldl' go 0 h, t)
61 where (# h,t #) = span_ isDigit txt
62 go n d = (n * 10 + fromIntegral (digitToInt d))
64-- | Read a hexadecimal integer, consisting of an optional leading
65-- @\"0x\"@ followed by at least one decimal digit. Input is consumed
66-- until a non-hex-digit or end of string is reached. This function
67-- is case insensitive.
69-- This function does not handle leading sign characters. If you need
70-- to handle signed input, use @'signed' 'hexadecimal'@.
72-- /Note/: For fixed-width integer types, this function does not
73-- attempt to detect overflow, so a sufficiently long input may give
74-- incorrect results. If you are worried about overflow, use
75-- 'Integer' for your result type.
76hexadecimal :: Integral a => Reader a
77{-# SPECIALIZE hexadecimal :: Reader Int #-}
78{-# SPECIALIZE hexadecimal :: Reader Int8 #-}
79{-# SPECIALIZE hexadecimal :: Reader Int16 #-}
80{-# SPECIALIZE hexadecimal :: Reader Int32 #-}
81{-# SPECIALIZE hexadecimal :: Reader Int64 #-}
82{-# SPECIALIZE hexadecimal :: Reader Integer #-}
83{-# SPECIALIZE hexadecimal :: Reader Word #-}
84{-# SPECIALIZE hexadecimal :: Reader Word8 #-}
85{-# SPECIALIZE hexadecimal :: Reader Word16 #-}
86{-# SPECIALIZE hexadecimal :: Reader Word32 #-}
87{-# SPECIALIZE hexadecimal :: Reader Word64 #-}
89 | h == "0x" || h == "0X" = hex t
91 where (h,t) = T.splitAt 2 txt
93hex :: Integral a => Reader a
94{-# SPECIALIZE hex :: Reader Int #-}
95{-# SPECIALIZE hex :: Reader Int8 #-}
96{-# SPECIALIZE hex :: Reader Int16 #-}
97{-# SPECIALIZE hex :: Reader Int32 #-}
98{-# SPECIALIZE hex :: Reader Int64 #-}
99{-# SPECIALIZE hex :: Reader Integer #-}
100{-# SPECIALIZE hex :: Reader Word #-}
101{-# SPECIALIZE hex :: Reader Word8 #-}
102{-# SPECIALIZE hex :: Reader Word16 #-}
103{-# SPECIALIZE hex :: Reader Word32 #-}
104{-# SPECIALIZE hex :: Reader Word64 #-}
106 | T.null h = Left "input does not start with a hexadecimal digit"
107 | otherwise = Right (T.foldl' go 0 h, t)
108 where (# h,t #) = span_ isHexDigit txt
109 go n d = (n * 16 + fromIntegral (hexDigitToInt d))
111hexDigitToInt :: Char -> Int
113 | c >= '0' && c <= '9' = ord c - ord '0'
114 | c >= 'a' && c <= 'f' = ord c - (ord 'a' - 10)
115 | otherwise = ord c - (ord 'A' - 10)
117digitToInt :: Char -> Int
118digitToInt c = ord c - ord '0'
120-- | Read an optional leading sign character (@\'-\'@ or @\'+\'@) and
121-- apply it to the result of applying the given reader.
122signed :: Num a => Reader a -> Reader a
124signed f = runP (signa (P f))
126-- | Read a rational number.
128-- This function accepts an optional leading sign character, followed
129-- by at least one decimal digit. The syntax similar to that accepted
130-- by the 'read' function, with the exception that a trailing @\'.\'@
131-- or @\'e\'@ /not/ followed by a number is not consumed.
133-- Examples (with behaviour identical to 'read'):
135-- >rational "3" == Right (3.0, "")
136-- >rational "3.1" == Right (3.1, "")
137-- >rational "3e4" == Right (30000.0, "")
138-- >rational "3.1e4" == Right (31000.0, "")
139-- >rational ".3" == Left "input does not start with a digit"
140-- >rational "e3" == Left "input does not start with a digit"
142-- Examples of differences from 'read':
144-- >rational "3.foo" == Right (3.0, ".foo")
145-- >rational "3e" == Right (3.0, "e")
146rational :: Fractional a => Reader a
147{-# SPECIALIZE rational :: Reader Double #-}
148rational = floaty $ \real frac fracDenom -> fromRational $
149 real % 1 + frac % fracDenom
151-- | Read a rational number.
153-- The syntax accepted by this function is the same as for 'rational'.
155-- /Note/: This function is almost ten times faster than 'rational',
156-- but is slightly less accurate.
158-- The 'Double' type supports about 16 decimal places of accuracy.
159-- For 94.2% of numbers, this function and 'rational' give identical
160-- results, but for the remaining 5.8%, this function loses precision
161-- around the 15th decimal place. For 0.001% of numbers, this
162-- function will lose precision at the 13th or 14th decimal place.
163double :: Reader Double
164double = floaty $ \real frac fracDenom ->
166 fromIntegral frac / fromIntegral fracDenom
168signa :: Num a => Parser a -> Parser a
169{-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
170{-# SPECIALIZE signa :: Parser Int8 -> Parser Int8 #-}
171{-# SPECIALIZE signa :: Parser Int16 -> Parser Int16 #-}
172{-# SPECIALIZE signa :: Parser Int32 -> Parser Int32 #-}
173{-# SPECIALIZE signa :: Parser Int64 -> Parser Int64 #-}
174{-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
176 sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
177 if sign == '+' then p else negate `liftM` p
179newtype Parser a = P {
183instance Monad Parser where
184 return a = P $ \t -> Right (a,t)
185 {-# INLINE return #-}
186 m >>= k = P $ \t -> case runP m t of
188 Right (a,t') -> runP (k a) t'
190 fail msg = P $ \_ -> Left msg
192perhaps :: a -> Parser a -> Parser a
193perhaps def m = P $ \t -> case runP m t of
194 Left _ -> Right (def,t)
197char :: (Char -> Bool) -> Parser Char
198char p = P $ \t -> case T.uncons t of
199 Just (c,t') | p c -> Right (c,t')
200 _ -> Left "character does not match"
202data T = T !Integer !Int
204floaty :: Fractional a => (Integer -> Integer -> Integer -> a) -> Reader a
207 sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
209 T fraction fracDigits <- perhaps (T 0 0) $ do
211 digits <- P $ \t -> Right (T.length $ T.takeWhile isDigit t, t)
214 let e c = c == 'e' || c == 'E'
215 power <- perhaps 0 (char e >> signa (P decimal) :: Parser Int)
216 let n = if fracDigits == 0
218 then fromIntegral real
219 else fromIntegral real * (10 ^^ power)
221 then f real fraction (10 ^ fracDigits)
222 else f real fraction (10 ^ fracDigits) * (10 ^^ power)
223 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