snack-0.4.0.0: bench/Bench.hs
-- |
-- Module : Bench
-- License : CC0-1.0
--
-- Maintainer : mordae@anilinux.org
-- Stability : unstable
-- Portability : non-portable (ghc)
--
module Main
( main
)
where
import Data.String.Conversions
import Control.Applicative
import Data.Either
import Data.Maybe
import Data.Text (Text)
import Data.ByteString (ByteString)
import Criterion.Main
import Data.ByteString.Parser.Char8 qualified as SC
import Data.Attoparsec.ByteString.Char8 qualified as AC
import Data.Text.Parser qualified as ST
import Data.Attoparsec.Text qualified as AT
main :: IO ()
main = defaultMain
[ bgroup "media"
[ bench "Data.ByteString.Parser.Char8" $ nf scMedia (cs $! sampleMedia)
, bench "Data.Attoparsec.ByteString" $ nf acMedia (cs $! sampleMedia)
, bench "Data.Text.Parser" $ nf stMedia (cs $! sampleMedia)
, bench "Data.Attoparsec.Text" $ nf atMedia (cs $! sampleMedia)
]
, bgroup "kv"
[ bench "Data.ByteString.Parser.Char8" $ nf scKeyValue (cs $! sampleKeyValue)
, bench "Data.Attoparsec.ByteString" $ nf acKeyValue (cs $! sampleKeyValue)
, bench "Data.Text.Parser" $ nf stKeyValue (cs $! sampleKeyValue)
, bench "Data.Attoparsec.Text" $ nf atKeyValue (cs $! sampleKeyValue)
]
]
type Media a = (a, a, [(a, a)], Float)
sampleMedia :: String
sampleMedia = "text/html, text/plain;q=0.7"
sampleKeyValue :: String
sampleKeyValue = "lst = \"\\\"first\\\", \\\"second\\\", \\\"third\\\"\""
{-# NOINLINE scMedia #-}
scMedia :: ByteString -> Maybe [Media ByteString]
scMedia = SC.parseOnly (SC.skipSpace *> pMediaList <* SC.endOfInput)
where
pMediaList :: SC.Parser [Media ByteString]
pMediaList = pMedia `SC.sepBy` pSeparator
pMedia :: SC.Parser (Media ByteString)
pMedia = do
mainType <- pToken
subType <- (SC.char '/' *> pToken) <|> pure ""
(qs, params) <- partitionEithers <$> SC.many (SC.eitherP pQuality pParameter)
let quality = fromMaybe 1.0 . listToMaybe $ qs
return (mainType, subType, params, quality)
pParameter :: SC.Parser (ByteString, ByteString)
pParameter = do
_ <- SC.char ';' <* SC.skipSpace
name <- pToken
_ <- SC.char '=' <* SC.skipSpace
value <- pValue
return (name, value)
pQuality :: SC.Parser Float
pQuality = do
_ <- SC.char ';' <* SC.skipSpace
_ <- SC.char 'q' <* SC.skipSpace
_ <- SC.char '=' <* SC.skipSpace
SC.fractional
pToken :: SC.Parser ByteString
pToken = SC.label "token" $ SC.takeTill1 isSpecial <* SC.skipSpace
pSeparator :: SC.Parser Char
pSeparator = SC.char ',' <* SC.skipSpace
pValue :: SC.Parser ByteString
pValue = SC.branch [ (SC.char '"', \_ -> pQuotedStr)
, ( pure ' ', \_ -> pToken)
]
pQuotedStr :: SC.Parser ByteString
pQuotedStr = pString <* SC.char '"' <* SC.skipSpace
pString :: SC.Parser ByteString
pString = SC.label "string" $ SC.takeWhile isStrChar
isStrChar :: (Char -> Bool)
isStrChar c = c /= '\\' && c /= '"'
isSpecial :: (Char -> Bool)
isSpecial c = c <= ' '
|| c == '(' || c == ')'
|| c == '<' || c == '>'
|| c == '@' || c == ',' || c == ';'
|| c == ':' || c == '\\'
|| c == '"' || c == '/'
|| c == '[' || c == ']'
|| c == '?' || c == '='
{-# NOINLINE acMedia #-}
acMedia :: ByteString -> Either String [Media ByteString]
acMedia = AC.parseOnly (AC.skipSpace *> pMediaList <* AC.endOfInput)
where
pMediaList :: AC.Parser [Media ByteString]
pMediaList = pMedia `AC.sepBy` pSeparator
pMedia :: AC.Parser (Media ByteString)
pMedia = do
mainType <- pToken
subType <- (AC.char '/' *> pToken) <|> pure ""
(qs, params) <- partitionEithers <$> AC.many' (AC.eitherP pQuality pParameter)
let quality = fromMaybe 1.0 . listToMaybe $ qs
return (mainType, subType, params, quality)
pParameter :: AC.Parser (ByteString, ByteString)
pParameter = do
_ <- AC.char ';' <* AC.skipSpace
name <- pToken
_ <- AC.char '=' <* AC.skipSpace
value <- pValue
return (name, value)
pQuality :: AC.Parser Float
pQuality = do
_ <- AC.char ';' <* AC.skipSpace
_ <- AC.char 'q' <* AC.skipSpace
_ <- AC.char '=' <* AC.skipSpace
AC.rational
pToken :: AC.Parser ByteString
pToken = AC.takeTill isSpecial <* AC.skipSpace
pSeparator :: AC.Parser Char
pSeparator = AC.char ',' <* AC.skipSpace
pValue :: AC.Parser ByteString
pValue = pQuotedStr <|> pToken
pQuotedStr :: AC.Parser ByteString
pQuotedStr = pQuoted $ AC.takeWhile isStrChar
pQuoted :: AC.Parser a -> AC.Parser a
pQuoted p = AC.char '"' *> p <* AC.char '"' <* AC.skipSpace
isStrChar :: (Char -> Bool)
isStrChar c = c /= '\\' && c /= '"'
isSpecial :: (Char -> Bool)
isSpecial c = c <= ' '
|| c == '(' || c == ')'
|| c == '<' || c == '>'
|| c == '@' || c == ',' || c == ';'
|| c == ':' || c == '\\'
|| c == '"' || c == '/'
|| c == '[' || c == ']'
|| c == '?' || c == '='
{-# NOINLINE stMedia #-}
stMedia :: Text -> Maybe [Media Text]
stMedia = ST.parseOnly (ST.skipSpace *> pMediaList <* ST.endOfInput)
where
pMediaList :: ST.Parser [Media Text]
pMediaList = pMedia `ST.sepBy` pSeparator
pMedia :: ST.Parser (Media Text)
pMedia = do
mainType <- pToken
subType <- (ST.char '/' *> pToken) <|> pure ""
(qs, params) <- partitionEithers <$> ST.many (ST.eitherP pQuality pParameter)
let quality = fromMaybe 1.0 . listToMaybe $ qs
return (mainType, subType, params, quality)
pParameter :: ST.Parser (Text, Text)
pParameter = do
_ <- ST.char ';' <* ST.skipSpace
name <- pToken
_ <- ST.char '=' <* ST.skipSpace
value <- pValue
return (name, value)
pQuality :: ST.Parser Float
pQuality = do
_ <- ST.char ';' <* ST.skipSpace
_ <- ST.char 'q' <* ST.skipSpace
_ <- ST.char '=' <* ST.skipSpace
ST.fractional
pToken :: ST.Parser Text
pToken = ST.takeTill1 isSpecial <* ST.skipSpace
pSeparator :: ST.Parser Char
pSeparator = ST.char ',' <* ST.skipSpace
pValue :: ST.Parser Text
pValue = ST.branch [ (ST.char '"', \_ -> pQuotedStr)
, ( pure ' ', \_ -> pToken)
]
pQuotedStr :: ST.Parser Text
pQuotedStr = pString <* ST.char '"' <* ST.skipSpace
pString :: ST.Parser Text
pString = ST.label "string" $ ST.takeWhile isStrChar
isStrChar :: (Char -> Bool)
isStrChar c = c /= '\\' && c /= '"'
isSpecial :: (Char -> Bool)
isSpecial c = c <= ' '
|| c == '(' || c == ')'
|| c == '<' || c == '>'
|| c == '@' || c == ',' || c == ';'
|| c == ':' || c == '\\'
|| c == '"' || c == '/'
|| c == '[' || c == ']'
|| c == '?' || c == '='
{-# NOINLINE atMedia #-}
atMedia :: Text -> Either String [Media Text]
atMedia = AT.parseOnly (AT.skipSpace *> pMediaList <* AT.endOfInput)
where
pMediaList :: AT.Parser [Media Text]
pMediaList = pMedia `AT.sepBy` pSeparator
pMedia :: AT.Parser (Media Text)
pMedia = do
mainType <- pToken
subType <- (AT.char '/' *> pToken) <|> pure ""
(qs, params) <- partitionEithers <$> AT.many' (AT.eitherP pQuality pParameter)
let quality = fromMaybe 1.0 . listToMaybe $ qs
return (mainType, subType, params, quality)
pParameter :: AT.Parser (Text, Text)
pParameter = do
_ <- AT.char ';' <* AT.skipSpace
name <- pToken
_ <- AT.char '=' <* AT.skipSpace
value <- pValue
return (name, value)
pQuality :: AT.Parser Float
pQuality = do
_ <- AT.char ';' <* AT.skipSpace
_ <- AT.char 'q' <* AT.skipSpace
_ <- AT.char '=' <* AT.skipSpace
AT.rational
pToken :: AT.Parser Text
pToken = AT.takeTill isSpecial <* AT.skipSpace
pSeparator :: AT.Parser Char
pSeparator = AT.char ',' <* AT.skipSpace
pValue :: AT.Parser Text
pValue = pQuotedStr <|> pToken
pQuotedStr :: AT.Parser Text
pQuotedStr = pQuoted $ AT.takeWhile isStrChar
pQuoted :: AT.Parser a -> AT.Parser a
pQuoted p = AT.char '"' *> p <* AT.char '"' <* AT.skipSpace
isStrChar :: (Char -> Bool)
isStrChar c = c /= '\\' && c /= '"'
isSpecial :: (Char -> Bool)
isSpecial c = c <= ' '
|| c == '(' || c == ')'
|| c == '<' || c == '>'
|| c == '@' || c == ',' || c == ';'
|| c == ':' || c == '\\'
|| c == '"' || c == '/'
|| c == '[' || c == ']'
|| c == '?' || c == '='
scKeyValue :: ByteString -> Maybe (ByteString, ByteString)
scKeyValue = SC.parseOnly (pKeyValue <* SC.endOfInput)
where
pKeyValue = do
_ <- SC.skipSpace
key <- SC.takeWhile1 isToken
_ <- SC.skipSpace
_ <- SC.char '='
_ <- SC.skipSpace
_ <- SC.char '"'
val <- SC.scan False scanString
_ <- SC.char '"'
return (key, val)
acKeyValue :: ByteString -> Either String (ByteString, ByteString)
acKeyValue = AC.parseOnly (pKeyValue <* AC.endOfInput)
where
pKeyValue = do
_ <- AC.skipSpace
key <- AC.takeWhile1 isToken
_ <- AC.skipSpace
_ <- AC.char '='
_ <- AC.skipSpace
_ <- AC.char '"'
val <- AC.scan False scanString
_ <- AC.char '"'
return (key, val)
stKeyValue :: Text -> Maybe (Text, Text)
stKeyValue = ST.parseOnly (pKeyValue <* ST.endOfInput)
where
pKeyValue = do
_ <- ST.skipSpace
key <- ST.takeWhile1 isToken
_ <- ST.skipSpace
_ <- ST.char '='
_ <- ST.skipSpace
_ <- ST.char '"'
val <- ST.scan False scanString
_ <- ST.char '"'
return (key, val)
atKeyValue :: Text -> Either String (Text, Text)
atKeyValue = AT.parseOnly (pKeyValue <* AT.endOfInput)
where
pKeyValue = do
_ <- AT.skipSpace
key <- AT.takeWhile1 isToken
_ <- AT.skipSpace
_ <- AT.char '='
_ <- AT.skipSpace
_ <- AT.char '"'
val <- AT.scan False scanString
_ <- AT.char '"'
return (key, val)
scanString :: Bool -> Char -> Maybe Bool
scanString True _ = Just False
scanString False '"' = Nothing
scanString False '\\' = Just True
scanString False _ = Just False
isToken :: Char -> Bool
isToken c = ('A' <= c && c <= 'Z')
|| ('a' <= c && c <= 'z')
|| ('0' <= c && c <= '9')
|| ('_' == c)
|| ('-' == c)
-- vim:set ft=haskell sw=2 ts=2 et: