packages feed

wireform-core-0.2.0.0: src/Wireform/Parser/Position.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE UnboxedSums #-}
{-# LANGUAGE UnboxedTuples #-}

module Wireform.Parser.Position (
  Pos (..),
  Span (..),
  subPos,
  getPos,
  withSpan,
  byteStringOf,
  spanToByteString,
  inSpan,
) where

import Data.Bits ((.&.))
import Data.ByteString (ByteString)
import Data.ByteString.Internal qualified as BSI
import Data.Word (Word64)
import Foreign.Marshal.Utils (copyBytes)
import Foreign.Ptr (plusPtr)
import GHC.Exts
import GHC.ForeignPtr (ForeignPtr (..))
import System.IO.Unsafe (unsafeDupablePerformIO)
import Wireform.Parser.Internal


newtype Pos = Pos {unPos :: Word64}
  deriving stock (Eq, Ord, Show)
  deriving newtype (Num)


data Span = Span !Pos !Pos
  deriving stock (Eq, Show)


subPos :: Pos -> Pos -> Int
subPos (Pos a) (Pos b) = fromIntegral (a - b)


getPos :: Parser m e Pos
getPos = Parser \env _ s st ->
  case curToPos env s st of
    (# st', pos #) -> (# st', OK# (Pos pos) s #)
{-# INLINE getPos #-}


withSpan :: Parser m e a -> (a -> Span -> Parser m e b) -> Parser m e b
withSpan (Parser p) f = Parser \env eob s st ->
  case p env eob s st of
    (# st', OK# a s' #) ->
      case curToPos env s st' of
        (# st'', startPos #) ->
          case curToPos env s' st'' of
            (# st''', endPos #) ->
              let !sp = Span (Pos startPos) (Pos endPos)
              in runParser# (f a sp) env eob s' st'''
    (# st', x #) -> (# st', unsafeCoerce# x #)
{-# INLINE withSpan #-}


byteStringOf :: Parser m e a -> Parser m e ByteString
byteStringOf (Parser p) = Parser \env eob s st ->
  case p env eob s st of
    (# st', OK# _ s' #) ->
      let !len = I# (minusAddr# s' s)
          !bs = BSI.BS (ForeignPtr s (peBackingFp env)) len
      in (# st', OK# bs s' #)
    (# st', x #) -> (# st', unsafeCoerce# x #)
{-# INLINE byteStringOf #-}


spanToByteString :: Span -> Parser m e ByteString
spanToByteString (Span (Pos start) (Pos end)) = Parser \env _ s st ->
  let !len = fromIntegral (end - start)
      base = peBaseAddr env
      mask = peMask env
      off = fromIntegral start .&. mask
      !(Ptr ptr) = base `plusPtr` off
      !bs = unsafeDupablePerformIO (BSI.create len \dst -> copyBytes dst (Ptr ptr) len)
  in (# st, OK# bs s #)


{- | Run a parser within an explicitly bounded byte window.
Temporarily restricts @eob@ to the span's end position, then
restores it on completion.
-}
inSpan :: Span -> Parser m e a -> Parser m e a
inSpan (Span (Pos start) (Pos end)) (Parser p) = Parser \env _ s st ->
  let base = peBaseAddr env
      mask = peMask env
      !endOff = fromIntegral end .&. mask
      !(Ptr spanEnd) = base `plusPtr` endOff
      !startOff = fromIntegral start .&. mask
      !(Ptr spanStart) = base `plusPtr` startOff
  in case p env spanEnd spanStart st of
       (# st', OK# a _ #) -> (# st', OK# a s #)
       (# st', x #) -> (# st', unsafeCoerce# x #)
{-# INLINE inSpan #-}