packages feed

bytepatch-0.3.0: src/BytePatch.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}

module BytePatch where

import           StreamPatch.Patch
import qualified StreamPatch.Patch.Binary as Bin
import qualified StreamPatch.Patch.Align  as Align

import           Numeric.Natural
import           Data.Functor.Const
import           Data.Vinyl
import           Data.Vinyl.TypeLevel
import           Data.Aeson
import           GHC.Generics ( Generic )

data MultiPatch s a = MultiPatch
  { mpData :: a
  , mpAt :: [Seek s a]
  } deriving (Generic)

deriving instance (Eq   a, Eq   (SeekRep s)) => Eq   (MultiPatch s a)
deriving instance (Show a, Show (SeekRep s)) => Show (MultiPatch s a)

data Seek s a = Seek
  { sSeek :: SeekRep s
  , sExpected :: Maybe a
  , sNullTerminates :: Maybe (SeekRep 'FwdSeek)
  , sMaxBytes :: Maybe (SeekRep 'FwdSeek) -- TODO confirm meaning, maybe improve name
  , sAligned :: Maybe Natural
  } deriving (Generic)

deriving instance (Eq   a, Eq   (SeekRep s)) => Eq   (Seek s a)
deriving instance (Show a, Show (SeekRep s)) => Show (Seek s a)

-- TODO write a Text newtype for Haskell-style negated integers -- allowing
-- negative hex integers!
data Aligned p = Aligned
  { alignedAlign   :: SeekRep 'RelSeek
  , alignedPatches :: [p]
  } deriving (Eq, Show, Generic)

--------------------------------------------------------------------------------

jsonCfgCamelDrop :: Int -> Options
jsonCfgCamelDrop x = defaultOptions
  { fieldLabelModifier = camelTo2 '_' . drop x
  , rejectUnknownFields = False }

instance (ToJSON   (SeekRep s), ToJSON   a) => ToJSON   (MultiPatch s a) where
    toJSON     = genericToJSON     $ jsonCfgCamelDrop 2
    toEncoding = genericToEncoding $ jsonCfgCamelDrop 2
instance (FromJSON (SeekRep s), FromJSON a) => FromJSON (MultiPatch s a) where
    parseJSON  = genericParseJSON  $ jsonCfgCamelDrop 2

instance (ToJSON   (SeekRep s), ToJSON   a) => ToJSON   (Seek s a) where
    toJSON     = genericToJSON     $ jsonCfgCamelDrop 1
    toEncoding = genericToEncoding $ jsonCfgCamelDrop 1
instance (FromJSON (SeekRep s), FromJSON a) => FromJSON (Seek s a) where
    parseJSON  = genericParseJSON  $ jsonCfgCamelDrop 1

instance (ToJSON   p) => ToJSON   (Aligned p) where
    toJSON     = genericToJSON     $ jsonCfgCamelDrop 7
    toEncoding = genericToEncoding $ jsonCfgCamelDrop 7
instance (FromJSON p) => FromJSON (Aligned p) where
    parseJSON  = genericParseJSON  $ jsonCfgCamelDrop 7

--------------------------------------------------------------------------------

convert :: (Seek s a -> Rec (Flap a) fs) -> MultiPatch s a -> [Patch s fs a]
convert f p = map go (mpAt p)
  where go s = Patch { patchData = mpData p
                     , patchSeek = sSeek s
                     , patchMeta = FunctorRec $ f s }

-- TODO how to clean up (it's like liftA2 over n instead of 2. like a fold)
-- likely 'ap'!
convertBinAlign
    :: forall s a
    .  SeekRep s ~ Natural
    => MultiPatch 'RelSeek a
    -> [Patch 'RelSeek '[Const (Align.Meta s), Const Bin.Meta, Bin.MetaStream] a]
convertBinAlign p = convert go p
  where go s = cmAlign s <+> cmBin s <+> cmBinStream s

convertBin
    :: forall s a
    .  MultiPatch s a
    -> [Patch s '[Const Bin.Meta, Bin.MetaStream] a]
convertBin p = convert go p
  where go s = cmBin s <+> cmBinStream s

convertAlign
    :: forall s a
    .  SeekRep s ~ Natural
    => MultiPatch 'RelSeek a
    -> [Patch 'RelSeek '[Const (Align.Meta s)] a]
convertAlign p = convert cmAlign p

convertEmpty
    :: forall s a
    .  MultiPatch s a
    -> [Patch s '[] a]
convertEmpty p = convert (const RNil) p

--------------------------------------------------------------------------------

align
    :: forall s a ss rs is i r
    .  ( SeekRep s ~ Natural
       , r ~ Const (Align.Meta s)
       , rs ~ RDelete r ss
       , RElem r ss i
       , RSubset rs ss is )
    => Aligned (Patch 'RelSeek ss a)
    -> Either (Align.Error s) [Patch s rs a]
align (Aligned a ps) = traverse (Align.align a) ps

--------------------------------------------------------------------------------

cmBin :: Seek s a -> Rec (Flap a) '[Const Bin.Meta]
cmBin s = cm $ Const $ Bin.Meta { Bin.mMaxBytes = sMaxBytes s }

cmBinStream :: Seek s a -> Rec (Flap a) '[Bin.MetaStream]
cmBinStream s = cm $ Bin.MetaStream
    { Bin.msNullTerminates = sNullTerminates s
    , Bin.msExpected = sExpected s }

cmAlign :: SeekRep s ~ Natural => Seek 'RelSeek a -> Rec (Flap a) '[Const (Align.Meta s)]
cmAlign s = cm $ Const $ Align.Meta { Align.mExpected = sAligned s }

cm :: f a -> Rec (Flap a) '[f]
cm fa = Flap fa :& RNil