binrep-1.0.0: src/Binrep/Type/NullTerminated.hs
{- | C-style null-terminated data.
I mix string and bytestring terminology here, due to bad C influences. This
module is specifically interested in bytestrings and their encoding. String/text
encoding is handled in 'Binrep.Type.Text'.
-}
{-# LANGUAGE OverloadedStrings #-} -- for refined errors
module Binrep.Type.NullTerminated where
import Binrep
import FlatParse.Basic qualified as FP
import Rerefined.Predicate.Common
import Rerefined.Refine
import Data.ByteString qualified as B
import Data.Word ( Word8 )
-- | Null-terminated data. Arbitrary length terminated with a null byte.
-- Permits no null bytes inside the data.
data NullTerminate
instance Predicate NullTerminate where
type PredicateName d NullTerminate = "NullTerminate"
type NullTerminated = Refined NullTerminate
-- | Null-terminated data may not contain any null bytes.
instance Refine NullTerminate B.ByteString where
-- TODO is there a faster check we can conjure up here...?
validate p a = validateBool p (not (B.any (== 0x00) a)) $
"null byte not permitted in null-terminated data"
instance BLen a => BLen (NullTerminated a) where
blen ra = 1 + blen (unrefine ra)
{-# INLINE blen #-}
-- | Serialization of null-terminated data may be defined generally using the
-- data's underlying serializer.
instance Put a => Put (NullTerminated a) where
{-# INLINE put #-}
put a = put (unrefine a) <> put @Word8 0x00
-- | We may parse any null-terminated data using a special flatparse combinator.
--
-- The combinator doesn't permit distinguishing between the two possible
-- failures: either there was no next null, or the inner parser didn't consume
-- up to it.
instance Get a => Get (NullTerminated a) where
{-# INLINE get #-}
get = unsafeRefine <$> cut1 (FP.isolateToNextNull get) e
where e = [ "while isolating to next null"
, "either there was no next null in the input,"
, "or the inner parser didn't fully consume its input" ]
{-
I don't know how to do @[a]@. Either I nullterm each element, which is weird
because it's not required in all cases, or I don't, in which case the general
Put doesn't work. Nullterming every element feels weird anyway -- what about
[Word8]?
instance NullCheck a => NullCheck [a] where
{-# INLINE hasNoNulls #-}
hasNoNulls = all hasNoNulls
instance NullCheck Word8 where
{-# INLINE hasNoNulls #-}
hasNoNulls = \case 0x00 -> False
_ -> True
-}