zenacy-html-2.0.0: src/Zenacy/HTML/Internal/Token.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Defines a token type used by the lexer.
module Zenacy.HTML.Internal.Token
( Token(..)
, TokenBuffer(..)
, TAttr(..)
, tokenAttr
, tokenHasAttr
, tokenGetAttr
, tokenGetAttrVal
, tokenBuffer
, tokenCapacity
, tokenReset
, tokenTail
, tokenFirst
, tokenNext
, tokenCount
, tokenOffset
, tokenList
, tokenDrop
, tokenHasEOF
, tokenSlice
, tokenTagStartName
, tokenTagEndName
, tokenDoctypeType
, tokenTagStartType
, tokenTagEndType
, tokenCommentType
, tokenCharType
, tokenEOFType
, tokenDoctypeInit
, tokenDoctypeNameAppend
, tokenDoctypeSetForceQuirks
, tokenDoctypePublicIdInit
, tokenDoctypePublicIdAppend
, tokenDoctypeSystemIdInit
, tokenDoctypeSystemIdAppend
, tokenTagStartInit
, tokenTagStartSetSelfClosing
, tokenTagEndInit
, tokenTagNameAppend
, tokenAttrInit
, tokenAttrNameAppend
, tokenAttrValAppend
, tokenAttrNamePrune
, tokenCommentInit
, tokenCommentAppend
, tokenCharInit
, tokenEOFInit
, tokenType
, tokenSize
, tokenPack
) where
import Zenacy.HTML.Internal.BS
import Zenacy.HTML.Internal.Buffer
import Zenacy.HTML.Internal.Core
import Zenacy.HTML.Internal.Types
import Control.Monad
( when
, forM
)
import Control.Monad.Extra
( anyM
)
import Control.Monad.ST
( ST
)
import Data.STRef
( STRef
, newSTRef
, readSTRef
, writeSTRef
)
import Data.DList
( DList
)
import qualified Data.DList as D
( empty
, snoc
, toList
)
import Data.List
( find
, or
)
import Data.Maybe
( catMaybes
, isJust
)
import Data.Vector.Unboxed.Mutable
( MVector
)
import qualified Data.Vector.Unboxed.Mutable as U
( new
, length
, read
, write
, grow
)
import Data.Word
( Word8
)
-- | Defines the token type.
-- The token type is used for testing and debugging only.
data Token
= TDoctype
{ tDoctypeName :: !BS
, tDoctypeQuirks :: !Bool
, tDoctypePublic :: !(Maybe BS)
, tDoctypeSystem :: !(Maybe BS)
}
| TStart
{ tStartName :: !BS
, tStartClosed :: !Bool
, tStartAttr :: ![TAttr]
}
| TEnd
{ tEndName :: !BS
}
| TComment
{ tCommentData :: !BS
}
| TChar
{ tCharData :: !Word8
}
| TEOF
deriving (Eq, Ord, Show)
-- | An HTML element attribute type.
data TAttr = TAttr
{ tAttrName :: BS
, tAttrVal :: BS
, tAttrNamespace :: HTMLAttrNamespace
} deriving (Eq, Ord, Show)
-- | A type of buffer used to hold tokens.
data TokenBuffer s = TokenBuffer
{ tbCntl :: MVector s Int
, tbData :: MVector s Word8
}
-- | Makes an attribute.
tokenAttr :: BS -> BS -> TAttr
tokenAttr n v = TAttr n v HTMLAttrNamespaceNone
-- | Determines if a token has an attribute.
tokenHasAttr :: BS -> Token -> Bool
tokenHasAttr x t = isJust $ tokenGetAttr x t
-- | Finds an attribute in a token.
tokenGetAttr :: BS -> Token -> Maybe TAttr
tokenGetAttr x = \case
TStart{..} -> find (\TAttr{..} -> tAttrName == x) tStartAttr
_otherwise -> Nothing
-- | Finds an attribute value for a token.
tokenGetAttrVal :: BS -> Token -> Maybe BS
tokenGetAttrVal x t = tAttrVal <$> tokenGetAttr x t
-- | Makes a new token buffer.
tokenBuffer :: ST s (STRef s (TokenBuffer s))
tokenBuffer = do
c <- U.new 100
d <- U.new 100
r <- newSTRef (TokenBuffer c d)
tokenReset r
pure r
-- | Gets the capacity of the buffer.
tokenCapacity :: STRef s (TokenBuffer s) -> ST s (Int, Int)
tokenCapacity r = do
TokenBuffer{..} <- readSTRef r
pure (U.length tbCntl, U.length tbData)
-- | Size of the buffer header.
headerSize :: Int
headerSize = 4
-- | Resets a token buffer.
tokenReset :: STRef s (TokenBuffer s) -> ST s ()
tokenReset r = do
TokenBuffer{..} <- readSTRef r
U.write tbCntl 0 1 -- data buffer length (skip first byte)
U.write tbCntl 1 0 -- token list head offset
U.write tbCntl 2 0 -- token list tail offset
U.write tbCntl 3 0 -- token emit offset
U.write tbData 0 0 -- first byte is unsed
-- | Get the token buffer tail offset.
tokenTail :: STRef s (TokenBuffer s) -> ST s Int
tokenTail r = do
TokenBuffer{..} <- readSTRef r
U.read tbCntl 2
-- | Positions the emitter to the first token and returns its offset.
tokenFirst :: STRef s (TokenBuffer s) -> ST s Int
tokenFirst r = do
TokenBuffer{..} <- readSTRef r
h <- U.read tbCntl 1
U.write tbCntl 3 h
pure h
-- | Positions the emitter to the next token and returns its offset.
tokenNext :: STRef s (TokenBuffer s) -> ST s Int
tokenNext r = do
TokenBuffer{..} <- readSTRef r
t <- U.read tbCntl 2
i <- U.read tbCntl 3
n <- U.read tbCntl (i + 1)
if i == t
then pure 0
else do
let j = i + n
U.write tbCntl 3 j
pure j
-- | Counts the number of tokens in the buffer.
tokenCount :: STRef s (TokenBuffer s) -> ST s Int
tokenCount r = tokenFold (\i n -> n + 1) 0 r
-- | Gets a list of the tokens in the buffer.
tokenOffset :: STRef s (TokenBuffer s) -> ST s [Int]
tokenOffset r = D.toList <$> tokenFold (flip D.snoc) D.empty r
-- | Gets a list of the tokens in the buffer.
tokenList :: STRef s (TokenBuffer s) -> ST s [Token]
tokenList r = tokenOffset r >>= mapM (flip tokenPack r)
-- | Performs a fold over the token offsets in the buffer.
tokenFold :: (Int -> a -> a) -> a -> STRef s (TokenBuffer s) -> ST s a
tokenFold f a r = do
TokenBuffer{..} <- readSTRef r
i <- U.read tbCntl 3
x <- go a tokenFirst
U.write tbCntl 3 i
pure x
where
go b g = do
i <- g r
if i == 0
then pure b
else go (f i b) tokenNext
-- | Drops the last token from the buffer.
tokenDrop :: STRef s (TokenBuffer s) -> ST s ()
tokenDrop r = do
TokenBuffer{..} <- readSTRef r
headPtr <- U.read tbCntl 1
tailPtr <- U.read tbCntl 2
when (tailPtr > 0) $ do
if tailPtr == headPtr
then tokenReset r
else go tbCntl tailPtr headPtr
where
go d t i = do
n <- U.read d (i + offsetSize)
if t == i + n
then U.write d 2 i
else go d t (i + n)
-- | Returns whether a buffer includes an EOF token.
tokenHasEOF :: STRef s (TokenBuffer s) -> ST s Bool
tokenHasEOF r =
tokenOffset r >>= anyM isEOF
where
isEOF i = (== tokenEOFType) <$> tokenType i r
-- | Returns a slice of the data area of a token buffer.
tokenSlice :: Int -> Int -> STRef s (TokenBuffer s) -> ST s [Word8]
tokenSlice offset len r = do
TokenBuffer{..} <- readSTRef r
go tbData offset len D.empty
where
go d i 0 a = pure $ D.toList a
go d i n a = do
w <- U.read d i
go d (i + 1) (n - 1) $ D.snoc a w
-- | Returns the start tag name at for the token at an offset.
tokenTagStartName :: Int -> STRef s (TokenBuffer s) -> ST s (Maybe [Word8])
tokenTagStartName = tagNameIfType tokenTagStartType
-- | Returns the end tag name at for the token at an offset.
tokenTagEndName :: Int -> STRef s (TokenBuffer s) -> ST s (Maybe [Word8])
tokenTagEndName = tagNameIfType tokenTagEndType
-- | Returns the start tag name at for the token at an offset.
tagNameIfType :: Int -> Int -> STRef s (TokenBuffer s) -> ST s (Maybe [Word8])
tagNameIfType t x r = do
TokenBuffer{..} <- readSTRef r
a <- tokenType x r
if a == t
then do
o <- U.read tbCntl (x + 2)
n <- U.read tbCntl (x + 3)
Just <$> tokenSlice o n r
else
pure Nothing
-- | Defines the type for a DOCTYPE token.
tokenDoctypeType :: Int
tokenDoctypeType = 101
-- | Defines the type for a start tag token.
tokenTagStartType :: Int
tokenTagStartType = 102
-- | Defines the type for a end tag token.
tokenTagEndType :: Int
tokenTagEndType = 103
-- | Defines the type for a comment token.
tokenCommentType :: Int
tokenCommentType = 104
-- | Defines the type for a character token.
tokenCharType :: Int
tokenCharType = 105
-- | Defines the type for an EOF token.
tokenEOFType :: Int
tokenEOFType = 106
tokenInit :: Int -> STRef s (TokenBuffer s) -> ST s (MVector s Int, Int)
tokenInit maxIndex r = do
t@TokenBuffer{..} <- readSTRef r
tailPtr <- U.read tbCntl 2
i <- if tailPtr == 0
then do
U.write tbCntl 1 headerSize
U.write tbCntl 2 headerSize
pure headerSize
else do
n <- U.read tbCntl (tailPtr + offsetSize)
let j = tailPtr + n
U.write tbCntl 2 j
pure j
c <- ensureCntl (i + maxIndex) t r
pure (c, i)
-- | Defines the size of the doctype token.
tokenDoctypeSize :: Int
tokenDoctypeSize = 11
-- | Adds a new DOCTYPE token to the lexer.
tokenDoctypeInit :: STRef s (TokenBuffer s) -> ST s ()
tokenDoctypeInit r = do
(c, i) <- tokenInit 10 r
U.write c (i + 0) tokenDoctypeType
U.write c (i + 1) tokenDoctypeSize
U.write c (i + 2) 0 -- name offset
U.write c (i + 3) 0 -- name length
U.write c (i + 4) 0 -- force quirks flag
U.write c (i + 5) 0 -- public id exists flag
U.write c (i + 6) 0 -- public id offset
U.write c (i + 7) 0 -- public id length
U.write c (i + 8) 0 -- system id exists flag
U.write c (i + 9) 0 -- system id offset
U.write c (i + 10) 0 -- system id length
-- | Appends a character to the current DOCTYPE token.
tokenDoctypeNameAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenDoctypeNameAppend = stringAppendTail 2
-- | Sets the force quirks flag for the current DOCTYPE.
tokenDoctypeSetForceQuirks :: STRef s (TokenBuffer s) -> ST s ()
tokenDoctypeSetForceQuirks r = do
TokenBuffer{..} <- readSTRef r
i <- U.read tbCntl 2
U.write tbCntl (i + 4) 1
-- | Initializes the DOCTYPE public ID.
tokenDoctypePublicIdInit :: STRef s (TokenBuffer s) -> ST s ()
tokenDoctypePublicIdInit r = do
TokenBuffer{..} <- readSTRef r
i <- U.read tbCntl 2
U.write tbCntl (i + 5) 1
-- | Appends a character to the DOCTYPE public ID.
tokenDoctypePublicIdAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenDoctypePublicIdAppend = stringAppendTail 6
-- | Initializes the DOCTYPE system ID.
tokenDoctypeSystemIdInit :: STRef s (TokenBuffer s) -> ST s ()
tokenDoctypeSystemIdInit r = do
TokenBuffer{..} <- readSTRef r
i <- U.read tbCntl 2
U.write tbCntl (i + 8) 1
-- | Appends a character to the DOCTYPE system ID.
tokenDoctypeSystemIdAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenDoctypeSystemIdAppend = stringAppendTail 9
-- | Defines the size of an attribute record.
attrSize :: Int
attrSize = 4
-- | Defines the offset to the start of the attributes.
attrStart :: Int
attrStart = 6
-- | Defines the size of the start tag token.
tokenTagStartSize :: Int
tokenTagStartSize = 6
-- | Adds a new start tag to the lexer.
tokenTagStartInit :: STRef s (TokenBuffer s) -> ST s ()
tokenTagStartInit r = do
(c, i) <- tokenInit 5 r
U.write c (i + 0) tokenTagStartType
U.write c (i + 1) tokenTagStartSize
U.write c (i + 2) 0 -- name offset
U.write c (i + 3) 0 -- name length
U.write c (i + 4) 0 -- self closing flag
U.write c (i + 5) 0 -- attribute count
-- | Defines the size of the end tag token.
tokenTagEndSize :: Int
tokenTagEndSize = 4
-- | Adds a new end tag to the lexer.
tokenTagEndInit :: STRef s (TokenBuffer s) -> ST s ()
tokenTagEndInit r = do
(c, i) <- tokenInit 3 r
U.write c (i + 0) tokenTagEndType
U.write c (i + 1) tokenTagEndSize
U.write c (i + 2) 0 -- name offset
U.write c (i + 3) 0 -- name length
-- | Adds a new start tag to the lexer.
tokenTagStartSetSelfClosing :: STRef s (TokenBuffer s) -> ST s ()
tokenTagStartSetSelfClosing r = do
TokenBuffer{..} <- readSTRef r
i <- U.read tbCntl 2
U.write tbCntl (i + 4) 1
-- | Appends a character to a tag name if the token is a tag.
tokenTagNameAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenTagNameAppend = stringAppendTail 2
-- | Starts a new attribute
tokenAttrInit :: STRef s (TokenBuffer s) -> ST s ()
tokenAttrInit r = do
t@TokenBuffer{..} <- readSTRef r
tailPtr <- U.read tbCntl 2
m <- U.read tbCntl (tailPtr + 1)
n <- U.read tbCntl (tailPtr + 5)
let i = attrSize * n + attrStart + tailPtr
c <- ensureCntl (i + 3) t r
U.write c (i + 0) 0 -- attribute name offset
U.write c (i + 1) 0 -- attribute name length
U.write c (i + 2) 0 -- attribute value offset
U.write c (i + 3) 0 -- attribute value length
U.write c (tailPtr + 1) (m + 4) -- add attr to size of token
U.write c (tailPtr + 5) (n + 1)
-- | Appends a character to the latest attribute name.
tokenAttrNameAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenAttrNameAppend x r = do
t@TokenBuffer{..} <- readSTRef r
tailPtr <- U.read tbCntl 2
n <- U.read tbCntl (tailPtr + 5)
let i = attrSize * (n - 1) + attrStart + tailPtr
stringAppend (i + 0) x t r
-- | Appends a character to the latest attribute value.
tokenAttrValAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenAttrValAppend x r = do
t@TokenBuffer{..} <- readSTRef r
tailPtr <- U.read tbCntl 2
n <- U.read tbCntl (tailPtr + 5)
let i = attrSize * (n - 1) + attrStart + tailPtr
stringAppend (i + 2) x t r
-- | Checks for duplicate attribute names.
-- Refer to section 12.2.5.33 for details.
tokenAttrNamePrune :: Int -> STRef s (TokenBuffer s) -> ST s Bool
tokenAttrNamePrune x r = do
t@TokenBuffer{..} <- readSTRef r
a <- U.read tbCntl (x + offsetType)
if a /= tokenTagStartType
then pure False
else do
attrCount <- U.read tbCntl (x + 5)
u <- forM [1 .. attrCount] $ \j -> do
let i = (j - 1) * attrSize + attrStart + x
no0 <- U.read tbCntl (i + 0)
nc0 <- U.read tbCntl (i + 1)
match <- forM [1 .. j - 1] $ \k -> do
let m = (k - 1) * attrSize + attrStart + x
no1 <- U.read tbCntl (m + 0)
nc1 <- U.read tbCntl (m + 1)
s0 <- tokenSlice no0 nc0 r
s1 <- tokenSlice no1 nc1 r
pure $ nc0 == nc1 && s0 == s1
if or match
then do
U.write tbCntl (i + 0) 0
U.write tbCntl (i + 1) 0
pure True
else
pure False
pure $ or u
-- | Defines the size of the comment token.
tokenCommentSize :: Int
tokenCommentSize = 4
-- | Adds a new comment token to the lexer.
tokenCommentInit :: STRef s (TokenBuffer s) -> ST s ()
tokenCommentInit r = do
(c, i) <- tokenInit 3 r
U.write c (i + 0) tokenCommentType
U.write c (i + 1) tokenCommentSize
U.write c (i + 2) 0 -- string offset
U.write c (i + 3) 0 -- string length
-- | Appends a character to the current comment token.
tokenCommentAppend :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenCommentAppend = stringAppendTail 2
-- | Defines the size of the char token.
tokenCharSize :: Int
tokenCharSize = 3
-- | Initializes a text token.
tokenCharInit :: Word8 -> STRef s (TokenBuffer s) -> ST s ()
tokenCharInit x r = do
t <- readSTRef r
(c, i) <- tokenInit 2 r
U.write c (i + 0) tokenCharType
U.write c (i + 1) tokenCharSize
U.write c (i + 2) (fromIntegral x)
-- | Defines the size of the eof token.
tokenEOFSize :: Int
tokenEOFSize = 2
-- | Initializes an EOF token.
tokenEOFInit :: STRef s (TokenBuffer s) -> ST s ()
tokenEOFInit r = do
(c, i) <- tokenInit 2 r
U.write c (i + 0) tokenEOFType
U.write c (i + 1) tokenEOFSize
-- | Defines the token offset for the type field.
offsetType :: Int
offsetType = 0
-- | Defines the token offset for the size field.
offsetSize :: Int
offsetSize = 1
-- | Gets the type of a token.
tokenType :: Int -> STRef s (TokenBuffer s) -> ST s Int
tokenType x r = do
TokenBuffer{..} <- readSTRef r
U.read tbCntl (x + offsetType)
-- | Gets the size of a token.
tokenSize :: Int -> STRef s (TokenBuffer s) -> ST s Int
tokenSize x r = do
TokenBuffer{..} <- readSTRef r
U.read tbCntl (x + offsetSize)
-- | Unpacks a token at the specified index.
tokenPack :: Int -> STRef s (TokenBuffer s) -> ST s Token
tokenPack x r = do
TokenBuffer{..} <- readSTRef r
t <- U.read tbCntl x
n <- U.read tbCntl 0
s <- bufferString tbData n
if | t == tokenDoctypeType -> packDoctype x tbCntl s
| t == tokenTagStartType -> packTagStart x tbCntl s
| t == tokenTagEndType -> packTagEnd x tbCntl s
| t == tokenCommentType -> packComment x tbCntl s
| t == tokenCharType -> packChar x tbCntl
| t == tokenEOFType -> pure TEOF
| otherwise -> pure TEOF
-- | Unpacks a doctype token.
packDoctype :: Int -> MVector s Int -> BS -> ST s Token
packDoctype x tbCntl bs = do
nameOffset <- U.read tbCntl (x + 2)
nameLen <- U.read tbCntl (x + 3)
quirks <- U.read tbCntl (x + 4)
pubExists <- U.read tbCntl (x + 5)
pubOffset <- U.read tbCntl (x + 6)
pubLen <- U.read tbCntl (x + 7)
sysExists <- U.read tbCntl (x + 8)
sysOffset <- U.read tbCntl (x + 9)
sysLen <- U.read tbCntl (x + 10)
pure $ TDoctype
(bsPart nameOffset nameLen bs)
(quirks == 1)
(if pubExists == 1 then Just (bsPart pubOffset pubLen bs) else Nothing)
(if sysExists == 1 then Just (bsPart sysOffset sysLen bs) else Nothing)
-- | Unpacks an start tag token.
packTagStart :: Int -> MVector s Int -> BS -> ST s Token
packTagStart x tbCntl bs = do
nameOffset <- U.read tbCntl (x + 2)
nameLen <- U.read tbCntl (x + 3)
selfClose <- U.read tbCntl (x + 4)
attrCount <- U.read tbCntl (x + 5)
attr <- forM [1 .. attrCount] $ \j -> do
let i = (j - 1) * attrSize + attrStart + x
no <- U.read tbCntl (i + 0)
nc <- U.read tbCntl (i + 1)
ao <- U.read tbCntl (i + 2)
ac <- U.read tbCntl (i + 3)
pure $
if no > 0
then Just $ TAttr
(bsPart no nc bs)
(bsPart ao ac bs)
HTMLAttrNamespaceNone
else Nothing
pure $ TStart
(bsPart nameOffset nameLen bs)
(selfClose == 1)
(catMaybes attr)
-- | Unpacks an end tag token.
packTagEnd :: Int -> MVector s Int -> BS -> ST s Token
packTagEnd x tbCntl bs = do
nameOffset <- U.read tbCntl (x + 2)
nameLen <- U.read tbCntl (x + 3)
pure $ TEnd $ bsPart nameOffset nameLen bs
-- | Unpacks a comment token.
packComment :: Int -> MVector s Int -> BS -> ST s Token
packComment x tbCntl bs = do
o <- U.read tbCntl (x + 2)
n <- U.read tbCntl (x + 3)
pure $ TComment $ bsPart o n bs
-- | Unpacks a character token.
packChar :: Int -> MVector s Int -> ST s Token
packChar x tbCntl = do
c <- U.read tbCntl (x + 2)
pure $ TChar $ fromIntegral c
-- | Appends a character to a string at an offset in the current token.
stringAppendTail :: Int -> Word8 -> STRef s (TokenBuffer s) -> ST s ()
stringAppendTail offset word r = do
t@TokenBuffer{..} <- readSTRef r
tailPtr <- U.read tbCntl 2
stringAppend (tailPtr + offset) word t r
{-# INLINE stringAppendTail #-}
-- | Appends a character to a string at an offset.
stringAppend :: Int -> Word8 -> TokenBuffer s -> STRef s (TokenBuffer s) -> ST s ()
stringAppend x word t@TokenBuffer{..} r = do
let y = x + 1
i <- U.read tbCntl 0
o <- U.read tbCntl x
n <- U.read tbCntl y
d <- ensureData i t r
U.write d i word
U.write tbCntl 0 (i + 1)
U.write tbCntl y (n + 1)
when (o == 0) $
U.write tbCntl x i
{-# INLINE stringAppend #-}
-- | Ensures that the control buffer has enough space and grows it if needed.
ensureCntl :: Int -> TokenBuffer s -> STRef s (TokenBuffer s) -> ST s (MVector s Int)
ensureCntl x TokenBuffer{..} r
| x < U.length tbCntl =
pure tbCntl
| otherwise = do
v <- U.grow tbCntl $ U.length tbCntl
writeSTRef r $ TokenBuffer v tbData
pure v
{-# INLINE ensureCntl #-}
-- | Ensures that the data buffer has enough space and grows it if needed.
ensureData :: Int -> TokenBuffer s -> STRef s (TokenBuffer s) -> ST s (MVector s Word8)
ensureData x TokenBuffer{..} r
| x < U.length tbData =
pure tbData
| otherwise = do
v <- U.grow tbData $ U.length tbData
writeSTRef r $ TokenBuffer tbCntl v
pure v
{-# INLINE ensureData #-}