on master help sign in
Add a faster, custom impl of double parser
b085507Harendra Kumar
benchmark/Streamly/Benchmark/Unicode/Parser.hs+36−19
@@ -10,6 +10,11 @@
10{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE ScopedTypeVariables #-}
11{-# OPTIONS_GHC -Wno-orphans #-}
11{-# OPTIONS_GHC -Wno-orphans #-}
12
12
13+#undef FUSION_CHECK
14+#ifdef FUSION_CHECK
15+{-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-}
16+#endif
17+
13module Main
18module Main
14 (
19 (
15 main
20 main
@@ -22,28 +27,18 @@ import Streamly.Internal.Data.Stream (Stream)
22import Prelude hiding
27import Prelude hiding
23 (any, all, take, sequence, sequence_, sequenceA, takeWhile, dropWhile)
28 (any, all, take, sequence, sequence_, sequenceA, takeWhile, dropWhile)
24
29
25−import qualified Streamly.Internal.Data.Fold as Fold
30+import qualified Streamly.Data.Array as Array
26import qualified Streamly.Data.Stream as Stream
31import qualified Streamly.Data.Stream as Stream
27−import qualified Streamly.Data.Unfold as Unfold
28import qualified Streamly.Internal.Unicode.Parser as PRU
32import qualified Streamly.Internal.Unicode.Parser as PRU
29
33
30import Gauge hiding (env)
34import Gauge hiding (env)
31import Streamly.Benchmark.Common
35import Streamly.Benchmark.Common
32−import Streamly.Benchmark.Common.Handle
33−
34−{-# INLINE sourceUnfoldrM #-}
35−sourceUnfoldrM :: Monad m => Int -> Int -> Stream m Int
36−sourceUnfoldrM value n = Stream.unfoldrM step n
37− where
38− step cnt =
39− if cnt > n + value
40− then return Nothing
41− else return (Just (cnt, cnt + 1))
42
36
43runParser :: Int -> (Stream IO Char -> IO a) -> IO ()
37runParser :: Int -> (Stream IO Char -> IO a) -> IO ()
44runParser count p = do
38runParser count p = do
45 let v = "+123456789.123456789e-123"
39 let v = "+123456789.123456789e-123"
40+ let !(arr :: Array.Array Char) = Array.fromListN (length v) v
46− let s = Stream.unfold Unfold.fromList v
41+ let s = Stream.unfold Array.reader arr
47 replicateM_ count (p s)
42 replicateM_ count (p s)
48
43
49-- | Takes a fold method, and uses it with a default source.
44-- | Takes a fold method, and uses it with a default source.
@@ -51,10 +46,23 @@ runParser count p = do
51benchIOSink :: Int -> String -> (Stream IO Char -> IO b) -> Benchmark
46benchIOSink :: Int -> String -> (Stream IO Char -> IO b) -> Benchmark
52benchIOSink value name f = bench name $ nfIO $ runParser value f
47benchIOSink value name f = bench name $ nfIO $ runParser value f
53
48
49+{-# INLINE doubleParser #-}
50+doubleParser :: Monad m => Stream m Char -> m (Either ParseError (Int, Int))
51+doubleParser = Stream.parse PRU.doubleParser
52+
53+{-# INLINE number #-}
54+number :: Monad m => Stream m Char -> m (Either ParseError (Integer, Int))
55+number = Stream.parse PRU.number
56+
54{-# INLINE double #-}
57{-# INLINE double #-}
55double :: Monad m => Stream m Char -> m (Either ParseError Double)
58double :: Monad m => Stream m Char -> m (Either ParseError Double)
56double = Stream.parse PRU.double
59double = Stream.parse PRU.double
57
60
61+{-# INLINE numberDouble #-}
62+numberDouble :: Monad m => Stream m Char -> m (Either ParseError Double)
63+numberDouble = Stream.parse p
64+ where p = uncurry PRU.mkDouble <$> PRU.number
65+
58-------------------------------------------------------------------------------
66-------------------------------------------------------------------------------
59-- Benchmarks
67-- Benchmarks
60-------------------------------------------------------------------------------
68-------------------------------------------------------------------------------
@@ -69,7 +77,10 @@ instance NFData ParseError where
69o_n_heap_serial :: Int -> [Benchmark]
77o_n_heap_serial :: Int -> [Benchmark]
70o_n_heap_serial value =
78o_n_heap_serial value =
71 [
79 [
72− benchIOSink value "double" double
80+ benchIOSink value "doubleParser" doubleParser
81+ , benchIOSink value "number" number
82+ , benchIOSink value "double (doubleParser)" double
83+ , benchIOSink value "double (number)" numberDouble
73 ]
84 ]
74
85
75-------------------------------------------------------------------------------
86-------------------------------------------------------------------------------
@@ -78,13 +89,19 @@ o_n_heap_serial value =
78
89
79main :: IO ()
90main :: IO ()
80main = do
91main = do
81− env <- mkHandleBenchEnv
92+#ifndef FUSION_CHECK
82− runWithCLIOptsEnv defaultStreamSize alloc (allBenchmarks env)
93+ runWithCLIOpts defaultStreamSize allBenchmarks
83
94
84 where
95 where
85
96
86− alloc value = Stream.fold Fold.toList $ Stream.chunksOf 100 $ sourceUnfoldrM value 0
87−
88− allBenchmarks _ _ value =
97+ allBenchmarks value =
89 [ bgroup (o_n_heap_prefix moduleName) (o_n_heap_serial value)
98 [ bgroup (o_n_heap_prefix moduleName) (o_n_heap_serial value)
90 ]
99 ]
100+#else
101+ -- Enable FUSION_CHECK macro at the beginning of the file
102+ -- Enable one benchmark below, and run the benchmark
103+ -- Check the .dump-simpl output
104+ let value = 100000
105+ runParser value double
106+ return ()
107+#endif
core/src/Streamly/Internal/Unicode/Parser.hs+148−5
@@ -48,6 +48,7 @@ module Streamly.Internal.Unicode.Parser
48 -- * Numeric
48 -- * Numeric
49 , signed
49 , signed
50 , number
50 , number
51+ , doubleParser
51 , double
52 , double
52 , decimal
53 , decimal
53 , hexadecimal
54 , hexadecimal
@@ -58,7 +59,7 @@ module Streamly.Internal.Unicode.Parser
58where
59where
59
60
60import Control.Applicative (Alternative(..))
61import Control.Applicative (Alternative(..))
61−import Data.Bits (Bits, (.|.), shiftL)
62+import Data.Bits (Bits, (.|.), shiftL, (.&.))
62import Data.Char (ord)
63import Data.Char (ord)
63import Data.Ratio ((%))
64import Data.Ratio ((%))
64import Fusion.Plugin.Types (Fuse(..))
65import Fusion.Plugin.Types (Fuse(..))
@@ -269,6 +270,7 @@ hexadecimal = Parser.takeWhile1 isHexDigit (Fold.foldl' step 0)
269signed :: (Num a, Monad m) => Parser Char m a -> Parser Char m a
270signed :: (Num a, Monad m) => Parser Char m a -> Parser Char m a
270signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p
271signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p
271
272
273+-- XXX Change Multiplier to Sign
272type Multiplier = Int
274type Multiplier = Int
273
275
274-- XXX We can use Int instead of Integer to make it twice as fast. But then we
276-- XXX We can use Int instead of Integer to make it twice as fast. But then we
@@ -376,12 +378,12 @@ number = Parser (\s a -> return $ step s a) initial (return . extract)
376 let num = ord val - 48
378 let num = ord val - 48
377 if num >= 0 && num <= 9
379 if num >= 0 && num <= 9
378 then Partial 0 $ SPAfterExponent multiplier buf decimalPlaces 1 num
380 then Partial 0 $ SPAfterExponent multiplier buf decimalPlaces 1 num
379− else Error $ exitSPInitial $ show val
381+ else Done 2 $ exitSPAfterDot multiplier buf decimalPlaces
380 step (SPExponentWithSign mult buf decimalPlaces powerMult) val =
382 step (SPExponentWithSign mult buf decimalPlaces powerMult) val =
381 let num = ord val - 48
383 let num = ord val - 48
382 in if num >= 0 && num <= 9
384 in if num >= 0 && num <= 9
383 then Partial 0 $ SPAfterExponent mult buf decimalPlaces powerMult num
385 then Partial 0 $ SPAfterExponent mult buf decimalPlaces powerMult num
384− else Error $ exitSPSign $ show val
386+ else Done 3 $ exitSPAfterDot mult buf decimalPlaces
385 step (SPAfterExponent mult num decimalPlaces powerMult buf) val =
387 step (SPAfterExponent mult num decimalPlaces powerMult buf) val =
386 let n = ord val - 48
388 let n = ord val - 48
387 in if n >= 0 && n <= 9
389 in if n >= 0 && n <= 9
@@ -407,9 +409,150 @@ number = Parser (\s a -> return $ step s a) initial (return . extract)
407 extract (SPAfterExponent mult num decimalPlaces powerMult powerNum) =
409 extract (SPAfterExponent mult num decimalPlaces powerMult powerNum) =
408 Done 0 $ exitSPAfterExponent mult num decimalPlaces powerMult powerNum
410 Done 0 $ exitSPAfterExponent mult num decimalPlaces powerMult powerNum
409
411
412+type MantissaInt = Int
413+type OverflowPower = Int
414+
415+{-# ANN type DoubleParseState Fuse #-}
416+data DoubleParseState
417+ = DPInitial
418+ | DPSign !Multiplier
419+ | DPAfterSign !Multiplier !MantissaInt !OverflowPower
420+ | DPDot !Multiplier !MantissaInt !OverflowPower
421+ | DPAfterDot !Multiplier !MantissaInt !OverflowPower
422+ | DPExponent !Multiplier !MantissaInt !OverflowPower
423+ | DPExponentWithSign !Multiplier !MantissaInt !OverflowPower !PowerMultiplier
424+ | DPAfterExponent !Multiplier !MantissaInt !OverflowPower !PowerMultiplier !Power
425+
426+-- | A fast, custom parser for double precision flaoting point numbers. Returns
427+-- (mantissa, exponent) tuple. This is much faster than 'number' because it
428+-- assumes the number will fit in a 'Double' type and uses 'Int' representation
429+-- to store mantissa.
430+--
431+-- Number larger than 'Double' may overflow. Int overflow is not checked in the
432+-- exponent.
433+--
434+{-# INLINE doubleParser #-}
435+doubleParser :: Monad m => Parser Char m (Int, Int)
436+doubleParser = Parser (\s a -> return $ step s a) initial (return . extract)
437+
438+ where
439+
440+ -- XXX Assuming Int = Int64
441+
442+ -- Up to 58 bits Int won't overflow
443+ -- ghci> (2^59-1)*10+9 :: Int
444+ -- 5764607523034234879
445+ mask :: Word
446+ mask = 0x7c00000000000000 -- 58 bits, ignore the sign bit
447+
448+ {-# INLINE combineNum #-}
449+ combineNum :: Int -> Int -> Int -> (Int, Int)
450+ combineNum mantissa power num =
451+ if fromIntegral mantissa .&. mask == 0
452+ then (mantissa * 10 + num, power)
453+ else (mantissa, power + 1)
454+
455+ {-# INLINE initial #-}
456+ initial = pure $ IPartial DPInitial
457+
458+ exitDPInitial msg =
459+ "number: expecting sign or decimal digit, got " ++ msg
460+ exitDPSign msg =
461+ "number: expecting decimal digit, got " ++ msg
462+ exitDPAfterSign multiplier num opower = (fromIntegral multiplier * num, opower)
463+ exitDPAfterDot multiplier num opow =
464+ (fromIntegral multiplier * num , opow)
465+ exitDPAfterExponent mult num opow powerMult powerNum =
466+ (fromIntegral mult * num, opow + powerMult * powerNum)
467+
468+ {-# INLINE step #-}
469+ step DPInitial val =
470+ case val of
471+ '+' -> Continue 0 (DPSign 1)
472+ '-' -> Continue 0 $ (DPSign (-1))
473+ _ -> do
474+ let num = ord val - 48
475+ if num >= 0 && num <= 9
476+ then Partial 0 $ DPAfterSign 1 num 0
477+ else Error $ exitDPInitial $ show val
478+ step (DPSign multiplier) val =
479+ let num = ord val - 48
480+ in if num >= 0 && num <= 9
481+ then Partial 0 $ DPAfterSign multiplier num 0
482+ else Error $ exitDPSign $ show val
483+ step (DPAfterSign multiplier buf opower) val =
484+ case val of
485+ '.' -> Continue 0 $ DPDot multiplier buf opower
486+ 'e' -> Continue 0 $ DPExponent multiplier buf opower
487+ 'E' -> Continue 0 $ DPExponent multiplier buf opower
488+ _ ->
489+ let num = ord val - 48
490+ in if num >= 0 && num <= 9
491+ then
492+ let (buf1, power1) = combineNum buf opower num
493+ in Partial 0
494+ $ DPAfterSign multiplier buf1 power1
495+ else Done 1 $ exitDPAfterSign multiplier buf opower
496+ step (DPDot multiplier buf opower) val =
497+ let num = ord val - 48
498+ in if num >= 0 && num <= 9
499+ then
500+ let (buf1, power1) = combineNum buf opower num
501+ in Partial 0 $ DPAfterDot multiplier buf1 (power1 - 1)
502+ else Done 2 $ exitDPAfterSign multiplier buf opower
503+ step (DPAfterDot multiplier buf opower) val =
504+ case val of
505+ 'e' -> Continue 0 $ DPExponent multiplier buf opower
506+ 'E' -> Continue 0 $ DPExponent multiplier buf opower
507+ _ ->
508+ let num = ord val - 48
509+ in if num >= 0 && num <= 9
510+ then
511+ let (buf1, power1) = combineNum buf opower num
512+ in Partial 0 $ DPAfterDot multiplier buf1 (power1 - 1)
513+ else Done 1 $ exitDPAfterDot multiplier buf opower
514+ step (DPExponent multiplier buf opower) val =
515+ case val of
516+ '+' -> Continue 0 (DPExponentWithSign multiplier buf opower 1)
517+ '-' -> Continue 0 (DPExponentWithSign multiplier buf opower (-1))
518+ _ -> do
519+ let num = ord val - 48
520+ if num >= 0 && num <= 9
521+ then Partial 0 $ DPAfterExponent multiplier buf opower 1 num
522+ else Done 2 $ exitDPAfterDot multiplier buf opower
523+ step (DPExponentWithSign mult buf opower powerMult) val =
524+ let num = ord val - 48
525+ in if num >= 0 && num <= 9
526+ then Partial 0 $ DPAfterExponent mult buf opower powerMult num
527+ else Done 3 $ exitDPAfterDot mult buf opower
528+ step (DPAfterExponent mult num opower powerMult buf) val =
529+ let n = ord val - 48
530+ in if n >= 0 && n <= 9
531+ then
532+ Partial 0
533+ $ DPAfterExponent mult num opower powerMult (buf * 10 + n)
534+ else Done 1 $ exitDPAfterExponent mult num opower powerMult buf
535+
536+ {-# INLINE extract #-}
537+ extract DPInitial = Error $ exitDPInitial "end of input"
538+ extract (DPSign _) = Error $ exitDPSign "end of input"
539+ extract (DPAfterSign mult num opow) = Done 0 $ exitDPAfterSign mult num opow
540+ extract (DPDot mult num opow) = Done 1 $ exitDPAfterSign mult num opow
541+ extract (DPAfterDot mult num opow) =
542+ Done 0 $ exitDPAfterDot mult num opow
543+ extract (DPExponent mult num opow) =
544+ Done 1 $ exitDPAfterDot mult num opow
545+ extract (DPExponentWithSign mult num opow _) =
546+ Done 2 $ exitDPAfterDot mult num opow
547+ extract (DPAfterExponent mult num opow powerMult powerNum) =
548+ Done 0 $ exitDPAfterExponent mult num opow powerMult powerNum
549+
410-- XXX We can have a `realFloat` parser instead to parse any RealFloat value.
550-- XXX We can have a `realFloat` parser instead to parse any RealFloat value.
411-- And a integral parser to read any integral value.
551-- And a integral parser to read any integral value.
412
552
553+-- XXX This is very expensive, takes much more time than the rest of the
554+-- parsing. Need to look into fromRational.
555+
413-- | @mkDouble mantissa exponent@ converts a mantissa and exponent to a
556-- | @mkDouble mantissa exponent@ converts a mantissa and exponent to a
414-- 'Double' value equivalent to @mantissa * 10^exponent@. It does not check for
557-- 'Double' value equivalent to @mantissa * 10^exponent@. It does not check for
415-- overflow, powers more than 308 will overflow.
558-- overflow, powers more than 308 will overflow.
@@ -417,7 +560,7 @@ number = Parser (\s a -> return $ step s a) initial (return . extract)
417mkDouble :: Integer -> Int -> Double
560mkDouble :: Integer -> Int -> Double
418mkDouble mantissa power =
561mkDouble mantissa power =
419 if power > 0
562 if power > 0
420− then fromRational (fromIntegral (mantissa * 10 ^ power))
563+ then fromRational ((mantissa * 10 ^ power) % 1)
421 else fromRational (mantissa % 10 ^ (-power))
564 else fromRational (mantissa % 10 ^ (-power))
422
565
423-- | Parse a decimal 'Double' value. This parser accepts an optional sign (+ or
566-- | Parse a decimal 'Double' value. This parser accepts an optional sign (+ or
@@ -473,4 +616,4 @@ mkDouble mantissa power =
473--
616--
474{-# INLINE double #-}
617{-# INLINE double #-}
475double :: Monad m => Parser Char m Double
618double :: Monad m => Parser Char m Double
476−double = uncurry mkDouble <$> number
619+double = fmap (\(m,e) -> mkDouble (fromIntegral m) e) doubleParser
test/Streamly/Test/Unicode/Parser.hs+124−77
@@ -6,7 +6,7 @@ where
6
6
7import Control.Monad.Identity (Identity(runIdentity))
7import Control.Monad.Identity (Identity(runIdentity))
8import Debug.Trace (trace)
8import Debug.Trace (trace)
9−import Streamly.Internal.Data.Parser (ParseError (..))
9+import Streamly.Internal.Data.Parser (ParseError (..), Parser)
10import Streamly.Test.Common (chooseDouble)
10import Streamly.Test.Common (chooseDouble)
11import Test.Hspec.QuickCheck (prop)
11import Test.Hspec.QuickCheck (prop)
12import Test.QuickCheck
12import Test.QuickCheck
@@ -89,103 +89,150 @@ double s d = monadicIO $ do
89 case x of
89 case x of
90 Right val -> if val == d
90 Right val -> if val == d
91 then assert (val == d)
91 then assert (val == d)
92− else trace ("Expectedc = " ++ show d ++ " Got = "++ show val) (assert (val == d))
92+ else trace ("Expected = " ++ show d ++ " Got = "++ show val) (assert (val == d))
93 Left (ParseError _) -> assert False
93 Left (ParseError _) -> assert False
94
94
95+numberP :: Monad m => Parser Char m Double
96+numberP = uncurry Parser.mkDouble <$> Parser.number
97+
98+numberParser :: String -> IO (Either ParseError Double)
99+numberParser = Stream.parse numberP . Stream.fromList
100+
101+number :: String -> Double -> Property
102+number s d = monadicIO $ do
103+ x <- run $ numberParser s
104+ case x of
105+ Right val -> if val == d
106+ then assert (val == d)
107+ else trace ("Expected = " ++ show d ++ " Got = "++ show val) (assert (val == d))
108+ Left (ParseError _) -> assert False
109+
95−doubleErr :: String -> String -> Property
110+doubleErr :: (String -> IO (Either ParseError Double)) -> String -> String -> Property
96−doubleErr s msg = monadicIO $ do
111+doubleErr f s msg = monadicIO $ do
97− x <- run $ doubleParser s
112+ x <- run $ f s
98 case x of
113 case x of
99 Right _ -> assert False
114 Right _ -> assert False
100 Left (ParseError err) -> if err == msg
115 Left (ParseError err) -> if err == msg
101 then assert (err == msg)
116 then assert (err == msg)
102 else trace err (assert (err == msg))
117 else trace err (assert (err == msg))
103
118
104−remainingStream :: String -> [String]
119+remainingStreamDouble :: String -> [String]
105−remainingStream x =
120+remainingStreamDouble x =
106 let f = Stream.parseBreak Unicode.double . Stream.fromList
121 let f = Stream.parseBreak Unicode.double . Stream.fromList
107 in concatMap (Stream.toList . snd) (f x)
122 in concatMap (Stream.toList . snd) (f x)
108
123
109−afterParse :: String -> String -> Property
124+remainingStreamNumber :: String -> [String]
110−afterParse si so = monadicIO $ do
125+remainingStreamNumber x =
111− let x = remainingStream si
126+ let f = Stream.parseBreak numberP . Stream.fromList
112− in assert (x == [so])
127+ in concatMap (Stream.toList . snd) (f x)
113−
114−moduleName :: String
115−moduleName = "Unicode.Parser"
116
128
117−main :: IO ()
129+afterParse :: (String -> [String]) -> String -> String -> Property
118−main = do
130+afterParse f si so = monadicIO $ do
119− H.hspec
131+ let x = f si
120− $ H.describe moduleName $ do
132+ in assert (x == [so])
121− H.describe "Unicode Parser" $ do
122− prop "double \"4\" 4.0" $ double "4" 4.0
123− prop "double \"00.3\" 0.3" $ double "00.3" 0.3
124− prop "double \"0.003\" 3.0e-3" $ double "0.003" 3.0e-3
125− prop "double \"44.56.67\" 44.56" $ double "44.56.67" 44.56
126− prop "double \"0.0.00\" 0.0" $ double "0.0.00" 0.0
127− prop "double \"44.56-78\" 44.56" $ double "44.56-78" 44.56
128− prop "double \"44-\" 44.0" $ double "44-" 44.0
129− prop "double \"44-56\" 44.0" $ double "44-56" 44.0
130− prop "double \"44+\" 44.0" $ double "44+" 44.0
131− prop "double \"44+56\" 44.0" $ double "44+56" 44.0
132− prop "double \"44u\" 44.0" $ double "44u" 44.0
133− prop "double \"+123.345\" 123.345" $ double "+123.345" 123.345
134− prop "double \"-123.345\" (-123.345)" $ double "-123.345" (-123.345)
135− prop "double \"1e\"" $ double "1e" 1.0
136− prop "double \"1e+\"" $ double "1e+" 1.0
137− prop "double \"1e+0\"" $ double "1e+0" 1.0
138− prop "double \"1e-0\"" $ double "1e-0" 1.0
139− prop "double \"1.0e-1\"" $ double "1.0e-1" 0.1
140− prop "double \"1.0e+1\"" $ double "1.0e+1" 10.0
141− prop "double \"1.0e1\"" $ double "1.0e1" 10.0
142− prop "double \"1.0e+309\"" $ double "1.0e+309" 1.0e309
143− prop "double \"1.0e-309\"" $ double "1.0e-309" 1.0e-309
144− prop "double \"1.0e1.\"" $ double "1.0e1." 10.0
145− prop "double \"1.0e1+\"" $ double "1.0e1+" 10.0
146− prop "double \"1.0e1-\"" $ double "1.0e1-" 10.0
147− prop "double \"1.0e1e\"" $ double "1.0e1e" 10.0
148− prop "double \"1.0E+1\"" $ double "1.0E+1" 10.0
149
133
134+testParser :: String -> (String -> Double -> Property) -> H.SpecWith ()
135+testParser desc f = do
136+ -- XXX We can combine the parser tests and remaining stream tests in the
137+ -- same test.
138+ H.describe ("Unicode Parser " ++ desc) $ do
139+ prop "double \"00.00\"" $ f "00.00" 0.0
140+ prop "double \"00.00e00\"" $ f "00.00e00" 0.0
141+ prop "double \"4\" 4.0" $ f "4" 4.0
142+ prop "double \"00.3\" 0.3" $ f "00.3" 0.3
143+ prop "double \"0.003\" 3.0e-3" $ f "0.003" 3.0e-3
144+ prop "double \"44.56.67\" 44.56" $ f "44.56.67" 44.56
145+ prop "double \"0.0.00\" 0.0" $ f "0.0.00" 0.0
146+ prop "double \"44.56-78\" 44.56" $ f "44.56-78" 44.56
147+ prop "double \"44-\" 44.0" $ f "44-" 44.0
148+ prop "double \"44-56\" 44.0" $ f "44-56" 44.0
149+ prop "double \"44+\" 44.0" $ f "44+" 44.0
150+ prop "double \"44+56\" 44.0" $ f "44+56" 44.0
151+ prop "double \"44u\" 44.0" $ f "44u" 44.0
152+ prop "double \"+123.345\" 123.345" $ f "+123.345" 123.345
153+ prop "double \"-123.345\" (-123.345)" $ f "-123.345" (-123.345)
154+ prop "double \"1e\"" $ f "1e" 1.0
155+ prop "double \"1e+\"" $ f "1e+" 1.0
156+ prop "double \"1ex\"" $ f "1ex" 1.0
157+ prop "double \"1e+x\"" $ f "1e+x" 1.0
158+ prop "double \"1e+0\"" $ f "1e+0" 1.0
159+ prop "double \"1e-0\"" $ f "1e-0" 1.0
160+ prop "double \"1.0e-1\"" $ f "1.0e-1" 0.1
161+ prop "double \"1.0e+1\"" $ f "1.0e+1" 10.0
162+ prop "double \"1.0e1\"" $ f "1.0e1" 10.0
163+ prop "double \"1.0e+309\"" $ f "1.0e+309" 1.0e309
164+ prop "double \"1.0e-309\"" $ f "1.0e-309" 1.0e-309
165+ prop "double \"1.0e1.\"" $ f "1.0e1." 10.0
166+ prop "double \"1.0e1+\"" $ f "1.0e1+" 10.0
167+ prop "double \"1.0e1-\"" $ f "1.0e1-" 10.0
168+ prop "double \"1.0e1e\"" $ f "1.0e1e" 10.0
169+ prop "double \"1.0E+1\"" $ f "1.0E+1" 10.0
170+ prop "double \"9223372036854775806\"" $ f "9223372036854775806" 9.223372036854776e18
171+ -- maxBound :: Int
172+ prop "double \"9223372036854775807\"" $ f "9223372036854775807" 9.223372036854776e18
173+ prop "double \"9223372036854775808\"" $ f "9223372036854775808" 9.223372036854776e18
174+
175+testParseErr :: String -> (String -> IO (Either ParseError Double)) -> H.SpecWith ()
176+testParseErr desc f = do
177+ H.describe ("Unicode Parser Error " ++ desc) $ do
150 prop "double \"\" Error"
178 prop "double \"\" Error"
151− $ doubleErr "" "number: expecting sign or decimal digit, got end of input"
179+ $ doubleErr f "" "number: expecting sign or decimal digit, got end of input"
152 prop "double \"a\" Error"
180 prop "double \"a\" Error"
153− $ doubleErr "a" "number: expecting sign or decimal digit, got 'a'"
181+ $ doubleErr f "a" "number: expecting sign or decimal digit, got 'a'"
154 prop "double \".4\" Error"
182 prop "double \".4\" Error"
155− $ doubleErr ".4" "number: expecting sign or decimal digit, got '.'"
183+ $ doubleErr f ".4" "number: expecting sign or decimal digit, got '.'"
156 prop "double \".\" Error"
184 prop "double \".\" Error"
157− $ doubleErr "." "number: expecting sign or decimal digit, got '.'"
185+ $ doubleErr f "." "number: expecting sign or decimal digit, got '.'"
158 prop "double \"..\" Error"
186 prop "double \"..\" Error"
159− $ doubleErr ".." "number: expecting sign or decimal digit, got '.'"
187+ $ doubleErr f ".." "number: expecting sign or decimal digit, got '.'"
160 prop "double \"-\" Error"
188 prop "double \"-\" Error"
161− $ doubleErr "-" "number: expecting decimal digit, got end of input"
189+ $ doubleErr f "-" "number: expecting decimal digit, got end of input"
162 prop "double \"+\" Error"
190 prop "double \"+\" Error"
163− $ doubleErr "+" "number: expecting decimal digit, got end of input"
191+ $ doubleErr f "+" "number: expecting decimal digit, got end of input"
164 prop "double \"++\" Error"
192 prop "double \"++\" Error"
165− $ doubleErr "++" "number: expecting decimal digit, got '+'"
193+ $ doubleErr f "++" "number: expecting decimal digit, got '+'"
166−
194+
195+testLeftOver :: String -> (String -> [String]) -> H.SpecWith ()
196+testLeftOver desc f = do
197+ H.describe ("Unicode Parser leftover " ++ desc) $ do
167− prop "afterParse \"4.\" \".\"" $ afterParse "4." "."
198+ prop "afterParse \"4.\" \".\"" $ afterParse f "4." "."
168− prop "afterParse \"4..\" \"..\"" $ afterParse "4.." ".."
199+ prop "afterParse \"4..\" \"..\"" $ afterParse f "4.." ".."
169− prop "afterParse \"4-\" \"-\"" $ afterParse "4-" "-"
200+ prop "afterParse \"4-\" \"-\"" $ afterParse f "4-" "-"
170− prop "afterParse \"4--\" \"--\"" $ afterParse "4--" "--"
201+ prop "afterParse \"4--\" \"--\"" $ afterParse f "4--" "--"
171− prop "afterParse \"4.9abc\" \"abc\"" $ afterParse "4.9abc" "abc"
202+ prop "afterParse \"4.9abc\" \"abc\"" $ afterParse f "4.9abc" "abc"
172− prop "afterParse \"4.9\" \"\"" $ afterParse "4.9" ""
203+ prop "afterParse \"4.9ex\"" $ afterParse f "4.9ex" "ex"
204+ prop "afterParse \"4.9e+x\"" $ afterParse f "4.9e+x" "e+x"
205+ prop "afterParse \"4.9\" \"\"" $ afterParse f "4.9" ""
173− prop "afterParse \"+4.9\" \"\"" $ afterParse "+4.9" ""
206+ prop "afterParse \"+4.9\" \"\"" $ afterParse f "+4.9" ""
174− prop "afterParse \"-4.9\" \"\"" $ afterParse "-4.9" ""
207+ prop "afterParse \"-4.9\" \"\"" $ afterParse f "-4.9" ""
175− prop "afterParse \"4.9.\" \".\"" $ afterParse "4.9." "."
208+ prop "afterParse \"4.9.\" \".\"" $ afterParse f "4.9." "."
176− prop "afterParse \"4.9..\" \"..\"" $ afterParse "4.9.." ".."
209+ prop "afterParse \"4.9..\" \"..\"" $ afterParse f "4.9.." ".."
177− prop "afterParse \"4.9...\" \"...\"" $ afterParse "4.9..." "..."
210+ prop "afterParse \"4.9...\" \"...\"" $ afterParse f "4.9..." "..."
178− prop "afterParse \"4.9+\" \"+\"" $ afterParse "4.9+" "+"
211+ prop "afterParse \"4.9+\" \"+\"" $ afterParse f "4.9+" "+"
179− prop "afterParse \"4.9++\" \"++\"" $ afterParse "4.9++" "++"
212+ prop "afterParse \"4.9++\" \"++\"" $ afterParse f "4.9++" "++"
180− prop "afterParse \"4.9+++\" \"+++\"" $ afterParse "4.9+++" "+++"
213+ prop "afterParse \"4.9+++\" \"+++\"" $ afterParse f "4.9+++" "+++"
181− prop "afterParse \"4.9-\" \"-\"" $ afterParse "4.9-" "-"
214+ prop "afterParse \"4.9-\" \"-\"" $ afterParse f "4.9-" "-"
182− prop "afterParse \"4.9--\" \"--\"" $ afterParse "4.9--" "--"
215+ prop "afterParse \"4.9--\" \"--\"" $ afterParse f "4.9--" "--"
183− prop "afterParse \"4.9---\" \"---\"" $ afterParse "4.9---" "---"
216+ prop "afterParse \"4.9---\" \"---\"" $ afterParse f "4.9---" "---"
184− prop "afterParse \"\" \"\"" $ afterParse "" ""
217+ prop "afterParse \"\" \"\"" $ afterParse f "" ""
185− prop "afterParse \".\" \".\"" $ afterParse "." "."
218+ prop "afterParse \".\" \".\"" $ afterParse f "." "."
186− prop "afterParse \"..\" \"..\"" $ afterParse ".." ".."
219+ prop "afterParse \"..\" \"..\"" $ afterParse f ".." ".."
187− prop "afterParse \"+\" \"+\"" $ afterParse "+" "+"
220+ prop "afterParse \"+\" \"+\"" $ afterParse f "+" "+"
188− prop "afterParse \"++\" \"++\"" $ afterParse "++" "++"
221+ prop "afterParse \"++\" \"++\"" $ afterParse f "++" "++"
222+
223+moduleName :: String
224+moduleName = "Unicode.Parser"
225+
226+main :: IO ()
227+main = do
228+ H.hspec
229+ $ H.describe moduleName $ do
230+ testParser "double" double
231+ testParser "number" number
232+ testParseErr "double" doubleParser
233+ testParseErr "number" numberParser
234+ testLeftOver "double" remainingStreamDouble
235+ testLeftOver "number" remainingStreamNumber
189 H.describe "Scientific parser property test" $ do
236 H.describe "Scientific parser property test" $ do
190 prop "Exponent format" scientificExpFP
237 prop "Exponent format" scientificExpFP
191 prop "Decimal format" scientificFixFP
238 prop "Decimal format" scientificFixFP