packages feed

flatparse 0.1.0.2 → 0.1.1.1

raw patch · 8 files changed

+140/−37 lines, 8 filesdep +bytesmithdep +primitivePVP ok

version bump matches the API change (PVP)

Dependencies added: bytesmith, primitive

API changes (from Hackage documentation)

+ FlatParse.Basic: optional_ :: Parser r e a -> Parser r e ()
+ FlatParse.Examples.BasicLambda.Parser: expectAtom :: [Expected]
+ FlatParse.Examples.BasicLambda.Parser: p1 :: String
+ FlatParse.Stateful: optional_ :: Parser r e a -> Parser r e ()

Files

README.md view
@@ -9,7 +9,7 @@ 
 ## Features and non-features
 
-* __Excellent performance__. On microbenchmarks, `flatparse` is around 10 times faster than `attoparsec` or `megaparsec`. On larger examples with heavier use of source positions and spans and/or indentation parsing, the performance difference grows to 20-30 times. Compile times and exectuable sizes are also significantly better with `flatparse` than with `megaparsec` or `attoparsec`. `flatparse` interals make liberal use of unboxed tuples and GHC primops. As a result, pure validators (parsers returning `()`) in `flatparse` are not difficult to implement with zero heap allocation.
+* __Excellent performance__. On microbenchmarks, `flatparse` is around 10 times faster than `attoparsec` or `megaparsec`. On larger examples with heavier use of source positions and spans and/or indentation parsing, the performance difference grows to 20-30 times. Compile times and exectuable sizes are also significantly better with `flatparse` than with `megaparsec` or `attoparsec`. `flatparse` internals make liberal use of unboxed tuples and GHC primops. As a result, pure validators (parsers returning `()`) in `flatparse` are not difficult to implement with zero heap allocation.
 * __No incremental parsing__, and __only strict `ByteString`__ is supported as input. However, it can be still useful to convert from `Text`, `String` or other types to `ByteString`, and then use `flatparse` for parsing, since `flatparse` performance usually more than makes up for the conversion costs.
 * __Only little-endian 64 bit systems are currently supported__. This may change in the future. Getting good performance requires architecture-specific optimizations; I've only considered the most common setting at this point.
 * __Support for fast source location handling, indentation parsing and informative error messages__. `flatparse` provides a low-level interface to these. Batteries are _not included_, but it should be possible for users to build custom solutions, which are more sophisticated, but still as fast as possible. In my experience, the included batteries in other libraries often come with major unavoidable overheads, and often we still have to extend existing machinery in order to scale to production features.
@@ -26,27 +26,34 @@ 
 Informative tutorials are work in progress. See [`src/FlatParse/Examples`](src/FlatParse/Examples) for a lexer/parser example with acceptably good error messages.
 
-### Some benchmarks
+## Contribution
 
+Pull requests are welcome. I'm fairly quick to add PR authors as collaborators.
+
+## Some benchmarks
+
 Execution times below. See source code in [bench](bench). Compiled with GHC 8.8.4 `-O2 -fllvm`.
 
 |      benchmark              |  runtime   |
 |-----------------------------|-------------
