{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
module Html.Convert
( Converted(..)
, Convert(..)
) where
import Html.Type.Internal
import Data.Word
import Data.Proxy
import Data.String
import Data.Char (ord)
import Data.Double.Conversion.ByteString
import Numeric.Natural
import GHC.TypeLits
import GHC.Types
import GHC.Prim (Addr#, ord#, indexCharOffAddr#)
import GHC.CString (unpackCString#, unpackCStringUtf8#, unpackFoldrCString#)
import GHC.Base (build)
import qualified Data.Semigroup as S
import qualified Data.Monoid as M
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Builder.Prim as BP
import qualified Data.ByteString.Builder.Internal as U
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
newtype Converted = Converted {unConv :: B.Builder} deriving (M.Monoid,S.Semigroup)
instance IsString Converted where fromString = convert
class Convert a where
convert :: a -> Converted
instance KnownSymbol a => Convert (Proxy a) where {-# INLINE convert #-}; convert = Converted . U.byteStringCopy . fromString . symbolVal
instance Convert () where {-# INLINE convert #-}; convert = const mempty
instance Convert (Raw Char) where {-# INLINE convert #-}; convert = Converted . B.charUtf8 . fromRaw
instance Convert (Raw String) where {-# INLINE convert #-}; convert = stringConvRaw . fromRaw
instance Convert (Raw T.Text) where {-# INLINE convert #-}; convert = Converted . T.encodeUtf8Builder . fromRaw
instance Convert (Raw TL.Text) where {-# INLINE convert #-}; convert = Converted . TL.encodeUtf8Builder . fromRaw
instance Convert (Raw B.Builder) where {-# INLINE convert #-}; convert = Converted . fromRaw
instance Convert Char where {-# INLINE convert #-}; convert = Converted . BP.primBounded escapeUtf8
instance Convert String where {-# INLINE convert #-}; convert = stringConv
instance Convert T.Text where {-# INLINE convert #-}; convert = Converted . T.encodeUtf8BuilderEscaped escape
instance Convert TL.Text where {-# INLINE convert #-}; convert = Converted . TL.encodeUtf8BuilderEscaped escape
instance Convert Int where {-# INLINE convert #-}; convert = Converted . B.intDec
instance Convert Integer where {-# INLINE convert #-}; convert = Converted . B.integerDec
instance Convert Natural where {-# INLINE convert #-}; convert = Converted . B.integerDec . fromIntegral
instance Convert Float where {-# INLINE convert #-}; convert = Converted . U.byteStringCopy . toShortest . realToFrac
instance Convert Double where {-# INLINE convert #-}; convert = Converted . U.byteStringCopy . toShortest
instance Convert Word where {-# INLINE convert #-}; convert = Converted . B.wordDec
{-# INLINE builderCString# #-}
builderCString# :: BP.BoundedPrim Word8 -> Addr# -> Converted
builderCString# bp addr = Converted $ BP.primUnfoldrBounded bp go 0
where
go !i | b /= 0 = Just (fromIntegral b, i+1)
| otherwise = Nothing
where
!b = I# (ord# (at# i))
at# (I# i#) = indexCharOffAddr# addr i#
{-# INLINE [0] stringConv #-}
stringConv :: String -> Converted
stringConv = Converted . BP.primMapListBounded escapeUtf8
{-# INLINE [0] stringConvRaw #-}
stringConvRaw :: String -> Converted
stringConvRaw = Converted . B.stringUtf8
escapeUtf8 :: BP.BoundedPrim Char
escapeUtf8
= BP.condB (> '>' ) BP.charUtf8
. BP.condB (== '<' ) (fixed4 ('&',('l',('t',';'))))
. BP.condB (== '>' ) (fixed4 ('&',('g',('t',';'))))
. BP.condB (== '&' ) (fixed5 ('&',('a',('m',('p',';')))))
. BP.condB (== '"' ) (fixed5 ('&',('#',('3',('4',';')))))
. BP.condB (== '\'') (fixed5 ('&',('#',('3',('9',';')))))
$ BP.liftFixedToBounded BP.char7
where
{-# INLINE fixed4 #-}
fixed4 x = BP.liftFixedToBounded $ const x BP.>$<
BP.char7 BP.>*< BP.char7 BP.>*< BP.char7 BP.>*< BP.char7
{-# INLINE fixed5 #-}
fixed5 x = BP.liftFixedToBounded $ const x BP.>$<
BP.char7 BP.>*< BP.char7 BP.>*< BP.char7 BP.>*< BP.char7 BP.>*< BP.char7
escape :: BP.BoundedPrim Word8
escape
= BP.condB (> c2w '>' ) (BP.liftFixedToBounded BP.word8)
. BP.condB (== c2w '<' ) (fixed4 (c2w '&',(c2w 'l',(c2w 't',c2w ';'))))
. BP.condB (== c2w '>' ) (fixed4 (c2w '&',(c2w 'g',(c2w 't',c2w ';'))))
. BP.condB (== c2w '&' ) (fixed5 (c2w '&',(c2w 'a',(c2w 'm',(c2w 'p',c2w ';')))))
. BP.condB (== c2w '"' ) (fixed5 (c2w '&',(c2w '#',(c2w '3',(c2w '4',c2w ';')))))
. BP.condB (== c2w '\'') (fixed5 (c2w '&',(c2w '#',(c2w '3',(c2w '9',c2w ';')))))
$ BP.liftFixedToBounded BP.word8
where
c2w = fromIntegral . ord
{-# INLINE fixed4 #-}
fixed4 x = BP.liftFixedToBounded $ const x BP.>$<
BP.word8 BP.>*< BP.word8 BP.>*< BP.word8 BP.>*< BP.word8
{-# INLINE fixed5 #-}
fixed5 x = BP.liftFixedToBounded $ const x BP.>$<
BP.word8 BP.>*< BP.word8 BP.>*< BP.word8 BP.>*< BP.word8 BP.>*< BP.word8
{-# RULES "CONVERTED literal" forall a.
stringConv (unpackCString# a) = builderCString# escape a #-}
{-# RULES "CONVERTED foldr literal" forall a.
stringConv (build (unpackFoldrCString# a)) = builderCString# escape a #-}
{-# RULES "CONVERTED literal raw" forall a.
stringConvRaw (unpackCString# a) = builderCString# (BP.liftFixedToBounded BP.word8) a #-}
{-# RULES "CONVERTED foldr literal raw" forall a.
stringConvRaw (build (unpackFoldrCString# a)) = builderCString# (BP.liftFixedToBounded BP.word8) a #-}
{-# RULES "CONVERTED literal utf8" forall a.
stringConv (unpackCStringUtf8# a) = convert (T.pack (unpackCStringUtf8# a)) #-}
{-# RULES "CONVERTED literal utf8 raw" forall a.
stringConvRaw (unpackCStringUtf8# a) = convert (Raw (T.pack (unpackCStringUtf8# a))) #-}