{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}

{- | Fast, non-backtracking field parsers for CSV ingest. Each is a pure
function over a @(buf, start, end)@ byte slice, bit-exact with the reference
parsers in "DataFrame.Internal.Parsing". Unboxed @#@ variants avoid boxing.
-}
module DataFrame.Internal.Parsing.Fast (
    -- * Int fields
    parseIntField,
    parseIntFieldSlice,
    parseIntField#,

    -- * Double fields
    parseDoubleField,
    parseDoubleFieldSlice,
    parseDoubleField#,

    -- * Bool fields
    parseBoolField,
    parseBoolFieldSlice,
    parseBoolField#,

    -- * Date fields (default @%Y-%m-%d@ format)
    parseDateField,
    parseDateFieldSlice,

    -- * Missing-token test
    isMissingField,
    isMissingFieldSlice,
    isMissingFieldIn,
) where

import qualified Data.ByteString as BS

import Data.Time (Day)
import GHC.Exts (Double (..), Int (..))

import DataFrame.Internal.Parsing.Fast.Double (parseDoubleField#)
import DataFrame.Internal.Parsing.Fast.Int (parseIntField#)
import DataFrame.Internal.Parsing.Fast.Token (
    isMissingFieldIn,
    isMissingFieldSlice,
    parseBoolField#,
    parseDateFieldSlice,
 )

{- | Strip-tolerant @Int@ parse of a whole field; rejects overflow,
matching @readByteStringInt@ exactly.
-}
parseIntField :: BS.ByteString -> Maybe Int
parseIntField :: ByteString -> Maybe Int
parseIntField ByteString
bs = ByteString -> Int -> Int -> Maybe Int
parseIntFieldSlice ByteString
bs Int
0 (ByteString -> Int
BS.length ByteString
bs)
{-# INLINE parseIntField #-}

parseIntFieldSlice :: BS.ByteString -> Int -> Int -> Maybe Int
parseIntFieldSlice :: ByteString -> Int -> Int -> Maybe Int
parseIntFieldSlice ByteString
bs Int
start Int
end =
    case ByteString -> Int -> Int -> (# Int#, Int# #)
parseIntField# ByteString
bs Int
start Int
end of
        (# Int#
0#, Int#
_ #) -> Maybe Int
forall a. Maybe a
Nothing
        (# Int#
_, Int#
n #) -> Int -> Maybe Int
forall a. a -> Maybe a
Just (Int# -> Int
I# Int#
n)
{-# INLINE parseIntFieldSlice #-}

{- | Strip-tolerant @Double@ parse of a whole field; bit-exact with
@readByteStringDouble@ (falls back to it outside the fast window).
-}
parseDoubleField :: BS.ByteString -> Maybe Double
parseDoubleField :: ByteString -> Maybe Double
parseDoubleField ByteString
bs = ByteString -> Int -> Int -> Maybe Double
parseDoubleFieldSlice ByteString
bs Int
0 (ByteString -> Int
BS.length ByteString
bs)
{-# INLINE parseDoubleField #-}

parseDoubleFieldSlice :: BS.ByteString -> Int -> Int -> Maybe Double
parseDoubleFieldSlice :: ByteString -> Int -> Int -> Maybe Double
parseDoubleFieldSlice ByteString
bs Int
start Int
end =
    case ByteString -> Int -> Int -> (# Int#, Double# #)
parseDoubleField# ByteString
bs Int
start Int
end of
        (# Int#
0#, Double#
_ #) -> Maybe Double
forall a. Maybe a
Nothing
        (# Int#
_, Double#
d #) -> Double -> Maybe Double
forall a. a -> Maybe a
Just (Double# -> Double
D# Double#
d)
{-# INLINE parseDoubleFieldSlice #-}

-- | Exact-match Bool parse (@True|true|TRUE|False|false|FALSE@, no strip).
parseBoolField :: BS.ByteString -> Maybe Bool
parseBoolField :: ByteString -> Maybe Bool
parseBoolField ByteString
bs = ByteString -> Int -> Int -> Maybe Bool
parseBoolFieldSlice ByteString
bs Int
0 (ByteString -> Int
BS.length ByteString
bs)
{-# INLINE parseBoolField #-}

parseBoolFieldSlice :: BS.ByteString -> Int -> Int -> Maybe Bool
parseBoolFieldSlice :: ByteString -> Int -> Int -> Maybe Bool
parseBoolFieldSlice ByteString
bs Int
start Int
end =
    case ByteString -> Int -> Int -> (# Int#, Int# #)
parseBoolField# ByteString
bs Int
start Int
end of
        (# Int#
0#, Int#
_ #) -> Maybe Bool
forall a. Maybe a
Nothing
        (# Int#
_, Int#
b #) -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just (Int# -> Int
I# Int#
b Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
0)
{-# INLINE parseBoolFieldSlice #-}

{- | @%Y-%m-%d@ date parse: byte-level fast path for the padded
10-byte shape, 'Data.Time.parseTimeM' fallback for everything else.
-}
parseDateField :: BS.ByteString -> Maybe Day
parseDateField :: ByteString -> Maybe Day
parseDateField ByteString
bs = ByteString -> Int -> Int -> Maybe Day
parseDateFieldSlice ByteString
bs Int
0 (ByteString -> Int
BS.length ByteString
bs)
{-# INLINE parseDateField #-}

{- | Byte-level test against the canonical missing-token list
(@Nothing NULL \"\" \" \" nan null N\/A NaN NAN NA@), no Text decode.
-}
isMissingField :: BS.ByteString -> Bool
isMissingField :: ByteString -> Bool
isMissingField ByteString
bs = ByteString -> Int -> Int -> Bool
isMissingFieldSlice ByteString
bs Int
0 (ByteString -> Int
BS.length ByteString
bs)
{-# INLINE isMissingField #-}