-| s-exp/fpbasic               |  3.365 ms  |
-| s-exp/fpstateful            |  3.421 ms  |
-| s-exp/attoparsec            |  42.84 ms  |
-| s-exp/megaparsec            |  57.54 ms  |
-| s-exp/parsec                |  179.7 ms  |
-| long keyword/fpbasic        |  216.4 μs  |
-| long keyword/fpstateful     |  299.0 μs  |
-| long keyword/attoparsec     |  5.297 ms  |
-| long keyword/megaparsec     |  3.646 ms  |
-| long keyword/parsec         |  49.18 ms  |
-| numeral csv/fpbasic         |  743.5 μs  |
-| numeral csv/fpstateful      |  848.5 μs  |
-| numeral csv/attoparsec      |  20.64 ms  |
-| numeral csv/megaparsec      |  10.12 ms  |
-| numeral csv/parsec          |  78.52 ms  |
+| sexp/fpbasic                | 3.345 ms   |
+| sexp/fpstateful             | 3.441 ms   |
+| sexp/bytesmith              | 5.646 ms   |
+| sexp/attoparsec             | 43.58 ms   |
+| sexp/megaparsec             | 57.76 ms   |
+| sexp/parsec                 | 182.4 ms   |
+| long keyword/fpbasic        | 306.1 μs   |
+| long keyword/fpstateful     | 220.3 μs   |
+| long keyword/bytesmith      | 1.707 ms   |
+| long keyword/attoparsec     | 5.420 ms   |
+| long keyword/megaparsec     | 3.605 ms   |
+| long keyword/parsec         | 50.10 ms   |
+| numeral csv/fpbasic         | 898.4 μs   |
+| numeral csv/fpstateful      | 868.3 μs   |
+| numeral csv/bytesmith       | 2.412 ms   |
+| numeral csv/attoparsec      | 21.30 ms   |
+| numeral csv/megaparsec      | 10.37 ms   |
+| numeral csv/parsec          | 78.16 ms   |
 
 Object file sizes for each module containing the `s-exp`, `long keyword` and `numeral csv` benchmarks.
 
@@ -54,6 +61,7 @@ | -------    | ------------------------ |
 | fpbasic    |  26456                   |
 | fpstateful |  30008                   |
+| bytesmith  |  39240                   |
 | attoparsec |  83288                   |
 | megaparsec |  188696                  |
 | parsec     |  75880                   |
