packages feed

myers-diff 0.1.0.0 → 0.2.0.0

raw patch · 16 files changed

+407/−375 lines, 16 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Diff.VectorMyers: EditDelete :: Int -> Int -> Edit
- Data.Diff.VectorMyers: EditInsert :: Int -> Int -> Int -> Edit
- Data.Diff.VectorMyers: [deleteFrom] :: Edit -> Int
- Data.Diff.VectorMyers: [deleteTo] :: Edit -> Int
- Data.Diff.VectorMyers: [insertFrom] :: Edit -> Int
- Data.Diff.VectorMyers: [insertPos] :: Edit -> Int
- Data.Diff.VectorMyers: [insertTo] :: Edit -> Int
- Data.Diff.VectorMyers: consolidateEditScript :: Seq Edit -> Seq Edit
- Data.Diff.VectorMyers: data Edit
- Data.Diff.VectorMyers: diff :: (PrimMonad m, Unbox a, Eq a, Show a) => Vector a -> Vector a -> m (Seq Edit)
- Data.Diff.VectorMyers: diffStrings :: String -> String -> Seq Edit
- Data.Diff.VectorMyers: diffStringsIO :: String -> String -> IO (Seq Edit)
- Data.Diff.VectorMyers: diffTexts :: Text -> Text -> Seq Edit
- Data.Diff.VectorMyers: diffTextsIO :: Text -> Text -> IO (Seq Edit)
- Data.Diff.VectorMyers: diffTextsToChangeEvents :: Text -> Text -> [ChangeEvent]
- Data.Diff.VectorMyers: diffTextsToChangeEvents' :: (Seq Edit -> Seq Edit) -> Text -> Text -> [ChangeEvent]
- Data.Diff.VectorMyers: diffTextsToChangeEventsConsolidate :: Text -> Text -> [ChangeEvent]
- Data.Diff.VectorMyers: diffTextsToChangeEventsIO :: Text -> Text -> IO [ChangeEvent]
- Data.Diff.VectorMyers: diffTextsToChangeEventsIO' :: (Seq Edit -> Seq Edit) -> Text -> Text -> IO [ChangeEvent]
- Data.Diff.VectorMyers: diffTextsToChangeEventsIOConsolidate :: Text -> Text -> IO [ChangeEvent]
- Data.Diff.VectorMyers: editScriptToChangeEvents :: Vector Char -> Vector Char -> Seq Edit -> Seq ChangeEvent
+ Data.Diff.Myers: EditDelete :: Int -> Int -> Edit
+ Data.Diff.Myers: EditInsert :: Int -> Int -> Int -> Edit
+ Data.Diff.Myers: [deleteFrom] :: Edit -> Int
+ Data.Diff.Myers: [deleteTo] :: Edit -> Int
+ Data.Diff.Myers: [insertFrom] :: Edit -> Int
+ Data.Diff.Myers: [insertPos] :: Edit -> Int
+ Data.Diff.Myers: [insertTo] :: Edit -> Int
+ Data.Diff.Myers: consolidateEditScript :: Seq Edit -> Seq Edit
+ Data.Diff.Myers: data Edit
+ Data.Diff.Myers: diff :: (PrimMonad m, Unbox a, Eq a, Show a) => Vector a -> Vector a -> m (Seq Edit)
+ Data.Diff.Myers: diffStrings :: String -> String -> Seq Edit
+ Data.Diff.Myers: diffTexts :: Text -> Text -> Seq Edit
+ Data.Diff.Myers: diffTextsToChangeEvents :: Text -> Text -> [ChangeEvent]
+ Data.Diff.Myers: diffTextsToChangeEvents' :: (Seq Edit -> Seq Edit) -> Text -> Text -> [ChangeEvent]
+ Data.Diff.Myers: diffTextsToChangeEventsConsolidate :: Text -> Text -> [ChangeEvent]
+ Data.Diff.Myers: editScriptToChangeEvents :: Vector Char -> Vector Char -> Seq Edit -> Seq ChangeEvent

Files

