packages feed

smirk-0.1.0.0: src/Smirk/Engine/Sequence.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}

{- |
Module      : Smirk.Engine.Sequence
Description : Sequence polymorphism abstraction for zero-copy and generic regex matching
Copyright   : (c) Nadia Yvette Chambers, 2026
License     : Apache-2.0

Provides the 'RegexTarget' typeclass, establishing a uniform sequence abstraction
across multiple standard Haskell container representations without boxing or
intermediate conversions.

=== Supported Sequence Targets

* 'Data.Text.Text': Standard UTF-8 strict text with $O(1)$ length/slicing.
* 'Data.ByteString.ByteString': Raw ASCII/UTF-8 byte sequences with zero-copy slicing.
* '[Char]': Standard Haskell linked lists of characters (lazy string processing).
* 'Data.Sequence.Seq Char': Finger-tree based sequences with logarithmic indexing.
* 'Data.Vector.Vector Char': Contiguous boxed/unboxed arrays of characters.
-}
module Smirk.Engine.Sequence
  ( RegexTarget (..)
  ) where

import qualified Data.ByteString as BS
import Data.Char (chr)
import qualified Data.Sequence as Seq
import Data.Sequence (Seq)
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.Text.Encoding as TE
import qualified Data.Vector as V
import Data.Vector (Vector)

-- | Abstract sequence interface required by the regex virtual machine.
-- Allows the VM execution loop to index characters, measure length, and extract
-- matched sub-slices without coupling to a concrete string representation.
class RegexTarget s where
  -- | Total length (number of characters) in the sequence.
  seqLength   :: s -> Int

  -- | Safely retrieve the character at the specified 0-based offset.
  -- Returns 'Nothing' if the index is out of bounds.
  seqIndex    :: s -> Int -> Maybe Char

  -- | Extract a contiguous sub-slice given an offset and length.
  seqSlice    :: Int -> Int -> s -> s

  -- | Convert the sequence (or a matched slice) to strict 'Text' for formatting.
  seqToText   :: s -> Text

  -- | Construct a sequence from strict 'Text'.
  seqFromText :: Text -> s

-- | Instance for strict 'Text' (standard high-performance string representation).
instance RegexTarget Text where
  seqLength = T.length
  {-# INLINE seqLength #-}

  seqIndex t i
    | i >= 0 && i < T.length t = Just (T.index t i)
    | otherwise                = Nothing
  {-# INLINE seqIndex #-}

  seqSlice off len t = T.take len (T.drop off t)
  {-# INLINE seqSlice #-}

  seqToText = id
  {-# INLINE seqToText #-}

  seqFromText = id
  {-# INLINE seqFromText #-}

-- | Instance for strict 'BS.ByteString' (interprets bytes as ASCII / Latin-1 codepoints).
instance RegexTarget BS.ByteString where
  seqLength = BS.length
  {-# INLINE seqLength #-}

  seqIndex bs i
    | i >= 0 && i < BS.length bs = Just (chr (fromIntegral (BS.index bs i)))
    | otherwise                  = Nothing
  {-# INLINE seqIndex #-}

  seqSlice off len bs = BS.take len (BS.drop off bs)
  {-# INLINE seqSlice #-}

  seqToText = TE.decodeUtf8Lenient
  {-# INLINE seqToText #-}

  seqFromText = TE.encodeUtf8
  {-# INLINE seqFromText #-}

-- | Instance for standard linked list of characters ('[Char]' / 'String').
instance RegexTarget [Char] where
  seqLength = length
  {-# INLINE seqLength #-}

  seqIndex xs i
    | i >= 0 = case drop i xs of
        (c:_) -> Just c
        []    -> Nothing
    | otherwise = Nothing
  {-# INLINE seqIndex #-}

  seqSlice off len xs = take len (drop off xs)
  {-# INLINE seqSlice #-}

  seqToText = T.pack
  {-# INLINE seqToText #-}

  seqFromText = T.unpack
  {-# INLINE seqFromText #-}

-- | Instance for finger-tree based 'Seq' collections.
instance RegexTarget (Seq Char) where
  seqLength = Seq.length
  {-# INLINE seqLength #-}

  seqIndex sq i = Seq.lookup i sq
  {-# INLINE seqIndex #-}

  seqSlice off len sq = Seq.take len (Seq.drop off sq)
  {-# INLINE seqSlice #-}

  seqToText sq = T.pack (foldr (:) [] sq)
  {-# INLINE seqToText #-}

  seqFromText t = Seq.fromList (T.unpack t)
  {-# INLINE seqFromText #-}

-- | Instance for contiguous 'Vector' arrays.
instance RegexTarget (Vector Char) where
  seqLength = V.length
  {-# INLINE seqLength #-}

  seqIndex v i = v V.!? i
  {-# INLINE seqIndex #-}

  seqSlice off len v = V.slice off (min len (V.length v - off)) v
  {-# INLINE seqSlice #-}

  seqToText v = T.pack (V.toList v)
  {-# INLINE seqToText #-}

  seqFromText t = V.fromList (T.unpack t)
  {-# INLINE seqFromText #-}