git.iko.soy/typeable/attoparsec
on master help sign in
.gitignore+1
since splitsnapshots
unifiedsplit
@@ -3,3 +3,4 @@
3/dist/
3/dist/
4.cabal-sandbox
4.cabal-sandbox
5cabal.sandbox.config
5cabal.sandbox.config
6+*.sw[op]
.hgtags+6
@@ -55,3 +55,9 @@ a3c7a521fd58595d6a470ab890cc54a80dd45bd7 0.11.3.1
55c9c2b05707b2fffcd749d5817e3e0d04e6ad6d0d 0.12.1.0
55c9c2b05707b2fffcd749d5817e3e0d04e6ad6d0d 0.12.1.0
569157c2b4c35aa5f683b4ac51e6ff52c696bc22e2 0.12.1.1
569157c2b4c35aa5f683b4ac51e6ff52c696bc22e2 0.12.1.1
57682ba515f7ba62500ad79371b37b90932e0c0e3d 0.12.1.2
57682ba515f7ba62500ad79371b37b90932e0c0e3d 0.12.1.2
58+2066a72b79b478f59a9c7f1839c522fc59ba9af7 0.12.1.3
59+ef875ad3bebef2b6fb1bd7063de6a46a06d0d4b4 0.12.1.4
60+dc1fcff28d13a508f677c7b06af6420877842493 0.12.1.5
61+013e04e4526bfd5e9e4a611b42e6f03c413bea91 0.12.1.6
62+0ddbce995b77b8db391b4d6519f13feddf4b5bf3 0.13.0.0
63+46c16463ec20ad46c9cd0bfb1050c4268f34b749 0.13.0.1
.travis.ymlnew file+161
@@ -0,0 +1,161 @@
1+# This Travis job script has been generated by a script via
2+#
3+# haskell-ci '--tests-jobs=>=7.4' '--no-benchmarks' 'attoparsec.cabal'
4+#
5+# For more information, see https://github.com/haskell-CI/haskell-ci
6+#
7+# version: 0.3.20190815
8+#
9+language: c
10+dist: xenial
11+git:
12+ # whether to recursively clone submodules
13+ submodules: false
14+cache:
15+ directories:
16+ - $HOME/.cabal/packages
17+ - $HOME/.cabal/store
18+before_cache:
19+ - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
20+ # remove files that are regenerated by 'cabal update'
21+ - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
22+ - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
23+ - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
24+ - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
25+ - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
26+ - rm -rfv $CABALHOME/packages/head.hackage
27+matrix:
28+ include:
29+ - compiler: ghc-8.8.1
30+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
31+ - compiler: ghc-8.6.5
32+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-2.4"]}}
33+ - compiler: ghc-8.4.4
34+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-2.4"]}}
35+ - compiler: ghc-8.2.2
36+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-2.4"]}}
37+ - compiler: ghc-8.0.2
38+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-2.4"]}}
39+ - compiler: ghc-7.10.3
40+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-2.4"]}}
41+ - compiler: ghc-7.8.4
42+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-2.4"]}}
43+ - compiler: ghc-7.6.3
44+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.6.3","cabal-install-2.4"]}}
45+ - compiler: ghc-7.4.2
46+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.4.2","cabal-install-2.4"]}}
47+ - compiler: ghc-7.2.2
48+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.2.2","cabal-install-2.4"]}}
49+ - compiler: ghc-7.0.4
50+ addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.0.4","cabal-install-2.4"]}}
51+before_install:
52+ - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
53+ - WITHCOMPILER="-w $HC"
54+ - HCPKG="$HC-pkg"
55+ - unset CC
56+ - CABAL=/opt/ghc/bin/cabal
57+ - CABALHOME=$HOME/.cabal
58+ - export PATH="$CABALHOME/bin:$PATH"
59+ - TOP=$(pwd)
60+ - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
61+ - echo $HCNUMVER
62+ - CABAL="$CABAL -vnormal+nowrap+markoutput"
63+ - set -o pipefail
64+ - |
65+ echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }' >> .colorful.awk
66+ echo 'BEGIN { state = "output"; }' >> .colorful.awk
67+ echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }' >> .colorful.awk
68+ echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }' >> .colorful.awk
69+ echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
70+ echo ' if (state == "cabal") {' >> .colorful.awk
71+ echo ' print blue($0)' >> .colorful.awk
72+ echo ' } else {' >> .colorful.awk
73+ echo ' print $0' >> .colorful.awk
74+ echo ' }' >> .colorful.awk
75+ echo '}' >> .colorful.awk
76+ - cat .colorful.awk
77+ - |
78+ color_cabal_output () {
79+ awk -f $TOP/.colorful.awk
80+ }
81+ - echo text | color_cabal_output
82+install:
83+ - ${CABAL} --version
84+ - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
85+ - TEST=--enable-tests
86+ - if [ $HCNUMVER -lt 70400 ] ; then TEST=--disable-tests ; fi
87+ - BENCH=--enable-benchmarks
88+ - BENCH=--disable-benchmarks
89+ - HEADHACKAGE=false
90+ - rm -f $CABALHOME/config
91+ - |
92+ echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config
93+ echo "remote-build-reporting: anonymous" >> $CABALHOME/config
94+ echo "write-ghc-environment-files: always" >> $CABALHOME/config
95+ echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config
96+ echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config
97+ echo "world-file: $CABALHOME/world" >> $CABALHOME/config
98+ echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config
99+ echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config
100+ echo "installdir: $CABALHOME/bin" >> $CABALHOME/config
101+ echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config
102+ echo "store-dir: $CABALHOME/store" >> $CABALHOME/config
103+ echo "install-dirs user" >> $CABALHOME/config
104+ echo " prefix: $CABALHOME" >> $CABALHOME/config
105+ echo "repository hackage.haskell.org" >> $CABALHOME/config
106+ echo " url: http://hackage.haskell.org/" >> $CABALHOME/config
107+ - cat $CABALHOME/config
108+ - rm -fv cabal.project cabal.project.local cabal.project.freeze
109+ - travis_retry ${CABAL} v2-update -v
110+ # Generate cabal.project
111+ - rm -rf cabal.project cabal.project.local cabal.project.freeze
112+ - touch cabal.project
113+ - |
114+ echo "packages: ." >> cabal.project
115+ - |
116+ - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(attoparsec)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
117+ - cat cabal.project || true
118+ - cat cabal.project.local || true
119+ - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
120+ - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
121+ - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
122+ - rm cabal.project.freeze
123+ - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
124+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
125+script:
126+ - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
127+ # Packaging...
128+ - ${CABAL} v2-sdist all | color_cabal_output
129+ # Unpacking...
130+ - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
131+ - cd ${DISTDIR} || false
132+ - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
133+ - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \;
134+ - PKGDIR_attoparsec="$(find . -maxdepth 1 -type d -regex '.*/attoparsec-[0-9.]*')"
135+ # Generate cabal.project
136+ - rm -rf cabal.project cabal.project.local cabal.project.freeze
137+ - touch cabal.project
138+ - |
139+ echo "packages: ${PKGDIR_attoparsec}" >> cabal.project
140+ - |
141+ - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(attoparsec)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
142+ - cat cabal.project || true
143+ - cat cabal.project.local || true
144+ # Building...
145+ # this builds all libraries and executables (without tests/benchmarks)
146+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
147+ # Building with tests and benchmarks...
148+ # build & run tests, build benchmarks
149+ - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
150+ # Testing...
151+ - if [ $HCNUMVER -ge 70400 ] ; then ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output ; fi
152+ # cabal check...
153+ - (cd ${PKGDIR_attoparsec} && ${CABAL} -vnormal check)
154+ # haddock...
155+ - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
156+ # Building without installed constraints for packages in global-db...
157+ - rm -f cabal.project.local
158+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
159+
160+# REGENDATA ["--tests-jobs=>=7.4","--no-benchmarks","attoparsec.cabal"]
161+# EOF
Data/Attoparsec.hs+1−1
@@ -1,6 +1,6 @@
1-- |
1-- |
2-- Module : Data.Attoparsec
2-- Module : Data.Attoparsec
3−-- Copyright : Bryan O'Sullivan 2007-2014
3+-- Copyright : Bryan O'Sullivan 2007-2015
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
Data/Attoparsec/ByteString.hs+11−4
@@ -1,6 +1,10 @@
1+{-# LANGUAGE CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-}
4+#endif
1-- |
5-- |
2-- Module : Data.Attoparsec.ByteString
6-- Module : Data.Attoparsec.ByteString
3−-- Copyright : Bryan O'Sullivan 2007-2014
7+-- Copyright : Bryan O'Sullivan 2007-2015
4-- License : BSD3
8-- License : BSD3
5--
9--
6-- Maintainer : bos@serpentine.com
10-- Maintainer : bos@serpentine.com
@@ -59,6 +63,7 @@ module Data.Attoparsec.ByteString
59 , I.skipWhile
63 , I.skipWhile
60 , I.take
64 , I.take
61 , I.scan
65 , I.scan
66+ , I.runScanner
62 , I.takeWhile
67 , I.takeWhile
63 , I.takeWhile1
68 , I.takeWhile1
64 , I.takeTill
69 , I.takeTill
@@ -92,6 +97,7 @@ module Data.Attoparsec.ByteString
92 ) where
97 ) where
93
98
94import Data.Attoparsec.Combinator
99import Data.Attoparsec.Combinator
100+import Data.List (intercalate)
95import qualified Data.Attoparsec.ByteString.Internal as I
101import qualified Data.Attoparsec.ByteString.Internal as I
96import qualified Data.Attoparsec.Internal as I
102import qualified Data.Attoparsec.Internal as I
97import qualified Data.ByteString as B
103import qualified Data.ByteString as B
@@ -218,6 +224,7 @@ maybeResult _ = Nothing
218-- | Convert a 'Result' value to an 'Either' value. A 'T.Partial'
224-- | Convert a 'Result' value to an 'Either' value. A 'T.Partial'
219-- result is treated as failure.
225-- result is treated as failure.
220eitherResult :: Result r -> Either String r
226eitherResult :: Result r -> Either String r
221−eitherResult (T.Done _ r) = Right r
227+eitherResult (T.Done _ r) = Right r
222−eitherResult (T.Fail _ _ msg) = Left msg
228+eitherResult (T.Fail _ [] msg) = Left msg
229+eitherResult (T.Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
223−eitherResult _ = Left "Result: incomplete input"
230+eitherResult _ = Left "Result: incomplete input"
Data/Attoparsec/ByteString/Buffer.hs+10−6
@@ -1,7 +1,7 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2-- |
2-- |
3-- Module : Data.Attoparsec.ByteString.Buffer
3-- Module : Data.Attoparsec.ByteString.Buffer
4−-- Copyright : Bryan O'Sullivan 2007-2014
4+-- Copyright : Bryan O'Sullivan 2007-2015
5-- License : BSD3
5-- License : BSD3
6--
6--
7-- Maintainer : bos@serpentine.com
7-- Maintainer : bos@serpentine.com
@@ -57,7 +57,8 @@ import Control.Exception (assert)
57import Data.ByteString.Internal (ByteString(..), memcpy, nullForeignPtr)
57import Data.ByteString.Internal (ByteString(..), memcpy, nullForeignPtr)
58import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
58import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
59import Data.List (foldl1')
59import Data.List (foldl1')
60−import Data.Monoid (Monoid(..))
60+import Data.Monoid as Mon (Monoid(..))
61+import Data.Semigroup (Semigroup(..))
61import Data.Word (Word8)
62import Data.Word (Word8)
62import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
63import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
63import Foreign.Ptr (castPtr, plusPtr)
64import Foreign.Ptr (castPtr, plusPtr)
@@ -86,14 +87,17 @@ buffer (PS fp off len) = Buf fp off len len 0
86unbuffer :: Buffer -> ByteString
87unbuffer :: Buffer -> ByteString
87unbuffer (Buf fp off len _ _) = PS fp off len
88unbuffer (Buf fp off len _ _) = PS fp off len
88
89
90+instance Semigroup Buffer where
91+ (Buf _ _ _ 0 _) <> b = b
92+ a <> (Buf _ _ _ 0 _) = a
93+ buf <> (Buf fp off len _ _) = append buf fp off len
94+
89instance Monoid Buffer where
95instance Monoid Buffer where
90 mempty = Buf nullForeignPtr 0 0 0 0
96 mempty = Buf nullForeignPtr 0 0 0 0
91
97
92− mappend (Buf _ _ _ 0 _) b = b
98+ mappend = (<>)
93− mappend a (Buf _ _ _ 0 _) = a
94− mappend buf (Buf fp off len _ _) = append buf fp off len
95
99
96− mconcat [] = mempty
100+ mconcat [] = Mon.mempty
97 mconcat xs = foldl1' mappend xs
101 mconcat xs = foldl1' mappend xs
98
102
99pappend :: Buffer -> ByteString -> Buffer
103pappend :: Buffer -> ByteString -> Buffer
Data/Attoparsec/ByteString/Char8.hs+16−20
@@ -1,10 +1,13 @@
1−{-# LANGUAGE BangPatterns, FlexibleInstances, TypeFamilies,
1+{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, TypeFamilies,
2 TypeSynonymInstances, GADTs #-}
2 TypeSynonymInstances, GADTs #-}
3+#if __GLASGOW_HASKELL__ >= 702
4+{-# LANGUAGE Trustworthy #-} -- Imports internal modules
5+#endif
3{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-warnings-deprecations #-}
6{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-warnings-deprecations #-}
4
7
5-- |
8-- |
6-- Module : Data.Attoparsec.ByteString.Char8
9-- Module : Data.Attoparsec.ByteString.Char8
7−-- Copyright : Bryan O'Sullivan 2007-2014
10+-- Copyright : Bryan O'Sullivan 2007-2015
8-- License : BSD3
11-- License : BSD3
9--
12--
10-- Maintainer : bos@serpentine.com
13-- Maintainer : bos@serpentine.com
@@ -67,7 +70,7 @@ module Data.Attoparsec.ByteString.Char8
67
70
68 -- * Efficient string handling
71 -- * Efficient string handling
69 , I.string
72 , I.string
70− , stringCI
73+ , I.stringCI
71 , skipSpace
74 , skipSpace
72 , skipWhile
75 , skipWhile
73 , I.take
76 , I.take
@@ -124,7 +127,11 @@ module Data.Attoparsec.ByteString.Char8
124 , I.atEnd
127 , I.atEnd
125 ) where
128 ) where
126
129
130+#if !MIN_VERSION_base(4,8,0)
127−import Control.Applicative (pure, (*>), (<*), (<$>), (<|>))
131+import Control.Applicative (pure, (*>), (<*), (<$>))
132+import Data.Word (Word)
133+#endif
134+import Control.Applicative ((<|>))
128import Control.Monad (void, when)
135import Control.Monad (void, when)
129import Data.Attoparsec.ByteString.FastSet (charClass, memberChar)
136import Data.Attoparsec.ByteString.FastSet (charClass, memberChar)
130import Data.Attoparsec.ByteString.Internal (Parser)
137import Data.Attoparsec.ByteString.Internal (Parser)
@@ -136,7 +143,7 @@ import Data.Int (Int8, Int16, Int32, Int64)
136import Data.String (IsString(..))
143import Data.String (IsString(..))
137import Data.Scientific (Scientific)
144import Data.Scientific (Scientific)
138import qualified Data.Scientific as Sci
145import qualified Data.Scientific as Sci
139−import Data.Word (Word8, Word16, Word32, Word64, Word)
146+import Data.Word (Word8, Word16, Word32, Word64)
140import Prelude hiding (takeWhile)
147import Prelude hiding (takeWhile)
141import qualified Data.Attoparsec.ByteString as A
148import qualified Data.Attoparsec.ByteString as A
142import qualified Data.Attoparsec.ByteString.Internal as I
149import qualified Data.Attoparsec.ByteString.Internal as I
@@ -162,16 +169,6 @@ instance (a ~ B.ByteString) => IsString (Parser a) where
162-- currency sign in ISO-8859-1). Haskell 'Char' values above U+00FF
169-- currency sign in ISO-8859-1). Haskell 'Char' values above U+00FF
163-- are truncated, so e.g. U+1D6B7 is truncated to the byte @0xB7@.
170-- are truncated, so e.g. U+1D6B7 is truncated to the byte @0xB7@.
164
171
165−-- ASCII-specific but fast, oh yes.
166−toLower :: Word8 -> Word8
167−toLower w | w >= 65 && w <= 90 = w + 32
168− | otherwise = w
169−
170−-- | Satisfy a literal string, ignoring case.
171−stringCI :: B.ByteString -> Parser B.ByteString
172−stringCI = I.stringTransform (B8.map toLower)
173−{-# INLINE stringCI #-}
174−
175-- | Consume input as long as the predicate returns 'True', and return
172-- | Consume input as long as the predicate returns 'True', and return
176-- the consumed input.
173-- the consumed input.
177--
174--
@@ -235,7 +232,7 @@ isDigit c = c >= '0' && c <= '9'
235
232
236-- | A fast digit predicate.
233-- | A fast digit predicate.
237isDigit_w8 :: Word8 -> Bool
234isDigit_w8 :: Word8 -> Bool
238−isDigit_w8 w = w >= 48 && w <= 57
235+isDigit_w8 w = w - 48 <= 9
239{-# INLINE isDigit_w8 #-}
236{-# INLINE isDigit_w8 #-}
240
237
241-- | Match any character.
238-- | Match any character.
@@ -272,7 +269,7 @@ isSpace c = (c == ' ') || ('\t' <= c && c <= '\r')
272
269
273-- | Fast 'Word8' predicate for matching ASCII space characters.
270-- | Fast 'Word8' predicate for matching ASCII space characters.
274isSpace_w8 :: Word8 -> Bool
271isSpace_w8 :: Word8 -> Bool
275−isSpace_w8 w = (w == 32) || (9 <= w && w <= 13)
272+isSpace_w8 w = w == 32 || w - 9 <= 4
276{-# INLINE isSpace_w8 #-}
273{-# INLINE isSpace_w8 #-}
277
274
278
275
@@ -447,9 +444,8 @@ hexadecimal = B8.foldl' step 0 `fmap` I.takeWhile1 isHexDigit
447
444
448-- | Parse and decode an unsigned decimal number.
445-- | Parse and decode an unsigned decimal number.
449decimal :: Integral a => Parser a
446decimal :: Integral a => Parser a
450−decimal = B8.foldl' step 0 `fmap` I.takeWhile1 isDig
447+decimal = B8.foldl' step 0 `fmap` I.takeWhile1 isDigit_w8
451− where isDig w = w >= 48 && w <= 57
452− step a w = a * 10 + fromIntegral (w - 48)
448+ where step a w = a * 10 + fromIntegral (w - 48)
453{-# SPECIALISE decimal :: Parser Int #-}
449{-# SPECIALISE decimal :: Parser Int #-}
454{-# SPECIALISE decimal :: Parser Int8 #-}
450{-# SPECIALISE decimal :: Parser Int8 #-}
455{-# SPECIALISE decimal :: Parser Int16 #-}
451{-# SPECIALISE decimal :: Parser Int16 #-}
Data/Attoparsec/ByteString/FastSet.hs+1−1
@@ -3,7 +3,7 @@
3-----------------------------------------------------------------------------
3-----------------------------------------------------------------------------
4-- |
4-- |
5-- Module : Data.Attoparsec.ByteString.FastSet
5-- Module : Data.Attoparsec.ByteString.FastSet
6−-- Copyright : Bryan O'Sullivan 2007-2014
6+-- Copyright : Bryan O'Sullivan 2007-2015
7-- License : BSD3
7-- License : BSD3
8--
8--
9-- Maintainer : bos@serpentine.com
9-- Maintainer : bos@serpentine.com
Data/Attoparsec/ByteString/Internal.hs+85−34
@@ -1,7 +1,8 @@
1−{-# LANGUAGE BangPatterns, GADTs, OverloadedStrings, RecordWildCards #-}
1+{-# LANGUAGE BangPatterns, CPP, GADTs, OverloadedStrings, RankNTypes,
2+ RecordWildCards #-}
2-- |
3-- |
3-- Module : Data.Attoparsec.ByteString.Internal
4-- Module : Data.Attoparsec.ByteString.Internal
4−-- Copyright : Bryan O'Sullivan 2007-2014
5+-- Copyright : Bryan O'Sullivan 2007-2015
5-- License : BSD3
6-- License : BSD3
6--
7--
7-- Maintainer : bos@serpentine.com
8-- Maintainer : bos@serpentine.com
@@ -46,7 +47,7 @@ module Data.Attoparsec.ByteString.Internal
46 -- * Efficient string handling
47 -- * Efficient string handling
47 , skipWhile
48 , skipWhile
48 , string
49 , string
49− , stringTransform
50+ , stringCI
50 , take
51 , take
51 , scan
52 , scan
52 , runScanner
53 , runScanner
@@ -65,7 +66,10 @@ module Data.Attoparsec.ByteString.Internal
65 , atEnd
66 , atEnd
66 ) where
67 ) where
67
68
69+#if !MIN_VERSION_base(4,8,0)
68−import Control.Applicative ((<|>), (<$>))
70+import Control.Applicative ((<$>))
71+#endif
72+import Control.Applicative ((<|>))
69import Control.Monad (when)
73import Control.Monad (when)
70import Data.Attoparsec.ByteString.Buffer (Buffer, buffer)
74import Data.Attoparsec.ByteString.Buffer (Buffer, buffer)
71import Data.Attoparsec.ByteString.FastSet (charClass, memberWord8)
75import Data.Attoparsec.ByteString.FastSet (charClass, memberWord8)
@@ -74,6 +78,7 @@ import Data.Attoparsec.Internal
74import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
78import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
75import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
79import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
76import Data.ByteString (ByteString)
80import Data.ByteString (ByteString)
81+import Data.List (intercalate)
77import Data.Word (Word8)
82import Data.Word (Word8)
78import Foreign.ForeignPtr (withForeignPtr)
83import Foreign.ForeignPtr (withForeignPtr)
79import Foreign.Ptr (castPtr, minusPtr, plusPtr)
84import Foreign.Ptr (castPtr, minusPtr, plusPtr)
@@ -136,22 +141,15 @@ storable = hack undefined
136 hack :: Storable b => b -> Parser b
141 hack :: Storable b => b -> Parser b
137 hack dummy = do
142 hack dummy = do
138 (fp,o,_) <- B.toForeignPtr `fmap` take (sizeOf dummy)
143 (fp,o,_) <- B.toForeignPtr `fmap` take (sizeOf dummy)
139− return . B.inlinePerformIO . withForeignPtr fp $ \p ->
144+ return . inlinePerformIO . withForeignPtr fp $ \p ->
140 peek (castPtr $ p `plusPtr` o)
145 peek (castPtr $ p `plusPtr` o)
141
146
142−-- | Consume @n@ bytes of input, but succeed only if the predicate
143−-- returns 'True'.
144−takeWith :: Int -> (ByteString -> Bool) -> Parser ByteString
145−takeWith n0 p = do
146− let n = max n0 0
147− s <- ensure n
148− if p s
149− then advance n >> return s
150− else fail "takeWith"
151−
152-- | Consume exactly @n@ bytes of input.
147-- | Consume exactly @n@ bytes of input.
153take :: Int -> Parser ByteString
148take :: Int -> Parser ByteString
154−take n = takeWith n (const True)
149+take n0 = do
150+ let n = max n0 0
151+ s <- ensure n
152+ advance n >> return s
155{-# INLINE take #-}
153{-# INLINE take #-}
156
154
157-- | @string s@ parses a sequence of bytes that identically match
155-- | @string s@ parses a sequence of bytes that identically match
@@ -170,13 +168,59 @@ take n = takeWith n (const True)
170-- before failing. In attoparsec, the above parser will /succeed/ on
168-- before failing. In attoparsec, the above parser will /succeed/ on
171-- that input, because the failed first branch will consume nothing.
169-- that input, because the failed first branch will consume nothing.
172string :: ByteString -> Parser ByteString
170string :: ByteString -> Parser ByteString
173−string s = takeWith (B.length s) (==s)
171+string s = string_ (stringSuspended id) id s
174{-# INLINE string #-}
172{-# INLINE string #-}
175
173
176−stringTransform :: (ByteString -> ByteString) -> ByteString
174+-- ASCII-specific but fast, oh yes.
177− -> Parser ByteString
175+toLower :: Word8 -> Word8
178−stringTransform f s = takeWith (B.length s) ((==f s) . f)
176+toLower w | w >= 65 && w <= 90 = w + 32
179−{-# INLINE stringTransform #-}
177+ | otherwise = w
178+
179+-- | Satisfy a literal string, ignoring case.
180+stringCI :: ByteString -> Parser ByteString
181+stringCI s = string_ (stringSuspended lower) lower s
182+ where lower = B8.map toLower
183+{-# INLINE stringCI #-}
184+
185+string_ :: (forall r. ByteString -> ByteString -> Buffer -> Pos -> More
186+ -> Failure r -> Success ByteString r -> Result r)
187+ -> (ByteString -> ByteString)
188+ -> ByteString -> Parser ByteString
189+string_ suspended f s0 = T.Parser $ \t pos more lose succ ->
190+ let n = B.length s
191+ s = f s0
192+ in if lengthAtLeast pos n t
193+ then let t' = substring pos (Pos n) t
194+ in if s == f t'
195+ then succ t (pos + Pos n) more t'
196+ else lose t pos more [] "string"
197+ else let t' = Buf.unsafeDrop (fromPos pos) t
198+ in if f t' `B.isPrefixOf` s
199+ then suspended s (B.drop (B.length t') s) t pos more lose succ
200+ else lose t pos more [] "string"
201+{-# INLINE string_ #-}
202+
203+stringSuspended :: (ByteString -> ByteString)
204+ -> ByteString -> ByteString -> Buffer -> Pos -> More
205+ -> Failure r
206+ -> Success ByteString r
207+ -> Result r
208+stringSuspended f s0 s t pos more lose succ =
209+ runParser (demandInput_ >>= go) t pos more lose succ
210+ where go s'0 = T.Parser $ \t' pos' more' lose' succ' ->
211+ let m = B.length s
212+ s' = f s'0
213+ n = B.length s'
214+ in if n >= m
215+ then if B.unsafeTake m s' == s
216+ then let o = Pos (B.length s0)
217+ in succ' t' (pos' + o) more'
218+ (substring pos' o t')
219+ else lose' t' pos' more' [] "string"
220+ else if s' == B.unsafeTake n s
221+ then stringSuspended f s0 (B.unsafeDrop n s)
222+ t' pos' more' lose' succ'
223+ else lose' t' pos' more' [] "string"
180
224
181-- | Skip past input for as long as the predicate returns 'True'.
225-- | Skip past input for as long as the predicate returns 'True'.
182skipWhile :: (Word8 -> Bool) -> Parser ()
226skipWhile :: (Word8 -> Bool) -> Parser ()
@@ -213,15 +257,24 @@ takeTill p = takeWhile (not . p)
213-- parsers loop until a failure occurs. Careless use will thus result
257-- parsers loop until a failure occurs. Careless use will thus result
214-- in an infinite loop.
258-- in an infinite loop.
215takeWhile :: (Word8 -> Bool) -> Parser ByteString
259takeWhile :: (Word8 -> Bool) -> Parser ByteString
216−takeWhile p = (B.concat . reverse) `fmap` go []
260+takeWhile p = do
261+ s <- B8.takeWhile p <$> get
262+ continue <- inputSpansChunks (B.length s)
263+ if continue
264+ then takeWhileAcc p [s]
265+ else return s
266+{-# INLINE takeWhile #-}
267+
268+takeWhileAcc :: (Word8 -> Bool) -> [ByteString] -> Parser ByteString
269+takeWhileAcc p = go
217 where
270 where
218 go acc = do
271 go acc = do
219 s <- B8.takeWhile p <$> get
272 s <- B8.takeWhile p <$> get
220 continue <- inputSpansChunks (B.length s)
273 continue <- inputSpansChunks (B.length s)
221 if continue
274 if continue
222 then go (s:acc)
275 then go (s:acc)
223− else return (s:acc)
276+ else return $ concatReverse (s:acc)
224−{-# INLINE takeWhile #-}
277+{-# INLINE takeWhileAcc #-}
225
278
226takeRest :: Parser [ByteString]
279takeRest :: Parser [ByteString]
227takeRest = go []
280takeRest = go []
@@ -285,16 +338,13 @@ scan_ f s0 p = go [] s0
285-- parsers loop until a failure occurs. Careless use will thus result
338-- parsers loop until a failure occurs. Careless use will thus result
286-- in an infinite loop.
339-- in an infinite loop.
287scan :: s -> (s -> Word8 -> Maybe s) -> Parser ByteString
340scan :: s -> (s -> Word8 -> Maybe s) -> Parser ByteString
288−scan = scan_ $ \_ chunks ->
341+scan = scan_ $ \_ chunks -> return $! concatReverse chunks
289− case chunks of
290− [x] -> return x
291− xs -> return $! B.concat $ reverse xs
292{-# INLINE scan #-}
342{-# INLINE scan #-}
293
343
294-- | Like 'scan', but generalized to return the final state of the
344-- | Like 'scan', but generalized to return the final state of the
295-- scanner.
345-- scanner.
296runScanner :: s -> (s -> Word8 -> Maybe s) -> Parser (ByteString, s)
346runScanner :: s -> (s -> Word8 -> Maybe s) -> Parser (ByteString, s)
297−runScanner = scan_ $ \s xs -> return (B.concat (reverse xs), s)
347+runScanner = scan_ $ \s xs -> let !sx = concatReverse xs in return (sx, s)
298{-# INLINE runScanner #-}
348{-# INLINE runScanner #-}
299
349
300-- | Consume input as long as the predicate returns 'True', and return
350-- | Consume input as long as the predicate returns 'True', and return
@@ -314,8 +364,9 @@ takeWhile1 p = do
314 advance len
364 advance len
315 eoc <- endOfChunk
365 eoc <- endOfChunk
316 if eoc
366 if eoc
317− then (s<>) `fmap` takeWhile p
367+ then takeWhileAcc p [s]
318 else return s
368 else return s
369+{-# INLINE takeWhile1 #-}
319
370
320-- | Match any byte in a set.
371-- | Match any byte in a set.
321--
372--
@@ -416,9 +467,10 @@ parse m s = T.runParser m (buffer s) (Pos 0) Incomplete failK successK
416-- @
467-- @
417parseOnly :: Parser a -> ByteString -> Either String a
468parseOnly :: Parser a -> ByteString -> Either String a
418parseOnly m s = case T.runParser m (buffer s) (Pos 0) Complete failK successK of
469parseOnly m s = case T.runParser m (buffer s) (Pos 0) Complete failK successK of
470+ Fail _ [] err -> Left err
419− Fail _ _ err -> Left err
471+ Fail _ ctxs err -> Left (intercalate " > " ctxs ++ ": " ++ err)
420− Done _ a -> Right a
472+ Done _ a -> Right a
421− _ -> error "parseOnly: impossible error!"
473+ _ -> error "parseOnly: impossible error!"
422{-# INLINE parseOnly #-}
474{-# INLINE parseOnly #-}
423
475
424get :: Parser ByteString
476get :: Parser ByteString
@@ -465,7 +517,6 @@ ensure n = T.Parser $ \t pos more lose succ ->
465 then succ t pos more (substring pos (Pos n) t)
517 then succ t pos more (substring pos (Pos n) t)
466 -- The uncommon case is kept out-of-line to reduce code size:
518 -- The uncommon case is kept out-of-line to reduce code size:
467 else ensureSuspended n t pos more lose succ
519 else ensureSuspended n t pos more lose succ
468−-- Non-recursive so the bounds check can be inlined:
469{-# INLINE ensure #-}
520{-# INLINE ensure #-}
470
521
471-- | Return both the result of a parse and the portion of the input
522-- | Return both the result of a parse and the portion of the input
Data/Attoparsec/ByteString/Lazy.hs+10−3
@@ -1,6 +1,11 @@
1+{-# LANGUAGE CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4+#endif
5+
1-- |
6-- |
2-- Module : Data.Attoparsec.ByteString.Lazy
7-- Module : Data.Attoparsec.ByteString.Lazy
3−-- Copyright : Bryan O'Sullivan 2007-2014
8+-- Copyright : Bryan O'Sullivan 2007-2015
4-- License : BSD3
9-- License : BSD3
5--
10--
6-- Maintainer : bos@serpentine.com
11-- Maintainer : bos@serpentine.com
@@ -36,6 +41,7 @@ module Data.Attoparsec.ByteString.Lazy
36
41
37import Control.DeepSeq (NFData(rnf))
42import Control.DeepSeq (NFData(rnf))
38import Data.ByteString.Lazy.Internal (ByteString(..), chunk)
43import Data.ByteString.Lazy.Internal (ByteString(..), chunk)
44+import Data.List (intercalate)
39import qualified Data.ByteString as B
45import qualified Data.ByteString as B
40import qualified Data.Attoparsec.ByteString as A
46import qualified Data.Attoparsec.ByteString as A
41import qualified Data.Attoparsec.Internal.Types as T
47import qualified Data.Attoparsec.Internal.Types as T
@@ -99,5 +105,6 @@ maybeResult _ = Nothing
99
105
100-- | Convert a 'Result' value to an 'Either' value.
106-- | Convert a 'Result' value to an 'Either' value.
101eitherResult :: Result r -> Either String r
107eitherResult :: Result r -> Either String r
102−eitherResult (Done _ r) = Right r
108+eitherResult (Done _ r) = Right r
103−eitherResult (Fail _ _ msg) = Left msg
109+eitherResult (Fail _ [] msg) = Left msg
110+eitherResult (Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
Data/Attoparsec/Char8.hs+1−1
@@ -1,6 +1,6 @@
1-- |
1-- |
2-- Module : Data.Attoparsec.Char8
2-- Module : Data.Attoparsec.Char8
3−-- Copyright : Bryan O'Sullivan 2007-2014
3+-- Copyright : Bryan O'Sullivan 2007-2015
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
Data/Attoparsec/Combinator.hs+23−10
@@ -1,7 +1,10 @@
1−{-# LANGUAGE BangPatterns #-}
1+{-# LANGUAGE BangPatterns, CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4+#endif
2-- |
5-- |
3-- Module : Data.Attoparsec.Combinator
6-- Module : Data.Attoparsec.Combinator
4−-- Copyright : Daan Leijen 1999-2001, Bryan O'Sullivan 2007-2014
7+-- Copyright : Daan Leijen 1999-2001, Bryan O'Sullivan 2007-2015
5-- License : BSD3
8-- License : BSD3
6--
9--
7-- Maintainer : bos@serpentine.com
10-- Maintainer : bos@serpentine.com
@@ -33,15 +36,18 @@ module Data.Attoparsec.Combinator
33 , satisfyElem
36 , satisfyElem
34 , endOfInput
37 , endOfInput
35 , atEnd
38 , atEnd
39+ , lookAhead
36 ) where
40 ) where
37
41
42+#if !MIN_VERSION_base(4,8,0)
43+import Control.Applicative (Applicative(..), (<$>))
44+import Data.Monoid (Monoid(mappend))
45+#endif
38−import Control.Applicative (Alternative(..), Applicative(..), empty, liftA2,
46+import Control.Applicative (Alternative(..), empty, liftA2, many, (<|>))
39− many, (<|>), (*>), (<$>))
40import Control.Monad (MonadPlus(..))
47import Control.Monad (MonadPlus(..))
41import Data.Attoparsec.Internal.Types (Parser(..), IResult(..))
48import Data.Attoparsec.Internal.Types (Parser(..), IResult(..))
42import Data.Attoparsec.Internal (endOfInput, atEnd, satisfyElem)
49import Data.Attoparsec.Internal (endOfInput, atEnd, satisfyElem)
43import Data.ByteString (ByteString)
50import Data.ByteString (ByteString)
44−import Data.Monoid (Monoid(mappend))
45import Data.Text (Text)
51import Data.Text (Text)
46import qualified Data.Attoparsec.Zepto as Z
52import qualified Data.Attoparsec.Zepto as Z
47import Prelude hiding (succ)
53import Prelude hiding (succ)
@@ -126,7 +132,7 @@ many1' p = liftM2' (:) p (many' p)
126-- | @sepBy p sep@ applies /zero/ or more occurrences of @p@, separated
132-- | @sepBy p sep@ applies /zero/ or more occurrences of @p@, separated
127-- by @sep@. Returns a list of the values returned by @p@.
133-- by @sep@. Returns a list of the values returned by @p@.
128--
134--
129−-- > commaSep p = p `sepBy` (symbol ",")
135+-- > commaSep p = p `sepBy` (char ',')
130sepBy :: Alternative f => f a -> f s -> f [a]
136sepBy :: Alternative f => f a -> f s -> f [a]
131sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
137sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
132{-# SPECIALIZE sepBy :: Parser ByteString a -> Parser ByteString s
138{-# SPECIALIZE sepBy :: Parser ByteString a -> Parser ByteString s
@@ -138,7 +144,7 @@ sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
138-- by @sep@. Returns a list of the values returned by @p@. The value
144-- by @sep@. Returns a list of the values returned by @p@. The value
139-- returned by @p@ is forced to WHNF.
145-- returned by @p@ is forced to WHNF.
140--
146--
141−-- > commaSep p = p `sepBy'` (symbol ",")
147+-- > commaSep p = p `sepBy'` (char ',')
142sepBy' :: (MonadPlus m) => m a -> m s -> m [a]
148sepBy' :: (MonadPlus m) => m a -> m s -> m [a]
143sepBy' p s = scan `mplus` return []
149sepBy' p s = scan `mplus` return []
144 where scan = liftM2' (:) p ((s >> sepBy1' p s) `mplus` return [])
150 where scan = liftM2' (:) p ((s >> sepBy1' p s) `mplus` return [])
@@ -150,7 +156,7 @@ sepBy' p s = scan `mplus` return []
150-- | @sepBy1 p sep@ applies /one/ or more occurrences of @p@, separated
156-- | @sepBy1 p sep@ applies /one/ or more occurrences of @p@, separated
151-- by @sep@. Returns a list of the values returned by @p@.
157-- by @sep@. Returns a list of the values returned by @p@.
152--
158--
153−-- > commaSep p = p `sepBy1` (symbol ",")
159+-- > commaSep p = p `sepBy1` (char ',')
154sepBy1 :: Alternative f => f a -> f s -> f [a]
160sepBy1 :: Alternative f => f a -> f s -> f [a]
155sepBy1 p s = scan
161sepBy1 p s = scan
156 where scan = liftA2 (:) p ((s *> scan) <|> pure [])
162 where scan = liftA2 (:) p ((s *> scan) <|> pure [])
@@ -163,7 +169,7 @@ sepBy1 p s = scan
163-- by @sep@. Returns a list of the values returned by @p@. The value
169-- by @sep@. Returns a list of the values returned by @p@. The value
164-- returned by @p@ is forced to WHNF.
170-- returned by @p@ is forced to WHNF.
165--
171--
166−-- > commaSep p = p `sepBy1'` (symbol ",")
172+-- > commaSep p = p `sepBy1'` (char ',')
167sepBy1' :: (MonadPlus m) => m a -> m s -> m [a]
173sepBy1' :: (MonadPlus m) => m a -> m s -> m [a]
168sepBy1' p s = scan
174sepBy1' p s = scan
169 where scan = liftM2' (:) p ((s >> scan) `mplus` return [])
175 where scan = liftM2' (:) p ((s >> scan) `mplus` return [])
@@ -236,7 +242,14 @@ eitherP a b = (Left <$> a) <|> (Right <$> b)
236-- | If a parser has returned a 'T.Partial' result, supply it with more
242-- | If a parser has returned a 'T.Partial' result, supply it with more
237-- input.
243-- input.
238feed :: Monoid i => IResult i r -> i -> IResult i r
244feed :: Monoid i => IResult i r -> i -> IResult i r
239−feed f@(Fail _ _ _) _ = f
245+feed (Fail t ctxs msg) d = Fail (mappend t d) ctxs msg
240feed (Partial k) d = k d
246feed (Partial k) d = k d
241feed (Done t r) d = Done (mappend t d) r
247feed (Done t r) d = Done (mappend t d) r
242{-# INLINE feed #-}
248{-# INLINE feed #-}
249+
250+-- | Apply a parser without consuming any input.
251+lookAhead :: Parser i a -> Parser i a
252+lookAhead p = Parser $ \t pos more lose succ ->
253+ let succ' t' _pos' more' = succ t' pos more'
254+ in runParser p t pos more lose succ'
255+{-# INLINE lookAhead #-}
Data/Attoparsec/Internal.hs+31−5
@@ -1,7 +1,7 @@
1−{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
1+{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables #-}
2-- |
2-- |
3-- Module : Data.Attoparsec.Internal
3-- Module : Data.Attoparsec.Internal
4−-- Copyright : Bryan O'Sullivan 2007-2014
4+-- Copyright : Bryan O'Sullivan 2007-2015
5-- License : BSD3
5-- License : BSD3
6--
6--
7-- Maintainer : bos@serpentine.com
7-- Maintainer : bos@serpentine.com
@@ -15,13 +15,18 @@ module Data.Attoparsec.Internal
15 ( compareResults
15 ( compareResults
16 , prompt
16 , prompt
17 , demandInput
17 , demandInput
18+ , demandInput_
18 , wantInput
19 , wantInput
19 , endOfInput
20 , endOfInput
20 , atEnd
21 , atEnd
21 , satisfyElem
22 , satisfyElem
23+ , concatReverse
22 ) where
24 ) where
23
25
26+#if !MIN_VERSION_base(4,8,0)
24import Control.Applicative ((<$>))
27import Control.Applicative ((<$>))
28+import Data.Monoid (Monoid, mconcat)
29+#endif
25import Data.Attoparsec.Internal.Types
30import Data.Attoparsec.Internal.Types
26import Data.ByteString (ByteString)
31import Data.ByteString (ByteString)
27import Data.Text (Text)
32import Data.Text (Text)
@@ -40,8 +45,8 @@ compareResults (Done t0 r0) (Done t1 r1) =
40compareResults (Partial _) (Partial _) = Nothing
45compareResults (Partial _) (Partial _) = Nothing
41compareResults _ _ = Just False
46compareResults _ _ = Just False
42
47
43−-- | Ask for input. If we receive any, pass it to a success
48+-- | Ask for input. If we receive any, pass the augmented input to a
44−-- continuation, otherwise to a failure continuation.
49+-- success continuation, otherwise to a failure continuation.
45prompt :: Chunk t
50prompt :: Chunk t
46 => State t -> Pos -> More
51 => State t -> Pos -> More
47 -> (State t -> Pos -> More -> IResult t r)
52 -> (State t -> Pos -> More -> IResult t r)
@@ -68,12 +73,25 @@ demandInput :: Chunk t => Parser t ()
68demandInput = Parser $ \t pos more lose succ ->
73demandInput = Parser $ \t pos more lose succ ->
69 case more of
74 case more of
70 Complete -> lose t pos more [] "not enough input"
75 Complete -> lose t pos more [] "not enough input"
71− _ -> let lose' t' pos' more' = lose t' pos' more' [] "not enough input"
76+ _ -> let lose' _ pos' more' = lose t pos' more' [] "not enough input"
72 succ' t' pos' more' = succ t' pos' more' ()
77 succ' t' pos' more' = succ t' pos' more' ()
73 in prompt t pos more lose' succ'
78 in prompt t pos more lose' succ'
74{-# SPECIALIZE demandInput :: Parser ByteString () #-}
79{-# SPECIALIZE demandInput :: Parser ByteString () #-}
75{-# SPECIALIZE demandInput :: Parser Text () #-}
80{-# SPECIALIZE demandInput :: Parser Text () #-}
76
81
82+-- | Immediately demand more input via a 'Partial' continuation
83+-- result. Return the new input.
84+demandInput_ :: Chunk t => Parser t t
85+demandInput_ = Parser $ \t pos more lose succ ->
86+ case more of
87+ Complete -> lose t pos more [] "not enough input"
88+ _ -> Partial $ \s ->
89+ if nullChunk s
90+ then lose t pos Complete [] "not enough input"
91+ else succ (pappendChunk t s) pos more s
92+{-# SPECIALIZE demandInput_ :: Parser ByteString ByteString #-}
93+{-# SPECIALIZE demandInput_ :: Parser Text Text #-}
94+
77-- | This parser always succeeds. It returns 'True' if any input is
95-- | This parser always succeeds. It returns 'True' if any input is
78-- available either immediately or on demand, and 'False' if the end
96-- available either immediately or on demand, and 'False' if the end
79-- of all input has been reached.
97-- of all input has been reached.
@@ -143,3 +161,11 @@ satisfyElem p = Parser $ \t pos more lose succ ->
143 | otherwise -> lose t pos more [] "satisfyElem"
161 | otherwise -> lose t pos more [] "satisfyElem"
144 Nothing -> satisfySuspended p t pos more lose succ
162 Nothing -> satisfySuspended p t pos more lose succ
145{-# INLINE satisfyElem #-}
163{-# INLINE satisfyElem #-}
164+
165+-- | Concatenate a monoid after reversing its elements. Used to
166+-- glue together a series of textual chunks that have been accumulated
167+-- \"backwards\".
168+concatReverse :: Monoid m => [m] -> m
169+concatReverse [x] = x
170+concatReverse xs = mconcat (reverse xs)
171+{-# INLINE concatReverse #-}
Data/Attoparsec/Internal/Types.hs+48−32
@@ -1,8 +1,8 @@
1−{-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving, OverloadedStrings,
1+{-# LANGUAGE CPP, BangPatterns, GeneralizedNewtypeDeriving, OverloadedStrings,
2 Rank2Types, RecordWildCards, TypeFamilies #-}
2 Rank2Types, RecordWildCards, TypeFamilies #-}
3-- |
3-- |
4-- Module : Data.Attoparsec.Internal.Types
4-- Module : Data.Attoparsec.Internal.Types
5−-- Copyright : Bryan O'Sullivan 2007-2014
5+-- Copyright : Bryan O'Sullivan 2007-2015
6-- License : BSD3
6-- License : BSD3
7--
7--
8-- Maintainer : bos@serpentine.com
8-- Maintainer : bos@serpentine.com
@@ -25,18 +25,21 @@ module Data.Attoparsec.Internal.Types
25 , Chunk(..)
25 , Chunk(..)
26 ) where
26 ) where
27
27
28−import Control.Applicative (Alternative(..), Applicative(..), (<$>))
28+import Control.Applicative as App (Applicative(..), (<$>))
29+import Control.Applicative (Alternative(..))
29import Control.DeepSeq (NFData(rnf))
30import Control.DeepSeq (NFData(rnf))
30import Control.Monad (MonadPlus(..))
31import Control.Monad (MonadPlus(..))
32+import qualified Control.Monad.Fail as Fail (MonadFail(..))
33+import Data.Monoid as Mon (Monoid(..))
34+import Data.Semigroup (Semigroup(..))
31import Data.Word (Word8)
35import Data.Word (Word8)
32import Data.ByteString (ByteString)
36import Data.ByteString (ByteString)
33import qualified Data.ByteString as BS
37import qualified Data.ByteString as BS
34import Data.ByteString.Internal (w2c)
38import Data.ByteString.Internal (w2c)
35−import Data.Monoid (Monoid(..))
36import Data.Text (Text)
39import Data.Text (Text)
37import qualified Data.Text as Text
40import qualified Data.Text as Text
38import Data.Text.Unsafe (Iter(..))
41import Data.Text.Unsafe (Iter(..))
39−import Prelude hiding (getChar, succ)
42+import Prelude hiding (succ)
40import qualified Data.Attoparsec.ByteString.Buffer as B
43import qualified Data.Attoparsec.ByteString.Buffer as B
41import qualified Data.Attoparsec.Text.Buffer as T
44import qualified Data.Attoparsec.Text.Buffer as T
42
45
@@ -67,10 +70,13 @@ data IResult i r =
67 -- not yet been consumed (if any) when the parse succeeded.
70 -- not yet been consumed (if any) when the parse succeeded.
68
71
69instance (Show i, Show r) => Show (IResult i r) where
72instance (Show i, Show r) => Show (IResult i r) where
73+ showsPrec d ir = showParen (d > 10) $
74+ case ir of
70− show (Fail t stk msg) =
75+ (Fail t stk msg) -> showString "Fail" . f t . f stk . f msg
71− unwords [ "Fail", show t, show stk, show msg]
72− show (Partial _) = "Partial _"
76+ (Partial _) -> showString "Partial _"
73− show (Done t r) = unwords ["Done", show t, show r]
77+ (Done t r) -> showString "Done" . f t . f r
78+ where f :: Show a => a -> ShowS
79+ f x = showChar ' ' . showsPrec 11 x
74
80
75instance (NFData i, NFData r) => NFData (IResult i r) where
81instance (NFData i, NFData r) => NFData (IResult i r) where
76 rnf (Fail t stk msg) = rnf t `seq` rnf stk `seq` rnf msg
82 rnf (Fail t stk msg) = rnf t `seq` rnf stk `seq` rnf msg
@@ -83,8 +89,8 @@ instance Functor (IResult i) where
83 fmap f (Partial k) = Partial (fmap f . k)
89 fmap f (Partial k) = Partial (fmap f . k)
84 fmap f (Done t r) = Done t (f r)
90 fmap f (Done t r) = Done t (f r)
85
91
86−-- | The core parser type. This is parameterised over the types @i@
92+-- | The core parser type. This is parameterised over the type @i@
87−-- of string being processed and @t@ of internal state representation.
93+-- of string being processed.
88--
94--
89-- This type is an instance of the following classes:
95-- This type is an instance of the following classes:
90--
96--
@@ -121,17 +127,21 @@ type Success i t a r = t -> Pos -> More -> a -> IResult i r
121data More = Complete | Incomplete
127data More = Complete | Incomplete
122 deriving (Eq, Show)
128 deriving (Eq, Show)
123
129
124−instance Monoid More where
130+instance Semigroup More where
125− mappend c@Complete _ = c
131+ c@Complete <> _ = c
126− mappend _ m = m
132+ _ <> m = m
133+
134+instance Mon.Monoid More where
135+ mappend = (<>)
127− mempty = Incomplete
136+ mempty = Incomplete
128
137
129instance Monad (Parser i) where
138instance Monad (Parser i) where
130− fail err = Parser $ \t pos more lose _succ -> lose t pos more [] msg
139+#if !(MIN_VERSION_base(4,13,0))
131− where msg = "Failed reading: " ++ err
140+ fail = Fail.fail
132 {-# INLINE fail #-}
141 {-# INLINE fail #-}
142+#endif
133
143
134− return v = Parser $ \t pos more _lose succ -> succ t pos more v
144+ return = App.pure
135 {-# INLINE return #-}
145 {-# INLINE return #-}
136
146
137 m >>= k = Parser $ \t !pos more lose succ ->
147 m >>= k = Parser $ \t !pos more lose succ ->
@@ -139,6 +149,15 @@ instance Monad (Parser i) where
139 in runParser m t pos more lose succ'
149 in runParser m t pos more lose succ'
140 {-# INLINE (>>=) #-}
150 {-# INLINE (>>=) #-}
141
151
152+ (>>) = (*>)
153+ {-# INLINE (>>) #-}
154+
155+
156+instance Fail.MonadFail (Parser i) where
157+ fail err = Parser $ \t pos more lose _succ -> lose t pos more [] msg
158+ where msg = "Failed reading: " ++ err
159+ {-# INLINE fail #-}
160+
142plus :: Parser i a -> Parser i a -> Parser i a
161plus :: Parser i a -> Parser i a -> Parser i a
143plus f g = Parser $ \t pos more lose succ ->
162plus f g = Parser $ \t pos more lose succ ->
144 let lose' t' _pos' more' _ctx _msg = runParser g t' pos more' lose succ
163 let lose' t' _pos' more' _ctx _msg = runParser g t' pos more' lose succ
@@ -163,23 +182,23 @@ apP d e = do
163{-# INLINE apP #-}
182{-# INLINE apP #-}
164
183
165instance Applicative (Parser i) where
184instance Applicative (Parser i) where
166− pure = return
185+ pure v = Parser $ \t !pos more _lose succ -> succ t pos more v
167 {-# INLINE pure #-}
186 {-# INLINE pure #-}
168 (<*>) = apP
187 (<*>) = apP
169 {-# INLINE (<*>) #-}
188 {-# INLINE (<*>) #-}
170−
189+ m *> k = m >>= \_ -> k
171− -- These definitions are equal to the defaults, but this
172− -- way the optimizer doesn't have to work so hard to figure
173− -- that out.
174− (*>) = (>>)
175 {-# INLINE (*>) #-}
190 {-# INLINE (*>) #-}
176− x <* y = x >>= \a -> y >> return a
191+ x <* y = x >>= \a -> y >> pure a
177 {-# INLINE (<*) #-}
192 {-# INLINE (<*) #-}
178
193
194+instance Semigroup (Parser i a) where
195+ (<>) = plus
196+ {-# INLINE (<>) #-}
197+
179instance Monoid (Parser i a) where
198instance Monoid (Parser i a) where
180 mempty = fail "mempty"
199 mempty = fail "mempty"
181 {-# INLINE mempty #-}
200 {-# INLINE mempty #-}
182− mappend = plus
201+ mappend = (<>)
183 {-# INLINE mappend #-}
202 {-# INLINE mappend #-}
184
203
185instance Alternative (Parser i) where
204instance Alternative (Parser i) where
@@ -190,8 +209,9 @@ instance Alternative (Parser i) where
190 {-# INLINE (<|>) #-}
209 {-# INLINE (<|>) #-}
191
210
192 many v = many_v
211 many v = many_v
212+ where
193− where many_v = some_v <|> pure []
213+ many_v = some_v <|> pure []
194− some_v = (:) <$> v <*> many_v
214+ some_v = (:) <$> v <*> many_v
195 {-# INLINE many #-}
215 {-# INLINE many #-}
196
216
197 some v = some_v
217 some v = some_v
@@ -200,10 +220,6 @@ instance Alternative (Parser i) where
200 some_v = (:) <$> v <*> many_v
220 some_v = (:) <$> v <*> many_v
201 {-# INLINE some #-}
221 {-# INLINE some #-}
202
222
203−(<>) :: (Monoid m) => m -> m -> m
204−(<>) = mappend
205−{-# INLINE (<>) #-}
206−
207-- | A common interface for input chunks.
223-- | A common interface for input chunks.
208class Monoid c => Chunk c where
224class Monoid c => Chunk c where
209 type ChunkElem c
225 type ChunkElem c
Data/Attoparsec/Lazy.hs+1−1
@@ -1,6 +1,6 @@
1-- |
1-- |
2-- Module : Data.Attoparsec.Lazy
2-- Module : Data.Attoparsec.Lazy
3−-- Copyright : Bryan O'Sullivan 2007-2014
3+-- Copyright : Bryan O'Sullivan 2007-2015
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
Data/Attoparsec/Number.hs+1−1
@@ -1,7 +1,7 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2-- |
2-- |
3-- Module : Data.Attoparsec.Number
3-- Module : Data.Attoparsec.Number
4−-- Copyright : Bryan O'Sullivan 2007-2014
4+-- Copyright : Bryan O'Sullivan 2007-2015
5-- License : BSD3
5-- License : BSD3
6--
6--
7-- Maintainer : bos@serpentine.com
7-- Maintainer : bos@serpentine.com
Data/Attoparsec/Text.hs+17−7
@@ -1,9 +1,12 @@
1−{-# LANGUAGE BangPatterns, FlexibleInstances, TypeSynonymInstances #-}
1+{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, TypeSynonymInstances #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4+#endif
2{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
5{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
3
6
4-- |
7-- |
5-- Module : Data.Attoparsec.Text
8-- Module : Data.Attoparsec.Text
6−-- Copyright : Bryan O'Sullivan 2007-2014
9+-- Copyright : Bryan O'Sullivan 2007-2015
7-- License : BSD3
10-- License : BSD3
8--
11--
9-- Maintainer : bos@serpentine.com
12-- Maintainer : bos@serpentine.com
@@ -69,6 +72,7 @@ module Data.Attoparsec.Text
69 , skipSpace
72 , skipSpace
70 , I.skipWhile
73 , I.skipWhile
71 , I.scan
74 , I.scan
75+ , I.runScanner
72 , I.take
76 , I.take
73 , I.takeWhile
77 , I.takeWhile
74 , I.takeWhile1
78 , I.takeWhile1
@@ -122,7 +126,11 @@ module Data.Attoparsec.Text
122 , I.atEnd
126 , I.atEnd
123 ) where
127 ) where
124
128
129+#if !MIN_VERSION_base(4,8,0)
125−import Control.Applicative (pure, (<$>), (*>), (<*), (<|>))
130+import Control.Applicative (pure, (*>), (<*), (<$>))
131+import Data.Word (Word)
132+#endif
133+import Control.Applicative ((<|>))
126import Data.Attoparsec.Combinator
134import Data.Attoparsec.Combinator
127import Data.Attoparsec.Number (Number(..))
135import Data.Attoparsec.Number (Number(..))
128import Data.Scientific (Scientific)
136import Data.Scientific (Scientific)
@@ -131,8 +139,9 @@ import Data.Attoparsec.Text.Internal (Parser, Result, parse, takeWhile1)
131import Data.Bits (Bits, (.|.), shiftL)
139import Data.Bits (Bits, (.|.), shiftL)
132import Data.Char (isAlpha, isDigit, isSpace, ord)
140import Data.Char (isAlpha, isDigit, isSpace, ord)
133import Data.Int (Int8, Int16, Int32, Int64)
141import Data.Int (Int8, Int16, Int32, Int64)
142+import Data.List (intercalate)
134import Data.Text (Text)
143import Data.Text (Text)
135−import Data.Word (Word8, Word16, Word32, Word64, Word)
144+import Data.Word (Word8, Word16, Word32, Word64)
136import qualified Data.Attoparsec.Internal as I
145import qualified Data.Attoparsec.Internal as I
137import qualified Data.Attoparsec.Internal.Types as T
146import qualified Data.Attoparsec.Internal.Types as T
138import qualified Data.Attoparsec.Text.Internal as I
147import qualified Data.Attoparsec.Text.Internal as I
@@ -257,9 +266,10 @@ maybeResult _ = Nothing
257-- | Convert a 'Result' value to an 'Either' value. A 'Partial' result
266-- | Convert a 'Result' value to an 'Either' value. A 'Partial' result
258-- is treated as failure.
267-- is treated as failure.
259eitherResult :: Result r -> Either String r
268eitherResult :: Result r -> Either String r
260−eitherResult (T.Done _ r) = Right r
269+eitherResult (T.Done _ r) = Right r
261−eitherResult (T.Fail _ _ msg) = Left msg
270+eitherResult (T.Fail _ [] msg) = Left msg
271+eitherResult (T.Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
262−eitherResult _ = Left "Result: incomplete input"
272+eitherResult _ = Left "Result: incomplete input"
263
273
264-- | A predicate that matches either a carriage return @\'\\r\'@ or
274-- | A predicate that matches either a carriage return @\'\\r\'@ or
265-- newline @\'\\n\'@ character.
275-- newline @\'\\n\'@ character.
Data/Attoparsec/Text/Buffer.hs+20−8
@@ -1,9 +1,9 @@
1−{-# LANGUAGE BangPatterns, MagicHash, RankNTypes, RecordWildCards,
1+{-# LANGUAGE BangPatterns, CPP, MagicHash, RankNTypes, RecordWildCards,
2 UnboxedTuples #-}
2 UnboxedTuples #-}
3
3
4-- |
4-- |
5-- Module : Data.Attoparsec.Text.Buffer
5-- Module : Data.Attoparsec.Text.Buffer
6−-- Copyright : Bryan O'Sullivan 2007-2014
6+-- Copyright : Bryan O'Sullivan 2007-2015
7-- License : BSD3
7-- License : BSD3
8--
8--
9-- Maintainer : bos@serpentine.com
9-- Maintainer : bos@serpentine.com
@@ -28,6 +28,7 @@ module Data.Attoparsec.Text.Buffer
28 Buffer
28 Buffer
29 , buffer
29 , buffer
30 , unbuffer
30 , unbuffer
31+ , unbufferAt
31 , length
32 , length
32 , pappend
33 , pappend
33 , iter
34 , iter
@@ -39,7 +40,8 @@ module Data.Attoparsec.Text.Buffer
39import Control.Exception (assert)
40import Control.Exception (assert)
40import Data.Bits (shiftR)
41import Data.Bits (shiftR)
41import Data.List (foldl1')
42import Data.List (foldl1')
42−import Data.Monoid (Monoid(..))
43+import Data.Monoid as Mon (Monoid(..))
44+import Data.Semigroup (Semigroup(..))
43import Data.Text ()
45import Data.Text ()
44import Data.Text.Internal (Text(..))
46import Data.Text.Internal (Text(..))
45import Data.Text.Internal.Encoding.Utf16 (chr2)
47import Data.Text.Internal.Encoding.Utf16 (chr2)
@@ -72,15 +74,25 @@ buffer (Text arr off len) = Buf arr off len len 0
72unbuffer :: Buffer -> Text
74unbuffer :: Buffer -> Text
73unbuffer (Buf arr off len _ _) = Text arr off len
75unbuffer (Buf arr off len _ _) = Text arr off len
74
76
77+unbufferAt :: Int -> Buffer -> Text
78+unbufferAt s (Buf arr off len _ _) =
79+ assert (s >= 0 && s <= len) $
80+ Text arr (off+s) (len-s)
81+
82+instance Semigroup Buffer where
83+ (Buf _ _ _ 0 _) <> b = b
84+ a <> (Buf _ _ _ 0 _) = a
85+ buf <> (Buf arr off len _ _) = append buf arr off len
86+ {-# INLINE (<>) #-}
87+
75instance Monoid Buffer where
88instance Monoid Buffer where
76 mempty = Buf A.empty 0 0 0 0
89 mempty = Buf A.empty 0 0 0 0
90+ {-# INLINE mempty #-}
77
91
78− mappend (Buf _ _ _ 0 _) b = b
92+ mappend = (<>)
79− mappend a (Buf _ _ _ 0 _) = a
80− mappend buf (Buf arr off len _ _) = append buf arr off len
81
93
82− mconcat [] = mempty
94+ mconcat [] = Mon.mempty
83− mconcat xs = foldl1' mappend xs
95+ mconcat xs = foldl1' (<>) xs
84
96
85pappend :: Buffer -> Text -> Buffer
97pappend :: Buffer -> Text -> Buffer
86pappend (Buf _ _ _ 0 _) t = buffer t
98pappend (Buf _ _ _ 0 _) t = buffer t
Data/Attoparsec/Text/FastSet.hs+4−2
@@ -3,7 +3,7 @@
3------------------------------------------------------------------------------
3------------------------------------------------------------------------------
4-- |
4-- |
5-- Module : Data.Attoparsec.FastSet
5-- Module : Data.Attoparsec.FastSet
6−-- Copyright : Felipe Lessa 2010, Bryan O'Sullivan 2007-2014
6+-- Copyright : Felipe Lessa 2010, Bryan O'Sullivan 2007-2015
7-- License : BSD3
7-- License : BSD3
8--
8--
9-- Maintainer : felipe.lessa@gmail.com
9-- Maintainer : felipe.lessa@gmail.com
@@ -62,7 +62,9 @@ resolveCollisions (a:b:entries) = a' : resolveCollisions (b' : entries)
62
62
63pad :: Int -> [Entry] -> [Entry]
63pad :: Int -> [Entry] -> [Entry]
64pad = go 0
64pad = go 0
65+ where -- ensure that we pad enough so that lookups beyond the
66+ -- last hash in the table fall within the array
65− where go !_ !m [] = replicate (max 1 m) empty
67+ go !_ !m [] = replicate (max 1 m + 1) empty
66 go k m (e:entries) = map (const empty) [k..i - 1] ++ e :
68 go k m (e:entries) = map (const empty) [k..i - 1] ++ e :
67 go (i + 1) (m + i - k - 1) entries
69 go (i + 1) (m + i - k - 1) entries
68 where i = index e
70 where i = index e
Data/Attoparsec/Text/Internal.hs+87−45
@@ -3,7 +3,7 @@
3{-# OPTIONS_GHC -fno-warn-orphans #-}
3{-# OPTIONS_GHC -fno-warn-orphans #-}
4-- |
4-- |
5-- Module : Data.Attoparsec.Text.Internal
5-- Module : Data.Attoparsec.Text.Internal
6−-- Copyright : Bryan O'Sullivan 2007-2014
6+-- Copyright : Bryan O'Sullivan 2007-2015
7-- License : BSD3
7-- License : BSD3
8--
8--
9-- Maintainer : bos@serpentine.com
9-- Maintainer : bos@serpentine.com
@@ -65,17 +65,18 @@ module Data.Attoparsec.Text.Internal
65 , atEnd
65 , atEnd
66 ) where
66 ) where
67
67
68−import Control.Applicative ((<|>), (<$>))
68+import Control.Applicative ((<|>), (<$>), pure, (*>))
69import Control.Monad (when)
69import Control.Monad (when)
70import Data.Attoparsec.Combinator ((<?>))
70import Data.Attoparsec.Combinator ((<?>))
71import Data.Attoparsec.Internal
71import Data.Attoparsec.Internal
72import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
72import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
73import qualified Data.Attoparsec.Text.Buffer as Buf
73import qualified Data.Attoparsec.Text.Buffer as Buf
74import Data.Attoparsec.Text.Buffer (Buffer, buffer)
74import Data.Attoparsec.Text.Buffer (Buffer, buffer)
75−import Data.Char (chr, ord)
75+import Data.Char (isAsciiUpper, isAsciiLower, toUpper, toLower)
76+import Data.List (intercalate)
76import Data.String (IsString(..))
77import Data.String (IsString(..))
77import Data.Text.Internal (Text(..))
78import Data.Text.Internal (Text(..))
78−import Prelude hiding (getChar, length, succ, take, takeWhile)
79+import Prelude hiding (getChar, succ, take, takeWhile)
79import qualified Data.Attoparsec.Internal.Types as T
80import qualified Data.Attoparsec.Internal.Types as T
80import qualified Data.Attoparsec.Text.FastSet as Set
81import qualified Data.Attoparsec.Text.FastSet as Set
81import qualified Data.Text as T
82import qualified Data.Text as T
@@ -159,9 +160,46 @@ take n = takeWith (max n 0) (const True)
159-- before failing. In attoparsec, the above parser will /succeed/ on
160-- before failing. In attoparsec, the above parser will /succeed/ on
160-- that input, because the failed first branch will consume nothing.
161-- that input, because the failed first branch will consume nothing.
161string :: Text -> Parser Text
162string :: Text -> Parser Text
162−string s = takeWith (T.length s) (==s)
163+string s = string_ (stringSuspended id) id s
163{-# INLINE string #-}
164{-# INLINE string #-}
164
165
166+string_ :: (forall r. Text -> Text -> Buffer -> Pos -> More
167+ -> Failure r -> Success Text r -> Result r)
168+ -> (Text -> Text)
169+ -> Text -> Parser Text
170+string_ suspended f s0 = T.Parser $ \t pos more lose succ ->
171+ let s = f s0
172+ ft = f (Buf.unbufferAt (fromPos pos) t)
173+ in case T.commonPrefixes s ft of
174+ Nothing
175+ | T.null s -> succ t pos more T.empty
176+ | T.null ft -> suspended s s t pos more lose succ
177+ | otherwise -> lose t pos more [] "string"
178+ Just (pfx,ssfx,tsfx)
179+ | T.null ssfx -> let l = Pos (T.lengthWord16 pfx)
180+ in succ t (pos + l) more (substring pos l t)
181+ | not (T.null tsfx) -> lose t pos more [] "string"
182+ | otherwise -> suspended s ssfx t pos more lose succ
183+{-# INLINE string_ #-}
184+
185+stringSuspended :: (Text -> Text)
186+ -> Text -> Text -> Buffer -> Pos -> More
187+ -> Failure r
188+ -> Success Text r
189+ -> Result r
190+stringSuspended f s000 s0 t0 pos0 more0 lose0 succ0 =
191+ runParser (demandInput_ >>= go) t0 pos0 more0 lose0 succ0
192+ where
193+ go s' = T.Parser $ \t pos more lose succ ->
194+ let s = f s'
195+ in case T.commonPrefixes s0 s of
196+ Nothing -> lose t pos more [] "string"
197+ Just (_pfx,ssfx,tsfx)
198+ | T.null ssfx -> let l = Pos (T.lengthWord16 s000)
199+ in succ t (pos + l) more (substring pos l t)
200+ | T.null tsfx -> stringSuspended f s000 ssfx t pos more lose succ
201+ | otherwise -> lose t pos more [] "string"
202+
165-- | Satisfy a literal string, ignoring case.
203-- | Satisfy a literal string, ignoring case.
166--
204--
167-- Note: this function is currently quite inefficient. Unicode case
205-- Note: this function is currently quite inefficient. Unicode case
@@ -184,30 +222,23 @@ stringCI s = go 0
184
222
185-- | Satisfy a literal string, ignoring case for characters in the ASCII range.
223-- | Satisfy a literal string, ignoring case for characters in the ASCII range.
186asciiCI :: Text -> Parser Text
224asciiCI :: Text -> Parser Text
187−asciiCI input = do
225+asciiCI s = fmap fst $ match $ T.foldr ((*>) . asciiCharCI) (pure ()) s
188− (k,t) <- ensure n
189− if asciiToLower t == s
190− then advance k >> return t
191− else fail "asciiCI"
192− where
193− n = T.length input
194− s = asciiToLower input
195−
196− -- convert letters in the ASCII range to lower-case
197− asciiToLower = T.map f
198− where
199− offset = ord 'a' - ord 'A'
200− f c | 'A' <= c && c <= 'Z' = chr (ord c + offset)
201− | otherwise = c
202{-# INLINE asciiCI #-}
226{-# INLINE asciiCI #-}
203
227
228+asciiCharCI :: Char -> Parser Char
229+asciiCharCI c
230+ | isAsciiUpper c = char c <|> char (toLower c)
231+ | isAsciiLower c = char c <|> char (toUpper c)
232+ | otherwise = char c
233+{-# INLINE asciiCharCI #-}
234+
204-- | Skip past input for as long as the predicate returns 'True'.
235-- | Skip past input for as long as the predicate returns 'True'.
205skipWhile :: (Char -> Bool) -> Parser ()
236skipWhile :: (Char -> Bool) -> Parser ()
206skipWhile p = go
237skipWhile p = go
207 where
238 where
208 go = do
239 go = do
209 t <- T.takeWhile p <$> get
240 t <- T.takeWhile p <$> get
210− continue <- inputSpansChunks (length t)
241+ continue <- inputSpansChunks (size t)
211 when continue go
242 when continue go
212{-# INLINE skipWhile #-}
243{-# INLINE skipWhile #-}
213
244
@@ -236,14 +267,25 @@ takeTill p = takeWhile (not . p)
236-- parsers loop until a failure occurs. Careless use will thus result
267-- parsers loop until a failure occurs. Careless use will thus result
237-- in an infinite loop.
268-- in an infinite loop.
238takeWhile :: (Char -> Bool) -> Parser Text
269takeWhile :: (Char -> Bool) -> Parser Text
239−takeWhile p = (T.concat . reverse) `fmap` go []
270+takeWhile p = do
271+ h <- T.takeWhile p <$> get
272+ continue <- inputSpansChunks (size h)
273+ -- only use slow concat path if necessary
274+ if continue
275+ then takeWhileAcc p [h]
276+ else return h
277+{-# INLINE takeWhile #-}
278+
279+takeWhileAcc :: (Char -> Bool) -> [Text] -> Parser Text
280+takeWhileAcc p = go
240 where
281 where
241 go acc = do
282 go acc = do
242 h <- T.takeWhile p <$> get
283 h <- T.takeWhile p <$> get
243− continue <- inputSpansChunks (length h)
284+ continue <- inputSpansChunks (size h)
244 if continue
285 if continue
245 then go (h:acc)
286 then go (h:acc)
246− else return (h:acc)
287+ else return $ concatReverse (h:acc)
288+{-# INLINE takeWhileAcc #-}
247
289
248takeRest :: Parser [Text]
290takeRest :: Parser [Text]
249takeRest = go []
291takeRest = go []
@@ -253,7 +295,7 @@ takeRest = go []
253 if input
295 if input
254 then do
296 then do
255 s <- get
297 s <- get
256− advance (length s)
298+ advance (size s)
257 go (s:acc)
299 go (s:acc)
258 else return (reverse acc)
300 else return (reverse acc)
259
301
@@ -266,7 +308,7 @@ takeLazyText :: Parser L.Text
266takeLazyText = L.fromChunks `fmap` takeRest
308takeLazyText = L.fromChunks `fmap` takeRest
267
309
268data Scan s = Continue s
310data Scan s = Continue s
269− | Finished {-# UNPACK #-} !Int Text
311+ | Finished s {-# UNPACK #-} !Int Text
270
312
271scan_ :: (s -> [Text] -> Parser r) -> s -> (s -> Char -> Maybe s) -> Parser r
313scan_ :: (s -> [Text] -> Parser r) -> s -> (s -> Char -> Maybe s) -> Parser r
272scan_ f s0 p = go [] s0
314scan_ f s0 p = go [] s0
@@ -275,17 +317,17 @@ scan_ f s0 p = go [] s0
275 case T.uncons t of
317 case T.uncons t of
276 Just (c,t') -> case p s c of
318 Just (c,t') -> case p s c of
277 Just s' -> scanner s' (n+1) t'
319 Just s' -> scanner s' (n+1) t'
278− Nothing -> Finished n t
320+ Nothing -> Finished s n t
279 Nothing -> Continue s
321 Nothing -> Continue s
280 go acc s = do
322 go acc s = do
281 input <- get
323 input <- get
282 case scanner s 0 input of
324 case scanner s 0 input of
283− Continue s' -> do continue <- inputSpansChunks (length input)
325+ Continue s' -> do continue <- inputSpansChunks (size input)
284 if continue
326 if continue
285 then go (input : acc) s'
327 then go (input : acc) s'
286 else f s' (input : acc)
328 else f s' (input : acc)
287− Finished n t -> do advance (length input - length t)
329+ Finished s' n t -> do advance (size input - size t)
288− f s (T.take n input : acc)
330+ f s' (T.take n input : acc)
289{-# INLINE scan_ #-}
331{-# INLINE scan_ #-}
290
332
291-- | A stateful scanner. The predicate consumes and transforms a
333-- | A stateful scanner. The predicate consumes and transforms a
@@ -301,16 +343,13 @@ scan_ f s0 p = go [] s0
301-- parsers loop until a failure occurs. Careless use will thus result
343-- parsers loop until a failure occurs. Careless use will thus result
302-- in an infinite loop.
344-- in an infinite loop.
303scan :: s -> (s -> Char -> Maybe s) -> Parser Text
345scan :: s -> (s -> Char -> Maybe s) -> Parser Text
304−scan = scan_ $ \_ chunks ->
346+scan = scan_ $ \_ chunks -> return $! concatReverse chunks
305− case chunks of
306− [x] -> return x
307− xs -> return . T.concat . reverse $ xs
308{-# INLINE scan #-}
347{-# INLINE scan #-}
309
348
310-- | Like 'scan', but generalized to return the final state of the
349-- | Like 'scan', but generalized to return the final state of the
311-- scanner.
350-- scanner.
312runScanner :: s -> (s -> Char -> Maybe s) -> Parser (Text, s)
351runScanner :: s -> (s -> Char -> Maybe s) -> Parser (Text, s)
313−runScanner = scan_ $ \s xs -> return (T.concat (reverse xs), s)
352+runScanner = scan_ $ \s xs -> let !sx = concatReverse xs in return (sx, s)
314{-# INLINE runScanner #-}
353{-# INLINE runScanner #-}
315
354
316-- | Consume input as long as the predicate returns 'True', and return
355-- | Consume input as long as the predicate returns 'True', and return
@@ -323,13 +362,14 @@ takeWhile1 :: (Char -> Bool) -> Parser Text
323takeWhile1 p = do
362takeWhile1 p = do
324 (`when` demandInput) =<< endOfChunk
363 (`when` demandInput) =<< endOfChunk
325 h <- T.takeWhile p <$> get
364 h <- T.takeWhile p <$> get
326− let len = length h
365+ let size' = size h
327− when (len == 0) $ fail "takeWhile1"
366+ when (size' == 0) $ fail "takeWhile1"
328− advance len
367+ advance size'
329 eoc <- endOfChunk
368 eoc <- endOfChunk
330 if eoc
369 if eoc
331− then (h<>) `fmap` takeWhile p
370+ then takeWhileAcc p [h]
332 else return h
371 else return h
372+{-# INLINE takeWhile1 #-}
333
373
334-- | Match any character in a set.
374-- | Match any character in a set.
335--
375--
@@ -429,9 +469,10 @@ parse m s = runParser m (buffer s) 0 Incomplete failK successK
429-- @
469-- @
430parseOnly :: Parser a -> Text -> Either String a
470parseOnly :: Parser a -> Text -> Either String a
431parseOnly m s = case runParser m (buffer s) 0 Complete failK successK of
471parseOnly m s = case runParser m (buffer s) 0 Complete failK successK of
472+ Fail _ [] err -> Left err
432− Fail _ _ err -> Left err
473+ Fail _ ctxs err -> Left (intercalate " > " ctxs ++ ": " ++ err)
433− Done _ a -> Right a
474+ Done _ a -> Right a
434− _ -> error "parseOnly: impossible error!"
475+ _ -> error "parseOnly: impossible error!"
435{-# INLINE parseOnly #-}
476{-# INLINE parseOnly #-}
436
477
437get :: Parser Text
478get :: Parser Text
@@ -476,7 +517,6 @@ ensure n = T.Parser $ \t pos more lose succ ->
476 Just n' -> succ t pos more (n', substring pos n' t)
517 Just n' -> succ t pos more (n', substring pos n' t)
477 -- The uncommon case is kept out-of-line to reduce code size:
518 -- The uncommon case is kept out-of-line to reduce code size:
478 Nothing -> ensureSuspended n t pos more lose succ
519 Nothing -> ensureSuspended n t pos more lose succ
479−-- Non-recursive so the bounds check can be inlined:
480{-# INLINE ensure #-}
520{-# INLINE ensure #-}
481
521
482-- | Return both the result of a parse and the portion of the input
522-- | Return both the result of a parse and the portion of the input
@@ -487,6 +527,8 @@ match p = T.Parser $ \t pos more lose succ ->
487 (substring pos (pos'-pos) t', a)
527 (substring pos (pos'-pos) t', a)
488 in runParser p t pos more lose succ'
528 in runParser p t pos more lose succ'
489
529
530+-- | Ensure that at least @n@ code points of input are available.
531+-- Returns the number of words consumed while traversing.
490lengthAtLeast :: Pos -> Int -> Buffer -> Maybe Pos
532lengthAtLeast :: Pos -> Int -> Buffer -> Maybe Pos
491lengthAtLeast pos n t = go 0 (fromPos pos)
533lengthAtLeast pos n t = go 0 (fromPos pos)
492 where go i !p
534 where go i !p
@@ -503,5 +545,5 @@ substring (Pos pos) (Pos n) = Buf.substring pos n
503lengthOf :: Buffer -> Pos
545lengthOf :: Buffer -> Pos
504lengthOf = Pos . Buf.length
546lengthOf = Pos . Buf.length
505
547
506−length :: Text -> Pos
548+size :: Text -> Pos
507−length (Text _ _ l) = Pos l
549+size (Text _ _ l) = Pos l
Data/Attoparsec/Text/Lazy.hs+13−10
@@ -1,7 +1,12 @@
1+{-# LANGUAGE CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4+#endif
5+
1{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
6{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
2-- |
7-- |
3-- Module : Data.Attoparsec.Text.Lazy
8-- Module : Data.Attoparsec.Text.Lazy
4−-- Copyright : Bryan O'Sullivan 2007-2014
9+-- Copyright : Bryan O'Sullivan 2007-2015
5-- License : BSD3
10-- License : BSD3
6--
11--
7-- Maintainer : bos@serpentine.com
12-- Maintainer : bos@serpentine.com
@@ -36,6 +41,7 @@ module Data.Attoparsec.Text.Lazy
36 ) where
41 ) where
37
42
38import Control.DeepSeq (NFData(rnf))
43import Control.DeepSeq (NFData(rnf))
44+import Data.List (intercalate)
39import Data.Text.Lazy.Internal (Text(..), chunk)
45import Data.Text.Lazy.Internal (Text(..), chunk)
40import qualified Data.Attoparsec.Internal.Types as T
46import qualified Data.Attoparsec.Internal.Types as T
41import qualified Data.Attoparsec.Text as A
47import qualified Data.Attoparsec.Text as A
@@ -45,20 +51,16 @@ import Data.Attoparsec.Text hiding (IResult(..), Result, eitherResult,
45
51
46-- | The result of a parse.
52-- | The result of a parse.
47data Result r = Fail Text [String] String
53data Result r = Fail Text [String] String
48− -- ^ The parse failed. The 'ByteString' is the input
54+ -- ^ The parse failed. The 'Text' is the input
49 -- that had not yet been consumed when the failure
55 -- that had not yet been consumed when the failure
50 -- occurred. The @[@'String'@]@ is a list of contexts
56 -- occurred. The @[@'String'@]@ is a list of contexts
51 -- in which the error occurred. The 'String' is the
57 -- in which the error occurred. The 'String' is the
52 -- message describing the error, if any.
58 -- message describing the error, if any.
53 | Done Text r
59 | Done Text r
54− -- ^ The parse succeeded. The 'ByteString' is the
60+ -- ^ The parse succeeded. The 'Text' is the
55 -- input that had not yet been consumed (if any) when
61 -- input that had not yet been consumed (if any) when
56 -- the parse succeeded.
62 -- the parse succeeded.
57−
63+ deriving (Show)
58−instance Show r => Show (Result r) where
59− show (Fail bs stk msg) =
60− "Fail " ++ show bs ++ " " ++ show stk ++ " " ++ show msg
61− show (Done bs r) = "Done " ++ show bs ++ " " ++ show r
62
64
63instance NFData r => NFData (Result r) where
65instance NFData r => NFData (Result r) where
64 rnf (Fail bs ctxs msg) = rnf bs `seq` rnf ctxs `seq` rnf msg
66 rnf (Fail bs ctxs msg) = rnf bs `seq` rnf ctxs `seq` rnf msg
@@ -94,5 +96,6 @@ maybeResult _ = Nothing
94
96
95-- | Convert a 'Result' value to an 'Either' value.
97-- | Convert a 'Result' value to an 'Either' value.
96eitherResult :: Result r -> Either String r
98eitherResult :: Result r -> Either String r
97−eitherResult (Done _ r) = Right r
99+eitherResult (Done _ r) = Right r
98−eitherResult (Fail _ _ msg) = Left msg
100+eitherResult (Fail _ [] msg) = Left msg
101+eitherResult (Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
Data/Attoparsec/Types.hs+1−1
@@ -1,6 +1,6 @@
1-- |
1-- |
2-- Module : Data.Attoparsec.Types
2-- Module : Data.Attoparsec.Types
3−-- Copyright : Bryan O'Sullivan 2007-2014
3+-- Copyright : Bryan O'Sullivan 2007-2015
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
Data/Attoparsec/Zepto.hs+86−44
@@ -1,8 +1,12 @@
1−{-# LANGUAGE BangPatterns, MagicHash, UnboxedTuples #-}
1+{-# LANGUAGE CPP #-}
2+#if __GLASGOW_HASKELL__ >= 702
3+{-# LANGUAGE Trustworthy #-} -- Data.ByteString.Unsafe
4+#endif
5+{-# LANGUAGE BangPatterns #-}
2
6
3-- |
7-- |
4-- Module : Data.Attoparsec.Zepto
8-- Module : Data.Attoparsec.Zepto
5−-- Copyright : Bryan O'Sullivan 2007-2014
9+-- Copyright : Bryan O'Sullivan 2007-2015
6-- License : BSD3
10-- License : BSD3
7--
11--
8-- Maintainer : bos@serpentine.com
12-- Maintainer : bos@serpentine.com
@@ -23,101 +27,139 @@
23module Data.Attoparsec.Zepto
27module Data.Attoparsec.Zepto
24 (
28 (
25 Parser
29 Parser
30+ , ZeptoT
26 , parse
31 , parse
32+ , parseT
27 , atEnd
33 , atEnd
28 , string
34 , string
29 , take
35 , take
30 , takeWhile
36 , takeWhile
31 ) where
37 ) where
32
38
33−import Data.Word (Word8)
34import Control.Applicative
39import Control.Applicative
35−import Control.Monad
40+import Control.Monad (MonadPlus(..), ap)
36−import Data.Monoid
37−import qualified Data.ByteString as B
41+import qualified Control.Monad.Fail as Fail
38−import qualified Data.ByteString.Unsafe as B
42+import Control.Monad.IO.Class (MonadIO(..))
39import Data.ByteString (ByteString)
43import Data.ByteString (ByteString)
44+import Data.Functor.Identity (Identity(runIdentity))
45+import Data.Monoid as Mon (Monoid(..))
46+import Data.Semigroup (Semigroup(..))
47+import Data.Word (Word8)
40import Prelude hiding (take, takeWhile)
48import Prelude hiding (take, takeWhile)
49+import qualified Data.ByteString as B
50+import qualified Data.ByteString.Unsafe as B
41
51
42newtype S = S {
52newtype S = S {
43 input :: ByteString
53 input :: ByteString
44 }
54 }
45
55
46data Result a = Fail String
56data Result a = Fail String
47− | OK !a
57+ | OK !a S
48
58
49-- | A simple parser.
59-- | A simple parser.
50--
60--
51-- This monad is strict in its state, and the monadic bind operator
61-- This monad is strict in its state, and the monadic bind operator
52-- ('>>=') evaluates each result to weak head normal form before
62-- ('>>=') evaluates each result to weak head normal form before
53-- passing it along.
63-- passing it along.
54−newtype Parser a = Parser {
64+newtype ZeptoT m a = Parser {
55− runParser :: S -> (# Result a, S #)
65+ runParser :: S -> m (Result a)
56 }
66 }
57
67
68+type Parser a = ZeptoT Identity a
69+
58−instance Functor Parser where
70+instance Monad m => Functor (ZeptoT m) where
59− fmap f m = Parser $ \s -> case runParser m s of
71+ fmap f m = Parser $ \s -> do
72+ result <- runParser m s
73+ case result of
60− (# OK a, s' #) -> (# OK (f a), s' #)
74+ OK a s' -> return (OK (f a) s')
61− (# Fail err, s' #) -> (# Fail err, s' #)
75+ Fail err -> return (Fail err)
62 {-# INLINE fmap #-}
76 {-# INLINE fmap #-}
63
77
64−instance Monad Parser where
78+instance MonadIO m => MonadIO (ZeptoT m) where
65− return a = Parser $ \s -> (# OK a, s #)
79+ liftIO act = Parser $ \s -> do
80+ result <- liftIO act
81+ return (OK result s)
82+ {-# INLINE liftIO #-}
83+
84+instance Monad m => Monad (ZeptoT m) where
85+ return = pure
66 {-# INLINE return #-}
86 {-# INLINE return #-}
67
87
68− m >>= k = Parser $ \s -> case runParser m s of
88+ m >>= k = Parser $ \s -> do
89+ result <- runParser m s
90+ case result of
69− (# OK a, s' #) -> runParser (k a) s'
91+ OK a s' -> runParser (k a) s'
70− (# Fail err, s' #) -> (# Fail err, s' #)
92+ Fail err -> return (Fail err)
71 {-# INLINE (>>=) #-}
93 {-# INLINE (>>=) #-}
72
94
95+#if !(MIN_VERSION_base(4,13,0))
73− fail msg = Parser $ \s -> (# Fail msg, s #)
96+ fail = Fail.fail
97+ {-# INLINE fail #-}
98+#endif
74
99
75−instance MonadPlus Parser where
100+instance Monad m => Fail.MonadFail (ZeptoT m) where
101+ fail msg = Parser $ \_ -> return (Fail msg)
102+ {-# INLINE fail #-}
103+
104+instance Monad m => MonadPlus (ZeptoT m) where
76 mzero = fail "mzero"
105 mzero = fail "mzero"
77 {-# INLINE mzero #-}
106 {-# INLINE mzero #-}
78
107
79− mplus a b = Parser $ \s ->
108+ mplus a b = Parser $ \s -> do
80− case runParser a s of
109+ result <- runParser a s
110+ case result of
81− (# ok@(OK _), s' #) -> (# ok, s' #)
111+ ok@(OK _ _) -> return ok
82− (# _, _ #) -> case runParser b s of
112+ _ -> runParser b s
83− (# ok@(OK _), s'' #) -> (# ok, s'' #)
84− (# err, s'' #) -> (# err, s'' #)
85 {-# INLINE mplus #-}
113 {-# INLINE mplus #-}
86
114
87−instance Applicative Parser where
115+instance (Monad m) => Applicative (ZeptoT m) where
88− pure = return
116+ pure a = Parser $ \s -> return (OK a s)
89 {-# INLINE pure #-}
117 {-# INLINE pure #-}
90 (<*>) = ap
118 (<*>) = ap
91 {-# INLINE (<*>) #-}
119 {-# INLINE (<*>) #-}
92
120
93−gets :: (S -> a) -> Parser a
121+gets :: Monad m => (S -> a) -> ZeptoT m a
94−gets f = Parser $ \s -> (# OK (f s), s #)
122+gets f = Parser $ \s -> return (OK (f s) s)
95{-# INLINE gets #-}
123{-# INLINE gets #-}
96
124
97−put :: S -> Parser ()
125+put :: Monad m => S -> ZeptoT m ()
98−put s = Parser $ \_ -> (# OK (), s #)
126+put s = Parser $ \_ -> return (OK () s)
99{-# INLINE put #-}
127{-# INLINE put #-}
100
128
101-- | Run a parser.
129-- | Run a parser.
102parse :: Parser a -> ByteString -> Either String a
130parse :: Parser a -> ByteString -> Either String a
103−parse p bs = case runParser p (S bs) of
131+parse p bs = case runIdentity (runParser p (S bs)) of
104− (# OK a, _ #) -> Right a
132+ (OK a _) -> Right a
105− (# Fail err, _ #) -> Left err
133+ (Fail err) -> Left err
106−
134+{-# INLINE parse #-}
107−instance Monoid (Parser a) where
135+
136+-- | Run a parser on top of the given base monad.
137+parseT :: Monad m => ZeptoT m a -> ByteString -> m (Either String a)
138+parseT p bs = do
139+ result <- runParser p (S bs)
140+ case result of
141+ OK a _ -> return (Right a)
142+ Fail err -> return (Left err)
143+{-# INLINE parseT #-}
144+
145+instance Monad m => Semigroup (ZeptoT m a) where
146+ (<>) = mplus
147+ {-# INLINE (<>) #-}
148+
149+instance Monad m => Mon.Monoid (ZeptoT m a) where
108 mempty = fail "mempty"
150 mempty = fail "mempty"
109 {-# INLINE mempty #-}
151 {-# INLINE mempty #-}
110− mappend = mplus
152+ mappend = (<>)
111 {-# INLINE mappend #-}
153 {-# INLINE mappend #-}
112
154
113−instance Alternative Parser where
155+instance Monad m => Alternative (ZeptoT m) where
114 empty = fail "empty"
156 empty = fail "empty"
115 {-# INLINE empty #-}
157 {-# INLINE empty #-}
116 (<|>) = mplus
158 (<|>) = mplus
117 {-# INLINE (<|>) #-}
159 {-# INLINE (<|>) #-}
118
160
119-- | Consume input while the predicate returns 'True'.
161-- | Consume input while the predicate returns 'True'.
120−takeWhile :: (Word8 -> Bool) -> Parser ByteString
162+takeWhile :: Monad m => (Word8 -> Bool) -> ZeptoT m ByteString
121takeWhile p = do
163takeWhile p = do
122 (h,t) <- gets (B.span p . input)
164 (h,t) <- gets (B.span p . input)
123 put (S t)
165 put (S t)
@@ -125,7 +167,7 @@ takeWhile p = do
125{-# INLINE takeWhile #-}
167{-# INLINE takeWhile #-}
126
168
127-- | Consume @n@ bytes of input.
169-- | Consume @n@ bytes of input.
128−take :: Int -> Parser ByteString
170+take :: Monad m => Int -> ZeptoT m ByteString
129take !n = do
171take !n = do
130 s <- gets input
172 s <- gets input
131 if B.length s >= n
173 if B.length s >= n
@@ -134,7 +176,7 @@ take !n = do
134{-# INLINE take #-}
176{-# INLINE take #-}
135
177
136-- | Match a string exactly.
178-- | Match a string exactly.
137−string :: ByteString -> Parser ()
179+string :: Monad m => ByteString -> ZeptoT m ()
138string s = do
180string s = do
139 i <- gets input
181 i <- gets input
140 if s `B.isPrefixOf` i
182 if s `B.isPrefixOf` i
@@ -143,7 +185,7 @@ string s = do
143{-# INLINE string #-}
185{-# INLINE string #-}
144
186
145-- | Indicate whether the end of the input has been reached.
187-- | Indicate whether the end of the input has been reached.
146−atEnd :: Parser Bool
188+atEnd :: Monad m => ZeptoT m Bool
147atEnd = do
189atEnd = do
148 i <- gets input
190 i <- gets input
149 return $! B.null i
191 return $! B.null i
README.markdown−6
@@ -16,12 +16,6 @@ Master [git repository](https://github.com/bos/attoparsec):
16
16
17* `git clone git://github.com/bos/attoparsec.git`
17* `git clone git://github.com/bos/attoparsec.git`
18
18
19−There's also a [Mercurial mirror](https://bitbucket.org/bos/attoparsec):
20−
21−* `hg clone https://bitbucket.org/bos/attoparsec`
22−
23−(You can create and contribute changes using either Mercurial or git.)
24−
25Authors
19Authors
26-------
20-------
27
21
attoparsec.cabal+59−15
@@ -1,12 +1,13 @@
1name: attoparsec
1name: attoparsec
2−version: 0.12.1.2
2+version: 0.13.2.2
3+x-revision: 3
3license: BSD3
4license: BSD3
4license-file: LICENSE
5license-file: LICENSE
5category: Text, Parsing
6category: Text, Parsing
6author: Bryan O'Sullivan <bos@serpentine.com>
7author: Bryan O'Sullivan <bos@serpentine.com>
7−maintainer: Bryan O'Sullivan <bos@serpentine.com>
8+maintainer: Bryan O'Sullivan <bos@serpentine.com>, Ben Gamari <ben@smart-cactus.org>
8stability: experimental
9stability: experimental
9−tested-with: GHC == 7.0, GHC == 7.2, GHC == 7.4, GHC == 7.6, GHC == 7.8
10+tested-with: GHC == 7.0.4, GHC == 7.2.2, GHC == 7.4.2, GHC ==7.6.3, GHC ==7.8.4, GHC ==7.10.3, GHC ==8.0.2, GHC ==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.1
10synopsis: Fast combinator parsing for bytestrings and text
11synopsis: Fast combinator parsing for bytestrings and text
11cabal-version: >= 1.8
12cabal-version: >= 1.8
12homepage: https://github.com/bos/attoparsec
13homepage: https://github.com/bos/attoparsec
@@ -21,6 +22,7 @@ extra-source-files:
21 benchmarks/*.cabal
22 benchmarks/*.cabal
22 benchmarks/*.hs
23 benchmarks/*.hs
23 benchmarks/*.txt
24 benchmarks/*.txt
25+ benchmarks/json-data/*.json
24 benchmarks/Makefile
26 benchmarks/Makefile
25 benchmarks/med.txt.bz2
27 benchmarks/med.txt.bz2
26 changelog.md
28 changelog.md
@@ -28,9 +30,8 @@ extra-source-files:
28 examples/*.hs
30 examples/*.hs
29 examples/Makefile
31 examples/Makefile
30 tests/*.hs
32 tests/*.hs
31− tests/Makefile
32 tests/QC/*.hs
33 tests/QC/*.hs
33− tests/TestFastSet.hs
34+ tests/QC/IPv6/*.hs
34
35
35Flag developer
36Flag developer
36 Description: Whether to build the library in development mode
37 Description: Whether to build the library in development mode
@@ -39,22 +40,30 @@ Flag developer
39
40
40library
41library
41 build-depends: array,
42 build-depends: array,
42− base >= 4.2 && < 5,
43+ base >= 4.3 && < 5,
43 bytestring,
44 bytestring,
44 containers,
45 containers,
45 deepseq,
46 deepseq,
46 scientific >= 0.3.1 && < 0.4,
47 scientific >= 0.3.1 && < 0.4,
48+ transformers >= 0.2 && (< 0.4 || >= 0.4.1.0) && < 0.6,
47 text >= 1.1.1.3
49 text >= 1.1.1.3
48 if impl(ghc < 7.4)
50 if impl(ghc < 7.4)
49 build-depends:
51 build-depends:
50 bytestring < 0.10.4.0
52 bytestring < 0.10.4.0
51
53
54+ if !impl(ghc >= 8.0)
55+ -- Data.Semigroup && Control.Monad.Fail are available in base-4.9+
56+ build-depends: fail == 4.9.*,
57+ semigroups >=0.16.1 && <0.20
58+
52 exposed-modules: Data.Attoparsec
59 exposed-modules: Data.Attoparsec
53 Data.Attoparsec.ByteString
60 Data.Attoparsec.ByteString
54 Data.Attoparsec.ByteString.Char8
61 Data.Attoparsec.ByteString.Char8
55 Data.Attoparsec.ByteString.Lazy
62 Data.Attoparsec.ByteString.Lazy
56 Data.Attoparsec.Char8
63 Data.Attoparsec.Char8
57 Data.Attoparsec.Combinator
64 Data.Attoparsec.Combinator
65+ Data.Attoparsec.Internal
66+ Data.Attoparsec.Internal.Types
58 Data.Attoparsec.Lazy
67 Data.Attoparsec.Lazy
59 Data.Attoparsec.Number
68 Data.Attoparsec.Number
60 Data.Attoparsec.Text
69 Data.Attoparsec.Text
@@ -64,9 +73,7 @@ library
64 other-modules: Data.Attoparsec.ByteString.Buffer
73 other-modules: Data.Attoparsec.ByteString.Buffer
65 Data.Attoparsec.ByteString.FastSet
74 Data.Attoparsec.ByteString.FastSet
66 Data.Attoparsec.ByteString.Internal
75 Data.Attoparsec.ByteString.Internal
67− Data.Attoparsec.Internal
68 Data.Attoparsec.Internal.Fhthagn
76 Data.Attoparsec.Internal.Fhthagn
69− Data.Attoparsec.Internal.Types
70 Data.Attoparsec.Text.Buffer
77 Data.Attoparsec.Text.Buffer
71 Data.Attoparsec.Text.FastSet
78 Data.Attoparsec.Text.FastSet
72 Data.Attoparsec.Text.Internal
79 Data.Attoparsec.Text.Internal
@@ -84,9 +91,31 @@ test-suite tests
84 QC.ByteString
91 QC.ByteString
85 QC.Combinator
92 QC.Combinator
86 QC.Common
93 QC.Common
94+ QC.IPv6.Internal
95+ QC.IPv6.Types
87 QC.Rechunked
96 QC.Rechunked
88 QC.Simple
97 QC.Simple
89 QC.Text
98 QC.Text
99+ QC.Text.FastSet
100+ QC.Text.Regressions
101+
102+ other-modules: Data.Attoparsec.ByteString
103+ Data.Attoparsec.ByteString.Buffer
104+ Data.Attoparsec.ByteString.Char8
105+ Data.Attoparsec.ByteString.FastSet
106+ Data.Attoparsec.ByteString.Internal
107+ Data.Attoparsec.ByteString.Lazy
108+ Data.Attoparsec.Combinator
109+ Data.Attoparsec.Internal
110+ Data.Attoparsec.Internal.Fhthagn
111+ Data.Attoparsec.Internal.Types
112+ Data.Attoparsec.Number
113+ Data.Attoparsec.Text
114+ Data.Attoparsec.Text.Buffer
115+ Data.Attoparsec.Text.FastSet
116+ Data.Attoparsec.Text.Internal
117+ Data.Attoparsec.Text.Lazy
118+ Data.Attoparsec.Zepto
90
119
91 ghc-options:
120 ghc-options:
92 -Wall -threaded -rtsopts
121 -Wall -threaded -rtsopts
@@ -96,16 +125,23 @@ test-suite tests
96
125
97 build-depends:
126 build-depends:
98 array,
127 array,
99− base >= 4 && < 5,
128+ base,
100 bytestring,
129 bytestring,
101 deepseq >= 1.1,
130 deepseq >= 1.1,
102− QuickCheck >= 2.7,
131+ QuickCheck >= 2.13.2 && < 2.14,
132+ quickcheck-unicode,
103 scientific,
133 scientific,
104− test-framework >= 0.8.0.2,
134+ tasty >= 0.11,
105− test-framework-quickcheck2 >= 0.3.0.3,
135+ tasty-quickcheck >= 0.8,
106 text,
136 text,
137+ transformers >= 0.2 && (< 0.4 || >= 0.4.1.0) && < 0.6,
107 vector
138 vector
108
139
140+ if !impl(ghc >= 8.0)
141+ -- Data.Semigroup && Control.Monad.Fail are available in base-4.9+
142+ build-depends: fail == 4.9.*,
143+ semigroups >=0.16.1 && <0.20
144+
109benchmark benchmarks
145benchmark benchmarks
110 type: exitcode-stdio-1.0
146 type: exitcode-stdio-1.0
111 hs-source-dirs: benchmarks benchmarks/warp-3.0.1.1 .
147 hs-source-dirs: benchmarks benchmarks/warp-3.0.1.1 .
@@ -117,7 +153,11 @@ benchmark benchmarks
117 HeadersByteString.Atto
153 HeadersByteString.Atto
118 HeadersText
154 HeadersText
119 Links
155 Links
156+ Network.Wai.Handler.Warp.ReadInt
157+ Network.Wai.Handler.Warp.RequestHeader
120 Numbers
158 Numbers
159+ Sets
160+ TextFastSet
121 Warp
161 Warp
122 ghc-options: -O2 -Wall
162 ghc-options: -O2 -Wall
123
163
@@ -129,6 +169,7 @@ benchmark benchmarks
129 base == 4.*,
169 base == 4.*,
130 bytestring >= 0.10.4.0,
170 bytestring >= 0.10.4.0,
131 case-insensitive,
171 case-insensitive,
172+ containers,
132 criterion >= 1.0,
173 criterion >= 1.0,
133 deepseq >= 1.1,
174 deepseq >= 1.1,
134 directory,
175 directory,
@@ -138,13 +179,16 @@ benchmark benchmarks
138 parsec >= 3.1.2,
179 parsec >= 3.1.2,
139 scientific,
180 scientific,
140 text >= 1.1.1.0,
181 text >= 1.1.1.0,
182+ transformers >= 0.2 && (< 0.4 || >= 0.4.1.0) && < 0.6,
141 unordered-containers,
183 unordered-containers,
142 vector
184 vector
143
185
186+ if !impl(ghc >= 8.0)
187+ -- Data.Semigroup && Control.Monad.Fail are available in base-4.9+
188+ build-depends: fail == 4.9.*,
189+ semigroups >=0.16.1 && <0.19
190+
144source-repository head
191source-repository head
145 type: git
192 type: git
146 location: https://github.com/bos/attoparsec
193 location: https://github.com/bos/attoparsec
147
194
148−source-repository head
149− type: mercurial
150− location: https://bitbucket.org/bos/attoparsec
benchmarks/Aeson.hs+6−2
@@ -10,8 +10,13 @@ module Aeson
10import Data.ByteString.Builder
10import Data.ByteString.Builder
11 (Builder, byteString, toLazyByteString, charUtf8, word8)
11 (Builder, byteString, toLazyByteString, charUtf8, word8)
12
12
13+#if !MIN_VERSION_base(4,8,0)
14+import Control.Applicative ((*>), (<$>), (<*), pure)
15+import Data.Monoid (mappend, mempty)
16+#endif
17+
13import Common (pathTo)
18import Common (pathTo)
14−import Control.Applicative ((*>), (<$>), (<*), liftA2, pure)
19+import Control.Applicative (liftA2)
15import Control.DeepSeq (NFData(..))
20import Control.DeepSeq (NFData(..))
16import Control.Monad (forM)
21import Control.Monad (forM)
17import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,
22import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,
@@ -20,7 +25,6 @@ import Data.Bits ((.|.), shiftL)
20import Data.ByteString (ByteString)
25import Data.ByteString (ByteString)
21import Data.Char (chr)
26import Data.Char (chr)
22import Data.List (sort)
27import Data.List (sort)
23−import Data.Monoid (mappend, mempty)
24import Data.Scientific (Scientific)
28import Data.Scientific (Scientific)
25import Data.Text (Text)
29import Data.Text (Text)
26import Data.Text.Encoding (decodeUtf8')
30import Data.Text.Encoding (decodeUtf8')
benchmarks/Benchmarks.hs+8
@@ -13,6 +13,7 @@ import Text.Parsec.Text ()
13import Text.Parsec.Text.Lazy ()
13import Text.Parsec.Text.Lazy ()
14import qualified Warp
14import qualified Warp
15import qualified Aeson
15import qualified Aeson
16+import qualified Genome
16import qualified Data.Attoparsec.ByteString as AB
17import qualified Data.Attoparsec.ByteString as AB
17import qualified Data.Attoparsec.ByteString.Char8 as AC
18import qualified Data.Attoparsec.ByteString.Char8 as AC
18import qualified Data.Attoparsec.ByteString.Lazy as ABL
19import qualified Data.Attoparsec.ByteString.Lazy as ABL
@@ -27,6 +28,7 @@ import qualified HeadersByteString
27import qualified HeadersText
28import qualified HeadersText
28import qualified Links
29import qualified Links
29import qualified Text.Parsec as P
30import qualified Text.Parsec as P
31+import qualified Sets
30
32
31main :: IO ()
33main :: IO ()
32main = do
34main = do
@@ -69,10 +71,14 @@ main = do
69 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile AC.isAlpha_ascii)) bl
71 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile AC.isAlpha_ascii)) bl
70 , bench "isAlpha_iso8859_15" $
72 , bench "isAlpha_iso8859_15" $
71 nf (ABL.parse (AC.takeWhile AC.isAlpha_iso8859_15)) bl
73 nf (ABL.parse (AC.takeWhile AC.isAlpha_iso8859_15)) bl
74+ , bench "T isAlpha" $ nf (AT.parse (AT.takeWhile isAlpha)) t
75+ , bench "TL isAlpha" $ nf (ATL.parse (AT.takeWhile isAlpha)) tl
72 ]
76 ]
73 , bgroup "takeWhile1" [
77 , bgroup "takeWhile1" [
74 bench "isAlpha" $ nf (ABL.parse (AC.takeWhile1 isAlpha)) bl
78 bench "isAlpha" $ nf (ABL.parse (AC.takeWhile1 isAlpha)) bl
75 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile1 AC.isAlpha_ascii)) bl
79 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile1 AC.isAlpha_ascii)) bl
80+ , bench "T isAlpha" $ nf (AT.parse (AT.takeWhile1 isAlpha)) t
81+ , bench "TL isAlpha" $ nf (ATL.parse (AT.takeWhile1 isAlpha)) tl
76 ]
82 ]
77 , bench "word32LE" $ nf (AB.parse word32LE) b
83 , bench "word32LE" $ nf (AB.parse word32LE) b
78 , bgroup "scan" [
84 , bgroup "scan" [
@@ -80,10 +86,12 @@ main = do
80 , bench "long" $ nf (AB.parse quotedString) b
86 , bench "long" $ nf (AB.parse quotedString) b
81 ]
87 ]
82 , aeson
88 , aeson
89+ , Genome.genome
83 , headersBS
90 , headersBS
84 , headersT
91 , headersT
85 , Links.links
92 , Links.links
86 , numbers
93 , numbers
94+ , Sets.benchmarks
87 , Warp.benchmarks
95 , Warp.benchmarks
88 ]
96 ]
89
97
benchmarks/Genome.hsnew file+75
@@ -0,0 +1,75 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module Genome
4+ (
5+ genome
6+ ) where
7+
8+import Control.Applicative
9+import Criterion.Main
10+import Data.ByteString (ByteString)
11+import qualified Data.ByteString.Char8 as B8
12+import qualified Data.ByteString.Lazy.Char8 as L8
13+import Data.Attoparsec.ByteString.Char8 as B
14+import qualified Data.Attoparsec.ByteString.Lazy as BL
15+import Data.Text (Text)
16+import qualified Data.Text as T
17+import qualified Data.Text.Lazy as TL
18+import Data.Attoparsec.Text as T
19+import qualified Data.Attoparsec.Text.Lazy as TL
20+import Common (rechunkBS, rechunkT)
21+
22+genome :: Benchmark
23+genome = bgroup "genome" [
24+ bgroup "bytestring" [
25+ bench "s" $ nf (map (B.parse searchBS)) (B8.tails geneB)
26+ , bench "l" $ nf (map (BL.parse searchBS)) (L8.tails geneBL)
27+ , bgroup "CI" [
28+ bench "s" $ nf (map (B.parse searchBSCI)) (B8.tails geneB)
29+ , bench "l" $ nf (map (BL.parse searchBSCI)) (L8.tails geneBL)
30+ ]
31+ ]
32+ , bgroup "text" [
33+ bench "s" $ nf (map (T.parse searchT)) (T.tails geneT)
34+ , bench "l" $ nf (map (TL.parse searchT)) (TL.tails geneTL)
35+ , bgroup "CI" [
36+ bench "s" $ nf (map (T.parse searchTCI)) (T.tails geneT)
37+ , bench "l" $ nf (map (TL.parse searchTCI)) (TL.tails geneTL)
38+ ]
39+ ]
40+ ]
41+ where geneB = B8.pack gene
42+ geneBL = rechunkBS 4 geneB
43+ geneT = T.pack gene
44+ geneTL = rechunkT 4 geneT
45+
46+searchBS :: B.Parser ByteString
47+searchBS = "caac" *> ("aaca" <|> "aact")
48+
49+searchBSCI :: B.Parser ByteString
50+searchBSCI = B.stringCI "CAAC" *> (B.stringCI "AACA" <|> B.stringCI "AACT")
51+
52+searchT :: T.Parser Text
53+searchT = "caac" *> ("aaca" <|> "aact")
54+
55+searchTCI :: T.Parser Text
56+searchTCI = T.asciiCI "CAAC" *> (T.asciiCI "AACA" <|> T.asciiCI "AACT")
57+
58+-- Dictyostelium discoideum developmental protein DG1094 (gacT) gene,
59+-- partial cds. http://www.ncbi.nlm.nih.gov/nuccore/AF081586.1
60+
61+gene :: String
62+gene = "atcgatttagaaagatacaaagatagaaccatcaataataaacaagagaagagagcaagt\
63+ \agagatattaataaagagattgaaagagagattgaaaagaagagattatcaccaagagaa\
64+ \agattaaatttatttggtctttcttcctcatcttcatcagtgaattcaacattaacaaga\
65+ \tctacagcaaatattatctctacaatagacggtagtggaggtagtaatcgtaatagtaaa\
66+ \aattatggtaatggctcatcctcctcctcaaatagaagatatagtaatactattaatcaa\
67+ \caattacaaatgcaattacaacaacttcaaatccaacaacaacaatatcaacaaactcaa\
68+ \caatctcaaataccattacaatatcaacaacaacaacagcaacaacaacaacaaaccact\
69+ \acaactacaactacatcaagtggtagtaatagattctcttcaaatagatataaaccagtt\
70+ \gatcttacacaatcatcttcaaactttcgttattcacgtgaaatttatgatgatgattat\
71+ \tattcaaataataatttaatgatgtttggtaatgagcaaccaaatcaaacaccaatttct\
72+ \gtatcatcttcatctgcattcacacgtcaaagatctcaaagttgctttgaaccagagaat\
73+ \cttgtattgctacaacaacaatatcaacaatatcaacaacaacaacaacaacaacaacaa\
74+ \attccattccaagcaaatccacaatatagtaatgctgttattgaacaaaaattggatcaa\
75+ \attagagataccattaataatttacatagagataaccgagtctctaga"
benchmarks/HeadersByteString.hs+10−1
@@ -9,6 +9,15 @@ import qualified Data.Attoparsec.ByteString.Char8 as B
9import qualified Data.Attoparsec.ByteString.Lazy as BL
9import qualified Data.Attoparsec.ByteString.Lazy as BL
10import qualified Data.ByteString.Char8 as B
10import qualified Data.ByteString.Char8 as B
11
11
12+-- Note: In the benchmarks for parsing an http request
13+-- from a strict bytestring, we consider warp's implementation,
14+-- which has highly optimized code for handling the first
15+-- line in particular. It's treatment of the headers
16+-- is more relaxed from the treatment they are given by the
17+-- attoparsec parser benchmarked here. Consequently, it
18+-- is should not be possible to match its performance since
19+-- it accepts header names with disallowed characters.
20+
12headers :: IO Benchmark
21headers :: IO Benchmark
13headers = do
22headers = do
14 req <- B.readFile =<< pathTo "http-request.txt"
23 req <- B.readFile =<< pathTo "http-request.txt"
@@ -18,7 +27,7 @@ headers = do
18 return $ bgroup "headers" [
27 return $ bgroup "headers" [
19 bgroup "B" [
28 bgroup "B" [
20 bench "request" $ nf (B.parseOnly request) req
29 bench "request" $ nf (B.parseOnly request) req
21− , bench "warp" $ nfIO (parseHeaderLines [req])
30+ , bench "warp" $ nfIO (parseHeaderLines (B.split '\n' req))
22 , bench "response" $ nf (B.parseOnly response) resp
31 , bench "response" $ nf (B.parseOnly response) resp
23 ]
32 ]
24 , bgroup "BL" [
33 , bgroup "BL" [
benchmarks/HeadersByteString/Atto.hs+9−1
@@ -15,8 +15,16 @@ import qualified Data.ByteString.Char8 as B
15instance NFData HttpVersion where
15instance NFData HttpVersion where
16 rnf !_ = ()
16 rnf !_ = ()
17
17
18+isHeaderChar :: Char -> Bool
19+isHeaderChar c =
20+ (c >= 'a' && c <= 'z') ||
21+ (c >= 'A' && c <= 'Z') ||
22+ (c >= '0' && c <= '9') ||
23+ (c == '_') ||
24+ (c == '-')
25+
18header = do
26header = do
19− name <- B.takeWhile1 (B.inClass "a-zA-Z0-9_-") <* B.char ':' <* B.skipSpace
27+ name <- B.takeWhile1 isHeaderChar <* B.char ':' <* B.skipSpace
20 body <- bodyLine
28 body <- bodyLine
21 return (name, body)
29 return (name, body)
22
30
benchmarks/Links.hs+2−2
@@ -5,8 +5,8 @@ module Links (links) where
5import Control.Applicative
5import Control.Applicative
6import Control.DeepSeq (NFData(..))
6import Control.DeepSeq (NFData(..))
7import Criterion.Main (Benchmark, bench, nf)
7import Criterion.Main (Benchmark, bench, nf)
8−import Data.Attoparsec as A
8+import Data.Attoparsec.ByteString as A
9−import Data.Attoparsec.Char8 as A8
9+import Data.Attoparsec.ByteString.Char8 as A8
10import Data.ByteString.Char8 as B8
10import Data.ByteString.Char8 as B8
11
11
12data Link = Link {
12data Link = Link {
benchmarks/Numbers.hs+1
@@ -1,4 +1,5 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2+{-# OPTIONS_GHC -fno-warn-deprecations #-}
2
3
3module Numbers (numbers) where
4module Numbers (numbers) where
4
5
benchmarks/Sets.hsnew file+21
@@ -0,0 +1,21 @@
1+module Sets (benchmarks) where
2+
3+import Criterion
4+import Data.Char (ord)
5+import qualified Data.Attoparsec.Text.FastSet as FastSet
6+import qualified TextFastSet
7+import qualified Data.HashSet as HashSet
8+import qualified Data.IntSet as IntSet
9+
10+smallSet :: String
11+smallSet = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']
12+
13+benchmarks :: Benchmark
14+benchmarks = bgroup "sets" [
15+ bench "Fast" $ whnf (FastSet.member '*') (FastSet.fromList smallSet)
16+ , bench "Hash" $ whnf (HashSet.member '*') (HashSet.fromList smallSet)
17+ , bench "Int" $ whnf (IntSet.member (ord '*'))
18+ (IntSet.fromList (map ord smallSet))
19+ , bench "TextFast" $ whnf (TextFastSet.member '*')
20+ (TextFastSet.fromList smallSet)
21+ ]
benchmarks/TextFastSet.hsnew file+121
@@ -0,0 +1,121 @@
1+{-# LANGUAGE BangPatterns #-}
2+
3+------------------------------------------------------------------------------
4+-- |
5+-- Module : Data.Attoparsec.FastSet
6+-- Copyright : Felipe Lessa 2010, Bryan O'Sullivan 2007-2015
7+-- License : BSD3
8+--
9+-- Maintainer : felipe.lessa@gmail.com
10+-- Stability : experimental
11+-- Portability : unknown
12+--
13+-- Fast set membership tests for 'Char' values. We test for
14+-- membership using a hashtable implemented with Robin Hood
15+-- collision resolution. The set representation is unboxed,
16+-- and the characters and hashes interleaved, for efficiency.
17+--
18+--
19+-----------------------------------------------------------------------------
20+module TextFastSet
21+ (
22+ -- * Data type
23+ FastSet
24+ -- * Construction
25+ , fromList
26+ , set
27+ -- * Lookup
28+ , member
29+ -- * Handy interface
30+ , charClass
31+ ) where
32+
33+import Data.Bits ((.|.), (.&.), shiftR)
34+import Data.Function (on)
35+import Data.List (sort, sortBy)
36+import qualified Data.Array.Base as AB
37+import qualified Data.Array.Unboxed as A
38+import qualified Data.Text as T
39+
40+data FastSet = FastSet {
41+ table :: {-# UNPACK #-} !(A.UArray Int Int)
42+ , mask :: {-# UNPACK #-} !Int
43+ }
44+
45+data Entry = Entry {
46+ key :: {-# UNPACK #-} !Char
47+ , initialIndex :: {-# UNPACK #-} !Int
48+ , index :: {-# UNPACK #-} !Int
49+ }
50+
51+offset :: Entry -> Int
52+offset e = index e - initialIndex e
53+
54+resolveCollisions :: [Entry] -> [Entry]
55+resolveCollisions [] = []
56+resolveCollisions [e] = [e]
57+resolveCollisions (a:b:entries) = a' : resolveCollisions (b' : entries)
58+ where (a', b')
59+ | index a < index b = (a, b)
60+ | offset a < offset b = (b { index=index a }, a { index=index a + 1 })
61+ | otherwise = (a, b { index=index a + 1 })
62+
63+pad :: Int -> [Entry] -> [Entry]
64+pad = go 0
65+ where -- ensure that we pad enough so that lookups beyond the
66+ -- last hash in the table fall within the array
67+ go !_ !m [] = replicate (max 1 m + 1) empty
68+ go k m (e:entries) = map (const empty) [k..i - 1] ++ e :
69+ go (i + 1) (m + i - k - 1) entries
70+ where i = index e
71+ empty = Entry '\0' maxBound 0
72+
73+nextPowerOf2 :: Int -> Int
74+nextPowerOf2 0 = 1
75+nextPowerOf2 x = go (x - 1) 1
76+ where go y 32 = y + 1
77+ go y k = go (y .|. (y `shiftR` k)) $ k * 2
78+
79+fastHash :: Char -> Int
80+fastHash = fromEnum
81+
82+fromList :: String -> FastSet
83+fromList s = FastSet (AB.listArray (0, length interleaved - 1) interleaved)
84+ mask'
85+ where s' = ordNub (sort s)
86+ l = length s'
87+ mask' = nextPowerOf2 ((5 * l) `div` 4) - 1
88+ entries = pad mask' .
89+ resolveCollisions .
90+ sortBy (compare `on` initialIndex) .
91+ zipWith (\c i -> Entry c i i) s' .
92+ map ((.&. mask') . fastHash) $ s'
93+ interleaved = concatMap (\e -> [fromEnum $ key e, initialIndex e])
94+ entries
95+
96+ordNub :: Eq a => [a] -> [a]
97+ordNub [] = []
98+ordNub (y:ys) = go y ys
99+ where go x (z:zs)
100+ | x == z = go x zs
101+ | otherwise = x : go z zs
102+ go x [] = [x]
103+
104+set :: T.Text -> FastSet
105+set = fromList . T.unpack
106+
107+-- | Check the set for membership.
108+member :: Char -> FastSet -> Bool
109+member c a = go (2 * i)
110+ where i = fastHash c .&. mask a
111+ lookupAt j b = (i' <= i) && (c == c' || b)
112+ where c' = toEnum $ AB.unsafeAt (table a) j
113+ i' = AB.unsafeAt (table a) $ j + 1
114+ go j = lookupAt j . lookupAt (j + 2) . lookupAt (j + 4) .
115+ lookupAt (j + 6) . go $ j + 8
116+
117+charClass :: String -> FastSet
118+charClass = fromList . go
119+ where go (a:'-':b:xs) = [a..b] ++ go xs
120+ go (x:xs) = x : go xs
121+ go _ = ""
benchmarks/attoparsec-benchmarks.cabal+4
@@ -16,6 +16,8 @@ executable attoparsec-benchmarks
16 Links
16 Links
17 Numbers
17 Numbers
18 Network.Wai.Handler.Warp.ReadInt
18 Network.Wai.Handler.Warp.ReadInt
19+ Sets
20+ TextFastSet
19 Warp
21 Warp
20 hs-source-dirs: .. . warp-3.0.1.1
22 hs-source-dirs: .. . warp-3.0.1.1
21 ghc-options: -O2 -Wall -rtsopts
23 ghc-options: -O2 -Wall -rtsopts
@@ -24,6 +26,7 @@ executable attoparsec-benchmarks
24 base == 4.*,
26 base == 4.*,
25 bytestring >= 0.10.4.0,
27 bytestring >= 0.10.4.0,
26 case-insensitive,
28 case-insensitive,
29+ containers,
27 criterion >= 1.0,
30 criterion >= 1.0,
28 deepseq >= 1.1,
31 deepseq >= 1.1,
29 directory,
32 directory,
@@ -33,5 +36,6 @@ executable attoparsec-benchmarks
33 parsec >= 3.1.2,
36 parsec >= 3.1.2,
34 scientific >= 0.3.1,
37 scientific >= 0.3.1,
35 text >= 1.1.1.0,
38 text >= 1.1.1.0,
39+ transformers,
36 unordered-containers,
40 unordered-containers,
37 vector
41 vector
benchmarks/http-request.txt+3−8
@@ -1,14 +1,9 @@
1GET / HTTP/1.1
1GET / HTTP/1.1
2Host: twitter.com
2Host: twitter.com
3−Accept: text/html, application/xhtml+xml, application/xml; q=0.9,
3+Accept: text/html, application/xhtml+xml, application/xml; q=0.9, image/webp, */*; q=0.8
4− image/webp, */*; q=0.8
5Accept-Encoding: gzip,deflate,sdch
4Accept-Encoding: gzip,deflate,sdch
6Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
5Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
7Cache-Control: max-age=0
6Cache-Control: max-age=0
8−Cookie: guest_id=v1%3A139; _twitter_sess=BAh7CSIKZmxhc2hJQz-e1e1;
7+Cookie: guest_id=v1%3A139; _twitter_sess=BAh7CSIKZmxhc2hJQz-e1e1; __utma=43838368.452555194.1399611824.1; __utmb=43838368; __utmc=43838368; __utmz=1399611824.1.1.utmcsr=(direct)|utmcmd=(none)
9− __utma=43838368.452555194.1399611824.1; __utmb=43838368;
10− __utmc=43838368; __utmz=1399611824.1.1.utmcsr=(direct)|utmcmd=(none)
11−User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5)
12− AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.86
8+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.86 Safari/537.36
13− Safari/537.36
14
9
benchmarks/http-response.txt+2−4
@@ -3,10 +3,8 @@ Date: Fri, 09 May 2014 04:24:57 GMT
3Expires: -1
3Expires: -1
4Cache-Control: private, max-age=0
4Cache-Control: private, max-age=0
5Content-Type: text/html; charset=ISO-8859-1
5Content-Type: text/html; charset=ISO-8859-1
6−Set-Cookie: PREF=ID=1a871afc4240db5e:FF:LM=1399609497:S=e5MZ0itEekjVwNcU;
6+Set-Cookie: PREF=ID=1a871afc4240db5e:FF:LM=1399609497:S=e5MZ0itEekjVwNcU; expires=Sun, 08-May-2016 04:24:57 GMT; path=/; domain=.google.com
7− expires=Sun, 08-May-2016 04:24:57 GMT; path=/; domain=.google.com
8−Set-Cookie: NID=67=-WlfaDG6VSEPk7abAjrK98HBSoCD2ID6JKkUR95tEumzDmg7Fc8pSQ8;
7+Set-Cookie: NID=67=-WlfaDG6VSEPk7abAjrK98HBSoCD2ID6JKkUR95tEumzDmg7Fc8pSQ8; expires=Sat, 08-Nov-2014 04:24:57 GMT; path=/; domain=.google.com; HttpOnly
9− expires=Sat, 08-Nov-2014 04:24:57 GMT; path=/; domain=.google.com; HttpOnly
10P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."
8P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."
11Server: gws
9Server: gws
12X-XSS-Protection: 1; mode=block
10X-XSS-Protection: 1; mode=block
benchmarks/warp-3.0.1.1/Network/Wai/Handler/Warp/RequestHeader.hs+1−1
@@ -139,7 +139,7 @@ parseRequestLine requestLine@(PS fptr off len) = withForeignPtr fptr $ \ptr -> d
139
139
140parseHeader :: ByteString -> H.Header
140parseHeader :: ByteString -> H.Header
141parseHeader s =
141parseHeader s =
142− let (k, rest) = S.breakByte 58 s -- ':'
142+ let (k, rest) = S.break (== 58) s -- ':'
143 rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
143 rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
144 in (CI.mk k, rest')
144 in (CI.mk k, rest')
145
145
changelog.md+54
@@ -1,3 +1,57 @@
1+0.13.2.1
2+
3+* Improved performance of `Data.Attoparsec.Text.asciiCI`
4+
5+0.13.2.0
6+
7+* `pure` is now strict in `Position`
8+
9+0.13.1.0
10+
11+* `runScanner` now correctly returns the final state
12+ (https://github.com/bos/attoparsec/issues/105).
13+* `Parser`, `ZeptoT`, `Buffer`, and `More` now expose `Semigroup` instances.
14+* `Parser`, and `ZeptoT` now expose `MonadFail` instances.
15+
16+0.13.0.2
17+
18+* Restore the fast specialised character set implementation for Text
19+* Move testsuite from test-framework to tasty
20+* Performance optimization of takeWhile and takeWhile1
21+
22+0.13.0.1
23+
24+* Fixed a bug in the implementations of inClass and notInClass for
25+ Text (https://github.com/bos/attoparsec/issues/103)
26+
27+0.13.0.0
28+
29+* Made the parser type in the Zepto module a monad transformer
30+ (needed by aeson's string unescaping parser).
31+
32+0.12.1.6
33+
34+* Fixed a case folding bug in the ByteString version of stringCI.
35+
36+0.12.1.5
37+
38+* Fixed an indexing bug in the new Text implementation of string,
39+ reported by Michel Boucey.
40+
41+0.12.1.4
42+
43+* Fixed a case where the string parser would consume an unnecessary
44+ amount of input before failing a match, when it could bail much
45+ earlier (https://github.com/bos/attoparsec/issues/97)
46+
47+* Added more context to error messages
48+ (https://github.com/bos/attoparsec/pull/79)
49+
50+0.12.1.3
51+
52+* Fixed incorrect tracking of Text lengths
53+ (https://github.com/bos/attoparsec/issues/80)
54+
10.12.1.2
550.12.1.2
2
56
3* Fixed the incorrect tracking of capacity if the initial buffer was
57* Fixed the incorrect tracking of capacity if the initial buffer was
tests/Makefiledeleted−14
@@ -1,14 +0,0 @@
1−all: TestFastSet.out qc.out
2−
3−%.out: %.exe
4− ./$< | tee $<.tmp
5− mv $<.tmp $@
6−
7−qc.exe: QC.hs
8− ghc -O -fno-warn-orphans --make -o $@ $<
9−
10−%.exe: %.hs
11− ghc -O -fno-warn-orphans --make -o $@ $<
12−
13−clean:
14− -rm -f *.hi *.o *.exe *.out
tests/QC.hs+2−2
@@ -6,11 +6,11 @@ import qualified QC.ByteString as ByteString
6import qualified QC.Combinator as Combinator
6import qualified QC.Combinator as Combinator
7import qualified QC.Simple as Simple
7import qualified QC.Simple as Simple
8import qualified QC.Text as Text
8import qualified QC.Text as Text
9−import Test.Framework (defaultMain, testGroup)
9+import Test.Tasty (defaultMain, testGroup)
10
10
11main = defaultMain tests
11main = defaultMain tests
12
12
13−tests = [
13+tests = testGroup "tests" [
14 testGroup "bs" ByteString.tests
14 testGroup "bs" ByteString.tests
15 , testGroup "buf" Buffer.tests
15 , testGroup "buf" Buffer.tests
16 , testGroup "combinator" Combinator.tests
16 , testGroup "combinator" Combinator.tests
tests/QC/Buffer.hs+6−4
@@ -1,13 +1,15 @@
1−{-# LANGUAGE BangPatterns, FlexibleInstances, OverloadedStrings,
1+{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, OverloadedStrings,
2 TypeSynonymInstances #-}
2 TypeSynonymInstances #-}
3
3
4module QC.Buffer (tests) where
4module QC.Buffer (tests) where
5
5
6+#if !MIN_VERSION_base(4,8,0)
6import Control.Applicative ((<$>))
7import Control.Applicative ((<$>))
7import Data.Monoid (Monoid(mconcat))
8import Data.Monoid (Monoid(mconcat))
9+#endif
8import QC.Common ()
10import QC.Common ()
9−import Test.Framework (Test)
11+import Test.Tasty (TestTree)
10−import Test.Framework.Providers.QuickCheck2 (testProperty)
12+import Test.Tasty.QuickCheck (testProperty)
11import Test.QuickCheck
13import Test.QuickCheck
12import qualified Data.Attoparsec.ByteString.Buffer as BB
14import qualified Data.Attoparsec.ByteString.Buffer as BB
13import qualified Data.Attoparsec.Text.Buffer as BT
15import qualified Data.Attoparsec.Text.Buffer as BT
@@ -80,7 +82,7 @@ t_dropWord16 (BP _ts t buf) = do
80 i <- choose (0, T.lengthWord16 t)
82 i <- choose (0, T.lengthWord16 t)
81 return $ T.dropWord16 i t === BT.dropWord16 i buf
83 return $ T.dropWord16 i t === BT.dropWord16 i buf
82
84
83−tests :: [Test]
85+tests :: [TestTree]
84tests = [
86tests = [
85 testProperty "b_unbuffer" b_unbuffer
87 testProperty "b_unbuffer" b_unbuffer
86 , testProperty "t_unbuffer" t_unbuffer
88 , testProperty "t_unbuffer" t_unbuffer
tests/QC/ByteString.hs+30−11
@@ -1,20 +1,23 @@
1−{-# LANGUAGE BangPatterns, OverloadedStrings #-}
1+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}
2module QC.ByteString (tests) where
2module QC.ByteString (tests) where
3
3
4+#if !MIN_VERSION_base(4,8,0)
4−import Control.Applicative ((<$>), (<*>))
5+import Control.Applicative ((<*>), (<$>))
6+#endif
5−import Data.Char (chr, ord)
7+import Data.Char (chr, ord, toUpper)
6import Data.Int (Int64)
8import Data.Int (Int64)
7import Data.Word (Word8)
9import Data.Word (Word8)
8import Prelude hiding (take, takeWhile)
10import Prelude hiding (take, takeWhile)
9−import QC.Common (liftOp, parseBS, toStrictBS)
11+import QC.Common (ASCII(..), liftOp, parseBS, toStrictBS)
10−import Test.Framework (Test)
12+import Test.Tasty (TestTree)
11−import Test.Framework.Providers.QuickCheck2 (testProperty)
13+import Test.Tasty.QuickCheck (testProperty)
12import Test.QuickCheck
14import Test.QuickCheck
13import qualified Data.Attoparsec.ByteString as P
15import qualified Data.Attoparsec.ByteString as P
14import qualified Data.Attoparsec.ByteString.Char8 as P8
16import qualified Data.Attoparsec.ByteString.Char8 as P8
15import qualified Data.Attoparsec.ByteString.FastSet as S
17import qualified Data.Attoparsec.ByteString.FastSet as S
16import qualified Data.Attoparsec.ByteString.Lazy as PL
18import qualified Data.Attoparsec.ByteString.Lazy as PL
17import qualified Data.ByteString as B
19import qualified Data.ByteString as B
20+import qualified Data.ByteString.Char8 as B8
18import qualified Data.ByteString.Lazy as L
21import qualified Data.ByteString.Lazy as L
19import qualified Data.ByteString.Lazy.Char8 as L8
22import qualified Data.ByteString.Lazy.Char8 as L8
20
23
@@ -23,9 +26,11 @@ import qualified Data.ByteString.Lazy.Char8 as L8
23satisfy :: Word8 -> L.ByteString -> Property
26satisfy :: Word8 -> L.ByteString -> Property
24satisfy w s = parseBS (P.satisfy (<=w)) (L.cons w s) === Just w
27satisfy w s = parseBS (P.satisfy (<=w)) (L.cons w s) === Just w
25
28
26−satisfyWith :: Char -> L.ByteString -> Property
29+satisfyWith :: Word8 -> L.ByteString -> Property
27−satisfyWith c s = parseBS (P.satisfyWith (chr . fromIntegral) (<=c))
30+satisfyWith w s = parseBS (P.satisfyWith (chr . fromIntegral) (<=c))
28 (L.cons (fromIntegral (ord c)) s) === Just c
31 (L.cons (fromIntegral (ord c)) s) === Just c
32+ where
33+ c = chr (fromIntegral w)
29
34
30word8 :: Word8 -> L.ByteString -> Property
35word8 :: Word8 -> L.ByteString -> Property
31word8 w s = parseBS (P.word8 w) (L.cons w s) === Just w
36word8 w s = parseBS (P.word8 w) (L.cons w s) === Just w
@@ -63,6 +68,18 @@ string :: L.ByteString -> L.ByteString -> Property
63string s t = parseBS (P.string s') (s `L.append` t) === Just s'
68string s t = parseBS (P.string s') (s `L.append` t) === Just s'
64 where s' = toStrictBS s
69 where s' = toStrictBS s
65
70
71+stringCI :: ASCII L.ByteString -> ASCII L.ByteString -> Property
72+stringCI (ASCII s) (ASCII t) =
73+ parseBS (P8.stringCI up) (s `L.append` t) === Just s'
74+ where s' = toStrictBS s
75+ up = B8.map toUpper s'
76+
77+strings :: L.ByteString -> L.ByteString -> L.ByteString -> Property
78+strings s t u =
79+ parseBS (P.string (toStrictBS s) >> P.string t') (L.concat [s,t,u])
80+ === Just t'
81+ where t' = toStrictBS t
82+
66skipWhile :: Word8 -> L.ByteString -> Property
83skipWhile :: Word8 -> L.ByteString -> Property
67skipWhile w s =
84skipWhile w s =
68 let t = L.dropWhile (<=w) s
85 let t = L.dropWhile (<=w) s
@@ -131,16 +148,16 @@ scan :: L.ByteString -> Positive Int64 -> Property
131scan s (Positive k) = parseBS p s === Just (toStrictBS $ L.take k s)
148scan s (Positive k) = parseBS p s === Just (toStrictBS $ L.take k s)
132 where p = P.scan k $ \ n _ ->
149 where p = P.scan k $ \ n _ ->
133 if n > 0 then let !n' = n - 1 in Just n' else Nothing
150 if n > 0 then let !n' = n - 1 in Just n' else Nothing
134−
151+
135members :: [Word8] -> Property
152members :: [Word8] -> Property
136members s = property $ all (`S.memberWord8` set) s
153members s = property $ all (`S.memberWord8` set) s
137 where set = S.fromList s
154 where set = S.fromList s
138−
155+
139nonmembers :: [Word8] -> [Word8] -> Property
156nonmembers :: [Word8] -> [Word8] -> Property
140nonmembers s s' = property . not . any (`S.memberWord8` set) $ filter (not . (`elem` s)) s'
157nonmembers s s' = property . not . any (`S.memberWord8` set) $ filter (not . (`elem` s)) s'
141 where set = S.fromList s
158 where set = S.fromList s
142
159
143−tests :: [Test]
160+tests :: [TestTree]
144tests = [
161tests = [
145 testProperty "anyWord8" anyWord8
162 testProperty "anyWord8" anyWord8
146 , testProperty "endOfInput" endOfInput
163 , testProperty "endOfInput" endOfInput
@@ -154,6 +171,8 @@ tests = [
154 , testProperty "skip" skip
171 , testProperty "skip" skip
155 , testProperty "skipWhile" skipWhile
172 , testProperty "skipWhile" skipWhile
156 , testProperty "string" string
173 , testProperty "string" string
174+ , testProperty "stringCI" stringCI
175+ , testProperty "strings" strings
157 , testProperty "take" take
176 , testProperty "take" take
158 , testProperty "takeByteString" takeByteString
177 , testProperty "takeByteString" takeByteString
159 , testProperty "takeCount" takeCount
178 , testProperty "takeCount" takeCount
tests/QC/Combinator.hs+18−5
@@ -1,10 +1,15 @@
1+{-# LANGUAGE CPP, OverloadedStrings #-}
2+
1module QC.Combinator where
3module QC.Combinator where
2
4
5+#if !MIN_VERSION_base(4,8,0)
3−import Control.Applicative
6+import Control.Applicative ((<*>), (<$>), (<*), (*>))
7+#endif
8+import Data.Maybe (fromJust, isJust)
4import Data.Word (Word8)
9import Data.Word (Word8)
5−import QC.Common (Repack, parseBS, repackBS)
10+import QC.Common (Repack, parseBS, repackBS, toLazyBS)
6−import Test.Framework (Test)
11+import Test.Tasty (TestTree)
7−import Test.Framework.Providers.QuickCheck2 (testProperty)
12+import Test.Tasty.QuickCheck (testProperty)
8import Test.QuickCheck
13import Test.QuickCheck
9import qualified Data.Attoparsec.ByteString.Char8 as P
14import qualified Data.Attoparsec.ByteString.Char8 as P
10import qualified Data.Attoparsec.Combinator as C
15import qualified Data.Attoparsec.Combinator as C
@@ -22,6 +27,13 @@ count (Positive (Small n)) rs s =
22 (length <$> parseBS (C.count n (P.string s)) input) == Just n
27 (length <$> parseBS (C.count n (P.string s)) input) == Just n
23 where input = repackBS rs (B.concat (replicate (n+1) s))
28 where input = repackBS rs (B.concat (replicate (n+1) s))
24
29
30+lookAhead :: NonEmptyList Word8 -> Bool
31+lookAhead (NonEmpty xs) =
32+ let ys = B.pack xs
33+ withLookAheadThenConsume = (\x y -> (x, y)) <$> C.lookAhead (P.string ys) <*> P.string ys
34+ mr = parseBS withLookAheadThenConsume $ toLazyBS ys
35+ in isJust mr && fst (fromJust mr) == snd (fromJust mr)
36+
25match :: Int -> NonNegative Int -> NonNegative Int -> Repack -> Bool
37match :: Int -> NonNegative Int -> NonNegative Int -> Repack -> Bool
26match n (NonNegative x) (NonNegative y) rs =
38match n (NonNegative x) (NonNegative y) rs =
27 parseBS (P.match parser) (repackBS rs input) == Just (input, n)
39 parseBS (P.match parser) (repackBS rs input) == Just (input, n)
@@ -31,9 +43,10 @@ match n (NonNegative x) (NonNegative y) rs =
31 B8.replicate x 'x', B8.pack (show n), B8.replicate y 'y'
43 B8.replicate x 'x', B8.pack (show n), B8.replicate y 'y'
32 ]
44 ]
33
45
34−tests :: [Test]
46+tests :: [TestTree]
35tests = [
47tests = [
36 testProperty "choice" choice
48 testProperty "choice" choice
37 , testProperty "count" count
49 , testProperty "count" count
50+ , testProperty "lookAhead" lookAhead
38 , testProperty "match" match
51 , testProperty "match" match
39 ]
52 ]
tests/QC/Common.hs+34−4
@@ -1,7 +1,9 @@
1+{-# LANGUAGE CPP, FlexibleInstances #-}
1{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
2module QC.Common
3module QC.Common
3 (
4 (
5+ ASCII(..)
4− parseBS
6+ , parseBS
5 , parseT
7 , parseT
6 , toLazyBS
8 , toLazyBS
7 , toStrictBS
9 , toStrictBS
@@ -13,9 +15,12 @@ module QC.Common
13 , liftOp
15 , liftOp
14 ) where
16 ) where
15
17
18+#if !MIN_VERSION_base(4,8,0)
16−import Control.Applicative ((<$>), (<*>))
19+import Control.Applicative ((<*>), (<$>))
20+#endif
17import Data.Char (isAlpha)
21import Data.Char (isAlpha)
18import Test.QuickCheck
22import Test.QuickCheck
23+import Test.QuickCheck.Unicode (shrinkChar, string)
19import qualified Data.ByteString as B
24import qualified Data.ByteString as B
20import qualified Data.ByteString.Lazy as BL
25import qualified Data.ByteString.Lazy as BL
21import qualified Data.Text as T
26import qualified Data.Text as T
@@ -23,6 +28,16 @@ import qualified Data.Text.Lazy as TL
23import qualified Data.Attoparsec.ByteString.Lazy as BL
28import qualified Data.Attoparsec.ByteString.Lazy as BL
24import qualified Data.Attoparsec.Text.Lazy as TL
29import qualified Data.Attoparsec.Text.Lazy as TL
25
30
31+#if !MIN_VERSION_base(4,4,0)
32+-- This should really be a dependency on the random package :-(
33+instance Random Word8 where
34+ randomR = integralRandomR
35+ random = randomR (minBound,maxBound)
36+
37+instance Arbitrary Word8 where
38+ arbitrary = choose (minBound, maxBound)
39+#endif
40+
26parseBS :: BL.Parser r -> BL.ByteString -> Maybe r
41parseBS :: BL.Parser r -> BL.ByteString -> Maybe r
27parseBS p = BL.maybeResult . BL.parse p
42parseBS p = BL.maybeResult . BL.parse p
28
43
@@ -37,9 +52,22 @@ toLazyBS = BL.fromChunks . (:[])
37
52
38instance Arbitrary B.ByteString where
53instance Arbitrary B.ByteString where
39 arbitrary = B.pack <$> arbitrary
54 arbitrary = B.pack <$> arbitrary
55+ shrink = map B.pack . shrink . B.unpack
40
56
41instance Arbitrary BL.ByteString where
57instance Arbitrary BL.ByteString where
42 arbitrary = repackBS <$> arbitrary <*> arbitrary
58 arbitrary = repackBS <$> arbitrary <*> arbitrary
59+ shrink = map BL.pack . shrink . BL.unpack
60+
61+newtype ASCII a = ASCII { fromASCII :: a }
62+ deriving (Eq, Ord, Show)
63+
64+instance Arbitrary (ASCII B.ByteString) where
65+ arbitrary = (ASCII . B.pack) <$> listOf (choose (0,127))
66+ shrink = map (ASCII . B.pack) . shrink . B.unpack . fromASCII
67+
68+instance Arbitrary (ASCII BL.ByteString) where
69+ arbitrary = ASCII <$> (repackBS <$> arbitrary <*> (fromASCII <$> arbitrary))
70+ shrink = map (ASCII . BL.pack) . shrink . BL.unpack . fromASCII
43
71
44type Repack = NonEmptyList (Positive (Small Int))
72type Repack = NonEmptyList (Positive (Small Int))
45
73
@@ -56,10 +84,12 @@ repackBS_ = go . cycle
56 go _ _ = error "unpossible"
84 go _ _ = error "unpossible"
57
85
58instance Arbitrary T.Text where
86instance Arbitrary T.Text where
59− arbitrary = T.pack <$> arbitrary
87+ arbitrary = T.pack <$> string
88+ shrink = map T.pack . shrinkList shrinkChar . T.unpack
60
89
61instance Arbitrary TL.Text where
90instance Arbitrary TL.Text where
62− arbitrary = repackT <$> arbitrary <*> arbitrary
91+ arbitrary = TL.pack <$> string
92+ shrink = map TL.pack . shrinkList shrinkChar . TL.unpack
63
93
64repackT :: Repack -> T.Text -> TL.Text
94repackT :: Repack -> T.Text -> TL.Text
65repackT (NonEmpty bs) =
95repackT (NonEmpty bs) =
tests/QC/IPv6/Internal.hsnew file+316
@@ -0,0 +1,316 @@
1+-- -----------------------------------------------------------------------------
2+
3+-- |
4+-- Module : Text.IPv6Addr
5+-- Copyright : Copyright © Michel Boucey 2011-2015
6+-- License : BSD-Style
7+-- Maintainer : michel.boucey@gmail.com
8+--
9+-- Dealing with IPv6 address text representations, canonization and manipulations.
10+--
11+
12+-- -----------------------------------------------------------------------------
13+
14+{-# LANGUAGE OverloadedStrings #-}
15+
16+module QC.IPv6.Internal where {-
17+ ( expandTokens
18+ , macAddr
19+ , maybeIPv6AddrTokens
20+ , ipv4AddrToIPv6AddrTokens
21+ , ipv6TokensToText
22+ , ipv6TokensToIPv6Addr
23+ , isIPv6Addr
24+ , maybeTokIPv6Addr
25+ , maybeTokPureIPv6Addr
26+ , fromDoubleColon
27+ , fromIPv6Addr
28+ , toDoubleColon
29+ ) where -}
30+
31+import Debug.Trace
32+import Control.Monad (replicateM)
33+import Data.Attoparsec.Text
34+import Data.Char (isDigit,isHexDigit,toLower)
35+import Data.Monoid ((<>))
36+import Control.Applicative ((<|>),(<*))
37+import Data.List (group,isSuffixOf,elemIndex,elemIndices,intersperse)
38+import Data.Word (Word32)
39+import Numeric (showHex)
40+import qualified Data.Text as T
41+import qualified Data.Text.Read as R (decimal)
42+import Data.Maybe (fromJust)
43+
44+import QC.IPv6.Types
45+
46+tok0 = "0"
47+
48+-- | Returns the 'T.Text' of an IPv6 address.
49+fromIPv6Addr :: IPv6Addr -> T.Text
50+fromIPv6Addr (IPv6Addr t) = t
51+
52+-- | Given an arbitrary list of 'IPv6AddrToken', returns the corresponding 'T.Text'.
53+ipv6TokensToText :: [IPv6AddrToken] -> T.Text
54+ipv6TokensToText l = T.concat $ map ipv6TokenToText l
55+
56+-- | Returns the corresponding 'T.Text' of an IPv6 address token.
57+ipv6TokenToText :: IPv6AddrToken -> T.Text
58+ipv6TokenToText (SixteenBit s) = s
59+ipv6TokenToText Colon = ":"
60+ipv6TokenToText DoubleColon = "::"
61+ipv6TokenToText AllZeros = tok0 -- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1)
62+ipv6TokenToText (IPv4Addr a) = a
63+
64+-- | Returns 'True' if a list of 'IPv6AddrToken' constitutes a valid IPv6 Address.
65+isIPv6Addr :: [IPv6AddrToken] -> Bool
66+isIPv6Addr [] = False
67+isIPv6Addr [DoubleColon] = True
68+isIPv6Addr [DoubleColon,SixteenBit tok1] = True
69+isIPv6Addr tks =
70+ diffNext tks && (do
71+ let cdctks = countDoubleColon tks
72+ let lentks = length tks
73+ let lasttk = last tks
74+ let lenconst = (lentks == 15 && cdctks == 0) || (lentks < 15 && cdctks == 1)
75+ firstValidToken tks &&
76+ (case countIPv4Addr tks of
77+ 0 -> case lasttk of
78+ SixteenBit _ -> lenconst
79+ DoubleColon -> lenconst
80+ AllZeros -> lenconst
81+ _ -> False
82+ 1 -> case lasttk of
83+ IPv4Addr _ -> (lentks == 13 && cdctks == 0) || (lentks < 12 && cdctks == 1)
84+ _ -> False
85+ otherwise -> False))
86+ where diffNext [] = False
87+ diffNext [_] = True
88+ diffNext (t:ts) = do
89+ let h = head ts
90+ case t of
91+ SixteenBit _ -> case h of
92+ SixteenBit _ -> False
93+ AllZeros -> False
94+ _ -> diffNext ts
95+ AllZeros -> case h of
96+ SixteenBit _ -> False
97+ AllZeros -> False
98+ _ -> diffNext ts
99+ _ -> diffNext ts
100+ firstValidToken l =
101+ case head l of
102+ SixteenBit _ -> True
103+ DoubleColon -> True
104+ AllZeros -> True
105+ _ -> False
106+ countDoubleColon l = length $ elemIndices DoubleColon l
107+ tok1 = "1"
108+
109+countIPv4Addr = foldr oneMoreIPv4Addr 0
110+ where
111+ oneMoreIPv4Addr t c = case t of
112+ IPv4Addr _ -> c + 1
113+ otherwise -> c
114+
115+-- | This is the main function which returns 'Just' the list of a tokenized IPv6
116+-- address text representation validated against RFC 4291 and canonized
117+-- in conformation with RFC 5952, or 'Nothing'.
118+maybeTokIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]
119+maybeTokIPv6Addr t =
120+ case maybeIPv6AddrTokens t of
121+ Just ltks -> if isIPv6Addr ltks
122+ then Just $ (ipv4AddrReplacement . toDoubleColon . fromDoubleColon) ltks
123+ else Nothing
124+ Nothing -> Nothing
125+ where
126+ ipv4AddrReplacement ltks =
127+ if ipv4AddrRewrite ltks
128+ then init ltks ++ ipv4AddrToIPv6AddrTokens (last ltks)
129+ else ltks
130+
131+-- | Returns 'Just' the list of tokenized pure IPv6 address, always rewriting an
132+-- embedded IPv4 address if present.
133+maybeTokPureIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]
134+maybeTokPureIPv6Addr t = do
135+ ltks <- maybeIPv6AddrTokens t
136+ if isIPv6Addr ltks
137+ then Just $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) ltks
138+ else Nothing
139+ where
140+ ipv4AddrReplacement ltks' = init ltks' ++ ipv4AddrToIPv6AddrTokens (last ltks')
141+
142+-- | Tokenize a 'T.Text' into 'Just' a list of 'IPv6AddrToken', or 'Nothing'.
143+maybeIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]
144+maybeIPv6AddrTokens s =
145+ case readText s of
146+ Done r l -> traceShow (r,l) $ if r==T.empty then Just l else Nothing
147+ Fail {} -> Nothing
148+ where
149+ readText s = feed (parse (many1 $ ipv4Addr <|> sixteenBit <|> doubleColon <|> colon) s) T.empty
150+
151+-- | An embedded IPv4 address have to be rewritten to output a pure IPv6 Address
152+-- text representation in hexadecimal digits. But some well-known prefixed IPv6
153+-- addresses have to keep visible in their text representation the fact that
154+-- they deals with IPv4 to IPv6 transition process (RFC 5952 Section 5):
155+--
156+-- IPv4-compatible IPv6 address like "::1.2.3.4"
157+--
158+-- IPv4-mapped IPv6 address like "::ffff:1.2.3.4"
159+--
160+-- IPv4-translated address like "::ffff:0:1.2.3.4"
161+--
162+-- IPv4-translatable address like "64:ff9b::1.2.3.4"
163+--
164+-- ISATAP address like "fe80::5efe:1.2.3.4"
165+--
166+ipv4AddrRewrite :: [IPv6AddrToken] -> Bool
167+ipv4AddrRewrite tks =
168+ case last tks of
169+ IPv4Addr _ -> do
170+ let itks = init tks
171+ not (itks == [DoubleColon]
172+ || itks == [DoubleColon,SixteenBit tokffff,Colon]
173+ || itks == [DoubleColon,SixteenBit tokffff,Colon,AllZeros,Colon]
174+ || itks == [SixteenBit "64",Colon,SixteenBit "ff9b",DoubleColon]
175+ || [SixteenBit "200",Colon,SixteenBit tok5efe,Colon] `isSuffixOf` itks
176+ || [AllZeros,Colon,SixteenBit tok5efe,Colon] `isSuffixOf` itks
177+ || [DoubleColon,SixteenBit tok5efe,Colon] `isSuffixOf` itks)
178+ _ -> False
179+ where
180+ tokffff = "ffff"
181+ tok5efe = "5efe"
182+
183+-- | Rewrites an embedded 'IPv4Addr' into the corresponding list of pure 'IPv6Addr' tokens.
184+--
185+-- > ipv4AddrToIPv6AddrTokens (IPv4Addr "127.0.0.1") == [SixteenBits "7f0",Colon,SixteenBits "1"]
186+--
187+ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken]
188+ipv4AddrToIPv6AddrTokens t =
189+ case t of
190+ IPv4Addr a -> do
191+ let m = toHex a
192+ [ SixteenBit ((!!) m 0 <> addZero ((!!) m 1))
193+ , Colon
194+ , SixteenBit ((!!) m 2 <> addZero ((!!) m 3)) ]
195+ _ -> [t]
196+ where
197+ toHex a = map (\x -> T.pack $ showHex (read (T.unpack x)::Int) "") $ T.split (=='.') a
198+ addZero d = if T.length d == 1 then tok0 <> d else d
199+
200+expandTokens :: [IPv6AddrToken] -> [IPv6AddrToken]
201+expandTokens = map expandToken
202+ where expandToken (SixteenBit s) = SixteenBit $ T.justifyRight 4 '0' s
203+ expandToken AllZeros = SixteenBit "0000"
204+ expandToken t = t
205+
206+fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]
207+fromDoubleColon tks =
208+ if DoubleColon `notElem` tks
209+ then tks
210+ else do let s = splitAt (fromJust $ elemIndex DoubleColon tks) tks
211+ let fsts = fst s
212+ let snds = if not (null (snd s)) then tail(snd s) else []
213+ let fste = if null fsts then [] else fsts ++ [Colon]
214+ let snde = if null snds then [] else Colon : snds
215+ fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace tks) ++ snde
216+ where
217+ allZerosTokensReplacement x = intersperse Colon (replicate x AllZeros)
218+ quantityOfAllZerosTokenToReplace x =
219+ ntks tks - foldl (\c x -> if (x /= DoubleColon) && (x /= Colon) then c+1 else c) 0 x
220+ where
221+ ntks tks = if countIPv4Addr tks == 1 then 7 else 8
222+
223+toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]
224+toDoubleColon tks =
225+ zerosToDoubleColon tks (zerosRunToReplace $ zerosRunsList tks)
226+ where
227+ zerosToDoubleColon :: [IPv6AddrToken] -> (Int,Int) -> [IPv6AddrToken]
228+ -- No all zeros token, so no double colon replacement...
229+ zerosToDoubleColon ls (_,0) = ls
230+ -- "The symbol '::' MUST NOT be used to shorten just one 16-bit 0 field" (RFC 5952 4.2.2)
231+ zerosToDoubleColon ls (_,1) = ls
232+ zerosToDoubleColon ls (i,l) =
233+ let ls' = filter (/= Colon) ls
234+ in intersperse Colon (Prelude.take i ls') ++ [DoubleColon] ++ intersperse Colon (drop (i+l) ls')
235+ zerosRunToReplace t =
236+ let l = longestLengthZerosRun t
237+ in (firstLongestZerosRunIndex t l,l)
238+ where
239+ firstLongestZerosRunIndex x y = sum . snd . unzip $ Prelude.takeWhile (/=(True,y)) x
240+ longestLengthZerosRun x =
241+ maximum $ map longest x
242+ where longest t = case t of
243+ (True,i) -> i
244+ _ -> 0
245+ zerosRunsList x = map helper $ groupZerosRuns x
246+ where
247+ helper h = (head h == AllZeros, lh) where lh = length h
248+ groupZerosRuns = group . filter (/= Colon)
249+
250+ipv6TokensToIPv6Addr :: [IPv6AddrToken] -> Maybe IPv6Addr
251+ipv6TokensToIPv6Addr l = Just $ IPv6Addr $ ipv6TokensToText l
252+
253+fullSixteenBit :: T.Text -> Maybe IPv6AddrToken
254+fullSixteenBit t =
255+ case parse ipv6AddrFullChunk t of
256+ Done a b -> if a==T.empty then Just $ SixteenBit $ T.pack b else Nothing
257+ _ -> Nothing
258+
259+macAddr :: Parser (Maybe [IPv6AddrToken])
260+macAddr = do
261+ n1 <- count 2 hexaChar <* ":"
262+ n2 <- count 2 hexaChar <* ":"
263+ n3 <- count 2 hexaChar <* ":"
264+ n4 <- count 2 hexaChar <* ":"
265+ n5 <- count 2 hexaChar <* ":"
266+ n6 <- count 2 hexaChar
267+ return $ maybeIPv6AddrTokens $ T.pack $ concat [n1,n2,n3,n4,n5,n6]
268+
269+sixteenBit :: Parser IPv6AddrToken
270+sixteenBit = do
271+ r <- ipv6AddrFullChunk <|> count 3 hexaChar <|> count 2 hexaChar <|> count 1 hexaChar
272+ -- "Leading zeros MUST be suppressed" (RFC 5952, 4.1)
273+ let r' = T.dropWhile (=='0') $ T.pack r
274+ return $ if T.null r'
275+ then AllZeros
276+ -- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3)
277+ else SixteenBit $ T.toLower r'
278+
279+ipv4Addr :: Parser IPv6AddrToken
280+ipv4Addr = do
281+ n1 <- manyDigits <* "."
282+ if n1 /= T.empty
283+ then do n2 <- manyDigits <* "."
284+ if n2 /= T.empty
285+ then do n3 <- manyDigits <* "."
286+ if n3 /= T.empty
287+ then do n4 <- manyDigits
288+ if n4 /= T.empty
289+ then return $ IPv4Addr $ T.intercalate "." [n1,n2,n3,n4]
290+ else parserFailure
291+ else parserFailure
292+ else parserFailure
293+ else parserFailure
294+ where
295+ parserFailure = fail "ipv4Addr parsing failure"
296+ manyDigits = do
297+ ds <- takeWhile1 isDigit
298+ case R.decimal ds of
299+ Right (n,_) -> return (if n < 256 then T.pack $ show n else T.empty)
300+ Left _ -> return T.empty
301+
302+doubleColon :: Parser IPv6AddrToken
303+doubleColon = do
304+ string "::"
305+ return DoubleColon
306+
307+colon :: Parser IPv6AddrToken
308+colon = do
309+ string ":"
310+ return Colon
311+
312+ipv6AddrFullChunk :: Parser String
313+ipv6AddrFullChunk = count 4 hexaChar
314+
315+hexaChar :: Parser Char
316+hexaChar = satisfy (inClass "0-9a-fA-F")
tests/QC/IPv6/Types.hsnew file+29
@@ -0,0 +1,29 @@
1+-- -----------------------------------------------------------------------------
2+
3+-- |
4+-- Module : Text.IPv6Addr
5+-- Copyright : Copyright © Michel Boucey 2011-2015
6+-- License : BSD-Style
7+-- Maintainer : michel.boucey@gmail.com
8+--
9+-- Dealing with IPv6 address text representations, canonization and manipulations.
10+--
11+
12+-- -----------------------------------------------------------------------------
13+
14+module QC.IPv6.Types where
15+
16+import qualified Data.Text as T
17+
18+data IPv6Addr = IPv6Addr T.Text
19+
20+instance Show IPv6Addr where
21+ show (IPv6Addr addr) = T.unpack addr
22+
23+data IPv6AddrToken
24+ = SixteenBit T.Text -- ^ A four hexadecimal digits group representing a 16-Bit chunk
25+ | AllZeros -- ^ An all zeros 16-Bit chunk
26+ | Colon -- ^ A separator between 16-Bit chunks
27+ | DoubleColon -- ^ A double-colon stands for a unique compression of many consecutive 16-Bit chunks
28+ | IPv4Addr T.Text -- ^ An embedded IPv4 address as representation of the last 32-Bit
29+ deriving (Eq,Show)
tests/QC/Rechunked.hs+3−2
@@ -6,7 +6,7 @@ module QC.Rechunked (
6 ) where
6 ) where
7
7
8import Control.Monad (forM, forM_)
8import Control.Monad (forM, forM_)
9−import Control.Monad.ST (runST)
9+import Control.Monad.ST (ST, runST)
10import Data.List (unfoldr)
10import Data.List (unfoldr)
11import Test.QuickCheck (Gen, choose)
11import Test.QuickCheck (Gen, choose)
12import qualified Data.ByteString as B
12import qualified Data.ByteString as B
@@ -29,7 +29,7 @@ rechunk_ split len xs = (unfoldr go . (,) xs) `fmap` rechunkSizes (len xs)
29
29
30rechunkSizes :: Int -> Gen [Int]
30rechunkSizes :: Int -> Gen [Int]
31rechunkSizes n0 = shuffle =<< loop [] (0:repeat 1) n0
31rechunkSizes n0 = shuffle =<< loop [] (0:repeat 1) n0
32− where loop _ [] _ = error "it's 2014, where's my Stream type?"
32+ where loop _ [] _ = error "it's 2015, where's my Stream type?"
33 loop acc (lb:lbs) n
33 loop acc (lb:lbs) n
34 | n <= 0 = shuffle (reverse acc)
34 | n <= 0 = shuffle (reverse acc)
35 | otherwise = do
35 | otherwise = do
@@ -49,6 +49,7 @@ fisherYates xs = (V.toList . V.backpermute v) `fmap` swapIndices (G.length v)
49 return (runST (swapAll swaps))
49 return (runST (swapAll swaps))
50 where
50 where
51 n = n0 - 1
51 n = n0 - 1
52+ swapAll :: [(Int,Int)] -> ST s (V.Vector Int)
52 swapAll ijs = do
53 swapAll ijs = do
53 mv <- G.unsafeThaw (G.enumFromTo 0 n :: V.Vector Int)
54 mv <- G.unsafeThaw (G.enumFromTo 0 n :: V.Vector Int)
54 forM_ ijs $ uncurry (M.swap mv)
55 forM_ ijs $ uncurry (M.swap mv)
tests/QC/Simple.hs+3−3
@@ -10,8 +10,8 @@ import Data.ByteString (ByteString)
10import Data.List (foldl')
10import Data.List (foldl')
11import Data.Maybe (fromMaybe)
11import Data.Maybe (fromMaybe)
12import QC.Rechunked (rechunkBS)
12import QC.Rechunked (rechunkBS)
13−import Test.Framework (Test)
13+import Test.Tasty (TestTree)
14−import Test.Framework.Providers.QuickCheck2 (testProperty)
14+import Test.Tasty.QuickCheck (testProperty)
15import Test.QuickCheck (Property, counterexample, forAll)
15import Test.QuickCheck (Property, counterexample, forAll)
16import qualified Data.Attoparsec.ByteString.Char8 as A
16import qualified Data.Attoparsec.ByteString.Char8 as A
17
17
@@ -31,7 +31,7 @@ parse :: A.Parser r -> [ByteString] -> A.Result r
31parse p (x:xs) = foldl' A.feed (A.parse p x) xs
31parse p (x:xs) = foldl' A.feed (A.parse p x) xs
32parse p [] = A.parse p ""
32parse p [] = A.parse p ""
33
33
34−tests :: [Test]
34+tests :: [TestTree]
35tests = [
35tests = [
36 testProperty "issue75" t_issue75
36 testProperty "issue75" t_issue75
37 ]
37 ]
tests/QC/Text.hs+34−13
@@ -1,19 +1,26 @@
1−{-# LANGUAGE BangPatterns, OverloadedStrings #-}
1+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}
2{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
2{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
3module QC.Text (tests) where
3module QC.Text (tests) where
4
4
5+#if !MIN_VERSION_base(4,8,0)
5−import Control.Applicative ((<$>), (<*>))
6+import Control.Applicative ((<*>), (<$>))
7+#endif
6import Data.Int (Int64)
8import Data.Int (Int64)
9+import Data.Word (Word8)
7import Prelude hiding (take, takeWhile)
10import Prelude hiding (take, takeWhile)
8import QC.Common (liftOp, parseT)
11import QC.Common (liftOp, parseT)
9−import Test.Framework (Test)
12+import qualified QC.Text.FastSet as FastSet
13+import qualified QC.Text.Regressions as Regressions
14+import Test.Tasty (TestTree, testGroup)
10−import Test.Framework.Providers.QuickCheck2 (testProperty)
15+import Test.Tasty.QuickCheck (testProperty)
11import Test.QuickCheck
16import Test.QuickCheck
12import qualified Data.Attoparsec.Text as P
17import qualified Data.Attoparsec.Text as P
13import qualified Data.Attoparsec.Text.Lazy as PL
18import qualified Data.Attoparsec.Text.Lazy as PL
14import qualified Data.Attoparsec.Text.FastSet as S
19import qualified Data.Attoparsec.Text.FastSet as S
20+import qualified Data.ByteString as BS
15import qualified Data.Char as Char
21import qualified Data.Char as Char
16import qualified Data.Text as T
22import qualified Data.Text as T
23+import qualified Data.Text.Encoding as TE
17import qualified Data.Text.Lazy as L
24import qualified Data.Text.Lazy as L
18
25
19-- Basic byte-level combinators.
26-- Basic byte-level combinators.
@@ -60,9 +67,18 @@ string :: L.Text -> L.Text -> Property
60string s t = parseT (P.string s') (s `L.append` t) === Just s'
67string s t = parseT (P.string s') (s `L.append` t) === Just s'
61 where s' = toStrict s
68 where s' = toStrict s
62
69
63−stringCI :: T.Text -> Property
70+strings :: L.Text -> L.Text -> L.Text -> Property
71+strings s t u =
72+ parseT (P.string (toStrict s) >> P.string t') (L.concat [s,t,u])
73+ === Just t'
74+ where t' = toStrict t
75+
76+-- | Note: "simple, and efficient" works for well formed input...
77+-- i.e. e.g. Latin1 texts
78+stringCI :: [Word8] -> Property
64−stringCI s = P.parseOnly (P.stringCI fs) s === Right s
79+stringCI ws = P.parseOnly (P.stringCI fs) s === Right s
65 where fs = T.toCaseFold s
80 where fs = T.toCaseFold s
81+ s = TE.decodeLatin1 (BS.pack ws)
66
82
67asciiCI :: T.Text -> Gen Bool
83asciiCI :: T.Text -> Gen Bool
68asciiCI x =
84asciiCI x =
@@ -110,8 +126,8 @@ takeWhile w s =
110
126
111takeWhile1 :: Char -> L.Text -> Property
127takeWhile1 :: Char -> L.Text -> Property
112takeWhile1 w s =
128takeWhile1 w s =
113− let s' = L.cons w s
129+ let s' = L.cons w s
114− (h,t) = L.span (<=w) s'
130+ (h,t) = L.span (<=w) s'
115 in case PL.parse (P.takeWhile1 (<=w)) s' of
131 in case PL.parse (P.takeWhile1 (<=w)) s' of
116 PL.Done t' h' -> t === t' .&&. toStrict h === h'
132 PL.Done t' h' -> t === t' .&&. toStrict h === h'
117 _ -> property False
133 _ -> property False
@@ -140,19 +156,21 @@ endOfLine s =
140 (c, fst <$> L.uncons s') === ('\r', Just '\n')
156 (c, fst <$> L.uncons s') === ('\r', Just '\n')
141
157
142scan :: L.Text -> Positive Int64 -> Property
158scan :: L.Text -> Positive Int64 -> Property
159+-- for some reason, if counterexample is removed, this test fails?
160+scan s (Positive k) = counterexample (show s)
143−scan s (Positive k) = parseT p s === Just (toStrict $ L.take k s)
161+ $ parseT p s === Just (toStrict $ L.take k s)
144 where p = P.scan k $ \ n _ ->
162 where p = P.scan k $ \ n _ ->
145 if n > 0 then let !n' = n - 1 in Just n' else Nothing
163 if n > 0 then let !n' = n - 1 in Just n' else Nothing
146−
164+
147members :: String -> Property
165members :: String -> Property
148members s = property $ all (`S.member` set) s
166members s = property $ all (`S.member` set) s
149 where set = S.fromList s
167 where set = S.fromList s
150−
168+
151nonmembers :: String -> String -> Property
169nonmembers :: String -> String -> Property
152nonmembers s s' = property . not . any (`S.member` set) $ filter (not . (`elem` s)) s'
170nonmembers s s' = property . not . any (`S.member` set) $ filter (not . (`elem` s)) s'
153 where set = S.fromList s
171 where set = S.fromList s
154−
172+
155−tests :: [Test]
173+tests :: [TestTree]
156tests = [
174tests = [
157 testProperty "anyChar" anyChar
175 testProperty "anyChar" anyChar
158 , testProperty "asciiCI" asciiCI
176 , testProperty "asciiCI" asciiCI
@@ -168,6 +186,7 @@ tests = [
168 , testProperty "skip" skip
186 , testProperty "skip" skip
169 , testProperty "skipWhile" skipWhile
187 , testProperty "skipWhile" skipWhile
170 , testProperty "string" string
188 , testProperty "string" string
189+ , testProperty "strings" strings
171 , testProperty "stringCI" stringCI
190 , testProperty "stringCI" stringCI
172 , testProperty "take" take
191 , testProperty "take" take
173 , testProperty "takeText" takeText
192 , testProperty "takeText" takeText
@@ -179,4 +198,6 @@ tests = [
179 , testProperty "takeWhile1_empty" takeWhile1_empty
198 , testProperty "takeWhile1_empty" takeWhile1_empty
180 , testProperty "members" members
199 , testProperty "members" members
181 , testProperty "nonmembers" nonmembers
200 , testProperty "nonmembers" nonmembers
201+ , testGroup "FastSet" FastSet.tests
202+ , testGroup "Regressions" Regressions.tests
182 ]
203 ]
tests/QC/Text/FastSet.hsnew file+15
@@ -0,0 +1,15 @@
1+module QC.Text.FastSet where
2+
3+import Test.Tasty (TestTree)
4+import Test.Tasty.QuickCheck (testProperty)
5+import Test.QuickCheck
6+import qualified Data.Attoparsec.Text.FastSet as FastSet
7+
8+membershipCorrect :: String -> String -> Property
9+membershipCorrect members others =
10+ let fs = FastSet.fromList members
11+ correct c = (c `FastSet.member` fs) == (c `elem` members)
12+ in property $ all correct (members ++ others)
13+
14+tests :: [TestTree]
15+tests = [ testProperty "membership is correct" membershipCorrect ]
tests/QC/Text/Regressions.hsnew file+54
@@ -0,0 +1,54 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module QC.Text.Regressions (
4+ tests
5+ ) where
6+
7+import Data.Text (Text)
8+import qualified Data.Text as T
9+import Data.List (foldl')
10+import Data.Maybe (fromMaybe)
11+import Data.Char (isLower)
12+import Data.Monoid ((<>))
13+import QC.Rechunked (rechunkT)
14+import Test.Tasty (TestTree)
15+import Test.Tasty.QuickCheck (testProperty)
16+import Test.QuickCheck (Property, counterexample, forAll)
17+import qualified Data.Attoparsec.Text as A
18+
19+
20+--------------------------------------------------------------------------------
21+-- 105 was about runScanner not always returning the final state. The result
22+-- did depend on how the data was fed to the parser.
23+
24+t_issue105 :: Property
25+t_issue105 = expect issue105 "lowER" (A.Done "ER" "low")
26+
27+issue105 :: A.Parser Text
28+issue105 = do
29+ (_, firstFourLowercaseLetters) <- A.runScanner "" f
30+ return $ firstFourLowercaseLetters
31+
32+ where
33+ f :: Text -> Char -> Maybe Text
34+ f acc c = if T.length acc < 4 && isLower c
35+ then Just $ acc <> T.singleton c
36+ else Nothing
37+
38+
39+expect :: (Show r, Eq r) => A.Parser r -> Text -> A.Result r -> Property
40+expect p input wanted =
41+ forAll (rechunkT input) $ \in' ->
42+ let result = parse p in'
43+ in counterexample (show result ++ " /= " ++ show wanted) $
44+ fromMaybe False (A.compareResults result wanted)
45+
46+parse :: A.Parser r -> [Text] -> A.Result r
47+parse p (x:xs) = foldl' A.feed (A.parse p x) xs
48+parse p [] = A.parse p ""
49+
50+
51+tests :: [TestTree]
52+tests = [
53+ testProperty "issue105" t_issue105
54+ ]