bench/Bench.hs view
@@ -1,6 +1,7 @@  module Main where +import Data.Primitive.ByteArray import Gauge import qualified Data.ByteString.Char8 as B @@ -9,6 +10,7 @@ import qualified Parsec import qualified FPStateful import qualified FPBasic+import qualified Bytesmith  sexpInp :: B.ByteString sexpInp =@@ -20,11 +22,21 @@ numcsvInp :: B.ByteString numcsvInp = B.concat ("0" : [B.pack (",  " ++ show n) | n <- [1..100000::Int]]) +sexpInp' :: ByteArray+sexpInp' = Bytesmith.strToByteArray $ B.unpack sexpInp++longwsInp' :: ByteArray+longwsInp' = Bytesmith.strToByteArray $ B.unpack longwsInp++numcsvInp' :: ByteArray+numcsvInp' = Bytesmith.strToByteArray $ B.unpack numcsvInp+ main :: IO () main = defaultMain [   bgroup "sexp" [     bench "fpbasic"     $ whnf FPBasic.runSexp    sexpInp,     bench "fpstateful"  $ whnf FPStateful.runSexp sexpInp,+    bench "bytesmith"   $ whnf Bytesmith.runSexp  sexpInp',     bench "attoparsec"  $ whnf Attoparsec.runSexp sexpInp,     bench "megaparsec"  $ whnf Megaparsec.runSexp sexpInp,     bench "parsec"      $ whnf Parsec.runSexp     sexpInp@@ -33,6 +45,7 @@   bgroup "long keyword" [     bench "fpbasic"    $ whnf FPBasic.runLongws    longwsInp,     bench "fpstateful" $ whnf FPStateful.runLongws longwsInp,+    bench "bytesmith"  $ whnf Bytesmith.runLongws  longwsInp',     bench "attoparsec" $ whnf Attoparsec.runLongws longwsInp,     bench "megaparsec" $ whnf Megaparsec.runLongws longwsInp,     bench "parsec"     $ whnf Parsec.runLongws     longwsInp@@ -41,6 +54,7 @@   bgroup "numeral csv" [     bench "fpbasic"    $ whnf FPBasic.runNumcsv    numcsvInp,     bench "fpstateful" $ whnf FPStateful.runNumcsv numcsvInp,+    bench "bytesmith"  $ whnf Bytesmith.runNumcsv  numcsvInp',     bench "attoparsec" $ whnf Attoparsec.runNumcsv numcsvInp,     bench "megaparsec" $ whnf Megaparsec.runNumcsv numcsvInp,     bench "parsec"     $ whnf Parsec.runNumcsv     numcsvInp
+ bench/Bytesmith.hs view
@@ -0,0 +1,63 @@+{-# language RankNTypes #-}++module Bytesmith (runSexp, runLongws, runNumcsv, strToByteArray) where++import Control.Applicative++import GHC.Exts++import qualified Data.Bytes.Parser       as P+import qualified Data.Bytes.Parser.Ascii as A++import Data.Primitive.ByteArray+import Data.Char++strToByteArray :: String -> ByteArray+strToByteArray = fromList . map (fromIntegral . ord)++byteArrayToStr :: ByteArray -> String+byteArrayToStr = map (chr . fromIntegral) . toList++parseByteArray :: (forall s. Parser s) -> ByteArray -> Bool+parseByteArray p b = case P.parseByteArray p b of+  P.Failure{} -> False+  _           -> True+{-# inline parseByteArray #-}++data U = U+instance Semigroup U where (<>) _ _ = U+instance Monoid U    where mempty   = U++type Parser s = P.Parser U s ()++many_ :: Parser s -> Parser s+many_ p = go where+  go = (p >> go) <|> pure ()+{-# inline many_ #-}++some_ :: Parser s -> Parser s+some_ p = p >> many_ p+{-# inline some_ #-}++ws :: Parser s+ws = many_ do+  P.any U >>= \case+    32 -> pure ()      -- ' '+    10 -> pure ()      -- '\n'+    _  -> P.fail U++open    = A.char U '(' >> ws+close   = A.char U ')' >> ws+ident   = A.skipAlpha1 U >> ws+sexp    = (open >> some_ sexp >> close) <|> ident+src     = sexp >> P.endOfInput U+runSexp = parseByteArray src++longw     = P.cstring U (Ptr "thisisalongkeyword"#)+longws    = some_ (longw >> ws) >> P.endOfInput U+runLongws = parseByteArray longws++numeral   = A.skipDigits1 U >> ws+comma     = A.char U ',' >> ws+numcsv    = numeral >> many_ (comma >> numeral) >> P.endOfInput U+runNumcsv = parseByteArray numcsv
flatparse.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: df83ba622236fe722641b9c67f9617e027392e36c25387858a3beffc734f05be+-- hash: aa216bc46905b15bd49381505a4e05c27e49635a2d8d9bfbe8ef1f5db26a0d08  name:           flatparse-version:        0.1.0.2+version:        0.1.1.1 synopsis:       High-performance parsing from strict bytestrings description:    @Flatparse@ is a high-performance parsing library, focusing on programming languages and                 human-readable data formats. See the README for more information:@@ -53,6 +53,7 @@   main-is: Bench.hs   other-modules:       Attoparsec+      Bytesmith       FPBasic       FPStateful       Megaparsec@@ -65,9 +66,11 @@   build-depends:       attoparsec     , base >=4.7 && <5+    , bytesmith     , bytestring     , flatparse     , gauge     , megaparsec     , parsec+    , primitive   default-language: Haskell2010
src/FlatParse/Basic.hs view
@@ -28,6 +28,7 @@   , fails   , try   , optional+  , optional_   , optioned   , cut   , cutting@@ -224,7 +225,7 @@         pure (OK a (B.drop (I# offset) b))       Fail# ->         pure Fail-{-# noinline runParser #-}+{-# inlinable runParser #-}  -- | Run a parser on a `String` input. Reminder: @OverloadedStrings@ for `B.ByteString` does not --   yield a valid UTF-8 encoding! For non-ASCII `B.ByteString` literal input, use `runParserS` or@@ -288,6 +289,11 @@ optional p = (Just <$> p) <|> pure Nothing {-# inline optional #-} +-- | Convert a parsing failure to a `()`.+optional_ :: Parser r e a -> Parser r e ()+optional_ p = (() <$ p) <|> pure ()+{-# inline optional_ #-}+ -- | CPS'd version of `optional`. This is usually more efficient, since it gets rid of the --   extra `Maybe` allocation. optioned :: Parser r e a -> (a -> Parser r e b) -> Parser r e b -> Parser r e b@@ -782,8 +788,8 @@         if pos == p then           ((i, (line, col)):) <$> go line col poss         else do-          c <- anyWord8-          if ord '\n' == fromIntegral c then+          c <- anyChar+          if '\n' == c then             go (line + 1) 0 ((i, pos):poss)           else             go line (col + 1) ((i, pos):poss)
src/FlatParse/Examples/BasicLambda/Lexer.hs view
@@ -4,9 +4,11 @@ demonstrates a simple but decently informative implementation of error message propagation. -} +{-# language StrictData #-}+ module FlatParse.Examples.BasicLambda.Lexer where -import FlatParse.Basic hiding (Parser, runParser, string, char, cut, err)+import FlatParse.Basic hiding (Parser, runParser, string, char, cut)  import qualified FlatParse.Basic as FP import qualified Data.ByteString as B
src/FlatParse/Examples/BasicLambda/Parser.hs view
@@ -11,7 +11,7 @@ import Data.Char (ord) import qualified Data.ByteString as B -import FlatParse.Basic hiding (Parser, runParser, string, char, err, cut)+import FlatParse.Basic hiding (Parser, runParser, string, char, cut) import FlatParse.Examples.BasicLambda.Lexer  --------------------------------------------------------------------------------@@ -62,7 +62,7 @@ int = token $   snd <$> chainr (\n (!place, !acc) -> (place*10,acc+place*n)) digit ((10,) <$> digit) --- | Parse a literal, identifier or parenthsized expression.+-- | Parse a literal, identifier or parenthesized expression. atom :: Parser Tm atom =        (Var           <$> ident)@@ -71,9 +71,12 @@    <|> (IntLit        <$> int)    <|> ($(symbol "(") *> tm <* $(cutSymbol ")")) +expectAtom :: [Expected]+expectAtom = [Msg "identifier", Lit "true", Lit "false", Msg "parenthesized expression"]+ -- | Parse an `App`-level expression. app :: Parser Tm-app = chainl App (atom `cut` [Msg "identifier", Lit "true", Lit "false"]) atom+app = chainl App (atom `cut` expectAtom) atom  -- | Parse a `Mul`-level expression. mul :: Parser Tm@@ -128,17 +131,15 @@  -- | Parse a complete source file. src :: Parser Tm-src = ws *> tm <* eof `cut`-  [Msg "end of input", Msg "identifier", Lit "true", Lit "false",-   Msg "integer literal", Msg "parenthesized expression"]+src = ws *> tm <* eof `cut` (Msg "end of input" : expectAtom)   -- Examples --------------------------------------------------------------------------------  -- testParser src p1--- p1 = unlines [---   "let f = lam x. lam y. x (x (x y)) in",---   "let g = if f true then false else true in",---   "f g g h"---   ]+p1 = unlines [+  "let f = lam x. lam y. x (x (x y)) in",+  "let g = if f true then false else true in",+  "f g g h"+  ]
src/FlatParse/Stateful.hs view
@@ -31,6 +31,7 @@   , fails   , try   , optional+  , optional_   , optioned   , cut   , cutting@@ -227,7 +228,7 @@         pure (OK a (I# n) (B.drop (I# offset) b))       Fail# ->         pure Fail-{-# noinline runParser #-}+{-# inlinable runParser #-}  -- | Run a parser on a `String` input. Reminder: @OverloadedStrings@ for `B.ByteString` does not --   yield a valid UTF-8 encoding! For non-ASCII `B.ByteString` literal input, use `runParserS` or@@ -308,6 +309,11 @@ optional p = (Just <$> p) <|> pure Nothing {-# inline optional #-} +-- | Convert a parsing failure to a `()`.+optional_ :: Parser r e a -> Parser r e ()+optional_ p = (() <$ p) <|> pure ()+{-# inline optional_ #-}+ -- | CPS'd version of `optional`. This is usually more efficient, since it gets rid of the --   extra `Maybe` allocation. optioned :: Parser r e a -> (a -> Parser r e b) -> Parser r e b -> Parser r e b@@ -803,8 +809,8 @@         if pos == p then           ((i, (line, col)):) <$> go line col poss         else do-          c <- anyWord8-          if ord '\n' == fromIntegral c then+          c <- anyChar+          if '\n' == c then             go (line + 1) 0 ((i, pos):poss)           else             go line (col + 1) ((i, pos):poss)