packages feed

yamlet-1.0.0.0: src/Yamlet/Internal/Chars.hs

{-# LANGUAGE PatternSynonyms #-}
{-# OPTIONS_HADDOCK not-home #-}

-- | The bytes of UTF-8 encoded YAML and the classes of characters that the
-- parser, the renderer and the error messages share.
--
-- This module is intended for internal use only, and may change without warning
-- in subsequent releases.
module Yamlet.Internal.Chars
  ( -- * Characters
    pattern TAB
  , pattern LF
  , pattern CR
  , pattern SPACE
  , pattern EXCL
  , pattern DQUOTE
  , pattern HASH
  , pattern PERCENT
  , pattern AMP
  , pattern SQUOTE
  , pattern STAR
  , pattern PLUS
  , pattern COMMA
  , pattern MINUS
  , pattern DOT
  , pattern DIGIT_0
  , pattern DIGIT_1
  , pattern DIGIT_9
  , pattern COLON
  , pattern LESS
  , pattern GREATER
  , pattern QUESTION
  , pattern AT
  , pattern UPPER_A
  , pattern UPPER_F
  , pattern UPPER_Z
  , pattern LBRACKET
  , pattern BACKSLASH
  , pattern RBRACKET
  , pattern GRAVE
  , pattern LOWER_A
  , pattern LOWER_F
  , pattern LOWER_U
  , pattern LOWER_Z
  , pattern LBRACE
  , pattern PIPE
  , pattern RBRACE
  , pattern DEL
  , isWhite
  , isBreak
  , isAsciiByte
  , asciiChar
  , isCharStart
  , isNsChar
  , isFlowIndicator
  , isIndicator
  , isDecDigit
  , isHexDigit'
  , hexValue
  , isWordChar
  , isUriChar
  , isTagChar
  , isAnchorChar
  , bomLength
  , isBomIn
  , skipBomsIn
  ) where

import Data.Bits
import Data.Char
import Data.Text.Array qualified as A
import Data.Word

pattern
  TAB
  , LF
  , CR
  , SPACE
  , EXCL
  , DQUOTE
  , HASH
  , PERCENT
  , AMP
  , SQUOTE
  , STAR
  , PLUS
  , COMMA
  , MINUS
  , DOT
  , DIGIT_0
  , DIGIT_1
  , DIGIT_9
  , COLON
  , LESS
  , GREATER
  , QUESTION
  , AT
  , UPPER_A
  , UPPER_F
  , UPPER_Z
  , LBRACKET
  , BACKSLASH
  , RBRACKET
  , GRAVE
  , LOWER_A
  , LOWER_F
  , LOWER_U
  , LOWER_Z
  , LBRACE
  , PIPE
  , RBRACE
  , DEL
    :: Word8
pattern TAB = 0x09
pattern LF = 0x0A
pattern CR = 0x0D
pattern SPACE = 0x20
pattern EXCL = 0x21
pattern DQUOTE = 0x22
pattern HASH = 0x23
pattern PERCENT = 0x25
pattern AMP = 0x26
pattern SQUOTE = 0x27
pattern STAR = 0x2A
pattern PLUS = 0x2B
pattern COMMA = 0x2C
pattern MINUS = 0x2D
pattern DOT = 0x2E
pattern DIGIT_0 = 0x30
pattern DIGIT_1 = 0x31
pattern DIGIT_9 = 0x39
pattern COLON = 0x3A
pattern LESS = 0x3C
pattern GREATER = 0x3E
pattern QUESTION = 0x3F
pattern AT = 0x40
pattern UPPER_A = 0x41
pattern UPPER_F = 0x46
pattern UPPER_Z = 0x5A
pattern LBRACKET = 0x5B
pattern BACKSLASH = 0x5C
pattern RBRACKET = 0x5D
pattern GRAVE = 0x60
pattern LOWER_A = 0x61
pattern LOWER_F = 0x66
pattern LOWER_U = 0x75
pattern LOWER_Z = 0x7A
pattern LBRACE = 0x7B
pattern PIPE = 0x7C
pattern RBRACE = 0x7D
pattern DEL = 0x7F

isWhite :: Word8 -> Bool
isWhite w = w == SPACE || w == TAB

isBreak :: Word8 -> Bool
isBreak w = w == LF || w == CR

isAsciiByte :: Word8 -> Bool
isAsciiByte w = w < 0x80

-- | A predicate on bytes for a character, e.g. 'isFlowIndicator' for the
-- emitter. A character beyond ASCII does not satisfy it.
asciiChar :: (Word8 -> Bool) -> Char -> Bool
asciiChar p c = isAscii c && p (fromIntegral (ord c))

-- | The byte starts a character in UTF-8, i.e. it is not a continuation byte.
isCharStart :: Word8 -> Bool
isCharStart w = isAsciiByte w || w >= 0xC0

-- | ns-char. Every byte of a multibyte character counts, because the input
-- contains printable characters only, except in quoted scalars, which the
-- parser checks after it parses the stream.
isNsChar :: Word8 -> Bool
isNsChar w = w > SPACE && w /= DEL

isFlowIndicator :: Word8 -> Bool
isFlowIndicator w =
  w == COMMA
    || w == LBRACKET
    || w == RBRACKET
    || w == LBRACE
    || w == RBRACE

isIndicator :: Word8 -> Bool
isIndicator w = isAsciiByte w && testBit indicators (fromIntegral w)
  where
    indicators :: Integer
    indicators = foldr @[] (\c acc -> setBit acc (ord c)) 0 "-?:,[]{}#&*!|>'\"%@`"

isDecDigit :: Word8 -> Bool
isDecDigit w = w >= DIGIT_0 && w <= DIGIT_9

isHexDigit' :: Word8 -> Bool
isHexDigit' w =
  isDecDigit w || (w >= UPPER_A && w <= UPPER_F) || (w >= LOWER_A && w <= LOWER_F)

hexValue :: Word8 -> Int
hexValue w
  | w <= DIGIT_9 = fromIntegral (w - DIGIT_0)
  | w <= UPPER_F = fromIntegral (w - UPPER_A) + 10
  | otherwise = fromIntegral (w - LOWER_A) + 10

isWordChar :: Word8 -> Bool
isWordChar w =
  isDecDigit w
    || (w >= UPPER_A && w <= UPPER_Z)
    || (w >= LOWER_A && w <= LOWER_Z)
    || w == MINUS

-- | ns-uri-char without the escaped characters.
isUriChar :: Word8 -> Bool
isUriChar w = isWordChar w || w `elem` extra
  where
    extra :: [Word8]
    extra = map (fromIntegral . ord) "#;/?:@&=+$,_.!~*'()[]"

-- | ns-tag-char without the escaped characters.
isTagChar :: Word8 -> Bool
isTagChar w = isUriChar w && w /= EXCL && not (isFlowIndicator w)

isAnchorChar :: Word8 -> Bool
isAnchorChar w = isNsChar w && not (isFlowIndicator w)

-- | The number of bytes of a byte order mark, U+FEFF in UTF-8.
bomLength :: Int
bomLength = 3

-- | A byte order mark at the index of the array, before the end index.
isBomIn :: A.Array -> Int -> Int -> Bool
isBomIn arr end i =
  i + bomLength <= end
    && A.unsafeIndex arr i == 0xEF
    && A.unsafeIndex arr (i + 1) == 0xBB
    && A.unsafeIndex arr (i + 2) == 0xBF

-- | The index after the byte order marks at the index of the array, before
-- the end index.
skipBomsIn :: A.Array -> Int -> Int -> Int
skipBomsIn arr end i = if isBomIn arr end i then skipBomsIn arr end (i + bomLength) else i