packages feed

yamlet-1.0.0.0: src/Yamlet/Internal/Parser/Scan.hs

{-# OPTIONS_HADDOCK not-home #-}

-- | Scans of the input around an index, outside of the parser monad.
--
-- This module is intended for internal use only, and may change without warning
-- in subsequent releases.
module Yamlet.Internal.Parser.Scan
  ( isBom
  , skipBoms
  , skipSpaces
  , skipWhites
  , breakEnd
  , isStartOfLine
  , lineStartAt
  , previousLineStart
  , lineEndAt
  , nextLineStart
  , contentLineAbove
  , markerLength
  , isMarker
  , isStartMarker
  , isEndMarker
  , startsPrefix
  , bomBeforeContent
  , canEndFlowNode
  , fitsKey
  ) where

import Yamlet.Internal.Chars
import Yamlet.Internal.Parser.Monad
import Yamlet.Internal.Utils

isBom :: Env -> Int -> Bool
isBom e = isBomIn e.array e.end

skipBoms :: Env -> Int -> Int
skipBoms e = skipBomsIn e.array e.end

skipSpaces :: Env -> Int -> Int
skipSpaces e i = if byteAt e i == SPACE then skipSpaces e (i + 1) else i

skipWhites :: Env -> Int -> Int
skipWhites e i = if isWhite (byteAt e i) then skipWhites e (i + 1) else i

-- | The index after the line break at the index.
breakEnd :: Env -> Int -> Int
breakEnd e i
  | byteAt e i == CR && byteAt e (i + 1) == LF = i + 2
  | otherwise = i + 1

isStartOfLine :: Env -> Int -> Bool
isStartOfLine e i
  | i <= e.base = True
  | isBreak (byteBefore e i) = True
  | i - bomLength >= e.base && isBom e (i - bomLength) = isStartOfLine e (i - bomLength)
  | otherwise = False

-- | The start of the line that contains the index.
lineStartAt :: Env -> Int -> Int
lineStartAt e i
  | i > e.base && not (isBreak (byteBefore e i)) = lineStartAt e (i - 1)
  | otherwise = i

-- | The start of the line above the line that starts at the index, which is
-- not the first line.
previousLineStart :: Env -> Int -> Int
previousLineStart e i = lineStartAt e (breakStart (i - 1))
  where
    -- The start of the line break that ends at the index, e.g. of CR LF.
    breakStart :: Int -> Int
    breakStart j
      | j > e.base && byteBefore e j == CR && byteAt e j == LF = j - 1
      | otherwise = j

-- | The index of the line break that ends the line of the index, or the end
-- of the input.
lineEndAt :: Env -> Int -> Int
lineEndAt e i
  | i < e.end && not (isBreak (byteAt e i)) = lineEndAt e (i + 1)
  | otherwise = i

-- | The start of the line below the line of the index, or the end of the
-- input.
nextLineStart :: Env -> Int -> Int
nextLineStart e i = let j = lineEndAt e i in if j < e.end then breakEnd e j else j

-- | The start of the closest line above the line that starts at the index
-- with content other than a comment. Byte order marks at the start of a line
-- do not count as content.
contentLineAbove :: Env -> Int -> Maybe Int
contentLineAbove e start
  | start <= e.base = Nothing
  | otherwise =
      let prev = previousLineStart e start
          b = byteAt e (skipWhites e (skipBoms e prev))
      in if isBreak b || b == HASH then contentLineAbove e prev else Just prev

-- | The number of characters of a @---@ or @...@ marker.
markerLength :: Int
markerLength = 3

-- | A @---@ or @...@ marker at the start of a line.
isMarker :: Env -> Int -> Bool
isMarker e i =
  let w = byteAt e i
      after = byteAt e (i + markerLength)
  in (w == MINUS || w == DOT)
       && all (\j -> byteAt e (i + j) == w) [1 .. markerLength - 1]
       && (after == 0 || isWhite after || isBreak after)
       && isStartOfLine e i

-- | A @---@ marker at the start of a line.
isStartMarker :: Env -> Int -> Bool
isStartMarker e i = isMarker e i && byteAt e i == MINUS

-- | A @...@ marker at the start of a line.
isEndMarker :: Env -> Int -> Bool
isEndMarker e i = isMarker e i && byteAt e i == DOT

-- | A byte order mark at the start of a line. Outside a quoted scalar, it
-- starts the prefix of the next document, so the content of a document ends
-- before it.
startsPrefix :: Env -> Int -> Bool
startsPrefix e i = isBom e i && isStartOfLine e i

-- | Byte order marks at the index before something other than a document
-- marker or a directive. Such marks at the start of a line inside a document
-- are an error.
bomBeforeContent :: Env -> Int -> Bool
bomBeforeContent e i =
  isBom e i && let j = skipBoms e i in not (isMarker e j || byteAt e j == PERCENT)

-- | A quoted scalar or a flow collection can end before the index: white
-- space, a comment, a line break, the end of the input, a colon or the end
-- of a flow entry follows.
canEndFlowNode :: Env -> Int -> Bool
canEndFlowNode e i =
  let j = skipWhites e i
      w = byteAt e j
  in w == 0
       || isBreak w
       || w == COLON
       || w == COMMA
       || w == RBRACKET
       || w == RBRACE
       || (w == HASH && j > i)

-- | The input between the indices fits in an implicit key.
fitsKey :: Env -> Int -> Int -> Bool
fitsKey e p q =
  q - p <= maxImplicitKeyLength
    || ( q - p <= maxImplicitKeyLength * maxCharBytes
           && countChars <= maxImplicitKeyLength
       )
  where
    -- The longest UTF-8 encoding of a character.
    maxCharBytes :: Int
    maxCharBytes = 4

    countChars :: Int
    countChars =
      length
        [() | x <- [p .. q - 1], isCharStart (byteAt e x)]