packages feed

cgrep-8.0.0: src/CGrep/Parser/Line.hs

--
-- Copyright (c) 2013-2023 Nicola Bonelli <nicola@larthia.com>
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
--

module CGrep.Parser.Line (
      getLineOffsets
    , getAllLineOffsets
    , getLineByOffset
    , lowerBound
    ) where

import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy.Char8 as LC

import qualified Data.Vector.Unboxed as UV
import Data.Vector.Unboxed ((!))
import qualified Data.ByteString.Unsafe as BU
import Data.ByteString.Internal (c2w)
import CGrep.Types ( Offset, Text8, LText8 )
import Data.Int ( Int64 )


-- Returns a vector of offsets for a given character in a ByteString, up to the given maximum offset.
charOffsets :: Char -> Int64 -> C.ByteString -> UV.Vector Int64
charOffsets c maxOff bs = UV.unfoldrN (fromIntegral maxOff) (findOffsets bs maxOff) 0
  where findOffsets :: C.ByteString -> Int64 -> Int64 -> Maybe (Int64, Int64)
        findOffsets bs' maxOff' i
          | i >= maxOff' = Nothing
          | BU.unsafeIndex bs' (fromIntegral i) == c2w c = Just (fromIntegral i, i + 1)
          | otherwise = findOffsets bs' maxOff' (i + 1)


getLineOffsets :: Int64 -> Text8 -> UV.Vector Offset
getLineOffsets maxOff text =
    let idx = nlOffsets (fromIntegral maxOff) text
    in if UV.null idx
        then idx
        else if UV.last idx == fromIntegral (C.length text -1)
            then UV.init idx
            else idx

{-# INLINE nlOffsets #-}
nlOffsets :: Int -> Text8 -> UV.Vector Int64
nlOffsets maxOff' bs' = UV.unfoldrN maxOff' (findOffsets maxOff' bs') (-1)

findOffsets :: Int -> Text8 -> Int -> Maybe (Int64, Int)
findOffsets max ts !i
    | i == -1 = Just (0, 0)
    | i >= max = Nothing
    | BU.unsafeIndex ts (fromIntegral i) == c2w '\n' = Just (fromIntegral i + 1, i + 1)
    | otherwise = findOffsets max ts (i + 1)


getAllLineOffsets :: Text8 -> UV.Vector Offset
getAllLineOffsets ts = getLineOffsets (fromIntegral $ C.length ts) ts
{-# INLINE getAllLineOffsets #-}


lowerBound :: UV.Vector Int64 -> Int64 -> Int64
lowerBound vec v = lowerBoundGo vec v 0 (UV.length vec-1)

lowerBoundGo :: UV.Vector Int64 -> Int64 -> Int -> Int -> Int64
lowerBoundGo vec v !left !right
    | left > right = if right >= 0 then vec `UV.unsafeIndex` right else -1
    | otherwise = case v `compare` midValue of
          LT -> lowerBoundGo vec v left (mid - 1)
          EQ -> midValue
          _  -> lowerBoundGo vec v (mid + 1) right
      where
        mid = (left + right) `div` 2
        midValue = vec `UV.unsafeIndex` mid


getLineByOffset :: Offset -> Text8 -> UV.Vector Int64 -> (# Text8, Offset #)
getLineByOffset off text vec = (# (head . C.lines) (C.drop (fromIntegral lb) text), lb #)
        where lb = lowerBound vec off
{-# INLINE getLineByOffset #-}