{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.TreeDiff.Class (
ediff,
ediff',
ToExpr (..),
defaultExprViaShow,
sopToExpr,
) where
import Data.Foldable (toList)
import Data.Proxy (Proxy (..))
import Data.TreeDiff.Expr
import Data.List.Compat (uncons)
import Generics.SOP
(All, All2, ConstructorInfo (..), DatatypeInfo (..), FieldInfo (..),
I (..), K (..), NP (..), SOP (..), constructorInfo, hcliftA2, hcmap,
hcollapse, mapIK)
import Generics.SOP.GGP (GCode, GDatatypeInfo, GFrom, gdatatypeInfo, gfrom)
import GHC.Generics (Generic)
import qualified Data.Map as Map
import Control.Applicative (Const (..), ZipList (..))
import Data.Fixed (Fixed, HasResolution)
import Data.Functor.Identity (Identity (..))
import Data.Int
import Data.List.NonEmpty (NonEmpty (..))
import Data.Void (Void)
import Data.Word
import Numeric.Natural (Natural)
import qualified Data.Monoid as Mon
import qualified Data.Ratio as Ratio
import qualified Data.Semigroup as Semi
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import qualified Data.Tree as Tree
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Time as Time
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Short as SBS
import qualified Data.Scientific as Sci
import qualified Data.UUID.Types as UUID
import qualified Data.Vector as V
import qualified Data.Vector.Primitive as VP
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Unboxed as VU
import Data.Tagged (Tagged (..))
import Data.Hashable (Hashed, unhashed)
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Data.Aeson as Aeson
ediff :: ToExpr a => a -> a -> Edit EditExpr
ediff x y = exprDiff (toExpr x) (toExpr y)
ediff' :: (ToExpr a, ToExpr b) => a -> b -> Edit EditExpr
ediff' x y = exprDiff (toExpr x) (toExpr y)
class ToExpr a where
toExpr :: a -> Expr
default toExpr
:: (Generic a, All2 ToExpr (GCode a), GFrom a, GDatatypeInfo a)
=> a -> Expr
toExpr x = sopToExpr (gdatatypeInfo (Proxy :: Proxy a)) (gfrom x)
listToExpr :: [a] -> Expr
listToExpr = Lst . map toExpr
instance ToExpr Expr where
toExpr = id
defaultExprViaShow :: Show a => a -> Expr
defaultExprViaShow x = App (show x) []
sopToExpr :: (All2 ToExpr xss) => DatatypeInfo xss -> SOP I xss -> Expr
sopToExpr di (SOP xss) = hcollapse $ hcliftA2
(Proxy :: Proxy (All ToExpr))
(\ci xs -> K (sopNPToExpr isNewtype ci xs))
(constructorInfo di)
xss
where
isNewtype = case di of
Newtype {} -> True
ADT {} -> False
sopNPToExpr :: All ToExpr xs => Bool -> ConstructorInfo xs -> NP I xs -> Expr
sopNPToExpr _ (Infix cn _ _) xs = App ("_" ++ cn ++ "_") $ hcollapse $
hcmap (Proxy :: Proxy ToExpr) (mapIK toExpr) xs
sopNPToExpr _ (Constructor cn) xs = App cn $ hcollapse $
hcmap (Proxy :: Proxy ToExpr) (mapIK toExpr) xs
sopNPToExpr True (Record cn _) xs = App cn $ hcollapse $
hcmap (Proxy :: Proxy ToExpr) (mapIK toExpr) xs
sopNPToExpr False (Record cn fi) xs = Rec cn $ Map.fromList $ hcollapse $
hcliftA2 (Proxy :: Proxy ToExpr) mk fi xs
where
mk :: ToExpr x => FieldInfo x -> I x -> K (FieldName, Expr) x
mk (FieldInfo fn) (I x) = K (fn, toExpr x)
instance ToExpr () where toExpr = defaultExprViaShow
instance ToExpr Bool where toExpr = defaultExprViaShow
instance ToExpr Ordering where toExpr = defaultExprViaShow
instance ToExpr Integer where toExpr = defaultExprViaShow
instance ToExpr Natural where toExpr = defaultExprViaShow
instance ToExpr Float where toExpr = defaultExprViaShow
instance ToExpr Double where toExpr = defaultExprViaShow
instance ToExpr Int where toExpr = defaultExprViaShow
instance ToExpr Int8 where toExpr = defaultExprViaShow
instance ToExpr Int16 where toExpr = defaultExprViaShow
instance ToExpr Int32 where toExpr = defaultExprViaShow
instance ToExpr Int64 where toExpr = defaultExprViaShow
instance ToExpr Word where toExpr = defaultExprViaShow
instance ToExpr Word8 where toExpr = defaultExprViaShow
instance ToExpr Word16 where toExpr = defaultExprViaShow
instance ToExpr Word32 where toExpr = defaultExprViaShow
instance ToExpr Word64 where toExpr = defaultExprViaShow
instance ToExpr (Proxy a) where toExpr = defaultExprViaShow
instance ToExpr Char where
toExpr = defaultExprViaShow
listToExpr = stringToExpr "concat" . unconcat uncons
stringToExpr
:: Show a
=> String
-> [a]
-> Expr
stringToExpr _ [] = App "\"\"" []
stringToExpr _ [l] = defaultExprViaShow l
stringToExpr cn ls = App cn [Lst (map defaultExprViaShow ls)]
unconcat :: forall a. (a -> Maybe (Char, a)) -> a -> [String]
unconcat uncons_ = go where
go :: a -> [String]
go xs = case span_ xs of
~(ys, zs)
| null ys -> []
| otherwise -> ys : go zs
span_ :: a -> (String, a)
span_ xs = case uncons_ xs of
Nothing -> ("", xs)
Just ~(x, xs')
| x == '\n' -> ("\n", xs')
| otherwise -> case span_ xs' of
~(ys, zs) -> (x : ys, zs)
instance ToExpr a => ToExpr (Maybe a) where
toExpr Nothing = App "Nothing" []
toExpr (Just x) = App "Just" [toExpr x]
instance (ToExpr a, ToExpr b) => ToExpr (Either a b) where
toExpr (Left x) = App "Left" [toExpr x]
toExpr (Right y) = App "Right" [toExpr y]
instance ToExpr a => ToExpr [a] where
toExpr = listToExpr
instance (ToExpr a, ToExpr b) => ToExpr (a, b) where
toExpr (a, b) = App "_×_" [toExpr a, toExpr b]
instance (ToExpr a, ToExpr b, ToExpr c) => ToExpr (a, b, c) where
toExpr (a, b, c) = App "_×_×_" [toExpr a, toExpr b, toExpr c]
instance (ToExpr a, ToExpr b, ToExpr c, ToExpr d) => ToExpr (a, b, c, d) where
toExpr (a, b, c, d) = App "_×_×_×_" [toExpr a, toExpr b, toExpr c, toExpr d]
instance (ToExpr a, ToExpr b, ToExpr c, ToExpr d, ToExpr e) => ToExpr (a, b, c, d, e) where
toExpr (a, b, c, d, e) = App "_×_×_×_×_" [toExpr a, toExpr b, toExpr c, toExpr d, toExpr e]
instance (ToExpr a, Integral a) => ToExpr (Ratio.Ratio a) where
toExpr r = App "_%_" [ toExpr $ Ratio.numerator r, toExpr $ Ratio.denominator r ]
instance HasResolution a => ToExpr (Fixed a) where toExpr = defaultExprViaShow
instance ToExpr a => ToExpr (Identity a) where
toExpr (Identity x) = App "Identity" [toExpr x]
instance ToExpr a => ToExpr (Const a b)
instance ToExpr a => ToExpr (ZipList a)
instance ToExpr a => ToExpr (NonEmpty a) where
toExpr (x :| xs) = App "NE.fromList" [toExpr (x : xs)]
instance ToExpr Void where
toExpr _ = App "error" [toExpr "Void"]
instance ToExpr a => ToExpr (Mon.Dual a) where
instance ToExpr a => ToExpr (Mon.Sum a) where
instance ToExpr a => ToExpr (Mon.Product a) where
instance ToExpr a => ToExpr (Mon.First a) where
instance ToExpr a => ToExpr (Mon.Last a) where
instance ToExpr a => ToExpr (Semi.Option a) where
instance ToExpr a => ToExpr (Semi.Min a) where
instance ToExpr a => ToExpr (Semi.Max a) where
instance ToExpr a => ToExpr (Semi.First a) where
instance ToExpr a => ToExpr (Semi.Last a) where
instance ToExpr a => ToExpr (Tree.Tree a) where
toExpr (Tree.Node x xs) = App "Node" [toExpr x, toExpr xs]
instance (ToExpr k, ToExpr v) => ToExpr (Map.Map k v) where
toExpr x = App "Map.fromList" [ toExpr $ Map.toList x ]
instance (ToExpr k) => ToExpr (Set.Set k) where
toExpr x = App "Set.fromList" [ toExpr $ Set.toList x ]
instance (ToExpr v) => ToExpr (IntMap.IntMap v) where
toExpr x = App "IntMap.fromList" [ toExpr $ IntMap.toList x ]
instance ToExpr IntSet.IntSet where
toExpr x = App "IntSet.fromList" [ toExpr $ IntSet.toList x ]
instance (ToExpr v) => ToExpr (Seq.Seq v) where
toExpr x = App "Seq.fromList" [ toExpr $ toList x ]
instance ToExpr LT.Text where
toExpr = stringToExpr "LT.concat" . unconcat LT.uncons
instance ToExpr T.Text where
toExpr = stringToExpr "T.concat" . unconcat T.uncons
instance ToExpr Time.Day where
toExpr d = App "Day" [ toExpr (show d) ]
instance ToExpr Time.UTCTime where
toExpr t = App "UTCTime" [ toExpr (show t) ]
instance ToExpr LBS.ByteString where
toExpr = stringToExpr "LBS.concat" . bsUnconcat LBS.null LBS.elemIndex LBS.splitAt
instance ToExpr BS.ByteString where
toExpr = stringToExpr "BS.concat" . bsUnconcat BS.null BS.elemIndex BS.splitAt
instance ToExpr SBS.ShortByteString where
toExpr = stringToExpr "mconcat" . bsUnconcat BS.null BS.elemIndex BS.splitAt . SBS.fromShort
bsUnconcat
:: forall bs int. Num int
=> (bs -> Bool)
-> (Word8 -> bs -> Maybe int)
-> (int -> bs -> (bs, bs))
-> bs
-> [bs]
bsUnconcat null_ elemIndex_ splitAt_ = go where
go :: bs -> [bs]
go bs
| null_ bs = []
| otherwise = case elemIndex_ 10 bs of
Nothing -> [bs]
Just i -> case splitAt_ (i + 1) bs of
(bs0, bs1) -> bs0 : go bs1
instance ToExpr Sci.Scientific where
toExpr s = App "scientific" [ toExpr $ Sci.coefficient s, toExpr $ Sci.base10Exponent s ]
instance ToExpr UUID.UUID where
toExpr u = App "UUID" [ toExpr $ UUID.toString u ]
instance ToExpr a => ToExpr (V.Vector a) where
toExpr x = App "V.fromList" [ toExpr $ V.toList x ]
instance (ToExpr a, VU.Unbox a) => ToExpr (VU.Vector a) where
toExpr x = App "VU.fromList" [ toExpr $ VU.toList x ]
instance (ToExpr a, VS.Storable a) => ToExpr (VS.Vector a) where
toExpr x = App "VS.fromList" [ toExpr $ VS.toList x ]
instance (ToExpr a, VP.Prim a) => ToExpr (VP.Vector a) where
toExpr x = App "VP.fromList" [ toExpr $ VP.toList x ]
instance ToExpr a => ToExpr (Tagged t a) where
toExpr (Tagged x) = App "Tagged" [ toExpr x ]
instance ToExpr a => ToExpr (Hashed a) where
toExpr x = App "hashed" [ toExpr $ unhashed x ]
instance (ToExpr k, ToExpr v) => ToExpr (HM.HashMap k v) where
toExpr x = App "HM.fromList" [ toExpr $ HM.toList x ]
instance (ToExpr k) => ToExpr (HS.HashSet k) where
toExpr x = App "HS.fromList" [ toExpr $ HS.toList x ]
instance ToExpr Aeson.Value