{- Copyright 2010-2016 Mario Blazevic This file is part of the Streaming Component Combinators (SCC) project. The SCC project is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SCC. If not, see . -} {-# LANGUAGE Haskell2010, BangPatterns, ExistentialQuantification, FlexibleContexts, OverloadedStrings, ScopedTypeVariables #-} module Main (main, parseWhole, parseChunked) where import Prelude hiding (null, splitAt) import Control.Applicative (Alternative, (<$>), (<*>), (<*), (*>), (<|>), many, pure) import Control.Monad (void) import Data.Foldable (foldl') import Data.Monoid (Monoid, (<>), mappend, mempty) import Data.Monoid.Textual (TextualMonoid) import Data.Monoid.Factorial (FactorialMonoid (splitAt)) import Data.Monoid.Null (MonoidNull (null)) import Text.ParserCombinators.Incremental.LeftBiasedLocal import Control.DeepSeq (NFData(..)) import Criterion.Main (bench, bgroup, defaultMain, nf) import qualified Data.ByteString as B import qualified Data.Text.IO as T import Data.Monoid.Instances.ByteString.UTF8 (ByteStringUTF8(ByteStringUTF8)) import Data.Monoid.Instances.Concat (Concat, extract) instance NFData ByteStringUTF8 where rnf (ByteStringUTF8 b) = rnf b instance NFData a => NFData (Concat a) where rnf s = rnf (extract s) endOfInput :: MonoidNull s => Parser s () endOfInput = eof char :: TextualMonoid t => Char -> Parser t t char = satisfyChar . (==) sepBy1 :: Alternative f => f a -> f s -> f [a] sepBy1 p q = (:) <$> p <*> many (q *> p) lineEnd :: TextualMonoid t => Parser t () lineEnd = void (char '\n') <|> void (string "\r\n") <|> void (char '\r') "end of line" unquotedField :: TextualMonoid t => Parser t t unquotedField = takeCharsWhile (`notElem` (",\n\r\"" :: [Char])) "unquoted field" insideQuotes :: TextualMonoid t => Parser t t insideQuotes = mappend <$> takeCharsWhile (/= '"') <*> concatMany (mappend <$> dquotes <*> insideQuotes) "inside of double quotes" where dquotes = string "\"\"" >> return "\"" "paired double quotes" quotedField :: TextualMonoid t => Parser t t quotedField = char '"' *> insideQuotes <* char '"' "quoted field" field :: TextualMonoid t => Parser t t field = quotedField <|> unquotedField "field" record :: TextualMonoid t => Parser t [t] record = field `sepBy1` char ',' -- file1 is not incremental because it's fallible file1 :: TextualMonoid t => Parser t [[t]] file1 = (:) <$> record +<*> manyTill (lineEnd *> ((:[]) <$> record)) (moptional lineEnd *> endOfInput) "file" file2 :: forall t. TextualMonoid t => Parser t [[t]] file2 = (:) <$> record +<*> many ((notFollowedBy (moptional lineEnd *> endOfInput) :: Parser t ()) *> lineEnd *> record) "file" parseWhole :: TextualMonoid t => Parser t [[t]] -> t -> [([[t]], t)] parseWhole p s = completeResults (feedEof $ feed s p) parseChunked :: TextualMonoid t => Parser t [[t]] -> [t] -> [([[t]], t)] parseChunked p chunks = completeResults (feedEof $ foldl' (flip feed) p $ chunks) parseIncremental :: (Monoid r, TextualMonoid t) => Parser t [[t]] -> ([[t]] -> r) -> [t] -> r parseIncremental p0 process chunks = let (inc, p') = foldl' feedInc (mempty, p0) chunks in case completeResults (feedEof p') of [] -> inc (r,_):_ -> inc <> process r where feedInc (!inc, !p) chunk = let !(prefix, p') = resultPrefix (feed chunk p) in (inc <> process prefix, p') chunksOf :: FactorialMonoid t => Int -> t -> [t] chunksOf len t | null t = [] | (h, t') <- splitAt len t = h : chunksOf len t' chunkSize :: Int chunkSize = 1024 data Input = forall t. (NFData t, TextualMonoid t) => Input String t main :: IO () main = do airportsS <- readFile "Benchmarks/airports.dat" airportsT <- T.readFile "Benchmarks/airports.dat" airportsB <- B.readFile "Benchmarks/airports.dat" let inputs = [Input "UTF8" (ByteStringUTF8 airportsB), Input "Text" airportsT, Input "Concat String" (pure airportsS :: Concat String)] defaultMain [ let chunks = chunksOf chunkSize i in nf id chunks `seq` bgroup inputName [ bgroup parserName [ bench "whole" $ nf (parseWhole p) i, bench "chunked" $ nf (parseChunked p) chunks, bench "incremental" $ nf (parseIncremental p id) chunks, bench "incremental'" $ nf (parseIncremental p (pure :: a -> Concat a)) chunks] | (parserName, p) <- [("manyTill", file1), ("many", file2)]] | Input inputName i <- inputs]