packages feed

binrep-1.1.0: src/Binrep/Type/Magic.hs

{-# LANGUAGE UndecidableInstances #-} -- for tons of stuff
{-# LANGUAGE PatternSynonyms #-} -- TODO wip
{-# LANGUAGE OverloadedStrings #-} -- for easy error building

{- | Magic numbers (also just magic): short constant bytestrings usually
     found at the top of a file, often used as an early sanity check.

There are two main flavors of magics:

  * byte magics e.g. Zstandard: @28 B5 2F FD@
  * printable magics e.g. Ogg: @4F 67 67 53@ -> @OggS@ (in ASCII)

For byte magics, use type-level 'Natural' lists e.g. @'Magic' \@'[0xFF, 0x01]@
For printable (UTF-8) magics, use 'Symbol's e.g. @'Magic' \@"hello"@.
-}

module Binrep.Type.Magic
  ( Magic(Magic)
  , Magical(type MagicBytes)
  , type Length
  ) where

import Data.Type.Symbol.Utf8 ( type SymbolToUtf8 )

import GHC.TypeLits ( type Natural, type Symbol, type KnownNat, type (+) )

import GHC.Generics ( Generic )
import Data.Data ( Data )

import Binrep
import Bytezap.Struct.TypeLits.Bytes ( ReifyBytesW64(reifyBytesW64) )
import Bytezap.Parser.Struct.TypeLits.Bytes
  ( ParseReifyBytesW64(parseReifyBytesW64) )
import Bytezap.Parser.Struct qualified as BZ
import GHC.Exts ( Int(I#) )
import FlatParse.Basic qualified as FP
import Data.Text.Builder.Linear qualified as TBL

{- | A unit data type representing a "magic number" via a phantom type.

The phantom type unambiguously defines a bytestring at compile time. This
depends on the type's kind. See 'MagicBytes' for details.

This is defined using GADT syntax to permit labelling the phantom type kind as
/inferred/, which effectively means hidden (not available for visible type
applications). That kind is always evident from the type, so it's just nicer.
-}
data Magic a where Magic :: forall {k} (a :: k). Magic a
    deriving stock (Generic, Data, Show, Eq)

-- | The byte length of a magic is known at compile time.
instance IsCBLen (Magic a) where type CBLen (Magic a) = Length (MagicBytes a)

deriving via ViaCBLen (Magic a) instance
    KnownNat (Length (MagicBytes a)) => BLen (Magic a)

-- | Efficiently serialize a @'Magic' a@.
instance (bs ~ MagicBytes a, ReifyBytesW64 bs) => PutC (Magic a) where
    putC Magic = reifyBytesW64 @bs

deriving via (ViaPutC (Magic a)) instance
  (bs ~ MagicBytes a, ReifyBytesW64 bs, KnownNat (Length bs)) => Put (Magic a)

-- | Efficiently parse a @'Magic' a@. Serialization constraints are included as
--   we emit the expected bytestring in errors.
instance (bs ~ MagicBytes a, ParseReifyBytesW64 0 bs) => GetC (Magic a) where
    getC = BZ.ParserT $ \fpc base# os# st0 ->
        case BZ.runParserT# (parseReifyBytesW64 @0 @bs) fpc base# os# st0 of
          BZ.OK#   st1 ()             -> BZ.OK#   st1 Magic
          BZ.Err#  st1 (pos, bActual) -> BZ.Err#  st1 (parseError1
            ["TODO magic parse error: "<>TBL.fromDec bActual]
            (pos + I# os#))
          BZ.Fail# st1                -> BZ.Fail# st1 -- shouldn't occur

deriving via ViaGetC (Magic a) instance
  ( bs ~ MagicBytes a, ParseReifyBytesW64 0 bs
  , ReifyBytesW64 bs, KnownNat (Length bs)
  ) => Get (Magic a)

-- | Types which define a magic value.
class Magical (a :: k) where
    -- | How to turn the type into a list of bytes (stored using 'Natural's).
    type MagicBytes a :: [Natural]

-- | Type-level naturals go as-is. (Make sure you don't go over 255, though!)
instance Magical (bs :: [Natural]) where type MagicBytes bs = bs

-- | Type-level symbols are converted to UTF-8.
instance Magical (sym :: Symbol) where type MagicBytes sym = SymbolToUtf8 sym

-- | The length of a type-level list.
type family Length (a :: [k]) :: Natural where
    Length (a ': as) = 1 + Length as
    Length '[]       = 0