git.iko.soy/typeable/attoparsec
on bfaa350 help sign in
.gitignore−1
since splitsnapshots
unifiedsplit
@@ -3,4 +3,3 @@
3/dist/
3/dist/
4.cabal-sandbox
4.cabal-sandbox
5cabal.sandbox.config
5cabal.sandbox.config
6−*.sw[op]
.hgignore−1
@@ -6,7 +6,6 @@ benchmarks/Arse
6benchmarks/dist
6benchmarks/dist
7benchmarks/med.txt
7benchmarks/med.txt
8benchmarks/tiny
8benchmarks/tiny
9−benchmarks/warp-[0-9]*
10cabal.sandbox.config
9cabal.sandbox.config
11examples/c-http
10examples/c-http
12examples/http_parser.[ch]
11examples/http_parser.[ch]
.hgtags−11
@@ -50,14 +50,3 @@ ec23283e88c00af34c3144a8775ca4358daea3f7 0.10.4.0
50a3c7a521fd58595d6a470ab890cc54a80dd45bd7 0.11.3.1
50a3c7a521fd58595d6a470ab890cc54a80dd45bd7 0.11.3.1
5138326ce07aca6c8c3a014b0d454d2a5bfdbd6a52 0.11.3.2
5138326ce07aca6c8c3a014b0d454d2a5bfdbd6a52 0.11.3.2
52694039f4fc157a8ca5dce37959391615b6c3f535 0.11.3.3
52694039f4fc157a8ca5dce37959391615b6c3f535 0.11.3.3
53−1fe92d1838fb1b1afcee1ca18d4970cf7e01956e 0.11.3.4
54−68203a4cb847da905fa44b5aa1942b99d5867ab4 0.12.0.0
55−c9c2b05707b2fffcd749d5817e3e0d04e6ad6d0d 0.12.1.0
56−9157c2b4c35aa5f683b4ac51e6ff52c696bc22e2 0.12.1.1
57−682ba515f7ba62500ad79371b37b90932e0c0e3d 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.ymldeleted−161
@@ -1,161 +0,0 @@
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+3−8
@@ -1,21 +1,16 @@
1-- |
1-- |
2-- Module : Data.Attoparsec
2-- Module : Data.Attoparsec
3−-- Copyright : Bryan O'Sullivan 2007-2015
3+-- Copyright : Bryan O'Sullivan 2007-2011
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
7-- Stability : experimental
7-- Stability : experimental
8-- Portability : unknown
8-- Portability : unknown
9--
9--
10−-- Simple, efficient combinator parsing for
10+-- Simple, efficient combinator parsing for 'ByteString' strings,
11−-- 'Data.ByteString.ByteString' strings, loosely based on the Parsec
11+-- loosely based on the Parsec library.
12−-- library.
13−--
14−-- This module is deprecated. Use "Data.Attoparsec.ByteString"
15−-- instead.
16
12
17module Data.Attoparsec
13module Data.Attoparsec
18− {-# DEPRECATED "This module will be removed in the next major release." #-}
19 (
14 (
20 module Data.Attoparsec.ByteString
15 module Data.Attoparsec.ByteString
21 ) where
16 ) where
Data/Attoparsec/ByteString.hs+32−60
@@ -1,10 +1,6 @@
1−{-# LANGUAGE CPP #-}
2−#if __GLASGOW_HASKELL__ >= 702
3−{-# LANGUAGE Trustworthy #-}
4−#endif
5-- |
1-- |
6-- Module : Data.Attoparsec.ByteString
2-- Module : Data.Attoparsec.ByteString
7−-- Copyright : Bryan O'Sullivan 2007-2015
3+-- Copyright : Bryan O'Sullivan 2007-2011
8-- License : BSD3
4-- License : BSD3
9--
5--
10-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
@@ -42,6 +38,9 @@ module Data.Attoparsec.ByteString
42 , maybeResult
38 , maybeResult
43 , eitherResult
39 , eitherResult
44
40
41+ -- * Combinators
42+ , module Data.Attoparsec.Combinator
43+
45 -- * Parsing individual bytes
44 -- * Parsing individual bytes
46 , I.word8
45 , I.word8
47 , I.anyWord8
46 , I.anyWord8
@@ -63,7 +62,6 @@ module Data.Attoparsec.ByteString
63 , I.skipWhile
62 , I.skipWhile
64 , I.take
63 , I.take
65 , I.scan
64 , I.scan
66− , I.runScanner
67 , I.takeWhile
65 , I.takeWhile
68 , I.takeWhile1
66 , I.takeWhile1
69 , I.takeTill
67 , I.takeTill
@@ -71,33 +69,9 @@ module Data.Attoparsec.ByteString
71 -- ** Consume all remaining input
69 -- ** Consume all remaining input
72 , I.takeByteString
70 , I.takeByteString
73 , I.takeLazyByteString
71 , I.takeLazyByteString
74−
75− -- * Combinators
76− , try
77− , (<?>)
78− , choice
79− , count
80− , option
81− , many'
82− , many1
83− , many1'
84− , manyTill
85− , manyTill'
86− , sepBy
87− , sepBy'
88− , sepBy1
89− , sepBy1'
90− , skipMany
91− , skipMany1
92− , eitherP
93− , I.match
94− -- * State observation and manipulation functions
95− , I.endOfInput
96− , I.atEnd
97 ) where
72 ) where
98
73
99import Data.Attoparsec.Combinator
74import Data.Attoparsec.Combinator
100−import Data.List (intercalate)
101import qualified Data.Attoparsec.ByteString.Internal as I
75import qualified Data.Attoparsec.ByteString.Internal as I
102import qualified Data.Attoparsec.Internal as I
76import qualified Data.Attoparsec.Internal as I
103import qualified Data.ByteString as B
77import qualified Data.ByteString as B
@@ -106,51 +80,49 @@ import qualified Data.Attoparsec.Internal.Types as T
106
80
107-- $parsec
81-- $parsec
108--
82--
109−-- Compared to Parsec 3, attoparsec makes several tradeoffs. It is
83+-- Compared to Parsec 3, Attoparsec makes several tradeoffs. It is
110-- not intended for, or ideal for, all possible uses.
84-- not intended for, or ideal for, all possible uses.
111--
85--
112−-- * While attoparsec can consume input incrementally, Parsec cannot.
86+-- * While Attoparsec can consume input incrementally, Parsec cannot.
113-- Incremental input is a huge deal for efficient and secure network
87-- Incremental input is a huge deal for efficient and secure network
114-- and system programming, since it gives much more control to users
88-- and system programming, since it gives much more control to users
115-- of the library over matters such as resource usage and the I/O
89-- of the library over matters such as resource usage and the I/O
116-- model to use.
90-- model to use.
117--
91--
118−-- * Much of the performance advantage of attoparsec is gained via
92+-- * Much of the performance advantage of Attoparsec is gained via
119-- high-performance parsers such as 'I.takeWhile' and 'I.string'.
93-- high-performance parsers such as 'I.takeWhile' and 'I.string'.
120-- If you use complicated combinators that return lists of bytes or
94-- If you use complicated combinators that return lists of bytes or
121-- characters, there is less performance difference between the two
95-- characters, there is less performance difference between the two
122-- libraries.
96-- libraries.
123--
97--
124−-- * Unlike Parsec 3, attoparsec does not support being used as a
98+-- * Unlike Parsec 3, Attoparsec does not support being used as a
125-- monad transformer.
99-- monad transformer.
126--
100--
127−-- * attoparsec is specialised to deal only with strict 'B.ByteString'
101+-- * Attoparsec is specialised to deal only with strict 'B.ByteString'
128-- input. Efficiency concerns rule out both lists and lazy
102-- input. Efficiency concerns rule out both lists and lazy
129-- bytestrings. The usual use for lazy bytestrings would be to
103-- bytestrings. The usual use for lazy bytestrings would be to
130-- allow consumption of very large input without a large footprint.
104-- allow consumption of very large input without a large footprint.
131−-- For this need, attoparsec's incremental input provides an
105+-- For this need, Attoparsec's incremental input provides an
132-- excellent substitute, with much more control over when input
106-- excellent substitute, with much more control over when input
133−-- takes place. If you must use lazy bytestrings, see the
107+-- takes place. If you must use lazy bytestrings, see the 'Lazy'
134−-- "Data.Attoparsec.ByteString.Lazy" module, which feeds lazy chunks
135−-- to a regular parser.
108+-- module, which feeds lazy chunks to a regular parser.
136--
109--
137-- * Parsec parsers can produce more helpful error messages than
110-- * Parsec parsers can produce more helpful error messages than
138−-- attoparsec parsers. This is a matter of focus: attoparsec avoids
111+-- Attoparsec parsers. This is a matter of focus: Attoparsec avoids
139-- the extra book-keeping in favour of higher performance.
112-- the extra book-keeping in favour of higher performance.
140
113
141-- $incremental
114-- $incremental
142--
115--
143−-- attoparsec supports incremental input, meaning that you can feed it
116+-- Attoparsec supports incremental input, meaning that you can feed it
144-- a bytestring that represents only part of the expected total amount
117-- a bytestring that represents only part of the expected total amount
145-- of data to parse. If your parser reaches the end of a fragment of
118-- of data to parse. If your parser reaches the end of a fragment of
146-- input and could consume more input, it will suspend parsing and
119-- input and could consume more input, it will suspend parsing and
147-- return a 'T.Partial' continuation.
120-- return a 'T.Partial' continuation.
148--
121--
149−-- Supplying the 'T.Partial' continuation with a bytestring will
122+-- Supplying the 'T.Partial' continuation with another bytestring will
150−-- resume parsing at the point where it was suspended, with the
123+-- resume parsing at the point where it was suspended. You must be
151−-- bytestring you supplied used as new input at the end of the
152−-- existing input. You must be prepared for the result of the resumed
124+-- prepared for the result of the resumed parse to be another
153−-- parse to be another 'T.Partial' continuation.
125+-- 'T.Partial' continuation.
154--
126--
155-- To indicate that you have no more input, supply the 'T.Partial'
127-- To indicate that you have no more input, supply the 'T.Partial'
156-- continuation with an empty bytestring.
128-- continuation with an empty bytestring.
@@ -162,19 +134,12 @@ import qualified Data.Attoparsec.Internal.Types as T
162-- If you do not need support for incremental input, consider using
134-- If you do not need support for incremental input, consider using
163-- the 'I.parseOnly' function to run your parser. It will never
135-- the 'I.parseOnly' function to run your parser. It will never
164-- prompt for more input.
136-- prompt for more input.
165−--
166−-- /Note/: incremental input does /not/ imply that attoparsec will
167−-- release portions of its internal state for garbage collection as it
168−-- proceeds. Its internal representation is equivalent to a single
169−-- 'ByteString': if you feed incremental input to a parser, it will
170−-- require memory proportional to the amount of input you supply.
171−-- (This is necessary to support arbitrary backtracking.)
172
137
173-- $performance
138-- $performance
174--
139--
175−-- If you write an attoparsec-based parser carefully, it can be
140+-- If you write an Attoparsec-based parser carefully, it can be
176−-- realistic to expect it to perform similarly to a hand-rolled C
141+-- realistic to expect it to perform within a factor of 2 of a
177−-- parser (measuring megabytes parsed per second).
142+-- hand-rolled C parser (measuring megabytes parsed per second).
178--
143--
179-- To actually achieve high performance, there are a few guidelines
144-- To actually achieve high performance, there are a few guidelines
180-- that it is useful to follow.
145-- that it is useful to follow.
@@ -195,6 +160,14 @@ import qualified Data.Attoparsec.Internal.Types as T
195-- Make active use of benchmarking and profiling tools to measure,
160-- Make active use of benchmarking and profiling tools to measure,
196-- find the problems with, and improve the performance of your parser.
161-- find the problems with, and improve the performance of your parser.
197
162
163+-- | If a parser has returned a 'T.Partial' result, supply it with more
164+-- input.
165+feed :: Result r -> B.ByteString -> Result r
166+feed f@(T.Fail _ _ _) _ = f
167+feed (T.Partial k) d = k d
168+feed (T.Done bs r) d = T.Done (B.append bs d) r
169+{-# INLINE feed #-}
170+
198-- | Run a parser and print its result to standard output.
171-- | Run a parser and print its result to standard output.
199parseTest :: (Show a) => I.Parser a -> B.ByteString -> IO ()
172parseTest :: (Show a) => I.Parser a -> B.ByteString -> IO ()
200parseTest p s = print (parse p s)
173parseTest p s = print (parse p s)
@@ -224,7 +197,6 @@ maybeResult _ = Nothing
224-- | Convert a 'Result' value to an 'Either' value. A 'T.Partial'
197-- | Convert a 'Result' value to an 'Either' value. A 'T.Partial'
225-- result is treated as failure.
198-- result is treated as failure.
226eitherResult :: Result r -> Either String r
199eitherResult :: Result r -> Either String r
227−eitherResult (T.Done _ r) = Right r
200+eitherResult (T.Done _ r) = Right r
228−eitherResult (T.Fail _ [] msg) = Left msg
201+eitherResult (T.Fail _ _ msg) = Left msg
229−eitherResult (T.Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
230−eitherResult _ = Left "Result: incomplete input"
202+eitherResult _ = Left "Result: incomplete input"
Data/Attoparsec/ByteString/Buffer.hsdeleted−156
@@ -1,156 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−-- |
3−-- Module : Data.Attoparsec.ByteString.Buffer
4−-- Copyright : Bryan O'Sullivan 2007-2015
5−-- License : BSD3
6−--
7−-- Maintainer : bos@serpentine.com
8−-- Stability : experimental
9−-- Portability : GHC
10−--
11−-- An "immutable" buffer that supports cheap appends.
12−--
13−-- A Buffer is divided into an immutable read-only zone, followed by a
14−-- mutable area that we've preallocated, but not yet written to.
15−--
16−-- We overallocate at the end of a Buffer so that we can cheaply
17−-- append. Since a user of an existing Buffer cannot see past the end
18−-- of its immutable zone into the data that will change during an
19−-- append, this is safe.
20−--
21−-- Once we run out of space at the end of a Buffer, we do the usual
22−-- doubling of the buffer size.
23−--
24−-- The fact of having a mutable buffer really helps with performance,
25−-- but it does have a consequence: if someone misuses the Partial API
26−-- that attoparsec uses by calling the same continuation repeatedly
27−-- (which never makes sense in practice), they could overwrite data.
28−--
29−-- Since the API *looks* pure, it should *act* pure, too, so we use
30−-- two generation counters (one mutable, one immutable) to track the
31−-- number of appends to a mutable buffer. If the counters ever get out
32−-- of sync, someone is appending twice to a mutable buffer, so we
33−-- duplicate the entire buffer in order to preserve the immutability
34−-- of its older self.
35−--
36−-- While we could go a step further and gain protection against API
37−-- abuse on a multicore system, by use of an atomic increment
38−-- instruction to bump the mutable generation counter, that would be
39−-- very expensive, and feels like it would also be in the realm of the
40−-- ridiculous. Clients should never call a continuation more than
41−-- once; we lack a linear type system that could enforce this; and
42−-- there's only so far we should go to accommodate broken uses.
43−
44−module Data.Attoparsec.ByteString.Buffer
45− (
46− Buffer
47− , buffer
48− , unbuffer
49− , pappend
50− , length
51− , unsafeIndex
52− , substring
53− , unsafeDrop
54− ) where
55−
56−import Control.Exception (assert)
57−import Data.ByteString.Internal (ByteString(..), memcpy, nullForeignPtr)
58−import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
59−import Data.List (foldl1')
60−import Data.Monoid as Mon (Monoid(..))
61−import Data.Semigroup (Semigroup(..))
62−import Data.Word (Word8)
63−import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
64−import Foreign.Ptr (castPtr, plusPtr)
65−import Foreign.Storable (peek, peekByteOff, poke, sizeOf)
66−import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
67−import Prelude hiding (length)
68−
69−-- If _cap is zero, this buffer is empty.
70−data Buffer = Buf {
71− _fp :: {-# UNPACK #-} !(ForeignPtr Word8)
72− , _off :: {-# UNPACK #-} !Int
73− , _len :: {-# UNPACK #-} !Int
74− , _cap :: {-# UNPACK #-} !Int
75− , _gen :: {-# UNPACK #-} !Int
76− }
77−
78−instance Show Buffer where
79− showsPrec p = showsPrec p . unbuffer
80−
81−-- | The initial 'Buffer' has no mutable zone, so we can avoid all
82−-- copies in the (hopefully) common case of no further input being fed
83−-- to us.
84−buffer :: ByteString -> Buffer
85−buffer (PS fp off len) = Buf fp off len len 0
86−
87−unbuffer :: Buffer -> ByteString
88−unbuffer (Buf fp off len _ _) = PS fp off len
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−
95−instance Monoid Buffer where
96− mempty = Buf nullForeignPtr 0 0 0 0
97−
98− mappend = (<>)
99−
100− mconcat [] = Mon.mempty
101− mconcat xs = foldl1' mappend xs
102−
103−pappend :: Buffer -> ByteString -> Buffer
104−pappend (Buf _ _ _ 0 _) bs = buffer bs
105−pappend buf (PS fp off len) = append buf fp off len
106−
107−append :: Buffer -> ForeignPtr a -> Int -> Int -> Buffer
108−append (Buf fp0 off0 len0 cap0 gen0) !fp1 !off1 !len1 =
109− inlinePerformIO . withForeignPtr fp0 $ \ptr0 ->
110− withForeignPtr fp1 $ \ptr1 -> do
111− let genSize = sizeOf (0::Int)
112− newlen = len0 + len1
113− gen <- if gen0 == 0
114− then return 0
115− else peek (castPtr ptr0)
116− if gen == gen0 && newlen <= cap0
117− then do
118− let newgen = gen + 1
119− poke (castPtr ptr0) newgen
120− memcpy (ptr0 `plusPtr` (off0+len0))
121− (ptr1 `plusPtr` off1)
122− (fromIntegral len1)
123− return (Buf fp0 off0 newlen cap0 newgen)
124− else do
125− let newcap = newlen * 2
126− fp <- mallocPlainForeignPtrBytes (newcap + genSize)
127− withForeignPtr fp $ \ptr_ -> do
128− let ptr = ptr_ `plusPtr` genSize
129− newgen = 1
130− poke (castPtr ptr_) newgen
131− memcpy ptr (ptr0 `plusPtr` off0) (fromIntegral len0)
132− memcpy (ptr `plusPtr` len0) (ptr1 `plusPtr` off1)
133− (fromIntegral len1)
134− return (Buf fp genSize newlen newcap newgen)
135−
136−length :: Buffer -> Int
137−length (Buf _ _ len _ _) = len
138−{-# INLINE length #-}
139−
140−unsafeIndex :: Buffer -> Int -> Word8
141−unsafeIndex (Buf fp off len _ _) i = assert (i >= 0 && i < len) .
142− inlinePerformIO . withForeignPtr fp $ flip peekByteOff (off+i)
143−{-# INLINE unsafeIndex #-}
144−
145−substring :: Int -> Int -> Buffer -> ByteString
146−substring s l (Buf fp off len _ _) =
147− assert (s >= 0 && s <= len) .
148− assert (l >= 0 && l <= len-s) $
149− PS fp (off+s) l
150−{-# INLINE substring #-}
151−
152−unsafeDrop :: Int -> Buffer -> ByteString
153−unsafeDrop s (Buf fp off len _ _) =
154− assert (s >= 0 && s <= len) $
155− PS fp (off+s) (len-s)
156−{-# INLINE unsafeDrop #-}
Data/Attoparsec/ByteString/Char8.hs+70−84
@@ -1,13 +1,10 @@
1−{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, TypeFamilies,
1+{-# LANGUAGE BangPatterns, FlexibleInstances, TypeFamilies,
2 TypeSynonymInstances, GADTs #-}
2 TypeSynonymInstances, GADTs #-}
3−#if __GLASGOW_HASKELL__ >= 702
4−{-# LANGUAGE Trustworthy #-} -- Imports internal modules
5−#endif
6−{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-warnings-deprecations #-}
3+{-# OPTIONS_GHC -fno-warn-orphans #-}
7
4
8-- |
5-- |
9-- Module : Data.Attoparsec.ByteString.Char8
6-- Module : Data.Attoparsec.ByteString.Char8
10−-- Copyright : Bryan O'Sullivan 2007-2015
7+-- Copyright : Bryan O'Sullivan 2007-2011
11-- License : BSD3
8-- License : BSD3
12--
9--
13-- Maintainer : bos@serpentine.com
10-- Maintainer : bos@serpentine.com
@@ -32,13 +29,16 @@ module Data.Attoparsec.ByteString.Char8
32 , A.parse
29 , A.parse
33 , A.feed
30 , A.feed
34 , A.parseOnly
31 , A.parseOnly
35− , A.parseWith
36 , A.parseTest
32 , A.parseTest
33+ , A.parseWith
37
34
38 -- ** Result conversion
35 -- ** Result conversion
39 , A.maybeResult
36 , A.maybeResult
40 , A.eitherResult
37 , A.eitherResult
41
38
39+ -- * Combinators
40+ , module Data.Attoparsec.Combinator
41+
42 -- * Parsing individual characters
42 -- * Parsing individual characters
43 , char
43 , char
44 , char8
44 , char8
@@ -70,7 +70,7 @@ module Data.Attoparsec.ByteString.Char8
70
70
71 -- * Efficient string handling
71 -- * Efficient string handling
72 , I.string
72 , I.string
73− , I.stringCI
73+ , stringCI
74 , skipSpace
74 , skipSpace
75 , skipWhile
75 , skipWhile
76 , I.take
76 , I.take
@@ -102,36 +102,9 @@ module Data.Attoparsec.ByteString.Char8
102 , number
102 , number
103 , rational
103 , rational
104 , scientific
104 , scientific
105−
106− -- * Combinators
107− , try
108− , (<?>)
109− , choice
110− , count
111− , option
112− , many'
113− , many1
114− , many1'
115− , manyTill
116− , manyTill'
117− , sepBy
118− , sepBy'
119− , sepBy1
120− , sepBy1'
121− , skipMany
122− , skipMany1
123− , eitherP
124− , I.match
125− -- * State observation and manipulation functions
126− , I.endOfInput
127− , I.atEnd
128 ) where
105 ) where
129
106
130−#if !MIN_VERSION_base(4,8,0)
131−import Control.Applicative (pure, (*>), (<*), (<$>))
107+import Control.Applicative (pure, (*>), (<*), (<$>), (<|>))
132−import Data.Word (Word)
133−#endif
134−import Control.Applicative ((<|>))
135import Control.Monad (void, when)
108import Control.Monad (void, when)
136import Data.Attoparsec.ByteString.FastSet (charClass, memberChar)
109import Data.Attoparsec.ByteString.FastSet (charClass, memberChar)
137import Data.Attoparsec.ByteString.Internal (Parser)
110import Data.Attoparsec.ByteString.Internal (Parser)
@@ -143,7 +116,7 @@ import Data.Int (Int8, Int16, Int32, Int64)
143import Data.String (IsString(..))
116import Data.String (IsString(..))
144import Data.Scientific (Scientific)
117import Data.Scientific (Scientific)
145import qualified Data.Scientific as Sci
118import qualified Data.Scientific as Sci
146−import Data.Word (Word8, Word16, Word32, Word64)
119+import Data.Word (Word8, Word16, Word32, Word64, Word)
147import Prelude hiding (takeWhile)
120import Prelude hiding (takeWhile)
148import qualified Data.Attoparsec.ByteString as A
121import qualified Data.Attoparsec.ByteString as A
149import qualified Data.Attoparsec.ByteString.Internal as I
122import qualified Data.Attoparsec.ByteString.Internal as I
@@ -169,6 +142,16 @@ instance (a ~ B.ByteString) => IsString (Parser a) where
169-- currency sign in ISO-8859-1). Haskell 'Char' values above U+00FF
142-- currency sign in ISO-8859-1). Haskell 'Char' values above U+00FF
170-- are truncated, so e.g. U+1D6B7 is truncated to the byte @0xB7@.
143-- are truncated, so e.g. U+1D6B7 is truncated to the byte @0xB7@.
171
144
145+-- ASCII-specific but fast, oh yes.
146+toLower :: Word8 -> Word8
147+toLower w | w >= 65 && w <= 90 = w + 32
148+ | otherwise = w
149+
150+-- | Satisfy a literal string, ignoring case.
151+stringCI :: B.ByteString -> Parser B.ByteString
152+stringCI = I.stringTransform (B8.map toLower)
153+{-# INLINE stringCI #-}
154+
172-- | Consume input as long as the predicate returns 'True', and return
155-- | Consume input as long as the predicate returns 'True', and return
173-- the consumed input.
156-- the consumed input.
174--
157--
@@ -232,7 +215,7 @@ isDigit c = c >= '0' && c <= '9'
232
215
233-- | A fast digit predicate.
216-- | A fast digit predicate.
234isDigit_w8 :: Word8 -> Bool
217isDigit_w8 :: Word8 -> Bool
235−isDigit_w8 w = w - 48 <= 9
218+isDigit_w8 w = w >= 48 && w <= 57
236{-# INLINE isDigit_w8 #-}
219{-# INLINE isDigit_w8 #-}
237
220
238-- | Match any character.
221-- | Match any character.
@@ -269,7 +252,7 @@ isSpace c = (c == ' ') || ('\t' <= c && c <= '\r')
269
252
270-- | Fast 'Word8' predicate for matching ASCII space characters.
253-- | Fast 'Word8' predicate for matching ASCII space characters.
271isSpace_w8 :: Word8 -> Bool
254isSpace_w8 :: Word8 -> Bool
272−isSpace_w8 w = w == 32 || w - 9 <= 4
255+isSpace_w8 w = (w == 32) || (9 <= w && w <= 13)
273{-# INLINE isSpace_w8 #-}
256{-# INLINE isSpace_w8 #-}
274
257
275
258
@@ -371,39 +354,30 @@ skipSpace = I.skipWhile isSpace_w8
371
354
372-- $specalt
355-- $specalt
373--
356--
374−-- If you enable the @OverloadedStrings@ language extension, you can
357+-- The '.*>' and '<*.' combinators are intended for use with the
375−-- use the '*>' and '<*' combinators to simplify the common task of
358+-- @OverloadedStrings@ language extension. They simplify the common
376−-- matching a statically known string, then immediately parsing
359+-- task of matching a statically known string, then immediately
377−-- something else.
360+-- parsing something else.
378--
361--
379−-- Instead of writing something like this:
362+-- An example makes this easier to understand:
380−--
381−-- @
382−--'I.string' \"foo\" '*>' wibble
383−-- @
384--
363--
385−-- Using @OverloadedStrings@, you can omit the explicit use of
364+-- @{-\# LANGUAGE OverloadedStrings #-}
386−-- 'I.string', and write a more compact version:
387--
365--
388−-- @
366+-- shoeSize = \"Shoe size: \" '.*>' 'decimal'
389−-- \"foo\" '*>' wibble
390-- @
367-- @
391--
368--
392−-- (Note: the '.*>' and '<*.' combinators that were originally
369+-- If we were to try to use '*>' above instead, the type checker would
393−-- provided for this purpose are obsolete and unnecessary, and will be
370+-- not be able to tell which 'IsString' instance to use for the text
394−-- removed in the next major version.)
371+-- in quotes. We would have to be explicit, using either a type
372+-- signature or the 'I.string' parser.
395
373
396−-- | /Obsolete/. A type-specialized version of '*>' for
374+-- | Type-specialized version of '*>' for 'B.ByteString'.
397−-- 'B.ByteString'. Use '*>' instead.
398(.*>) :: B.ByteString -> Parser a -> Parser a
375(.*>) :: B.ByteString -> Parser a -> Parser a
399s .*> f = I.string s *> f
376s .*> f = I.string s *> f
400−{-# DEPRECATED (.*>) "This is no longer necessary, and will be removed. Use '*>' instead." #-}
401
377
402−-- | /Obsolete/. A type-specialized version of '<*' for
378+-- | Type-specialized version of '<*' for 'B.ByteString'.
403−-- 'B.ByteString'. Use '<*' instead.
404(<*.) :: Parser a -> B.ByteString -> Parser a
379(<*.) :: Parser a -> B.ByteString -> Parser a
405f <*. s = f <* I.string s
380f <*. s = f <* I.string s
406−{-# DEPRECATED (<*.) "This is no longer necessary, and will be removed. Use '<*' instead." #-}
407
381
408-- | A predicate that matches either a carriage return @\'\\r\'@ or
382-- | A predicate that matches either a carriage return @\'\\r\'@ or
409-- newline @\'\\n\'@ character.
383-- newline @\'\\n\'@ character.
@@ -444,8 +418,9 @@ hexadecimal = B8.foldl' step 0 `fmap` I.takeWhile1 isHexDigit
444
418
445-- | Parse and decode an unsigned decimal number.
419-- | Parse and decode an unsigned decimal number.
446decimal :: Integral a => Parser a
420decimal :: Integral a => Parser a
447−decimal = B8.foldl' step 0 `fmap` I.takeWhile1 isDigit_w8
421+decimal = B8.foldl' step 0 `fmap` I.takeWhile1 isDig
422+ where isDig w = w >= 48 && w <= 57
448− where step a w = a * 10 + fromIntegral (w - 48)
423+ step a w = a * 10 + fromIntegral (w - 48)
449{-# SPECIALISE decimal :: Parser Int #-}
424{-# SPECIALISE decimal :: Parser Int #-}
450{-# SPECIALISE decimal :: Parser Int8 #-}
425{-# SPECIALISE decimal :: Parser Int8 #-}
451{-# SPECIALISE decimal :: Parser Int16 #-}
426{-# SPECIALISE decimal :: Parser Int16 #-}
@@ -471,24 +446,6 @@ signed p = (negate <$> (char8 '-' *> p))
471 <|> (char8 '+' *> p)
446 <|> (char8 '+' *> p)
472 <|> p
447 <|> p
473
448
474−-- | Parse a rational number.
475−--
476−-- The syntax accepted by this parser is the same as for 'double'.
477−--
478−-- /Note/: this parser is not safe for use with inputs from untrusted
479−-- sources. An input with a suitably large exponent such as
480−-- @"1e1000000000"@ will cause a huge 'Integer' to be allocated,
481−-- resulting in what is effectively a denial-of-service attack.
482−--
483−-- In most cases, it is better to use 'double' or 'scientific'
484−-- instead.
485−rational :: Fractional a => Parser a
486−{-# SPECIALIZE rational :: Parser Double #-}
487−{-# SPECIALIZE rational :: Parser Float #-}
488−{-# SPECIALIZE rational :: Parser Rational #-}
489−{-# SPECIALIZE rational :: Parser Scientific #-}
490−rational = scientifically realToFrac
491−
492-- | Parse a rational number.
449-- | Parse a rational number.
493--
450--
494-- This parser accepts an optional leading sign character, followed by
451-- This parser accepts an optional leading sign character, followed by
@@ -516,12 +473,42 @@ rational = scientifically realToFrac
516--
473--
517-- This function does not accept string representations of \"NaN\" or
474-- This function does not accept string representations of \"NaN\" or
518-- \"Infinity\".
475-- \"Infinity\".
476+rational :: Fractional a => Parser a
477+{-# SPECIALIZE rational :: Parser Double #-}
478+{-# SPECIALIZE rational :: Parser Float #-}
479+{-# SPECIALIZE rational :: Parser Rational #-}
480+{-# SPECIALIZE rational :: Parser Scientific #-}
481+rational = scientifically realToFrac
482+
483+-- | Parse a rational number.
484+--
485+-- The syntax accepted by this parser is the same as for 'rational'.
486+--
487+-- /Note/: This function is almost ten times faster than 'rational',
488+-- but is slightly less accurate.
489+--
490+-- The 'Double' type supports about 16 decimal places of accuracy.
491+-- For 94.2% of numbers, this function and 'rational' give identical
492+-- results, but for the remaining 5.8%, this function loses precision
493+-- around the 15th decimal place. For 0.001% of numbers, this
494+-- function will lose precision at the 13th or 14th decimal place.
495+--
496+-- This function does not accept string representations of \"NaN\" or
497+-- \"Infinity\".
519double :: Parser Double
498double :: Parser Double
520double = scientifically Sci.toRealFloat
499double = scientifically Sci.toRealFloat
521
500
522-- | Parse a number, attempting to preserve both speed and precision.
501-- | Parse a number, attempting to preserve both speed and precision.
523--
502--
524−-- The syntax accepted by this parser is the same as for 'double'.
503+-- The syntax accepted by this parser is the same as for 'rational'.
504+--
505+-- /Note/: This function is almost ten times faster than 'rational'.
506+-- On integral inputs, it gives perfectly accurate answers, and on
507+-- floating point inputs, it is slightly less accurate than
508+-- 'rational'.
509+--
510+-- This function does not accept string representations of \"NaN\" or
511+-- \"
525number :: Parser Number
512number :: Parser Number
526number = scientifically $ \s ->
513number = scientifically $ \s ->
527 let e = Sci.base10Exponent s
514 let e = Sci.base10Exponent s
@@ -529,11 +516,10 @@ number = scientifically $ \s ->
529 in if e >= 0
516 in if e >= 0
530 then I (c * 10 ^ e)
517 then I (c * 10 ^ e)
531 else D (Sci.toRealFloat s)
518 else D (Sci.toRealFloat s)
532−{-# DEPRECATED number "Use 'scientific' instead." #-}
533
519
534-- | Parse a scientific number.
520-- | Parse a scientific number.
535--
521--
536−-- The syntax accepted by this parser is the same as for 'double'.
522+-- The syntax accepted by this parser is the same as for 'rational'.
537scientific :: Parser Scientific
523scientific :: Parser Scientific
538scientific = scientifically id
524scientific = scientifically id
539
525
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-2015
6+-- Copyright : Bryan O'Sullivan 2008
7-- License : BSD3
7-- License : BSD3
8--
8--
9-- Maintainer : bos@serpentine.com
9-- Maintainer : bos@serpentine.com
Data/Attoparsec/ByteString/Internal.hs+130−247
@@ -1,15 +1,15 @@
1−{-# LANGUAGE BangPatterns, CPP, GADTs, OverloadedStrings, RankNTypes,
1+{-# LANGUAGE BangPatterns, Rank2Types, OverloadedStrings,
2− RecordWildCards #-}
2+ RecordWildCards, MagicHash, UnboxedTuples #-}
3-- |
3-- |
4-- Module : Data.Attoparsec.ByteString.Internal
4-- Module : Data.Attoparsec.ByteString.Internal
5−-- Copyright : Bryan O'Sullivan 2007-2015
5+-- Copyright : Bryan O'Sullivan 2007-2011
6-- License : BSD3
6-- License : BSD3
7--
7--
8-- Maintainer : bos@serpentine.com
8-- Maintainer : bos@serpentine.com
9-- Stability : experimental
9-- Stability : experimental
10-- Portability : unknown
10-- Portability : unknown
11--
11--
12−-- Simple, efficient parser combinators for 'ByteString' strings,
12+-- Simple, efficient parser combinators for 'B.ByteString' strings,
13-- loosely based on the Parsec library.
13-- loosely based on the Parsec library.
14
14
15module Data.Attoparsec.ByteString.Internal
15module Data.Attoparsec.ByteString.Internal
@@ -47,7 +47,7 @@ module Data.Attoparsec.ByteString.Internal
47 -- * Efficient string handling
47 -- * Efficient string handling
48 , skipWhile
48 , skipWhile
49 , string
49 , string
50− , stringCI
50+ , stringTransform
51 , take
51 , take
52 , scan
52 , scan
53 , runScanner
53 , runScanner
@@ -61,30 +61,21 @@ module Data.Attoparsec.ByteString.Internal
61
61
62 -- * Utilities
62 -- * Utilities
63 , endOfLine
63 , endOfLine
64− , endOfInput
65− , match
66− , atEnd
67 ) where
64 ) where
68
65
69−#if !MIN_VERSION_base(4,8,0)
70−import Control.Applicative ((<$>))
66+import Control.Applicative ((<|>), (<$>))
71−#endif
72−import Control.Applicative ((<|>))
73import Control.Monad (when)
67import Control.Monad (when)
74−import Data.Attoparsec.ByteString.Buffer (Buffer, buffer)
75import Data.Attoparsec.ByteString.FastSet (charClass, memberWord8)
68import Data.Attoparsec.ByteString.FastSet (charClass, memberWord8)
76−import Data.Attoparsec.Combinator ((<?>))
69+import Data.Attoparsec.Combinator
70+import Data.Attoparsec.Internal.Types
71+ hiding (Parser, Input, Added, Failure, Success)
77import Data.Attoparsec.Internal
72import Data.Attoparsec.Internal
78−import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
73+import Data.Monoid (Monoid(..))
79−import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
80−import Data.ByteString (ByteString)
81−import Data.List (intercalate)
82import Data.Word (Word8)
74import Data.Word (Word8)
83import Foreign.ForeignPtr (withForeignPtr)
75import Foreign.ForeignPtr (withForeignPtr)
84import Foreign.Ptr (castPtr, minusPtr, plusPtr)
76import Foreign.Ptr (castPtr, minusPtr, plusPtr)
85import Foreign.Storable (Storable(peek, sizeOf))
77import Foreign.Storable (Storable(peek, sizeOf))
86−import Prelude hiding (getChar, succ, take, takeWhile)
78+import Prelude hiding (getChar, take, takeWhile)
87−import qualified Data.Attoparsec.ByteString.Buffer as Buf
88import qualified Data.Attoparsec.Internal.Types as T
79import qualified Data.Attoparsec.Internal.Types as T
89import qualified Data.ByteString as B8
80import qualified Data.ByteString as B8
90import qualified Data.ByteString.Char8 as B
81import qualified Data.ByteString.Char8 as B
@@ -92,10 +83,13 @@ import qualified Data.ByteString.Internal as B
92import qualified Data.ByteString.Lazy as L
83import qualified Data.ByteString.Lazy as L
93import qualified Data.ByteString.Unsafe as B
84import qualified Data.ByteString.Unsafe as B
94
85
86+import GHC.Base (realWorld#)
87+import GHC.IO (IO(IO))
88+
95−type Parser = T.Parser ByteString
89+type Parser = T.Parser B.ByteString
96−type Result = IResult ByteString
90+type Result = IResult B.ByteString
97−type Failure r = T.Failure ByteString Buffer r
91+type Failure r = T.Failure B.ByteString r
98−type Success a r = T.Success ByteString Buffer a r
92+type Success a r = T.Success B.ByteString a r
99
93
100-- | The parser @satisfy p@ succeeds for any byte for which the
94-- | The parser @satisfy p@ succeeds for any byte for which the
101-- predicate @p@ returns 'True'. Returns the byte that is actually
95-- predicate @p@ returns 'True'. Returns the byte that is actually
@@ -104,11 +98,7 @@ type Success a r = T.Success ByteString Buffer a r
104-- >digit = satisfy isDigit
98-- >digit = satisfy isDigit
105-- > where isDigit w = w >= 48 && w <= 57
99-- > where isDigit w = w >= 48 && w <= 57
106satisfy :: (Word8 -> Bool) -> Parser Word8
100satisfy :: (Word8 -> Bool) -> Parser Word8
107−satisfy p = do
101+satisfy = satisfyElem
108− h <- peekWord8'
109− if p h
110− then advance 1 >> return h
111− else fail "satisfy"
112{-# INLINE satisfy #-}
102{-# INLINE satisfy #-}
113
103
114-- | The parser @skip p@ succeeds for any byte for which the predicate
104-- | The parser @skip p@ succeeds for any byte for which the predicate
@@ -118,9 +108,9 @@ satisfy p = do
118-- > where isDigit w = w >= 48 && w <= 57
108-- > where isDigit w = w >= 48 && w <= 57
119skip :: (Word8 -> Bool) -> Parser ()
109skip :: (Word8 -> Bool) -> Parser ()
120skip p = do
110skip p = do
121− h <- peekWord8'
111+ s <- ensure 1
122− if p h
112+ if p (B.unsafeHead s)
123− then advance 1
113+ then put (B.unsafeTail s)
124 else fail "skip"
114 else fail "skip"
125
115
126-- | The parser @satisfyWith f p@ transforms a byte, and succeeds if
116-- | The parser @satisfyWith f p@ transforms a byte, and succeeds if
@@ -128,10 +118,11 @@ skip p = do
128-- parser returns the transformed byte that was parsed.
118-- parser returns the transformed byte that was parsed.
129satisfyWith :: (Word8 -> a) -> (a -> Bool) -> Parser a
119satisfyWith :: (Word8 -> a) -> (a -> Bool) -> Parser a
130satisfyWith f p = do
120satisfyWith f p = do
131− h <- peekWord8'
121+ s <- ensure 1
132− let c = f h
122+ let c = f $! B.unsafeHead s
133 if p c
123 if p c
124+ then let !t = B.unsafeTail s
134− then advance 1 >> return c
125+ in put t >> return c
135 else fail "satisfyWith"
126 else fail "satisfyWith"
136{-# INLINE satisfyWith #-}
127{-# INLINE satisfyWith #-}
137
128
@@ -141,15 +132,24 @@ storable = hack undefined
141 hack :: Storable b => b -> Parser b
132 hack :: Storable b => b -> Parser b
142 hack dummy = do
133 hack dummy = do
143 (fp,o,_) <- B.toForeignPtr `fmap` take (sizeOf dummy)
134 (fp,o,_) <- B.toForeignPtr `fmap` take (sizeOf dummy)
144− return . inlinePerformIO . withForeignPtr fp $ \p ->
135+ return . B.inlinePerformIO . withForeignPtr fp $ \p ->
145 peek (castPtr $ p `plusPtr` o)
136 peek (castPtr $ p `plusPtr` o)
146
137
147−-- | Consume exactly @n@ bytes of input.
138+-- | Consume @n@ bytes of input, but succeed only if the predicate
139+-- returns 'True'.
148−take :: Int -> Parser ByteString
140+takeWith :: Int -> (B.ByteString -> Bool) -> Parser B.ByteString
149−take n0 = do
141+takeWith n0 p = do
150 let n = max n0 0
142 let n = max n0 0
151 s <- ensure n
143 s <- ensure n
144+ let h = B.unsafeTake n s
145+ t = B.unsafeDrop n s
146+ if p h
152− advance n >> return s
147+ then put t >> return h
148+ else fail "takeWith"
149+
150+-- | Consume exactly @n@ bytes of input.
151+take :: Int -> Parser B.ByteString
152+take n = takeWith n (const True)
153{-# INLINE take #-}
153{-# INLINE take #-}
154
154
155-- | @string s@ parses a sequence of bytes that identically match
155-- | @string s@ parses a sequence of bytes that identically match
@@ -165,71 +165,27 @@ take n0 = do
165--
165--
166-- The reason for its failure is that the first branch is a
166-- The reason for its failure is that the first branch is a
167-- partial match, and will consume the letters @\'f\'@ and @\'o\'@
167-- partial match, and will consume the letters @\'f\'@ and @\'o\'@
168−-- before failing. In attoparsec, the above parser will /succeed/ on
168+-- before failing. In Attoparsec, the above parser will /succeed/ on
169-- that input, because the failed first branch will consume nothing.
169-- that input, because the failed first branch will consume nothing.
170−string :: ByteString -> Parser ByteString
170+string :: B.ByteString -> Parser B.ByteString
171−string s = string_ (stringSuspended id) id s
171+string s = takeWith (B.length s) (==s)
172{-# INLINE string #-}
172{-# INLINE string #-}
173
173
174−-- ASCII-specific but fast, oh yes.
174+stringTransform :: (B.ByteString -> B.ByteString) -> B.ByteString
175−toLower :: Word8 -> Word8
175+ -> Parser B.ByteString
176−toLower w | w >= 65 && w <= 90 = w + 32
176+stringTransform f s = takeWith (B.length s) ((==f s) . f)
177− | otherwise = w
177+{-# INLINE stringTransform #-}
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"
224
178
225-- | Skip past input for as long as the predicate returns 'True'.
179-- | Skip past input for as long as the predicate returns 'True'.
226skipWhile :: (Word8 -> Bool) -> Parser ()
180skipWhile :: (Word8 -> Bool) -> Parser ()
227skipWhile p = go
181skipWhile p = go
228 where
182 where
229 go = do
183 go = do
230− t <- B8.takeWhile p <$> get
184+ t <- B8.dropWhile p <$> get
231− continue <- inputSpansChunks (B.length t)
185+ put t
186+ when (B.null t) $ do
187+ input <- wantInput
232− when continue go
188+ when input go
233{-# INLINE skipWhile #-}
189{-# INLINE skipWhile #-}
234
190
235-- | Consume input as long as the predicate returns 'False'
191-- | Consume input as long as the predicate returns 'False'
@@ -239,10 +195,9 @@ skipWhile p = go
239-- predicate returns 'True' on the first byte of input.
195-- predicate returns 'True' on the first byte of input.
240--
196--
241-- /Note/: Because this parser does not fail, do not use it with
197-- /Note/: Because this parser does not fail, do not use it with
242−-- combinators such as 'Control.Applicative.many', because such
198+-- combinators such as 'many', because such parsers loop until a
243−-- parsers loop until a failure occurs. Careless use will thus result
199+-- failure occurs. Careless use will thus result in an infinite loop.
244−-- in an infinite loop.
245−takeTill :: (Word8 -> Bool) -> Parser ByteString
200+takeTill :: (Word8 -> Bool) -> Parser B.ByteString
246takeTill p = takeWhile (not . p)
201takeTill p = takeWhile (not . p)
247{-# INLINE takeTill #-}
202{-# INLINE takeTill #-}
248
203
@@ -253,30 +208,24 @@ takeTill p = takeWhile (not . p)
253-- predicate returns 'False' on the first byte of input.
208-- predicate returns 'False' on the first byte of input.
254--
209--
255-- /Note/: Because this parser does not fail, do not use it with
210-- /Note/: Because this parser does not fail, do not use it with
256−-- combinators such as 'Control.Applicative.many', because such
211+-- combinators such as 'many', because such parsers loop until a
257−-- parsers loop until a failure occurs. Careless use will thus result
212+-- failure occurs. Careless use will thus result in an infinite loop.
258−-- in an infinite loop.
259−takeWhile :: (Word8 -> Bool) -> Parser ByteString
213+takeWhile :: (Word8 -> Bool) -> Parser B.ByteString
260−takeWhile p = do
214+takeWhile p = (B.concat . reverse) `fmap` go []
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
270 where
215 where
271 go acc = do
216 go acc = do
272− s <- B8.takeWhile p <$> get
217+ (h,t) <- B8.span p <$> get
273− continue <- inputSpansChunks (B.length s)
218+ put t
274− if continue
219+ if B.null t
220+ then do
221+ input <- wantInput
222+ if input
275− then go (s:acc)
223+ then go (h:acc)
276− else return $ concatReverse (s:acc)
224+ else return (h:acc)
277−{-# INLINE takeWhileAcc #-}
225+ else return (h:acc)
278−
226+{-# INLINE takeWhile #-}
227+
279−takeRest :: Parser [ByteString]
228+takeRest :: Parser [B.ByteString]
280takeRest = go []
229takeRest = go []
281 where
230 where
282 go acc = do
231 go acc = do
@@ -284,12 +233,12 @@ takeRest = go []
284 if input
233 if input
285 then do
234 then do
286 s <- get
235 s <- get
287− advance (B.length s)
236+ put B.empty
288 go (s:acc)
237 go (s:acc)
289 else return (reverse acc)
238 else return (reverse acc)
290
239
291-- | Consume all remaining input and return it as a single string.
240-- | Consume all remaining input and return it as a single string.
292−takeByteString :: Parser ByteString
241+takeByteString :: Parser B.ByteString
293takeByteString = B.concat `fmap` takeRest
242takeByteString = B.concat `fmap` takeRest
294
243
295-- | Consume all remaining input and return it as a single string.
244-- | Consume all remaining input and return it as a single string.
@@ -298,7 +247,7 @@ takeLazyByteString = L.fromChunks `fmap` takeRest
298
247
299data T s = T {-# UNPACK #-} !Int s
248data T s = T {-# UNPACK #-} !Int s
300
249
301−scan_ :: (s -> [ByteString] -> Parser r) -> s -> (s -> Word8 -> Maybe s)
250+scan_ :: (s -> [B.ByteString] -> Parser r) -> s -> (s -> Word8 -> Maybe s)
302 -> Parser r
251 -> Parser r
303scan_ f s0 p = go [] s0
252scan_ f s0 p = go [] s0
304 where
253 where
@@ -319,9 +268,14 @@ scan_ f s0 p = go [] s0
319 bs <- get
268 bs <- get
320 let T i s' = inlinePerformIO $ scanner bs
269 let T i s' = inlinePerformIO $ scanner bs
321 !h = B.unsafeTake i bs
270 !h = B.unsafeTake i bs
322− continue <- inputSpansChunks i
271+ !t = B.unsafeDrop i bs
323− if continue
272+ put t
273+ if B.null t
274+ then do
275+ input <- wantInput
276+ if input
324− then go (h:acc) s'
277+ then go (h:acc) s'
278+ else f s' (h:acc)
325 else f s' (h:acc)
279 else f s' (h:acc)
326{-# INLINE scan_ #-}
280{-# INLINE scan_ #-}
327
281
@@ -334,17 +288,19 @@ scan_ f s0 p = go [] s0
334-- predicate returns 'Nothing' on the first byte of input.
288-- predicate returns 'Nothing' on the first byte of input.
335--
289--
336-- /Note/: Because this parser does not fail, do not use it with
290-- /Note/: Because this parser does not fail, do not use it with
337−-- combinators such as 'Control.Applicative.many', because such
291+-- combinators such as 'many', because such parsers loop until a
338−-- parsers loop until a failure occurs. Careless use will thus result
292+-- failure occurs. Careless use will thus result in an infinite loop.
339−-- in an infinite loop.
340−scan :: s -> (s -> Word8 -> Maybe s) -> Parser ByteString
293+scan :: s -> (s -> Word8 -> Maybe s) -> Parser B.ByteString
341−scan = scan_ $ \_ chunks -> return $! concatReverse chunks
294+scan = scan_ $ \_ chunks ->
295+ case chunks of
296+ [x] -> return x
297+ xs -> return $! B.concat $ reverse xs
342{-# INLINE scan #-}
298{-# INLINE scan #-}
343
299
344-- | Like 'scan', but generalized to return the final state of the
300-- | Like 'scan', but generalized to return the final state of the
345-- scanner.
301-- scanner.
346−runScanner :: s -> (s -> Word8 -> Maybe s) -> Parser (ByteString, s)
302+runScanner :: s -> (s -> Word8 -> Maybe s) -> Parser (B.ByteString, s)
347−runScanner = scan_ $ \s xs -> let !sx = concatReverse xs in return (sx, s)
303+runScanner = scan_ $ \s xs -> return (B.concat (reverse xs), s)
348{-# INLINE runScanner #-}
304{-# INLINE runScanner #-}
349
305
350-- | Consume input as long as the predicate returns 'True', and return
306-- | Consume input as long as the predicate returns 'True', and return
@@ -353,20 +309,15 @@ runScanner = scan_ $ \s xs -> let !sx = concatReverse xs in return (sx, s)
353-- This parser requires the predicate to succeed on at least one byte
309-- This parser requires the predicate to succeed on at least one byte
354-- of input: it will fail if the predicate never returns 'True' or if
310-- of input: it will fail if the predicate never returns 'True' or if
355-- there is no input left.
311-- there is no input left.
356−takeWhile1 :: (Word8 -> Bool) -> Parser ByteString
312+takeWhile1 :: (Word8 -> Bool) -> Parser B.ByteString
357takeWhile1 p = do
313takeWhile1 p = do
358− (`when` demandInput) =<< endOfChunk
314+ (`when` demandInput) =<< B.null <$> get
359− s <- B8.takeWhile p <$> get
315+ (h,t) <- B8.span p <$> get
360− let len = B.length s
361− if len == 0
362− then fail "takeWhile1"
316+ when (B.null h) $ fail "takeWhile1"
363− else do
317+ put t
364− advance len
318+ if B.null t
365− eoc <- endOfChunk
319+ then (h<>) `fmap` takeWhile p
366− if eoc
367− then takeWhileAcc p [s]
368− else return s
320+ else return h
369−{-# INLINE takeWhile1 #-}
370
321
371-- | Match any byte in a set.
322-- | Match any byte in a set.
372--
323--
@@ -408,32 +359,27 @@ notWord8 c = satisfy (/= c) <?> "not " ++ show c
408-- input has been reached. Does not consume any input.
359-- input has been reached. Does not consume any input.
409--
360--
410-- /Note/: Because this parser does not fail, do not use it with
361-- /Note/: Because this parser does not fail, do not use it with
411−-- combinators such as 'Control.Applicative.many', because such
362+-- combinators such as 'many', because such parsers loop until a
412−-- parsers loop until a failure occurs. Careless use will thus result
363+-- failure occurs. Careless use will thus result in an infinite loop.
413−-- in an infinite loop.
414peekWord8 :: Parser (Maybe Word8)
364peekWord8 :: Parser (Maybe Word8)
415−peekWord8 = T.Parser $ \t pos@(Pos pos_) more _lose succ ->
365+peekWord8 = T.Parser $ \i0 a0 m0 _kf ks ->
416− case () of
366+ if B.null (unI i0)
417− _| pos_ < Buf.length t ->
418− let !w = Buf.unsafeIndex t pos_
419− in succ t pos more (Just w)
420− | more == Complete ->
367+ then if m0 == Complete
421− succ t pos more Nothing
368+ then ks i0 a0 m0 Nothing
422− | otherwise ->
369+ else let ks' i a m = let !w = B.unsafeHead (unI i)
423− let succ' t' pos' more' = let !w = Buf.unsafeIndex t' pos_
424− in succ t' pos' more' (Just w)
370+ in ks i a m (Just w)
425− lose' t' pos' more' = succ t' pos' more' Nothing
371+ kf' i a m = ks i a m Nothing
426− in prompt t pos more lose' succ'
372+ in prompt i0 a0 m0 kf' ks'
373+ else let !w = B.unsafeHead (unI i0)
374+ in ks i0 a0 m0 (Just w)
427{-# INLINE peekWord8 #-}
375{-# INLINE peekWord8 #-}
428
376
429-- | Match any byte, to perform lookahead. Does not consume any
377-- | Match any byte, to perform lookahead. Does not consume any
430-- input, but will fail if end of input has been reached.
378-- input, but will fail if end of input has been reached.
431peekWord8' :: Parser Word8
379peekWord8' :: Parser Word8
432−peekWord8' = T.Parser $ \t pos more lose succ ->
380+peekWord8' = do
433− if lengthAtLeast pos 1 t
381+ s <- ensure 1
434− then succ t pos more (Buf.unsafeIndex t (fromPos pos))
382+ return $! B.unsafeHead s
435− else let succ' t' pos' more' bs' = succ t' pos' more' $! B.unsafeHead bs'
436− in ensureSuspended 1 t pos more lose succ'
437{-# INLINE peekWord8' #-}
383{-# INLINE peekWord8' #-}
438
384
439-- | Match either a single newline character @\'\\n\'@, or a carriage
385-- | Match either a single newline character @\'\\n\'@, or a carriage
@@ -443,94 +389,31 @@ endOfLine = (word8 10 >> return ()) <|> (string "\r\n" >> return ())
443
389
444-- | Terminal failure continuation.
390-- | Terminal failure continuation.
445failK :: Failure a
391failK :: Failure a
446−failK t (Pos pos) _more stack msg = Fail (Buf.unsafeDrop pos t) stack msg
392+failK i0 _a0 _m0 stack msg = Fail (unI i0) stack msg
447{-# INLINE failK #-}
393{-# INLINE failK #-}
448
394
449-- | Terminal success continuation.
395-- | Terminal success continuation.
450successK :: Success a a
396successK :: Success a a
451−successK t (Pos pos) _more a = Done (Buf.unsafeDrop pos t) a
397+successK i0 _a0 _m0 a = Done (unI i0) a
452{-# INLINE successK #-}
398{-# INLINE successK #-}
453
399
454-- | Run a parser.
400-- | Run a parser.
455−parse :: Parser a -> ByteString -> Result a
401+parse :: Parser a -> B.ByteString -> Result a
456−parse m s = T.runParser m (buffer s) (Pos 0) Incomplete failK successK
402+parse m s = T.runParser m (I s) mempty Incomplete failK successK
457{-# INLINE parse #-}
403{-# INLINE parse #-}
458
404
459-- | Run a parser that cannot be resupplied via a 'Partial' result.
405-- | Run a parser that cannot be resupplied via a 'Partial' result.
460−--
461−-- This function does not force a parser to consume all of its input.
462−-- Instead, any residual input will be discarded. To force a parser
463−-- to consume all of its input, use something like this:
464−--
465−-- @
466−--'parseOnly' (myParser 'Control.Applicative.<*' 'endOfInput')
467−-- @
468−parseOnly :: Parser a -> ByteString -> Either String a
406+parseOnly :: Parser a -> B.ByteString -> Either String a
469−parseOnly m s = case T.runParser m (buffer s) (Pos 0) Complete failK successK of
407+parseOnly m s = case T.runParser m (I s) mempty Complete failK successK of
470− Fail _ [] err -> Left err
471− Fail _ ctxs err -> Left (intercalate " > " ctxs ++ ": " ++ err)
408+ Fail _ _ err -> Left err
472− Done _ a -> Right a
409+ Done _ a -> Right a
473− _ -> error "parseOnly: impossible error!"
410+ _ -> error "parseOnly: impossible error!"
474{-# INLINE parseOnly #-}
411{-# INLINE parseOnly #-}
475
412
476−get :: Parser ByteString
413+-- | Just like unsafePerformIO, but we inline it. Big performance gains as
477−get = T.Parser $ \t pos more _lose succ ->
414+-- it exposes lots of things to further inlining. /Very unsafe/. In
478− succ t pos more (Buf.unsafeDrop (fromPos pos) t)
415+-- particular, you should do no memory allocation inside an
479−{-# INLINE get #-}
416+-- 'inlinePerformIO' block. On Hugs this is just @unsafePerformIO@.
480−
417+inlinePerformIO :: IO a -> a
481−endOfChunk :: Parser Bool
418+inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
482−endOfChunk = T.Parser $ \t pos more _lose succ ->
419+{-# INLINE inlinePerformIO #-}
483− succ t pos more (fromPos pos == Buf.length t)
484−{-# INLINE endOfChunk #-}
485−
486−inputSpansChunks :: Int -> Parser Bool
487−inputSpansChunks i = T.Parser $ \t pos_ more _lose succ ->
488− let pos = pos_ + Pos i
489− in if fromPos pos < Buf.length t || more == Complete
490− then succ t pos more False
491− else let lose' t' pos' more' = succ t' pos' more' False
492− succ' t' pos' more' = succ t' pos' more' True
493− in prompt t pos more lose' succ'
494−{-# INLINE inputSpansChunks #-}
495−
496−advance :: Int -> Parser ()
497−advance n = T.Parser $ \t pos more _lose succ ->
498− succ t (pos + Pos n) more ()
499−{-# INLINE advance #-}
500−
501−ensureSuspended :: Int -> Buffer -> Pos -> More
502− -> Failure r
503− -> Success ByteString r
504− -> Result r
505−ensureSuspended n t pos more lose succ =
506− runParser (demandInput >> go) t pos more lose succ
507− where go = T.Parser $ \t' pos' more' lose' succ' ->
508− if lengthAtLeast pos' n t'
509− then succ' t' pos' more' (substring pos (Pos n) t')
510− else runParser (demandInput >> go) t' pos' more' lose' succ'
511−
512−-- | If at least @n@ elements of input are available, return the
513−-- current input, otherwise fail.
514−ensure :: Int -> Parser ByteString
515−ensure n = T.Parser $ \t pos more lose succ ->
516− if lengthAtLeast pos n t
517− then succ t pos more (substring pos (Pos n) t)
518− -- The uncommon case is kept out-of-line to reduce code size:
519− else ensureSuspended n t pos more lose succ
520−{-# INLINE ensure #-}
521−
522−-- | Return both the result of a parse and the portion of the input
523−-- that was consumed while it was being parsed.
524−match :: Parser a -> Parser (ByteString, a)
525−match p = T.Parser $ \t pos more lose succ ->
526− let succ' t' pos' more' a =
527− succ t' pos' more' (substring pos (pos'-pos) t', a)
528− in runParser p t pos more lose succ'
529−
530−lengthAtLeast :: Pos -> Int -> Buffer -> Bool
531−lengthAtLeast (Pos pos) n bs = Buf.length bs >= pos + n
532−{-# INLINE lengthAtLeast #-}
533−
534−substring :: Pos -> Pos -> Buffer -> ByteString
535−substring (Pos pos) (Pos n) = Buf.substring pos n
536−{-# INLINE substring #-}
Data/Attoparsec/ByteString/Lazy.hs+3−10
@@ -1,11 +1,6 @@
1−{-# LANGUAGE CPP #-}
2−#if __GLASGOW_HASKELL__ >= 702
3−{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4−#endif
5−
6-- |
1-- |
7-- Module : Data.Attoparsec.ByteString.Lazy
2-- Module : Data.Attoparsec.ByteString.Lazy
8−-- Copyright : Bryan O'Sullivan 2007-2015
3+-- Copyright : Bryan O'Sullivan 2010, 2011
9-- License : BSD3
4-- License : BSD3
10--
5--
11-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
@@ -41,7 +36,6 @@ module Data.Attoparsec.ByteString.Lazy
41
36
42import Control.DeepSeq (NFData(rnf))
37import Control.DeepSeq (NFData(rnf))
43import Data.ByteString.Lazy.Internal (ByteString(..), chunk)
38import Data.ByteString.Lazy.Internal (ByteString(..), chunk)
44−import Data.List (intercalate)
45import qualified Data.ByteString as B
39import qualified Data.ByteString as B
46import qualified Data.Attoparsec.ByteString as A
40import qualified Data.Attoparsec.ByteString as A
47import qualified Data.Attoparsec.Internal.Types as T
41import qualified Data.Attoparsec.Internal.Types as T
@@ -105,6 +99,5 @@ maybeResult _ = Nothing
105
99
106-- | Convert a 'Result' value to an 'Either' value.
100-- | Convert a 'Result' value to an 'Either' value.
107eitherResult :: Result r -> Either String r
101eitherResult :: Result r -> Either String r
108−eitherResult (Done _ r) = Right r
102+eitherResult (Done _ r) = Right r
109−eitherResult (Fail _ [] msg) = Left msg
103+eitherResult (Fail _ _ msg) = Left msg
110−eitherResult (Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
Data/Attoparsec/Char8.hs+2−7
@@ -1,6 +1,6 @@
1-- |
1-- |
2-- Module : Data.Attoparsec.Char8
2-- Module : Data.Attoparsec.Char8
3−-- Copyright : Bryan O'Sullivan 2007-2015
3+-- Copyright : Bryan O'Sullivan 2007-2011
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
@@ -8,14 +8,9 @@
8-- Portability : unknown
8-- Portability : unknown
9--
9--
10-- Simple, efficient, character-oriented combinator parsing for
10-- Simple, efficient, character-oriented combinator parsing for
11−-- 'Data.ByteString.ByteString' strings, loosely based on the Parsec
11+-- 'ByteString' strings, loosely based on the Parsec library.
12−-- library.
13−--
14−-- This module is deprecated. Use "Data.Attoparsec.ByteString.Char8"
15−-- instead.
16
12
17module Data.Attoparsec.Char8
13module Data.Attoparsec.Char8
18− {-# DEPRECATED "This module will be removed in the next major release." #-}
19 (
14 (
20 module Data.Attoparsec.ByteString.Char8
15 module Data.Attoparsec.ByteString.Char8
21 ) where
16 ) where
Data/Attoparsec/Combinator.hs+60−41
@@ -1,10 +1,7 @@
1{-# LANGUAGE BangPatterns, CPP #-}
1{-# LANGUAGE BangPatterns, CPP #-}
2−#if __GLASGOW_HASKELL__ >= 702
3−{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4−#endif
5-- |
2-- |
6-- Module : Data.Attoparsec.Combinator
3-- Module : Data.Attoparsec.Combinator
7−-- Copyright : Daan Leijen 1999-2001, Bryan O'Sullivan 2007-2015
4+-- Copyright : Daan Leijen 1999-2001, Bryan O'Sullivan 2009-2010
8-- License : BSD3
5-- License : BSD3
9--
6--
10-- Maintainer : bos@serpentine.com
7-- Maintainer : bos@serpentine.com
@@ -32,42 +29,43 @@ module Data.Attoparsec.Combinator
32 , skipMany
29 , skipMany
33 , skipMany1
30 , skipMany1
34 , eitherP
31 , eitherP
35− , feed
32+ -- * Parsing individual chunk elements
36 , satisfyElem
33 , satisfyElem
34+ -- * State observation and manipulation functions
37 , endOfInput
35 , endOfInput
38 , atEnd
36 , atEnd
39− , lookAhead
40 ) where
37 ) where
41
38
42−#if !MIN_VERSION_base(4,8,0)
43−import Control.Applicative (Applicative(..), (<$>))
44−import Data.Monoid (Monoid(mappend))
45−#endif
46−import Control.Applicative (Alternative(..), empty, liftA2, many, (<|>))
39+import Control.Applicative (Alternative(..), Applicative(..), empty, liftA2,
40+ (<|>), (*>), (<$>))
47import Control.Monad (MonadPlus(..))
41import Control.Monad (MonadPlus(..))
42+#if !MIN_VERSION_base(4,2,0)
43+import Control.Applicative (many)
44+#endif
45+
46+import Data.Attoparsec.Internal (demandInput, ensure, put, wantInput)
48−import Data.Attoparsec.Internal.Types (Parser(..), IResult(..))
47+import Data.Attoparsec.Internal.Types (Chunk(..), Input(..), Parser(..), addS)
49−import Data.Attoparsec.Internal (endOfInput, atEnd, satisfyElem)
48+import Data.Attoparsec.Internal.Types (More(..))
50import Data.ByteString (ByteString)
49import Data.ByteString (ByteString)
51import Data.Text (Text)
50import Data.Text (Text)
52import qualified Data.Attoparsec.Zepto as Z
51import qualified Data.Attoparsec.Zepto as Z
53−import Prelude hiding (succ)
54
52
55-- | Attempt a parse, and if it fails, rewind the input so that no
53-- | Attempt a parse, and if it fails, rewind the input so that no
56-- input appears to have been consumed.
54-- input appears to have been consumed.
57--
55--
58-- This combinator is provided for compatibility with Parsec.
56-- This combinator is provided for compatibility with Parsec.
59−-- attoparsec parsers always backtrack on failure.
57+-- Attoparsec parsers always backtrack on failure.
60−try :: Parser i a -> Parser i a
58+try :: Parser t a -> Parser t a
61try p = p
59try p = p
62{-# INLINE try #-}
60{-# INLINE try #-}
63
61
64-- | Name the parser, in case failure occurs.
62-- | Name the parser, in case failure occurs.
65−(<?>) :: Parser i a
63+(<?>) :: Parser t a
66 -> String -- ^ the name to use if parsing fails
64 -> String -- ^ the name to use if parsing fails
67− -> Parser i a
65+ -> Parser t a
68−p <?> msg0 = Parser $ \t pos more lose succ ->
66+p <?> msg0 = Parser $ \i0 a0 m0 kf ks ->
69− let lose' t' pos' more' strs msg = lose t' pos' more' (msg0:strs) msg
67+ let kf' i a m strs msg = kf i a m (msg0:strs) msg
70− in runParser p t pos more lose' succ
68+ in runParser p i0 a0 m0 kf' ks
71{-# INLINE (<?>) #-}
69{-# INLINE (<?>) #-}
72infix 0 <?>
70infix 0 <?>
73
71
@@ -76,8 +74,7 @@ infix 0 <?>
76-- action.
74-- action.
77choice :: Alternative f => [f a] -> f a
75choice :: Alternative f => [f a] -> f a
78choice = foldr (<|>) empty
76choice = foldr (<|>) empty
79−{-# SPECIALIZE choice :: [Parser ByteString a]
77+{-# SPECIALIZE choice :: [Parser ByteString a] -> Parser ByteString a #-}
80− -> Parser ByteString a #-}
81{-# SPECIALIZE choice :: [Parser Text a] -> Parser Text a #-}
78{-# SPECIALIZE choice :: [Parser Text a] -> Parser Text a #-}
82{-# SPECIALIZE choice :: [Z.Parser a] -> Z.Parser a #-}
79{-# SPECIALIZE choice :: [Z.Parser a] -> Z.Parser a #-}
83
80
@@ -132,7 +129,7 @@ many1' p = liftM2' (:) p (many' p)
132-- | @sepBy p sep@ applies /zero/ or more occurrences of @p@, separated
129-- | @sepBy p sep@ applies /zero/ or more occurrences of @p@, separated
133-- by @sep@. Returns a list of the values returned by @p@.
130-- by @sep@. Returns a list of the values returned by @p@.
134--
131--
135−-- > commaSep p = p `sepBy` (char ',')
132+-- > commaSep p = p `sepBy` (symbol ",")
136sepBy :: Alternative f => f a -> f s -> f [a]
133sepBy :: Alternative f => f a -> f s -> f [a]
137sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
134sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
138{-# SPECIALIZE sepBy :: Parser ByteString a -> Parser ByteString s
135{-# SPECIALIZE sepBy :: Parser ByteString a -> Parser ByteString s
@@ -144,7 +141,7 @@ sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
144-- by @sep@. Returns a list of the values returned by @p@. The value
141-- by @sep@. Returns a list of the values returned by @p@. The value
145-- returned by @p@ is forced to WHNF.
142-- returned by @p@ is forced to WHNF.
146--
143--
147−-- > commaSep p = p `sepBy'` (char ',')
144+-- > commaSep p = p `sepBy'` (symbol ",")
148sepBy' :: (MonadPlus m) => m a -> m s -> m [a]
145sepBy' :: (MonadPlus m) => m a -> m s -> m [a]
149sepBy' p s = scan `mplus` return []
146sepBy' p s = scan `mplus` return []
150 where scan = liftM2' (:) p ((s >> sepBy1' p s) `mplus` return [])
147 where scan = liftM2' (:) p ((s >> sepBy1' p s) `mplus` return [])
@@ -156,7 +153,7 @@ sepBy' p s = scan `mplus` return []
156-- | @sepBy1 p sep@ applies /one/ or more occurrences of @p@, separated
153-- | @sepBy1 p sep@ applies /one/ or more occurrences of @p@, separated
157-- by @sep@. Returns a list of the values returned by @p@.
154-- by @sep@. Returns a list of the values returned by @p@.
158--
155--
159−-- > commaSep p = p `sepBy1` (char ',')
156+-- > commaSep p = p `sepBy1` (symbol ",")
160sepBy1 :: Alternative f => f a -> f s -> f [a]
157sepBy1 :: Alternative f => f a -> f s -> f [a]
161sepBy1 p s = scan
158sepBy1 p s = scan
162 where scan = liftA2 (:) p ((s *> scan) <|> pure [])
159 where scan = liftA2 (:) p ((s *> scan) <|> pure [])
@@ -169,7 +166,7 @@ sepBy1 p s = scan
169-- by @sep@. Returns a list of the values returned by @p@. The value
166-- by @sep@. Returns a list of the values returned by @p@. The value
170-- returned by @p@ is forced to WHNF.
167-- returned by @p@ is forced to WHNF.
171--
168--
172−-- > commaSep p = p `sepBy1'` (char ',')
169+-- > commaSep p = p `sepBy1'` (symbol ",")
173sepBy1' :: (MonadPlus m) => m a -> m s -> m [a]
170sepBy1' :: (MonadPlus m) => m a -> m s -> m [a]
174sepBy1' p s = scan
171sepBy1' p s = scan
175 where scan = liftM2' (:) p ((s >> scan) `mplus` return [])
172 where scan = liftM2' (:) p ((s >> scan) `mplus` return [])
@@ -239,17 +236,39 @@ eitherP :: (Alternative f) => f a -> f b -> f (Either a b)
239eitherP a b = (Left <$> a) <|> (Right <$> b)
236eitherP a b = (Left <$> a) <|> (Right <$> b)
240{-# INLINE eitherP #-}
237{-# INLINE eitherP #-}
241
238
242−-- | If a parser has returned a 'T.Partial' result, supply it with more
239+-- | The parser @satisfyElem p@ succeeds for any chunk element for which the
243−-- input.
240+-- predicate @p@ returns 'True'. Returns the element that is
244−feed :: Monoid i => IResult i r -> i -> IResult i r
241+-- actually parsed.
245−feed (Fail t ctxs msg) d = Fail (mappend t d) ctxs msg
242+--
246−feed (Partial k) d = k d
243+-- >digit = satisfyElem isDigit
247−feed (Done t r) d = Done (mappend t d) r
244+-- > where isDigit c = c >= '0' && c <= '9'
248−{-# INLINE feed #-}
245+satisfyElem :: Chunk t => (ChunkElem t -> Bool) -> Parser t (ChunkElem t)
249−
246+satisfyElem p = do
250−-- | Apply a parser without consuming any input.
247+ c <- ensure 1
251−lookAhead :: Parser i a -> Parser i a
248+ let !h = unsafeChunkHead c
252−lookAhead p = Parser $ \t pos more lose succ ->
249+ if p h
253− let succ' t' _pos' more' = succ t' pos more'
250+ then put (unsafeChunkTail c) >> return h
254− in runParser p t pos more lose succ'
251+ else fail "satisfyElem"
252+{-# INLINE satisfyElem #-}
253+
254+-- | Match only if all input has been consumed.
255+endOfInput :: Chunk t => Parser t ()
256+endOfInput = Parser $ \i0 a0 m0 kf ks ->
257+ if nullChunk (unI i0)
258+ then if m0 == Complete
259+ then ks i0 a0 m0 ()
260+ else let kf' i1 a1 m1 _ _ = addS i0 a0 m0 i1 a1 m1 $
261+ \ i2 a2 m2 -> ks i2 a2 m2 ()
262+ ks' i1 a1 m1 _ = addS i0 a0 m0 i1 a1 m1 $
263+ \ i2 a2 m2 -> kf i2 a2 m2 []
264+ "endOfInput"
265+ in runParser demandInput i0 a0 m0 kf' ks'
266+ else kf i0 a0 m0 [] "endOfInput"
267+{-# SPECIALIZE endOfInput :: Parser ByteString () #-}
268+{-# SPECIALIZE endOfInput :: Parser Text () #-}
269+
270+-- | Return an indication of whether the end of input has been
271+-- reached.
272+atEnd :: Chunk t => Parser t Bool
273+atEnd = not <$> wantInput
255−{-# INLINE lookAhead #-}
274+{-# INLINE atEnd #-}
Data/Attoparsec/Internal.hs+79−125
@@ -1,7 +1,7 @@
1−{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables #-}
1+{-# LANGUAGE BangPatterns #-}
2-- |
2-- |
3-- Module : Data.Attoparsec.Internal
3-- Module : Data.Attoparsec.Internal
4−-- Copyright : Bryan O'Sullivan 2007-2015
4+-- Copyright : Bryan O'Sullivan 2012
5-- License : BSD3
5-- License : BSD3
6--
6--
7-- Maintainer : bos@serpentine.com
7-- Maintainer : bos@serpentine.com
@@ -12,160 +12,114 @@
12-- library.
12-- library.
13
13
14module Data.Attoparsec.Internal
14module Data.Attoparsec.Internal
15+ (
15− ( compareResults
16+ compareResults
17+ , get
18+ , put
19+ , ensure
16 , prompt
20 , prompt
17 , demandInput
21 , demandInput
18− , demandInput_
19 , wantInput
22 , wantInput
20− , endOfInput
21− , atEnd
22− , satisfyElem
23− , concatReverse
24 ) where
23 ) where
25
24
26−#if !MIN_VERSION_base(4,8,0)
27−import Control.Applicative ((<$>))
28−import Data.Monoid (Monoid, mconcat)
29−#endif
30import Data.Attoparsec.Internal.Types
25import Data.Attoparsec.Internal.Types
31import Data.ByteString (ByteString)
26import Data.ByteString (ByteString)
32import Data.Text (Text)
27import Data.Text (Text)
33−import Prelude hiding (succ)
34
28
35-- | Compare two 'IResult' values for equality.
29-- | Compare two 'IResult' values for equality.
36--
30--
37-- If both 'IResult's are 'Partial', the result will be 'Nothing', as
31-- If both 'IResult's are 'Partial', the result will be 'Nothing', as
38-- they are incomplete and hence their equality cannot be known.
32-- they are incomplete and hence their equality cannot be known.
39-- (This is why there is no 'Eq' instance for 'IResult'.)
33-- (This is why there is no 'Eq' instance for 'IResult'.)
40−compareResults :: (Eq i, Eq r) => IResult i r -> IResult i r -> Maybe Bool
34+compareResults :: (Eq t, Eq r) => IResult t r -> IResult t r -> Maybe Bool
41−compareResults (Fail t0 ctxs0 msg0) (Fail t1 ctxs1 msg1) =
35+compareResults (Fail i0 ctxs0 msg0) (Fail i1 ctxs1 msg1) =
42− Just (t0 == t1 && ctxs0 == ctxs1 && msg0 == msg1)
36+ Just (i0 == i1 && ctxs0 == ctxs1 && msg0 == msg1)
43−compareResults (Done t0 r0) (Done t1 r1) =
37+compareResults (Done i0 r0) (Done i1 r1) =
44− Just (t0 == t1 && r0 == r1)
38+ Just (i0 == i1 && r0 == r1)
45compareResults (Partial _) (Partial _) = Nothing
39compareResults (Partial _) (Partial _) = Nothing
46compareResults _ _ = Just False
40compareResults _ _ = Just False
47
41
42+get :: Parser t t
43+get = Parser $ \i0 a0 m0 _kf ks -> ks i0 a0 m0 (unI i0)
44+{-# INLINE get #-}
45+
46+put :: t -> Parser t ()
47+put c = Parser $ \_i0 a0 m0 _kf ks -> ks (I c) a0 m0 ()
48+{-# INLINE put #-}
49+
50+ensure' :: Chunk t
51+ => Int -> Input t -> Added t -> More -> Failure t r -> Success t t r
52+ -> IResult t r
53+ensure' !n0 i0 a0 m0 kf0 ks0 =
54+ runParser (demandInput >> go n0) i0 a0 m0 kf0 ks0
55+ where
56+ go !n = Parser $ \i a m kf ks ->
57+ if chunkLengthAtLeast (unI i) n
58+ then ks i a m (unI i)
59+ else runParser (demandInput >> go n) i a m kf ks
60+{-# SPECIALIZE ensure' :: Int -> Input ByteString -> Added ByteString -> More
61+ -> Failure ByteString r
62+ -> Success ByteString ByteString r
63+ -> IResult ByteString r #-}
64+{-# SPECIALIZE ensure' :: Int -> Input Text -> Added Text -> More
65+ -> Failure Text r -> Success Text Text r
66+ -> IResult Text r #-}
67+
68+-- | If at least @n@ elements of input are available, return the
69+-- current input, otherwise fail.
70+ensure :: Chunk t => Int -> Parser t t
71+ensure !n = Parser $ \i0 a0 m0 kf ks ->
72+ if chunkLengthAtLeast (unI i0) n
73+ then ks i0 a0 m0 (unI i0)
74+ -- The uncommon case is kept out-of-line to reduce code size:
75+ else ensure' n i0 a0 m0 kf ks
76+-- Non-recursive so the bounds check can be inlined:
77+{-# INLINE ensure #-}
78+
48−-- | Ask for input. If we receive any, pass the augmented input to a
79+-- | Ask for input. If we receive any, pass it to a success
49−-- success continuation, otherwise to a failure continuation.
80+-- continuation, otherwise to a failure continuation.
50prompt :: Chunk t
81prompt :: Chunk t
51− => State t -> Pos -> More
82+ => Input t -> Added t -> More
52− -> (State t -> Pos -> More -> IResult t r)
83+ -> (Input t -> Added t -> More -> IResult t r)
53− -> (State t -> Pos -> More -> IResult t r)
84+ -> (Input t -> Added t -> More -> IResult t r)
54 -> IResult t r
85 -> IResult t r
55−prompt t pos _more lose succ = Partial $ \s ->
86+prompt i0 a0 _m0 kf ks = Partial $ \s ->
56− if nullChunk s
87+ if nullChunk s
57− then lose t pos Complete
88+ then kf i0 a0 Complete
58− else succ (pappendChunk t s) pos Incomplete
89+ else ks (i0 <> I s) (a0 <> A s) Incomplete
59−{-# SPECIALIZE prompt :: State ByteString -> Pos -> More
90+{-# SPECIALIZE prompt :: Input ByteString -> Added ByteString -> More
60− -> (State ByteString -> Pos -> More
91+ -> (Input ByteString -> Added ByteString -> More
61 -> IResult ByteString r)
92 -> IResult ByteString r)
62− -> (State ByteString -> Pos -> More
93+ -> (Input ByteString -> Added ByteString -> More
63 -> IResult ByteString r)
94 -> IResult ByteString r)
64 -> IResult ByteString r #-}
95 -> IResult ByteString r #-}
65−{-# SPECIALIZE prompt :: State Text -> Pos -> More
96+{-# SPECIALIZE prompt :: Input Text -> Added Text -> More
66− -> (State Text -> Pos -> More -> IResult Text r)
97+ -> (Input Text -> Added Text -> More -> IResult Text r)
67− -> (State Text -> Pos -> More -> IResult Text r)
98+ -> (Input Text -> Added Text-> More -> IResult Text r)
68 -> IResult Text r #-}
99 -> IResult Text r #-}
69
100
70-- | Immediately demand more input via a 'Partial' continuation
101-- | Immediately demand more input via a 'Partial' continuation
71-- result.
102-- result.
72demandInput :: Chunk t => Parser t ()
103demandInput :: Chunk t => Parser t ()
73−demandInput = Parser $ \t pos more lose succ ->
104+demandInput = Parser $ \i0 a0 m0 kf ks ->
74− case more of
105+ if m0 == Complete
75− Complete -> lose t pos more [] "not enough input"
106+ then kf i0 a0 m0 ["demandInput"] "not enough input"
76− _ -> let lose' _ pos' more' = lose t pos' more' [] "not enough input"
107+ else let kf' i a m = kf i a m ["demandInput"] "not enough input"
77− succ' t' pos' more' = succ t' pos' more' ()
108+ ks' i a m = ks i a m ()
78− in prompt t pos more lose' succ'
109+ in prompt i0 a0 m0 kf' ks'
79{-# SPECIALIZE demandInput :: Parser ByteString () #-}
110{-# SPECIALIZE demandInput :: Parser ByteString () #-}
80{-# SPECIALIZE demandInput :: Parser Text () #-}
111{-# SPECIALIZE demandInput :: Parser Text () #-}
81
112
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−
95-- | This parser always succeeds. It returns 'True' if any input is
113-- | This parser always succeeds. It returns 'True' if any input is
96-- available either immediately or on demand, and 'False' if the end
114-- available either immediately or on demand, and 'False' if the end
97-- of all input has been reached.
115-- of all input has been reached.
98−wantInput :: forall t . Chunk t => Parser t Bool
116+wantInput :: Chunk t => Parser t Bool
99−wantInput = Parser $ \t pos more _lose succ ->
117+wantInput = Parser $ \i0 a0 m0 _kf ks ->
100 case () of
118 case () of
101− _ | pos < atBufferEnd (undefined :: t) t -> succ t pos more True
119+ _ | not (nullChunk (unI i0)) -> ks i0 a0 m0 True
102− | more == Complete -> succ t pos more False
120+ | m0 == Complete -> ks i0 a0 m0 False
103− | otherwise -> let lose' t' pos' more' = succ t' pos' more' False
121+ | otherwise -> let kf' i a m = ks i a m False
104− succ' t' pos' more' = succ t' pos' more' True
122+ ks' i a m = ks i a m True
105− in prompt t pos more lose' succ'
123+ in prompt i0 a0 m0 kf' ks'
106−{-# INLINE wantInput #-}
107−
108−-- | Match only if all input has been consumed.
109−endOfInput :: forall t . Chunk t => Parser t ()
110−endOfInput = Parser $ \t pos more lose succ ->
111− case () of
112− _| pos < atBufferEnd (undefined :: t) t -> lose t pos more [] "endOfInput"
113− | more == Complete -> succ t pos more ()
114− | otherwise ->
115− let lose' t' pos' more' _ctx _msg = succ t' pos' more' ()
116− succ' t' pos' more' _a = lose t' pos' more' [] "endOfInput"
117− in runParser demandInput t pos more lose' succ'
118−{-# SPECIALIZE endOfInput :: Parser ByteString () #-}
124+{-# SPECIALIZE wantInput :: Parser ByteString Bool #-}
119−{-# SPECIALIZE endOfInput :: Parser Text () #-}
125+{-# SPECIALIZE wantInput :: Parser Text Bool #-}
120−
121−-- | Return an indication of whether the end of input has been
122−-- reached.
123−atEnd :: Chunk t => Parser t Bool
124−atEnd = not <$> wantInput
125−{-# INLINE atEnd #-}
126−
127−satisfySuspended :: forall t r . Chunk t
128− => (ChunkElem t -> Bool)
129− -> State t -> Pos -> More
130− -> Failure t (State t) r
131− -> Success t (State t) (ChunkElem t) r
132− -> IResult t r
133−satisfySuspended p t pos more lose succ =
134− runParser (demandInput >> go) t pos more lose succ
135− where go = Parser $ \t' pos' more' lose' succ' ->
136− case bufferElemAt (undefined :: t) pos' t' of
137− Just (e, l) | p e -> succ' t' (pos' + Pos l) more' e
138− | otherwise -> lose' t' pos' more' [] "satisfyElem"
139− Nothing -> runParser (demandInput >> go) t' pos' more' lose' succ'
140−{-# SPECIALIZE satisfySuspended :: (ChunkElem ByteString -> Bool)
141− -> State ByteString -> Pos -> More
142− -> Failure ByteString (State ByteString) r
143− -> Success ByteString (State ByteString)
144− (ChunkElem ByteString) r
145− -> IResult ByteString r #-}
146−{-# SPECIALIZE satisfySuspended :: (ChunkElem Text -> Bool)
147− -> State Text -> Pos -> More
148− -> Failure Text (State Text) r
149− -> Success Text (State Text)
150− (ChunkElem Text) r
151− -> IResult Text r #-}
152−
153−-- | The parser @satisfyElem p@ succeeds for any chunk element for which the
154−-- predicate @p@ returns 'True'. Returns the element that is
155−-- actually parsed.
156−satisfyElem :: forall t . Chunk t
157− => (ChunkElem t -> Bool) -> Parser t (ChunkElem t)
158−satisfyElem p = Parser $ \t pos more lose succ ->
159− case bufferElemAt (undefined :: t) pos t of
160− Just (e, l) | p e -> succ t (pos + Pos l) more e
161− | otherwise -> lose t pos more [] "satisfyElem"
162− Nothing -> satisfySuspended p t pos more lose succ
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/Fhthagn.hsdeleted−18
@@ -1,18 +0,0 @@
1−{-# LANGUAGE BangPatterns, Rank2Types, OverloadedStrings,
2− RecordWildCards, MagicHash, UnboxedTuples #-}
3−
4−module Data.Attoparsec.Internal.Fhthagn
5− (
6− inlinePerformIO
7− ) where
8−
9−import GHC.Base (realWorld#)
10−import GHC.IO (IO(IO))
11−
12−-- | Just like unsafePerformIO, but we inline it. Big performance gains as
13−-- it exposes lots of things to further inlining. /Very unsafe/. In
14−-- particular, you should do no memory allocation inside an
15−-- 'inlinePerformIO' block. On Hugs this is just @unsafePerformIO@.
16−inlinePerformIO :: IO a -> a
17−inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
18−{-# INLINE inlinePerformIO #-}
Data/Attoparsec/Internal/Types.hs+162−152
@@ -1,8 +1,8 @@
1−{-# LANGUAGE CPP, BangPatterns, GeneralizedNewtypeDeriving, OverloadedStrings,
1+{-# LANGUAGE BangPatterns, CPP, 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-2015
5+-- Copyright : Bryan O'Sullivan 2007-2011
6-- License : BSD3
6-- License : BSD3
7--
7--
8-- Maintainer : bos@serpentine.com
8-- Maintainer : bos@serpentine.com
@@ -15,82 +15,78 @@
15module Data.Attoparsec.Internal.Types
15module Data.Attoparsec.Internal.Types
16 (
16 (
17 Parser(..)
17 Parser(..)
18− , State
19 , Failure
18 , Failure
20 , Success
19 , Success
21− , Pos(..)
22 , IResult(..)
20 , IResult(..)
21+ , Input(..)
22+ , Added(..)
23 , More(..)
23 , More(..)
24+ , addS
24 , (<>)
25 , (<>)
25 , Chunk(..)
26 , Chunk(..)
26 ) where
27 ) where
27
28
28−import Control.Applicative as App (Applicative(..), (<$>))
29+import Control.Applicative (Alternative(..), Applicative(..), (<$>))
29−import Control.Applicative (Alternative(..))
30import Control.DeepSeq (NFData(rnf))
30import Control.DeepSeq (NFData(rnf))
31import 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(..))
35−import Data.Word (Word8)
36import Data.ByteString (ByteString)
32import Data.ByteString (ByteString)
37−import qualified Data.ByteString as BS
38import Data.ByteString.Internal (w2c)
33import Data.ByteString.Internal (w2c)
34+import Data.Monoid (Monoid(..))
39import Data.Text (Text)
35import Data.Text (Text)
40−import qualified Data.Text as Text
36+import Data.Word (Word8)
41−import Data.Text.Unsafe (Iter(..))
42−import Prelude hiding (succ)
37+import Prelude hiding (getChar, take, takeWhile)
43−import qualified Data.Attoparsec.ByteString.Buffer as B
38+import qualified Data.ByteString as BS
44−import qualified Data.Attoparsec.Text.Buffer as T
39+import qualified Data.ByteString.Unsafe as BS
45−
40+import qualified Data.Text as T
46−newtype Pos = Pos { fromPos :: Int }
41+import qualified Data.Text.Unsafe as T
47− deriving (Eq, Ord, Show, Num)
48
42
49−-- | The result of a parse. This is parameterised over the type @i@
43+-- | The result of a parse. This is parameterised over the type @t@
50-- of string that was processed.
44-- of string that was processed.
51--
45--
52-- This type is an instance of 'Functor', where 'fmap' transforms the
46-- This type is an instance of 'Functor', where 'fmap' transforms the
53-- value in a 'Done' result.
47-- value in a 'Done' result.
54−data IResult i r =
55− Fail i [String] String
48+data IResult t r = Fail t [String] String
56− -- ^ The parse failed. The @i@ parameter is the input that had
49+ -- ^ The parse failed. The 't' parameter is the
57− -- not yet been consumed when the failure occurred. The
50+ -- input that had not yet been consumed when the
51+ -- failure occurred. The @[@'String'@]@ is a list of
58− -- @[@'String'@]@ is a list of contexts in which the error
52+ -- contexts in which the error occurred. The
59− -- occurred. The 'String' is the message describing the error, if
53+ -- 'String' is the message describing the error, if
60− -- any.
54+ -- any.
61− | Partial (i -> IResult i r)
55+ | Partial (t -> IResult t r)
62− -- ^ Supply this continuation with more input so that the parser
56+ -- ^ Supply this continuation with more input so that
63− -- can resume. To indicate that no more input is available, pass
57+ -- the parser can resume. To indicate that no more
64− -- an empty string to the continuation.
58+ -- input is available, use an empty string.
65− --
66− -- __Note__: if you get a 'Partial' result, do not call its
67− -- continuation more than once.
68− | Done i r
59+ | Done t r
69− -- ^ The parse succeeded. The @i@ parameter is the input that had
60+ -- ^ The parse succeeded. The 't' parameter is the
70− -- not yet been consumed (if any) when the parse succeeded.
61+ -- input that had not yet been consumed (if any) when
71−
62+ -- the parse succeeded.
63+
72−instance (Show i, Show r) => Show (IResult i r) where
64+instance (Show t, Show r) => Show (IResult t r) where
73− showsPrec d ir = showParen (d > 10) $
65+ show (Fail t stk msg) =
74− case ir of
66+ "Fail " ++ show t ++ " " ++ show stk ++ " " ++ show msg
75− (Fail t stk msg) -> showString "Fail" . f t . f stk . f msg
76− (Partial _) -> showString "Partial _"
67+ show (Partial _) = "Partial _"
77− (Done t r) -> showString "Done" . f t . f r
68+ show (Done t r) = "Done " ++ show t ++ " " ++ show r
78− where f :: Show a => a -> ShowS
69+
79− f x = showChar ' ' . showsPrec 11 x
80−
81−instance (NFData i, NFData r) => NFData (IResult i r) where
70+instance (NFData t, NFData r) => NFData (IResult t r) where
82 rnf (Fail t stk msg) = rnf t `seq` rnf stk `seq` rnf msg
71 rnf (Fail t stk msg) = rnf t `seq` rnf stk `seq` rnf msg
83 rnf (Partial _) = ()
72 rnf (Partial _) = ()
84 rnf (Done t r) = rnf t `seq` rnf r
73 rnf (Done t r) = rnf t `seq` rnf r
85 {-# INLINE rnf #-}
74 {-# INLINE rnf #-}
86
75
76+fmapR :: (a -> b) -> IResult t a -> IResult t b
77+fmapR _ (Fail t stk msg) = Fail t stk msg
78+fmapR f (Partial k) = Partial (fmapR f . k)
79+fmapR f (Done t r) = Done t (f r)
80+
87−instance Functor (IResult i) where
81+instance Functor (IResult t) where
88− fmap _ (Fail t stk msg) = Fail t stk msg
82+ fmap = fmapR
89− fmap f (Partial k) = Partial (fmap f . k)
83+ {-# INLINE fmap #-}
90− fmap f (Done t r) = Done t (f r)
84+
85+newtype Input t = I {unI :: t} deriving (Monoid)
86+newtype Added t = A {unA :: t} deriving (Monoid)
91
87
92−-- | The core parser type. This is parameterised over the type @i@
88+-- | The core parser type. This is parameterised over the type @t@ of
93−-- of string being processed.
89+-- string being processed.
94--
90--
95-- This type is an instance of the following classes:
91-- This type is an instance of the following classes:
96--
92--
@@ -103,115 +99,123 @@ instance Functor (IResult i) where
103-- 'mplus' executes the right-hand parser if the left-hand one
99-- 'mplus' executes the right-hand parser if the left-hand one
104-- fails. When the parser on the right executes, the input is reset
100-- fails. When the parser on the right executes, the input is reset
105-- to the same state as the parser on the left started with. (In
101-- to the same state as the parser on the left started with. (In
106−-- other words, attoparsec is a backtracking parser that supports
102+-- other words, Attoparsec is a backtracking parser that supports
107-- arbitrary lookahead.)
103-- arbitrary lookahead.)
108--
104--
109-- * 'Alternative', which follows 'MonadPlus'.
105-- * 'Alternative', which follows 'MonadPlus'.
110−newtype Parser i a = Parser {
106+newtype Parser t a = Parser {
111− runParser :: forall r.
107+ runParser :: forall r. Input t -> Added t -> More
112− State i -> Pos -> More
113− -> Failure i (State i) r
108+ -> Failure t r
114− -> Success i (State i) a r
109+ -> Success t a r
115− -> IResult i r
110+ -> IResult t r
116 }
111 }
117
112
118−type family State i
119−type instance State ByteString = B.Buffer
120−type instance State Text = T.Buffer
121−
122−type Failure i t r = t -> Pos -> More -> [String] -> String
113+type Failure t r = Input t -> Added t -> More -> [String] -> String
123− -> IResult i r
114+ -> IResult t r
124−type Success i t a r = t -> Pos -> More -> a -> IResult i r
115+type Success t a r = Input t -> Added t -> More -> a -> IResult t r
125
116
126-- | Have we read all available input?
117-- | Have we read all available input?
127data More = Complete | Incomplete
118data More = Complete | Incomplete
128 deriving (Eq, Show)
119 deriving (Eq, Show)
129
120
130−instance Semigroup More where
121+instance Monoid More where
131− c@Complete <> _ = c
122+ mappend c@Complete _ = c
132− _ <> m = m
123+ mappend _ m = m
133−
134−instance Mon.Monoid More where
135− mappend = (<>)
136− mempty = Incomplete
124+ mempty = Incomplete
137−
125+
126+addS :: (Monoid t) =>
127+ Input t -> Added t -> More
128+ -> Input t -> Added t -> More
129+ -> (Input t -> Added t -> More -> r) -> r
130+addS i0 a0 m0 _i1 a1 m1 f =
131+ let !i = i0 <> I (unA a1)
132+ a = a0 <> a1
133+ !m = m0 <> m1
134+ in f i a m
135+{-# INLINE addS #-}
136+
137+bindP :: Parser t a -> (a -> Parser t b) -> Parser t b
138+bindP m g =
139+ Parser $ \i0 a0 m0 kf ks -> runParser m i0 a0 m0 kf $
140+ \i1 a1 m1 a -> runParser (g a) i1 a1 m1 kf ks
141+{-# INLINE bindP #-}
142+
143+returnP :: a -> Parser t a
144+returnP a = Parser (\i0 a0 m0 _kf ks -> ks i0 a0 m0 a)
145+{-# INLINE returnP #-}
146+
138−instance Monad (Parser i) where
147+instance Monad (Parser t) where
139−#if !(MIN_VERSION_base(4,13,0))
148+ return = returnP
140− fail = Fail.fail
149+ (>>=) = bindP
141− {-# INLINE fail #-}
150+ fail = failDesc
142−#endif
151+
143−
152+noAdds :: (Monoid t) =>
144− return = App.pure
153+ Input t -> Added t -> More
145− {-# INLINE return #-}
154+ -> (Input t -> Added t -> More -> r) -> r
146−
155+noAdds i0 _a0 m0 f = f i0 mempty m0
147− m >>= k = Parser $ \t !pos more lose succ ->
148− let succ' t' !pos' more' a = runParser (k a) t' pos' more' lose succ
149− in runParser m t pos more lose succ'
150− {-# INLINE (>>=) #-}
156+{-# INLINE noAdds #-}
151−
157+
152− (>>) = (*>)
158+plus :: (Monoid t) => Parser t a -> Parser t a -> Parser t a
153− {-# INLINE (>>) #-}
159+plus a b = Parser $ \i0 a0 m0 kf ks ->
154−
160+ let kf' i1 a1 m1 _ _ = addS i0 a0 m0 i1 a1 m1 $
155−
161+ \ i2 a2 m2 -> runParser b i2 a2 m2 kf ks
156−instance Fail.MonadFail (Parser i) where
162+ ks' i1 a1 m1 = ks i1 (a0 <> a1) m1
157− fail err = Parser $ \t pos more lose _succ -> lose t pos more [] msg
163+ in noAdds i0 a0 m0 $ \i2 a2 m2 -> runParser a i2 a2 m2 kf' ks'
158− where msg = "Failed reading: " ++ err
164+
159− {-# INLINE fail #-}
160−
161−plus :: Parser i a -> Parser i a -> Parser i a
162−plus f g = Parser $ \t pos more lose succ ->
163− let lose' t' _pos' more' _ctx _msg = runParser g t' pos more' lose succ
164− in runParser f t pos more lose' succ
165−
166−instance MonadPlus (Parser i) where
165+instance (Monoid t) => MonadPlus (Parser t) where
167− mzero = fail "mzero"
166+ mzero = failDesc "mzero"
168 {-# INLINE mzero #-}
167 {-# INLINE mzero #-}
169 mplus = plus
168 mplus = plus
170
169
170+fmapP :: (a -> b) -> Parser t a -> Parser t b
171+fmapP p m = Parser $ \i0 a0 m0 f k ->
172+ runParser m i0 a0 m0 f $ \i1 a1 s1 a -> k i1 a1 s1 (p a)
173+{-# INLINE fmapP #-}
174+
171−instance Functor (Parser i) where
175+instance Functor (Parser t) where
172− fmap f p = Parser $ \t pos more lose succ ->
176+ fmap = fmapP
173− let succ' t' pos' more' a = succ t' pos' more' (f a)
174− in runParser p t pos more lose succ'
175 {-# INLINE fmap #-}
177 {-# INLINE fmap #-}
176
178
177−apP :: Parser i (a -> b) -> Parser i a -> Parser i b
179+apP :: Parser t (a -> b) -> Parser t a -> Parser t b
178apP d e = do
180apP d e = do
179 b <- d
181 b <- d
180 a <- e
182 a <- e
181 return (b a)
183 return (b a)
182{-# INLINE apP #-}
184{-# INLINE apP #-}
183
185
184−instance Applicative (Parser i) where
186+instance Applicative (Parser t) where
185− pure v = Parser $ \t !pos more _lose succ -> succ t pos more v
187+ pure = returnP
186 {-# INLINE pure #-}
188 {-# INLINE pure #-}
187 (<*>) = apP
189 (<*>) = apP
188 {-# INLINE (<*>) #-}
190 {-# INLINE (<*>) #-}
189− m *> k = m >>= \_ -> k
191+
192+#if MIN_VERSION_base(4,2,0)
193+ -- These definitions are equal to the defaults, but this
194+ -- way the optimizer doesn't have to work so hard to figure
195+ -- that out.
196+ (*>) = (>>)
190 {-# INLINE (*>) #-}
197 {-# INLINE (*>) #-}
191− x <* y = x >>= \a -> y >> pure a
198+ x <* y = x >>= \a -> y >> return a
192 {-# INLINE (<*) #-}
199 {-# INLINE (<*) #-}
200+#endif
193
201
194−instance Semigroup (Parser i a) where
195− (<>) = plus
196− {-# INLINE (<>) #-}
197−
198−instance Monoid (Parser i a) where
202+instance (Monoid t) => Monoid (Parser t a) where
199− mempty = fail "mempty"
203+ mempty = failDesc "mempty"
200 {-# INLINE mempty #-}
204 {-# INLINE mempty #-}
201− mappend = (<>)
205+ mappend = plus
202 {-# INLINE mappend #-}
206 {-# INLINE mappend #-}
203
207
204−instance Alternative (Parser i) where
208+instance (Monoid t) => Alternative (Parser t) where
205− empty = fail "empty"
209+ empty = failDesc "empty"
206 {-# INLINE empty #-}
210 {-# INLINE empty #-}
207
211
208 (<|>) = plus
212 (<|>) = plus
209 {-# INLINE (<|>) #-}
213 {-# INLINE (<|>) #-}
210
214
215+#if MIN_VERSION_base(4,2,0)
211 many v = many_v
216 many v = many_v
212− where
213− many_v = some_v <|> pure []
217+ where many_v = some_v <|> pure []
214− some_v = (:) <$> v <*> many_v
218+ some_v = (:) <$> v <*> many_v
215 {-# INLINE many #-}
219 {-# INLINE many #-}
216
220
217 some v = some_v
221 some v = some_v
@@ -219,18 +223,28 @@ instance Alternative (Parser i) where
219 many_v = some_v <|> pure []
223 many_v = some_v <|> pure []
220 some_v = (:) <$> v <*> many_v
224 some_v = (:) <$> v <*> many_v
221 {-# INLINE some #-}
225 {-# INLINE some #-}
226+#endif
227+
228+failDesc :: String -> Parser t a
229+failDesc err = Parser (\i0 a0 m0 kf _ks -> kf i0 a0 m0 [] msg)
230+ where msg = "Failed reading: " ++ err
231+{-# INLINE failDesc #-}
232+
233+(<>) :: (Monoid m) => m -> m -> m
234+(<>) = mappend
235+{-# INLINE (<>) #-}
222
236
223-- | A common interface for input chunks.
237-- | A common interface for input chunks.
224class Monoid c => Chunk c where
238class Monoid c => Chunk c where
225 type ChunkElem c
239 type ChunkElem c
226 -- | Test if the chunk is empty.
240 -- | Test if the chunk is empty.
227 nullChunk :: c -> Bool
241 nullChunk :: c -> Bool
228− -- | Append chunk to a buffer.
242+ -- | Get the head element of a non-empty chunk.
229− pappendChunk :: State c -> c -> State c
243+ unsafeChunkHead :: c -> ChunkElem c
230− -- | Position at the end of a buffer. The first argument is ignored.
244+ -- | Get the tail of a non-empty chunk.
231− atBufferEnd :: c -> State c -> Pos
245+ unsafeChunkTail :: c -> c
232− -- | Return the buffer element at the given position along with its length.
246+ -- | Check if the chunk has the length of at least @n@ elements.
233− bufferElemAt :: c -> Pos -> State c -> Maybe (ChunkElem c, Int)
247+ chunkLengthAtLeast :: c -> Int -> Bool
234 -- | Map an element to the corresponding character.
248 -- | Map an element to the corresponding character.
235 -- The first argument is ignored.
249 -- The first argument is ignored.
236 chunkElemToChar :: c -> ChunkElem c -> Char
250 chunkElemToChar :: c -> ChunkElem c -> Char
@@ -239,28 +253,24 @@ instance Chunk ByteString where
239 type ChunkElem ByteString = Word8
253 type ChunkElem ByteString = Word8
240 nullChunk = BS.null
254 nullChunk = BS.null
241 {-# INLINE nullChunk #-}
255 {-# INLINE nullChunk #-}
242− pappendChunk = B.pappend
256+ unsafeChunkHead = BS.unsafeHead
243− {-# INLINE pappendChunk #-}
257+ {-# INLINE unsafeChunkHead #-}
244− atBufferEnd _ = Pos . B.length
258+ unsafeChunkTail = BS.unsafeTail
245− {-# INLINE atBufferEnd #-}
259+ {-# INLINE unsafeChunkTail #-}
246− bufferElemAt _ (Pos i) buf
260+ chunkLengthAtLeast bs n = BS.length bs >= n
247− | i < B.length buf = Just (B.unsafeIndex buf i, 1)
261+ {-# INLINE chunkLengthAtLeast #-}
248− | otherwise = Nothing
249− {-# INLINE bufferElemAt #-}
250− chunkElemToChar _ = w2c
262+ chunkElemToChar = const w2c
251 {-# INLINE chunkElemToChar #-}
263 {-# INLINE chunkElemToChar #-}
252
264
253instance Chunk Text where
265instance Chunk Text where
254 type ChunkElem Text = Char
266 type ChunkElem Text = Char
255− nullChunk = Text.null
267+ nullChunk = T.null
256 {-# INLINE nullChunk #-}
268 {-# INLINE nullChunk #-}
257− pappendChunk = T.pappend
269+ unsafeChunkHead = T.unsafeHead
258− {-# INLINE pappendChunk #-}
270+ {-# INLINE unsafeChunkHead #-}
259− atBufferEnd _ = Pos . T.length
271+ unsafeChunkTail = T.unsafeTail
260− {-# INLINE atBufferEnd #-}
272+ {-# INLINE unsafeChunkTail #-}
261− bufferElemAt _ (Pos i) buf
273+ chunkLengthAtLeast t n = T.lengthWord16 t `quot` 2 >= n || T.length t >= n
262− | i < T.length buf = let Iter c l = T.iter buf i in Just (c, l)
274+ {-# INLINE chunkLengthAtLeast #-}
263− | otherwise = Nothing
264− {-# INLINE bufferElemAt #-}
265− chunkElemToChar _ = id
275+ chunkElemToChar = const id
266 {-# INLINE chunkElemToChar #-}
276 {-# INLINE chunkElemToChar #-}
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-2015
3+-- Copyright : Bryan O'Sullivan 2010
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
Data/Attoparsec/Number.hs+2−15
@@ -1,25 +1,16 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2-- |
2-- |
3-- Module : Data.Attoparsec.Number
3-- Module : Data.Attoparsec.Number
4−-- Copyright : Bryan O'Sullivan 2007-2015
4+-- Copyright : Bryan O'Sullivan 2011
5-- License : BSD3
5-- License : BSD3
6--
6--
7-- Maintainer : bos@serpentine.com
7-- Maintainer : bos@serpentine.com
8-- Stability : experimental
8-- Stability : experimental
9-- Portability : unknown
9-- Portability : unknown
10--
10--
11−-- This module is deprecated, and both the module and 'Number' type
12−-- will be removed in the next major release. Use the
13−-- <http://hackage.haskell.org/package/scientific scientific> package
14−-- and the 'Data.Scientific.Scientific' type instead.
15−--
16-- A simple number type, useful for parsing both exact and inexact
11-- A simple number type, useful for parsing both exact and inexact
17-- quantities without losing much precision.
12-- quantities without losing much precision.
18−module Data.Attoparsec.Number
13+module Data.Attoparsec.Number ( Number(..) ) where
19− {-# DEPRECATED "This module will be removed in the next major release." #-}
20− (
21− Number(..)
22− ) where
23
14
24import Control.DeepSeq (NFData(rnf))
15import Control.DeepSeq (NFData(rnf))
25import Data.Data (Data)
16import Data.Data (Data)
@@ -28,13 +19,9 @@ import Data.Typeable (Typeable)
28
19
29-- | A numeric type that can represent integers accurately, and
20-- | A numeric type that can represent integers accurately, and
30-- floating point numbers to the precision of a 'Double'.
21-- floating point numbers to the precision of a 'Double'.
31−--
32−-- /Note/: this type is deprecated, and will be removed in the next
33−-- major release. Use the 'Data.Scientific.Scientific' type instead.
34data Number = I !Integer
22data Number = I !Integer
35 | D {-# UNPACK #-} !Double
23 | D {-# UNPACK #-} !Double
36 deriving (Typeable, Data)
24 deriving (Typeable, Data)
37−{-# DEPRECATED Number "Use Scientific instead." #-}
38
25
39instance Show Number where
26instance Show Number where
40 show (I a) = show a
27 show (I a) = show a
Data/Attoparsec/Text.hs+74−106
@@ -1,12 +1,8 @@
1−{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, TypeSynonymInstances #-}
1+{-# LANGUAGE BangPatterns, FlexibleInstances, TypeSynonymInstances #-}
2−#if __GLASGOW_HASKELL__ >= 702
3−{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4−#endif
5−{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
6
2
7-- |
3-- |
8-- Module : Data.Attoparsec.Text
4-- Module : Data.Attoparsec.Text
9−-- Copyright : Bryan O'Sullivan 2007-2015
5+-- Copyright : Bryan O'Sullivan 2011
10-- License : BSD3
6-- License : BSD3
11--
7--
12-- Maintainer : bos@serpentine.com
8-- Maintainer : bos@serpentine.com
@@ -44,6 +40,9 @@ module Data.Attoparsec.Text
44 , maybeResult
40 , maybeResult
45 , eitherResult
41 , eitherResult
46
42
43+ -- * Combinators
44+ , module Data.Attoparsec.Combinator
45+
47 -- * Parsing individual characters
46 -- * Parsing individual characters
48 , I.char
47 , I.char
49 , I.anyChar
48 , I.anyChar
@@ -72,7 +71,6 @@ module Data.Attoparsec.Text
72 , skipSpace
71 , skipSpace
73 , I.skipWhile
72 , I.skipWhile
74 , I.scan
73 , I.scan
75− , I.runScanner
76 , I.take
74 , I.take
77 , I.takeWhile
75 , I.takeWhile
78 , I.takeWhile1
76 , I.takeWhile1
@@ -101,36 +99,9 @@ module Data.Attoparsec.Text
101 , number
99 , number
102 , rational
100 , rational
103 , scientific
101 , scientific
104−
105− -- * Combinators
106− , try
107− , (<?>)
108− , choice
109− , count
110− , option
111− , many'
112− , many1
113− , many1'
114− , manyTill
115− , manyTill'
116− , sepBy
117− , sepBy'
118− , sepBy1
119− , sepBy1'
120− , skipMany
121− , skipMany1
122− , eitherP
123− , I.match
124− -- * State observation and manipulation functions
125− , I.endOfInput
126− , I.atEnd
127 ) where
102 ) where
128
103
129−#if !MIN_VERSION_base(4,8,0)
130−import Control.Applicative (pure, (*>), (<*), (<$>))
104+import Control.Applicative (pure, (<$>), (*>), (<*), (<|>))
131−import Data.Word (Word)
132−#endif
133−import Control.Applicative ((<|>))
134import Data.Attoparsec.Combinator
105import Data.Attoparsec.Combinator
135import Data.Attoparsec.Number (Number(..))
106import Data.Attoparsec.Number (Number(..))
136import Data.Scientific (Scientific)
107import Data.Scientific (Scientific)
@@ -139,9 +110,8 @@ import Data.Attoparsec.Text.Internal (Parser, Result, parse, takeWhile1)
139import Data.Bits (Bits, (.|.), shiftL)
110import Data.Bits (Bits, (.|.), shiftL)
140import Data.Char (isAlpha, isDigit, isSpace, ord)
111import Data.Char (isAlpha, isDigit, isSpace, ord)
141import Data.Int (Int8, Int16, Int32, Int64)
112import Data.Int (Int8, Int16, Int32, Int64)
142−import Data.List (intercalate)
143import Data.Text (Text)
113import Data.Text (Text)
144−import Data.Word (Word8, Word16, Word32, Word64)
114+import Data.Word (Word8, Word16, Word32, Word64, Word)
145import qualified Data.Attoparsec.Internal as I
115import qualified Data.Attoparsec.Internal as I
146import qualified Data.Attoparsec.Internal.Types as T
116import qualified Data.Attoparsec.Internal.Types as T
147import qualified Data.Attoparsec.Text.Internal as I
117import qualified Data.Attoparsec.Text.Internal as I
@@ -149,50 +119,49 @@ import qualified Data.Text as T
149
119
150-- $parsec
120-- $parsec
151--
121--
152−-- Compared to Parsec 3, attoparsec makes several tradeoffs. It is
122+-- Compared to Parsec 3, Attoparsec makes several tradeoffs. It is
153-- not intended for, or ideal for, all possible uses.
123-- not intended for, or ideal for, all possible uses.
154--
124--
155−-- * While attoparsec can consume input incrementally, Parsec cannot.
125+-- * While Attoparsec can consume input incrementally, Parsec cannot.
156-- Incremental input is a huge deal for efficient and secure network
126-- Incremental input is a huge deal for efficient and secure network
157-- and system programming, since it gives much more control to users
127-- and system programming, since it gives much more control to users
158-- of the library over matters such as resource usage and the I/O
128-- of the library over matters such as resource usage and the I/O
159-- model to use.
129-- model to use.
160--
130--
161−-- * Much of the performance advantage of attoparsec is gained via
131+-- * Much of the performance advantage of Attoparsec is gained via
162-- high-performance parsers such as 'I.takeWhile' and 'I.string'.
132-- high-performance parsers such as 'I.takeWhile' and 'I.string'.
163-- If you use complicated combinators that return lists of
133-- If you use complicated combinators that return lists of
164-- characters, there is less performance difference between the two
134-- characters, there is less performance difference between the two
165-- libraries.
135-- libraries.
166--
136--
167−-- * Unlike Parsec 3, attoparsec does not support being used as a
137+-- * Unlike Parsec 3, Attoparsec does not support being used as a
168-- monad transformer.
138-- monad transformer.
169--
139--
170−-- * attoparsec is specialised to deal only with strict 'Text'
140+-- * Attoparsec is specialised to deal only with strict 'Text'
171-- input. Efficiency concerns rule out both lists and lazy text.
141-- input. Efficiency concerns rule out both lists and lazy text.
172-- The usual use for lazy text would be to allow consumption of very
142-- The usual use for lazy text would be to allow consumption of very
173-- large input without a large footprint. For this need,
143-- large input without a large footprint. For this need,
174−-- attoparsec's incremental input provides an excellent substitute,
144+-- Attoparsec's incremental input provides an excellent substitute,
175-- with much more control over when input takes place. If you must
145-- with much more control over when input takes place. If you must
176-- use lazy text, see the 'Lazy' module, which feeds lazy chunks to
146-- use lazy text, see the 'Lazy' module, which feeds lazy chunks to
177-- a regular parser.
147-- a regular parser.
178--
148--
179-- * Parsec parsers can produce more helpful error messages than
149-- * Parsec parsers can produce more helpful error messages than
180−-- attoparsec parsers. This is a matter of focus: attoparsec avoids
150+-- Attoparsec parsers. This is a matter of focus: Attoparsec avoids
181-- the extra book-keeping in favour of higher performance.
151-- the extra book-keeping in favour of higher performance.
182
152
183-- $incremental
153-- $incremental
184--
154--
185−-- attoparsec supports incremental input, meaning that you can feed it
155+-- Attoparsec supports incremental input, meaning that you can feed it
186-- a 'Text' that represents only part of the expected total amount
156-- a 'Text' that represents only part of the expected total amount
187-- of data to parse. If your parser reaches the end of a fragment of
157-- of data to parse. If your parser reaches the end of a fragment of
188-- input and could consume more input, it will suspend parsing and
158-- input and could consume more input, it will suspend parsing and
189-- return a 'T.Partial' continuation.
159-- return a 'T.Partial' continuation.
190--
160--
191-- Supplying the 'T.Partial' continuation with another string will
161-- Supplying the 'T.Partial' continuation with another string will
192−-- resume parsing at the point where it was suspended, with the string
162+-- resume parsing at the point where it was suspended. You must be
193−-- you supplied used as new input at the end of the existing
194−-- input. You must be prepared for the result of the resumed parse to
163+-- prepared for the result of the resumed parse to be another
195−-- be another 'Partial' continuation.
164+-- 'Partial' continuation.
196--
165--
197-- To indicate that you have no more input, supply the 'Partial'
166-- To indicate that you have no more input, supply the 'Partial'
198-- continuation with an 'T.empty' 'Text'.
167-- continuation with an 'T.empty' 'Text'.
@@ -204,19 +173,12 @@ import qualified Data.Text as T
204-- If you do not need support for incremental input, consider using
173-- If you do not need support for incremental input, consider using
205-- the 'I.parseOnly' function to run your parser. It will never
174-- the 'I.parseOnly' function to run your parser. It will never
206-- prompt for more input.
175-- prompt for more input.
207−--
208−-- /Note/: incremental input does /not/ imply that attoparsec will
209−-- release portions of its internal state for garbage collection as it
210−-- proceeds. Its internal representation is equivalent to a single
211−-- 'Text': if you feed incremental input to an a parser, it will
212−-- require memory proportional to the amount of input you supply.
213−-- (This is necessary to support arbitrary backtracking.)
214
176
215-- $performance
177-- $performance
216--
178--
217−-- If you write an attoparsec-based parser carefully, it can be
179+-- If you write an Attoparsec-based parser carefully, it can be
218−-- realistic to expect it to perform similarly to a hand-rolled C
180+-- realistic to expect it to perform within a factor of 2 of a
219−-- parser (measuring megabytes parsed per second).
181+-- hand-rolled C parser (measuring megabytes parsed per second).
220--
182--
221-- To actually achieve high performance, there are a few guidelines
183-- To actually achieve high performance, there are a few guidelines
222-- that it is useful to follow.
184-- that it is useful to follow.
@@ -237,6 +199,14 @@ import qualified Data.Text as T
237-- Make active use of benchmarking and profiling tools to measure,
199-- Make active use of benchmarking and profiling tools to measure,
238-- find the problems with, and improve the performance of your parser.
200-- find the problems with, and improve the performance of your parser.
239
201
202+-- | If a parser has returned a 'Partial' result, supply it with more
203+-- input.
204+feed :: Result r -> Text -> Result r
205+feed f@(T.Fail _ _ _) _ = f
206+feed (T.Partial k) d = k d
207+feed (T.Done bs r) d = T.Done (T.append bs d) r
208+{-# INLINE feed #-}
209+
240-- | Run a parser and print its result to standard output.
210-- | Run a parser and print its result to standard output.
241parseTest :: (Show a) => I.Parser a -> Text -> IO ()
211parseTest :: (Show a) => I.Parser a -> Text -> IO ()
242parseTest p s = print (parse p s)
212parseTest p s = print (parse p s)
@@ -266,10 +236,9 @@ maybeResult _ = Nothing
266-- | Convert a 'Result' value to an 'Either' value. A 'Partial' result
236-- | Convert a 'Result' value to an 'Either' value. A 'Partial' result
267-- is treated as failure.
237-- is treated as failure.
268eitherResult :: Result r -> Either String r
238eitherResult :: Result r -> Either String r
269−eitherResult (T.Done _ r) = Right r
239+eitherResult (T.Done _ r) = Right r
270−eitherResult (T.Fail _ [] msg) = Left msg
240+eitherResult (T.Fail _ _ msg) = Left msg
271−eitherResult (T.Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
272−eitherResult _ = Left "Result: incomplete input"
241+eitherResult _ = Left "Result: incomplete input"
273
242
274-- | A predicate that matches either a carriage return @\'\\r\'@ or
243-- | A predicate that matches either a carriage return @\'\\r\'@ or
275-- newline @\'\\n\'@ character.
244-- newline @\'\\n\'@ character.
@@ -342,24 +311,6 @@ signed p = (negate <$> (I.char '-' *> p))
342 <|> (I.char '+' *> p)
311 <|> (I.char '+' *> p)
343 <|> p
312 <|> p
344
313
345−-- | Parse a rational number.
346−--
347−-- The syntax accepted by this parser is the same as for 'double'.
348−--
349−-- /Note/: this parser is not safe for use with inputs from untrusted
350−-- sources. An input with a suitably large exponent such as
351−-- @"1e1000000000"@ will cause a huge 'Integer' to be allocated,
352−-- resulting in what is effectively a denial-of-service attack.
353−--
354−-- In most cases, it is better to use 'double' or 'scientific'
355−-- instead.
356−rational :: Fractional a => Parser a
357−{-# SPECIALIZE rational :: Parser Double #-}
358−{-# SPECIALIZE rational :: Parser Float #-}
359−{-# SPECIALIZE rational :: Parser Rational #-}
360−{-# SPECIALIZE rational :: Parser Scientific #-}
361−rational = scientifically realToFrac
362−
363-- | Parse a rational number.
314-- | Parse a rational number.
364--
315--
365-- This parser accepts an optional leading sign character, followed by
316-- This parser accepts an optional leading sign character, followed by
@@ -387,12 +338,39 @@ rational = scientifically realToFrac
387--
338--
388-- This function does not accept string representations of \"NaN\" or
339-- This function does not accept string representations of \"NaN\" or
389-- \"Infinity\".
340-- \"Infinity\".
341+rational :: Fractional a => Parser a
342+{-# SPECIALIZE rational :: Parser Double #-}
343+{-# SPECIALIZE rational :: Parser Float #-}
344+{-# SPECIALIZE rational :: Parser Rational #-}
345+{-# SPECIALIZE rational :: Parser Scientific #-}
346+rational = scientifically realToFrac
347+
348+-- | Parse a rational number.
349+--
350+-- The syntax accepted by this parser is the same as for 'rational'.
351+--
352+-- /Note/: This function is almost ten times faster than 'rational',
353+-- but is slightly less accurate.
354+--
355+-- The 'Double' type supports about 16 decimal places of accuracy.
356+-- For 94.2% of numbers, this function and 'rational' give identical
357+-- results, but for the remaining 5.8%, this function loses precision
358+-- around the 15th decimal place. For 0.001% of numbers, this
359+-- function will lose precision at the 13th or 14th decimal place.
360+--
361+-- This function does not accept string representations of \"NaN\" or
362+-- \"Infinity\".
390double :: Parser Double
363double :: Parser Double
391double = scientifically Sci.toRealFloat
364double = scientifically Sci.toRealFloat
392
365
393-- | Parse a number, attempting to preserve both speed and precision.
366-- | Parse a number, attempting to preserve both speed and precision.
394--
367--
395−-- The syntax accepted by this parser is the same as for 'double'.
368+-- The syntax accepted by this parser is the same as for 'rational'.
369+--
370+-- /Note/: This function is almost ten times faster than 'rational'.
371+-- On integral inputs, it gives perfectly accurate answers, and on
372+-- floating point inputs, it is slightly less accurate than
373+-- 'rational'.
396--
374--
397-- This function does not accept string representations of \"NaN\" or
375-- This function does not accept string representations of \"NaN\" or
398-- \"Infinity\".
376-- \"Infinity\".
@@ -403,11 +381,10 @@ number = scientifically $ \s ->
403 in if e >= 0
381 in if e >= 0
404 then I (c * 10 ^ e)
382 then I (c * 10 ^ e)
405 else D (Sci.toRealFloat s)
383 else D (Sci.toRealFloat s)
406−{-# DEPRECATED number "Use 'scientific' instead." #-}
407
384
408-- | Parse a scientific number.
385-- | Parse a scientific number.
409--
386--
410−-- The syntax accepted by this parser is the same as for 'double'.
387+-- The syntax accepted by this parser is the same as for 'rational'.
411scientific :: Parser Scientific
388scientific :: Parser Scientific
412scientific = scientifically id
389scientific = scientifically id
413
390
@@ -458,36 +435,27 @@ skipSpace = I.skipWhile isSpace
458
435
459-- $specalt
436-- $specalt
460--
437--
461−-- If you enable the @OverloadedStrings@ language extension, you can
438+-- The '.*>' and '<*.' combinators are intended for use with the
462−-- use the '*>' and '<*' combinators to simplify the common task of
439+-- @OverloadedStrings@ language extension. They simplify the common
463−-- matching a statically known string, then immediately parsing
440+-- task of matching a statically known string, then immediately
464−-- something else.
441+-- parsing something else.
465--
442--
466−-- Instead of writing something like this:
443+-- An example makes this easier to understand:
467--
444--
468−-- @
445+-- @{-\# LANGUAGE OverloadedStrings #-}
469−--'I.string' \"foo\" '*>' wibble
470−-- @
471--
446--
472−-- Using @OverloadedStrings@, you can omit the explicit use of
447+-- shoeSize = \"Shoe size: \" '.*>' 'decimal'
473−-- 'I.string', and write a more compact version:
474−--
475−-- @
476−-- \"foo\" '*>' wibble
477-- @
448-- @
478--
449--
479−-- (Note: the '.*>' and '<*.' combinators that were originally
450+-- If we were to try to use '*>' above instead, the type checker would
480−-- provided for this purpose are obsolete and unnecessary, and will be
451+-- not be able to tell which 'IsString' instance to use for the text
481−-- removed in the next major version.)
452+-- in quotes. We would have to be explicit, using either a type
453+-- signature or the 'I.string' parser.
482
454
483−-- | /Obsolete/. A type-specialized version of '*>' for 'Text'. Use
455+-- | Type-specialized version of '*>' for 'Text'.
484−-- '*>' instead.
485(.*>) :: Text -> Parser a -> Parser a
456(.*>) :: Text -> Parser a -> Parser a
486s .*> f = I.string s *> f
457s .*> f = I.string s *> f
487−{-# DEPRECATED (.*>) "This is no longer necessary, and will be removed. Use '*>' instead." #-}
488
458
489−-- | /Obsolete/. A type-specialized version of '<*' for 'Text'. Use
459+-- | Type-specialized version of '<*' for 'Text'.
490−-- '*>' instead.
491(<*.) :: Parser a -> Text -> Parser a
460(<*.) :: Parser a -> Text -> Parser a
492f <*. s = f <* I.string s
461f <*. s = f <* I.string s
493−{-# DEPRECATED (<*.) "This is no longer necessary, and will be removed. Use '<*' instead." #-}
Data/Attoparsec/Text/Buffer.hsdeleted−172
@@ -1,172 +0,0 @@
1−{-# LANGUAGE BangPatterns, CPP, MagicHash, RankNTypes, RecordWildCards,
2− UnboxedTuples #-}
3−
4−-- |
5−-- Module : Data.Attoparsec.Text.Buffer
6−-- Copyright : Bryan O'Sullivan 2007-2015
7−-- License : BSD3
8−--
9−-- Maintainer : bos@serpentine.com
10−-- Stability : experimental
11−-- Portability : GHC
12−--
13−-- An immutable buffer that supports cheap appends.
14−
15−-- A Buffer is divided into an immutable read-only zone, followed by a
16−-- mutable area that we've preallocated, but not yet written to.
17−--
18−-- We overallocate at the end of a Buffer so that we can cheaply
19−-- append. Since a user of an existing Buffer cannot see past the end
20−-- of its immutable zone into the data that will change during an
21−-- append, this is safe.
22−--
23−-- Once we run out of space at the end of a Buffer, we do the usual
24−-- doubling of the buffer size.
25−
26−module Data.Attoparsec.Text.Buffer
27− (
28− Buffer
29− , buffer
30− , unbuffer
31− , unbufferAt
32− , length
33− , pappend
34− , iter
35− , iter_
36− , substring
37− , dropWord16
38− ) where
39−
40−import Control.Exception (assert)
41−import Data.Bits (shiftR)
42−import Data.List (foldl1')
43−import Data.Monoid as Mon (Monoid(..))
44−import Data.Semigroup (Semigroup(..))
45−import Data.Text ()
46−import Data.Text.Internal (Text(..))
47−import Data.Text.Internal.Encoding.Utf16 (chr2)
48−import Data.Text.Internal.Unsafe.Char (unsafeChr)
49−import Data.Text.Unsafe (Iter(..))
50−import Foreign.Storable (sizeOf)
51−import GHC.Base (Int(..), indexIntArray#, unsafeCoerce#, writeIntArray#)
52−import GHC.ST (ST(..), runST)
53−import Prelude hiding (length)
54−import qualified Data.Text.Array as A
55−
56−-- If _cap is zero, this buffer is empty.
57−data Buffer = Buf {
58− _arr :: {-# UNPACK #-} !A.Array
59− , _off :: {-# UNPACK #-} !Int
60− , _len :: {-# UNPACK #-} !Int
61− , _cap :: {-# UNPACK #-} !Int
62− , _gen :: {-# UNPACK #-} !Int
63− }
64−
65−instance Show Buffer where
66− showsPrec p = showsPrec p . unbuffer
67−
68−-- | The initial 'Buffer' has no mutable zone, so we can avoid all
69−-- copies in the (hopefully) common case of no further input being fed
70−-- to us.
71−buffer :: Text -> Buffer
72−buffer (Text arr off len) = Buf arr off len len 0
73−
74−unbuffer :: Buffer -> Text
75−unbuffer (Buf arr off len _ _) = Text arr off len
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−
88−instance Monoid Buffer where
89− mempty = Buf A.empty 0 0 0 0
90− {-# INLINE mempty #-}
91−
92− mappend = (<>)
93−
94− mconcat [] = Mon.mempty
95− mconcat xs = foldl1' (<>) xs
96−
97−pappend :: Buffer -> Text -> Buffer
98−pappend (Buf _ _ _ 0 _) t = buffer t
99−pappend buf (Text arr off len) = append buf arr off len
100−
101−append :: Buffer -> A.Array -> Int -> Int -> Buffer
102−append (Buf arr0 off0 len0 cap0 gen0) !arr1 !off1 !len1 = runST $ do
103− let woff = sizeOf (0::Int) `shiftR` 1
104− newlen = len0 + len1
105− !gen = if gen0 == 0 then 0 else readGen arr0
106− if gen == gen0 && newlen <= cap0
107− then do
108− let newgen = gen + 1
109− marr <- unsafeThaw arr0
110− writeGen marr newgen
111− A.copyI marr (off0+len0) arr1 off1 (off0+newlen)
112− arr2 <- A.unsafeFreeze marr
113− return (Buf arr2 off0 newlen cap0 newgen)
114− else do
115− let newcap = newlen * 2
116− newgen = 1
117− marr <- A.new (newcap + woff)
118− writeGen marr newgen
119− A.copyI marr woff arr0 off0 (woff+len0)
120− A.copyI marr (woff+len0) arr1 off1 (woff+newlen)
121− arr2 <- A.unsafeFreeze marr
122− return (Buf arr2 woff newlen newcap newgen)
123−
124−length :: Buffer -> Int
125−length (Buf _ _ len _ _) = len
126−{-# INLINE length #-}
127−
128−substring :: Int -> Int -> Buffer -> Text
129−substring s l (Buf arr off len _ _) =
130− assert (s >= 0 && s <= len) .
131− assert (l >= 0 && l <= len-s) $
132− Text arr (off+s) l
133−{-# INLINE substring #-}
134−
135−dropWord16 :: Int -> Buffer -> Text
136−dropWord16 s (Buf arr off len _ _) =
137− assert (s >= 0 && s <= len) $
138− Text arr (off+s) (len-s)
139−{-# INLINE dropWord16 #-}
140−
141−-- | /O(1)/ Iterate (unsafely) one step forwards through a UTF-16
142−-- array, returning the current character and the delta to add to give
143−-- the next offset to iterate at.
144−iter :: Buffer -> Int -> Iter
145−iter (Buf arr off _ _ _) i
146− | m < 0xD800 || m > 0xDBFF = Iter (unsafeChr m) 1
147− | otherwise = Iter (chr2 m n) 2
148− where m = A.unsafeIndex arr j
149− n = A.unsafeIndex arr k
150− j = off + i
151− k = j + 1
152−{-# INLINE iter #-}
153−
154−-- | /O(1)/ Iterate one step through a UTF-16 array, returning the
155−-- delta to add to give the next offset to iterate at.
156−iter_ :: Buffer -> Int -> Int
157−iter_ (Buf arr off _ _ _) i | m < 0xD800 || m > 0xDBFF = 1
158− | otherwise = 2
159− where m = A.unsafeIndex arr (off+i)
160−{-# INLINE iter_ #-}
161−
162−unsafeThaw :: A.Array -> ST s (A.MArray s)
163−unsafeThaw A.Array{..} = ST $ \s# ->
164− (# s#, A.MArray (unsafeCoerce# aBA) #)
165−
166−readGen :: A.Array -> Int
167−readGen a = case indexIntArray# (A.aBA a) 0# of r# -> I# r#
168−
169−writeGen :: A.MArray s -> Int -> ST s ()
170−writeGen a (I# gen#) = ST $ \s0# ->
171− case writeIntArray# (A.maBA a) 0# gen# s0# of
172− s1# -> (# s1#, () #)
Data/Attoparsec/Text/FastSet.hs+27−85
@@ -1,20 +1,16 @@
1−{-# LANGUAGE BangPatterns #-}
1+-----------------------------------------------------------------------------
2−
3−------------------------------------------------------------------------------
4-- |
2-- |
5-- Module : Data.Attoparsec.FastSet
3-- Module : Data.Attoparsec.FastSet
6−-- Copyright : Felipe Lessa 2010, Bryan O'Sullivan 2007-2015
4+-- Copyright : Felipe Lessa 2010, Bryan O'Sullivan 2008
7-- License : BSD3
5-- License : BSD3
8--
6--
9-- Maintainer : felipe.lessa@gmail.com
7-- Maintainer : felipe.lessa@gmail.com
10-- Stability : experimental
8-- Stability : experimental
11-- Portability : unknown
9-- Portability : unknown
12--
10--
13−-- Fast set membership tests for 'Char' values. We test for
11+-- Fast set membership tests for 'Char' values. The set
14−-- membership using a hashtable implemented with Robin Hood
15−-- collision resolution. The set representation is unboxed,
12+-- representation is unboxed for efficiency. We test for
16−-- and the characters and hashes interleaved, for efficiency.
13+-- membership using a binary search.
17−--
18--
14--
19-----------------------------------------------------------------------------
15-----------------------------------------------------------------------------
20module Data.Attoparsec.Text.FastSet
16module Data.Attoparsec.Text.FastSet
@@ -30,92 +26,38 @@ module Data.Attoparsec.Text.FastSet
30 , charClass
26 , charClass
31 ) where
27 ) where
32
28
33−import Data.Bits ((.|.), (.&.), shiftR)
29+import Data.List (sort)
34−import Data.Function (on)
35−import Data.List (sort, sortBy)
36import qualified Data.Array.Base as AB
30import qualified Data.Array.Base as AB
37import qualified Data.Array.Unboxed as A
31import qualified Data.Array.Unboxed as A
38import qualified Data.Text as T
32import qualified Data.Text as T
39
33
40−data FastSet = FastSet {
34+newtype FastSet = FastSet (A.UArray Int Char)
41− table :: {-# UNPACK #-} !(A.UArray Int Int)
35+ deriving (Eq, Ord, Show)
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
36
63−pad :: Int -> [Entry] -> [Entry]
37+-- | Create a set.
64−pad = go 0
38+set :: T.Text -> FastSet
65− where -- ensure that we pad enough so that lookups beyond the
39+set t = mkSet (T.length t) (sort $ T.unpack t)
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
40
96−ordNub :: Eq a => [a] -> [a]
41+fromList :: [Char] -> FastSet
97−ordNub [] = []
42+fromList cs = mkSet (length cs) (sort cs)
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
43
104−set :: T.Text -> FastSet
44+mkSet :: Int -> [Char] -> FastSet
105−set = fromList . T.unpack
45+mkSet l = FastSet . A.listArray (0,l-1)
106
46
107-- | Check the set for membership.
47-- | Check the set for membership.
108member :: Char -> FastSet -> Bool
48member :: Char -> FastSet -> Bool
109−member c a = go (2 * i)
49+member c (FastSet a) = uncurry search (A.bounds a)
110− where i = fastHash c .&. mask a
50+ where search lo hi
111− lookupAt j b = (i' <= i) && (c == c' || b)
51+ | hi < lo = False
112− where c' = toEnum $ AB.unsafeAt (table a) j
52+ | otherwise =
53+ let mid = (lo + hi) `quot` 2
113− i' = AB.unsafeAt (table a) $ j + 1
54+ in case compare c (AB.unsafeAt a mid) of
114− go j = lookupAt j . lookupAt (j + 2) . lookupAt (j + 4) .
55+ GT -> search (mid + 1) hi
115− lookupAt (j + 6) . go $ j + 8
56+ LT -> search lo (mid - 1)
57+ _ -> True
116
58
117charClass :: String -> FastSet
59charClass :: String -> FastSet
118charClass = fromList . go
60charClass = fromList . go
119− where go (a:'-':b:xs) = [a..b] ++ go xs
61+ where go (a:'-':b:xs) = [a..b] ++ go xs
120− go (x:xs) = x : go xs
62+ go (x:xs) = x : go xs
121− go _ = ""
63+ go _ = ""
Data/Attoparsec/Text/Internal.hs+119−238
@@ -1,16 +1,16 @@
1−{-# LANGUAGE BangPatterns, FlexibleInstances, GADTs, OverloadedStrings,
1+{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, GADTs, OverloadedStrings,
2 Rank2Types, RecordWildCards, TypeFamilies, TypeSynonymInstances #-}
2 Rank2Types, RecordWildCards, TypeFamilies, TypeSynonymInstances #-}
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-2015
6+-- Copyright : Bryan O'Sullivan 2011
7-- License : BSD3
7-- License : BSD3
8--
8--
9-- Maintainer : bos@serpentine.com
9-- Maintainer : bos@serpentine.com
10-- Stability : experimental
10-- Stability : experimental
11-- Portability : unknown
11-- Portability : unknown
12--
12--
13−-- Simple, efficient parser combinators for 'Text' strings, loosely
13+-- Simple, efficient parser combinators for 'T.Text' strings, loosely
14-- based on the Parsec library.
14-- based on the Parsec library.
15
15
16module Data.Attoparsec.Text.Internal
16module Data.Attoparsec.Text.Internal
@@ -60,37 +60,43 @@ module Data.Attoparsec.Text.Internal
60
60
61 -- * Utilities
61 -- * Utilities
62 , endOfLine
62 , endOfLine
63− , endOfInput
64− , match
65− , atEnd
66 ) where
63 ) where
67
64
68−import Control.Applicative ((<|>), (<$>), pure, (*>))
65+import Control.Applicative ((<|>), (<$>))
69import Control.Monad (when)
66import Control.Monad (when)
70−import Data.Attoparsec.Combinator ((<?>))
67+import Data.Attoparsec.Combinator
68+import Data.Attoparsec.Internal.Types hiding (Parser, Input, Added, Failure, Success)
71import Data.Attoparsec.Internal
69import Data.Attoparsec.Internal
72−import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
70+import Data.Monoid (Monoid(..))
73−import qualified Data.Attoparsec.Text.Buffer as Buf
74−import Data.Attoparsec.Text.Buffer (Buffer, buffer)
75−import Data.Char (isAsciiUpper, isAsciiLower, toUpper, toLower)
76−import Data.List (intercalate)
77import Data.String (IsString(..))
71import Data.String (IsString(..))
78−import Data.Text.Internal (Text(..))
72+import Data.Text (Text)
79−import Prelude hiding (getChar, succ, take, takeWhile)
73+import Prelude hiding (getChar, take, takeWhile)
74+import Data.Char (chr, ord)
80import qualified Data.Attoparsec.Internal.Types as T
75import qualified Data.Attoparsec.Internal.Types as T
81import qualified Data.Attoparsec.Text.FastSet as Set
76import qualified Data.Attoparsec.Text.FastSet as Set
82import qualified Data.Text as T
77import qualified Data.Text as T
83import qualified Data.Text.Lazy as L
78import qualified Data.Text.Lazy as L
84−import qualified Data.Text.Unsafe as T
85
79
86type Parser = T.Parser Text
80type Parser = T.Parser Text
87type Result = IResult Text
81type Result = IResult Text
88−type Failure r = T.Failure Text Buffer r
82+type Failure r = T.Failure Text r
89−type Success a r = T.Success Text Buffer a r
83+type Success a r = T.Success Text a r
90
84
91instance (a ~ Text) => IsString (Parser a) where
85instance (a ~ Text) => IsString (Parser a) where
92 fromString = string . T.pack
86 fromString = string . T.pack
93
87
88+unsafeHead :: Text -> Char
89+unsafeHead = T.head
90+
91+unsafeTail :: Text -> Text
92+unsafeTail = T.tail
93+
94+unsafeTake :: Int -> Text -> Text
95+unsafeTake = T.take
96+
97+unsafeDrop :: Int -> Text -> Text
98+unsafeDrop = T.drop
99+
94-- | The parser @satisfy p@ succeeds for any character for which the
100-- | The parser @satisfy p@ succeeds for any character for which the
95-- predicate @p@ returns 'True'. Returns the character that is
101-- predicate @p@ returns 'True'. Returns the character that is
96-- actually parsed.
102-- actually parsed.
@@ -98,12 +104,7 @@ instance (a ~ Text) => IsString (Parser a) where
98-- >digit = satisfy isDigit
104-- >digit = satisfy isDigit
99-- > where isDigit c = c >= '0' && c <= '9'
105-- > where isDigit c = c >= '0' && c <= '9'
100satisfy :: (Char -> Bool) -> Parser Char
106satisfy :: (Char -> Bool) -> Parser Char
101−satisfy p = do
107+satisfy = satisfyElem
102− (k,c) <- ensure 1
103− let !h = T.unsafeHead c
104− if p h
105− then advance k >> return h
106− else fail "satisfy"
107{-# INLINE satisfy #-}
108{-# INLINE satisfy #-}
108
109
109-- | The parser @skip p@ succeeds for any character for which the
110-- | The parser @skip p@ succeeds for any character for which the
@@ -113,9 +114,9 @@ satisfy p = do
113-- > where isDigit c = c >= '0' && c <= '9'
114-- > where isDigit c = c >= '0' && c <= '9'
114skip :: (Char -> Bool) -> Parser ()
115skip :: (Char -> Bool) -> Parser ()
115skip p = do
116skip p = do
116− (k,s) <- ensure 1
117+ s <- ensure 1
117− if p (T.unsafeHead s)
118+ if p (unsafeHead s)
118− then advance k
119+ then put (unsafeTail s)
119 else fail "skip"
120 else fail "skip"
120
121
121-- | The parser @satisfyWith f p@ transforms a character, and succeeds
122-- | The parser @satisfyWith f p@ transforms a character, and succeeds
@@ -123,10 +124,11 @@ skip p = do
123-- parser returns the transformed character that was parsed.
124-- parser returns the transformed character that was parsed.
124satisfyWith :: (Char -> a) -> (a -> Bool) -> Parser a
125satisfyWith :: (Char -> a) -> (a -> Bool) -> Parser a
125satisfyWith f p = do
126satisfyWith f p = do
126− (k,s) <- ensure 1
127+ s <- ensure 1
127− let c = f $! T.unsafeHead s
128+ let c = f $! unsafeHead s
128 if p c
129 if p c
130+ then let !t = unsafeTail s
129− then advance k >> return c
131+ in put t >> return c
130 else fail "satisfyWith"
132 else fail "satisfyWith"
131{-# INLINE satisfyWith #-}
133{-# INLINE satisfyWith #-}
132
134
@@ -134,14 +136,15 @@ satisfyWith f p = do
134-- predicate returns 'True'.
136-- predicate returns 'True'.
135takeWith :: Int -> (Text -> Bool) -> Parser Text
137takeWith :: Int -> (Text -> Bool) -> Parser Text
136takeWith n p = do
138takeWith n p = do
137− (k,s) <- ensure n
139+ s <- ensure n
140+ let (h,t) = T.splitAt n s
138− if p s
141+ if p h
139− then advance k >> return s
142+ then put t >> return h
140 else fail "takeWith"
143 else fail "takeWith"
141
144
142-- | Consume exactly @n@ characters of input.
145-- | Consume exactly @n@ characters of input.
143take :: Int -> Parser Text
146take :: Int -> Parser Text
144−take n = takeWith (max n 0) (const True)
147+take n = takeWith n (const True)
145{-# INLINE take #-}
148{-# INLINE take #-}
146
149
147-- | @string s@ parses a sequence of characters that identically match
150-- | @string s@ parses a sequence of characters that identically match
@@ -157,49 +160,12 @@ take n = takeWith (max n 0) (const True)
157--
160--
158-- The reason for its failure is that the first branch is a
161-- The reason for its failure is that the first branch is a
159-- partial match, and will consume the letters @\'f\'@ and @\'o\'@
162-- partial match, and will consume the letters @\'f\'@ and @\'o\'@
160−-- before failing. In attoparsec, the above parser will /succeed/ on
163+-- before failing. In Attoparsec, the above parser will /succeed/ on
161-- that input, because the failed first branch will consume nothing.
164-- that input, because the failed first branch will consume nothing.
162string :: Text -> Parser Text
165string :: Text -> Parser Text
163−string s = string_ (stringSuspended id) id s
166+string s = takeWith (T.length s) (==s)
164{-# INLINE string #-}
167{-# INLINE string #-}
165
168
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−
203-- | Satisfy a literal string, ignoring case.
169-- | Satisfy a literal string, ignoring case.
204--
170--
205-- Note: this function is currently quite inefficient. Unicode case
171-- Note: this function is currently quite inefficient. Unicode case
@@ -212,9 +178,10 @@ stringCI s = go 0
212 go !n
178 go !n
213 | n > T.length fs = fail "stringCI"
179 | n > T.length fs = fail "stringCI"
214 | otherwise = do
180 | otherwise = do
215− (k,t) <- ensure n
181+ t <- ensure n
182+ let h = unsafeTake n t
216− if T.toCaseFold t == fs
183+ if T.toCaseFold h == fs
217− then advance k >> return t
184+ then put (unsafeDrop n t) >> return h
218 else go (n+1)
185 else go (n+1)
219 fs = T.toCaseFold s
186 fs = T.toCaseFold s
220{-# INLINE stringCI #-}
187{-# INLINE stringCI #-}
@@ -222,24 +189,34 @@ stringCI s = go 0
222
189
223-- | Satisfy a literal string, ignoring case for characters in the ASCII range.
190-- | Satisfy a literal string, ignoring case for characters in the ASCII range.
224asciiCI :: Text -> Parser Text
191asciiCI :: Text -> Parser Text
225−asciiCI s = fmap fst $ match $ T.foldr ((*>) . asciiCharCI) (pure ()) s
192+asciiCI input = do
193+ t <- ensure n
194+ let h = unsafeTake n t
195+ if asciiToLower h == s
196+ then put (unsafeDrop n t) >> return h
197+ else fail "asciiCI"
198+ where
199+ n = T.length input
200+ s = asciiToLower input
201+
202+ -- convert letters in the ASCII range to lower-case
203+ asciiToLower = T.map f
204+ where
205+ offset = ord 'a' - ord 'A'
206+ f c | 'A' <= c && c <= 'Z' = chr (ord c + offset)
207+ | otherwise = c
226{-# INLINE asciiCI #-}
208{-# INLINE asciiCI #-}
227
209
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−
235-- | Skip past input for as long as the predicate returns 'True'.
210-- | Skip past input for as long as the predicate returns 'True'.
236skipWhile :: (Char -> Bool) -> Parser ()
211skipWhile :: (Char -> Bool) -> Parser ()
237skipWhile p = go
212skipWhile p = go
238 where
213 where
239 go = do
214 go = do
240− t <- T.takeWhile p <$> get
215+ t <- T.dropWhile p <$> get
241− continue <- inputSpansChunks (size t)
216+ put t
217+ when (T.null t) $ do
218+ input <- wantInput
242− when continue go
219+ when input go
243{-# INLINE skipWhile #-}
220{-# INLINE skipWhile #-}
244
221
245-- | Consume input as long as the predicate returns 'False'
222-- | Consume input as long as the predicate returns 'False'
@@ -249,9 +226,8 @@ skipWhile p = go
249-- predicate returns 'True' on the first character of input.
226-- predicate returns 'True' on the first character of input.
250--
227--
251-- /Note/: Because this parser does not fail, do not use it with
228-- /Note/: Because this parser does not fail, do not use it with
252−-- combinators such as 'Control.Applicative.many', because such
229+-- combinators such as 'many', because such parsers loop until a
253−-- parsers loop until a failure occurs. Careless use will thus result
230+-- failure occurs. Careless use will thus result in an infinite loop.
254−-- in an infinite loop.
255takeTill :: (Char -> Bool) -> Parser Text
231takeTill :: (Char -> Bool) -> Parser Text
256takeTill p = takeWhile (not . p)
232takeTill p = takeWhile (not . p)
257{-# INLINE takeTill #-}
233{-# INLINE takeTill #-}
@@ -263,29 +239,21 @@ takeTill p = takeWhile (not . p)
263-- predicate returns 'False' on the first character of input.
239-- predicate returns 'False' on the first character of input.
264--
240--
265-- /Note/: Because this parser does not fail, do not use it with
241-- /Note/: Because this parser does not fail, do not use it with
266−-- combinators such as 'Control.Applicative.many', because such
242+-- combinators such as 'many', because such parsers loop until a
267−-- parsers loop until a failure occurs. Careless use will thus result
243+-- failure occurs. Careless use will thus result in an infinite loop.
268−-- in an infinite loop.
269takeWhile :: (Char -> Bool) -> Parser Text
244takeWhile :: (Char -> Bool) -> Parser Text
270−takeWhile p = do
245+takeWhile p = (T.concat . reverse) `fmap` go []
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
281 where
246 where
282 go acc = do
247 go acc = do
283− h <- T.takeWhile p <$> get
248+ (h,t) <- T.span p <$> get
284− continue <- inputSpansChunks (size h)
249+ put t
285− if continue
250+ if T.null t
251+ then do
252+ input <- wantInput
253+ if input
286− then go (h:acc)
254+ then go (h:acc)
287− else return $ concatReverse (h:acc)
255+ else return (h:acc)
288−{-# INLINE takeWhileAcc #-}
256+ else return (h:acc)
289
257
290takeRest :: Parser [Text]
258takeRest :: Parser [Text]
291takeRest = go []
259takeRest = go []
@@ -295,7 +263,7 @@ takeRest = go []
295 if input
263 if input
296 then do
264 then do
297 s <- get
265 s <- get
298− advance (size s)
266+ put T.empty
299 go (s:acc)
267 go (s:acc)
300 else return (reverse acc)
268 else return (reverse acc)
301
269
@@ -308,7 +276,7 @@ takeLazyText :: Parser L.Text
308takeLazyText = L.fromChunks `fmap` takeRest
276takeLazyText = L.fromChunks `fmap` takeRest
309
277
310data Scan s = Continue s
278data Scan s = Continue s
311− | Finished s {-# UNPACK #-} !Int Text
279+ | Finished {-# UNPACK #-} !Int T.Text
312
280
313scan_ :: (s -> [Text] -> Parser r) -> s -> (s -> Char -> Maybe s) -> Parser r
281scan_ :: (s -> [Text] -> Parser r) -> s -> (s -> Char -> Maybe s) -> Parser r
314scan_ f s0 p = go [] s0
282scan_ f s0 p = go [] s0
@@ -317,17 +285,17 @@ scan_ f s0 p = go [] s0
317 case T.uncons t of
285 case T.uncons t of
318 Just (c,t') -> case p s c of
286 Just (c,t') -> case p s c of
319 Just s' -> scanner s' (n+1) t'
287 Just s' -> scanner s' (n+1) t'
320− Nothing -> Finished s n t
288+ Nothing -> Finished n t
321 Nothing -> Continue s
289 Nothing -> Continue s
322 go acc s = do
290 go acc s = do
323 input <- get
291 input <- get
324 case scanner s 0 input of
292 case scanner s 0 input of
325− Continue s' -> do continue <- inputSpansChunks (size input)
293+ Continue s' -> do put T.empty
326− if continue
294+ more <- wantInput
295+ if more
327 then go (input : acc) s'
296 then go (input : acc) s'
328 else f s' (input : acc)
297 else f s' (input : acc)
329− Finished s' n t -> do advance (size input - size t)
330− f s' (T.take n input : acc)
298+ Finished n t -> put t >> f s (T.take n input : acc)
331{-# INLINE scan_ #-}
299{-# INLINE scan_ #-}
332
300
333-- | A stateful scanner. The predicate consumes and transforms a
301-- | A stateful scanner. The predicate consumes and transforms a
@@ -339,17 +307,19 @@ scan_ f s0 p = go [] s0
339-- predicate returns 'Nothing' on the first character of input.
307-- predicate returns 'Nothing' on the first character of input.
340--
308--
341-- /Note/: Because this parser does not fail, do not use it with
309-- /Note/: Because this parser does not fail, do not use it with
342−-- combinators such as 'Control.Applicative.many', because such
310+-- combinators such as 'many', because such parsers loop until a
343−-- parsers loop until a failure occurs. Careless use will thus result
311+-- failure occurs. Careless use will thus result in an infinite loop.
344−-- in an infinite loop.
345scan :: s -> (s -> Char -> Maybe s) -> Parser Text
312scan :: s -> (s -> Char -> Maybe s) -> Parser Text
346−scan = scan_ $ \_ chunks -> return $! concatReverse chunks
313+scan = scan_ $ \_ chunks ->
314+ case chunks of
315+ [x] -> return x
316+ xs -> return . T.concat . reverse $ xs
347{-# INLINE scan #-}
317{-# INLINE scan #-}
348
318
349-- | Like 'scan', but generalized to return the final state of the
319-- | Like 'scan', but generalized to return the final state of the
350-- scanner.
320-- scanner.
351runScanner :: s -> (s -> Char -> Maybe s) -> Parser (Text, s)
321runScanner :: s -> (s -> Char -> Maybe s) -> Parser (Text, s)
352−runScanner = scan_ $ \s xs -> let !sx = concatReverse xs in return (sx, s)
322+runScanner = scan_ $ \s xs -> return (T.concat (reverse xs), s)
353{-# INLINE runScanner #-}
323{-# INLINE runScanner #-}
354
324
355-- | Consume input as long as the predicate returns 'True', and return
325-- | Consume input as long as the predicate returns 'True', and return
@@ -360,16 +330,13 @@ runScanner = scan_ $ \s xs -> let !sx = concatReverse xs in return (sx, s)
360-- 'True' or if there is no input left.
330-- 'True' or if there is no input left.
361takeWhile1 :: (Char -> Bool) -> Parser Text
331takeWhile1 :: (Char -> Bool) -> Parser Text
362takeWhile1 p = do
332takeWhile1 p = do
363− (`when` demandInput) =<< endOfChunk
333+ (`when` demandInput) =<< T.null <$> get
364− h <- T.takeWhile p <$> get
334+ (h,t) <- T.span p <$> get
365− let size' = size h
366− when (size' == 0) $ fail "takeWhile1"
335+ when (T.null h) $ fail "takeWhile1"
367− advance size'
336+ put t
368− eoc <- endOfChunk
337+ if T.null t
369− if eoc
338+ then (h<>) `fmap` takeWhile p
370− then takeWhileAcc p [h]
371 else return h
339 else return h
372−{-# INLINE takeWhile1 #-}
373
340
374-- | Match any character in a set.
341-- | Match any character in a set.
375--
342--
@@ -411,31 +378,27 @@ notChar c = satisfy (/= c) <?> "not " ++ show c
411-- end of input has been reached. Does not consume any input.
378-- end of input has been reached. Does not consume any input.
412--
379--
413-- /Note/: Because this parser does not fail, do not use it with
380-- /Note/: Because this parser does not fail, do not use it with
414−-- combinators such as 'Control.Applicative.many', because such
381+-- combinators such as 'many', because such parsers loop until a
415−-- parsers loop until a failure occurs. Careless use will thus result
382+-- failure occurs. Careless use will thus result in an infinite loop.
416−-- in an infinite loop.
417peekChar :: Parser (Maybe Char)
383peekChar :: Parser (Maybe Char)
418−peekChar = T.Parser $ \t pos more _lose succ ->
384+peekChar = T.Parser $ \i0 a0 m0 _kf ks ->
419− case () of
385+ if T.null (unI i0)
420− _| pos < lengthOf t ->
421− let T.Iter !c _ = Buf.iter t (fromPos pos)
422− in succ t pos more (Just c)
423− | more == Complete ->
386+ then if m0 == Complete
424− succ t pos more Nothing
387+ then ks i0 a0 m0 Nothing
425− | otherwise ->
388+ else let ks' i a m = let !c = unsafeHead (unI i)
426− let succ' t' pos' more' =
427− let T.Iter !c _ = Buf.iter t' (fromPos pos')
428− in succ t' pos' more' (Just c)
389+ in ks i a m (Just c)
429− lose' t' pos' more' = succ t' pos' more' Nothing
390+ kf' i a m = ks i a m Nothing
430− in prompt t pos more lose' succ'
391+ in prompt i0 a0 m0 kf' ks'
392+ else let !c = unsafeHead (unI i0)
393+ in ks i0 a0 m0 (Just c)
431{-# INLINE peekChar #-}
394{-# INLINE peekChar #-}
432
395
433-- | Match any character, to perform lookahead. Does not consume any
396-- | Match any character, to perform lookahead. Does not consume any
434-- input, but will fail if end of input has been reached.
397-- input, but will fail if end of input has been reached.
435peekChar' :: Parser Char
398peekChar' :: Parser Char
436peekChar' = do
399peekChar' = do
437− (_,s) <- ensure 1
400+ s <- ensure 1
438− return $! T.unsafeHead s
401+ return $! unsafeHead s
439{-# INLINE peekChar' #-}
402{-# INLINE peekChar' #-}
440
403
441-- | Match either a single newline character @\'\\n\'@, or a carriage
404-- | Match either a single newline character @\'\\n\'@, or a carriage
@@ -445,105 +408,23 @@ endOfLine = (char '\n' >> return ()) <|> (string "\r\n" >> return ())
445
408
446-- | Terminal failure continuation.
409-- | Terminal failure continuation.
447failK :: Failure a
410failK :: Failure a
448−failK t (Pos pos) _more stack msg = Fail (Buf.dropWord16 pos t) stack msg
411+failK i0 _a0 _m0 stack msg = Fail (unI i0) stack msg
449{-# INLINE failK #-}
412{-# INLINE failK #-}
450
413
451-- | Terminal success continuation.
414-- | Terminal success continuation.
452successK :: Success a a
415successK :: Success a a
453−successK t (Pos pos) _more a = Done (Buf.dropWord16 pos t) a
416+successK i0 _a0 _m0 a = Done (unI i0) a
454{-# INLINE successK #-}
417{-# INLINE successK #-}
455
418
456-- | Run a parser.
419-- | Run a parser.
457parse :: Parser a -> Text -> Result a
420parse :: Parser a -> Text -> Result a
458−parse m s = runParser m (buffer s) 0 Incomplete failK successK
421+parse m s = runParser m (I s) mempty Incomplete failK successK
459{-# INLINE parse #-}
422{-# INLINE parse #-}
460
423
461-- | Run a parser that cannot be resupplied via a 'Partial' result.
424-- | Run a parser that cannot be resupplied via a 'Partial' result.
462−--
463−-- This function does not force a parser to consume all of its input.
464−-- Instead, any residual input will be discarded. To force a parser
465−-- to consume all of its input, use something like this:
466−--
467−-- @
468−--'parseOnly' (myParser 'Control.Applicative.<*' 'endOfInput')
469−-- @
470parseOnly :: Parser a -> Text -> Either String a
425parseOnly :: Parser a -> Text -> Either String a
471−parseOnly m s = case runParser m (buffer s) 0 Complete failK successK of
426+parseOnly m s = case runParser m (I s) mempty Complete failK successK of
472− Fail _ [] err -> Left err
473− Fail _ ctxs err -> Left (intercalate " > " ctxs ++ ": " ++ err)
427+ Fail _ _ err -> Left err
474− Done _ a -> Right a
428+ Done _ a -> Right a
475− _ -> error "parseOnly: impossible error!"
429+ _ -> error "parseOnly: impossible error!"
476{-# INLINE parseOnly #-}
430{-# INLINE parseOnly #-}
477−
478−get :: Parser Text
479−get = T.Parser $ \t pos more _lose succ ->
480− succ t pos more (Buf.dropWord16 (fromPos pos) t)
481−{-# INLINE get #-}
482−
483−endOfChunk :: Parser Bool
484−endOfChunk = T.Parser $ \t pos more _lose succ ->
485− succ t pos more (pos == lengthOf t)
486−{-# INLINE endOfChunk #-}
487−
488−inputSpansChunks :: Pos -> Parser Bool
489−inputSpansChunks i = T.Parser $ \t pos_ more _lose succ ->
490− let pos = pos_ + i
491− in if pos < lengthOf t || more == Complete
492− then succ t pos more False
493− else let lose' t' pos' more' = succ t' pos' more' False
494− succ' t' pos' more' = succ t' pos' more' True
495− in prompt t pos more lose' succ'
496−{-# INLINE inputSpansChunks #-}
497−
498−advance :: Pos -> Parser ()
499−advance n = T.Parser $ \t pos more _lose succ -> succ t (pos+n) more ()
500−{-# INLINE advance #-}
501−
502−ensureSuspended :: Int -> Buffer -> Pos -> More
503− -> Failure r -> Success (Pos, Text) r
504− -> Result r
505−ensureSuspended n t pos more lose succ =
506− runParser (demandInput >> go) t pos more lose succ
507− where go = T.Parser $ \t' pos' more' lose' succ' ->
508− case lengthAtLeast pos' n t' of
509− Just n' -> succ' t' pos' more' (n', substring pos n' t')
510− Nothing -> runParser (demandInput >> go) t' pos' more' lose' succ'
511−
512−-- | If at least @n@ elements of input are available, return the
513−-- current input, otherwise fail.
514−ensure :: Int -> Parser (Pos, Text)
515−ensure n = T.Parser $ \t pos more lose succ ->
516− case lengthAtLeast pos n t of
517− Just n' -> succ t pos more (n', substring pos n' t)
518− -- The uncommon case is kept out-of-line to reduce code size:
519− Nothing -> ensureSuspended n t pos more lose succ
520−{-# INLINE ensure #-}
521−
522−-- | Return both the result of a parse and the portion of the input
523−-- that was consumed while it was being parsed.
524−match :: Parser a -> Parser (Text, a)
525−match p = T.Parser $ \t pos more lose succ ->
526− let succ' t' pos' more' a = succ t' pos' more'
527− (substring pos (pos'-pos) t', a)
528− in runParser p t pos more lose succ'
529−
530−-- | Ensure that at least @n@ code points of input are available.
531−-- Returns the number of words consumed while traversing.
532−lengthAtLeast :: Pos -> Int -> Buffer -> Maybe Pos
533−lengthAtLeast pos n t = go 0 (fromPos pos)
534− where go i !p
535− | i == n = Just (Pos p - pos)
536− | p == len = Nothing
537− | otherwise = go (i+1) (p + Buf.iter_ t p)
538− Pos len = lengthOf t
539−{-# INLINE lengthAtLeast #-}
540−
541−substring :: Pos -> Pos -> Buffer -> Text
542−substring (Pos pos) (Pos n) = Buf.substring pos n
543−{-# INLINE substring #-}
544−
545−lengthOf :: Buffer -> Pos
546−lengthOf = Pos . Buf.length
547−
548−size :: Text -> Pos
549−size (Text _ _ l) = Pos l
Data/Attoparsec/Text/Lazy.hs+10−14
@@ -1,12 +1,6 @@
1−{-# LANGUAGE CPP #-}
2−#if __GLASGOW_HASKELL__ >= 702
3−{-# LANGUAGE Trustworthy #-} -- Imports internal modules
4−#endif
5−
6−{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
7-- |
1-- |
8-- Module : Data.Attoparsec.Text.Lazy
2-- Module : Data.Attoparsec.Text.Lazy
9−-- Copyright : Bryan O'Sullivan 2007-2015
3+-- Copyright : Bryan O'Sullivan 2011
10-- License : BSD3
4-- License : BSD3
11--
5--
12-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
@@ -41,7 +35,6 @@ module Data.Attoparsec.Text.Lazy
41 ) where
35 ) where
42
36
43import Control.DeepSeq (NFData(rnf))
37import Control.DeepSeq (NFData(rnf))
44−import Data.List (intercalate)
45import Data.Text.Lazy.Internal (Text(..), chunk)
38import Data.Text.Lazy.Internal (Text(..), chunk)
46import qualified Data.Attoparsec.Internal.Types as T
39import qualified Data.Attoparsec.Internal.Types as T
47import qualified Data.Attoparsec.Text as A
40import qualified Data.Attoparsec.Text as A
@@ -51,16 +44,20 @@ import Data.Attoparsec.Text hiding (IResult(..), Result, eitherResult,
51
44
52-- | The result of a parse.
45-- | The result of a parse.
53data Result r = Fail Text [String] String
46data Result r = Fail Text [String] String
54− -- ^ The parse failed. The 'Text' is the input
47+ -- ^ The parse failed. The 'ByteString' is the input
55 -- that had not yet been consumed when the failure
48 -- that had not yet been consumed when the failure
56 -- occurred. The @[@'String'@]@ is a list of contexts
49 -- occurred. The @[@'String'@]@ is a list of contexts
57 -- in which the error occurred. The 'String' is the
50 -- in which the error occurred. The 'String' is the
58 -- message describing the error, if any.
51 -- message describing the error, if any.
59 | Done Text r
52 | Done Text r
60− -- ^ The parse succeeded. The 'Text' is the
53+ -- ^ The parse succeeded. The 'ByteString' is the
61 -- input that had not yet been consumed (if any) when
54 -- input that had not yet been consumed (if any) when
62 -- the parse succeeded.
55 -- the parse succeeded.
63− deriving (Show)
56+
57+instance Show r => Show (Result r) where
58+ show (Fail bs stk msg) =
59+ "Fail " ++ show bs ++ " " ++ show stk ++ " " ++ show msg
60+ show (Done bs r) = "Done " ++ show bs ++ " " ++ show r
64
61
65instance NFData r => NFData (Result r) where
62instance NFData r => NFData (Result r) where
66 rnf (Fail bs ctxs msg) = rnf bs `seq` rnf ctxs `seq` rnf msg
63 rnf (Fail bs ctxs msg) = rnf bs `seq` rnf ctxs `seq` rnf msg
@@ -96,6 +93,5 @@ maybeResult _ = Nothing
96
93
97-- | Convert a 'Result' value to an 'Either' value.
94-- | Convert a 'Result' value to an 'Either' value.
98eitherResult :: Result r -> Either String r
95eitherResult :: Result r -> Either String r
99−eitherResult (Done _ r) = Right r
96+eitherResult (Done _ r) = Right r
100−eitherResult (Fail _ [] msg) = Left msg
97+eitherResult (Fail _ _ msg) = Left msg
101−eitherResult (Fail _ ctxs msg) = Left (intercalate " > " ctxs ++ ": " ++ msg)
Data/Attoparsec/Types.hs+2−2
@@ -1,6 +1,6 @@
1-- |
1-- |
2-- Module : Data.Attoparsec.Types
2-- Module : Data.Attoparsec.Types
3−-- Copyright : Bryan O'Sullivan 2007-2015
3+-- Copyright : Bryan O'Sullivan 2011
4-- License : BSD3
4-- License : BSD3
5--
5--
6-- Maintainer : bos@serpentine.com
6-- Maintainer : bos@serpentine.com
@@ -14,7 +14,7 @@ module Data.Attoparsec.Types
14 (
14 (
15 Parser
15 Parser
16 , IResult(..)
16 , IResult(..)
17− , Chunk(chunkElemToChar)
17+ , Chunk(..)
18 ) where
18 ) where
19
19
20import Data.Attoparsec.Internal.Types (Parser(..), IResult(..), Chunk(..))
20import Data.Attoparsec.Internal.Types (Parser(..), IResult(..), Chunk(..))
Data/Attoparsec/Zepto.hs+46−88
@@ -1,12 +1,8 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE BangPatterns, MagicHash, UnboxedTuples #-}
2−#if __GLASGOW_HASKELL__ >= 702
3−{-# LANGUAGE Trustworthy #-} -- Data.ByteString.Unsafe
4−#endif
5−{-# LANGUAGE BangPatterns #-}
6
2
7-- |
3-- |
8-- Module : Data.Attoparsec.Zepto
4-- Module : Data.Attoparsec.Zepto
9−-- Copyright : Bryan O'Sullivan 2007-2015
5+-- Copyright : Bryan O'Sullivan 2011
10-- License : BSD3
6-- License : BSD3
11--
7--
12-- Maintainer : bos@serpentine.com
8-- Maintainer : bos@serpentine.com
@@ -16,150 +12,112 @@
16-- A tiny, highly specialized combinator parser for 'B.ByteString'
12-- A tiny, highly specialized combinator parser for 'B.ByteString'
17-- strings.
13-- strings.
18--
14--
19−-- While the main attoparsec module generally performs well, this
15+-- While the main Attoparsec module generally performs well, this
20-- module is particularly fast for simple non-recursive loops that
16-- module is particularly fast for simple non-recursive loops that
21-- should not normally result in failed parses.
17-- should not normally result in failed parses.
22--
18--
23-- /Warning/: on more complex inputs involving recursion or failure,
19-- /Warning/: on more complex inputs involving recursion or failure,
24-- parsers based on this module may be as much as /ten times slower/
20-- parsers based on this module may be as much as /ten times slower/
25−-- than regular attoparsec! You should /only/ use this module when you
21+-- than regular Attoparsec! You should /only/ use this module when you
26-- have benchmarks that prove that its use speeds your code up.
22-- have benchmarks that prove that its use speeds your code up.
27module Data.Attoparsec.Zepto
23module Data.Attoparsec.Zepto
28 (
24 (
29 Parser
25 Parser
30− , ZeptoT
31 , parse
26 , parse
32− , parseT
33 , atEnd
27 , atEnd
34 , string
28 , string
35 , take
29 , take
36 , takeWhile
30 , takeWhile
37 ) where
31 ) where
38
32
39−import Control.Applicative
40−import Control.Monad (MonadPlus(..), ap)
41−import qualified Control.Monad.Fail as Fail
42−import Control.Monad.IO.Class (MonadIO(..))
43−import Data.ByteString (ByteString)
44−import Data.Functor.Identity (Identity(runIdentity))
45−import Data.Monoid as Mon (Monoid(..))
46−import Data.Semigroup (Semigroup(..))
47import Data.Word (Word8)
33import Data.Word (Word8)
48−import Prelude hiding (take, takeWhile)
34+import Control.Applicative
35+import Control.Monad
36+import Data.Monoid
49import qualified Data.ByteString as B
37import qualified Data.ByteString as B
50import qualified Data.ByteString.Unsafe as B
38import qualified Data.ByteString.Unsafe as B
39+import Data.ByteString (ByteString)
40+import Prelude hiding (take, takeWhile)
51
41
52newtype S = S {
42newtype S = S {
53 input :: ByteString
43 input :: ByteString
54 }
44 }
55
45
56data Result a = Fail String
46data Result a = Fail String
57− | OK !a S
47+ | OK !a
58
48
59-- | A simple parser.
49-- | A simple parser.
60--
50--
61-- This monad is strict in its state, and the monadic bind operator
51-- This monad is strict in its state, and the monadic bind operator
62-- ('>>=') evaluates each result to weak head normal form before
52-- ('>>=') evaluates each result to weak head normal form before
63-- passing it along.
53-- passing it along.
64−newtype ZeptoT m a = Parser {
54+newtype Parser a = Parser {
65− runParser :: S -> m (Result a)
55+ runParser :: S -> (# Result a, S #)
66 }
56 }
67
57
68−type Parser a = ZeptoT Identity a
69−
70−instance Monad m => Functor (ZeptoT m) where
58+instance Functor Parser where
71− fmap f m = Parser $ \s -> do
59+ fmap f m = Parser $ \s -> case runParser m s of
72− result <- runParser m s
73− case result of
74− OK a s' -> return (OK (f a) s')
60+ (# OK a, s' #) -> (# OK (f a), s' #)
75− Fail err -> return (Fail err)
61+ (# Fail err, s' #) -> (# Fail err, s' #)
76 {-# INLINE fmap #-}
62 {-# INLINE fmap #-}
77
63
78−instance MonadIO m => MonadIO (ZeptoT m) where
64+instance Monad Parser where
79− liftIO act = Parser $ \s -> do
65+ return a = Parser $ \s -> (# OK a, s #)
80− result <- liftIO act
81− return (OK result s)
82− {-# INLINE liftIO #-}
83−
84−instance Monad m => Monad (ZeptoT m) where
85− return = pure
86 {-# INLINE return #-}
66 {-# INLINE return #-}
87
67
88− m >>= k = Parser $ \s -> do
68+ m >>= k = Parser $ \s -> case runParser m s of
89− result <- runParser m s
90− case result of
91− OK a s' -> runParser (k a) s'
69+ (# OK a, s' #) -> runParser (k a) s'
92− Fail err -> return (Fail err)
70+ (# Fail err, s' #) -> (# Fail err, s' #)
93 {-# INLINE (>>=) #-}
71 {-# INLINE (>>=) #-}
94
72
95−#if !(MIN_VERSION_base(4,13,0))
96− fail = Fail.fail
73+ fail msg = Parser $ \s -> (# Fail msg, s #)
97− {-# INLINE fail #-}
98−#endif
99
74
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
75+instance MonadPlus Parser where
105 mzero = fail "mzero"
76 mzero = fail "mzero"
106 {-# INLINE mzero #-}
77 {-# INLINE mzero #-}
107
78
108− mplus a b = Parser $ \s -> do
79+ mplus a b = Parser $ \s ->
109− result <- runParser a s
80+ case runParser a s of
110− case result of
81+ (# ok@(OK _), s' #) -> (# ok, s' #)
82+ (# _, _ #) -> case runParser b s of
111− ok@(OK _ _) -> return ok
83+ (# ok@(OK _), s'' #) -> (# ok, s'' #)
112− _ -> runParser b s
84+ (# err, s'' #) -> (# err, s'' #)
113 {-# INLINE mplus #-}
85 {-# INLINE mplus #-}
114
86
115−instance (Monad m) => Applicative (ZeptoT m) where
87+instance Applicative Parser where
116− pure a = Parser $ \s -> return (OK a s)
88+ pure = return
117 {-# INLINE pure #-}
89 {-# INLINE pure #-}
118 (<*>) = ap
90 (<*>) = ap
119 {-# INLINE (<*>) #-}
91 {-# INLINE (<*>) #-}
120
92
121−gets :: Monad m => (S -> a) -> ZeptoT m a
93+gets :: (S -> a) -> Parser a
122−gets f = Parser $ \s -> return (OK (f s) s)
94+gets f = Parser $ \s -> (# OK (f s), s #)
123{-# INLINE gets #-}
95{-# INLINE gets #-}
124
96
125−put :: Monad m => S -> ZeptoT m ()
97+put :: S -> Parser ()
126−put s = Parser $ \_ -> return (OK () s)
98+put s = Parser $ \_ -> (# OK (), s #)
127{-# INLINE put #-}
99{-# INLINE put #-}
128
100
129-- | Run a parser.
101-- | Run a parser.
130parse :: Parser a -> ByteString -> Either String a
102parse :: Parser a -> ByteString -> Either String a
131−parse p bs = case runIdentity (runParser p (S bs)) of
103+parse p bs = case runParser p (S bs) of
132− (OK a _) -> Right a
104+ (# OK a, _ #) -> Right a
133− (Fail err) -> Left err
134−{-# INLINE parse #-}
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)
105+ (# Fail err, _ #) -> Left err
143−{-# INLINE parseT #-}
106+
144−
107+instance Monoid (Parser a) where
145−instance Monad m => Semigroup (ZeptoT m a) where
146− (<>) = mplus
147− {-# INLINE (<>) #-}
148−
149−instance Monad m => Mon.Monoid (ZeptoT m a) where
150 mempty = fail "mempty"
108 mempty = fail "mempty"
151 {-# INLINE mempty #-}
109 {-# INLINE mempty #-}
152− mappend = (<>)
110+ mappend = mplus
153 {-# INLINE mappend #-}
111 {-# INLINE mappend #-}
154
112
155−instance Monad m => Alternative (ZeptoT m) where
113+instance Alternative Parser where
156 empty = fail "empty"
114 empty = fail "empty"
157 {-# INLINE empty #-}
115 {-# INLINE empty #-}
158 (<|>) = mplus
116 (<|>) = mplus
159 {-# INLINE (<|>) #-}
117 {-# INLINE (<|>) #-}
160
118
161-- | Consume input while the predicate returns 'True'.
119-- | Consume input while the predicate returns 'True'.
162−takeWhile :: Monad m => (Word8 -> Bool) -> ZeptoT m ByteString
120+takeWhile :: (Word8 -> Bool) -> Parser ByteString
163takeWhile p = do
121takeWhile p = do
164 (h,t) <- gets (B.span p . input)
122 (h,t) <- gets (B.span p . input)
165 put (S t)
123 put (S t)
@@ -167,7 +125,7 @@ takeWhile p = do
167{-# INLINE takeWhile #-}
125{-# INLINE takeWhile #-}
168
126
169-- | Consume @n@ bytes of input.
127-- | Consume @n@ bytes of input.
170−take :: Monad m => Int -> ZeptoT m ByteString
128+take :: Int -> Parser ByteString
171take !n = do
129take !n = do
172 s <- gets input
130 s <- gets input
173 if B.length s >= n
131 if B.length s >= n
@@ -176,7 +134,7 @@ take !n = do
176{-# INLINE take #-}
134{-# INLINE take #-}
177
135
178-- | Match a string exactly.
136-- | Match a string exactly.
179−string :: Monad m => ByteString -> ZeptoT m ()
137+string :: ByteString -> Parser ()
180string s = do
138string s = do
181 i <- gets input
139 i <- gets input
182 if s `B.isPrefixOf` i
140 if s `B.isPrefixOf` i
@@ -185,7 +143,7 @@ string s = do
185{-# INLINE string #-}
143{-# INLINE string #-}
186
144
187-- | Indicate whether the end of the input has been reached.
145-- | Indicate whether the end of the input has been reached.
188−atEnd :: Monad m => ZeptoT m Bool
146+atEnd :: Parser Bool
189atEnd = do
147atEnd = do
190 i <- gets input
148 i <- gets input
191 return $! B.null i
149 return $! B.null i
README.markdown+6
@@ -16,6 +16,12 @@ 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+
19Authors
25Authors
20-------
26-------
21
27
attoparsec.cabal+31−105
@@ -1,13 +1,12 @@
1name: attoparsec
1name: attoparsec
2−version: 0.13.2.2
2+version: 0.11.3.4
3−x-revision: 3
4license: BSD3
3license: BSD3
5license-file: LICENSE
4license-file: LICENSE
6category: Text, Parsing
5category: Text, Parsing
7author: Bryan O'Sullivan <bos@serpentine.com>
6author: Bryan O'Sullivan <bos@serpentine.com>
8−maintainer: Bryan O'Sullivan <bos@serpentine.com>, Ben Gamari <ben@smart-cactus.org>
7+maintainer: Bryan O'Sullivan <bos@serpentine.com>
9stability: experimental
8stability: experimental
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
9+tested-with: GHC == 7.0, GHC == 7.2, GHC == 7.4, GHC == 7.6, GHC == 7.8
11synopsis: Fast combinator parsing for bytestrings and text
10synopsis: Fast combinator parsing for bytestrings and text
12cabal-version: >= 1.8
11cabal-version: >= 1.8
13homepage: https://github.com/bos/attoparsec
12homepage: https://github.com/bos/attoparsec
@@ -19,19 +18,19 @@ description:
19 file formats.
18 file formats.
20extra-source-files:
19extra-source-files:
21 README.markdown
20 README.markdown
22− benchmarks/*.cabal
23 benchmarks/*.hs
21 benchmarks/*.hs
24 benchmarks/*.txt
22 benchmarks/*.txt
25− benchmarks/json-data/*.json
26 benchmarks/Makefile
23 benchmarks/Makefile
24+ benchmarks/attoparsec-benchmarks.cabal
27 benchmarks/med.txt.bz2
25 benchmarks/med.txt.bz2
28 changelog.md
26 changelog.md
29 examples/*.c
27 examples/*.c
30 examples/*.hs
28 examples/*.hs
31 examples/Makefile
29 examples/Makefile
32 tests/*.hs
30 tests/*.hs
31+ tests/Makefile
33 tests/QC/*.hs
32 tests/QC/*.hs
34− tests/QC/IPv6/*.hs
33+ tests/TestFastSet.hs
35
34
36Flag developer
35Flag developer
37 Description: Whether to build the library in development mode
36 Description: Whether to build the library in development mode
@@ -40,21 +39,16 @@ Flag developer
40
39
41library
40library
42 build-depends: array,
41 build-depends: array,
43− base >= 4.3 && < 5,
42+ base >= 3 && < 5,
44 bytestring,
43 bytestring,
45 containers,
44 containers,
46 deepseq,
45 deepseq,
46+ text >= 0.11.3.1,
47− 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,
49− text >= 1.1.1.3
50 if impl(ghc < 7.4)
48 if impl(ghc < 7.4)
51 build-depends:
49 build-depends:
52− bytestring < 0.10.4.0
50+ bytestring < 0.10.4.0,
53−
51+ text < 1.1
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
59 exposed-modules: Data.Attoparsec
53 exposed-modules: Data.Attoparsec
60 Data.Attoparsec.ByteString
54 Data.Attoparsec.ByteString
@@ -62,133 +56,65 @@ library
62 Data.Attoparsec.ByteString.Lazy
56 Data.Attoparsec.ByteString.Lazy
63 Data.Attoparsec.Char8
57 Data.Attoparsec.Char8
64 Data.Attoparsec.Combinator
58 Data.Attoparsec.Combinator
65− Data.Attoparsec.Internal
66− Data.Attoparsec.Internal.Types
67 Data.Attoparsec.Lazy
59 Data.Attoparsec.Lazy
68 Data.Attoparsec.Number
60 Data.Attoparsec.Number
69 Data.Attoparsec.Text
61 Data.Attoparsec.Text
70 Data.Attoparsec.Text.Lazy
62 Data.Attoparsec.Text.Lazy
71 Data.Attoparsec.Types
63 Data.Attoparsec.Types
72 Data.Attoparsec.Zepto
64 Data.Attoparsec.Zepto
73− other-modules: Data.Attoparsec.ByteString.Buffer
65+ other-modules: Data.Attoparsec.ByteString.FastSet
74− Data.Attoparsec.ByteString.FastSet
75 Data.Attoparsec.ByteString.Internal
66 Data.Attoparsec.ByteString.Internal
76− Data.Attoparsec.Internal.Fhthagn
67+ Data.Attoparsec.Internal
77− Data.Attoparsec.Text.Buffer
68+ Data.Attoparsec.Internal.Types
78 Data.Attoparsec.Text.FastSet
69 Data.Attoparsec.Text.FastSet
79 Data.Attoparsec.Text.Internal
70 Data.Attoparsec.Text.Internal
80 ghc-options: -O2 -Wall
71 ghc-options: -O2 -Wall
81
72
82 if flag(developer)
73 if flag(developer)
83 ghc-prof-options: -auto-all
74 ghc-prof-options: -auto-all
84− ghc-options: -Werror
85
75
86test-suite tests
76test-suite tests
87 type: exitcode-stdio-1.0
77 type: exitcode-stdio-1.0
88− hs-source-dirs: tests .
78+ hs-source-dirs: tests
89 main-is: QC.hs
79 main-is: QC.hs
90− other-modules: QC.Buffer
80+ other-modules: QC.ByteString
91− QC.ByteString
92− QC.Combinator
93− QC.Common
94− QC.IPv6.Internal
95− QC.IPv6.Types
96− QC.Rechunked
97− QC.Simple
98 QC.Text
81 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
119
82
120 ghc-options:
83 ghc-options:
121 -Wall -threaded -rtsopts
84 -Wall -threaded -rtsopts
122
85
123− if flag(developer)
124− ghc-options: -Werror
125−
126 build-depends:
86 build-depends:
127− array,
87+ attoparsec,
128− base,
88+ base >= 4 && < 5,
129 bytestring,
89 bytestring,
130− deepseq >= 1.1,
131− QuickCheck >= 2.13.2 && < 2.14,
90+ QuickCheck >= 2.4,
132− quickcheck-unicode,
91+ test-framework >= 0.4,
133− scientific,
92+ test-framework-quickcheck2 >= 0.2,
134− tasty >= 0.11,
135− tasty-quickcheck >= 0.8,
136− text,
93+ text
137− transformers >= 0.2 && (< 0.4 || >= 0.4.1.0) && < 0.6,
138− vector
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
94
145benchmark benchmarks
95benchmark benchmarks
146 type: exitcode-stdio-1.0
96 type: exitcode-stdio-1.0
147− hs-source-dirs: benchmarks benchmarks/warp-3.0.1.1 .
97+ hs-source-dirs: benchmarks
148 ghc-options: -O2 -Wall -rtsopts
98 ghc-options: -O2 -Wall -rtsopts
149 main-is: Benchmarks.hs
99 main-is: Benchmarks.hs
150 other-modules:
100 other-modules:
151− Common
152 HeadersByteString
101 HeadersByteString
153− HeadersByteString.Atto
154 HeadersText
102 HeadersText
155 Links
103 Links
156− Network.Wai.Handler.Warp.ReadInt
157− Network.Wai.Handler.Warp.RequestHeader
158 Numbers
104 Numbers
159− Sets
160− TextFastSet
161− Warp
162− ghc-options: -O2 -Wall
163−
164− if flag(developer)
165− ghc-options: -Werror
166−
167 build-depends:
105 build-depends:
168− array,
106+ attoparsec,
169− base == 4.*,
107+ base,
170− bytestring >= 0.10.4.0,
108+ bytestring,
171− case-insensitive,
172− containers,
173− criterion >= 1.0,
109+ criterion >= 0.5,
174 deepseq >= 1.1,
110 deepseq >= 1.1,
175− directory,
176− filepath,
177− ghc-prim,
178− http-types,
179 parsec >= 3.1.2,
111 parsec >= 3.1.2,
180− scientific,
181− text >= 1.1.1.0,
112+ text
182− transformers >= 0.2 && (< 0.4 || >= 0.4.1.0) && < 0.6,
183− unordered-containers,
184− vector
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
113
191source-repository head
114source-repository head
192 type: git
115 type: git
193 location: https://github.com/bos/attoparsec
116 location: https://github.com/bos/attoparsec
194
117
118+source-repository head
119+ type: mercurial
120+ location: https://bitbucket.org/bos/attoparsec
benchmarks/Aeson.hs+3−8
@@ -10,13 +10,7 @@ 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)
13+import Control.Applicative ((*>), (<$>), (<*), liftA2, pure)
15−import Data.Monoid (mappend, mempty)
16−#endif
17−
18−import Common (pathTo)
19−import Control.Applicative (liftA2)
20import Control.DeepSeq (NFData(..))
14import Control.DeepSeq (NFData(..))
21import Control.Monad (forM)
15import Control.Monad (forM)
22import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,
16import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,
@@ -25,6 +19,7 @@ import Data.Bits ((.|.), shiftL)
25import Data.ByteString (ByteString)
19import Data.ByteString (ByteString)
26import Data.Char (chr)
20import Data.Char (chr)
27import Data.List (sort)
21import Data.List (sort)
22+import Data.Monoid (mappend, mempty)
28import Data.Scientific (Scientific)
23import Data.Scientific (Scientific)
29import Data.Text (Text)
24import Data.Text (Text)
30import Data.Text.Encoding (decodeUtf8')
25import Data.Text.Encoding (decodeUtf8')
@@ -345,7 +340,7 @@ toByteString = L.toStrict . toLazyByteString
345
340
346aeson :: IO Benchmark
341aeson :: IO Benchmark
347aeson = do
342aeson = do
348− path <- pathTo "json-data"
343+ let path = "json-data"
349 names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path
344 names <- sort . filter (`notElem` [".", ".."]) <$> getDirectoryContents path
350 benches <- forM names $ \name -> do
345 benches <- forM names $ \name -> do
351 bs <- B.readFile (path </> name)
346 bs <- B.readFile (path </> name)
benchmarks/Benchmarks.hs+7−11
@@ -1,4 +1,5 @@
1{-# LANGUAGE BangPatterns, CPP #-}
1{-# LANGUAGE BangPatterns, CPP #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
2
3
3import Common ()
4import Common ()
4import Control.Applicative (many)
5import Control.Applicative (many)
@@ -8,12 +9,9 @@ import Data.Char (isAlpha)
8import Data.Word (Word32)
9import Data.Word (Word32)
9import Data.Word (Word8)
10import Data.Word (Word8)
10import Numbers (numbers)
11import Numbers (numbers)
11−import Common (chunksOf)
12import Text.Parsec.Text ()
12import Text.Parsec.Text ()
13import Text.Parsec.Text.Lazy ()
13import Text.Parsec.Text.Lazy ()
14−import qualified Warp
15import qualified Aeson
14import qualified Aeson
16−import qualified Genome
17import qualified Data.Attoparsec.ByteString as AB
15import qualified Data.Attoparsec.ByteString as AB
18import qualified Data.Attoparsec.ByteString.Char8 as AC
16import qualified Data.Attoparsec.ByteString.Char8 as AC
19import qualified Data.Attoparsec.ByteString.Lazy as ABL
17import qualified Data.Attoparsec.ByteString.Lazy as ABL
@@ -28,7 +26,12 @@ import qualified HeadersByteString
28import qualified HeadersText
26import qualified HeadersText
29import qualified Links
27import qualified Links
30import qualified Text.Parsec as P
28import qualified Text.Parsec as P
31−import qualified Sets
29+
30+chunksOf :: Int -> [a] -> [[a]]
31+chunksOf k = go
32+ where go xs = case splitAt k xs of
33+ ([],_) -> []
34+ (y, ys) -> y : go ys
32
35
33main :: IO ()
36main :: IO ()
34main = do
37main = do
@@ -71,14 +74,10 @@ main = do
71 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile AC.isAlpha_ascii)) bl
74 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile AC.isAlpha_ascii)) bl
72 , bench "isAlpha_iso8859_15" $
75 , bench "isAlpha_iso8859_15" $
73 nf (ABL.parse (AC.takeWhile AC.isAlpha_iso8859_15)) bl
76 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
76 ]
77 ]
77 , bgroup "takeWhile1" [
78 , bgroup "takeWhile1" [
78 bench "isAlpha" $ nf (ABL.parse (AC.takeWhile1 isAlpha)) bl
79 bench "isAlpha" $ nf (ABL.parse (AC.takeWhile1 isAlpha)) bl
79 , bench "isAlpha_ascii" $ nf (ABL.parse (AC.takeWhile1 AC.isAlpha_ascii)) bl
80 , 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
82 ]
81 ]
83 , bench "word32LE" $ nf (AB.parse word32LE) b
82 , bench "word32LE" $ nf (AB.parse word32LE) b
84 , bgroup "scan" [
83 , bgroup "scan" [
@@ -86,13 +85,10 @@ main = do
86 , bench "long" $ nf (AB.parse quotedString) b
85 , bench "long" $ nf (AB.parse quotedString) b
87 ]
86 ]
88 , aeson
87 , aeson
89− , Genome.genome
90 , headersBS
88 , headersBS
91 , headersT
89 , headersT
92 , Links.links
90 , Links.links
93 , numbers
91 , numbers
94− , Sets.benchmarks
95− , Warp.benchmarks
96 ]
92 ]
97
93
98-- Benchmarks bind and (potential) bounds-check merging.
94-- Benchmarks bind and (potential) bounds-check merging.
benchmarks/Common.hs−10
@@ -3,14 +3,11 @@
3
3
4module Common (
4module Common (
5 chunksOf
5 chunksOf
6− , pathTo
7 , rechunkBS
6 , rechunkBS
8 , rechunkT
7 , rechunkT
9 ) where
8 ) where
10
9
11import Control.DeepSeq (NFData(rnf))
10import Control.DeepSeq (NFData(rnf))
12−import System.Directory (doesDirectoryExist)
13−import System.FilePath ((</>))
14import Text.Parsec (ParseError)
11import Text.Parsec (ParseError)
15import qualified Data.ByteString as B
12import qualified Data.ByteString as B
16import qualified Data.ByteString.Lazy as BL
13import qualified Data.ByteString.Lazy as BL
@@ -38,10 +35,3 @@ rechunkBS n = BL.fromChunks . map B.pack . chunksOf n . B.unpack
38
35
39rechunkT :: Int -> T.Text -> TL.Text
36rechunkT :: Int -> T.Text -> TL.Text
40rechunkT n = TL.fromChunks . map T.pack . chunksOf n . T.unpack
37rechunkT n = TL.fromChunks . map T.pack . chunksOf n . T.unpack
41−
42−pathTo :: String -> IO FilePath
43−pathTo wat = do
44− exists <- doesDirectoryExist "benchmarks"
45− return $ if exists
46− then "benchmarks" </> wat
47− else wat
benchmarks/Genome.hsdeleted−75
@@ -1,75 +0,0 @@
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+34−15
@@ -1,33 +1,52 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
1module HeadersByteString (headers) where
3module HeadersByteString (headers) where
2
4
3−import Common (pathTo, rechunkBS)
5+import Common (rechunkBS)
6+import Control.Applicative
4−import Criterion.Main (bench, bgroup, nf, nfIO)
7+import Criterion.Main (bench, bgroup, nf)
5import Criterion.Types (Benchmark)
8import Criterion.Types (Benchmark)
6−import HeadersByteString.Atto (request, response)
7−import Network.Wai.Handler.Warp.RequestHeader (parseHeaderLines)
8import qualified Data.Attoparsec.ByteString.Char8 as B
9import qualified Data.Attoparsec.ByteString.Char8 as B
9import qualified Data.Attoparsec.ByteString.Lazy as BL
10import qualified Data.Attoparsec.ByteString.Lazy as BL
10import qualified Data.ByteString.Char8 as B
11import qualified Data.ByteString.Char8 as B
11
12
12−-- Note: In the benchmarks for parsing an http request
13+header = do
13−-- from a strict bytestring, we consider warp's implementation,
14+ name <- B.takeWhile1 (B.inClass "a-zA-Z0-9_-") <* B.char ':' <* B.skipSpace
14−-- which has highly optimized code for handling the first
15+ body <- (:) <$> bodyLine <*> many (B.takeWhile1 B.isSpace *> bodyLine)
15−-- line in particular. It's treatment of the headers
16+ return (name, body)
16−-- is more relaxed from the treatment they are given by the
17+
17−-- attoparsec parser benchmarked here. Consequently, it
18+bodyLine = B.takeTill (\c -> c == '\r' || c == '\n') <* B.endOfLine
18−-- is should not be possible to match its performance since
19+
19−-- it accepts header names with disallowed characters.
20+requestLine =
21+ (,,) <$>
22+ (method <* B.skipSpace) <*>
23+ (B.takeTill B.isSpace <* B.skipSpace) <*>
24+ httpVersion
25+ where method = "GET" <|> "POST"
26+
27+httpVersion = "HTTP/" *> ((,) <$> (int <* B.char '.') <*> int)
28+
29+responseLine = (,,) <$>
30+ (httpVersion <* B.skipSpace) <*>
31+ (int <* B.skipSpace) <*>
32+ bodyLine
33+
34+int :: B.Parser Int
35+int = B.decimal
36+
37+request = (,) <$> (requestLine <* B.endOfLine) <*> many header
38+
39+response = (,) <$> responseLine <*> many header
20
40
21headers :: IO Benchmark
41headers :: IO Benchmark
22headers = do
42headers = do
23− req <- B.readFile =<< pathTo "http-request.txt"
43+ req <- B.readFile "http-request.txt"
24− resp <- B.readFile =<< pathTo "http-response.txt"
44+ resp <- B.readFile "http-response.txt"
25 let reql = rechunkBS 4 req
45 let reql = rechunkBS 4 req
26 respl = rechunkBS 4 resp
46 respl = rechunkBS 4 resp
27 return $ bgroup "headers" [
47 return $ bgroup "headers" [
28 bgroup "B" [
48 bgroup "B" [
29 bench "request" $ nf (B.parseOnly request) req
49 bench "request" $ nf (B.parseOnly request) req
30− , bench "warp" $ nfIO (parseHeaderLines (B.split '\n' req))
31 , bench "response" $ nf (B.parseOnly response) resp
50 , bench "response" $ nf (B.parseOnly response) resp
32 ]
51 ]
33 , bgroup "BL" [
52 , bgroup "BL" [
benchmarks/HeadersByteString/Atto.hsdeleted−57
@@ -1,57 +0,0 @@
1−{-# LANGUAGE BangPatterns, OverloadedStrings #-}
2−{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-}
3−module HeadersByteString.Atto
4− (
5− request
6− , response
7− ) where
8−
9−import Control.Applicative
10−import Control.DeepSeq (NFData(..))
11−import Network.HTTP.Types.Version (HttpVersion, http11)
12−import qualified Data.Attoparsec.ByteString.Char8 as B
13−import qualified Data.ByteString.Char8 as B
14−
15−instance NFData HttpVersion where
16− rnf !_ = ()
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−
26−header = do
27− name <- B.takeWhile1 isHeaderChar <* B.char ':' <* B.skipSpace
28− body <- bodyLine
29− return (name, body)
30−
31−bodyLine = B.takeTill (\c -> c == '\r' || c == '\n') <* B.endOfLine
32−
33−requestLine = do
34− m <- (B.takeTill B.isSpace <* B.char ' ')
35− (p,q) <- B.break (=='?') <$> (B.takeTill B.isSpace <* B.char ' ')
36− v <- httpVersion
37− return (m,p,q,v)
38−
39−httpVersion = http11 <$ "HTTP/1.1"
40−
41−responseLine = (,,) <$>
42− (httpVersion <* B.skipSpace) <*>
43− (int <* B.skipSpace) <*>
44− bodyLine
45−
46−int :: B.Parser Int
47−int = B.decimal
48−
49−request = (,) <$> (requestLine <* B.endOfLine) <*> manyheader
50−
51−response = (,) <$> responseLine <*> many header
52−
53−manyheader = do
54− c <- B.peekChar'
55− if c == '\r' || c == '\n'
56− then return []
57− else (:) <$> header <*> manyheader
benchmarks/HeadersText.hs+3−3
@@ -2,7 +2,7 @@
2{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
2{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
3module HeadersText (headers) where
3module HeadersText (headers) where
4
4
5−import Common (pathTo, rechunkT)
5+import Common (rechunkT)
6import Control.Applicative
6import Control.Applicative
7import Criterion.Main (bench, bgroup, nf)
7import Criterion.Main (bench, bgroup, nf)
8import Criterion.Types (Benchmark)
8import Criterion.Types (Benchmark)
@@ -41,8 +41,8 @@ response = (,) <$> responseLine <*> many header
41
41
42headers :: IO Benchmark
42headers :: IO Benchmark
43headers = do
43headers = do
44− req <- T.readFile =<< pathTo "http-request.txt"
44+ req <- T.readFile "http-request.txt"
45− resp <- T.readFile =<< pathTo "http-response.txt"
45+ resp <- T.readFile "http-response.txt"
46 let reql = rechunkT 4 req
46 let reql = rechunkT 4 req
47 respl = rechunkT 4 resp
47 respl = rechunkT 4 resp
48 return $ bgroup "headers" [
48 return $ bgroup "headers" [
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.ByteString as A
8+import Data.Attoparsec as A
9−import Data.Attoparsec.ByteString.Char8 as A8
9+import Data.Attoparsec.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,5 +1,4 @@
1{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE BangPatterns #-}
2−{-# OPTIONS_GHC -fno-warn-deprecations #-}
3
2
4module Numbers (numbers) where
3module Numbers (numbers) where
5
4
benchmarks/Sets.hsdeleted−21
@@ -1,21 +0,0 @@
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.hsdeleted−121
@@ -1,121 +0,0 @@
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/Warp.hsdeleted−17
@@ -1,17 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−
3−module Warp (benchmarks) where
4−
5−import Criterion.Main (bench, bgroup, nf)
6−import Criterion.Types (Benchmark)
7−import Data.ByteString (ByteString)
8−import Network.Wai.Handler.Warp.ReadInt (readInt)
9−import qualified Data.Attoparsec.ByteString.Char8 as B
10−
11−benchmarks :: Benchmark
12−benchmarks = bgroup "warp" [
13− bgroup "decimal" [
14− bench "warp" $ nf (readInt :: ByteString -> Int) "31337"
15− , bench "atto" $ nf (B.parse (B.decimal :: B.Parser Int)) "31337"
16− ]
17− ]
benchmarks/attoparsec-benchmarks.cabal+3−14
@@ -9,33 +9,22 @@ build-type: Simple
9executable attoparsec-benchmarks
9executable attoparsec-benchmarks
10 main-is: Benchmarks.hs
10 main-is: Benchmarks.hs
11 other-modules:
11 other-modules:
12− Common
13 HeadersByteString
12 HeadersByteString
14− HeadersByteString.Atto
15 HeadersText
13 HeadersText
16 Links
14 Links
17 Numbers
15 Numbers
18− Network.Wai.Handler.Warp.ReadInt
19− Sets
20− TextFastSet
21− Warp
22− hs-source-dirs: .. . warp-3.0.1.1
16+ hs-source-dirs: .. .
23 ghc-options: -O2 -Wall -rtsopts
17 ghc-options: -O2 -Wall -rtsopts
24 build-depends:
18 build-depends:
25 array,
19 array,
26 base == 4.*,
20 base == 4.*,
27 bytestring >= 0.10.4.0,
21 bytestring >= 0.10.4.0,
28− case-insensitive,
29− containers,
30− criterion >= 1.0,
22+ criterion >= 0.5,
31 deepseq >= 1.1,
23 deepseq >= 1.1,
32 directory,
24 directory,
33 filepath,
25 filepath,
34− ghc-prim,
35− http-types,
36 parsec >= 3.1.2,
26 parsec >= 3.1.2,
37− scientific >= 0.3.1,
27+ scientific,
38 text >= 1.1.1.0,
28 text >= 1.1.1.0,
39− transformers,
40 unordered-containers,
29 unordered-containers,
41 vector
30 vector
benchmarks/http-request.txt+8−3
@@ -1,9 +1,14 @@
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, image/webp, */*; q=0.8
3+Accept: text/html, application/xhtml+xml, application/xml; q=0.9,
4+ image/webp, */*; q=0.8
4Accept-Encoding: gzip,deflate,sdch
5Accept-Encoding: gzip,deflate,sdch
5Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
6Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
6Cache-Control: max-age=0
7Cache-Control: max-age=0
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)
8+Cookie: guest_id=v1%3A139; _twitter_sess=BAh7CSIKZmxhc2hJQz-e1e1;
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)
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
12+ AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.86
13+ Safari/537.36
9
14
benchmarks/http-response.txt+4−2
@@ -3,8 +3,10 @@ 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; expires=Sun, 08-May-2016 04:24:57 GMT; path=/; domain=.google.com
6+Set-Cookie: PREF=ID=1a871afc4240db5e:FF:LM=1399609497:S=e5MZ0itEekjVwNcU;
7+ expires=Sun, 08-May-2016 04:24:57 GMT; path=/; domain=.google.com
7−Set-Cookie: NID=67=-WlfaDG6VSEPk7abAjrK98HBSoCD2ID6JKkUR95tEumzDmg7Fc8pSQ8; expires=Sat, 08-Nov-2014 04:24:57 GMT; path=/; domain=.google.com; HttpOnly
8+Set-Cookie: NID=67=-WlfaDG6VSEPk7abAjrK98HBSoCD2ID6JKkUR95tEumzDmg7Fc8pSQ8;
9+ expires=Sat, 08-Nov-2014 04:24:57 GMT; path=/; domain=.google.com; HttpOnly
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."
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."
9Server: gws
11Server: gws
10X-XSS-Protection: 1; mode=block
12X-XSS-Protection: 1; mode=block
benchmarks/warp-3.0.1.1/LICENSEdeleted−20
@@ -1,20 +0,0 @@
1−Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
2−
3−Permission is hereby granted, free of charge, to any person obtaining
4−a copy of this software and associated documentation files (the
5−"Software"), to deal in the Software without restriction, including
6−without limitation the rights to use, copy, modify, merge, publish,
7−distribute, sublicense, and/or sell copies of the Software, and to
8−permit persons to whom the Software is furnished to do so, subject to
9−the following conditions:
10−
11−The above copyright notice and this permission notice shall be
12−included in all copies or substantial portions of the Software.
13−
14−THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15−EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16−MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17−NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18−LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19−OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20−WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
benchmarks/warp-3.0.1.1/Network/Wai/Handler/Warp/ReadInt.hsdeleted−67
@@ -1,67 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE MagicHash #-}
3−{-# LANGUAGE BangPatterns #-}
4−
5−-- Copyright : Erik de Castro Lopo <erikd@mega-nerd.com>
6−-- License : BSD3
7−
8−module Network.Wai.Handler.Warp.ReadInt (
9− readInt
10− , readInt64
11− ) where
12−
13−-- This function lives in its own file because the MagicHash pragma interacts
14−-- poorly with the CPP pragma.
15−
16−import Data.ByteString (ByteString)
17−import qualified Data.ByteString as S
18−import Data.Int (Int64)
19−import GHC.Prim
20−import GHC.Types
21−import GHC.Word
22−
23−{-# INLINE readInt #-}
24−readInt :: Integral a => ByteString -> a
25−readInt bs = fromIntegral $ readInt64 bs
26−
27−-- This function is used to parse the Content-Length field of HTTP headers and
28−-- is a performance hot spot. It should only be replaced with something
29−-- significantly and provably faster.
30−--
31−-- It needs to be able work correctly on 32 bit CPUs for file sizes > 2G so we
32−-- use Int64 here and then make a generic 'readInt' that allows conversion to
33−-- Int and Integer.
34−
35−{-# NOINLINE readInt64 #-}
36−readInt64 :: ByteString -> Int64
37−readInt64 bs = S.foldl' (\ !i !c -> i * 10 + fromIntegral (mhDigitToInt c)) 0
38− $ S.takeWhile isDigit bs
39−
40−data Table = Table !Addr#
41−
42−{-# NOINLINE mhDigitToInt #-}
43−mhDigitToInt :: Word8 -> Int
44−mhDigitToInt (W8# i) = I# (word2Int# (indexWord8OffAddr# addr (word2Int# i)))
45− where
46− !(Table addr) = table
47− table :: Table
48− table = Table
49− "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
50− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
51− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
52− \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\x00\x00\x00\x00\x00\
53− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
54− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
55− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
56− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
57− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
58− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
59− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
60− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
61− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
62− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
63− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
64− \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#
65−
66−isDigit :: Word8 -> Bool
67−isDigit w = w >= 48 && w <= 57
benchmarks/warp-3.0.1.1/Network/Wai/Handler/Warp/RequestHeader.hsdeleted−172
@@ -1,172 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE BangPatterns #-}
4−
5−module Network.Wai.Handler.Warp.RequestHeader (
6− parseHeaderLines
7− , parseByteRanges
8− ) where
9−
10−import Control.Exception (Exception, throwIO)
11−import Control.Monad (when)
12−import Data.Typeable (Typeable)
13−import qualified Data.ByteString as S
14−import qualified Data.ByteString.Char8 as B (unpack, readInteger)
15−import Data.ByteString.Internal (ByteString(..), memchr)
16−import qualified Data.CaseInsensitive as CI
17−import Data.Word (Word8)
18−import Foreign.ForeignPtr (withForeignPtr)
19−import Foreign.Ptr (Ptr, plusPtr, minusPtr, nullPtr)
20−import Foreign.Storable (peek)
21−import qualified Network.HTTP.Types as H
22−-- import Network.Wai.Handler.Warp.Types
23−import qualified Network.HTTP.Types.Header as HH
24−-- $setup
25−-- >>> :set -XOverloadedStrings
26−
27−data InvalidRequest = NotEnoughLines [String]
28− | BadFirstLine String
29− | NonHttp
30− | IncompleteHeaders
31− | ConnectionClosedByPeer
32− | OverLargeHeader
33− deriving (Eq, Typeable, Show)
34−
35−instance Exception InvalidRequest
36−
37−----------------------------------------------------------------
38−
39−parseHeaderLines :: [ByteString]
40− -> IO (H.Method
41− ,ByteString -- Path
42− ,ByteString -- Path, parsed
43− ,ByteString -- Query
44− ,H.HttpVersion
45− ,H.RequestHeaders
46− )
47−parseHeaderLines [] = throwIO $ NotEnoughLines []
48−parseHeaderLines (firstLine:otherLines) = do
49− (method, path', query, httpversion) <- parseRequestLine firstLine
50− let path = H.extractPath path'
51− hdr = map parseHeader otherLines
52− return (method, path', path, query, httpversion, hdr)
53−
54−----------------------------------------------------------------
55−
56−-- |
57−--
58−-- >>> parseRequestLine "GET / HTTP/1.1"
59−-- ("GET","/","",HTTP/1.1)
60−-- >>> parseRequestLine "POST /cgi/search.cgi?key=foo HTTP/1.0"
61−-- ("POST","/cgi/search.cgi","?key=foo",HTTP/1.0)
62−-- >>> parseRequestLine "GET "
63−-- *** Exception: Warp: Invalid first line of request: "GET "
64−-- >>> parseRequestLine "GET /NotHTTP UNKNOWN/1.1"
65−-- *** Exception: Warp: Request line specified a non-HTTP request
66−parseRequestLine :: ByteString
67− -> IO (H.Method
68− ,ByteString -- Path
69− ,ByteString -- Query
70− ,H.HttpVersion)
71−parseRequestLine requestLine@(PS fptr off len) = withForeignPtr fptr $ \ptr -> do
72− when (len < 14) $ throwIO baderr
73− let methodptr = ptr `plusPtr` off
74− limptr = methodptr `plusPtr` len
75− lim0 = fromIntegral len
76−
77− pathptr0 <- memchr methodptr 32 lim0 -- ' '
78− when (pathptr0 == nullPtr || (limptr `minusPtr` pathptr0) < 11) $
79− throwIO baderr
80− let pathptr = pathptr0 `plusPtr` 1
81− lim1 = fromIntegral (limptr `minusPtr` pathptr0)
82−
83− httpptr0 <- memchr pathptr 32 lim1 -- ' '
84− when (httpptr0 == nullPtr || (limptr `minusPtr` httpptr0) < 9) $
85− throwIO baderr
86− let httpptr = httpptr0 `plusPtr` 1
87− lim2 = fromIntegral (httpptr0 `minusPtr` pathptr)
88−
89− checkHTTP httpptr
90− !hv <- httpVersion httpptr
91− queryptr <- memchr pathptr 63 lim2 -- '?'
92−
93− let !method = bs ptr methodptr pathptr0
94− !path
95− | queryptr == nullPtr = bs ptr pathptr httpptr0
96− | otherwise = bs ptr pathptr queryptr
97− !query
98− | queryptr == nullPtr = S.empty
99− | otherwise = bs ptr queryptr httpptr0
100−
101− return (method,path,query,hv)
102− where
103− baderr = BadFirstLine $ B.unpack requestLine
104− check :: Ptr Word8 -> Int -> Word8 -> IO ()
105− check p n w = do
106− w0 <- peek $ p `plusPtr` n
107− when (w0 /= w) $ throwIO NonHttp
108− checkHTTP httpptr = do
109− check httpptr 0 72 -- 'H'
110− check httpptr 1 84 -- 'T'
111− check httpptr 2 84 -- 'T'
112− check httpptr 3 80 -- 'P'
113− check httpptr 4 47 -- '/'
114− check httpptr 6 46 -- '.'
115− httpVersion httpptr = do
116− major <- peek $ httpptr `plusPtr` 5
117− minor <- peek $ httpptr `plusPtr` 7
118− return $ if major == (49 :: Word8) && minor == (49 :: Word8) then
119− H.http11
120− else
121− H.http10
122− bs ptr p0 p1 = PS fptr o l
123− where
124− o = p0 `minusPtr` ptr
125− l = p1 `minusPtr` p0
126−
127−----------------------------------------------------------------
128−
129−-- |
130−--
131−-- >>> parseHeader "Content-Length:47"
132−-- ("Content-Length","47")
133−-- >>> parseHeader "Accept-Ranges: bytes"
134−-- ("Accept-Ranges","bytes")
135−-- >>> parseHeader "Host: example.com:8080"
136−-- ("Host","example.com:8080")
137−-- >>> parseHeader "NoSemiColon"
138−-- ("NoSemiColon","")
139−
140−parseHeader :: ByteString -> H.Header
141−parseHeader s =
142− let (k, rest) = S.break (== 58) s -- ':'
143− rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
144− in (CI.mk k, rest')
145−
146−parseByteRanges :: S.ByteString -> Maybe HH.ByteRanges
147−parseByteRanges bs1 = do
148− bs2 <- stripPrefix "bytes=" bs1
149− (r, bs3) <- range bs2
150− ranges (r:) bs3
151− where
152− range bs2 =
153− case stripPrefix "-" bs2 of
154− Just bs3 -> do
155− (i, bs4) <- B.readInteger bs3
156− Just (HH.ByteRangeSuffix i, bs4)
157− Nothing -> do
158− (i, bs3) <- B.readInteger bs2
159− bs4 <- stripPrefix "-" bs3
160− case B.readInteger bs4 of
161− Nothing -> Just (HH.ByteRangeFrom i, bs4)
162− Just (j, bs5) -> Just (HH.ByteRangeFromTo i j, bs5)
163− ranges front bs3 =
164− case stripPrefix "," bs3 of
165− Nothing -> Just (front [])
166− Just bs4 -> do
167− (r, bs5) <- range bs4
168− ranges (front . (r:)) bs5
169−
170− stripPrefix x y
171− | x `S.isPrefixOf` y = Just (S.drop (S.length x) y)
172− | otherwise = Nothing
changelog.md−93
@@ -1,96 +1,3 @@
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−
55−0.12.1.2
56−
57−* Fixed the incorrect tracking of capacity if the initial buffer was
58− empty (https://github.com/bos/attoparsec/issues/75)
59−
60−0.12.1.1
61−
62−* Fixed a data corruption bug that occurred under some circumstances
63− if a buffer grew after prompting for more input
64− (https://github.com/bos/attoparsec/issues/74)
65−
66−0.12.1.0
67−
68−* Now compatible with GHC 7.9
69−
70−* Reintroduced the Chunk class, used by the parsers package
71−
72−0.12.0.0
73−
74−* A new internal representation makes almost all real-world parsers
75− faster, sometimes by big margins. For example, parsing JSON data
76− with aeson is now up to 70% faster. These performance improvements
77− also come with reduced memory consumption and some new capabilities.
78−
79−* The new match combinator gives both the result of a parse and the
80− input that it matched.
81−
82−* The test suite has doubled in size. This made it possible to switch
83− to the new internal representation with a decent degree of
84− confidence that everything was more or less working.
85−
86−* The benchmark suite now contains a small family of benchmarks taken
87− from real-world uses of attoparsec.
88−
89−* A few types that ought to have been private now are.
90−
91−* A few obsolete modules and functions have been marked as deprecated.
92− They will be removed from the next major release.
93−
940.11.3.0
10.11.3.0
95
2
96* New function scientific is compatible with rational, but parses
3* New function scientific is compatible with rational, but parses
examples/RFC2616.hs+47−22
@@ -5,61 +5,86 @@ module RFC2616
5 Header(..)
5 Header(..)
6 , Request(..)
6 , Request(..)
7 , Response(..)
7 , Response(..)
8+ , isToken
9+ , messageHeader
8 , request
10 , request
11+ , requestLine
9 , response
12 , response
13+ , responseLine
14+ , lowerHeader
15+ , lookupHeader
10 ) where
16 ) where
11
17
12import Control.Applicative
18import Control.Applicative
13import Data.Attoparsec.ByteString as P
19import Data.Attoparsec.ByteString as P
20+import qualified Data.Attoparsec.ByteString.Char8 as P8
14import Data.Attoparsec.ByteString.Char8 (char8, endOfLine, isDigit_w8)
21import Data.Attoparsec.ByteString.Char8 (char8, endOfLine, isDigit_w8)
15−import Data.ByteString (ByteString)
16import Data.Word (Word8)
22import Data.Word (Word8)
17−import Data.Attoparsec.ByteString.Char8 (isEndOfLine, isHorizontalSpace)
23+import qualified Data.ByteString.Char8 as B hiding (map)
24+import qualified Data.ByteString as B (map)
18
25
19isToken :: Word8 -> Bool
26isToken :: Word8 -> Bool
20isToken w = w <= 127 && notInClass "\0-\31()<>@,;:\\\"/[]?={} \t" w
27isToken w = w <= 127 && notInClass "\0-\31()<>@,;:\\\"/[]?={} \t" w
21
28
22skipSpaces :: Parser ()
29skipSpaces :: Parser ()
23−skipSpaces = satisfy isHorizontalSpace *> skipWhile isHorizontalSpace
30+skipSpaces = satisfy P8.isHorizontalSpace *> skipWhile P8.isHorizontalSpace
24
31
25data Request = Request {
32data Request = Request {
26− requestMethod :: ByteString
33+ requestMethod :: !B.ByteString
27− , requestUri :: ByteString
34+ , requestUri :: !B.ByteString
28− , requestVersion :: ByteString
35+ , requestVersion :: !B.ByteString
29 } deriving (Eq, Ord, Show)
36 } deriving (Eq, Ord, Show)
30
37
31−httpVersion :: Parser ByteString
38+httpVersion :: Parser B.ByteString
32httpVersion = "HTTP/" *> P.takeWhile (\c -> isDigit_w8 c || c == 46)
39httpVersion = "HTTP/" *> P.takeWhile (\c -> isDigit_w8 c || c == 46)
33
40
34requestLine :: Parser Request
41requestLine :: Parser Request
42+requestLine = do
35−requestLine = Request <$> (takeWhile1 isToken <* char8 ' ')
43+ method <- P.takeWhile1 isToken <* char8 ' '
36− <*> (takeWhile1 (/=32) <* char8 ' ')
44+ uri <- P.takeWhile1 (/=32) <* char8 ' '
37− <*> (httpVersion <* endOfLine)
45+ version <- httpVersion <* endOfLine
46+ return $! Request method uri version
38
47
39data Header = Header {
48data Header = Header {
40− headerName :: ByteString
49+ headerName :: !B.ByteString
41− , headerValue :: [ByteString]
50+ , headerValue :: [B.ByteString]
42 } deriving (Eq, Ord, Show)
51 } deriving (Eq, Ord, Show)
43
52
44messageHeader :: Parser Header
53messageHeader :: Parser Header
45−messageHeader = Header
54+messageHeader = do
46− <$> (P.takeWhile isToken <* char8 ':' <* skipWhile isHorizontalSpace)
55+ header <- P.takeWhile isToken <* char8 ':' <* skipWhile P8.isHorizontalSpace
47− <*> ((:) <$> (takeTill isEndOfLine <* endOfLine)
56+ body <- takeTill P8.isEndOfLine <* endOfLine
48− <*> (many $ skipSpaces *> takeTill isEndOfLine <* endOfLine))
57+ bodies <- many $ skipSpaces *> takeTill P8.isEndOfLine <* endOfLine
58+ return $! Header header (body:bodies)
49
59
50request :: Parser (Request, [Header])
60request :: Parser (Request, [Header])
51request = (,) <$> requestLine <*> many messageHeader <* endOfLine
61request = (,) <$> requestLine <*> many messageHeader <* endOfLine
52
62
53data Response = Response {
63data Response = Response {
54− responseVersion :: ByteString
64+ responseVersion :: !B.ByteString
55− , responseCode :: ByteString
65+ , responseCode :: !B.ByteString
56− , responseMsg :: ByteString
66+ , responseMsg :: !B.ByteString
57 } deriving (Eq, Ord, Show)
67 } deriving (Eq, Ord, Show)
58
68
59responseLine :: Parser Response
69responseLine :: Parser Response
70+responseLine = do
60−responseLine = Response <$> (httpVersion <* char8 ' ')
71+ version <- httpVersion <* char8 ' '
61− <*> (P.takeWhile isDigit_w8 <* char8 ' ')
72+ code <- P.takeWhile isDigit_w8 <* char8 ' '
62− <*> (takeTill isEndOfLine <* endOfLine)
73+ msg <- P.takeTill P8.isEndOfLine <* endOfLine
74+ return $! Response version code msg
63
75
64response :: Parser (Response, [Header])
76response :: Parser (Response, [Header])
65response = (,) <$> responseLine <*> many messageHeader <* endOfLine
77response = (,) <$> responseLine <*> many messageHeader <* endOfLine
78+
79+lowerHeader :: Header -> Header
80+lowerHeader (Header n v) = Header (B.map toLower n) (map (B.map toLower) v)
81+ where toLower w | w >= 65 && w <= 90 = w + 32
82+ | otherwise = w
83+
84+lookupHeader :: B.ByteString -> [Header] -> [B.ByteString]
85+lookupHeader k = go
86+ where
87+ go (Header n v:hs)
88+ | k == n = v
89+ | otherwise = go hs
90+ go _ = []
tests/Makefilenew file+14
@@ -0,0 +1,14 @@
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−8
@@ -1,19 +1,13 @@
1{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
1{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
2module Main (main) where
2module Main (main) where
3
3
4−import qualified QC.Buffer as Buffer
5import qualified QC.ByteString as ByteString
4import qualified QC.ByteString as ByteString
6−import qualified QC.Combinator as Combinator
7−import qualified QC.Simple as Simple
8import qualified QC.Text as Text
5import qualified QC.Text as Text
9−import Test.Tasty (defaultMain, testGroup)
6+import Test.Framework (defaultMain, testGroup)
10
7
11main = defaultMain tests
8main = defaultMain tests
12
9
13−tests = testGroup "tests" [
10+tests = [
14 testGroup "bs" ByteString.tests
11 testGroup "bs" ByteString.tests
15− , testGroup "buf" Buffer.tests
16− , testGroup "combinator" Combinator.tests
17− , testGroup "simple" Simple.tests
18 , testGroup "text" Text.tests
12 , testGroup "text" Text.tests
19 ]
13 ]
tests/QC/Buffer.hsdeleted−96
@@ -1,96 +0,0 @@
1−{-# LANGUAGE BangPatterns, CPP, FlexibleInstances, OverloadedStrings,
2− TypeSynonymInstances #-}
3−
4−module QC.Buffer (tests) where
5−
6−#if !MIN_VERSION_base(4,8,0)
7−import Control.Applicative ((<$>))
8−import Data.Monoid (Monoid(mconcat))
9−#endif
10−import QC.Common ()
11−import Test.Tasty (TestTree)
12−import Test.Tasty.QuickCheck (testProperty)
13−import Test.QuickCheck
14−import qualified Data.Attoparsec.ByteString.Buffer as BB
15−import qualified Data.Attoparsec.Text.Buffer as BT
16−import qualified Data.ByteString as B
17−import qualified Data.ByteString.Unsafe as B
18−import qualified Data.Text as T
19−import qualified Data.Text.Unsafe as T
20−
21−data BP t b = BP [t] !t !b
22− deriving (Eq, Show)
23−
24−type BPB = BP B.ByteString BB.Buffer
25−type BPT = BP T.Text BT.Buffer
26−
27−instance Arbitrary BPB where
28− arbitrary = do
29− bss <- arbitrary
30− return $! toBP BB.buffer bss
31−
32− shrink (BP bss _ _) = toBP BB.buffer <$> shrink bss
33−
34−instance Arbitrary BPT where
35− arbitrary = do
36− bss <- arbitrary
37− return $! toBP BT.buffer bss
38−
39− shrink (BP bss _ _) = toBP BT.buffer <$> shrink bss
40−
41−toBP :: (Monoid a, Monoid b) => (a -> b) -> [a] -> BP a b
42−toBP buf bss = BP bss (mconcat bss) (mconcat (map buf bss))
43−
44−b_unbuffer :: BPB -> Property
45−b_unbuffer (BP _ts t buf) = t === BB.unbuffer buf
46−
47−t_unbuffer :: BPT -> Property
48−t_unbuffer (BP _ts t buf) = t === BT.unbuffer buf
49−
50−b_length :: BPB -> Property
51−b_length (BP _ts t buf) = B.length t === BB.length buf
52−
53−t_length :: BPT -> Property
54−t_length (BP _ts t buf) = T.lengthWord16 t === BT.length buf
55−
56−b_unsafeIndex :: BPB -> Gen Property
57−b_unsafeIndex (BP _ts t buf) = do
58− let l = B.length t
59− i <- choose (0,l-1)
60− return $ l === 0 .||. B.unsafeIndex t i === BB.unsafeIndex buf i
61−
62−t_iter :: BPT -> Gen Property
63−t_iter (BP _ts t buf) = do
64− let l = T.lengthWord16 t
65− i <- choose (0,l-1)
66− let it (T.Iter c q) = (c,q)
67− return $ l === 0 .||. it (T.iter t i) === it (BT.iter buf i)
68−
69−t_iter_ :: BPT -> Gen Property
70−t_iter_ (BP _ts t buf) = do
71− let l = T.lengthWord16 t
72− i <- choose (0,l-1)
73− return $ l === 0 .||. T.iter_ t i === BT.iter_ buf i
74−
75−b_unsafeDrop :: BPB -> Gen Property
76−b_unsafeDrop (BP _ts t buf) = do
77− i <- choose (0, B.length t)
78− return $ B.unsafeDrop i t === BB.unsafeDrop i buf
79−
80−t_dropWord16 :: BPT -> Gen Property
81−t_dropWord16 (BP _ts t buf) = do
82− i <- choose (0, T.lengthWord16 t)
83− return $ T.dropWord16 i t === BT.dropWord16 i buf
84−
85−tests :: [TestTree]
86−tests = [
87− testProperty "b_unbuffer" b_unbuffer
88− , testProperty "t_unbuffer" t_unbuffer
89− , testProperty "b_length" b_length
90− , testProperty "t_length" t_length
91− , testProperty "b_unsafeIndex" b_unsafeIndex
92− , testProperty "t_iter" t_iter
93− , testProperty "t_iter_" t_iter_
94− , testProperty "b_unsafeDrop" b_unsafeDrop
95− , testProperty "t_dropWord16" t_dropWord16
96− ]
tests/QC/ByteString.hs+78−149
@@ -1,187 +1,116 @@
1−{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}
1+{-# LANGUAGE BangPatterns, OverloadedStrings #-}
2+{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-}
2module QC.ByteString (tests) where
3module QC.ByteString (tests) where
3
4
4−#if !MIN_VERSION_base(4,8,0)
5−import Control.Applicative ((<*>), (<$>))
5+import Control.Applicative ((<$>), (<*>))
6−#endif
7−import Data.Char (chr, ord, toUpper)
8−import Data.Int (Int64)
9−import Data.Word (Word8)
10−import Prelude hiding (take, takeWhile)
6+import Prelude hiding (takeWhile)
11−import QC.Common (ASCII(..), liftOp, parseBS, toStrictBS)
7+import Test.Framework.Providers.QuickCheck2 (testProperty)
12−import Test.Tasty (TestTree)
13−import Test.Tasty.QuickCheck (testProperty)
14import Test.QuickCheck
8import Test.QuickCheck
15import qualified Data.Attoparsec.ByteString as P
9import qualified Data.Attoparsec.ByteString as P
16−import qualified Data.Attoparsec.ByteString.Char8 as P8
17−import qualified Data.Attoparsec.ByteString.FastSet as S
18import qualified Data.Attoparsec.ByteString.Lazy as PL
10import qualified Data.Attoparsec.ByteString.Lazy as PL
19import qualified Data.ByteString as B
11import qualified Data.ByteString as B
20−import qualified Data.ByteString.Char8 as B8
21import qualified Data.ByteString.Lazy as L
12import qualified Data.ByteString.Lazy as L
22−import qualified Data.ByteString.Lazy.Char8 as L8
13+
14+instance Arbitrary B.ByteString where
15+ arbitrary = B.pack <$> arbitrary
16+
17+instance Arbitrary L.ByteString where
18+ arbitrary = sized $ \n -> resize (round (sqrt (toEnum n :: Double)))
19+ ((L.fromChunks . map (B.pack . nonEmpty)) <$> arbitrary)
20+ where nonEmpty (NonEmpty a) = a
21+
22+-- Naming.
23+
24+{-
25+label (NonEmpty s) = case parse (anyWord8 <?> s) B.empty of
26+ (_, Left err) -> s `isInfixOf` err
27+ _ -> False
28+-}
23
29
24-- Basic byte-level combinators.
30-- Basic byte-level combinators.
25
31
26−satisfy :: Word8 -> L.ByteString -> Property
32+maybeP p = PL.maybeResult . PL.parse p
27−satisfy w s = parseBS (P.satisfy (<=w)) (L.cons w s) === Just w
28
33
29−satisfyWith :: Word8 -> L.ByteString -> Property
34+defP p = PL.parse p
30−satisfyWith w s = parseBS (P.satisfyWith (chr . fromIntegral) (<=c))
31− (L.cons (fromIntegral (ord c)) s) === Just c
32− where
33− c = chr (fromIntegral w)
34
35
35−word8 :: Word8 -> L.ByteString -> Property
36−word8 w s = parseBS (P.word8 w) (L.cons w s) === Just w
36+satisfy w s = maybeP (P.satisfy (<=w)) (L.cons w s) == Just w
37
37
38−skip :: Word8 -> L.ByteString -> Property
38+word8 w s = maybeP (P.word8 w) (L.cons w s) == Just w
39−skip w s =
40− case (parseBS (P.skip (<w)) s, L.uncons s) of
41− (Nothing, mcs) -> maybe (property True) (expectFailure . it) mcs
42− (Just _, mcs) -> maybe (property False) it mcs
43− where it cs = liftOp "<" (<) (fst cs) w
44
39
45−anyWord8 :: L.ByteString -> Property
46anyWord8 s
40anyWord8 s
47− | L.null s = p === Nothing
41+ | L.null s = p == Nothing
48− | otherwise = p === Just (L.head s)
42+ | otherwise = p == Just (L.head s)
49− where p = parseBS P.anyWord8 s
43+ where p = maybeP P.anyWord8 s
50−
44+
51−notWord8 :: Word8 -> NonEmptyList Word8 -> Property
52−notWord8 w (NonEmpty s) = parseBS (P.notWord8 w) bs === if v == w
45+notWord8 w (NonEmpty s) = maybeP (P.notWord8 w) bs == if v == w
53− then Nothing
46+ then Nothing
54− else Just v
47+ else Just v
55 where v = L.head bs
48 where v = L.head bs
56 bs = L.pack s
49 bs = L.pack s
57
50
58−peekWord8 :: L.ByteString -> Property
59peekWord8 s
51peekWord8 s
60− | L.null s = p === Just (Nothing, s)
52+ | L.null s = p == Just (Nothing, s)
61− | otherwise = p === Just (Just (L.head s), s)
53+ | otherwise = p == Just (Just (L.head s), s)
62− where p = parseBS ((,) <$> P.peekWord8 <*> P.takeLazyByteString) s
54+ where p = maybeP ((,) <$> P.peekWord8 <*> P.takeLazyByteString) s
63−
55+
64−peekWord8' :: L.ByteString -> Property
65−peekWord8' s = parseBS P.peekWord8' s === (fst <$> L.uncons s)
66−
67−string :: L.ByteString -> L.ByteString -> Property
68−string s t = parseBS (P.string s') (s `L.append` t) === Just s'
56+string s t = maybeP (P.string s') (s `L.append` t) == Just s'
69− where s' = toStrictBS s
57+ where s' = toStrict s
70−
58+
71−stringCI :: ASCII L.ByteString -> ASCII L.ByteString -> Property
59+toStrict = B.concat . L.toChunks
72−stringCI (ASCII s) (ASCII t) =
60+
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−
83−skipWhile :: Word8 -> L.ByteString -> Property
84skipWhile w s =
61skipWhile w s =
85 let t = L.dropWhile (<=w) s
62 let t = L.dropWhile (<=w) s
86− in case PL.parse (P.skipWhile (<=w)) s of
63+ in case defP (P.skipWhile (<=w)) s of
87− PL.Done t' () -> t === t'
64+ PL.Done t' () -> t == t'
88− _ -> property False
65+ _ -> False
89
66
90−takeCount :: Positive Int -> L.ByteString -> Property
91takeCount (Positive k) s =
67takeCount (Positive k) s =
92− case parseBS (P.take k) s of
68+ case maybeP (P.take k) s of
93− Nothing -> liftOp ">" (>) (fromIntegral k) (L.length s)
69+ Nothing -> fromIntegral k > L.length s
94− Just _s -> liftOp "<=" (<=) (fromIntegral k) (L.length s)
70+ Just _ -> fromIntegral k <= L.length s
95
71
96−takeWhile :: Word8 -> L.ByteString -> Property
97takeWhile w s =
72takeWhile w s =
98 let (h,t) = L.span (==w) s
73 let (h,t) = L.span (==w) s
99− in case PL.parse (P.takeWhile (==w)) s of
74+ in case defP (P.takeWhile (==w)) s of
100− PL.Done t' h' -> t === t' .&&. toStrictBS h === h'
75+ PL.Done t' h' -> t == t' && toStrict h == h'
101− _ -> property False
76+ _ -> False
102
77
103−take :: Int -> L.ByteString -> Property
104−take n s = maybe (property $ L.length s < fromIntegral n)
105− (=== B.take n (toStrictBS s)) $
106− parseBS (P.take n) s
107−
108−takeByteString :: L.ByteString -> Property
109−takeByteString s = maybe (property False) (=== toStrictBS s) .
110− parseBS P.takeByteString $ s
111−
112−takeLazyByteString :: L.ByteString -> Property
113−takeLazyByteString s = maybe (property False) (=== s) .
114− parseBS P.takeLazyByteString $ s
115−
116−takeWhile1 :: Word8 -> L.ByteString -> Property
117takeWhile1 w s =
78takeWhile1 w s =
118 let s' = L.cons w s
79 let s' = L.cons w s
119 (h,t) = L.span (<=w) s'
80 (h,t) = L.span (<=w) s'
120− in case PL.parse (P.takeWhile1 (<=w)) s' of
81+ in case defP (P.takeWhile1 (<=w)) s' of
121− PL.Done t' h' -> t === t' .&&. toStrictBS h === h'
82+ PL.Done t' h' -> t == t' && toStrict h == h'
122− _ -> property False
83+ _ -> False
123
84
124−takeTill :: Word8 -> L.ByteString -> Property
125takeTill w s =
85takeTill w s =
126 let (h,t) = L.break (==w) s
86 let (h,t) = L.break (==w) s
127− in case PL.parse (P.takeTill (==w)) s of
87+ in case defP (P.takeTill (==w)) s of
128− PL.Done t' h' -> t === t' .&&. toStrictBS h === h'
88+ PL.Done t' h' -> t == t' && toStrict h == h'
129− _ -> property False
89+ _ -> False
130−
90+
131−takeWhile1_empty :: Property
132−takeWhile1_empty = parseBS (P.takeWhile1 undefined) L.empty === Nothing
91+takeWhile1_empty = maybeP (P.takeWhile1 undefined) L.empty == Nothing
133−
134−endOfInput :: L.ByteString -> Property
135−endOfInput s = parseBS P.endOfInput s === if L.null s
136− then Just ()
137− else Nothing
138−
139−endOfLine :: L.ByteString -> Property
140−endOfLine s =
141− case (parseBS P8.endOfLine s, L8.uncons s) of
142− (Nothing, mcs) -> maybe (property True) (expectFailure . eol) mcs
143− (Just _, mcs) -> maybe (property False) eol mcs
144− where eol (c,s') = c === '\n' .||.
145− (c, fst <$> L8.uncons s') === ('\r', Just '\n')
146−
147−scan :: L.ByteString -> Positive Int64 -> Property
148−scan s (Positive k) = parseBS p s === Just (toStrictBS $ L.take k s)
149− where p = P.scan k $ \ n _ ->
150− if n > 0 then let !n' = n - 1 in Just n' else Nothing
151
92
152−members :: [Word8] -> Property
93+endOfInput s = maybeP P.endOfInput s == if L.null s
153−members s = property $ all (`S.memberWord8` set) s
94+ then Just ()
154− where set = S.fromList s
95+ else Nothing
155
96
156−nonmembers :: [Word8] -> [Word8] -> Property
97+scan s (Positive k) = maybeP p s == (Just $ toStrict $ L.take k s)
157−nonmembers s s' = property . not . any (`S.memberWord8` set) $ filter (not . (`elem` s)) s'
98+ where p = P.scan k $ \ n _ ->
158− where set = S.fromList s
99+ if n > 0 then let !n' = n - 1 in Just n' else Nothing
159
100
160−tests :: [TestTree]
161tests = [
101tests = [
162− testProperty "anyWord8" anyWord8
163− , testProperty "endOfInput" endOfInput
102+ testProperty "satisfy" satisfy,
164− , testProperty "endOfLine" endOfLine
103+ testProperty "word8" word8,
165− , testProperty "notWord8" notWord8
104+ testProperty "notWord8" notWord8,
166− , testProperty "peekWord8" peekWord8
105+ testProperty "anyWord8" anyWord8,
167− , testProperty "peekWord8'" peekWord8'
106+ testProperty "peekWord8" peekWord8,
168− , testProperty "satisfy" satisfy
169− , testProperty "satisfyWith" satisfyWith
107+ testProperty "string" string,
170− , testProperty "scan" scan
171− , testProperty "skip" skip
172− , testProperty "skipWhile" skipWhile
108+ testProperty "skipWhile" skipWhile,
173− , testProperty "string" string
174− , testProperty "stringCI" stringCI
175− , testProperty "strings" strings
176− , testProperty "take" take
177− , testProperty "takeByteString" takeByteString
178− , testProperty "takeCount" takeCount
109+ testProperty "takeCount" takeCount,
179− , testProperty "takeLazyByteString" takeLazyByteString
180− , testProperty "takeTill" takeTill
181− , testProperty "takeWhile" takeWhile
110+ testProperty "takeWhile" takeWhile,
182− , testProperty "takeWhile1" takeWhile1
111+ testProperty "takeWhile1" takeWhile1,
183− , testProperty "takeWhile1_empty" takeWhile1_empty
112+ testProperty "takeWhile1_empty" takeWhile1_empty,
184− , testProperty "word8" word8
113+ testProperty "takeTill" takeTill,
185− , testProperty "members" members
114+ testProperty "endOfInput" endOfInput,
186− , testProperty "nonmembers" nonmembers
115+ testProperty "scan" scan
187 ]
116 ]
tests/QC/Combinator.hsdeleted−52
@@ -1,52 +0,0 @@
1−{-# LANGUAGE CPP, OverloadedStrings #-}
2−
3−module QC.Combinator where
4−
5−#if !MIN_VERSION_base(4,8,0)
6−import Control.Applicative ((<*>), (<$>), (<*), (*>))
7−#endif
8−import Data.Maybe (fromJust, isJust)
9−import Data.Word (Word8)
10−import QC.Common (Repack, parseBS, repackBS, toLazyBS)
11−import Test.Tasty (TestTree)
12−import Test.Tasty.QuickCheck (testProperty)
13−import Test.QuickCheck
14−import qualified Data.Attoparsec.ByteString.Char8 as P
15−import qualified Data.Attoparsec.Combinator as C
16−import qualified Data.ByteString as B
17−import qualified Data.ByteString.Char8 as B8
18−
19−choice :: NonEmptyList (NonEmptyList Word8) -> Gen Property
20−choice (NonEmpty xs) = do
21− let ys = map (B.pack . getNonEmpty) xs
22− return . forAll (repackBS <$> arbitrary <*> elements ys) $
23− maybe False (`elem` ys) . parseBS (C.choice (map P.string ys))
24−
25−count :: Positive (Small Int) -> Repack -> B.ByteString -> Bool
26−count (Positive (Small n)) rs s =
27− (length <$> parseBS (C.count n (P.string s)) input) == Just n
28− where input = repackBS rs (B.concat (replicate (n+1) s))
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−
37−match :: Int -> NonNegative Int -> NonNegative Int -> Repack -> Bool
38−match n (NonNegative x) (NonNegative y) rs =
39− parseBS (P.match parser) (repackBS rs input) == Just (input, n)
40− where parser = P.skipWhile (=='x') *> P.signed P.decimal <*
41− P.skipWhile (=='y')
42− input = B.concat [
43− B8.replicate x 'x', B8.pack (show n), B8.replicate y 'y'
44− ]
45−
46−tests :: [TestTree]
47−tests = [
48− testProperty "choice" choice
49− , testProperty "count" count
50− , testProperty "lookAhead" lookAhead
51− , testProperty "match" match
52− ]
tests/QC/Common.hsdeleted−113
@@ -1,113 +0,0 @@
1−{-# LANGUAGE CPP, FlexibleInstances #-}
2−{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module QC.Common
4− (
5− ASCII(..)
6− , parseBS
7− , parseT
8− , toLazyBS
9− , toStrictBS
10− , Repack
11− , repackBS
12− , repackBS_
13− , repackT
14− , repackT_
15− , liftOp
16− ) where
17−
18−#if !MIN_VERSION_base(4,8,0)
19−import Control.Applicative ((<*>), (<$>))
20−#endif
21−import Data.Char (isAlpha)
22−import Test.QuickCheck
23−import Test.QuickCheck.Unicode (shrinkChar, string)
24−import qualified Data.ByteString as B
25−import qualified Data.ByteString.Lazy as BL
26−import qualified Data.Text as T
27−import qualified Data.Text.Lazy as TL
28−import qualified Data.Attoparsec.ByteString.Lazy as BL
29−import qualified Data.Attoparsec.Text.Lazy as TL
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−
41−parseBS :: BL.Parser r -> BL.ByteString -> Maybe r
42−parseBS p = BL.maybeResult . BL.parse p
43−
44−parseT :: TL.Parser r -> TL.Text -> Maybe r
45−parseT p = TL.maybeResult . TL.parse p
46−
47−toStrictBS :: BL.ByteString -> B.ByteString
48−toStrictBS = B.concat . BL.toChunks
49−
50−toLazyBS :: B.ByteString -> BL.ByteString
51−toLazyBS = BL.fromChunks . (:[])
52−
53−instance Arbitrary B.ByteString where
54− arbitrary = B.pack <$> arbitrary
55− shrink = map B.pack . shrink . B.unpack
56−
57−instance Arbitrary BL.ByteString where
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
71−
72−type Repack = NonEmptyList (Positive (Small Int))
73−
74−repackBS :: Repack -> B.ByteString -> BL.ByteString
75−repackBS (NonEmpty bs) =
76− BL.fromChunks . repackBS_ (map (getSmall . getPositive) bs)
77−
78−repackBS_ :: [Int] -> B.ByteString -> [B.ByteString]
79−repackBS_ = go . cycle
80− where go (b:bs) s
81− | B.null s = []
82− | otherwise = let (h,t) = B.splitAt b s
83− in h : go bs t
84− go _ _ = error "unpossible"
85−
86−instance Arbitrary T.Text where
87− arbitrary = T.pack <$> string
88− shrink = map T.pack . shrinkList shrinkChar . T.unpack
89−
90−instance Arbitrary TL.Text where
91− arbitrary = TL.pack <$> string
92− shrink = map TL.pack . shrinkList shrinkChar . TL.unpack
93−
94−repackT :: Repack -> T.Text -> TL.Text
95−repackT (NonEmpty bs) =
96− TL.fromChunks . repackT_ (map (getSmall . getPositive) bs)
97−
98−repackT_ :: [Int] -> T.Text -> [T.Text]
99−repackT_ = go . cycle
100− where go (b:bs) s
101− | T.null s = []
102− | otherwise = let (h,t) = T.splitAt b s
103− in h : go bs t
104− go _ _ = error "unpossible"
105−
106−liftOp :: (Show a, Testable prop) =>
107− String -> (a -> a -> prop) -> a -> a -> Property
108−liftOp name f x y = counterexample desc (f x y)
109− where op = case name of
110− (c:_) | isAlpha c -> " `" ++ name ++ "` "
111− | otherwise -> " " ++ name ++ " "
112− _ -> " ??? "
113− desc = "not (" ++ show x ++ op ++ show y ++ ")"
tests/QC/IPv6/Internal.hsdeleted−316
@@ -1,316 +0,0 @@
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.hsdeleted−29
@@ -1,29 +0,0 @@
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.hsdeleted−56
@@ -1,56 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−
3−module QC.Rechunked (
4− rechunkBS
5− , rechunkT
6− ) where
7−
8−import Control.Monad (forM, forM_)
9−import Control.Monad.ST (ST, runST)
10−import Data.List (unfoldr)
11−import Test.QuickCheck (Gen, choose)
12−import qualified Data.ByteString as B
13−import qualified Data.Text as T
14−import qualified Data.Vector as V
15−import qualified Data.Vector.Generic as G
16−import qualified Data.Vector.Generic.Mutable as M
17−
18−rechunkBS :: B.ByteString -> Gen [B.ByteString]
19−rechunkBS = fmap (map B.copy) . rechunk_ B.splitAt B.length
20−
21−rechunkT :: T.Text -> Gen [T.Text]
22−rechunkT = fmap (map T.copy) . rechunk_ T.splitAt T.length
23−
24−rechunk_ :: (Int -> a -> (a,a)) -> (a -> Int) -> a -> Gen [a]
25−rechunk_ split len xs = (unfoldr go . (,) xs) `fmap` rechunkSizes (len xs)
26− where go (b,r:rs) = Just (h, (t,rs))
27− where (h,t) = split r b
28− go (_,_) = Nothing
29−
30−rechunkSizes :: Int -> Gen [Int]
31−rechunkSizes n0 = shuffle =<< loop [] (0:repeat 1) n0
32− where loop _ [] _ = error "it's 2015, where's my Stream type?"
33− loop acc (lb:lbs) n
34− | n <= 0 = shuffle (reverse acc)
35− | otherwise = do
36− !i <- choose (lb,n)
37− loop (i:acc) lbs (n-i)
38−
39−shuffle :: [Int] -> Gen [Int]
40−shuffle (0:xs) = (0:) `fmap` shuffle xs
41−shuffle xs = fisherYates xs
42−
43−fisherYates :: [a] -> Gen [a]
44−fisherYates xs = (V.toList . V.backpermute v) `fmap` swapIndices (G.length v)
45− where
46− v = V.fromList xs
47− swapIndices n0 = do
48− swaps <- forM [0..n] $ \i -> ((,) i) `fmap` choose (i,n)
49− return (runST (swapAll swaps))
50− where
51− n = n0 - 1
52− swapAll :: [(Int,Int)] -> ST s (V.Vector Int)
53− swapAll ijs = do
54− mv <- G.unsafeThaw (G.enumFromTo 0 n :: V.Vector Int)
55− forM_ ijs $ uncurry (M.swap mv)
56− G.unsafeFreeze mv
tests/QC/Simple.hsdeleted−37
@@ -1,37 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
3−
4−module QC.Simple (
5− tests
6− ) where
7−
8−import Control.Applicative ((<|>))
9−import Data.ByteString (ByteString)
10−import Data.List (foldl')
11−import Data.Maybe (fromMaybe)
12−import QC.Rechunked (rechunkBS)
13−import Test.Tasty (TestTree)
14−import Test.Tasty.QuickCheck (testProperty)
15−import Test.QuickCheck (Property, counterexample, forAll)
16−import qualified Data.Attoparsec.ByteString.Char8 as A
17−
18−t_issue75 = expect issue75 "ab" (A.Done "" "b")
19−
20−issue75 :: A.Parser ByteString
21−issue75 = "a" >> ("b" <|> "")
22−
23−expect :: (Show r, Eq r) => A.Parser r -> ByteString -> A.Result r -> Property
24−expect p input wanted =
25− forAll (rechunkBS input) $ \in' ->
26− let result = parse p in'
27− in counterexample (show result ++ " /= " ++ show wanted) $
28− fromMaybe False (A.compareResults result wanted)
29−
30−parse :: A.Parser r -> [ByteString] -> A.Result r
31−parse p (x:xs) = foldl' A.feed (A.parse p x) xs
32−parse p [] = A.parse p ""
33−
34−tests :: [TestTree]
35−tests = [
36− testProperty "issue75" t_issue75
37− ]
tests/QC/Text.hs+74−151
@@ -1,86 +1,65 @@
1−{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}
1+{-# LANGUAGE OverloadedStrings #-}
2−{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
2+{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-}
3module QC.Text (tests) where
3module QC.Text (tests) where
4
4
5−#if !MIN_VERSION_base(4,8,0)
6−import Control.Applicative ((<*>), (<$>))
5+import Control.Applicative ((<$>), (<*>))
7−#endif
8−import Data.Int (Int64)
9−import Data.Word (Word8)
10−import Prelude hiding (take, takeWhile)
6+import Prelude hiding (takeWhile)
11−import QC.Common (liftOp, parseT)
7+import Test.Framework.Providers.QuickCheck2 (testProperty)
12−import qualified QC.Text.FastSet as FastSet
13−import qualified QC.Text.Regressions as Regressions
14−import Test.Tasty (TestTree, testGroup)
15−import Test.Tasty.QuickCheck (testProperty)
16import Test.QuickCheck
8import Test.QuickCheck
9+import qualified Data.Char as Char
17import qualified Data.Attoparsec.Text as P
10import qualified Data.Attoparsec.Text as P
18import qualified Data.Attoparsec.Text.Lazy as PL
11import qualified Data.Attoparsec.Text.Lazy as PL
19−import qualified Data.Attoparsec.Text.FastSet as S
20−import qualified Data.ByteString as BS
21−import qualified Data.Char as Char
22import qualified Data.Text as T
12import qualified Data.Text as T
23−import qualified Data.Text.Encoding as TE
24import qualified Data.Text.Lazy as L
13import qualified Data.Text.Lazy as L
25
14
15+instance Arbitrary T.Text where
16+ arbitrary = T.pack <$> arbitrary
17+
18+instance Arbitrary L.Text where
19+ arbitrary = sized $ \n -> resize (round (sqrt (toEnum n :: Double)))
20+ ((L.fromChunks . map (T.pack . nonEmpty)) <$> arbitrary)
21+ where nonEmpty (NonEmpty a) = a
22+
23+-- Naming.
24+
25+{-
26+label (NonEmpty s) = case parse (anyChar <?> s) T.empty of
27+ (_, Left err) -> s `isInfixOf` err
28+ _ -> False
29+-}
30+
26-- Basic byte-level combinators.
31-- Basic byte-level combinators.
27
32
28−satisfy :: Char -> L.Text -> Property
33+maybeP p = PL.maybeResult . PL.parse p
29−satisfy w s = parseT (P.satisfy (<=w)) (L.cons w s) === Just w
30
34
31−satisfyWith :: Char -> L.Text -> Property
35+defP p = PL.parse p
32−satisfyWith c s = parseT (P.satisfyWith id (<=c)) (L.cons c s) === Just c
33
36
34−char :: Char -> L.Text -> Property
35−char w s = parseT (P.char w) (L.cons w s) === Just w
37+satisfy w s = maybeP (P.satisfy (<=w)) (L.cons w s) == Just w
36
38
37−skip :: Char -> L.Text -> Property
39+char w s = maybeP (P.char w) (L.cons w s) == Just w
38−skip w s =
39− case (parseT (P.skip (<w)) s, L.uncons s) of
40− (Nothing, mcs) -> maybe (property True) (expectFailure . it) mcs
41− (Just _, mcs) -> maybe (property False) it mcs
42− where it cs = liftOp "<" (<) (fst cs) w
43
40
44−anyChar :: L.Text -> Property
45anyChar s
41anyChar s
46− | L.null s = p === Nothing
42+ | L.null s = p == Nothing
47− | otherwise = p === Just (L.head s)
43+ | otherwise = p == Just (L.head s)
48− where p = parseT P.anyChar s
44+ where p = maybeP P.anyChar s
49
45
50−notChar :: Char -> NonEmptyList Char -> Property
51−notChar w (NonEmpty s) = parseT (P.notChar w) bs === if v == w
46+notChar w (NonEmpty s) = maybeP (P.notChar w) bs == if v == w
52 then Nothing
47 then Nothing
53 else Just v
48 else Just v
54 where v = L.head bs
49 where v = L.head bs
55 bs = L.pack s
50 bs = L.pack s
56
51
57−peekChar :: L.Text -> Property
58peekChar s
52peekChar s
59− | L.null s = p === Just (Nothing, s)
53+ | L.null s = p == Just (Nothing, s)
60− | otherwise = p === Just (Just (L.head s), s)
54+ | otherwise = p == Just (Just (L.head s), s)
61− where p = parseT ((,) <$> P.peekChar <*> P.takeLazyText) s
55+ where p = maybeP ((,) <$> P.peekChar <*> P.takeLazyText) s
62−
63−peekChar' :: L.Text -> Property
64−peekChar' s = parseT P.peekChar' s === (fst <$> L.uncons s)
65
56
66−string :: L.Text -> L.Text -> Property
67−string s t = parseT (P.string s') (s `L.append` t) === Just s'
57+string s t = maybeP (P.string s') (s `L.append` t) == Just s'
68 where s' = toStrict s
58 where s' = toStrict s
69
59
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
79−stringCI ws = P.parseOnly (P.stringCI fs) s === Right s
60+stringCI s = P.parseOnly (P.stringCI fs) s == Right s
80 where fs = T.toCaseFold s
61 where fs = T.toCaseFold s
81− s = TE.decodeLatin1 (BS.pack ws)
82
62
83−asciiCI :: T.Text -> Gen Bool
84asciiCI x =
63asciiCI x =
85 (\s i -> P.parseOnly (P.asciiCI s) i == Right i)
64 (\s i -> P.parseOnly (P.asciiCI s) i == Right i)
86 <$> maybeModifyCase x
65 <$> maybeModifyCase x
@@ -90,114 +69,58 @@ asciiCI x =
90 toLower = T.map (\c -> if c < Char.chr 127 then Char.toLower c else c)
69 toLower = T.map (\c -> if c < Char.chr 127 then Char.toLower c else c)
91 toUpper = T.map (\c -> if c < Char.chr 127 then Char.toUpper c else c)
70 toUpper = T.map (\c -> if c < Char.chr 127 then Char.toUpper c else c)
92
71
93−toStrict :: L.Text -> T.Text
94toStrict = T.concat . L.toChunks
72toStrict = T.concat . L.toChunks
95
73
96−skipWhile :: Char -> L.Text -> Property
97skipWhile w s =
74skipWhile w s =
98 let t = L.dropWhile (<=w) s
75 let t = L.dropWhile (<=w) s
99− in case PL.parse (P.skipWhile (<=w)) s of
76+ in case defP (P.skipWhile (<=w)) s of
100− PL.Done t' () -> t === t'
77+ PL.Done t' () -> t == t'
101− _ -> property False
78+ _ -> False
102−
103−take :: Int -> L.Text -> Property
104−take n s = maybe (liftOp "<" (<) (L.length s) (fromIntegral n))
105− (=== T.take n (toStrict s)) $
106− parseT (P.take n) s
107−
108−takeText :: L.Text -> Property
109−takeText s = maybe (property False) (=== toStrict s) . parseT P.takeText $ s
110−
111−takeLazyText :: L.Text -> Property
112−takeLazyText s = maybe (property False) (=== s) . parseT P.takeLazyText $ s
113
79
114−takeCount :: Positive Int -> L.Text -> Property
115takeCount (Positive k) s =
80takeCount (Positive k) s =
116− case parseT (P.take k) s of
81+ case maybeP (P.take k) s of
117− Nothing -> liftOp ">" (>) (fromIntegral k) (L.length s)
82+ Nothing -> fromIntegral k > L.length s
118− Just _s -> liftOp "<=" (<=) (fromIntegral k) (L.length s)
83+ Just s' -> fromIntegral k <= L.length s
119
84
120−takeWhile :: Char -> L.Text -> Property
121takeWhile w s =
85takeWhile w s =
122 let (h,t) = L.span (==w) s
86 let (h,t) = L.span (==w) s
123− in case PL.parse (P.takeWhile (==w)) s of
87+ in case defP (P.takeWhile (==w)) s of
124− PL.Done t' h' -> t === t' .&&. toStrict h === h'
88+ PL.Done t' h' -> t == t' && toStrict h == h'
125− _ -> property False
89+ _ -> False
126
90
127−takeWhile1 :: Char -> L.Text -> Property
128takeWhile1 w s =
91takeWhile1 w s =
129− let s' = L.cons w s
92+ let s' = L.cons w s
130− (h,t) = L.span (<=w) s'
93+ (h,t) = L.span (<=w) s'
131− in case PL.parse (P.takeWhile1 (<=w)) s' of
94+ in case defP (P.takeWhile1 (<=w)) s' of
132− PL.Done t' h' -> t === t' .&&. toStrict h === h'
95+ PL.Done t' h' -> t == t' && toStrict h == h'
133− _ -> property False
96+ _ -> False
134
97
135−takeTill :: Char -> L.Text -> Property
136takeTill w s =
98takeTill w s =
137 let (h,t) = L.break (==w) s
99 let (h,t) = L.break (==w) s
138− in case PL.parse (P.takeTill (==w)) s of
100+ in case defP (P.takeTill (==w)) s of
139− PL.Done t' h' -> t === t' .&&. toStrict h === h'
101+ PL.Done t' h' -> t == t' && toStrict h == h'
140− _ -> property False
102+ _ -> False
141−
103+
142−takeWhile1_empty :: Property
143−takeWhile1_empty = parseT (P.takeWhile1 undefined) L.empty === Nothing
104+takeWhile1_empty = maybeP (P.takeWhile1 undefined) L.empty == Nothing
144−
105+
145−endOfInput :: L.Text -> Property
146−endOfInput s = parseT P.endOfInput s === if L.null s
106+endOfInput s = maybeP P.endOfInput s == if L.null s
147− then Just ()
107+ then Just ()
148− else Nothing
149−
150−endOfLine :: L.Text -> Property
151−endOfLine s =
152− case (parseT P.endOfLine s, L.uncons s) of
153− (Nothing, mcs) -> maybe (property True) (expectFailure . eol) mcs
154− (Just _, mcs) -> maybe (property False) eol mcs
155− where eol (c,s') = c === '\n' .||.
156− (c, fst <$> L.uncons s') === ('\r', Just '\n')
157−
158−scan :: L.Text -> Positive Int64 -> Property
159−-- for some reason, if counterexample is removed, this test fails?
160−scan s (Positive k) = counterexample (show s)
161− $ parseT p s === Just (toStrict $ L.take k s)
162− where p = P.scan k $ \ n _ ->
163− if n > 0 then let !n' = n - 1 in Just n' else Nothing
108+ else Nothing
164−
109+
165−members :: String -> Property
166−members s = property $ all (`S.member` set) s
167− where set = S.fromList s
168−
169−nonmembers :: String -> String -> Property
170−nonmembers s s' = property . not . any (`S.member` set) $ filter (not . (`elem` s)) s'
171− where set = S.fromList s
172−
173−tests :: [TestTree]
174tests = [
110tests = [
175− testProperty "anyChar" anyChar
111+ testProperty "satisfy" satisfy,
176− , testProperty "asciiCI" asciiCI
177− , testProperty "char" char
112+ testProperty "char" char,
178− , testProperty "endOfInput" endOfInput
179− , testProperty "endOfLine" endOfLine
180− , testProperty "notChar" notChar
113+ testProperty "notChar" notChar,
181− , testProperty "peekChar" peekChar
182− , testProperty "peekChar'" peekChar'
114+ testProperty "anyChar" anyChar,
183− , testProperty "satisfy" satisfy
115+ testProperty "peekChar" peekChar,
184− , testProperty "satisfyWith" satisfyWith
185− , testProperty "scan" scan
186− , testProperty "skip" skip
187− , testProperty "skipWhile" skipWhile
188− , testProperty "string" string
116+ testProperty "string" string,
189− , testProperty "strings" strings
190− , testProperty "stringCI" stringCI
117+ testProperty "stringCI" stringCI,
191− , testProperty "take" take
118+ testProperty "asciiCI" asciiCI,
192− , testProperty "takeText" takeText
119+ testProperty "skipWhile" skipWhile,
193− , testProperty "takeCount" takeCount
120+ testProperty "takeCount" takeCount,
194− , testProperty "takeLazyText" takeLazyText
195− , testProperty "takeTill" takeTill
196− , testProperty "takeWhile" takeWhile
121+ testProperty "takeWhile" takeWhile,
197− , testProperty "takeWhile1" takeWhile1
122+ testProperty "takeWhile1" takeWhile1,
198− , testProperty "takeWhile1_empty" takeWhile1_empty
123+ testProperty "takeWhile1_empty" takeWhile1_empty,
199− , testProperty "members" members
200− , testProperty "nonmembers" nonmembers
124+ testProperty "takeTill" takeTill,
201− , testGroup "FastSet" FastSet.tests
125+ testProperty "endOfInput" endOfInput
202− , testGroup "Regressions" Regressions.tests
203 ]
126 ]
tests/QC/Text/FastSet.hsdeleted−15
@@ -1,15 +0,0 @@
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.hsdeleted−54
@@ -1,54 +0,0 @@
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− ]
tests/TestFastSet.hsnew file+25
@@ -0,0 +1,25 @@
1+module Main (main) where
2+
3+import Data.Word (Word8)
4+import qualified Data.Attoparsec.FastSet as F
5+import System.Random (Random(..), RandomGen)
6+import Test.QuickCheck
7+
8+integralRandomR :: (Integral a, RandomGen g) => (a, a) -> g -> (a, g)
9+integralRandomR (a,b) g = case randomR (fromIntegral a :: Int,
10+ fromIntegral b :: Int) g
11+ of (x,g') -> (fromIntegral x, g')
12+
13+instance Random Word8 where
14+ randomR = integralRandomR
15+ random = randomR (minBound,maxBound)
16+
17+instance Arbitrary Word8 where
18+ arbitrary = choose (minBound, maxBound)
19+
20+prop_AllMembers s =
21+ let set = F.fromList s
22+ in all (`F.memberWord8` set) s
23+
24+main = do
25+ quickCheck prop_AllMembers