packages feed

bytepatch-0.2.0: src/BytePatch/Linear/Gen.hs

module BytePatch.Linear.Gen (gen, Error(..)) where

import           BytePatch.Core
import           BytePatch.Linear.Core

import qualified Data.ByteString        as BS
import           Control.Monad.State
import qualified Data.List              as List

type Bytes = BS.ByteString

-- | Error encountered during linear patchscript generation.
data Error a
  = ErrorOverlap (Patch a) (Patch a)
  -- ^ Two edits wrote to the same offset.
    deriving (Eq, Show)

-- | Process an offset patchscript into a linear patchscript.
--
-- 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 Bytes] -> (Patchscript Bytes, [Error 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 (Int, Patchscript Bytes, [Error Bytes], Patch Bytes) m) => [Patch Bytes] -> m ()
    go [] = return ()
    go (p@(Patch bs offset meta):ps) = do
        (cursor, script, errors, prevPatch) <- get
        case trySkipTo offset cursor of
          -- next offset is behind current cursor: overlapping patches
          -- record error, recover via dropping patch
          Left _ -> do
            let e = ErrorOverlap p prevPatch
            let errors' = e : errors
            put (cursor, script, errors', p)
            go ps
          Right skip -> do
            let cursor' = cursor + skip + BS.length bs
                o       = Overwrite bs meta
            put (cursor', (skip, o):script, errors, p)
            go ps
    trySkipTo to from =
        let diff = to - from in if diff >= 0 then Right diff else Left (-diff)