packages feed

idn-0.1.0.0: benchmark/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Criterion.Main
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.IDN
import Data.Text.Punycode
import Test.QuickCheck
import Test.QuickCheck.Gen
import Test.QuickCheck.Random (mkQCGen)
import Control.DeepSeq (force)
import Control.Exception (evaluate)

-- | Deterministic generator runner
runGen :: Gen a -> a
runGen g = unGen g (mkQCGen 42) 30

-- | Generate a random ASCII label
genAsciiLabel :: Gen Text
genAsciiLabel = do
  len <- choose (5, 63)
  chars <- vectorOf len (elements ['a'..'z'])
  return $ T.pack chars

-- | Generate a random Latin-1 label (with some non-ASCII)
genLatin1Label :: Gen Text
genLatin1Label = do
  len <- choose (5, 20)
  -- mix of ascii and latin-1 supplements
  chars <- vectorOf len (elements (['a'..'z'] ++ ['\224'..'\255'])) 
  return $ T.pack chars

-- | Generate a random RTL label (Arabic/Hebrew)
genRTLLabel :: Gen Text
genRTLLabel = do
  len <- choose (3, 15)
  -- Arabic range roughly
  chars <- vectorOf len (elements ['\x0627'..'\x064A'])
  return $ T.pack chars

-- | Generate a random CJK label
genCJKLabel :: Gen Text
genCJKLabel = do
  len <- choose (2, 10)
  -- Common CJK range
  chars <- vectorOf len (elements ['\x4E00'..'\x4E50'])
  return $ T.pack chars

-- | Generate a random Emoji/Mixed label
genEmojiLabel :: Gen Text
genEmojiLabel = do
  let emojis = ["😀", "🚀", "fractal", "✨", "café", "こんにちは"]
  elements (map T.pack emojis)

-- | The Benchmark Corpus
corpus :: [(String, [Text])]
corpus = 
  [ ("ASCII", runGen $ vectorOf 100 genAsciiLabel)
  , ("Latin1", runGen $ vectorOf 100 genLatin1Label)
  , ("RTL", runGen $ vectorOf 100 genRTLLabel)
  , ("CJK", runGen $ vectorOf 100 genCJKLabel)
  , ("Emoji/Mixed", runGen $ vectorOf 50 genEmojiLabel)
  ]

-- | Pre-calculated Punycode versions for decoding benchmarks
encodedCorpus :: [(String, [Text])]
encodedCorpus = 
      [ (name, [ forceRes $ either (const t) ("xn--" <>) (encode t) | t <- texts ])
  | (name, texts) <- corpus
  ]
  where
    forceRes x = x `seq` x

-- | Extract just the punycode part (without xn--) for Punycode.decode benchmarks
punycodeOnlyCorpus :: [(String, [Text])]
punycodeOnlyCorpus =
  [ (name, [ forceRes $ if "xn--" `T.isPrefixOf` t then T.drop 4 t else t | t <- texts ])
  | (name, texts) <- encodedCorpus
  ]
  where
    forceRes x = x `seq` x

main :: IO ()
main = do
  -- Ensure corpus is evaluated
  _ <- evaluate (force corpus)
  _ <- evaluate (force encodedCorpus)
  
  defaultMain
    [ bgroup "toASCII" 
        [ bench name $ nf (map toASCII) texts
        | (name, texts) <- corpus
        ]
    , bgroup "toUnicode"
        [ bench name $ nf (map toUnicode) texts
        | (name, texts) <- encodedCorpus
        ]
    , bgroup "validateLabel"
        [ bench name $ nf (map validateLabel) texts
        | (name, texts) <- corpus
        ]
    , bgroup "Punycode.encode"
        [ bench name $ nf (map encode) texts
        | (name, texts) <- corpus
        ]
    , bgroup "Punycode.decode"
        [ bench name $ nf (map decode) texts
        | (name, texts) <- punycodeOnlyCorpus
        ]
    ]