-- | This module uses 'Expr' for richer diffs than based on 'Tree'.
module Test.StateMachine.TreeDiff.Expr (
-- * Types
Expr (..),
ConstructorName,
FieldName,
EditExpr (..),
Edit (..),
exprDiff,
) where
import Prelude ()
import Prelude.Compat
import Data.Map (Map)
import Test.StateMachine.TreeDiff.List
import qualified Data.Map as Map
import qualified Test.QuickCheck as QC
-- | Constructor name is a string
type ConstructorName = String
--
-- | Record field name is a string too.
type FieldName = String
-- | A untyped Haskell-like expression.
--
-- Having richer structure than just 'Tree' allows to have richer diffs.
data Expr
= App ConstructorName [Expr] -- ^ application
| Rec ConstructorName (Map FieldName Expr) -- ^ record constructor
| Lst [Expr] -- ^ list constructor
deriving (Eq, Show)
instance QC.Arbitrary Expr where
arbitrary = QC.scale (min 25) $ QC.sized arb where
arb n | n <= 0 = QC.oneof
[ (`App` []) <$> arbName
, (`Rec` mempty) <$> arbName
]
arb n | otherwise = do
n' <- QC.choose (0, n `div` 3)
QC.oneof
[ App <$> arbName <*> QC.liftArbitrary (arb n')
, Rec <$> arbName <*> QC.liftArbitrary (arb n')
, Lst <$> QC.liftArbitrary (arb n')
]
shrink (Lst es) = es
++ [ Lst es' | es' <- QC.shrink es ]
shrink (Rec n fs) = Map.elems fs
++ [ Rec n' fs | n' <- QC.shrink n ]
++ [ Rec n fs' | fs' <- QC.shrink fs ]
shrink (App n es) = es
++ [ App n' es | n' <- QC.shrink n ]
++ [ App n es' | es' <- QC.shrink es ]
arbName :: QC.Gen String
arbName = QC.frequency
[ (10, QC.liftArbitrary $ QC.elements $ ['a'..'z'] ++ ['0' .. '9'] ++ "+-_:")
, (1, show <$> (QC.arbitrary :: QC.Gen String))
, (1, QC.arbitrary)
, (1, QC.elements ["_×_", "_×_×_", "_×_×_×_"])
]
-- | Diff two 'Expr'.
--
-- For examples see 'ediff' in "Data.TreeDiff.Class".
exprDiff :: Expr -> Expr -> Edit EditExpr
exprDiff = impl
where
impl ea eb | ea == eb = Cpy (EditExp ea)
impl ea@(App a as) eb@(App b bs)
| a == b = Cpy $ EditApp a (map recurse (diffBy (==) as bs))
| otherwise = Swp (EditExp ea) (EditExp eb)
impl ea@(Rec a as) eb@(Rec b bs)
| a == b = Cpy $ EditRec a $ Map.unions [inter, onlyA, onlyB]
| otherwise = Swp (EditExp ea) (EditExp eb)
where
inter = Map.intersectionWith exprDiff as bs
onlyA = fmap (Del . EditExp) (Map.difference as inter)
onlyB = fmap (Ins . EditExp) (Map.difference bs inter)
impl (Lst as) (Lst bs) =
Cpy $ EditLst (map recurse (diffBy (==) as bs))
-- If higher level doesn't match, just swap.
impl a b = Swp (EditExp a) (EditExp b)
recurse (Ins x) = Ins (EditExp x)
recurse (Del y) = Del (EditExp y)
recurse (Cpy z) = Cpy (EditExp z)
recurse (Swp x y) = impl x y
-- | Type used in the result of 'ediff'.
data EditExpr
= EditApp ConstructorName [Edit EditExpr]
| EditRec ConstructorName (Map FieldName (Edit EditExpr))
| EditLst [Edit EditExpr]
| EditExp Expr -- ^ unchanged tree
deriving Show