bytepatch-0.2.1: src/BytePatch/Linear/Gen.hs
{-# LANGUAGE DataKinds, TypeFamilies, UndecidableInstances #-}
module BytePatch.Linear.Gen (gen, Error(..)) where
import BytePatch.Core
import qualified Data.ByteString as BS
import Control.Monad.State
import qualified Data.List as List
import GHC.Natural
type Bytes = BS.ByteString
-- | Error encountered during linear patchscript generation.
data Error s a
= ErrorOverlap (Patch s a) (Patch s a)
-- ^ Two edits wrote to the same offset.
deriving instance (Eq (SeekRep s), Eq a) => Eq (Error s a)
deriving instance (Show (SeekRep s), Show a) => Show (Error s a)
-- | Process a list of patches into a linear patch script.
--
-- Errors are reported, but do not interrupt patch generation. The user could
-- discard patchscripts that errored, or perhaps attempt to recover them. This
-- is what we do for errors:
--
-- * overlapping edit: later edit is skipped & overlapping edits reported
gen
:: [Patch 'AbsSeek Bytes]
-> ([Patch 'FwdSeek Bytes], [Error 'AbsSeek Bytes])
gen pList =
let pList' = List.sortBy comparePatchOffsets pList
(_, script, errors, _) = execState (go pList') (0, [], [], undefined)
-- I believe the undefined is inaccessible providing the first patch has
-- a non-negative offset (negative offsets are forbidden)
in (reverse script, reverse errors)
where
comparePatchOffsets (Patch o1 _) (Patch o2 _) = compare o1 o2
go
:: (MonadState (Natural, [Patch 'FwdSeek Bytes], [Error 'AbsSeek Bytes], Patch 'AbsSeek Bytes) m)
=> [Patch 'AbsSeek Bytes]
-> m ()
go [] = return ()
go (p@(Patch offset edit) : ps) = do
(cursor, script, errors, prevPatch) <- get
case offset `minusNaturalMaybe` cursor of
-- next offset is behind current cursor: overlapping patches
-- record error, recover via dropping patch
Nothing -> do
let e = ErrorOverlap p prevPatch
let errors' = e : errors
put (cursor, script, errors', p)
go ps
Just skip -> do
let dataLen = fromIntegral $ BS.length $ editData edit
let cursor' = cursor + skip + dataLen
put (cursor', Patch skip edit : script, errors, p)
go ps