git.iko.soy/typeable/attoparsec
on master help sign in
Add precanned parsers for double and rational numbers
commita2f68454cb54d6df1f16596a268be2550dc8e48e authorBryan O'Sullivan <bos@serpentine.com> dateTue 21 Dec 2010, 17:24 (16 years ago) parent25681ae
2 files changed+86−8
Data/Attoparsec/Char8.hs+85−7 attoparsec.cabal+1−1
Data/Attoparsec/Char8.hs+85−7
@@ -77,7 +77,8 @@ module Data.Attoparsec.Char8
7777 , decimal
7878 , hexadecimal
7979 , signed
80− --, double
80+ , double
81+ , rational
8182
8283 -- * State observation and manipulation functions
8384 , I.endOfInput
@@ -89,6 +90,7 @@ import Data.Attoparsec.Combinator
8990import Data.Attoparsec.FastSet (charClass, memberChar)
9091import Data.Attoparsec.Internal (Parser, (<?>))
9192import Data.ByteString.Internal (c2w, w2c)
93+import Data.Ratio ((%))
9294import Data.String (IsString(..))
9395import Data.Word (Word8)
9496import Prelude hiding (takeWhile)
@@ -306,12 +308,6 @@ isHorizontalSpace :: Word8 -> Bool
306308isHorizontalSpace w = w == 32 || w == 9
307309{-# INLINE isHorizontalSpace #-}
308310
309−{-
310−-- | Parse a Double. The position counter is not updated.
311−double :: Parser Double
312−double = numeric "Double" readDouble
313−-}
314−
315311-- | Parse and decode an unsigned hexadecimal number. The hex digits
316312-- @\'a\'@ through @\'f\'@ may be upper or lower case.
317313--
@@ -328,6 +324,7 @@ hexadecimal = B8.foldl' step 0 `fmap` I.takeWhile1 isHexDigit
328324-- | Parse and decode an unsigned decimal number.
329325decimal :: Integral a => Parser a
330326{-# SPECIALISE decimal :: Parser Int #-}
327+{-# SPECIALISE decimal :: Parser Integer #-}
331328decimal = B8.foldl' step 0 `fmap` I.takeWhile1 isDig
332329 where isDig w = w >= 48 && w <= 57
333330 step a w = a * 10 + fromIntegral (w - 48)
@@ -339,3 +336,84 @@ signed :: Num a => Parser a -> Parser a
339336signed p = (negate <$> (char8 '-' *> p))
340337 <|> (char8 '+' *> p)
341338 <|> p
339+
340+data T = T !Integer !Int
341+
342+-- | Parse a rational number.
343+--
344+-- This parser accepts an optional leading sign character, followed by
345+-- at least one decimal digit. The syntax similar to that accepted by
346+-- the 'read' function, with the exception that a trailing @\'.\'@ or
347+-- @\'e\'@ /not/ followed by a number is not consumed.
348+--
349+-- Examples with behaviour identical to 'read', if you feed an empty
350+-- continuation to the first result:
351+--
352+-- >rational "3" == Done 3.0 ""
353+-- >rational "3.1" == Done 3.1 ""
354+-- >rational "3e4" == Done 30000.0 ""
355+-- >rational "3.1e4" == Done 31000.0, ""
356+
357+-- Examples with behaviour identical to 'read':
358+--
359+-- >rational ".3" == Fail "input does not start with a digit"
360+-- >rational "e3" == Fail "input does not start with a digit"
361+--
362+-- Examples of differences from 'read':
363+--
364+-- >rational "3.foo" == Done 3.0 ".foo"
365+-- >rational "3e" == Done 3.0 "e"
366+rational :: RealFloat a => Parser a
367+{-# SPECIALIZE rational :: Parser Double #-}
368+rational = floaty $ \real frac fracDenom -> fromRational $
369+ real % 1 + frac % fracDenom
370+
371+-- | Parse a rational number.
372+--
373+-- The syntax accepted by this parser is the same as for 'rational'.
374+--
375+-- /Note/: This function is almost ten times faster than 'rational',
376+-- but is slightly less accurate.
377+--
378+-- The 'Double' type supports about 16 decimal places of accuracy.
379+-- For 94.2% of numbers, this function and 'rational' give identical
380+-- results, but for the remaining 5.8%, this function loses precision
381+-- around the 15th decimal place. For 0.001% of numbers, this
382+-- function will lose precision at the 13th or 14th decimal place.
383+double :: Parser Double
384+double = floaty $ \real frac fracDenom ->
385+ fromIntegral real +
386+ fromIntegral frac / fromIntegral fracDenom
387+
388+floaty :: RealFloat a => (Integer -> Integer -> Integer -> a) -> Parser a
389+{-# INLINE floaty #-}
390+floaty f = do
391+ let minus = 45
392+ plus = 43
393+ sign <- I.satisfy (\c -> c == minus || c == plus) <|> return plus
394+ real <- decimal
395+ let tryFraction = do
396+ let dot = 46
397+ _ <- I.satisfy (==dot)
398+ ds <- I.takeWhile isDigit_w8
399+ case (case I.parse decimal ds of
400+ I.Partial k -> k B.empty
401+ r -> r) of
402+ I.Done _ n -> return $ T n (B.length ds)
403+ _ -> fail "no digits after decimal"
404+ T fraction fracDigits <- tryFraction <|> return (T 0 0)
405+ let littleE = 101
406+ bigE = 69
407+ e w = w == littleE || w == bigE
408+ power <- (I.satisfy e *> signed decimal) <|> return (0::Int)
409+ let n = if fracDigits == 0
410+ then if power == 0
411+ then fromIntegral real
412+ else fromIntegral real * (10 ^^ power)
413+ else if power == 0
414+ then f real fraction (10 ^ fracDigits)
415+ else f real fraction (10 ^ fracDigits) * (10 ^^ power)
416+ return $! if sign == plus
417+ then n
418+ else -n
419+
attoparsec.cabal+1−1
@@ -1,5 +1,5 @@
11name: attoparsec
2−version: 0.8.2.1
2+version: 0.8.3.0
33license: BSD3
44license-file: LICENSE
55category: Text, Parsing