fastparser 0.3.0 → 0.3.0.1
raw patch · 5 files changed
+107/−36 lines, 5 filesdep +criteriondep +fastparserdep +kan-extensionsdep ~basedep ~bytestringdep ~vector-spacePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: criterion, fastparser, kan-extensions, transformers
Dependency ranges changed: base, bytestring, vector-space
API changes (from Hackage documentation)
- ByteString.Parser.Fast: instance GHC.Base.Alternative ByteString.Parser.Fast.Parser
- ByteString.Parser.Fast: instance GHC.Base.Applicative ByteString.Parser.Fast.Parser
- ByteString.Parser.Fast: instance GHC.Base.Functor ByteString.Parser.Fast.Parser
- ByteString.Parser.Fast: instance GHC.Base.Monad ByteString.Parser.Fast.Parser
- ByteString.Parser.Fast: instance GHC.Base.MonadPlus ByteString.Parser.Fast.Parser
- ByteString.Parser.Fast: newtype Parser a
+ ByteString.Parser.Fast: instance GHC.Base.Alternative ByteString.Parser.Fast.ParserM
+ ByteString.Parser.Fast: instance GHC.Base.Applicative ByteString.Parser.Fast.ParserM
+ ByteString.Parser.Fast: instance GHC.Base.Functor ByteString.Parser.Fast.ParserM
+ ByteString.Parser.Fast: instance GHC.Base.Monad ByteString.Parser.Fast.ParserM
+ ByteString.Parser.Fast: instance GHC.Base.MonadPlus ByteString.Parser.Fast.ParserM
+ ByteString.Parser.Fast: newtype ParserM a
+ ByteString.Parser.Fast: type Parser = Codensity ParserM
- ByteString.Parser.Fast: Parser :: (forall r. ByteString -> (ParseError -> r) -> (ByteString -> a -> r) -> r) -> Parser a
+ ByteString.Parser.Fast: Parser :: (forall r. ByteString -> (ParseError -> r) -> (ByteString -> a -> r) -> r) -> ParserM a
- ByteString.Parser.Fast: [runParser] :: Parser a -> forall r. ByteString -> (ParseError -> r) -> (ByteString -> a -> r) -> r
+ ByteString.Parser.Fast: [runParser] :: ParserM a -> forall r. ByteString -> (ParseError -> r) -> (ByteString -> a -> r) -> r
Files
- CHANGELOG.md +8/−0
- README.md +24/−0
- bench/timestamps.hs +13/−0
- fastparser.cabal +22/−8
- src/ByteString/Parser/Fast.hs +40/−28
+ CHANGELOG.md view
@@ -0,0 +1,8 @@+v0.3.0.1++ * Parser is now wrapped in Codensity, as the performance impact is minimal+ * Prototype benchmark suite++v0.2.0++ * Dropped the lens dependency, for microlens
+ README.md view
@@ -0,0 +1,24 @@+[](https://travis-ci.org/bartavelle/fastparser)+++A very simple, backtracking, fast parser combinator library.++It is measurably faster than [attoparsec](https://hackage.haskell.org/package/attoparsec) (36% in [this use case](https://hbtvl.banquise.net/posts/2015-12-14-fastParsing03.html)), but only works on strict `ByteString`, lacks many helper functions, and is not resumable.+It also should consume a tiny bit less memory for equivalent operations.++# When NOT to use fastparser++ * When performance is not the **most** pressing concern.+ * When you need to parse anything else but strict `ByteString`.+ * When you need to use a battle-tested library. While very simple, and in constant use by me, this package is still quite experimental.+ * When you need to parse large inputs that are not easily cut into many smaller pieces that can be parsed independently.++# How to use fastparser++`fastparser` works well with small pieces, such as individual log file lines. It is recommended to use it with a coroutine library (such as [conduit](http://hackage.haskell.org/package/conduit) or [pipe](http://hackage.haskell.org/package/pipes)), so that the input could be incrementaly consumed, cut into individual records, all of which would end up parsed independently.++One such setup, with the `conduit` ecosystem, would look like:++ sourceFile "/tmp/foo" .| Data.Conduit.Binary.lines .| CL.map (parseOnly parser) .| ...++Other than that, `fastparser` is fairly close to any other parser combinators library.
+ bench/timestamps.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import ByteString.Parser.Fast+import Criterion+import Criterion.Main+import qualified Data.ByteString as BS++timestampBench :: BS.ByteString -> Benchmark+timestampBench b = bench (show b) $ nf ((\(Right a) -> a) . parseOnly timestamp) b++main :: IO ()+main = defaultMain [timestampBench "2016-12-27+09:58:09.9634133070+CET", timestampBench "2014-07-23+00:16:31.0000000000+CEST"]
fastparser.cabal view
@@ -1,5 +1,5 @@ name: fastparser-version: 0.3.0+version: 0.3.0.1 synopsis: A fast, but bare bones, bytestring parser combinators library. description: Please see README.md homepage: https://github.com/bartavelle/fastparser#readme@@ -10,22 +10,36 @@ copyright: Simon Marechal category: Parsing build-type: Simple--- extra-source-files:+extra-source-files: CHANGELOG.md, README.md cabal-version: >=1.10+Tested-With: GHC == 7.10.2, GHC == 8.0.2, GHC == 8.2.1 library hs-source-dirs: src exposed-modules: ByteString.Parser.Fast- build-depends: base >= 4.7 && < 5- , bytestring == 0.10.*- , thyme == 0.3.*- , vector-space == 0.10.*- , microlens == 0.4.*+ build-depends: base >= 4.7 && < 5+ , bytestring+ , thyme == 0.3.*+ , vector-space >= 0.10 && < 1+ , microlens == 0.4.* , bytestring-lexing == 0.5.*- , containers == 0.5.*+ , containers == 0.5.*+ , kan-extensions == 5.*+ , transformers >= 0.4 ghc-options: -O2 -Wall default-language: Haskell2010 source-repository head type: git location: https://github.com/bartavelle/fastparser++benchmark timestamps+ type: exitcode-stdio-1.0+ main-is: timestamps.hs+ hs-source-dirs: bench+ build-depends: base >= 4.7 && < 5+ , criterion+ , fastparser+ , bytestring+ ghc-options: -O2 -Wall+ default-language: Haskell2010
src/ByteString/Parser/Fast.hs view
@@ -48,7 +48,7 @@ {-# LANGUAGE OverloadedStrings #-} module ByteString.Parser.Fast (- Parser(..), parseOnly,+ Parser, ParserM(..), parseOnly, -- * Error handling ParseError(..), ErrorItem(..), ueof, ufail, parseError, -- * Parsing numerical values@@ -76,8 +76,11 @@ import Data.Thyme import Data.Thyme.Time.Core import Lens.Micro-+import Data.Monoid import qualified Data.ByteString.Lex.Fractional as L+import Control.Monad.Codensity (Codensity, lowerCodensity)+import Control.Monad.Trans.Class (lift)+import Prelude -- | A parser, church encoded. The arguments to the wrapped function are: --@@ -85,7 +88,7 @@ -- * A function that handles parse errors. -- * A function that handles success, taking as argument the remaining -- input and the parser result.-newtype Parser a+newtype ParserM a = Parser { runParser :: forall r. BS.ByteString -> (ParseError -> r)@@ -93,7 +96,9 @@ -> r } deriving Functor -instance Applicative Parser where+type Parser = Codensity ParserM++instance Applicative ParserM where pure a = Parser $ \b _ s -> s b a {-# INLINE pure #-} Parser pf <*> Parser px = Parser $ \input failure success ->@@ -101,20 +106,20 @@ in pf input failure succ' {-# INLINE (<*>) #-} -instance Alternative Parser where+instance Alternative ParserM where empty = Parser (\_ failure _ -> failure mempty) {-# INLINE empty #-} Parser a <|> Parser b = Parser $ \input failure success -> a input (\rr -> b input (failure . mappend rr) success) success {-# INLINE (<|>) #-} -instance Monad Parser where+instance Monad ParserM where fail s = Parser $ \_ failure _ -> failure (ufail s) m >>= k = Parser $ \input failure success -> let succ' input' a = runParser (k a) input' failure success in runParser m input failure succ' {-# INLINE (>>=) #-} -instance MonadPlus Parser+instance MonadPlus ParserM data ErrorItem = Tokens BS.ByteString@@ -154,7 +159,7 @@ -- It works well with the -- [bytestring-lexing](https://hackage.haskell.org/package/bytestring-lexing) library. wlex :: (BS.ByteString -> Maybe (a, BS.ByteString)) -> Parser a-wlex p = Parser $ \i failure success -> case p i of+wlex p = lift $ Parser $ \i failure success -> case p i of Nothing -> failure mempty Just (a, i') -> success i' a {-# INLINABLE wlex #-}@@ -223,25 +228,27 @@ -- | Consumes n bytes of input takeN :: Int -> Parser BS.ByteString-takeN n = Parser $ \input failure success -> if BS.length input < n- then failure ueof- else let (a,rest) = BS.splitAt n input- in success rest a+takeN n = lift $ Parser $ \input failure success+ -> if BS.length input < n+ then failure ueof+ else let (a,rest) = BS.splitAt n input+ in success rest a -- | Parses any character. anyChar :: Parser Char-anyChar = Parser $ \input failure success -> if BS.null input then failure ueof else success (BS8.tail input) (BS8.head input)+anyChar = lift $ Parser $ \input failure success -> if BS.null input then failure ueof else success (BS8.tail input) (BS8.head input) -- | Parses a specific character. char :: Char -> Parser ()-char c = Parser $ \input failure success -> if BS.null input then failure ueof else if BS8.head input == c then success (BS.tail input) () else failure (parseError (BS8.take 1 input) (BS8.singleton c))+char c = lift $ Parser $ \input failure success -> if BS.null input then failure ueof else if BS8.head input == c then success (BS.tail input) () else failure (parseError (BS8.take 1 input) (BS8.singleton c)) {-# INLINE char #-} -- | Parses the supplied string. string :: BS.ByteString -> Parser ()-string s = Parser $ \input failure success -> if s `BS.isPrefixOf` input- then success (BS.drop (BS.length s) input) ()- else failure (parseError (BS.take (BS.length s) input) s)+string s = lift $ Parser $ \input failure success+ -> if s `BS.isPrefixOf` input+ then success (BS.drop (BS.length s) input) ()+ else failure (parseError (BS.take (BS.length s) input) s) -- | Parses strings between double quotes. This functions handles the -- following escape sequences: \\r, \\n, \\t, \\a, \\b, \\", \\\\.@@ -277,42 +284,47 @@ {-# INLINE scientific #-} charTakeWhile1 :: (Char -> Bool) -> Parser BS.ByteString-charTakeWhile1 prd = Parser $ \s failure success -> case BS8.span prd s of- (a,b) -> if BS.null a then failure (ParseError (S.singleton (Tokens (BS.take 1 s))) mempty) else success b a+charTakeWhile1 prd = lift $ Parser $ \s failure success ->+ case BS8.span prd s of+ (a,b) -> if BS.null a then failure (ParseError (S.singleton (Tokens (BS.take 1 s))) mempty) else success b a {-# INLINE charTakeWhile1 #-} takeWhile1 :: (Word8 -> Bool) -> Parser BS.ByteString-takeWhile1 prd = Parser $ \s failure success -> case BS.span prd s of- (a,b) -> if BS.null a then failure (ParseError (S.singleton (Tokens (BS.take 1 s))) mempty) else success b a+takeWhile1 prd = lift $ Parser $ \s failure success ->+ case BS.span prd s of+ (a,b) -> if BS.null a then failure (ParseError (S.singleton (Tokens (BS.take 1 s))) mempty) else success b a {-# INLINE takeWhile1 #-} -- | Consumes the input as long as the predicate remains true. charTakeWhile :: (Char -> Bool) -> Parser BS.ByteString-charTakeWhile prd = Parser $ \s _ success -> case BS8.span prd s of- (a,b) -> success b a+charTakeWhile prd = lift $ Parser $ \s _ success ->+ case BS8.span prd s of+ (a,b) -> success b a {-# INLINE charTakeWhile #-} -- | Consumes the input as long as the predicate remains true. takeWhile :: (Word8 -> Bool) -> Parser BS.ByteString-takeWhile prd = Parser $ \s _ success -> case BS.span prd s of- (a,b) -> success b a+takeWhile prd = lift $ Parser $ \s _ success ->+ case BS.span prd s of+ (a,b) -> success b a {-# INLINE takeWhile #-} -- | Discards the input as long as the predicate remains true. skipWhile :: (Word8 -> Bool) -> Parser ()-skipWhile prd = Parser $ \s _ success -> success (BS.dropWhile prd s) ()+skipWhile prd = lift $ Parser $ \s _ success -> success (BS.dropWhile prd s) () {-# INLINE skipWhile #-} -- | Runs the parser. Will return a parse error if the parser fails -- or if the input is not completely consumed. parseOnly :: Parser a -> BS.ByteString -> Either ParseError a-parseOnly (Parser p) s = p s Left $ \b a -> if BS.null b+parseOnly prs s = case lowerCodensity prs of+ Parser p -> p s Left $ \b a -> if BS.null b then Right a else Left (ParseError (S.singleton (Tokens (BS.take 1 b))) mempty) -- | Parses the remaining input. remaining :: Parser BS.ByteString-remaining = Parser $ \input _ success -> success BS.empty input+remaining = lift $ Parser $ \input _ success -> success BS.empty input -- | Parses days, with format YYYY-MM-DD parseYMD :: Parser Day