CHANGELOG.md view
@@ -2,6 +2,10 @@  ## Unreleased +## 0.2.0.0++* Tidy modules and add haddocks+ ## 0.1.0.0  * Initial release.
README.md view
@@ -1,5 +1,5 @@ -# Welcome to `myers-diff`+# Welcome to `myers-diff` [![Hackage](https://img.shields.io/hackage/v/myers-diff.svg)](https://hackage.haskell.org/package/myers-diff) ![myers-diff](https://github.com/codedownio/myers-diff/actions/workflows/ci.yml/badge.svg)  This is a fast Haskell implementation of the Myers text diff algorithm[^1]. It is heavily inspired by the Python version in [this post](https://blog.robertelder.org/diff-algorithm/), and should have the same `O(min(len(a), len(b)))` space complexity. (By contrast, the [Diff](https://hackage.haskell.org/package/Diff) package advertises `O(ab)` space complexity.) The implementation uses unboxed mutable vectors for performance. 
app/Main.hs view
@@ -1,7 +1,7 @@  module Main (main) where -import Data.Diff.VectorMyers+import Data.Diff.Myers import Data.String.Interpolate import Data.Text.IO as T import Test.QuickCheck
+ bench-criterion/Bench/VectorIO.hs view
@@ -0,0 +1,44 @@+{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}++module Bench.VectorIO where++import Data.Diff.Types+import Data.Diff.Myers+import qualified Data.Foldable as F+import Data.Sequence+import Data.Text as T+import Data.Vector.Unboxed as VU+++-- * IO versions of the 'Data.Diff.VectorMyers' diff functions for benchmarking++-- | Diff 'Text's to produce an edit script.+diffTextsIO :: Text -> Text -> IO (Seq Edit)+diffTextsIO left right = do+  -- This is faster than VU.fromList (T.unpack left), right?+  let l = VU.generate (T.length left) (\i -> T.index left i)+  let r = VU.generate (T.length right) (\i -> T.index right i)+  diff l r++-- | Diff 'Text's to produce LSP-style change events.+diffTextsToChangeEventsIO :: Text -> Text -> IO [ChangeEvent]+diffTextsToChangeEventsIO = diffTextsToChangeEventsIO' id++-- | Diff 'Text's to produce consolidated LSP-style change events.+diffTextsToChangeEventsIOConsolidate :: Text -> Text -> IO [ChangeEvent]+diffTextsToChangeEventsIOConsolidate = diffTextsToChangeEventsIO' consolidateEditScript++diffTextsToChangeEventsIO' :: (Seq Edit -> Seq Edit) -> Text -> Text -> IO [ChangeEvent]+diffTextsToChangeEventsIO' consolidateFn left right = do+  -- This is faster than VU.fromList (T.unpack left), right?+  let l = VU.generate (T.length left) (\i -> T.index left i)+  let r = VU.generate (T.length right) (\i -> T.index right i)+  edits <- diff l r+  return $ F.toList $ editScriptToChangeEvents l r (consolidateFn edits)++-- | To use in benchmarking against other libraries that use String+diffStringsIO :: String -> String -> IO (Seq Edit)+diffStringsIO left right = do+  let leftThawed = VU.fromList left+  let rightThawed = VU.fromList right+  diff leftThawed rightThawed
bench-criterion/Main.hs view
@@ -4,13 +4,13 @@  import Criterion import Criterion.Main-import qualified Data.Diff.VectorMyers as VM+import qualified Data.Diff.Myers as VM import Data.String.Interpolate import Data.Text as T import TestLib.Instances ()  #ifdef DIFF_MYERS-import qualified Data.Diff.DiffMyersShim as DM+import qualified Data.Diff.DiffMyers as DM #endif  @@ -29,7 +29,7 @@       , bench "Vector to ChangeEvents (consolidated)" $ nf (\(x, y) -> VM.diffTextsToChangeEventsConsolidate x y) (initialText, finalText)  #ifdef DIFF_MYERS-      , bench "Diff" $ nf (\(x, y) -> DM.diffDiff x y) (initial, final)+      , bench "Diff" $ nf (\(x, y) -> DM.diff x y) (initial, final) #endif     ]   ]
bench-weigh/Main.hs view
@@ -2,7 +2,7 @@  module Main (main) where -import qualified Data.Diff.VectorMyers as VM+import qualified Data.Diff.Myers as VM import Data.String import Data.String.Interpolate import Data.Text as T@@ -13,14 +13,14 @@ import Weigh  #ifdef DIFF_MYERS-import qualified Data.Diff.DiffMyersShim as DM+import qualified Data.Diff.DiffMyers as DM #endif   main :: IO () main = mainWith $ do #ifdef DIFF_MYERS-  func "Diff" (\(x, y) -> DM.diffDiff x y) (T.unpack file1, T.unpack file2)+  func "Diff" (\(x, y) -> DM.diff x y) (T.unpack file1, T.unpack file2) #endif    func "Vector" (\(x, y) -> VM.diffTextsToChangeEvents x y) (file1, file2)
myers-diff.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           myers-diff-version:        0.1.0.0+version:        0.2.0.0 description:    Please see the README on GitHub at <https://github.com/codedownio/myers-diff#readme> homepage:       https://github.com/codedownio/myers-diff#readme bug-reports:    https://github.com/codedownio/myers-diff/issues@@ -34,8 +34,8 @@  library   exposed-modules:+      Data.Diff.Myers       Data.Diff.Types-      Data.Diff.VectorMyers   other-modules:       Paths_myers_diff   hs-source-dirs:@@ -71,14 +71,13 @@   if flag(uni_myers)     exposed-modules:         Data.Diff.UniMyers-        Data.Diff.UniMyersShim     hs-source-dirs:         src-uni-myers     build-depends:         array   if flag(diff_myers)     exposed-modules:-        Data.Diff.DiffMyersShim+        Data.Diff.DiffMyers     hs-source-dirs:         src-diff-myers     build-depends:@@ -184,7 +183,7 @@         Spec.UniMyersSpec     hs-source-dirs:         test-uni-myers-  if flag(uni_myers)+  if flag(diff_myers)     other-modules:         Spec.DiffMyersSpec     hs-source-dirs:@@ -194,6 +193,7 @@   type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:+      Bench.VectorIO       TestLib.Apply       TestLib.Generators       TestLib.Instances
+ src-diff-myers/Data/Diff/DiffMyers.hs view
@@ -0,0 +1,49 @@++module Data.Diff.DiffMyers (+  diff+  ) where++import qualified Data.Algorithm.Diff as DD+import Data.Diff.Types+import Data.Function+import qualified Data.List as L+import qualified Data.Text as T+++utilDiffToLspDiff :: [DD.Diff String] -> [ChangeEvent]+utilDiffToLspDiff elems = go [] 0 0 elems+  where+    go events curLine curChar ((DD.Both chars _):xs) = go events curLine' curChar' xs+      where+        curLine' = curLine + countNewlines chars+        curChar' = if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars)++    go events curLine curChar ((DD.First chars):xs) = go ((ChangeEvent (Range startPos endPos) ""):events) curLine' curChar' xs+      where+        startPos = Position curLine curChar+        endPos = Position (curLine + countNewlines chars) (if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars))++        curLine' = curLine+        curChar' = curChar+    go events curLine curChar ((DD.Second chars):xs) = go ((ChangeEvent (Range startPos endPos) (T.pack chars)):events) curLine' curChar' xs+      where+        startPos = Position curLine curChar+        endPos = startPos++        curLine' = curLine + countNewlines chars+        curChar' = if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars)++    go events _curLine _curChar [] = reverse events++    hasNewline = any (== '\n')++    lengthOfLastLine chars = chars+                           & T.pack+                           & T.splitOn "\n"+                           & last+                           & T.length++    countNewlines = L.foldl' (\total c -> if c == '\n' then total + 1 else total) 0++diff :: String -> String -> [ChangeEvent]+diff s1 s2 = utilDiffToLspDiff (DD.getGroupedDiff s1 s2)
− src-diff-myers/Data/Diff/DiffMyersShim.hs
@@ -1,49 +0,0 @@--module Data.Diff.DiffMyersShim (-  diffDiff-  ) where--import qualified Data.Algorithm.Diff as DD-import Data.Diff.Types-import Data.Function-import qualified Data.List as L-import qualified Data.Text as T---utilDiffToLspDiff :: [DD.Diff String] -> [ChangeEvent]-utilDiffToLspDiff elems = go [] 0 0 elems-  where-    go events curLine curChar ((DD.Both chars _):xs) = go events curLine' curChar' xs-      where-        curLine' = curLine + countNewlines chars-        curChar' = if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars)--    go events curLine curChar ((DD.First chars):xs) = go ((ChangeEvent (Range startPos endPos) ""):events) curLine' curChar' xs-      where-        startPos = Position curLine curChar-        endPos = Position (curLine + countNewlines chars) (if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars))--        curLine' = curLine-        curChar' = curChar-    go events curLine curChar ((DD.Second chars):xs) = go ((ChangeEvent (Range startPos endPos) (T.pack chars)):events) curLine' curChar' xs-      where-        startPos = Position curLine curChar-        endPos = startPos--        curLine' = curLine + countNewlines chars-        curChar' = if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars)--    go events _curLine _curChar [] = reverse events--    hasNewline = any (== '\n')--    lengthOfLastLine chars = chars-                           & T.pack-                           & T.splitOn "\n"-                           & last-                           & T.length--    countNewlines = L.foldl' (\total c -> if c == '\n' then total + 1 else total) 0--diffDiff :: String -> String -> [ChangeEvent]-diffDiff s1 s2 = utilDiffToLspDiff (DD.getGroupedDiff s1 s2)
src-uni-myers/Data/Diff/UniMyers.hs view
@@ -31,13 +31,19 @@    diff,    diff2,    DiffElement(..),++   utilDiff,+   utilDiffToLspDiff    ) where  -import Data.Array- import Control.Monad.ST-import Data.Array.ST+import Data.Array hiding (index)+import Data.Array.ST hiding (index)+import Data.Diff.Types+import Data.Function+import qualified Data.List as L+import qualified Data.Text as T  -- import Util.ExtendedPrelude @@ -317,3 +323,45 @@             fmap                (\ (xs1,xs2) -> (x:xs1,xs2))                (sTC xs)++-- * Shim+++utilDiffToLspDiff :: [DiffElement Char] -> [ChangeEvent]+utilDiffToLspDiff elems = go [] 0 0 elems+  where+    go events curLine curChar ((InBoth chars):xs) = go events curLine' curChar' xs+      where+        curLine' = curLine + countNewlines chars+        curChar' = if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars)++    go events curLine curChar ((InFirst chars):xs) = go ((ChangeEvent (Range startPos endPos) ""):events) curLine' curChar' xs+      where+        startPos = Position curLine curChar+        endPos = Position (curLine + countNewlines chars) (if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars))++        curLine' = curLine+        curChar' = curChar++    go events curLine curChar ((InSecond chars):xs) = go ((ChangeEvent (Range startPos endPos) (T.pack chars)):events) curLine' curChar' xs+      where+        startPos = Position curLine curChar+        endPos = startPos++        curLine' = curLine + countNewlines chars+        curChar' = if hasNewline chars then lengthOfLastLine chars else curChar + (L.length chars)++    go events _curLine _curChar [] = reverse events++    hasNewline = any (== '\n')++    lengthOfLastLine chars = chars+                           & T.pack+                           & T.splitOn "\n"+                           & last+                           & T.length++    countNewlines = L.foldl' (\total c -> if c == '\n' then total + 1 else total) 0++utilDiff :: String -> String -> [ChangeEvent]+utilDiff s1 s2 = utilDiffToLspDiff (diff s1 s2)
− src-uni-myers/Data/Diff/UniMyersShim.hs
@@ -1,53 +0,0 @@--module Data.Diff.UniMyersShim (-  utilDiff-  ) where--import Data.Diff.Types-import qualified Data.Diff.UniMyers as UM-import Data.Function-import qualified Data.List as L-import qualified Data.Text as T---utilDiffToLspDiff :: [UM.DiffElement Char] -> [ChangeEvent]-utilDiffToLspDiff elems = go [] 0 0 elems-  where-    go events curLine curChar ((UM.InBoth chars):xs) = go events curLine' curChar' xs-      where-        curLine' = curLine + countNewlines chars-        curChar' = if hasNewline chars then fromIntegral (lengthOfLastLine chars) else curChar + fromIntegral (L.length chars)--    go events curLine curChar ((UM.InFirst chars):xs) = go ((ChangeEvent (Range startPos endPos) ""):events) curLine' curChar' xs-      where-        startPos = Position curLine curChar-        endPos = Position (curLine + countNewlines chars) (if hasNewline chars then fromIntegral (lengthOfLastLine chars) else curChar + fromIntegral (L.length chars))--        curLine' = curLine-        curChar' = curChar--    go events curLine curChar ((UM.InSecond chars):xs) = go ((ChangeEvent (Range startPos endPos) (T.pack chars)):events) curLine' curChar' xs-      where-        startPos = Position curLine curChar-        endPos = startPos--        curLine' = curLine + countNewlines chars-        curChar' = if hasNewline chars then fromIntegral (lengthOfLastLine chars) else curChar + fromIntegral (L.length chars)--    go events _curLine _curChar [] = reverse events--    hasNewline = any (== '\n')--    lengthOfLastLine chars = chars-                           & T.pack-                           & T.splitOn "\n"-                           & last-                           & T.length--    countNewlines = L.foldl' (\total c -> if c == '\n' then total + 1 else total) 0--utilDiff :: String -> String -> [ChangeEvent]-utilDiff s1 s2 = utilDiffToLspDiff (UM.diff s1 s2)--s1 = ""-s2 = "I"
+ src/Data/Diff/Myers.hs view
@@ -0,0 +1,240 @@+{-# LANGUAGE OverloadedLists #-}++{-|+Module:      Data.Diff.Myers+Copyright:   (c) 2023 Tom McLaughlin+License:     BSD3+Stability:   experimental+Portability: portable++This is a fast Haskell implementation of the Myers text diff algorithm[1]. It is heavily inspired by the Python version in [this post](https://blog.robertelder.org/diff-algorithm/), and should have the same @O(min(len(a), len(b)))@ space complexity. (By contrast, the [Diff](https://hackage.haskell.org/package/Diff) package advertises @O(ab)@ space complexity.) The implementation uses unboxed mutable vectors for performance.++This repo also can also build a couple other versions for benchmarking comparison, gated behind flags.++* @-funi_myers@ will build the version from the [uni-util](https://hackage.haskell.org/package/uni-util-2.3.0.3/docs/Util-Myers.html) package.+* @-fdiff_myers@ will use the [Diff](https://hackage.haskell.org/package/Diff) package.++[1]: E. Myers (1986). "An O(ND) Difference Algorithm and Its Variations". Algorithmica. 1 (2): 251–266. CiteSeerX [10.1.1.4.6927](https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927). doi:[10.1007/BF01840446](https://doi.org/10.1007%2FBF01840446). S2CID [6996809](https://api.semanticscholar.org/CorpusID:6996809).++-}++module Data.Diff.Myers (+  -- * Pure diffing (uses ST monad)+  diffTexts+  , diffTextsToChangeEvents+  , diffTextsToChangeEventsConsolidate+  , diffTextsToChangeEvents'+  , diffStrings++  -- * Lowest level diff function+  , diff++  -- * Working with edit scripts+  , editScriptToChangeEvents+  , consolidateEditScript++  -- * Types+  , Edit(..)+  ) where++import Control.Monad.Primitive+import Control.Monad.ST+import Data.Bits (xor)+import Data.Diff.Types+import qualified Data.Foldable as F+import Data.Function+import Data.Sequence as Seq+import Data.Text as T+import Data.Vector.Unboxed as VU+import Data.Vector.Unboxed.Mutable as VUM+import Prelude hiding (read)+++-- | Diff 'Text's to produce an edit script.+diffTexts :: Text -> Text -> Seq Edit+diffTexts left right = runST $ do+  -- This is faster than VU.fromList (T.unpack left), right?+  let l = VU.generate (T.length left) (\i -> T.index left i)+  let r = VU.generate (T.length right) (\i -> T.index right i)+  diff l r++-- | Diff 'Text's to produce LSP-style change events.+diffTextsToChangeEvents :: Text -> Text -> [ChangeEvent]+diffTextsToChangeEvents = diffTextsToChangeEvents' id++-- | Diff 'Text's to produce consolidated LSP-style change events.+diffTextsToChangeEventsConsolidate :: Text -> Text -> [ChangeEvent]+diffTextsToChangeEventsConsolidate = diffTextsToChangeEvents' consolidateEditScript++-- | Diff 'Text's with a custom consolidation function.+diffTextsToChangeEvents' :: (Seq Edit -> Seq Edit) -> Text -> Text -> [ChangeEvent]+diffTextsToChangeEvents' consolidateFn left right = F.toList $ editScriptToChangeEvents l r (consolidateFn (runST (diff l r)))+  where+    l = VU.generate (T.length left) (\i -> T.index left i)+    r = VU.generate (T.length right) (\i -> T.index right i)++-- | To use in benchmarking against other libraries that use String.+diffStrings :: String -> String -> Seq Edit+diffStrings left right = runST $ do+  let leftThawed = VU.fromList left+  let rightThawed = VU.fromList right+  diff leftThawed rightThawed++-- * Core++diff :: (+  PrimMonad m, Unbox a, Eq a, Show a+  ) => Vector a -> Vector a -> m (Seq Edit)+diff e f = diff' e f 0 0++diff' :: (+  PrimMonad m, Unbox a, Eq a, Show a+  ) => Vector a -> Vector a -> Int -> Int -> m (Seq Edit)+diff' e f i j = do+  let (bigN, bigM) = (VU.length e, VU.length f)+  let bigZ = (2 * (min bigN bigM)) + 2+  g <- new bigZ+  p <- new bigZ+  diff'' g p e f i j++diff'' :: (+  PrimMonad m, Unbox a, Eq a, Show a+  ) => MVector (PrimState m) Int -> MVector (PrimState m) Int -> Vector a -> Vector a -> Int -> Int -> m (Seq Edit)+diff'' g' p' e f i j = do+  let (bigN, bigM) = (VU.length e, VU.length f)+  let (bigL, bigZ) = (bigN + bigM, (2 * (min bigN bigM)) + 2)++  if | bigN > 0 && bigM > 0 -> do+         let w = bigN - bigM++         -- Clear out the reused memory vectors+         let g = VUM.unsafeSlice 0 bigZ g'+         VUM.set g 0+         let p = VUM.unsafeSlice 0 bigZ p'+         VUM.set p 0++         flip fix 0 $ \loopBaseH -> \case+           h | not (h <= ((bigL `pyDiv` 2) + (if (bigL `pyMod` 2) /= 0 then 1 else 0))) -> return []+           h -> do+             let loopH = loopBaseH (h + 1)+             flip fix (0 :: Int) $ \loopBaseR -> \case+               r | not (r <= 1) -> loopH+               r -> do+                 let loopR = loopBaseR (r + 1)+                 let (c, d, o, m) = if r == 0 then (g, p, 1, 1) else (p, g, 0, -1)+                 flip fix (negate (h - (2 * (max 0 (h - bigM))))) $ \loopBaseK -> \case+                   k | not (k <= (h - (2 * (max 0 (h - bigN))))) -> loopR+                   k -> do+                     let loopK = loopBaseK (k + 2)+                     aInitial <- do+                       prevC <- unsafeRead c ((k-1) `pyMod` bigZ)+                       nextC <- unsafeRead c ((k+1) `pyMod` bigZ)+                       return (if (k == (-h) || (k /= h && (prevC < nextC))) then nextC else prevC + 1)+                     let bInitial = aInitial - k+                     let (s, t) = (aInitial, bInitial)++                     (a, b) <- flip fix (aInitial, bInitial) $ \loop (a', b') -> do+                       if | a' < bigN && b' < bigM -> do+                              let eVal = e `unsafeIndex` (((1 - o) * bigN) + (m*a') + (o - 1))+                              let fVal = f `unsafeIndex` (((1 - o) * bigM) + (m*b') + (o - 1))+                              if | eVal == fVal -> loop (a' + 1, b' + 1)+                                 | otherwise -> pure (a', b')+                          | otherwise -> pure (a', b')++                     write c (k `pyMod` bigZ) a+                     let z = negate (k - w)++                     cVal <- unsafeRead c (k `pyMod` bigZ)+                     dVal <- unsafeRead d (z `pyMod` bigZ)+                     if | (bigL `pyMod` 2 == o) && (z >= (negate (h-o))) && (z <= (h-o)) && (cVal + dVal >= bigN) -> do+                            let (bigD, x, y, u, v) = if o == 1 then ((2*h)-1, s, t, a, b) else (2*h, bigN-a, bigM-b, bigN-s, bigM-t)+                            if | bigD > 1 || (x /= u && y /= v) ->+                                  mappend <$> diff'' g p (VU.unsafeSlice 0 x e) (VU.unsafeSlice 0 y f) i j+                                          <*> diff'' g p (VU.unsafeSlice u (bigN - u) e) (VU.unsafeSlice v (bigM - v) f) (i+u) (j+v)+                               | bigM > bigN ->+                                  diff'' g p (VU.unsafeSlice 0 0 e) (VU.unsafeSlice bigN (bigM - bigN) f) (i+bigN) (j+bigN)+                               | bigM < bigN ->+                                  diff'' g p (VU.unsafeSlice bigM (bigN - bigM) e) (VU.unsafeSlice 0 0 f) (i+bigM) (j+bigM)+                               | otherwise -> return []+                        | otherwise -> loopK+++     | bigN > 0 -> return [EditDelete i (i + (bigN - 1))]+     | bigM == 0 -> return []+     | otherwise -> return [EditInsert i j (j + (bigM - 1))]++{-# INLINABLE pyMod #-}+pyMod :: Integral a => a -> a -> a+pyMod x y = if y >= 0 then x `mod` y else (x `mod` y) - y++{-# INLINABLE pyDiv #-}+pyDiv :: Integral a => a -> a -> a+pyDiv x y = if (x < 0) `xor` (y < 0) then -((-x) `div` y) else x `div` y+++-- | Convert edit script to LSP-style change events.+editScriptToChangeEvents :: VU.Vector Char -> VU.Vector Char -> Seq Edit -> Seq ChangeEvent+editScriptToChangeEvents left right = go mempty 0 0 0+  where+    go :: Seq ChangeEvent -> Int -> Int -> Int -> Seq Edit -> Seq ChangeEvent+    go seqSoFar _ _ _ Empty = seqSoFar++    -- Implicit unchanged section before delete+    go seqSoFar pos line ch args@((EditDelete from _to) :<| _) |+      pos < from = go seqSoFar from line' ch' args+        where+          (numNewlinesEncountered, lastLineLength) = countNewlinesAndLastLineLength (VU.slice pos (from - pos) left)+          line' = line + numNewlinesEncountered+          ch' | numNewlinesEncountered == 0 = ch + (from - pos)+              | otherwise = lastLineLength+    -- Implicit unchanged section before insert+    go seqSoFar pos line ch args@((EditInsert from _rightFrom _rightTo) :<| _) |+      pos < from = go seqSoFar from line' ch' args+        where+          (numNewlinesEncountered, lastLineLength) = countNewlinesAndLastLineLength (VU.slice pos (from - pos) left)+          line' = line + numNewlinesEncountered+          ch' | numNewlinesEncountered == 0 = ch + (from - pos)+              | otherwise = lastLineLength++    go seqSoFar pos line ch ((EditDelete from to) :<| rest) = go (seqSoFar |> change) pos' line ch rest+      where+        change = ChangeEvent (Range (Position line ch) (Position line' ch')) ""+        pos' = to + 1++        deleted = VU.slice from (to + 1 - from) left+        (numNewlinesInDeleted, lastLineLengthInDeleted) = countNewlinesAndLastLineLength deleted+        line' = line + numNewlinesInDeleted+        ch' = if | numNewlinesInDeleted == 0 -> ch + (to - pos + 1)+                 | otherwise -> lastLineLengthInDeleted++    go seqSoFar pos line ch ((EditInsert _at rightFrom rightTo) :<| rest) = go (seqSoFar |> change) pos' line' ch' rest+      where+        change = ChangeEvent (Range (Position line ch) (Position line ch)) (vectorToText inserted)+        pos' = pos++        inserted = VU.slice rightFrom (rightTo + 1 - rightFrom) right+        (numNewlinesInInserted, lastLineLengthInInserted) = countNewlinesAndLastLineLength inserted+        line' = line + numNewlinesInInserted+        ch' = if | numNewlinesInInserted == 0 -> ch + VU.length inserted+                 | otherwise -> lastLineLengthInInserted++    countNewlinesAndLastLineLength :: VU.Vector Char -> (Int, Int)+    countNewlinesAndLastLineLength = VU.foldl' (\(tot, lastLineLength) ch -> if ch == '\n' then (tot + 1, 0) else (tot, lastLineLength + 1)) (0, 0)++    vectorToText :: VU.Vector Char -> T.Text+    vectorToText = T.pack . VU.toList++-- * Consolidate edits++-- λ> diffTexts "x" "xab"+-- fromList [EditInsert {insertPos = 1, insertFrom = 1, insertTo = 1},EditInsert {insertPos = 1, insertFrom = 2, insertTo = 2}]+-- λ> diffTexts "xab" "x"+-- fromList [EditDelete {deleteFrom = 1, deleteTo = 1},EditDelete {deleteFrom = 2, deleteTo = 2}]+-- | Consolidate adjacent edit script entries to shorten the script.+consolidateEditScript :: Seq Edit -> Seq Edit+consolidateEditScript ((EditInsert pos1 from1 to1) :<| (EditInsert pos2 from2 to2) :<| rest)+  | pos1 == pos2 && to1 + 1 == from2 = consolidateEditScript ((EditInsert pos1 from1 to2) <| rest)+consolidateEditScript ((EditDelete from1 to1) :<| (EditDelete from2 to2) :<| rest)+  | to1 + 1 == from2 = consolidateEditScript ((EditDelete from1 to2) <| rest)+consolidateEditScript (x :<| y :<| rest) = x <| (consolidateEditScript (y <| rest))+consolidateEditScript x = x
− src/Data/Diff/VectorMyers.hs
@@ -1,251 +0,0 @@-{-# LANGUAGE OverloadedLists #-}--module Data.Diff.VectorMyers (-  diffTexts-  , diffTextsToChangeEvents-  , diffTextsToChangeEventsConsolidate-  , diffTextsToChangeEvents'-  , diffStrings--  , diffTextsIO-  , diffTextsToChangeEventsIO-  , diffTextsToChangeEventsIOConsolidate-  , diffTextsToChangeEventsIO'-  , diffStringsIO--  , diff-  , Edit(..)--  , editScriptToChangeEvents-  , consolidateEditScript-  ) where--import Control.Monad.Primitive-import Control.Monad.ST-import Data.Bits (xor)-import Data.Diff.Types-import qualified Data.Foldable as F-import Data.Function-import Data.Sequence as Seq-import Data.Text as T-import Data.Vector.Unboxed as VU-import Data.Vector.Unboxed.Mutable as VUM-import Prelude hiding (read)----- * Pure version uses ST--diffTexts :: Text -> Text -> Seq Edit-diffTexts left right = runST $ do-  -- This is faster than VU.fromList (T.unpack left), right?-  let l = VU.generate (T.length left) (\i -> T.index left i)-  let r = VU.generate (T.length right) (\i -> T.index right i)-  diff l r--diffTextsToChangeEvents :: Text -> Text -> [ChangeEvent]-diffTextsToChangeEvents = diffTextsToChangeEvents' id--diffTextsToChangeEventsConsolidate :: Text -> Text -> [ChangeEvent]-diffTextsToChangeEventsConsolidate = diffTextsToChangeEvents' consolidateEditScript--diffTextsToChangeEvents' :: (Seq Edit -> Seq Edit) -> Text -> Text -> [ChangeEvent]-diffTextsToChangeEvents' consolidateFn left right = F.toList $ editScriptToChangeEvents l r (consolidateFn (runST (diff l r)))-  where-    l = VU.generate (T.length left) (\i -> T.index left i)-    r = VU.generate (T.length right) (\i -> T.index right i)---- | To use in benchmarking against other libraries that use String-diffStrings :: String -> String -> Seq Edit-diffStrings left right = runST $ do-  let leftThawed = VU.fromList left-  let rightThawed = VU.fromList right-  diff leftThawed rightThawed---- * IO version to benchmark against--diffTextsIO :: Text -> Text -> IO (Seq Edit)-diffTextsIO left right = do-  -- This is faster than VU.fromList (T.unpack left), right?-  let l = VU.generate (T.length left) (\i -> T.index left i)-  let r = VU.generate (T.length right) (\i -> T.index right i)-  diff l r--diffTextsToChangeEventsIO :: Text -> Text -> IO [ChangeEvent]-diffTextsToChangeEventsIO = diffTextsToChangeEventsIO' id--diffTextsToChangeEventsIOConsolidate :: Text -> Text -> IO [ChangeEvent]-diffTextsToChangeEventsIOConsolidate = diffTextsToChangeEventsIO' consolidateEditScript--diffTextsToChangeEventsIO' :: (Seq Edit -> Seq Edit) -> Text -> Text -> IO [ChangeEvent]-diffTextsToChangeEventsIO' consolidateFn left right = do-  -- This is faster than VU.fromList (T.unpack left), right?-  let l = VU.generate (T.length left) (\i -> T.index left i)-  let r = VU.generate (T.length right) (\i -> T.index right i)-  edits <- diff l r-  return $ F.toList $ editScriptToChangeEvents l r (consolidateFn edits)---- | To use in benchmarking against other libraries that use String-diffStringsIO :: String -> String -> IO (Seq Edit)-diffStringsIO left right = do-  let leftThawed = VU.fromList left-  let rightThawed = VU.fromList right-  diff leftThawed rightThawed---- * Core--diff :: (-  PrimMonad m, Unbox a, Eq a, Show a-  ) => Vector a -> Vector a -> m (Seq Edit)-diff e f = diff' e f 0 0--diff' :: (-  PrimMonad m, Unbox a, Eq a, Show a-  ) => Vector a -> Vector a -> Int -> Int -> m (Seq Edit)-diff' e f i j = do-  let (bigN, bigM) = (VU.length e, VU.length f)-  let bigZ = (2 * (min bigN bigM)) + 2-  g <- new bigZ-  p <- new bigZ-  diff'' g p e f i j--diff'' :: (-  PrimMonad m, Unbox a, Eq a, Show a-  ) => MVector (PrimState m) Int -> MVector (PrimState m) Int -> Vector a -> Vector a -> Int -> Int -> m (Seq Edit)-diff'' g' p' e f i j = do-  let (bigN, bigM) = (VU.length e, VU.length f)-  let (bigL, bigZ) = (bigN + bigM, (2 * (min bigN bigM)) + 2)--  if | bigN > 0 && bigM > 0 -> do-         let w = bigN - bigM--         -- Clear out the reused memory vectors-         let g = VUM.unsafeSlice 0 bigZ g'-         VUM.set g 0-         let p = VUM.unsafeSlice 0 bigZ p'-         VUM.set p 0--         flip fix 0 $ \loopBaseH -> \case-           h | not (h <= ((bigL `pyDiv` 2) + (if (bigL `pyMod` 2) /= 0 then 1 else 0))) -> return []-           h -> do-             let loopH = loopBaseH (h + 1)-             flip fix (0 :: Int) $ \loopBaseR -> \case-               r | not (r <= 1) -> loopH-               r -> do-                 let loopR = loopBaseR (r + 1)-                 let (c, d, o, m) = if r == 0 then (g, p, 1, 1) else (p, g, 0, -1)-                 flip fix (negate (h - (2 * (max 0 (h - bigM))))) $ \loopBaseK -> \case-                   k | not (k <= (h - (2 * (max 0 (h - bigN))))) -> loopR-                   k -> do-                     let loopK = loopBaseK (k + 2)-                     aInitial <- do-                       prevC <- unsafeRead c ((k-1) `pyMod` bigZ)-                       nextC <- unsafeRead c ((k+1) `pyMod` bigZ)-                       return (if (k == (-h) || (k /= h && (prevC < nextC))) then nextC else prevC + 1)-                     let bInitial = aInitial - k-                     let (s, t) = (aInitial, bInitial)--                     (a, b) <- flip fix (aInitial, bInitial) $ \loop (a', b') -> do-                       if | a' < bigN && b' < bigM -> do-                              let eVal = e `unsafeIndex` (((1 - o) * bigN) + (m*a') + (o - 1))-                              let fVal = f `unsafeIndex` (((1 - o) * bigM) + (m*b') + (o - 1))-                              if | eVal == fVal -> loop (a' + 1, b' + 1)-                                 | otherwise -> pure (a', b')-                          | otherwise -> pure (a', b')--                     write c (k `pyMod` bigZ) a-                     let z = negate (k - w)--                     cVal <- unsafeRead c (k `pyMod` bigZ)-                     dVal <- unsafeRead d (z `pyMod` bigZ)-                     if | (bigL `pyMod` 2 == o) && (z >= (negate (h-o))) && (z <= (h-o)) && (cVal + dVal >= bigN) -> do-                            let (bigD, x, y, u, v) = if o == 1 then ((2*h)-1, s, t, a, b) else (2*h, bigN-a, bigM-b, bigN-s, bigM-t)-                            if | bigD > 1 || (x /= u && y /= v) ->-                                  mappend <$> diff'' g p (VU.unsafeSlice 0 x e) (VU.unsafeSlice 0 y f) i j-                                          <*> diff'' g p (VU.unsafeSlice u (bigN - u) e) (VU.unsafeSlice v (bigM - v) f) (i+u) (j+v)-                               | bigM > bigN ->-                                  diff'' g p (VU.unsafeSlice 0 0 e) (VU.unsafeSlice bigN (bigM - bigN) f) (i+bigN) (j+bigN)-                               | bigM < bigN ->-                                  diff'' g p (VU.unsafeSlice bigM (bigN - bigM) e) (VU.unsafeSlice 0 0 f) (i+bigM) (j+bigM)-                               | otherwise -> return []-                        | otherwise -> loopK---     | bigN > 0 -> return [EditDelete i (i + (bigN - 1))]-     | bigM == 0 -> return []-     | otherwise -> return [EditInsert i j (j + (bigM - 1))]--{-# INLINABLE pyMod #-}-pyMod :: Integral a => a -> a -> a-pyMod x y = if y >= 0 then x `mod` y else (x `mod` y) - y--{-# INLINABLE pyDiv #-}-pyDiv :: Integral a => a -> a -> a-pyDiv x y = if (x < 0) `xor` (y < 0) then -((-x) `div` y) else x `div` y----- * Converting edit script to LSP-style change events--editScriptToChangeEvents :: VU.Vector Char -> VU.Vector Char -> Seq Edit -> Seq ChangeEvent-editScriptToChangeEvents left right = go mempty 0 0 0-  where-    go :: Seq ChangeEvent -> Int -> Int -> Int -> Seq Edit -> Seq ChangeEvent-    go seqSoFar _ _ _ Empty = seqSoFar--    -- Implicit unchanged section before delete-    go seqSoFar pos line ch args@((EditDelete from _to) :<| _) |-      pos < from = go seqSoFar from line' ch' args-        where-          (numNewlinesEncountered, lastLineLength) = countNewlinesAndLastLineLength (VU.slice pos (from - pos) left)-          line' = line + numNewlinesEncountered-          ch' | numNewlinesEncountered == 0 = ch + (from - pos)-              | otherwise = lastLineLength-    -- Implicit unchanged section before insert-    go seqSoFar pos line ch args@((EditInsert from _rightFrom _rightTo) :<| _) |-      pos < from = go seqSoFar from line' ch' args-        where-          (numNewlinesEncountered, lastLineLength) = countNewlinesAndLastLineLength (VU.slice pos (from - pos) left)-          line' = line + numNewlinesEncountered-          ch' | numNewlinesEncountered == 0 = ch + (from - pos)-              | otherwise = lastLineLength--    go seqSoFar pos line ch ((EditDelete from to) :<| rest) = go (seqSoFar |> change) pos' line ch rest-      where-        change = ChangeEvent (Range (Position line ch) (Position line' ch')) ""-        pos' = to + 1--        deleted = VU.slice from (to + 1 - from) left-        (numNewlinesInDeleted, lastLineLengthInDeleted) = countNewlinesAndLastLineLength deleted-        line' = line + numNewlinesInDeleted-        ch' = if | numNewlinesInDeleted == 0 -> ch + (to - pos + 1)-                 | otherwise -> lastLineLengthInDeleted--    go seqSoFar pos line ch ((EditInsert _at rightFrom rightTo) :<| rest) = go (seqSoFar |> change) pos' line' ch' rest-      where-        change = ChangeEvent (Range (Position line ch) (Position line ch)) (vectorToText inserted)-        pos' = pos--        inserted = VU.slice rightFrom (rightTo + 1 - rightFrom) right-        (numNewlinesInInserted, lastLineLengthInInserted) = countNewlinesAndLastLineLength inserted-        line' = line + numNewlinesInInserted-        ch' = if | numNewlinesInInserted == 0 -> ch + VU.length inserted-                 | otherwise -> lastLineLengthInInserted--    countNewlinesAndLastLineLength :: VU.Vector Char -> (Int, Int)-    countNewlinesAndLastLineLength = VU.foldl' (\(tot, lastLineLength) ch -> if ch == '\n' then (tot + 1, 0) else (tot, lastLineLength + 1)) (0, 0)--    vectorToText :: VU.Vector Char -> T.Text-    vectorToText = T.pack . VU.toList---- * Consolidate edits---- λ> diffTexts "x" "xab"--- fromList [EditInsert {insertPos = 1, insertFrom = 1, insertTo = 1},EditInsert {insertPos = 1, insertFrom = 2, insertTo = 2}]--- λ> diffTexts "xab" "x"--- fromList [EditDelete {deleteFrom = 1, deleteTo = 1},EditDelete {deleteFrom = 2, deleteTo = 2}]-consolidateEditScript :: Seq Edit -> Seq Edit-consolidateEditScript ((EditInsert pos1 from1 to1) :<| (EditInsert pos2 from2 to2) :<| rest)-  | pos1 == pos2 && to1 + 1 == from2 = consolidateEditScript ((EditInsert pos1 from1 to2) <| rest)-consolidateEditScript ((EditDelete from1 to1) :<| (EditDelete from2 to2) :<| rest)-  | to1 + 1 == from2 = consolidateEditScript ((EditDelete from1 to2) <| rest)-consolidateEditScript (x :<| y :<| rest) = x <| (consolidateEditScript (y <| rest))-consolidateEditScript x = x
test-diff-myers/Spec/DiffMyersSpec.hs view
@@ -3,7 +3,7 @@  module Spec.DiffMyersSpec (spec) where -import Data.Diff.DiffMyersShim+import Data.Diff.DiffMyers import Data.Diff.Types import Data.Text as T import Test.Sandwich@@ -16,10 +16,10 @@ spec = describe "DiffMyers" $ do   describe "Single-line cases" $ do     it "simple insertion" $ do-      diffDiff "ab" "abc" `shouldBe` ([ChangeEvent (Range (Position 0 2) (Position 0 2)) "c"])+      diff "ab" "abc" `shouldBe` ([ChangeEvent (Range (Position 0 2) (Position 0 2)) "c"])      it "simple deletion" $ do-      diffDiff "abc" "ab" `shouldBe` ([ChangeEvent (Range (Position 0 2) (Position 0 3)) ""])+      diff "abc" "ab" `shouldBe` ([ChangeEvent (Range (Position 0 2) (Position 0 3)) ""])    describe "QuickCheck" $ introduceQuickCheck $ modifyMaxSuccess (const 10000) $ do     prop "Single change" $ \(InsertOrDelete (from, to)) -> verifyDiff from to@@ -29,7 +29,7 @@ verifyDiff :: Text -> Text -> Bool verifyDiff from to = applyChangesText change from == to   where-    change = diffDiff (T.unpack from) (T.unpack to)+    change = diff (T.unpack from) (T.unpack to)  main :: IO () main = runSandwichWithCommandLineArgs defaultOptions spec
test-uni-myers/Spec/UniMyersSpec.hs view
@@ -3,7 +3,7 @@ module Spec.UniMyersSpec (spec) where  import Data.Diff.Types-import Data.Diff.UniMyersShim+import Data.Diff.UniMyers import Data.Text as T import Test.Sandwich import Test.Sandwich.QuickCheck
test/Spec/VectorMyersSpec.hs view
@@ -6,7 +6,7 @@  import Control.Monad.Catch (MonadThrow) import Data.Diff.Types-import Data.Diff.VectorMyers+import Data.Diff.Myers import Data.Text as T import Test.Sandwich import Test.Sandwich.QuickCheck