mirror of
https://github.com/github/semantic.git
synced 2024-12-22 06:11:49 +03:00
Merge branch 'master' into charliesome/value-ref
This commit is contained in:
commit
f19dabc20b
@ -73,11 +73,11 @@ library
|
|||||||
, Data.File
|
, Data.File
|
||||||
, Data.Functor.Both
|
, Data.Functor.Both
|
||||||
, Data.Functor.Classes.Generic
|
, Data.Functor.Classes.Generic
|
||||||
|
, Data.Graph
|
||||||
, Data.JSON.Fields
|
, Data.JSON.Fields
|
||||||
, Data.Language
|
, Data.Language
|
||||||
, Data.Map.Monoidal
|
, Data.Map.Monoidal
|
||||||
, Data.Mergeable
|
, Data.Mergeable
|
||||||
, Data.Output
|
|
||||||
, Data.Patch
|
, Data.Patch
|
||||||
, Data.Range
|
, Data.Range
|
||||||
, Data.Record
|
, Data.Record
|
||||||
@ -131,14 +131,14 @@ library
|
|||||||
, Parsing.TreeSitter
|
, Parsing.TreeSitter
|
||||||
, Paths_semantic
|
, Paths_semantic
|
||||||
-- Rendering formats
|
-- Rendering formats
|
||||||
, Rendering.DOT
|
, Rendering.Graph
|
||||||
, Rendering.Imports
|
, Rendering.Imports
|
||||||
, Rendering.JSON
|
, Rendering.JSON
|
||||||
, Rendering.Renderer
|
, Rendering.Renderer
|
||||||
, Rendering.SExpression
|
|
||||||
, Rendering.Symbol
|
, Rendering.Symbol
|
||||||
, Rendering.TOC
|
, Rendering.TOC
|
||||||
-- High-level flow & operational functionality (logging, stats, etc.)
|
-- High-level flow & operational functionality (logging, stats, etc.)
|
||||||
|
, Semantic.AST
|
||||||
, Semantic.CLI
|
, Semantic.CLI
|
||||||
, Semantic.Diff
|
, Semantic.Diff
|
||||||
, Semantic.Distribute
|
, Semantic.Distribute
|
||||||
@ -151,7 +151,11 @@ library
|
|||||||
, Semantic.Task
|
, Semantic.Task
|
||||||
, Semantic.Telemetry
|
, Semantic.Telemetry
|
||||||
, Semantic.Util
|
, Semantic.Util
|
||||||
-- Custom Prelude
|
-- Serialization
|
||||||
|
, Serializing.DOT
|
||||||
|
, Serializing.Format
|
||||||
|
, Serializing.SExpression
|
||||||
|
-- Custom Prelude
|
||||||
other-modules: Prologue
|
other-modules: Prologue
|
||||||
build-depends: base >= 4.8 && < 5
|
build-depends: base >= 4.8 && < 5
|
||||||
, aeson
|
, aeson
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies, TypeOperators, UndecidableInstances #-}
|
{-# LANGUAGE TypeFamilies, TypeOperators, UndecidableInstances #-}
|
||||||
module Analysis.Abstract.Graph
|
module Analysis.Abstract.Graph
|
||||||
( Graph(..)
|
( Graph(..)
|
||||||
, Vertex(..)
|
, Vertex(..)
|
||||||
, renderGraph
|
, style
|
||||||
, appendGraph
|
, appendGraph
|
||||||
, variableDefinition
|
, variableDefinition
|
||||||
, moduleInclusion
|
, moduleInclusion
|
||||||
@ -14,9 +14,6 @@ module Analysis.Abstract.Graph
|
|||||||
, graphing
|
, graphing
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Algebra.Graph as G
|
|
||||||
import qualified Algebra.Graph.Class as GC
|
|
||||||
import Algebra.Graph.Class hiding (Graph, Vertex)
|
|
||||||
import Algebra.Graph.Export.Dot hiding (vertexName)
|
import Algebra.Graph.Export.Dot hiding (vertexName)
|
||||||
import Control.Abstract
|
import Control.Abstract
|
||||||
import Data.Abstract.Address
|
import Data.Abstract.Address
|
||||||
@ -25,17 +22,13 @@ import Data.Abstract.FreeVariables
|
|||||||
import Data.Abstract.Module (Module(moduleInfo), ModuleInfo(..))
|
import Data.Abstract.Module (Module(moduleInfo), ModuleInfo(..))
|
||||||
import Data.Abstract.Package (PackageInfo(..))
|
import Data.Abstract.Package (PackageInfo(..))
|
||||||
import Data.Aeson hiding (Result)
|
import Data.Aeson hiding (Result)
|
||||||
|
import Data.ByteString.Builder
|
||||||
import qualified Data.ByteString.Char8 as BC
|
import qualified Data.ByteString.Char8 as BC
|
||||||
import Data.ByteString.Lazy (toStrict)
|
import Data.Graph
|
||||||
import Data.Output
|
|
||||||
import qualified Data.Syntax as Syntax
|
import qualified Data.Syntax as Syntax
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import Data.Text.Encoding as T
|
import Data.Text.Encoding as T
|
||||||
import Prologue hiding (empty, packageName)
|
import Prologue hiding (packageName)
|
||||||
|
|
||||||
-- | The graph of function variableDefinitions to symbols used in a given program.
|
|
||||||
newtype Graph = Graph { unGraph :: G.Graph Vertex }
|
|
||||||
deriving (Eq, GC.Graph, Show)
|
|
||||||
|
|
||||||
-- | A vertex of some specific type.
|
-- | A vertex of some specific type.
|
||||||
data Vertex
|
data Vertex
|
||||||
@ -44,12 +37,8 @@ data Vertex
|
|||||||
| Variable { vertexName :: ByteString }
|
| Variable { vertexName :: ByteString }
|
||||||
deriving (Eq, Ord, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
-- | Render a 'Graph' to a 'ByteString' in DOT notation.
|
style :: Style Vertex Builder
|
||||||
renderGraph :: Graph -> ByteString
|
style = (defaultStyle (byteString . vertexName))
|
||||||
renderGraph = export style . unGraph
|
|
||||||
|
|
||||||
style :: Style Vertex ByteString
|
|
||||||
style = (defaultStyle vertexName)
|
|
||||||
{ vertexAttributes = vertexAttributes
|
{ vertexAttributes = vertexAttributes
|
||||||
, edgeAttributes = edgeAttributes
|
, edgeAttributes = edgeAttributes
|
||||||
}
|
}
|
||||||
@ -68,7 +57,7 @@ graphingTerms :: ( Element Syntax.Identifier syntax
|
|||||||
, Reader ModuleInfo
|
, Reader ModuleInfo
|
||||||
, Reader PackageInfo
|
, Reader PackageInfo
|
||||||
, State (Environment (Located location) value)
|
, State (Environment (Located location) value)
|
||||||
, State Graph
|
, State (Graph Vertex)
|
||||||
] effects
|
] effects
|
||||||
, term ~ Term (Sum syntax) ann
|
, term ~ Term (Sum syntax) ann
|
||||||
)
|
)
|
||||||
@ -85,7 +74,7 @@ graphingTerms recur term@(In _ syntax) = do
|
|||||||
-- | Add vertices to the graph for 'LoadError's.
|
-- | Add vertices to the graph for 'LoadError's.
|
||||||
graphingLoadErrors :: Members '[ Reader ModuleInfo
|
graphingLoadErrors :: Members '[ Reader ModuleInfo
|
||||||
, Resumable (LoadError location value)
|
, Resumable (LoadError location value)
|
||||||
, State Graph
|
, State (Graph Vertex)
|
||||||
] effects
|
] effects
|
||||||
=> SubtermAlgebra (Base term) term (Evaluator location value effects a)
|
=> SubtermAlgebra (Base term) term (Evaluator location value effects a)
|
||||||
-> SubtermAlgebra (Base term) term (Evaluator location value effects a)
|
-> SubtermAlgebra (Base term) term (Evaluator location value effects a)
|
||||||
@ -94,7 +83,7 @@ graphingLoadErrors recur term = recur term `resumeLoadError` (\ (ModuleNotFound
|
|||||||
-- | Add vertices to the graph for evaluated modules and the packages containing them.
|
-- | Add vertices to the graph for evaluated modules and the packages containing them.
|
||||||
graphingModules :: Members '[ Reader ModuleInfo
|
graphingModules :: Members '[ Reader ModuleInfo
|
||||||
, Reader PackageInfo
|
, Reader PackageInfo
|
||||||
, State Graph
|
, State (Graph Vertex)
|
||||||
] effects
|
] effects
|
||||||
=> SubtermAlgebra Module term (Evaluator location value effects a)
|
=> SubtermAlgebra Module term (Evaluator location value effects a)
|
||||||
-> SubtermAlgebra Module term (Evaluator location value effects a)
|
-> SubtermAlgebra Module term (Evaluator location value effects a)
|
||||||
@ -105,16 +94,16 @@ graphingModules recur m = do
|
|||||||
recur m
|
recur m
|
||||||
|
|
||||||
|
|
||||||
packageGraph :: PackageInfo -> Graph
|
packageGraph :: PackageInfo -> Graph Vertex
|
||||||
packageGraph = vertex . Package . unName . packageName
|
packageGraph = vertex . Package . unName . packageName
|
||||||
|
|
||||||
moduleGraph :: ModuleInfo -> Graph
|
moduleGraph :: ModuleInfo -> Graph Vertex
|
||||||
moduleGraph = vertex . Module . BC.pack . modulePath
|
moduleGraph = vertex . Module . BC.pack . modulePath
|
||||||
|
|
||||||
-- | Add an edge from the current package to the passed vertex.
|
-- | Add an edge from the current package to the passed vertex.
|
||||||
packageInclusion :: ( Effectful m
|
packageInclusion :: ( Effectful m
|
||||||
, Members '[ Reader PackageInfo
|
, Members '[ Reader PackageInfo
|
||||||
, State Graph
|
, State (Graph Vertex)
|
||||||
] effects
|
] effects
|
||||||
, Monad (m effects)
|
, Monad (m effects)
|
||||||
)
|
)
|
||||||
@ -127,7 +116,7 @@ packageInclusion v = do
|
|||||||
-- | Add an edge from the current module to the passed vertex.
|
-- | Add an edge from the current module to the passed vertex.
|
||||||
moduleInclusion :: ( Effectful m
|
moduleInclusion :: ( Effectful m
|
||||||
, Members '[ Reader ModuleInfo
|
, Members '[ Reader ModuleInfo
|
||||||
, State Graph
|
, State (Graph Vertex)
|
||||||
] effects
|
] effects
|
||||||
, Monad (m effects)
|
, Monad (m effects)
|
||||||
)
|
)
|
||||||
@ -140,46 +129,18 @@ moduleInclusion v = do
|
|||||||
-- | Add an edge from the passed variable name to the module it originated within.
|
-- | Add an edge from the passed variable name to the module it originated within.
|
||||||
variableDefinition :: ( Member (Reader (Environment (Located location) value)) effects
|
variableDefinition :: ( Member (Reader (Environment (Located location) value)) effects
|
||||||
, Member (State (Environment (Located location) value)) effects
|
, Member (State (Environment (Located location) value)) effects
|
||||||
, Member (State Graph) effects
|
, Member (State (Graph Vertex)) effects
|
||||||
)
|
)
|
||||||
=> Name
|
=> Name
|
||||||
-> Evaluator (Located location) value effects ()
|
-> Evaluator (Located location) value effects ()
|
||||||
variableDefinition name = do
|
variableDefinition name = do
|
||||||
graph <- maybe empty (moduleGraph . locationModule . unAddress) <$> lookupEnv name
|
graph <- maybe lowerBound (moduleGraph . locationModule . unAddress) <$> lookupEnv name
|
||||||
appendGraph (vertex (Variable (unName name)) `connect` graph)
|
appendGraph (vertex (Variable (unName name)) `connect` graph)
|
||||||
|
|
||||||
appendGraph :: (Effectful m, Member (State Graph) effects) => Graph -> m effects ()
|
appendGraph :: (Effectful m, Member (State (Graph Vertex)) effects) => Graph Vertex -> m effects ()
|
||||||
appendGraph = modify' . (<>)
|
appendGraph = modify' . (<>)
|
||||||
|
|
||||||
|
|
||||||
instance Semigroup Graph where
|
|
||||||
(<>) = overlay
|
|
||||||
|
|
||||||
instance Monoid Graph where
|
|
||||||
mempty = empty
|
|
||||||
mappend = (<>)
|
|
||||||
|
|
||||||
instance Ord Graph where
|
|
||||||
compare (Graph G.Empty) (Graph G.Empty) = EQ
|
|
||||||
compare (Graph G.Empty) _ = LT
|
|
||||||
compare _ (Graph G.Empty) = GT
|
|
||||||
compare (Graph (G.Vertex a)) (Graph (G.Vertex b)) = compare a b
|
|
||||||
compare (Graph (G.Vertex _)) _ = LT
|
|
||||||
compare _ (Graph (G.Vertex _)) = GT
|
|
||||||
compare (Graph (G.Overlay a1 a2)) (Graph (G.Overlay b1 b2)) = (compare `on` Graph) a1 b1 <> (compare `on` Graph) a2 b2
|
|
||||||
compare (Graph (G.Overlay _ _)) _ = LT
|
|
||||||
compare _ (Graph (G.Overlay _ _)) = GT
|
|
||||||
compare (Graph (G.Connect a1 a2)) (Graph (G.Connect b1 b2)) = (compare `on` Graph) a1 b1 <> (compare `on` Graph) a2 b2
|
|
||||||
|
|
||||||
instance Output Graph where
|
|
||||||
toOutput = toStrict . (<> "\n") . encode
|
|
||||||
|
|
||||||
instance ToJSON Graph where
|
|
||||||
toJSON Graph{..} = object [ "vertices" .= vertices, "edges" .= edges ]
|
|
||||||
where
|
|
||||||
vertices = toJSON (G.vertexList unGraph)
|
|
||||||
edges = fmap (\(a, b) -> object [ "source" .= vertexToText a, "target" .= vertexToText b ]) (G.edgeList unGraph)
|
|
||||||
|
|
||||||
instance ToJSON Vertex where
|
instance ToJSON Vertex where
|
||||||
toJSON v = object [ "name" .= vertexToText v, "type" .= vertexToType v ]
|
toJSON v = object [ "name" .= vertexToText v, "type" .= vertexToType v ]
|
||||||
|
|
||||||
@ -192,5 +153,5 @@ vertexToType Module{} = "module"
|
|||||||
vertexToType Variable{} = "variable"
|
vertexToType Variable{} = "variable"
|
||||||
|
|
||||||
|
|
||||||
graphing :: Effectful m => m (State Graph ': effects) result -> m effects (result, Graph)
|
graphing :: Effectful m => m (State (Graph Vertex) ': effects) result -> m effects (result, Graph Vertex)
|
||||||
graphing = runState mempty
|
graphing = runState mempty
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, UndecidableInstances #-}
|
{-# LANGUAGE ScopedTypeVariables, TypeFamilies, UndecidableInstances #-}
|
||||||
module Analysis.CallGraph
|
module Analysis.CallGraph
|
||||||
( CallGraph(..)
|
( CallGraph
|
||||||
, renderCallGraph
|
, renderCallGraph
|
||||||
, buildCallGraph
|
, buildCallGraph
|
||||||
, CallGraphAlgebra(..)
|
, CallGraphAlgebra(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Algebra.Graph as G
|
|
||||||
import Algebra.Graph.Class
|
|
||||||
import Algebra.Graph.Export.Dot
|
import Algebra.Graph.Export.Dot
|
||||||
import Data.Abstract.Evaluatable
|
import Data.Abstract.Evaluatable
|
||||||
import Data.Sum
|
import Data.Abstract.FreeVariables
|
||||||
|
import Data.Graph
|
||||||
import qualified Data.Syntax as Syntax
|
import qualified Data.Syntax as Syntax
|
||||||
import qualified Data.Syntax.Declaration as Declaration
|
import qualified Data.Syntax.Declaration as Declaration
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import Prologue hiding (empty)
|
import Prologue
|
||||||
|
|
||||||
-- | The graph of function definitions to symbols used in a given program.
|
type CallGraph = Graph Name
|
||||||
newtype CallGraph = CallGraph { unCallGraph :: G.Graph Name }
|
|
||||||
deriving (Eq, Graph, Show)
|
|
||||||
|
|
||||||
-- | Build the 'CallGraph' for a 'Term' recursively.
|
-- | Build the 'CallGraph' for a 'Term' recursively.
|
||||||
buildCallGraph :: (CallGraphAlgebra syntax, FreeVariables1 syntax, Functor syntax) => Term syntax ann -> [Name] -> CallGraph
|
buildCallGraph :: (CallGraphAlgebra syntax, FreeVariables1 syntax, Functor syntax) => Term syntax ann -> [Name] -> CallGraph
|
||||||
@ -27,7 +24,7 @@ buildCallGraph = foldSubterms callGraphAlgebra
|
|||||||
|
|
||||||
-- | Render a 'CallGraph' to a 'ByteString' in DOT notation.
|
-- | Render a 'CallGraph' to a 'ByteString' in DOT notation.
|
||||||
renderCallGraph :: CallGraph -> ByteString
|
renderCallGraph :: CallGraph -> ByteString
|
||||||
renderCallGraph = export (defaultStyle unName) . unCallGraph
|
renderCallGraph = export (defaultStyle unName)
|
||||||
|
|
||||||
|
|
||||||
-- | Types which contribute to a 'CallGraph'. There is exactly one instance of this typeclass; customizing the 'CallGraph's for a new type is done by defining an instance of 'CustomCallGraphAlgebra' instead.
|
-- | Types which contribute to a 'CallGraph'. There is exactly one instance of this typeclass; customizing the 'CallGraph's for a new type is done by defining an instance of 'CustomCallGraphAlgebra' instead.
|
||||||
@ -56,7 +53,7 @@ instance CustomCallGraphAlgebra Declaration.Method where
|
|||||||
-- | 'Syntax.Identifier's produce a vertex iff it’s unbound in the 'Set'.
|
-- | 'Syntax.Identifier's produce a vertex iff it’s unbound in the 'Set'.
|
||||||
instance CustomCallGraphAlgebra Syntax.Identifier where
|
instance CustomCallGraphAlgebra Syntax.Identifier where
|
||||||
customCallGraphAlgebra (Syntax.Identifier name) bound
|
customCallGraphAlgebra (Syntax.Identifier name) bound
|
||||||
| name `elem` bound = empty
|
| name `elem` bound = lowerBound
|
||||||
| otherwise = vertex name
|
| otherwise = vertex name
|
||||||
|
|
||||||
instance Apply CallGraphAlgebra syntaxes => CustomCallGraphAlgebra (Sum syntaxes) where
|
instance Apply CallGraphAlgebra syntaxes => CustomCallGraphAlgebra (Sum syntaxes) where
|
||||||
@ -90,22 +87,3 @@ type family CallGraphAlgebraStrategy syntax where
|
|||||||
CallGraphAlgebraStrategy (Sum fs) = 'Custom
|
CallGraphAlgebraStrategy (Sum fs) = 'Custom
|
||||||
CallGraphAlgebraStrategy (TermF f a) = 'Custom
|
CallGraphAlgebraStrategy (TermF f a) = 'Custom
|
||||||
CallGraphAlgebraStrategy a = 'Default
|
CallGraphAlgebraStrategy a = 'Default
|
||||||
|
|
||||||
instance Semigroup CallGraph where
|
|
||||||
(<>) = overlay
|
|
||||||
|
|
||||||
instance Monoid CallGraph where
|
|
||||||
mempty = empty
|
|
||||||
mappend = (<>)
|
|
||||||
|
|
||||||
instance Ord CallGraph where
|
|
||||||
compare (CallGraph G.Empty) (CallGraph G.Empty) = EQ
|
|
||||||
compare (CallGraph G.Empty) _ = LT
|
|
||||||
compare _ (CallGraph G.Empty) = GT
|
|
||||||
compare (CallGraph (G.Vertex a)) (CallGraph (G.Vertex b)) = compare a b
|
|
||||||
compare (CallGraph (G.Vertex _)) _ = LT
|
|
||||||
compare _ (CallGraph (G.Vertex _)) = GT
|
|
||||||
compare (CallGraph (G.Overlay a1 a2)) (CallGraph (G.Overlay b1 b2)) = (compare `on` CallGraph) a1 b1 <> (compare `on` CallGraph) a2 b2
|
|
||||||
compare (CallGraph (G.Overlay _ _)) _ = LT
|
|
||||||
compare _ (CallGraph (G.Overlay _ _)) = GT
|
|
||||||
compare (CallGraph (G.Connect a1 a2)) (CallGraph (G.Connect b1 b2)) = (compare `on` CallGraph) a1 b1 <> (compare `on` CallGraph) a2 b2
|
|
||||||
|
@ -36,15 +36,12 @@ class ConstructorName syntax where
|
|||||||
instance (ConstructorNameStrategy syntax ~ strategy, ConstructorNameWithStrategy strategy syntax) => ConstructorName syntax where
|
instance (ConstructorNameStrategy syntax ~ strategy, ConstructorNameWithStrategy strategy syntax) => ConstructorName syntax where
|
||||||
constructorName = constructorNameWithStrategy (Proxy :: Proxy strategy)
|
constructorName = constructorNameWithStrategy (Proxy :: Proxy strategy)
|
||||||
|
|
||||||
class CustomConstructorName syntax where
|
instance Apply ConstructorName fs => ConstructorNameWithStrategy 'Custom (Sum fs) where
|
||||||
customConstructorName :: syntax a -> String
|
constructorNameWithStrategy _ = apply @ConstructorName constructorName
|
||||||
|
|
||||||
instance Apply ConstructorName fs => CustomConstructorName (Sum fs) where
|
instance ConstructorNameWithStrategy 'Custom [] where
|
||||||
customConstructorName = apply @ConstructorName constructorName
|
constructorNameWithStrategy _ [] = "[]"
|
||||||
|
constructorNameWithStrategy _ _ = ""
|
||||||
instance CustomConstructorName [] where
|
|
||||||
customConstructorName [] = "[]"
|
|
||||||
customConstructorName _ = ""
|
|
||||||
|
|
||||||
data Strategy = Default | Custom
|
data Strategy = Default | Custom
|
||||||
|
|
||||||
@ -59,9 +56,6 @@ class ConstructorNameWithStrategy (strategy :: Strategy) syntax where
|
|||||||
instance (Generic1 syntax, GConstructorName (Rep1 syntax)) => ConstructorNameWithStrategy 'Default syntax where
|
instance (Generic1 syntax, GConstructorName (Rep1 syntax)) => ConstructorNameWithStrategy 'Default syntax where
|
||||||
constructorNameWithStrategy _ = gconstructorName . from1
|
constructorNameWithStrategy _ = gconstructorName . from1
|
||||||
|
|
||||||
instance CustomConstructorName syntax => ConstructorNameWithStrategy 'Custom syntax where
|
|
||||||
constructorNameWithStrategy _ = customConstructorName
|
|
||||||
|
|
||||||
|
|
||||||
class GConstructorName f where
|
class GConstructorName f where
|
||||||
gconstructorName :: f a -> String
|
gconstructorName :: f a -> String
|
||||||
|
57
src/Data/Graph.hs
Normal file
57
src/Data/Graph.hs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies, UndecidableInstances #-}
|
||||||
|
module Data.Graph
|
||||||
|
( Graph(..)
|
||||||
|
, Class.overlay
|
||||||
|
, Class.connect
|
||||||
|
, Class.vertex
|
||||||
|
, Lower(..)
|
||||||
|
, simplify
|
||||||
|
) where
|
||||||
|
|
||||||
|
import qualified Algebra.Graph as G
|
||||||
|
import qualified Algebra.Graph.Class as Class
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Semilattice.Lower
|
||||||
|
import Prologue
|
||||||
|
|
||||||
|
-- | An algebraic graph with 'Ord', 'Semigroup', and 'Monoid' instances.
|
||||||
|
newtype Graph vertex = Graph (G.Graph vertex)
|
||||||
|
deriving (Eq, Foldable, Functor, Class.Graph, Show, Class.ToGraph, Traversable)
|
||||||
|
|
||||||
|
|
||||||
|
simplify :: Ord vertex => Graph vertex -> Graph vertex
|
||||||
|
simplify (Graph graph) = Graph (G.simplify graph)
|
||||||
|
|
||||||
|
|
||||||
|
instance Lower (Graph vertex) where
|
||||||
|
lowerBound = Class.empty
|
||||||
|
|
||||||
|
instance Semigroup (Graph vertex) where
|
||||||
|
(<>) = Class.overlay
|
||||||
|
|
||||||
|
instance Monoid (Graph vertex) where
|
||||||
|
mempty = Class.empty
|
||||||
|
mappend = (<>)
|
||||||
|
|
||||||
|
instance Ord vertex => Ord (Graph vertex) where
|
||||||
|
compare (Graph G.Empty) (Graph G.Empty) = EQ
|
||||||
|
compare (Graph G.Empty) _ = LT
|
||||||
|
compare _ (Graph G.Empty) = GT
|
||||||
|
compare (Graph (G.Vertex a)) (Graph (G.Vertex b)) = compare a b
|
||||||
|
compare (Graph (G.Vertex _)) _ = LT
|
||||||
|
compare _ (Graph (G.Vertex _)) = GT
|
||||||
|
compare (Graph (G.Overlay a1 a2)) (Graph (G.Overlay b1 b2)) = (compare `on` Graph) a1 b1 <> (compare `on` Graph) a2 b2
|
||||||
|
compare (Graph (G.Overlay _ _)) _ = LT
|
||||||
|
compare _ (Graph (G.Overlay _ _)) = GT
|
||||||
|
compare (Graph (G.Connect a1 a2)) (Graph (G.Connect b1 b2)) = (compare `on` Graph) a1 b1 <> (compare `on` Graph) a2 b2
|
||||||
|
|
||||||
|
|
||||||
|
instance (Ord vertex, ToJSON vertex) => ToJSON (Graph vertex) where
|
||||||
|
toJSON (Graph graph) = object ["vertices" .= G.vertexList graph, "edges" .= (JSONEdge <$> G.edgeList graph)]
|
||||||
|
toEncoding (Graph graph) = pairs ("vertices" .= G.vertexList graph <> "edges" .= (JSONEdge <$> G.edgeList graph))
|
||||||
|
|
||||||
|
newtype JSONEdge vertex = JSONEdge (vertex, vertex)
|
||||||
|
|
||||||
|
instance ToJSON vertex => ToJSON (JSONEdge vertex) where
|
||||||
|
toJSON (JSONEdge (a, b)) = object ["source" .= a, "target" .= b]
|
||||||
|
toEncoding (JSONEdge (a, b)) = pairs ("source" .= a <> "target" .= b)
|
@ -3,12 +3,14 @@
|
|||||||
module Data.Map.Monoidal
|
module Data.Map.Monoidal
|
||||||
( Map
|
( Map
|
||||||
, lookup
|
, lookup
|
||||||
|
, singleton
|
||||||
, size
|
, size
|
||||||
, insert
|
, insert
|
||||||
, filterWithKey
|
, filterWithKey
|
||||||
, module Reducer
|
, module Reducer
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Data.Aeson (ToJSON)
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import Data.Semigroup.Reducer as Reducer
|
import Data.Semigroup.Reducer as Reducer
|
||||||
import Data.Semilattice.Lower
|
import Data.Semilattice.Lower
|
||||||
@ -16,12 +18,16 @@ import Prelude hiding (lookup)
|
|||||||
import Prologue hiding (Map)
|
import Prologue hiding (Map)
|
||||||
|
|
||||||
newtype Map key value = Map (Map.Map key value)
|
newtype Map key value = Map (Map.Map key value)
|
||||||
deriving (Eq, Eq1, Eq2, Foldable, Functor, Ord, Ord1, Ord2, Show, Show1, Show2, Traversable)
|
deriving (Eq, Eq1, Eq2, Foldable, Functor, Ord, Ord1, Ord2, Show, Show1, Show2, ToJSON, Traversable)
|
||||||
|
|
||||||
unMap :: Map key value -> Map.Map key value
|
unMap :: Map key value -> Map.Map key value
|
||||||
unMap (Map map) = map
|
unMap (Map map) = map
|
||||||
|
|
||||||
|
|
||||||
|
singleton :: key -> value -> Map key value
|
||||||
|
singleton k v = Map (Map.singleton k v)
|
||||||
|
|
||||||
|
|
||||||
lookup :: Ord key => key -> Map key value -> Maybe value
|
lookup :: Ord key => key -> Map key value -> Maybe value
|
||||||
lookup key = Map.lookup key . unMap
|
lookup key = Map.lookup key . unMap
|
||||||
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
module Data.Output where
|
|
||||||
|
|
||||||
import Prologue
|
|
||||||
import Data.Aeson (Value, encode)
|
|
||||||
import Data.ByteString.Lazy (toStrict)
|
|
||||||
import Data.Text (Text, intercalate)
|
|
||||||
import Data.Text.Encoding (encodeUtf8)
|
|
||||||
|
|
||||||
class Monoid o => Output o where
|
|
||||||
toOutput :: o -> ByteString
|
|
||||||
|
|
||||||
instance Output ByteString where
|
|
||||||
toOutput s = s
|
|
||||||
|
|
||||||
instance Output [Text] where
|
|
||||||
toOutput = encodeUtf8 . intercalate "\n"
|
|
||||||
|
|
||||||
instance Output (Map Text Value) where
|
|
||||||
toOutput = toStrict . (<> "\n") . encode
|
|
||||||
|
|
||||||
instance Output [Value] where
|
|
||||||
toOutput = toStrict . (<> "\n") . encode
|
|
@ -1,17 +1,18 @@
|
|||||||
{-# LANGUAGE DataKinds, GADTs, RankNTypes, ScopedTypeVariables, TypeOperators #-}
|
{-# LANGUAGE DataKinds, GADTs, RankNTypes, ScopedTypeVariables, TypeOperators #-}
|
||||||
module Diffing.Interpreter
|
module Diffing.Interpreter
|
||||||
( diffTerms
|
( diffTerms
|
||||||
|
, diffTermPair
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Prologue
|
|
||||||
import Data.Align.Generic (galignWith)
|
|
||||||
import Analysis.Decorator
|
import Analysis.Decorator
|
||||||
import Control.Monad.Free.Freer
|
import Control.Monad.Free.Freer
|
||||||
|
import Data.Align.Generic (galignWith)
|
||||||
import Data.Diff
|
import Data.Diff
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import Diffing.Algorithm
|
import Diffing.Algorithm
|
||||||
import Diffing.Algorithm.RWS
|
import Diffing.Algorithm.RWS
|
||||||
|
import Prologue
|
||||||
|
|
||||||
-- | Diff two à la carte terms recursively.
|
-- | Diff two à la carte terms recursively.
|
||||||
diffTerms :: (Diffable syntax, Eq1 syntax, GAlign syntax, Show1 syntax, Traversable syntax)
|
diffTerms :: (Diffable syntax, Eq1 syntax, GAlign syntax, Show1 syntax, Traversable syntax)
|
||||||
@ -22,6 +23,11 @@ diffTerms t1 t2 = stripDiff (fromMaybe (replacing t1' t2') (runAlgorithm (diff t
|
|||||||
where (t1', t2') = ( defaultFeatureVectorDecorator constructorNameAndConstantFields t1
|
where (t1', t2') = ( defaultFeatureVectorDecorator constructorNameAndConstantFields t1
|
||||||
, defaultFeatureVectorDecorator constructorNameAndConstantFields t2)
|
, defaultFeatureVectorDecorator constructorNameAndConstantFields t2)
|
||||||
|
|
||||||
|
-- | Diff a 'These' of terms.
|
||||||
|
diffTermPair :: (Diffable syntax, Eq1 syntax, GAlign syntax, Show1 syntax, Traversable syntax) => These (Term syntax (Record fields1)) (Term syntax (Record fields2)) -> Diff syntax (Record fields1) (Record fields2)
|
||||||
|
diffTermPair = these deleting inserting diffTerms
|
||||||
|
|
||||||
|
|
||||||
-- | Run an 'Algorithm' to completion in an 'Alternative' context using the supplied comparability & equivalence relations.
|
-- | Run an 'Algorithm' to completion in an 'Alternative' context using the supplied comparability & equivalence relations.
|
||||||
runAlgorithm :: forall syntax fields1 fields2 m result
|
runAlgorithm :: forall syntax fields1 fields2 m result
|
||||||
. (Diffable syntax, Eq1 syntax, GAlign syntax, Traversable syntax, Alternative m, Monad m)
|
. (Diffable syntax, Eq1 syntax, GAlign syntax, Traversable syntax, Alternative m, Monad m)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{-# LANGUAGE ConstraintKinds, DataKinds, GADTs, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators #-}
|
{-# LANGUAGE AllowAmbiguousTypes, ConstraintKinds, GADTs, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators #-}
|
||||||
module Parsing.Parser
|
module Parsing.Parser
|
||||||
( Parser(..)
|
( Parser(..)
|
||||||
, SomeParser(..)
|
, SomeTerm(..)
|
||||||
|
, withSomeTerm
|
||||||
, SomeAnalysisParser(..)
|
, SomeAnalysisParser(..)
|
||||||
, SomeASTParser(..)
|
, SomeASTParser(..)
|
||||||
, someParser
|
, someParser
|
||||||
@ -83,7 +84,7 @@ someAnalysisParser _ l = error $ "Analysis not supported for: " <> show
|
|||||||
-- | A parser from 'Source' onto some term type.
|
-- | A parser from 'Source' onto some term type.
|
||||||
data Parser term where
|
data Parser term where
|
||||||
-- | A parser producing 'AST' using a 'TS.Language'.
|
-- | A parser producing 'AST' using a 'TS.Language'.
|
||||||
ASTParser :: (Bounded grammar, Enum grammar) => Ptr TS.Language -> Parser (AST [] grammar)
|
ASTParser :: (Bounded grammar, Enum grammar, Show grammar) => Ptr TS.Language -> Parser (AST [] grammar)
|
||||||
-- | A parser producing an à la carte term given an 'AST'-producing parser and an 'Assignment' onto 'Term's in some syntax type.
|
-- | A parser producing an à la carte term given an 'AST'-producing parser and an 'Assignment' onto 'Term's in some syntax type.
|
||||||
AssignmentParser :: (Enum grammar, Ix grammar, Show grammar, TS.Symbol grammar, Syntax.Error :< fs, Eq1 ast, Apply Foldable fs, Apply Functor fs, Foldable ast, Functor ast)
|
AssignmentParser :: (Enum grammar, Ix grammar, Show grammar, TS.Symbol grammar, Syntax.Error :< fs, Eq1 ast, Apply Foldable fs, Apply Functor fs, Foldable ast, Functor ast)
|
||||||
=> Parser (Term ast (Node grammar)) -- A parser producing AST.
|
=> Parser (Term ast (Node grammar)) -- A parser producing AST.
|
||||||
@ -91,23 +92,19 @@ data Parser term where
|
|||||||
-> Parser (Term (Sum fs) (Record Location)) -- A parser producing 'Term's.
|
-> Parser (Term (Sum fs) (Record Location)) -- A parser producing 'Term's.
|
||||||
-- | A parser for 'Markdown' using cmark.
|
-- | A parser for 'Markdown' using cmark.
|
||||||
MarkdownParser :: Parser (Term (TermF [] CMarkGFM.NodeType) (Node Markdown.Grammar))
|
MarkdownParser :: Parser (Term (TermF [] CMarkGFM.NodeType) (Node Markdown.Grammar))
|
||||||
|
-- | An abstraction over parsers when we don’t know the details of the term type.
|
||||||
|
SomeParser :: ApplyAll typeclasses syntax => Parser (Term syntax ann) -> Parser (SomeTerm typeclasses ann)
|
||||||
|
|
||||||
-- | Apply all of a list of typeclasses to all of a list of functors using 'Apply'. Used by 'someParser' to constrain all of the language-specific syntax types to the typeclasses in question.
|
-- | Apply all of a list of typeclasses to all of a list of functors using 'Apply'. Used by 'someParser' to constrain all of the language-specific syntax types to the typeclasses in question.
|
||||||
type family ApplyAll (typeclasses :: [(* -> *) -> Constraint]) (syntax :: * -> *) :: Constraint where
|
type family ApplyAll (typeclasses :: [(* -> *) -> Constraint]) (syntax :: * -> *) :: Constraint where
|
||||||
ApplyAll (typeclass ': typeclasses) syntax = (typeclass syntax, ApplyAll typeclasses syntax)
|
ApplyAll (typeclass ': typeclasses) syntax = (typeclass syntax, ApplyAll typeclasses syntax)
|
||||||
ApplyAll '[] syntax = ()
|
ApplyAll '[] syntax = ()
|
||||||
|
|
||||||
-- | A parser for some specific language, producing 'Term's whose syntax satisfies a list of typeclass constraints.
|
-- | Construct a 'Parser' given a proxy for a list of typeclasses and the 'Language' to be parsed, all of which must be satisfied by all of the types in the syntaxes of our supported languages.
|
||||||
--
|
|
||||||
-- This enables us to abstract over the details of the specific syntax types in cases where we can describe all the requirements on the syntax with a list of typeclasses.
|
|
||||||
data SomeParser typeclasses ann where
|
|
||||||
SomeParser :: ApplyAll typeclasses syntax => Parser (Term syntax ann) -> SomeParser typeclasses ann
|
|
||||||
|
|
||||||
-- | Construct a 'SomeParser' given a proxy for a list of typeclasses and the 'Language' to be parsed, all of which must be satisfied by all of the types in the syntaxes of our supported languages.
|
|
||||||
--
|
--
|
||||||
-- This can be used to perform operations uniformly over terms produced by blobs with different 'Language's, and which therefore have different types in general. For example, given some 'Blob', we can parse and 'show' the parsed & assigned 'Term' like so:
|
-- This can be used to perform operations uniformly over terms produced by blobs with different 'Language's, and which therefore have different types in general. For example, given some 'Blob', we can parse and 'show' the parsed & assigned 'Term' like so:
|
||||||
--
|
--
|
||||||
-- > case someParser (Proxy :: Proxy '[Show1]) <$> blobLanguage language of { Just (SomeParser parser) -> runTask (parse parser blob) >>= putStrLn . show ; _ -> return () }
|
-- > runTask (parse (someParser @'[Show1] language) blob) >>= putStrLn . withSomeTerm show
|
||||||
someParser :: ( ApplyAll typeclasses (Sum Go.Syntax)
|
someParser :: ( ApplyAll typeclasses (Sum Go.Syntax)
|
||||||
, ApplyAll typeclasses (Sum JSON.Syntax)
|
, ApplyAll typeclasses (Sum JSON.Syntax)
|
||||||
, ApplyAll typeclasses (Sum Markdown.Syntax)
|
, ApplyAll typeclasses (Sum Markdown.Syntax)
|
||||||
@ -116,18 +113,17 @@ someParser :: ( ApplyAll typeclasses (Sum Go.Syntax)
|
|||||||
, ApplyAll typeclasses (Sum TypeScript.Syntax)
|
, ApplyAll typeclasses (Sum TypeScript.Syntax)
|
||||||
, ApplyAll typeclasses (Sum PHP.Syntax)
|
, ApplyAll typeclasses (Sum PHP.Syntax)
|
||||||
)
|
)
|
||||||
=> proxy typeclasses -- ^ A proxy for the list of typeclasses required, e.g. @(Proxy :: Proxy '[Show1])@.
|
=> Language -- ^ The 'Language' to select.
|
||||||
-> Language -- ^ The 'Language' to select.
|
-> Parser (SomeTerm typeclasses (Record Location)) -- ^ A 'SomeParser' abstracting the syntax type to be produced.
|
||||||
-> SomeParser typeclasses (Record Location) -- ^ A 'SomeParser' abstracting the syntax type to be produced.
|
someParser Go = SomeParser goParser
|
||||||
someParser _ Go = SomeParser goParser
|
someParser JavaScript = SomeParser typescriptParser
|
||||||
someParser _ JavaScript = SomeParser typescriptParser
|
someParser JSON = SomeParser jsonParser
|
||||||
someParser _ JSON = SomeParser jsonParser
|
someParser JSX = SomeParser typescriptParser
|
||||||
someParser _ JSX = SomeParser typescriptParser
|
someParser Markdown = SomeParser markdownParser
|
||||||
someParser _ Markdown = SomeParser markdownParser
|
someParser Python = SomeParser pythonParser
|
||||||
someParser _ Python = SomeParser pythonParser
|
someParser Ruby = SomeParser rubyParser
|
||||||
someParser _ Ruby = SomeParser rubyParser
|
someParser TypeScript = SomeParser typescriptParser
|
||||||
someParser _ TypeScript = SomeParser typescriptParser
|
someParser PHP = SomeParser phpParser
|
||||||
someParser _ PHP = SomeParser phpParser
|
|
||||||
|
|
||||||
|
|
||||||
goParser :: Parser Go.Term
|
goParser :: Parser Go.Term
|
||||||
@ -152,9 +148,16 @@ markdownParser :: Parser Markdown.Term
|
|||||||
markdownParser = AssignmentParser MarkdownParser Markdown.assignment
|
markdownParser = AssignmentParser MarkdownParser Markdown.assignment
|
||||||
|
|
||||||
|
|
||||||
|
data SomeTerm typeclasses ann where
|
||||||
|
SomeTerm :: ApplyAll typeclasses syntax => Term syntax ann -> SomeTerm typeclasses ann
|
||||||
|
|
||||||
|
withSomeTerm :: (forall syntax . ApplyAll typeclasses syntax => Term syntax ann -> a) -> SomeTerm typeclasses ann -> a
|
||||||
|
withSomeTerm with (SomeTerm term) = with term
|
||||||
|
|
||||||
|
|
||||||
-- | A parser for producing specialized (tree-sitter) ASTs.
|
-- | A parser for producing specialized (tree-sitter) ASTs.
|
||||||
data SomeASTParser where
|
data SomeASTParser where
|
||||||
SomeASTParser :: forall grammar. (Bounded grammar, Enum grammar, Show grammar)
|
SomeASTParser :: (Bounded grammar, Enum grammar, Show grammar)
|
||||||
=> Parser (AST [] grammar)
|
=> Parser (AST [] grammar)
|
||||||
-> SomeASTParser
|
-> SomeASTParser
|
||||||
|
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
{-# LANGUAGE MonoLocalBinds #-}
|
|
||||||
module Rendering.DOT
|
|
||||||
( renderDOTDiff
|
|
||||||
, renderDOTTerm
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Prologue hiding (empty)
|
|
||||||
import Algebra.Graph
|
|
||||||
import Algebra.Graph.Export.Dot
|
|
||||||
import Analysis.ConstructorName
|
|
||||||
import Data.Blob
|
|
||||||
import qualified Data.ByteString.Char8 as B
|
|
||||||
import qualified Data.IntMap as IntMap
|
|
||||||
import Data.Diff
|
|
||||||
import Data.Patch
|
|
||||||
import Data.Term
|
|
||||||
|
|
||||||
renderDOTDiff :: (ConstructorName syntax, Foldable syntax, Functor syntax) => BlobPair -> Diff syntax ann1 ann2 -> B.ByteString
|
|
||||||
renderDOTDiff blobs diff = renderGraph (defaultStyleViaShow { graphName = B.pack (quote (pathKeyForBlobPair blobs)) }) (cata diffAlgebra diff 0 [])
|
|
||||||
where quote a = "\"" <> a <> "\""
|
|
||||||
|
|
||||||
renderDOTTerm :: (ConstructorName syntax, Foldable syntax, Functor syntax) => Blob -> Term syntax ann -> B.ByteString
|
|
||||||
renderDOTTerm Blob{..} term = renderGraph (defaultStyleViaShow { graphName = B.pack (quote blobPath) }) (cata termAlgebra term 0 [])
|
|
||||||
where quote a = "\"" <> a <> "\""
|
|
||||||
|
|
||||||
diffAlgebra :: (ConstructorName syntax, Foldable syntax) => DiffF syntax ann1 ann2 (Int -> [Attribute B.ByteString] -> State) -> Int -> [Attribute B.ByteString] -> State
|
|
||||||
diffAlgebra d i as = case d of
|
|
||||||
Merge t -> termAlgebra t i as
|
|
||||||
Patch (Delete t1) -> termAlgebra t1 i ("color" := "red" : as)
|
|
||||||
Patch (Insert t2) -> termAlgebra t2 i ("color" := "green" : as)
|
|
||||||
Patch (Replace t1 t2) -> let r1 = termAlgebra t1 i ("color" := "red" : as)
|
|
||||||
in r1 <> termAlgebra t2 (maximum (stateGraph r1)) ("color" := "green" : as)
|
|
||||||
|
|
||||||
termAlgebra :: (ConstructorName syntax, Foldable syntax) => TermF syntax ann (Int -> [Attribute B.ByteString] -> State) -> Int -> [Attribute B.ByteString] -> State
|
|
||||||
termAlgebra t i defaultAttrs = State
|
|
||||||
root
|
|
||||||
(root `connect` stateRoots combined `overlay` stateGraph combined)
|
|
||||||
(IntMap.insert (succ i) ("label" := unConstructorLabel (constructorLabel t) : defaultAttrs) (stateVertexAttributes combined))
|
|
||||||
where root = vertex (succ i)
|
|
||||||
combined = foldl' combine (State empty root mempty) t
|
|
||||||
combine prev makeSubgraph = prev <> makeSubgraph (maximum (stateGraph prev)) defaultAttrs
|
|
||||||
|
|
||||||
|
|
||||||
data State = State { stateRoots :: Graph Int, stateGraph :: Graph Int, stateVertexAttributes :: IntMap.IntMap [Attribute B.ByteString] }
|
|
||||||
|
|
||||||
instance Semigroup State where
|
|
||||||
State r1 g1 v1 <> State r2 g2 v2 = State (r1 `overlay` r2) (g1 `overlay` g2) (v1 <> v2)
|
|
||||||
|
|
||||||
instance Monoid State where
|
|
||||||
mempty = State empty empty mempty
|
|
||||||
mappend = (<>)
|
|
||||||
|
|
||||||
|
|
||||||
renderGraph :: Style Int B.ByteString -> State -> B.ByteString
|
|
||||||
renderGraph style State{..} = export (style { vertexAttributes = flip (IntMap.findWithDefault []) stateVertexAttributes }) stateGraph
|
|
76
src/Rendering/Graph.hs
Normal file
76
src/Rendering/Graph.hs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{-# LANGUAGE FunctionalDependencies, MonoLocalBinds #-}
|
||||||
|
module Rendering.Graph
|
||||||
|
( renderTreeGraph
|
||||||
|
, termStyle
|
||||||
|
, diffStyle
|
||||||
|
, ToTreeGraph(..)
|
||||||
|
, Vertex(..)
|
||||||
|
, DiffTag(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Algebra.Graph.Export.Dot
|
||||||
|
import Analysis.ConstructorName
|
||||||
|
import Control.Monad.Effect
|
||||||
|
import Control.Monad.Effect.Fresh
|
||||||
|
import Control.Monad.Effect.Reader
|
||||||
|
import Data.Diff
|
||||||
|
import Data.Graph
|
||||||
|
import Data.Patch
|
||||||
|
import Data.Semigroup.App
|
||||||
|
import Data.String (IsString(..))
|
||||||
|
import Data.Term
|
||||||
|
import Prologue
|
||||||
|
|
||||||
|
renderTreeGraph :: (Ord vertex, Recursive t, ToTreeGraph vertex (Base t)) => t -> Graph vertex
|
||||||
|
renderTreeGraph = simplify . runGraph . cata toTreeGraph
|
||||||
|
|
||||||
|
runGraph :: Eff '[Fresh, Reader (Graph vertex)] (Graph vertex) -> Graph vertex
|
||||||
|
runGraph = run . runReader mempty . runFresh 0
|
||||||
|
|
||||||
|
|
||||||
|
termAlgebra :: (ConstructorName syntax, Foldable syntax, Members '[Fresh, Reader (Graph (Vertex tag))] effs)
|
||||||
|
=> tag
|
||||||
|
-> TermF syntax ann (Eff effs (Graph (Vertex tag)))
|
||||||
|
-> Eff effs (Graph (Vertex tag))
|
||||||
|
termAlgebra tag (In _ syntax) = do
|
||||||
|
i <- fresh
|
||||||
|
let root = vertex (Vertex i tag (constructorName syntax))
|
||||||
|
parent <- ask
|
||||||
|
(parent `connect` root <>) <$> local (const root) (runAppMerge (foldMap AppMerge syntax))
|
||||||
|
|
||||||
|
|
||||||
|
style :: (IsString string, Monoid string) => String -> (tag -> [Attribute string]) -> Style (Vertex tag) string
|
||||||
|
style name tagAttributes = (defaultStyle (fromString . show . vertexId))
|
||||||
|
{ graphName = fromString (quote name)
|
||||||
|
, vertexAttributes = vertexAttributes }
|
||||||
|
where quote a = "\"" <> a <> "\""
|
||||||
|
vertexAttributes Vertex{..} = "label" := fromString vertexName : tagAttributes vertexTag
|
||||||
|
|
||||||
|
termStyle :: (IsString string, Monoid string) => String -> Style (Vertex ()) string
|
||||||
|
termStyle name = style name (const [])
|
||||||
|
|
||||||
|
diffStyle :: (IsString string, Monoid string) => String -> Style (Vertex DiffTag) string
|
||||||
|
diffStyle name = style name diffTagAttributes
|
||||||
|
where diffTagAttributes Deleted = ["color" := "red"]
|
||||||
|
diffTagAttributes Inserted = ["color" := "green"]
|
||||||
|
diffTagAttributes _ = []
|
||||||
|
|
||||||
|
data Vertex tag = Vertex { vertexId :: Int, vertexTag :: tag, vertexName :: String }
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
data DiffTag = Deleted | Inserted | Merged
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
|
||||||
|
class ToTreeGraph vertex t | t -> vertex where
|
||||||
|
toTreeGraph :: Members '[Fresh, Reader (Graph vertex)] effs => t (Eff effs (Graph vertex)) -> Eff effs (Graph vertex)
|
||||||
|
|
||||||
|
instance (ConstructorName syntax, Foldable syntax) => ToTreeGraph (Vertex ()) (TermF syntax ann) where
|
||||||
|
toTreeGraph = termAlgebra ()
|
||||||
|
|
||||||
|
instance (ConstructorName syntax, Foldable syntax) => ToTreeGraph (Vertex DiffTag) (DiffF syntax ann1 ann2) where
|
||||||
|
toTreeGraph d = case d of
|
||||||
|
Merge t -> termAlgebra Merged t
|
||||||
|
Patch (Delete t1) -> termAlgebra Deleted t1
|
||||||
|
Patch (Insert t2) -> termAlgebra Inserted t2
|
||||||
|
Patch (Replace t1 t2) -> (<>) <$> termAlgebra Deleted t1 <*> termAlgebra Inserted t2
|
@ -9,9 +9,7 @@ import Analysis.Declaration
|
|||||||
import Analysis.PackageDef
|
import Analysis.PackageDef
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.Blob
|
import Data.Blob
|
||||||
import Data.ByteString.Lazy (toStrict)
|
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Output
|
|
||||||
import Data.Span
|
import Data.Span
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import System.FilePath.Posix (takeBaseName)
|
import System.FilePath.Posix (takeBaseName)
|
||||||
@ -29,9 +27,6 @@ instance Monoid ImportSummary where
|
|||||||
mempty = ImportSummary mempty
|
mempty = ImportSummary mempty
|
||||||
mappend = (<>)
|
mappend = (<>)
|
||||||
|
|
||||||
instance Output ImportSummary where
|
|
||||||
toOutput = toStrict . (<> "\n") . encode
|
|
||||||
|
|
||||||
instance ToJSON ImportSummary where
|
instance ToJSON ImportSummary where
|
||||||
toJSON (ImportSummary m) = object [ "modules" .= m ]
|
toJSON (ImportSummary m) = object [ "modules" .= m ]
|
||||||
|
|
||||||
|
@ -1,37 +1,80 @@
|
|||||||
|
{-# LANGUAGE DataKinds, GADTs, GeneralizedNewtypeDeriving, KindSignatures, ScopedTypeVariables #-}
|
||||||
module Rendering.JSON
|
module Rendering.JSON
|
||||||
( renderJSONDiff
|
( JSON(..)
|
||||||
, renderJSONDiffs
|
, renderJSONDiff
|
||||||
, renderJSONTerm
|
, renderJSONTerm
|
||||||
, renderJSONTerm'
|
, renderJSONAST
|
||||||
, renderJSONTerms
|
, renderSymbolTerms
|
||||||
|
, SomeJSON(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Prologue
|
|
||||||
import Data.Aeson (ToJSON, toJSON, object, (.=))
|
import Data.Aeson (ToJSON, toJSON, object, (.=))
|
||||||
import Data.Aeson as A
|
import Data.Aeson as A
|
||||||
import Data.JSON.Fields
|
import Data.JSON.Fields
|
||||||
import Data.Blob
|
import Data.Blob
|
||||||
import qualified Data.Map as Map
|
|
||||||
import Data.Patch
|
import Data.Patch
|
||||||
|
import Data.Text (pack)
|
||||||
|
import GHC.TypeLits
|
||||||
|
import Prologue
|
||||||
|
|
||||||
|
newtype JSON (key :: Symbol) a = JSON { unJSON :: [a] }
|
||||||
|
deriving (Eq, Monoid, Semigroup, Show)
|
||||||
|
|
||||||
|
instance (KnownSymbol key, ToJSON a) => ToJSON (JSON key a) where
|
||||||
|
toJSON (JSON as) = object [ pack (symbolVal @key undefined) .= as ]
|
||||||
|
toEncoding (JSON as) = pairs (pack (symbolVal @key undefined) .= as)
|
||||||
|
|
||||||
|
|
||||||
-- | Render a diff to a value representing its JSON.
|
-- | Render a diff to a value representing its JSON.
|
||||||
renderJSONDiff :: ToJSON a => BlobPair -> a -> [Value]
|
renderJSONDiff :: ToJSON a => BlobPair -> a -> JSON "diffs" SomeJSON
|
||||||
renderJSONDiff blobs diff = pure $
|
renderJSONDiff blobs diff = JSON [ SomeJSON (JSONDiff (JSONStat blobs) diff) ]
|
||||||
toJSON (object [ "diff" .= diff, "stat" .= object (pathKey <> toJSONFields statPatch) ])
|
|
||||||
where statPatch = these Delete Insert Replace (runJoin blobs)
|
|
||||||
pathKey = [ "path" .= pathKeyForBlobPair blobs ]
|
|
||||||
|
|
||||||
renderJSONDiffs :: [Value] -> Map.Map Text Value
|
data JSONDiff a = JSONDiff { jsonDiffStat :: JSONStat, jsonDiff :: a }
|
||||||
renderJSONDiffs = Map.singleton "diffs" . toJSON
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance ToJSON a => ToJSON (JSONDiff a) where
|
||||||
|
toJSON JSONDiff{..} = object [ "diff" .= jsonDiff, "stat" .= jsonDiffStat ]
|
||||||
|
toEncoding JSONDiff{..} = pairs ("diff" .= jsonDiff <> "stat" .= jsonDiffStat)
|
||||||
|
|
||||||
|
newtype JSONStat = JSONStat { jsonStatBlobs :: BlobPair }
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance ToJSON JSONStat where
|
||||||
|
toJSON JSONStat{..} = object ("path" .= pathKeyForBlobPair jsonStatBlobs : toJSONFields (these Delete Insert Replace (runJoin jsonStatBlobs)))
|
||||||
|
toEncoding JSONStat{..} = pairs (fold ("path" .= pathKeyForBlobPair jsonStatBlobs : toJSONFields (these Delete Insert Replace (runJoin jsonStatBlobs))))
|
||||||
|
|
||||||
|
|
||||||
-- | Render a term to a value representing its JSON.
|
-- | Render a term to a value representing its JSON.
|
||||||
renderJSONTerm :: ToJSON a => Blob -> a -> [Value]
|
renderJSONTerm :: ToJSON a => Blob -> a -> JSON "trees" SomeJSON
|
||||||
renderJSONTerm blob content = pure $ toJSON (object ("programNode" .= content : toJSONFields blob))
|
renderJSONTerm blob content = JSON [ SomeJSON (JSONTerm blob content) ]
|
||||||
|
|
||||||
renderJSONTerm' :: (ToJSON a) => Blob -> a -> [Value]
|
data JSONTerm a = JSONTerm { jsonTermBlob :: Blob, jsonTerm :: a }
|
||||||
renderJSONTerm' blob content = pure $ toJSON (object ("ast" .= content : toJSONFields blob))
|
deriving (Eq, Show)
|
||||||
|
|
||||||
renderJSONTerms :: [Value] -> Map.Map Text Value
|
instance ToJSON a => ToJSON (JSONTerm a) where
|
||||||
renderJSONTerms = Map.singleton "trees" . toJSON
|
toJSON JSONTerm{..} = object ("programNode" .= jsonTerm : toJSONFields jsonTermBlob)
|
||||||
|
toEncoding JSONTerm{..} = pairs (fold ("programNode" .= jsonTerm : toJSONFields jsonTermBlob))
|
||||||
|
|
||||||
|
|
||||||
|
renderJSONAST :: ToJSON a => Blob -> a -> JSON "trees" SomeJSON
|
||||||
|
renderJSONAST blob content = JSON [ SomeJSON (JSONAST blob content) ]
|
||||||
|
|
||||||
|
data JSONAST a = JSONAST { jsonASTBlob :: Blob, jsonAST :: a }
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance ToJSON a => ToJSON (JSONAST a) where
|
||||||
|
toJSON JSONAST{..} = object ("ast" .= jsonAST : toJSONFields jsonASTBlob)
|
||||||
|
toEncoding JSONAST{..} = pairs (fold ("ast" .= jsonAST : toJSONFields jsonASTBlob))
|
||||||
|
|
||||||
|
|
||||||
|
-- | Render terms to final JSON structure.
|
||||||
|
renderSymbolTerms :: ToJSON a => [a] -> JSON "files" SomeJSON
|
||||||
|
renderSymbolTerms = JSON . map SomeJSON
|
||||||
|
|
||||||
|
|
||||||
|
data SomeJSON where
|
||||||
|
SomeJSON :: ToJSON a => a -> SomeJSON
|
||||||
|
|
||||||
|
instance ToJSON SomeJSON where
|
||||||
|
toJSON (SomeJSON a) = toJSON a
|
||||||
|
toEncoding (SomeJSON a) = toEncoding a
|
||||||
|
@ -2,16 +2,9 @@
|
|||||||
module Rendering.Renderer
|
module Rendering.Renderer
|
||||||
( DiffRenderer(..)
|
( DiffRenderer(..)
|
||||||
, TermRenderer(..)
|
, TermRenderer(..)
|
||||||
, GraphRenderer(..)
|
|
||||||
, SomeRenderer(..)
|
|
||||||
, renderSExpressionDiff
|
|
||||||
, renderSExpressionTerm
|
|
||||||
, renderSExpressionAST
|
|
||||||
, renderJSONDiff
|
, renderJSONDiff
|
||||||
, renderJSONDiffs
|
|
||||||
, renderJSONTerm
|
, renderJSONTerm
|
||||||
, renderJSONTerm'
|
, renderJSONAST
|
||||||
, renderJSONTerms
|
|
||||||
, renderToCDiff
|
, renderToCDiff
|
||||||
, renderToCTerm
|
, renderToCTerm
|
||||||
, renderSymbolTerms
|
, renderSymbolTerms
|
||||||
@ -19,20 +12,18 @@ module Rendering.Renderer
|
|||||||
, ImportSummary(..)
|
, ImportSummary(..)
|
||||||
, renderToImports
|
, renderToImports
|
||||||
, renderToTags
|
, renderToTags
|
||||||
, renderDOTDiff
|
, renderTreeGraph
|
||||||
, renderDOTTerm
|
|
||||||
, Summaries(..)
|
, Summaries(..)
|
||||||
, SymbolFields(..)
|
, SymbolFields(..)
|
||||||
, defaultSymbolFields
|
, defaultSymbolFields
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Aeson (Value)
|
import Data.Aeson (Value)
|
||||||
import Data.Output
|
import Data.ByteString.Builder
|
||||||
import Prologue
|
import Data.Graph
|
||||||
import Rendering.DOT as R
|
import Rendering.Graph as R
|
||||||
import Rendering.Imports as R
|
import Rendering.Imports as R
|
||||||
import Rendering.JSON as R
|
import Rendering.JSON as R
|
||||||
import Rendering.SExpression as R
|
|
||||||
import Rendering.Symbol as R
|
import Rendering.Symbol as R
|
||||||
import Rendering.TOC as R
|
import Rendering.TOC as R
|
||||||
|
|
||||||
@ -41,11 +32,11 @@ data DiffRenderer output where
|
|||||||
-- | Compute a table of contents for the diff & encode it as JSON.
|
-- | Compute a table of contents for the diff & encode it as JSON.
|
||||||
ToCDiffRenderer :: DiffRenderer Summaries
|
ToCDiffRenderer :: DiffRenderer Summaries
|
||||||
-- | Render to JSON with the format documented in docs/json-format.md
|
-- | Render to JSON with the format documented in docs/json-format.md
|
||||||
JSONDiffRenderer :: DiffRenderer [Value]
|
JSONDiffRenderer :: DiffRenderer (JSON "diffs" SomeJSON)
|
||||||
-- | Render to a 'ByteString' formatted as nested s-expressions with patches indicated.
|
-- | Render to a 'ByteString' formatted as nested s-expressions with patches indicated.
|
||||||
SExpressionDiffRenderer :: DiffRenderer ByteString
|
SExpressionDiffRenderer :: DiffRenderer Builder
|
||||||
-- | Render to a 'ByteString' formatted as a DOT description of the diff.
|
-- | Render to a 'ByteString' formatted as a DOT description of the diff.
|
||||||
DOTDiffRenderer :: DiffRenderer ByteString
|
DOTDiffRenderer :: DiffRenderer (Graph (Vertex DiffTag))
|
||||||
|
|
||||||
deriving instance Eq (DiffRenderer output)
|
deriving instance Eq (DiffRenderer output)
|
||||||
deriving instance Show (DiffRenderer output)
|
deriving instance Show (DiffRenderer output)
|
||||||
@ -53,33 +44,17 @@ deriving instance Show (DiffRenderer output)
|
|||||||
-- | Specification of renderers for terms, producing output in the parameter type.
|
-- | Specification of renderers for terms, producing output in the parameter type.
|
||||||
data TermRenderer output where
|
data TermRenderer output where
|
||||||
-- | Render to JSON with the format documented in docs/json-format.md under “Term.”
|
-- | Render to JSON with the format documented in docs/json-format.md under “Term.”
|
||||||
JSONTermRenderer :: TermRenderer [Value]
|
JSONTermRenderer :: TermRenderer (JSON "trees" SomeJSON)
|
||||||
-- | Render to a 'ByteString' formatted as nested s-expressions.
|
-- | Render to a 'ByteString' formatted as nested s-expressions.
|
||||||
SExpressionTermRenderer :: TermRenderer ByteString
|
SExpressionTermRenderer :: TermRenderer Builder
|
||||||
-- | Render to a list of tags (deprecated).
|
-- | Render to a list of tags (deprecated).
|
||||||
TagsTermRenderer :: TermRenderer [Value]
|
TagsTermRenderer :: TermRenderer [Value]
|
||||||
-- | Render to a list of symbols.
|
-- | Render to a list of symbols.
|
||||||
SymbolsTermRenderer :: SymbolFields -> TermRenderer [Value]
|
SymbolsTermRenderer :: SymbolFields -> TermRenderer (JSON "files" SomeJSON)
|
||||||
-- | Render to a list of modules that represent the import graph.
|
-- | Render to a list of modules that represent the import graph.
|
||||||
ImportsTermRenderer :: TermRenderer ImportSummary
|
ImportsTermRenderer :: TermRenderer ImportSummary
|
||||||
-- | Render to a 'ByteString' formatted as a DOT description of the term.
|
-- | Render to a 'ByteString' formatted as a DOT description of the term.
|
||||||
DOTTermRenderer :: TermRenderer ByteString
|
DOTTermRenderer :: TermRenderer (Graph (Vertex ()))
|
||||||
|
|
||||||
deriving instance Eq (TermRenderer output)
|
deriving instance Eq (TermRenderer output)
|
||||||
deriving instance Show (TermRenderer output)
|
deriving instance Show (TermRenderer output)
|
||||||
|
|
||||||
-- | Specification of renderers for graph analysis, producing output in the parameter type.
|
|
||||||
data GraphRenderer output where
|
|
||||||
JSONGraphRenderer :: GraphRenderer ByteString
|
|
||||||
DOTGraphRenderer :: GraphRenderer ByteString
|
|
||||||
|
|
||||||
deriving instance Eq (GraphRenderer output)
|
|
||||||
deriving instance Show (GraphRenderer output)
|
|
||||||
|
|
||||||
-- | Abstraction of some renderer to some 'Monoid'al output which can be serialized to a 'ByteString'.
|
|
||||||
--
|
|
||||||
-- This type abstracts the type indices of 'DiffRenderer', 'TermRenderer', and 'GraphRenderer' s.t. multiple renderers can be present in a single list, alternation, etc., while retaining the ability to render and serialize. (Without 'SomeRenderer', the different output types of individual term/diff renderers prevent them from being used in a homogeneously typed setting.)
|
|
||||||
data SomeRenderer f where
|
|
||||||
SomeRenderer :: (Output output, Show (f output)) => f output -> SomeRenderer f
|
|
||||||
|
|
||||||
deriving instance Show (SomeRenderer f)
|
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
{-# LANGUAGE GADTs, RankNTypes, ScopedTypeVariables #-}
|
|
||||||
module Rendering.SExpression
|
|
||||||
( renderSExpressionDiff
|
|
||||||
, renderSExpressionTerm
|
|
||||||
, renderSExpressionAST
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Prologue
|
|
||||||
import Data.ByteString.Char8
|
|
||||||
import Data.Diff
|
|
||||||
import Data.Patch
|
|
||||||
import Data.Record
|
|
||||||
import Data.AST
|
|
||||||
import Data.Term
|
|
||||||
import Prelude hiding (replicate)
|
|
||||||
|
|
||||||
-- | Returns a ByteString SExpression formatted diff.
|
|
||||||
renderSExpressionDiff :: (ConstrainAll Show fields, Foldable syntax, Functor syntax) => Diff syntax (Record fields) (Record fields) -> ByteString
|
|
||||||
renderSExpressionDiff diff = cata printDiffF diff 0 <> "\n"
|
|
||||||
|
|
||||||
-- | Returns a ByteString SExpression formatted term (generalized).
|
|
||||||
renderSExpressionTerm :: (ConstrainAll Show fields, Foldable syntax, Functor syntax) => Term syntax (Record fields) -> ByteString
|
|
||||||
renderSExpressionTerm = toSExpression showRecord
|
|
||||||
|
|
||||||
-- | Returns a ByteString SExpression formatted term (specialized)
|
|
||||||
renderSExpressionAST :: Show grammar => Term [] (Node grammar) -> ByteString
|
|
||||||
renderSExpressionAST = toSExpression (pack . show . nodeSymbol)
|
|
||||||
|
|
||||||
|
|
||||||
toSExpression :: (Base t ~ TermF syntax ann, Foldable syntax, Recursive t) => (ann -> ByteString) -> t -> ByteString
|
|
||||||
toSExpression showAnn term = cata (\ term n -> nl n <> replicate (2 * n) ' ' <> printTermF showAnn term n) term 0 <> "\n"
|
|
||||||
|
|
||||||
printDiffF :: (ConstrainAll Show fields, Foldable syntax) => DiffF syntax (Record fields) (Record fields) (Int -> ByteString) -> Int -> ByteString
|
|
||||||
printDiffF diff n = case diff of
|
|
||||||
Patch (Delete term) -> nl n <> pad (n - 1) <> "{-" <> printTermF showRecord term n <> "-}"
|
|
||||||
Patch (Insert term) -> nl n <> pad (n - 1) <> "{+" <> printTermF showRecord term n <> "+}"
|
|
||||||
Patch (Replace term1 term2) -> nl n <> pad (n - 1) <> "{ " <> printTermF showRecord term1 n
|
|
||||||
<> nl (n + 1) <> pad (n - 1) <> "->" <> printTermF showRecord term2 n <> " }"
|
|
||||||
Merge (In (_, ann) syntax) -> nl n <> pad n <> "(" <> showRecord ann <> foldMap (\ d -> d (n + 1)) syntax <> ")"
|
|
||||||
|
|
||||||
printTermF :: Foldable syntax => (ann -> ByteString) -> TermF syntax ann (Int -> ByteString) -> Int -> ByteString
|
|
||||||
printTermF f (In ann syntax) n = "(" <> f ann <> foldMap (\t -> t (succ n)) syntax <> ")"
|
|
||||||
|
|
||||||
nl :: Int -> ByteString
|
|
||||||
nl n | n <= 0 = ""
|
|
||||||
| otherwise = "\n"
|
|
||||||
|
|
||||||
pad :: Int -> ByteString
|
|
||||||
pad n = replicate (2 * n) ' '
|
|
||||||
|
|
||||||
showRecord :: ConstrainAll Show fields => Record fields -> ByteString
|
|
||||||
showRecord Nil = ""
|
|
||||||
showRecord (only :. Nil) = pack (show only)
|
|
||||||
showRecord (first :. rest) = pack (show first) <> " " <> showRecord rest
|
|
@ -1,7 +1,6 @@
|
|||||||
{-# LANGUAGE RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
|
{-# LANGUAGE RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
|
||||||
module Rendering.Symbol
|
module Rendering.Symbol
|
||||||
( renderSymbolTerms
|
( renderToSymbols
|
||||||
, renderToSymbols
|
|
||||||
, renderToTags
|
, renderToTags
|
||||||
, SymbolFields(..)
|
, SymbolFields(..)
|
||||||
, defaultSymbolFields
|
, defaultSymbolFields
|
||||||
@ -15,7 +14,6 @@ import Data.Record
|
|||||||
import Data.Span
|
import Data.Span
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Map as Map
|
|
||||||
import Rendering.TOC
|
import Rendering.TOC
|
||||||
|
|
||||||
|
|
||||||
@ -31,10 +29,6 @@ renderToTags Blob{..} = fmap toJSON . termToC blobPath
|
|||||||
termToC path = mapMaybe (symbolSummary defaultTagSymbolFields path "unchanged") . termTableOfContentsBy declaration
|
termToC path = mapMaybe (symbolSummary defaultTagSymbolFields path "unchanged") . termTableOfContentsBy declaration
|
||||||
|
|
||||||
|
|
||||||
-- | Render terms to final JSON structure.
|
|
||||||
renderSymbolTerms :: [Value] -> Map.Map T.Text Value
|
|
||||||
renderSymbolTerms = Map.singleton "files" . toJSON
|
|
||||||
|
|
||||||
-- | Render a 'Term' to a list of symbols (See 'Symbol').
|
-- | Render a 'Term' to a list of symbols (See 'Symbol').
|
||||||
renderToSymbols :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => SymbolFields -> Blob -> Term f (Record fields) -> [Value]
|
renderToSymbols :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => SymbolFields -> Blob -> Term f (Record fields) -> [Value]
|
||||||
renderToSymbols fields Blob{..} term = [toJSON (termToC fields blobPath term)]
|
renderToSymbols fields Blob{..} term = [toJSON (termToC fields blobPath term)]
|
||||||
|
@ -21,13 +21,11 @@ import Analysis.Declaration
|
|||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.Align (bicrosswalk)
|
import Data.Align (bicrosswalk)
|
||||||
import Data.Blob
|
import Data.Blob
|
||||||
import Data.ByteString.Lazy (toStrict)
|
|
||||||
import Data.Diff
|
import Data.Diff
|
||||||
import Data.Language as Language
|
import Data.Language as Language
|
||||||
import Data.List (sortOn)
|
import Data.List (sortOn)
|
||||||
import qualified Data.List as List
|
import qualified Data.List as List
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import Data.Output
|
|
||||||
import Data.Patch
|
import Data.Patch
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Span
|
import Data.Span
|
||||||
@ -44,9 +42,6 @@ instance Monoid Summaries where
|
|||||||
mempty = Summaries mempty mempty
|
mempty = Summaries mempty mempty
|
||||||
mappend = (<>)
|
mappend = (<>)
|
||||||
|
|
||||||
instance Output Summaries where
|
|
||||||
toOutput = toStrict . (<> "\n") . encode
|
|
||||||
|
|
||||||
instance ToJSON Summaries where
|
instance ToJSON Summaries where
|
||||||
toJSON Summaries{..} = object [ "changes" .= changes, "errors" .= errors ]
|
toJSON Summaries{..} = object [ "changes" .= changes, "errors" .= errors ]
|
||||||
|
|
||||||
|
31
src/Semantic/AST.hs
Normal file
31
src/Semantic/AST.hs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{-# LANGUAGE GADTs, RankNTypes #-}
|
||||||
|
module Semantic.AST where
|
||||||
|
|
||||||
|
import Data.AST
|
||||||
|
import Data.Blob
|
||||||
|
import Parsing.Parser
|
||||||
|
import Prologue hiding (MonadError(..))
|
||||||
|
import Rendering.JSON (renderJSONAST)
|
||||||
|
import Semantic.IO (noLanguageForBlob)
|
||||||
|
import Semantic.Task
|
||||||
|
import qualified Serializing.Format as F
|
||||||
|
|
||||||
|
data SomeAST where
|
||||||
|
SomeAST :: Show grammar => AST [] grammar -> SomeAST
|
||||||
|
|
||||||
|
withSomeAST :: (forall grammar . Show grammar => AST [] grammar -> a) -> SomeAST -> a
|
||||||
|
withSomeAST f (SomeAST ast) = f ast
|
||||||
|
|
||||||
|
astParseBlob :: Members '[Task, Exc SomeException] effs => Blob -> Eff effs SomeAST
|
||||||
|
astParseBlob blob@Blob{..}
|
||||||
|
| Just (SomeASTParser parser) <- someASTParser <$> blobLanguage
|
||||||
|
= SomeAST <$> parse parser blob
|
||||||
|
| otherwise = noLanguageForBlob blobPath
|
||||||
|
|
||||||
|
|
||||||
|
data ASTFormat = SExpression | JSON
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
runASTParse :: Members '[Distribute WrappedTask, Task, Exc SomeException] effects => ASTFormat -> [Blob] -> Eff effects F.Builder
|
||||||
|
runASTParse SExpression = distributeFoldMap (WrapTask . (withSomeAST (serialize (F.SExpression F.ByShow)) <=< astParseBlob))
|
||||||
|
runASTParse JSON = serialize F.JSON <=< distributeFoldMap (\ blob -> WrapTask (withSomeAST (render (renderJSONAST blob)) =<< astParseBlob blob))
|
@ -1,44 +1,36 @@
|
|||||||
{-# LANGUAGE ApplicativeDo, TemplateHaskell #-}
|
{-# LANGUAGE ApplicativeDo, RankNTypes, TemplateHaskell #-}
|
||||||
module Semantic.CLI
|
module Semantic.CLI
|
||||||
( main
|
( main
|
||||||
-- Testing
|
-- Testing
|
||||||
, runDiff
|
, Diff.runDiff
|
||||||
, runParse
|
, Parse.runParse
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.File
|
import Data.File
|
||||||
import Data.Language
|
import Data.Language (Language)
|
||||||
import Data.List (intercalate)
|
import Data.List (intercalate)
|
||||||
import Data.List.Split (splitWhen)
|
import Data.List.Split (splitWhen)
|
||||||
import Data.Version (showVersion)
|
import Data.Version (showVersion)
|
||||||
import Development.GitRev
|
import Development.GitRev
|
||||||
import Options.Applicative
|
import Options.Applicative hiding (style)
|
||||||
import qualified Paths_semantic as Library (version)
|
import qualified Paths_semantic as Library (version)
|
||||||
import Prologue
|
import Prologue
|
||||||
import Rendering.Renderer
|
import Rendering.Renderer
|
||||||
import qualified Semantic.Diff as Semantic (diffBlobPairs)
|
import qualified Semantic.AST as AST
|
||||||
import Semantic.Graph as Semantic (graph, GraphType(..))
|
import qualified Semantic.Diff as Diff
|
||||||
import Semantic.IO (languageForFilePath)
|
import Semantic.Graph as Semantic (Graph, GraphType(..), Vertex, graph, style)
|
||||||
|
import Semantic.IO as IO
|
||||||
import qualified Semantic.Log as Log
|
import qualified Semantic.Log as Log
|
||||||
import qualified Semantic.Parse as Semantic (parseBlobs, astParseBlobs)
|
import qualified Semantic.Parse as Parse
|
||||||
import qualified Semantic.Task as Task
|
import qualified Semantic.Task as Task
|
||||||
import System.IO (Handle, stdin, stdout)
|
import Serializing.Format
|
||||||
import Text.Read
|
import Text.Read
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = customExecParser (prefs showHelpOnEmpty) arguments >>= uncurry Task.runTaskWithOptions
|
main = customExecParser (prefs showHelpOnEmpty) arguments >>= uncurry Task.runTaskWithOptions
|
||||||
|
|
||||||
runDiff :: SomeRenderer DiffRenderer -> Either Handle [Both File] -> Task.TaskEff ByteString
|
runGraph :: Semantic.GraphType -> Maybe FilePath -> FilePath -> Language -> [FilePath] -> Task.TaskEff (Graph Vertex)
|
||||||
runDiff (SomeRenderer diffRenderer) = Semantic.diffBlobPairs diffRenderer <=< Task.readBlobPairs
|
runGraph graphType rootDir dir excludeDirs = Semantic.graph graphType <=< Task.readProject rootDir dir excludeDirs
|
||||||
|
|
||||||
runParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString
|
|
||||||
runParse (SomeRenderer parseTreeRenderer) = Semantic.parseBlobs parseTreeRenderer <=< Task.readBlobs
|
|
||||||
|
|
||||||
runASTParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString
|
|
||||||
runASTParse (SomeRenderer parseTreeRenderer) = Semantic.astParseBlobs parseTreeRenderer <=< Task.readBlobs
|
|
||||||
|
|
||||||
runGraph :: Semantic.GraphType -> SomeRenderer GraphRenderer -> Maybe FilePath -> FilePath -> Language -> [FilePath] -> Task.TaskEff ByteString
|
|
||||||
runGraph graphType (SomeRenderer r) rootDir dir excludeDirs = Semantic.graph graphType r <=< Task.readProject rootDir dir excludeDirs
|
|
||||||
|
|
||||||
-- | A parser for the application's command-line arguments.
|
-- | A parser for the application's command-line arguments.
|
||||||
--
|
--
|
||||||
@ -60,50 +52,50 @@ arguments = info (version <*> helper <*> ((,) <$> optionsParser <*> argumentsPar
|
|||||||
|
|
||||||
argumentsParser = do
|
argumentsParser = do
|
||||||
subparser <- hsubparser (diffCommand <> parseCommand <> tsParseCommand <> graphCommand)
|
subparser <- hsubparser (diffCommand <> parseCommand <> tsParseCommand <> graphCommand)
|
||||||
output <- Right <$> strOption (long "output" <> short 'o' <> help "Output path, defaults to stdout") <|> pure (Left stdout)
|
output <- ToPath <$> strOption (long "output" <> short 'o' <> help "Output path, defaults to stdout") <|> pure (ToHandle stdout)
|
||||||
pure $ subparser >>= Task.writeToOutput output
|
pure $ subparser >>= Task.write output
|
||||||
|
|
||||||
diffCommand = command "diff" (info diffArgumentsParser (progDesc "Compute changes between paths"))
|
diffCommand = command "diff" (info diffArgumentsParser (progDesc "Compute changes between paths"))
|
||||||
diffArgumentsParser = do
|
diffArgumentsParser = do
|
||||||
renderer <- flag (SomeRenderer SExpressionDiffRenderer) (SomeRenderer SExpressionDiffRenderer) (long "sexpression" <> help "Output s-expression diff tree (default)")
|
renderer <- flag (Diff.runDiff SExpressionDiffRenderer) (Diff.runDiff SExpressionDiffRenderer) (long "sexpression" <> help "Output s-expression diff tree (default)")
|
||||||
<|> flag' (SomeRenderer JSONDiffRenderer) (long "json" <> help "Output JSON diff trees")
|
<|> flag' (Diff.runDiff JSONDiffRenderer) (long "json" <> help "Output JSON diff trees")
|
||||||
<|> flag' (SomeRenderer ToCDiffRenderer) (long "toc" <> help "Output JSON table of contents diff summary")
|
<|> flag' (Diff.runDiff ToCDiffRenderer) (long "toc" <> help "Output JSON table of contents diff summary")
|
||||||
<|> flag' (SomeRenderer DOTDiffRenderer) (long "dot" <> help "Output the diff as a DOT graph")
|
<|> flag' (Diff.runDiff DOTDiffRenderer) (long "dot" <> help "Output the diff as a DOT graph")
|
||||||
filesOrStdin <- Right <$> some (both <$> argument filePathReader (metavar "FILE_A") <*> argument filePathReader (metavar "FILE_B")) <|> pure (Left stdin)
|
filesOrStdin <- Right <$> some (both <$> argument filePathReader (metavar "FILE_A") <*> argument filePathReader (metavar "FILE_B")) <|> pure (Left stdin)
|
||||||
pure $ runDiff renderer filesOrStdin
|
pure $ Task.readBlobPairs filesOrStdin >>= renderer
|
||||||
|
|
||||||
parseCommand = command "parse" (info parseArgumentsParser (progDesc "Generate parse trees for path(s)"))
|
parseCommand = command "parse" (info parseArgumentsParser (progDesc "Generate parse trees for path(s)"))
|
||||||
parseArgumentsParser = do
|
parseArgumentsParser = do
|
||||||
renderer <- flag (SomeRenderer SExpressionTermRenderer) (SomeRenderer SExpressionTermRenderer) (long "sexpression" <> help "Output s-expression parse trees (default)")
|
renderer <- flag (Parse.runParse SExpressionTermRenderer) (Parse.runParse SExpressionTermRenderer) (long "sexpression" <> help "Output s-expression parse trees (default)")
|
||||||
<|> flag' (SomeRenderer JSONTermRenderer) (long "json" <> help "Output JSON parse trees")
|
<|> flag' (Parse.runParse JSONTermRenderer) (long "json" <> help "Output JSON parse trees")
|
||||||
<|> flag' (SomeRenderer TagsTermRenderer) (long "tags" <> help "Output JSON tags")
|
<|> flag' (Parse.runParse TagsTermRenderer) (long "tags" <> help "Output JSON tags")
|
||||||
<|> flag' (SomeRenderer . SymbolsTermRenderer) (long "symbols" <> help "Output JSON symbol list")
|
<|> flag' (Parse.runParse . SymbolsTermRenderer) (long "symbols" <> help "Output JSON symbol list")
|
||||||
<*> (option symbolFieldsReader ( long "fields"
|
<*> (option symbolFieldsReader ( long "fields"
|
||||||
<> help "Comma delimited list of specific fields to return (symbols output only)."
|
<> help "Comma delimited list of specific fields to return (symbols output only)."
|
||||||
<> metavar "FIELDS")
|
<> metavar "FIELDS")
|
||||||
<|> pure defaultSymbolFields)
|
<|> pure defaultSymbolFields)
|
||||||
<|> flag' (SomeRenderer ImportsTermRenderer) (long "import-graph" <> help "Output JSON import graph")
|
<|> flag' (Parse.runParse ImportsTermRenderer) (long "import-graph" <> help "Output JSON import graph")
|
||||||
<|> flag' (SomeRenderer DOTTermRenderer) (long "dot" <> help "Output DOT graph parse trees")
|
<|> flag' (Parse.runParse DOTTermRenderer) (long "dot" <> help "Output DOT graph parse trees")
|
||||||
filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin)
|
filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin)
|
||||||
pure $ runParse renderer filesOrStdin
|
pure $ Task.readBlobs filesOrStdin >>= renderer
|
||||||
|
|
||||||
tsParseCommand = command "ts-parse" (info tsParseArgumentsParser (progDesc "Print specialized tree-sitter ASTs for path(s)"))
|
tsParseCommand = command "ts-parse" (info tsParseArgumentsParser (progDesc "Print specialized tree-sitter ASTs for path(s)"))
|
||||||
tsParseArgumentsParser = do
|
tsParseArgumentsParser = do
|
||||||
renderer <- flag (SomeRenderer SExpressionTermRenderer) (SomeRenderer SExpressionTermRenderer) (long "sexpression" <> help "Output s-expression ASTs (default)")
|
format <- flag AST.SExpression AST.SExpression (long "sexpression" <> help "Output s-expression ASTs (default)")
|
||||||
<|> flag' (SomeRenderer JSONTermRenderer) (long "json" <> help "Output JSON ASTs")
|
<|> flag' AST.JSON (long "json" <> help "Output JSON ASTs")
|
||||||
filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin)
|
filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin)
|
||||||
pure $ runASTParse renderer filesOrStdin
|
pure $ Task.readBlobs filesOrStdin >>= AST.runASTParse format
|
||||||
|
|
||||||
graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute a graph for a directory or entry point"))
|
graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute a graph for a directory or entry point"))
|
||||||
graphArgumentsParser = do
|
graphArgumentsParser = do
|
||||||
graphType <- flag ImportGraph ImportGraph (long "imports" <> help "Compute an import graph (default)")
|
graphType <- flag ImportGraph ImportGraph (long "imports" <> help "Compute an import graph (default)")
|
||||||
<|> flag' CallGraph (long "calls" <> help "Compute a call graph")
|
<|> flag' CallGraph (long "calls" <> help "Compute a call graph")
|
||||||
renderer <- flag (SomeRenderer DOTGraphRenderer) (SomeRenderer DOTGraphRenderer) (long "dot" <> help "Output in DOT graph format (default)")
|
serializer <- flag (Task.serialize (DOT style)) (Task.serialize (DOT style)) (long "dot" <> help "Output in DOT graph format (default)")
|
||||||
<|> flag' (SomeRenderer JSONGraphRenderer) (long "json" <> help "Output JSON graph")
|
<|> flag' (Task.serialize JSON) (long "json" <> help "Output JSON graph")
|
||||||
rootDir <- rootDirectoryOption
|
rootDir <- rootDirectoryOption
|
||||||
excludeDirs <- excludeDirsOption
|
excludeDirs <- excludeDirsOption
|
||||||
File{..} <- argument filePathReader (metavar "DIR:LANGUAGE | FILE")
|
File{..} <- argument filePathReader (metavar "DIR:LANGUAGE | FILE")
|
||||||
pure $ runGraph graphType renderer rootDir filePath (fromJust fileLanguage) excludeDirs
|
pure $ runGraph graphType rootDir filePath (fromJust fileLanguage) excludeDirs >>= serializer
|
||||||
|
|
||||||
rootDirectoryOption = optional (strOption (long "root" <> help "Root directory of project. Optional, defaults to entry file/directory." <> metavar "DIR"))
|
rootDirectoryOption = optional (strOption (long "root" <> help "Root directory of project. Optional, defaults to entry file/directory." <> metavar "DIR"))
|
||||||
excludeDirsOption = many (strOption (long "exclude-dir" <> help "Exclude a directory (e.g. vendor)" <> metavar "DIR"))
|
excludeDirsOption = many (strOption (long "exclude-dir" <> help "Exclude a directory (e.g. vendor)" <> metavar "DIR"))
|
||||||
|
@ -1,55 +1,54 @@
|
|||||||
{-# LANGUAGE GADTs #-}
|
{-# LANGUAGE GADTs, RankNTypes, ScopedTypeVariables #-}
|
||||||
module Semantic.Diff where
|
module Semantic.Diff where
|
||||||
|
|
||||||
import Prologue hiding (MonadError(..))
|
|
||||||
import Analysis.ConstructorName (ConstructorName, constructorLabel)
|
import Analysis.ConstructorName (ConstructorName, constructorLabel)
|
||||||
import Analysis.IdentifierName (IdentifierName, identifierLabel)
|
import Analysis.IdentifierName (IdentifierName, identifierLabel)
|
||||||
import Analysis.Declaration (HasDeclaration, declarationAlgebra)
|
import Analysis.Declaration (HasDeclaration, declarationAlgebra)
|
||||||
|
import Data.AST
|
||||||
import Data.Blob
|
import Data.Blob
|
||||||
import Data.Diff
|
import Data.Diff
|
||||||
import Data.JSON.Fields
|
import Data.JSON.Fields
|
||||||
import Data.Output
|
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import Diffing.Algorithm (Diffable)
|
import Diffing.Algorithm (Diffable)
|
||||||
import Diffing.Interpreter
|
|
||||||
import Parsing.Parser
|
import Parsing.Parser
|
||||||
|
import Prologue hiding (MonadError(..))
|
||||||
|
import Rendering.Graph
|
||||||
import Rendering.Renderer
|
import Rendering.Renderer
|
||||||
import Semantic.IO (NoLanguageForBlob(..))
|
import Semantic.IO (noLanguageForBlob)
|
||||||
import Semantic.Stat as Stat
|
import Semantic.Stat as Stat
|
||||||
import Semantic.Task as Task
|
import Semantic.Task as Task
|
||||||
|
import Serializing.Format
|
||||||
|
|
||||||
diffBlobPairs :: (Members '[Distribute WrappedTask, Task, Telemetry, Exc SomeException, IO] effs, Output output) => DiffRenderer output -> [BlobPair] -> Eff effs ByteString
|
runDiff :: Members '[Distribute WrappedTask, Task, Telemetry, Exc SomeException, IO] effs => DiffRenderer output -> [BlobPair] -> Eff effs Builder
|
||||||
diffBlobPairs renderer blobs = toOutput' <$> distributeFoldMap (WrapTask . diffBlobPair renderer) blobs
|
runDiff ToCDiffRenderer = withParsedBlobPairs (decorate . declarationAlgebra) (render . renderToCDiff) >=> serialize JSON
|
||||||
where toOutput' = case renderer of
|
runDiff JSONDiffRenderer = withParsedBlobPairs (const (decorate constructorLabel >=> decorate identifierLabel)) (render . renderJSONDiff) >=> serialize JSON
|
||||||
JSONDiffRenderer -> toOutput . renderJSONDiffs
|
runDiff SExpressionDiffRenderer = withParsedBlobPairs (const pure) (const (serialize (SExpression ByConstructorName)))
|
||||||
_ -> toOutput
|
runDiff DOTDiffRenderer = withParsedBlobPairs (const pure) (const (render renderTreeGraph)) >=> serialize (DOT (diffStyle "diffs"))
|
||||||
|
|
||||||
-- | A task to parse a pair of 'Blob's, diff them, and render the 'Diff'.
|
data SomeTermPair typeclasses ann where
|
||||||
diffBlobPair :: Members '[Distribute WrappedTask, Task, Telemetry, Exc SomeException, IO] effs => DiffRenderer output -> BlobPair -> Eff effs output
|
SomeTermPair :: ApplyAll typeclasses syntax => Join These (Term syntax ann) -> SomeTermPair typeclasses ann
|
||||||
diffBlobPair renderer blobs
|
|
||||||
| Just (SomeParser parser) <- someParser (Proxy :: Proxy '[ConstructorName, Diffable, Eq1, GAlign, HasDeclaration, IdentifierName, Show1, ToJSONFields1, Traversable]) <$> effectiveLanguage
|
|
||||||
= case renderer of
|
|
||||||
ToCDiffRenderer -> run (WrapTask . (\ blob -> parse parser blob >>= decorate (declarationAlgebra blob))) diffTerms renderToCDiff
|
|
||||||
JSONDiffRenderer -> run (WrapTask . ( parse parser >=> decorate constructorLabel >=> decorate identifierLabel)) diffTerms renderJSONDiff
|
|
||||||
SExpressionDiffRenderer -> run (WrapTask . ( parse parser >=> decorate constructorLabel . (Nil <$))) diffTerms (const renderSExpressionDiff)
|
|
||||||
DOTDiffRenderer -> run (WrapTask . parse parser) diffTerms renderDOTDiff
|
|
||||||
| otherwise = throwError (SomeException (NoLanguageForBlob effectivePath))
|
|
||||||
where effectivePath = pathForBlobPair blobs
|
|
||||||
effectiveLanguage = languageForBlobPair blobs
|
|
||||||
|
|
||||||
run :: (Foldable syntax, Functor syntax) => Members [Distribute WrappedTask, Task, Telemetry, IO] effs => (Blob -> WrappedTask (Term syntax ann)) -> (Term syntax ann -> Term syntax ann -> Diff syntax ann ann) -> (BlobPair -> Diff syntax ann ann -> output) -> Eff effs output
|
withSomeTermPair :: (forall syntax . ApplyAll typeclasses syntax => Join These (Term syntax ann) -> a) -> SomeTermPair typeclasses ann -> a
|
||||||
run parse diff renderer = do
|
withSomeTermPair with (SomeTermPair terms) = with terms
|
||||||
terms <- distributeFor blobs parse
|
|
||||||
time "diff" languageTag $ do
|
|
||||||
diff <- diffTermPair diff (runJoin terms)
|
|
||||||
writeStat (Stat.count "diff.nodes" (bilength diff) languageTag)
|
|
||||||
render (renderer blobs) diff
|
|
||||||
where
|
|
||||||
languageTag = languageTagForBlobPair blobs
|
|
||||||
|
|
||||||
-- | A task to diff 'Term's, producing insertion/deletion 'Patch'es for non-existent 'Blob's.
|
withParsedBlobPairs :: (Members '[Distribute WrappedTask, Exc SomeException, IO, Task, Telemetry] effs, Monoid output)
|
||||||
diffTermPair :: (Functor syntax, Member Task effs) => Differ syntax ann1 ann2 -> These (Term syntax ann1) (Term syntax ann2) -> Eff effs (Diff syntax ann1 ann2)
|
=> (forall syntax . (ConstructorName syntax, Diffable syntax, Eq1 syntax, GAlign syntax, HasDeclaration syntax, IdentifierName syntax, Show1 syntax, ToJSONFields1 syntax, Traversable syntax) => Blob -> Term syntax (Record Location) -> TaskEff (Term syntax (Record fields)))
|
||||||
diffTermPair _ (This t1 ) = pure (deleting t1)
|
-> (forall syntax . (ConstructorName syntax, Diffable syntax, Eq1 syntax, GAlign syntax, HasDeclaration syntax, IdentifierName syntax, Show1 syntax, ToJSONFields1 syntax, Traversable syntax) => BlobPair -> Diff syntax (Record fields) (Record fields) -> TaskEff output)
|
||||||
diffTermPair _ (That t2) = pure (inserting t2)
|
-> [BlobPair]
|
||||||
diffTermPair differ (These t1 t2) = diff differ t1 t2
|
-> Eff effs output
|
||||||
|
withParsedBlobPairs decorate render = distributeFoldMap (\ blobs -> WrapTask (withParsedBlobPair decorate blobs >>= withSomeTermPair (diffTerms blobs >=> render blobs)))
|
||||||
|
where diffTerms :: (Diffable syntax, Eq1 syntax, GAlign syntax, Show1 syntax, Traversable syntax, Members '[IO, Task, Telemetry] effs) => BlobPair -> Join These (Term syntax (Record fields)) -> Eff effs (Diff syntax (Record fields) (Record fields))
|
||||||
|
diffTerms blobs terms = time "diff" languageTag $ do
|
||||||
|
diff <- diff (runJoin terms)
|
||||||
|
diff <$ writeStat (Stat.count "diff.nodes" (bilength diff) languageTag)
|
||||||
|
where languageTag = languageTagForBlobPair blobs
|
||||||
|
|
||||||
|
withParsedBlobPair :: Members '[Distribute WrappedTask, Exc SomeException, Task] effs
|
||||||
|
=> (forall syntax . (ConstructorName syntax, Diffable syntax, Eq1 syntax, GAlign syntax, HasDeclaration syntax, IdentifierName syntax, Show1 syntax, ToJSONFields1 syntax, Traversable syntax) => Blob -> Term syntax (Record Location) -> TaskEff (Term syntax (Record fields)))
|
||||||
|
-> BlobPair
|
||||||
|
-> Eff effs (SomeTermPair '[ConstructorName, Diffable, Eq1, GAlign, HasDeclaration, IdentifierName, Show1, ToJSONFields1, Traversable] (Record fields))
|
||||||
|
withParsedBlobPair decorate blobs
|
||||||
|
| Just (SomeParser parser) <- someParser @'[ConstructorName, Diffable, Eq1, GAlign, HasDeclaration, IdentifierName, Show1, ToJSONFields1, Traversable] <$> languageForBlobPair blobs
|
||||||
|
= SomeTermPair <$> distributeFor blobs (\ blob -> WrapTask (parse parser blob >>= decorate blob))
|
||||||
|
| otherwise = noLanguageForBlob (pathForBlobPair blobs)
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
{-# LANGUAGE GADTs, TypeOperators #-}
|
{-# LANGUAGE GADTs, TypeOperators #-}
|
||||||
module Semantic.Graph where
|
module Semantic.Graph
|
||||||
|
( graph
|
||||||
|
, GraphType(..)
|
||||||
|
, Graph
|
||||||
|
, Vertex
|
||||||
|
, style
|
||||||
|
, parsePackage
|
||||||
|
, withTermSpans
|
||||||
|
, resumingResolutionError
|
||||||
|
, resumingLoadError
|
||||||
|
, resumingEvalError
|
||||||
|
, resumingUnspecialized
|
||||||
|
, resumingAddressError
|
||||||
|
, resumingValueError
|
||||||
|
, resumingEnvironmentError
|
||||||
|
) where
|
||||||
|
|
||||||
import Analysis.Abstract.Evaluating
|
import Analysis.Abstract.Evaluating
|
||||||
import Analysis.Abstract.Graph
|
import Analysis.Abstract.Graph
|
||||||
@ -13,13 +28,11 @@ import Data.Abstract.Package as Package
|
|||||||
import Data.Abstract.Value (Value, ValueError(..), runValueErrorWith)
|
import Data.Abstract.Value (Value, ValueError(..), runValueErrorWith)
|
||||||
import Data.ByteString.Char8 (pack)
|
import Data.ByteString.Char8 (pack)
|
||||||
import Data.File
|
import Data.File
|
||||||
import Data.Output
|
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Semilattice.Lower
|
import Data.Semilattice.Lower
|
||||||
import Data.Term
|
import Data.Term
|
||||||
import Parsing.Parser
|
import Parsing.Parser
|
||||||
import Prologue hiding (MonadError (..))
|
import Prologue hiding (MonadError (..))
|
||||||
import Rendering.Renderer
|
|
||||||
import Semantic.IO (Files)
|
import Semantic.IO (Files)
|
||||||
import Semantic.Task as Task
|
import Semantic.Task as Task
|
||||||
|
|
||||||
@ -27,19 +40,16 @@ data GraphType = ImportGraph | CallGraph
|
|||||||
|
|
||||||
graph :: Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Telemetry, Trace] effs
|
graph :: Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Telemetry, Trace] effs
|
||||||
=> GraphType
|
=> GraphType
|
||||||
-> GraphRenderer output
|
|
||||||
-> Project
|
-> Project
|
||||||
-> Eff effs ByteString
|
-> Eff effs (Graph Vertex)
|
||||||
graph graphType renderer project
|
graph graphType project
|
||||||
| SomeAnalysisParser parser prelude <- someAnalysisParser
|
| SomeAnalysisParser parser prelude <- someAnalysisParser
|
||||||
(Proxy :: Proxy '[ Evaluatable, Declarations1, FreeVariables1, Functor, Eq1, Ord1, Show1 ]) (projectLanguage project) = do
|
(Proxy :: Proxy '[ Evaluatable, Declarations1, FreeVariables1, Functor, Eq1, Ord1, Show1 ]) (projectLanguage project) = do
|
||||||
package <- parsePackage parser prelude project
|
package <- parsePackage parser prelude project
|
||||||
let analyzeTerm = case graphType of
|
let analyzeTerm = case graphType of
|
||||||
ImportGraph -> id
|
ImportGraph -> id
|
||||||
CallGraph -> graphingTerms
|
CallGraph -> graphingTerms
|
||||||
analyze runGraphAnalysis (evaluatePackageWith graphingModules (withTermSpans . graphingLoadErrors . analyzeTerm) package) >>= extractGraph >>= case renderer of
|
analyze runGraphAnalysis (evaluatePackageWith graphingModules (withTermSpans . graphingLoadErrors . analyzeTerm) package) >>= extractGraph
|
||||||
JSONGraphRenderer -> pure . toOutput
|
|
||||||
DOTGraphRenderer -> pure . renderGraph
|
|
||||||
where extractGraph result = case result of
|
where extractGraph result = case result of
|
||||||
(Right ((_, graph), _), _) -> pure graph
|
(Right ((_, graph), _), _) -> pure graph
|
||||||
_ -> Task.throwError (toException (Exc.ErrorCall ("graphImports: import graph rendering failed " <> show result)))
|
_ -> Task.throwError (toException (Exc.ErrorCall ("graphImports: import graph rendering failed " <> show result)))
|
||||||
|
@ -11,12 +11,21 @@ module Semantic.IO
|
|||||||
, findFiles
|
, findFiles
|
||||||
, languageForFilePath
|
, languageForFilePath
|
||||||
, NoLanguageForBlob(..)
|
, NoLanguageForBlob(..)
|
||||||
, FormatNotSupported(..)
|
, noLanguageForBlob
|
||||||
, readBlob
|
, readBlob
|
||||||
, readProject
|
, readProject
|
||||||
, readBlobs
|
, readBlobs
|
||||||
, readBlobPairs
|
, readBlobPairs
|
||||||
, writeToOutput
|
, write
|
||||||
|
, Handle(..)
|
||||||
|
, getHandle
|
||||||
|
, IO.IOMode(..)
|
||||||
|
, stdin
|
||||||
|
, stdout
|
||||||
|
, stderr
|
||||||
|
, openFileForReading
|
||||||
|
, Source(..)
|
||||||
|
, Destination(..)
|
||||||
, Files
|
, Files
|
||||||
, runFiles
|
, runFiles
|
||||||
, rethrowing
|
, rethrowing
|
||||||
@ -31,9 +40,10 @@ import qualified Data.Blob as Blob
|
|||||||
import Data.Bool
|
import Data.Bool
|
||||||
import Data.File
|
import Data.File
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
|
import qualified Data.ByteString.Builder as B
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import Data.Language
|
import Data.Language
|
||||||
import Data.Source
|
import Data.Source (fromBytes, fromText)
|
||||||
import Prelude hiding (readFile)
|
import Prelude hiding (readFile)
|
||||||
import Prologue hiding (MonadError (..), fail)
|
import Prologue hiding (MonadError (..), fail)
|
||||||
import System.Directory (doesDirectoryExist)
|
import System.Directory (doesDirectoryExist)
|
||||||
@ -42,7 +52,7 @@ import System.Directory.Tree (AnchoredDirTree(..))
|
|||||||
import System.Exit
|
import System.Exit
|
||||||
import System.FilePath
|
import System.FilePath
|
||||||
import System.FilePath.Glob
|
import System.FilePath.Glob
|
||||||
import System.IO (Handle)
|
import qualified System.IO as IO
|
||||||
import Text.Read
|
import Text.Read
|
||||||
|
|
||||||
-- | Read a utf8-encoded file to a 'Blob'.
|
-- | Read a utf8-encoded file to a 'Blob'.
|
||||||
@ -53,14 +63,14 @@ readFile (File path language) = do
|
|||||||
pure $ Blob.sourceBlob path language . fromBytes <$> raw
|
pure $ Blob.sourceBlob path language . fromBytes <$> raw
|
||||||
|
|
||||||
readFilePair :: forall m. MonadIO m => File -> File -> m Blob.BlobPair
|
readFilePair :: forall m. MonadIO m => File -> File -> m Blob.BlobPair
|
||||||
readFilePair a b = do
|
readFilePair a b = Join <$> join (maybeThese <$> readFile a <*> readFile b)
|
||||||
before <- readFile a
|
|
||||||
after <- readFile b
|
maybeThese :: Monad m => Maybe a -> Maybe b -> m (These a b)
|
||||||
case (before, after) of
|
maybeThese a b = case (a, b) of
|
||||||
(Just a, Nothing) -> pure (Join (This a))
|
(Just a, Nothing) -> pure (This a)
|
||||||
(Nothing, Just b) -> pure (Join (That b))
|
(Nothing, Just b) -> pure (That b)
|
||||||
(Just a, Just b) -> pure (Join (These a b))
|
(Just a, Just b) -> pure (These a b)
|
||||||
_ -> fail "expected file pair with content on at least one side"
|
_ -> fail "expected file pair with content on at least one side"
|
||||||
|
|
||||||
isDirectory :: MonadIO m => FilePath -> m Bool
|
isDirectory :: MonadIO m => FilePath -> m Bool
|
||||||
isDirectory path = liftIO (doesDirectoryExist path)
|
isDirectory path = liftIO (doesDirectoryExist path)
|
||||||
@ -70,7 +80,7 @@ languageForFilePath :: FilePath -> Maybe Language
|
|||||||
languageForFilePath = languageForType . takeExtension
|
languageForFilePath = languageForType . takeExtension
|
||||||
|
|
||||||
-- | Read JSON encoded blob pairs from a handle.
|
-- | Read JSON encoded blob pairs from a handle.
|
||||||
readBlobPairsFromHandle :: MonadIO m => Handle -> m [Blob.BlobPair]
|
readBlobPairsFromHandle :: MonadIO m => Handle 'IO.ReadMode -> m [Blob.BlobPair]
|
||||||
readBlobPairsFromHandle = fmap toBlobPairs . readFromHandle
|
readBlobPairsFromHandle = fmap toBlobPairs . readFromHandle
|
||||||
where
|
where
|
||||||
toBlobPairs :: BlobDiff -> [Blob.BlobPair]
|
toBlobPairs :: BlobDiff -> [Blob.BlobPair]
|
||||||
@ -78,7 +88,7 @@ readBlobPairsFromHandle = fmap toBlobPairs . readFromHandle
|
|||||||
toBlobPair blobs = toBlob <$> blobs
|
toBlobPair blobs = toBlob <$> blobs
|
||||||
|
|
||||||
-- | Read JSON encoded blobs from a handle.
|
-- | Read JSON encoded blobs from a handle.
|
||||||
readBlobsFromHandle :: MonadIO m => Handle -> m [Blob.Blob]
|
readBlobsFromHandle :: MonadIO m => Handle 'IO.ReadMode -> m [Blob.Blob]
|
||||||
readBlobsFromHandle = fmap toBlobs . readFromHandle
|
readBlobsFromHandle = fmap toBlobs . readFromHandle
|
||||||
where toBlobs BlobParse{..} = fmap toBlob blobs
|
where toBlobs BlobParse{..} = fmap toBlob blobs
|
||||||
|
|
||||||
@ -134,8 +144,8 @@ readBlobsFromDir path = do
|
|||||||
blobs <- traverse readFile paths'
|
blobs <- traverse readFile paths'
|
||||||
pure (catMaybes blobs)
|
pure (catMaybes blobs)
|
||||||
|
|
||||||
readFromHandle :: (FromJSON a, MonadIO m) => Handle -> m a
|
readFromHandle :: (FromJSON a, MonadIO m) => Handle 'IO.ReadMode -> m a
|
||||||
readFromHandle h = do
|
readFromHandle (ReadHandle h) = do
|
||||||
input <- liftIO $ BL.hGetContents h
|
input <- liftIO $ BL.hGetContents h
|
||||||
case eitherDecode input of
|
case eitherDecode input of
|
||||||
Left e -> liftIO (die (e <> ". Invalid input on " <> show h <> ", expecting JSON"))
|
Left e -> liftIO (die (e <> ". Invalid input on " <> show h <> ", expecting JSON"))
|
||||||
@ -178,47 +188,77 @@ instance FromJSON BlobPair where
|
|||||||
newtype NoLanguageForBlob = NoLanguageForBlob FilePath
|
newtype NoLanguageForBlob = NoLanguageForBlob FilePath
|
||||||
deriving (Eq, Exception, Ord, Show, Typeable)
|
deriving (Eq, Exception, Ord, Show, Typeable)
|
||||||
|
|
||||||
-- | An exception indicating that the output format is not supported
|
noLanguageForBlob :: Member (Exc SomeException) effs => FilePath -> Eff effs a
|
||||||
newtype FormatNotSupported = FormatNotSupported String
|
noLanguageForBlob blobPath = throwError (SomeException (NoLanguageForBlob blobPath))
|
||||||
deriving (Eq, Exception, Ord, Show, Typeable)
|
|
||||||
|
|
||||||
readBlob :: Member Files effs => File -> Eff effs Blob.Blob
|
readBlob :: Member Files effs => File -> Eff effs Blob.Blob
|
||||||
readBlob = send . ReadBlob
|
readBlob = send . Read . FromPath
|
||||||
|
|
||||||
-- | A task which reads a list of 'Blob's from a 'Handle' or a list of 'FilePath's optionally paired with 'Language's.
|
-- | A task which reads a list of 'Blob's from a 'Handle' or a list of 'FilePath's optionally paired with 'Language's.
|
||||||
readBlobs :: Member Files effs => Either Handle [File] -> Eff effs [Blob.Blob]
|
readBlobs :: Member Files effs => Either (Handle 'IO.ReadMode) [File] -> Eff effs [Blob.Blob]
|
||||||
readBlobs = send . ReadBlobs
|
readBlobs (Left handle) = send (Read (FromHandle handle))
|
||||||
|
readBlobs (Right paths) = traverse (send . Read . FromPath) paths
|
||||||
|
|
||||||
-- | A task which reads a list of pairs of 'Blob's from a 'Handle' or a list of pairs of 'FilePath's optionally paired with 'Language's.
|
-- | A task which reads a list of pairs of 'Blob's from a 'Handle' or a list of pairs of 'FilePath's optionally paired with 'Language's.
|
||||||
readBlobPairs :: Member Files effs => Either Handle [Both File] -> Eff effs [Blob.BlobPair]
|
readBlobPairs :: Member Files effs => Either (Handle 'IO.ReadMode) [Both File] -> Eff effs [Blob.BlobPair]
|
||||||
readBlobPairs = send . ReadBlobPairs
|
readBlobPairs (Left handle) = send (Read (FromPairHandle handle))
|
||||||
|
readBlobPairs (Right paths) = traverse (send . Read . FromPathPair) paths
|
||||||
|
|
||||||
readProject :: Member Files effs => Maybe FilePath -> FilePath -> Language -> [FilePath] -> Eff effs Project
|
readProject :: Member Files effs => Maybe FilePath -> FilePath -> Language -> [FilePath] -> Eff effs Project
|
||||||
readProject rootDir dir excludeDirs = send . ReadProject rootDir dir excludeDirs
|
readProject rootDir dir excludeDirs = send . ReadProject rootDir dir excludeDirs
|
||||||
|
|
||||||
-- | A task which writes a 'B.ByteString' to a 'Handle' or a 'FilePath'.
|
-- | A task which writes a 'B.Builder' to a 'Handle' or a 'FilePath'.
|
||||||
writeToOutput :: Member Files effs => Either Handle FilePath -> B.ByteString -> Eff effs ()
|
write :: Member Files effs => Destination -> B.Builder -> Eff effs ()
|
||||||
writeToOutput path = send . WriteToOutput path
|
write dest = send . Write dest
|
||||||
|
|
||||||
|
data Handle mode where
|
||||||
|
ReadHandle :: IO.Handle -> Handle 'IO.ReadMode
|
||||||
|
WriteHandle :: IO.Handle -> Handle 'IO.WriteMode
|
||||||
|
|
||||||
|
deriving instance Eq (Handle mode)
|
||||||
|
deriving instance Show (Handle mode)
|
||||||
|
|
||||||
|
getHandle :: Handle mode -> IO.Handle
|
||||||
|
getHandle (ReadHandle handle) = handle
|
||||||
|
getHandle (WriteHandle handle) = handle
|
||||||
|
|
||||||
|
stdin :: Handle 'IO.ReadMode
|
||||||
|
stdin = ReadHandle IO.stdin
|
||||||
|
|
||||||
|
stdout :: Handle 'IO.WriteMode
|
||||||
|
stdout = WriteHandle IO.stdout
|
||||||
|
|
||||||
|
stderr :: Handle 'IO.WriteMode
|
||||||
|
stderr = WriteHandle IO.stderr
|
||||||
|
|
||||||
|
openFileForReading :: FilePath -> IO (Handle 'IO.ReadMode)
|
||||||
|
openFileForReading path = ReadHandle <$> IO.openFile path IO.ReadMode
|
||||||
|
|
||||||
|
data Source blob where
|
||||||
|
FromPath :: File -> Source Blob.Blob
|
||||||
|
FromHandle :: Handle 'IO.ReadMode -> Source [Blob.Blob]
|
||||||
|
FromPathPair :: Both File -> Source Blob.BlobPair
|
||||||
|
FromPairHandle :: Handle 'IO.ReadMode -> Source [Blob.BlobPair]
|
||||||
|
|
||||||
|
data Destination = ToPath FilePath | ToHandle (Handle 'IO.WriteMode)
|
||||||
|
|
||||||
-- | An effect to read/write 'Blob.Blob's from 'Handle's or 'FilePath's.
|
-- | An effect to read/write 'Blob.Blob's from 'Handle's or 'FilePath's.
|
||||||
data Files out where
|
data Files out where
|
||||||
ReadBlob :: File -> Files Blob.Blob
|
Read :: Source out -> Files out
|
||||||
ReadBlobs :: Either Handle [File] -> Files [Blob.Blob]
|
ReadProject :: Maybe FilePath -> FilePath -> Language -> [FilePath] -> Files Project
|
||||||
ReadBlobPairs :: Either Handle [Both File] -> Files [Blob.BlobPair]
|
Write :: Destination -> B.Builder -> Files ()
|
||||||
ReadProject :: Maybe FilePath -> FilePath -> Language -> [FilePath] -> Files Project
|
|
||||||
WriteToOutput :: Either Handle FilePath -> B.ByteString -> Files ()
|
|
||||||
|
|
||||||
-- | Run a 'Files' effect in 'IO'.
|
-- | Run a 'Files' effect in 'IO'.
|
||||||
runFiles :: Members '[Exc SomeException, IO] effs => Eff (Files ': effs) a -> Eff effs a
|
runFiles :: Members '[Exc SomeException, IO] effs => Eff (Files ': effs) a -> Eff effs a
|
||||||
runFiles = interpret $ \ files -> case files of
|
runFiles = interpret $ \ files -> case files of
|
||||||
ReadBlob path -> rethrowing (readBlobFromPath path)
|
Read (FromPath path) -> rethrowing (readBlobFromPath path)
|
||||||
ReadBlobs (Left handle) -> rethrowing (readBlobsFromHandle handle)
|
Read (FromHandle handle) -> rethrowing (readBlobsFromHandle handle)
|
||||||
ReadBlobs (Right paths@[File path _]) -> rethrowing (isDirectory path >>= bool (readBlobsFromPaths paths) (readBlobsFromDir path))
|
Read (FromPathPair paths) -> rethrowing (runBothWith readFilePair paths)
|
||||||
ReadBlobs (Right paths) -> rethrowing (readBlobsFromPaths paths)
|
Read (FromPairHandle handle) -> rethrowing (readBlobPairsFromHandle handle)
|
||||||
ReadBlobPairs source -> rethrowing (either readBlobPairsFromHandle (traverse (runBothWith readFilePair)) source)
|
|
||||||
ReadProject rootDir dir language excludeDirs -> rethrowing (readProjectFromPaths rootDir dir language excludeDirs)
|
ReadProject rootDir dir language excludeDirs -> rethrowing (readProjectFromPaths rootDir dir language excludeDirs)
|
||||||
WriteToOutput destination contents -> liftIO (either B.hPutStr B.writeFile destination contents)
|
Write (ToPath path) builder -> liftIO (IO.withBinaryFile path IO.WriteMode (`B.hPutBuilder` builder))
|
||||||
|
Write (ToHandle (WriteHandle handle)) builder -> liftIO (B.hPutBuilder handle builder)
|
||||||
|
|
||||||
|
|
||||||
-- | Catch exceptions in 'IO' actions embedded in 'Eff', handling them with the passed function.
|
-- | Catch exceptions in 'IO' actions embedded in 'Eff', handling them with the passed function.
|
||||||
|
@ -1,53 +1,33 @@
|
|||||||
{-# LANGUAGE GADTs #-}
|
{-# LANGUAGE GADTs, RankNTypes #-}
|
||||||
module Semantic.Parse where
|
module Semantic.Parse where
|
||||||
|
|
||||||
import Analysis.ConstructorName (ConstructorName, constructorLabel)
|
import Analysis.ConstructorName (ConstructorName, constructorLabel)
|
||||||
import Analysis.IdentifierName (IdentifierName, identifierLabel)
|
import Analysis.IdentifierName (IdentifierName, identifierLabel)
|
||||||
import Analysis.Declaration (HasDeclaration, declarationAlgebra)
|
import Analysis.Declaration (HasDeclaration, declarationAlgebra)
|
||||||
import Analysis.PackageDef (HasPackageDef, packageDefAlgebra)
|
import Analysis.PackageDef (HasPackageDef, packageDefAlgebra)
|
||||||
|
import Data.AST
|
||||||
import Data.Blob
|
import Data.Blob
|
||||||
import Data.JSON.Fields
|
import Data.JSON.Fields
|
||||||
import Data.Output
|
|
||||||
import Data.Record
|
import Data.Record
|
||||||
|
import Data.Term
|
||||||
import Parsing.Parser
|
import Parsing.Parser
|
||||||
import Prologue hiding (MonadError(..))
|
import Prologue hiding (MonadError(..))
|
||||||
|
import Rendering.Graph
|
||||||
import Rendering.Renderer
|
import Rendering.Renderer
|
||||||
import Semantic.IO (NoLanguageForBlob(..), FormatNotSupported(..))
|
import Semantic.IO (noLanguageForBlob)
|
||||||
import Semantic.Task
|
import Semantic.Task
|
||||||
|
import Serializing.Format
|
||||||
|
|
||||||
parseBlobs :: (Members '[Distribute WrappedTask, Task, Exc SomeException] effs, Output output) => TermRenderer output -> [Blob] -> Eff effs ByteString
|
runParse :: Members '[Distribute WrappedTask, Task, Exc SomeException] effs => TermRenderer output -> [Blob] -> Eff effs Builder
|
||||||
parseBlobs renderer blobs = toOutput' <$> distributeFoldMap (WrapTask . parseBlob renderer) blobs
|
runParse JSONTermRenderer = withParsedBlobs (\ blob -> decorate constructorLabel >=> decorate identifierLabel >=> render (renderJSONTerm blob)) >=> serialize JSON
|
||||||
where toOutput' = case renderer of
|
runParse SExpressionTermRenderer = withParsedBlobs (const (serialize (SExpression ByConstructorName)))
|
||||||
JSONTermRenderer -> toOutput . renderJSONTerms
|
runParse TagsTermRenderer = withParsedBlobs (\ blob -> decorate (declarationAlgebra blob) >=> render (renderToTags blob)) >=> serialize JSON
|
||||||
SymbolsTermRenderer _ -> toOutput . renderSymbolTerms
|
runParse ImportsTermRenderer = withParsedBlobs (\ blob -> decorate (declarationAlgebra blob) >=> decorate (packageDefAlgebra blob) >=> render (renderToImports blob)) >=> serialize JSON
|
||||||
_ -> toOutput
|
runParse (SymbolsTermRenderer fields) = withParsedBlobs (\ blob -> decorate (declarationAlgebra blob) >=> render (renderSymbolTerms . renderToSymbols fields blob)) >=> serialize JSON
|
||||||
|
runParse DOTTermRenderer = withParsedBlobs (const (render renderTreeGraph)) >=> serialize (DOT (termStyle "terms"))
|
||||||
|
|
||||||
-- | A task to parse a 'Blob' and render the resulting 'Term'.
|
withParsedBlobs :: (Members '[Distribute WrappedTask, Task, Exc SomeException] effs, Monoid output) => (forall syntax . (ConstructorName syntax, HasPackageDef syntax, HasDeclaration syntax, IdentifierName syntax, Foldable syntax, Functor syntax, ToJSONFields1 syntax) => Blob -> Term syntax (Record Location) -> TaskEff output) -> [Blob] -> Eff effs output
|
||||||
parseBlob :: Members '[Task, Exc SomeException] effs => TermRenderer output -> Blob -> Eff effs output
|
withParsedBlobs render = distributeFoldMap (\ blob -> WrapTask (parseSomeBlob blob >>= withSomeTerm (render blob)))
|
||||||
parseBlob renderer blob@Blob{..}
|
|
||||||
| Just (SomeParser parser) <- someParser (Proxy :: Proxy '[ConstructorName, HasPackageDef, HasDeclaration, IdentifierName, Foldable, Functor, ToJSONFields1]) <$> blobLanguage
|
|
||||||
= parse parser blob >>= case renderer of
|
|
||||||
JSONTermRenderer -> decorate constructorLabel >=> decorate identifierLabel >=> render (renderJSONTerm blob)
|
|
||||||
SExpressionTermRenderer -> decorate constructorLabel . (Nil <$) >=> render renderSExpressionTerm
|
|
||||||
TagsTermRenderer -> decorate (declarationAlgebra blob) >=> render (renderToTags blob)
|
|
||||||
ImportsTermRenderer -> decorate (declarationAlgebra blob) >=> decorate (packageDefAlgebra blob) >=> render (renderToImports blob)
|
|
||||||
SymbolsTermRenderer fields -> decorate (declarationAlgebra blob) >=> render (renderToSymbols fields blob)
|
|
||||||
DOTTermRenderer -> render (renderDOTTerm blob)
|
|
||||||
| otherwise = throwError (SomeException (NoLanguageForBlob blobPath))
|
|
||||||
|
|
||||||
|
parseSomeBlob :: Members '[Task, Exc SomeException] effs => Blob -> Eff effs (SomeTerm '[ConstructorName, HasPackageDef, HasDeclaration, IdentifierName, Foldable, Functor, ToJSONFields1] (Record Location))
|
||||||
astParseBlobs :: (Members '[Distribute WrappedTask, Task, Exc SomeException] effs, Output output) => TermRenderer output -> [Blob] -> Eff effs ByteString
|
parseSomeBlob blob@Blob{..} = maybe (noLanguageForBlob blobPath) (flip parse blob . someParser) blobLanguage
|
||||||
astParseBlobs renderer blobs = toOutput' <$> distributeFoldMap (WrapTask . astParseBlob renderer) blobs
|
|
||||||
where
|
|
||||||
toOutput' = case renderer of
|
|
||||||
JSONTermRenderer -> toOutput . renderJSONTerms
|
|
||||||
_ -> toOutput
|
|
||||||
|
|
||||||
astParseBlob :: Members '[Task, Exc SomeException] effs => TermRenderer output -> Blob -> Eff effs output
|
|
||||||
astParseBlob renderer blob@Blob{..}
|
|
||||||
| Just (SomeASTParser parser) <- someASTParser <$> blobLanguage
|
|
||||||
= parse parser blob >>= case renderer of
|
|
||||||
SExpressionTermRenderer -> render renderSExpressionAST
|
|
||||||
JSONTermRenderer -> render (renderJSONTerm' blob)
|
|
||||||
_ -> pure $ throwError (SomeException (FormatNotSupported "Only SExpression and JSON output supported for tree-sitter ASTs."))
|
|
||||||
| otherwise = throwError (SomeException (NoLanguageForBlob blobPath))
|
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
{-# LANGUAGE ConstraintKinds, GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeOperators, UndecidableInstances #-}
|
{-# LANGUAGE ConstraintKinds, GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeOperators #-}
|
||||||
module Semantic.Task
|
module Semantic.Task
|
||||||
( Task
|
( Task
|
||||||
, TaskEff
|
, TaskEff
|
||||||
, WrappedTask(..)
|
, WrappedTask(..)
|
||||||
, Level(..)
|
, Level(..)
|
||||||
, RAlgebra
|
, RAlgebra
|
||||||
, Differ
|
|
||||||
-- * I/O
|
-- * I/O
|
||||||
, IO.readBlob
|
, IO.readBlob
|
||||||
, IO.readBlobs
|
, IO.readBlobs
|
||||||
, IO.readBlobPairs
|
, IO.readBlobPairs
|
||||||
, IO.readProject
|
, IO.readProject
|
||||||
, IO.writeToOutput
|
, IO.write
|
||||||
-- * Telemetry
|
-- * Telemetry
|
||||||
, writeLog
|
, writeLog
|
||||||
, writeStat
|
, writeStat
|
||||||
@ -22,6 +21,7 @@ module Semantic.Task
|
|||||||
, decorate
|
, decorate
|
||||||
, diff
|
, diff
|
||||||
, render
|
, render
|
||||||
|
, serialize
|
||||||
-- * Concurrency
|
-- * Concurrency
|
||||||
, distribute
|
, distribute
|
||||||
, distributeFor
|
, distributeFor
|
||||||
@ -52,11 +52,14 @@ import Control.Monad.Effect.Exception
|
|||||||
import Control.Monad.Effect.Reader
|
import Control.Monad.Effect.Reader
|
||||||
import Control.Monad.Effect.Trace
|
import Control.Monad.Effect.Trace
|
||||||
import Data.Blob
|
import Data.Blob
|
||||||
|
import Data.ByteString.Builder
|
||||||
import Data.Diff
|
import Data.Diff
|
||||||
import qualified Data.Error as Error
|
import qualified Data.Error as Error
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import qualified Data.Syntax as Syntax
|
import qualified Data.Syntax as Syntax
|
||||||
import Data.Term
|
import Data.Term
|
||||||
|
import Diffing.Algorithm (Diffable)
|
||||||
|
import Diffing.Interpreter
|
||||||
import Parsing.CMark
|
import Parsing.CMark
|
||||||
import Parsing.Parser
|
import Parsing.Parser
|
||||||
import Parsing.TreeSitter
|
import Parsing.TreeSitter
|
||||||
@ -67,6 +70,7 @@ import Semantic.Log
|
|||||||
import Semantic.Queue
|
import Semantic.Queue
|
||||||
import Semantic.Stat as Stat
|
import Semantic.Stat as Stat
|
||||||
import Semantic.Telemetry
|
import Semantic.Telemetry
|
||||||
|
import Serializing.Format hiding (Options)
|
||||||
import System.Exit (die)
|
import System.Exit (die)
|
||||||
import System.IO (stderr)
|
import System.IO (stderr)
|
||||||
|
|
||||||
@ -84,9 +88,6 @@ type TaskEff = Eff '[Distribute WrappedTask
|
|||||||
newtype WrappedTask a = WrapTask { unwrapTask :: TaskEff a }
|
newtype WrappedTask a = WrapTask { unwrapTask :: TaskEff a }
|
||||||
deriving (Applicative, Functor, Monad)
|
deriving (Applicative, Functor, Monad)
|
||||||
|
|
||||||
-- | A function to compute the 'Diff' for a pair of 'Term's with arbitrary syntax functor & annotation types.
|
|
||||||
type Differ syntax ann1 ann2 = Term syntax ann1 -> Term syntax ann2 -> Diff syntax ann1 ann2
|
|
||||||
|
|
||||||
-- | A function to render terms or diffs.
|
-- | A function to render terms or diffs.
|
||||||
type Renderer i o = i -> o
|
type Renderer i o = i -> o
|
||||||
|
|
||||||
@ -103,13 +104,16 @@ decorate :: (Functor f, Member Task effs) => RAlgebra (TermF f (Record fields))
|
|||||||
decorate algebra = send . Decorate algebra
|
decorate algebra = send . Decorate algebra
|
||||||
|
|
||||||
-- | A task which diffs a pair of terms using the supplied 'Differ' function.
|
-- | A task which diffs a pair of terms using the supplied 'Differ' function.
|
||||||
diff :: Member Task effs => Differ syntax ann1 ann2 -> Term syntax ann1 -> Term syntax ann2 -> Eff effs (Diff syntax ann1 ann2)
|
diff :: (Diffable syntax, Eq1 syntax, GAlign syntax, Show1 syntax, Traversable syntax, Member Task effs) => These (Term syntax (Record fields1)) (Term syntax (Record fields2)) -> Eff effs (Diff syntax (Record fields1) (Record fields2))
|
||||||
diff differ term1 term2 = send (Semantic.Task.Diff differ term1 term2)
|
diff terms = send (Semantic.Task.Diff terms)
|
||||||
|
|
||||||
-- | A task which renders some input using the supplied 'Renderer' function.
|
-- | A task which renders some input using the supplied 'Renderer' function.
|
||||||
render :: Member Task effs => Renderer input output -> input -> Eff effs output
|
render :: Member Task effs => Renderer input output -> input -> Eff effs output
|
||||||
render renderer = send . Render renderer
|
render renderer = send . Render renderer
|
||||||
|
|
||||||
|
serialize :: Member Task effs => Format input -> input -> Eff effs Builder
|
||||||
|
serialize format = send . Serialize format
|
||||||
|
|
||||||
-- | Execute a 'Task' with the 'defaultOptions', yielding its result value in 'IO'.
|
-- | Execute a 'Task' with the 'defaultOptions', yielding its result value in 'IO'.
|
||||||
--
|
--
|
||||||
-- > runTask = runTaskWithOptions defaultOptions
|
-- > runTask = runTaskWithOptions defaultOptions
|
||||||
@ -140,11 +144,12 @@ runTraceInTelemetry = interpret (\ (Trace str) -> writeLog Debug str [])
|
|||||||
|
|
||||||
-- | An effect describing high-level tasks to be performed.
|
-- | An effect describing high-level tasks to be performed.
|
||||||
data Task output where
|
data Task output where
|
||||||
Parse :: Parser term -> Blob -> Task term
|
Parse :: Parser term -> Blob -> Task term
|
||||||
Analyze :: (Analysis.Evaluator location value effects a -> result) -> Analysis.Evaluator location value effects a -> Task result
|
Analyze :: (Analysis.Evaluator location value effects a -> result) -> Analysis.Evaluator location value effects a -> Task result
|
||||||
Decorate :: Functor f => RAlgebra (TermF f (Record fields)) (Term f (Record fields)) field -> Term f (Record fields) -> Task (Term f (Record (field ': fields)))
|
Decorate :: Functor f => RAlgebra (TermF f (Record fields)) (Term f (Record fields)) field -> Term f (Record fields) -> Task (Term f (Record (field ': fields)))
|
||||||
Diff :: Differ syntax ann1 ann2 -> Term syntax ann1 -> Term syntax ann2 -> Task (Diff syntax ann1 ann2)
|
Diff :: (Diffable syntax, Eq1 syntax, GAlign syntax, Show1 syntax, Traversable syntax) => These (Term syntax (Record fields1)) (Term syntax (Record fields2)) -> Task (Diff syntax (Record fields1) (Record fields2))
|
||||||
Render :: Renderer input output -> input -> Task output
|
Render :: Renderer input output -> input -> Task output
|
||||||
|
Serialize :: Format input -> input -> Task Builder
|
||||||
|
|
||||||
-- | Run a 'Task' effect by performing the actions in 'IO'.
|
-- | Run a 'Task' effect by performing the actions in 'IO'.
|
||||||
runTaskF :: Members '[Reader Options, Telemetry, Exc SomeException, Trace, IO] effs => Eff (Task ': effs) a -> Eff effs a
|
runTaskF :: Members '[Reader Options, Telemetry, Exc SomeException, Trace, IO] effs => Eff (Task ': effs) a -> Eff effs a
|
||||||
@ -152,8 +157,9 @@ runTaskF = interpret $ \ task -> case task of
|
|||||||
Parse parser blob -> runParser blob parser
|
Parse parser blob -> runParser blob parser
|
||||||
Analyze interpret analysis -> pure (interpret analysis)
|
Analyze interpret analysis -> pure (interpret analysis)
|
||||||
Decorate algebra term -> pure (decoratorWithAlgebra algebra term)
|
Decorate algebra term -> pure (decoratorWithAlgebra algebra term)
|
||||||
Semantic.Task.Diff differ term1 term2 -> pure (differ term1 term2)
|
Semantic.Task.Diff terms -> pure (diffTermPair terms)
|
||||||
Render renderer input -> pure (renderer input)
|
Render renderer input -> pure (renderer input)
|
||||||
|
Serialize format input -> pure (runSerialize format input)
|
||||||
|
|
||||||
|
|
||||||
-- | Log an 'Error.Error' at the specified 'Level'.
|
-- | Log an 'Error.Error' at the specified 'Level'.
|
||||||
@ -193,6 +199,7 @@ runParser blob@Blob{..} parser = case parser of
|
|||||||
time "parse.cmark_parse" languageTag $
|
time "parse.cmark_parse" languageTag $
|
||||||
let term = cmarkParser blobSource
|
let term = cmarkParser blobSource
|
||||||
in length term `seq` pure term
|
in length term `seq` pure term
|
||||||
|
SomeParser parser -> SomeTerm <$> runParser blob parser
|
||||||
where blobFields = ("path", blobPath) : languageTag
|
where blobFields = ("path", blobPath) : languageTag
|
||||||
languageTag = maybe [] (pure . (,) ("language" :: String) . show) blobLanguage
|
languageTag = maybe [] (pure . (,) ("language" :: String) . show) blobLanguage
|
||||||
errors :: (Syntax.Error :< fs, Apply Foldable fs, Apply Functor fs) => Term (Sum fs) (Record Assignment.Location) -> [Error.Error String]
|
errors :: (Syntax.Error :< fs, Apply Foldable fs, Apply Functor fs) => Term (Sum fs) (Record Assignment.Location) -> [Error.Error String]
|
||||||
|
40
src/Serializing/DOT.hs
Normal file
40
src/Serializing/DOT.hs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
module Serializing.DOT
|
||||||
|
( Style
|
||||||
|
, serializeDOT
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Algebra.Graph.Class
|
||||||
|
import Algebra.Graph.Export hiding ((<+>))
|
||||||
|
import Algebra.Graph.Export.Dot hiding (export)
|
||||||
|
import Data.List
|
||||||
|
import Data.String
|
||||||
|
import Prologue
|
||||||
|
|
||||||
|
-- | Serialize a graph to DOT format.
|
||||||
|
--
|
||||||
|
-- This is modified from @algebraic-graphs@’ definition to avoid the need for 'Eq' constraints on @s@, thereby enabling us to efficiently construct bytestrings using Builders.
|
||||||
|
serializeDOT :: (IsString s, Monoid s, Ord a, ToGraph g, ToVertex g ~ a) => Style a s -> g -> s
|
||||||
|
serializeDOT Style {..} g = render $ header <> body <> "}\n"
|
||||||
|
where
|
||||||
|
header = "digraph" <+> literal graphName <> "\n{\n"
|
||||||
|
<> literal preamble <> "\n"
|
||||||
|
with x as = if null as then mempty else line (x <+> attributes as)
|
||||||
|
line s = indent 2 s <> "\n"
|
||||||
|
body = ("graph" `with` graphAttributes)
|
||||||
|
<> ("node" `with` defaultVertexAttributes)
|
||||||
|
<> ("edge" `with` defaultEdgeAttributes)
|
||||||
|
<> export vDoc eDoc g
|
||||||
|
label = doubleQuotes . literal . vertexName
|
||||||
|
vDoc x = line $ label x <+> attributes (vertexAttributes x)
|
||||||
|
eDoc x y = line $ label x <> " -> " <> label y <+> attributes (edgeAttributes x y)
|
||||||
|
|
||||||
|
|
||||||
|
(<+>) :: IsString s => Doc s -> Doc s -> Doc s
|
||||||
|
x <+> y = x <> " " <> y
|
||||||
|
|
||||||
|
attributes :: IsString s => [Attribute s] -> Doc s
|
||||||
|
attributes [] = mempty
|
||||||
|
attributes as = brackets . mconcat . intersperse " " $ map dot as
|
||||||
|
where
|
||||||
|
dot (k := v) = literal k <> "=" <> doubleQuotes (literal v)
|
34
src/Serializing/Format.hs
Normal file
34
src/Serializing/Format.hs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
module Serializing.Format
|
||||||
|
( Format(..)
|
||||||
|
, Builder
|
||||||
|
, runSerialize
|
||||||
|
, SomeFormat(..)
|
||||||
|
, Options(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Algebra.Graph.Class
|
||||||
|
import Data.Aeson (ToJSON(..), fromEncoding)
|
||||||
|
import Data.ByteString.Builder
|
||||||
|
import Prologue
|
||||||
|
import Serializing.DOT
|
||||||
|
import Serializing.SExpression
|
||||||
|
|
||||||
|
data Format input where
|
||||||
|
DOT :: (Ord vertex, ToGraph graph, ToVertex graph ~ vertex) => Style vertex Builder -> Format graph
|
||||||
|
JSON :: ToJSON input => Format input
|
||||||
|
SExpression :: (Recursive input, ToSExpression (Base input)) => Options -> Format input
|
||||||
|
Show :: Show input => Format input
|
||||||
|
|
||||||
|
runSerialize :: Format input -> input -> Builder
|
||||||
|
runSerialize (DOT style) = serializeDOT style
|
||||||
|
runSerialize JSON = (<> "\n") . fromEncoding . toEncoding
|
||||||
|
runSerialize (SExpression opts) = serializeSExpression opts
|
||||||
|
runSerialize Show = stringUtf8 . show
|
||||||
|
|
||||||
|
-- TODO: it would be kinda neat if we could use pretty-show/hscolour for Show output
|
||||||
|
|
||||||
|
|
||||||
|
-- | Abstract over a 'Format'’s input type.
|
||||||
|
data SomeFormat where
|
||||||
|
SomeFormat :: Format input -> SomeFormat
|
48
src/Serializing/SExpression.hs
Normal file
48
src/Serializing/SExpression.hs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{-# LANGUAGE GADTs, RankNTypes, ScopedTypeVariables #-}
|
||||||
|
module Serializing.SExpression
|
||||||
|
( serializeSExpression
|
||||||
|
, ToSExpression(..)
|
||||||
|
, Options(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Analysis.ConstructorName
|
||||||
|
import Data.ByteString.Builder
|
||||||
|
import Data.Diff
|
||||||
|
import Data.Patch
|
||||||
|
import Data.Term
|
||||||
|
import Prelude
|
||||||
|
import Prologue
|
||||||
|
|
||||||
|
data Options = ByShow | ByConstructorName
|
||||||
|
|
||||||
|
serializeSExpression :: (Recursive t, ToSExpression (Base t)) => Options -> t -> Builder
|
||||||
|
serializeSExpression options t = cata (toSExpression options) t 0 <> "\n"
|
||||||
|
|
||||||
|
branch :: Foldable syntax => String -> syntax (Int -> Builder) -> Int -> Builder
|
||||||
|
branch name syntax n = "(" <> stringUtf8 name <> foldMap ($ (n + 1)) syntax <> ")"
|
||||||
|
|
||||||
|
namedBranch :: (ConstructorName syntax, Foldable syntax, Show ann) => Options -> TermF syntax ann (Int -> Builder) -> Int -> Builder
|
||||||
|
namedBranch ByShow (In ann syntax) = branch (show ann) syntax
|
||||||
|
namedBranch ByConstructorName (In _ syntax) = branch (constructorName syntax) syntax
|
||||||
|
|
||||||
|
nl :: Int -> Builder
|
||||||
|
nl n | n <= 0 = ""
|
||||||
|
| otherwise = "\n"
|
||||||
|
|
||||||
|
pad :: Int -> Builder
|
||||||
|
pad n = stringUtf8 (replicate (2 * n) ' ')
|
||||||
|
|
||||||
|
|
||||||
|
class ToSExpression base where
|
||||||
|
toSExpression :: Options -> base (Int -> Builder) -> (Int -> Builder)
|
||||||
|
|
||||||
|
instance (ConstructorName syntax, Foldable syntax, Show ann) => ToSExpression (TermF syntax ann) where
|
||||||
|
toSExpression options term n = nl n <> pad n <> namedBranch options term n
|
||||||
|
|
||||||
|
instance (ConstructorName syntax, Foldable syntax, Show ann1, Show ann2) => ToSExpression (DiffF syntax ann1 ann2) where
|
||||||
|
toSExpression options diff n = case diff of
|
||||||
|
Patch (Delete term) -> nl n <> pad (n - 1) <> "{-" <> namedBranch options term n <> "-}"
|
||||||
|
Patch (Insert term) -> nl n <> pad (n - 1) <> "{+" <> namedBranch options term n <> "+}"
|
||||||
|
Patch (Replace term1 term2) -> nl n <> pad (n - 1) <> "{ " <> namedBranch options term1 n
|
||||||
|
<> nl (n + 1) <> pad (n - 1) <> "->" <> namedBranch options term2 n <> " }"
|
||||||
|
Merge term -> nl n <> pad n <> namedBranch options term n
|
@ -6,6 +6,7 @@ import Data.Functor.Listable
|
|||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Sum
|
import Data.Sum
|
||||||
import Data.Term
|
import Data.Term
|
||||||
|
import Data.These
|
||||||
import Diffing.Interpreter
|
import Diffing.Interpreter
|
||||||
import qualified Data.Syntax as Syntax
|
import qualified Data.Syntax as Syntax
|
||||||
import Test.Hspec (Spec, describe, it, parallel)
|
import Test.Hspec (Spec, describe, it, parallel)
|
||||||
@ -47,3 +48,12 @@ spec = parallel $ do
|
|||||||
b = wrap [a]
|
b = wrap [a]
|
||||||
c = wrap [a, b] in
|
c = wrap [a, b] in
|
||||||
diffTerms (wrap [a, b, c]) (wrap [b, c, a :: Term ListableSyntax (Record '[])]) `shouldBe` merge (Nil, Nil) (injectSum [ deleting a, merging b, merging c, inserting a ])
|
diffTerms (wrap [a, b, c]) (wrap [b, c, a :: Term ListableSyntax (Record '[])]) `shouldBe` merge (Nil, Nil) (injectSum [ deleting a, merging b, merging c, inserting a ])
|
||||||
|
|
||||||
|
describe "diffTermPair" $ do
|
||||||
|
prop "produces an Insert when the first term is missing" $ do
|
||||||
|
\ after -> let diff = diffTermPair (That after) :: Diff ListableSyntax (Record '[]) (Record '[]) in
|
||||||
|
diff `shouldBe` inserting after
|
||||||
|
|
||||||
|
prop "produces a Delete when the second term is missing" $ do
|
||||||
|
\ before -> let diff = diffTermPair (This before) :: Diff ListableSyntax (Record '[]) (Record '[]) in
|
||||||
|
diff `shouldBe` deleting before
|
||||||
|
@ -145,23 +145,23 @@ spec = parallel $ do
|
|||||||
describe "diff with ToCDiffRenderer'" $ do
|
describe "diff with ToCDiffRenderer'" $ do
|
||||||
it "produces JSON output" $ do
|
it "produces JSON output" $ do
|
||||||
blobs <- blobsForPaths (both "ruby/toc/methods.A.rb" "ruby/toc/methods.B.rb")
|
blobs <- blobsForPaths (both "ruby/toc/methods.A.rb" "ruby/toc/methods.B.rb")
|
||||||
output <- runTask (diffBlobPair ToCDiffRenderer blobs)
|
output <- runTask (runDiff ToCDiffRenderer [blobs])
|
||||||
toOutput output `shouldBe` ("{\"changes\":{\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"category\":\"Method\",\"term\":\"self.foo\",\"changeType\":\"added\"},{\"span\":{\"start\":[4,1],\"end\":[6,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"},{\"span\":{\"start\":[4,1],\"end\":[5,4]},\"category\":\"Method\",\"term\":\"baz\",\"changeType\":\"removed\"}]},\"errors\":{}}\n" :: ByteString)
|
runBuilder output `shouldBe` ("{\"changes\":{\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"category\":\"Method\",\"term\":\"self.foo\",\"changeType\":\"added\"},{\"span\":{\"start\":[4,1],\"end\":[6,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"},{\"span\":{\"start\":[4,1],\"end\":[5,4]},\"category\":\"Method\",\"term\":\"baz\",\"changeType\":\"removed\"}]},\"errors\":{}}\n" :: ByteString)
|
||||||
|
|
||||||
it "produces JSON output if there are parse errors" $ do
|
it "produces JSON output if there are parse errors" $ do
|
||||||
blobs <- blobsForPaths (both "ruby/toc/methods.A.rb" "ruby/toc/methods.X.rb")
|
blobs <- blobsForPaths (both "ruby/toc/methods.A.rb" "ruby/toc/methods.X.rb")
|
||||||
output <- runTask (diffBlobPair ToCDiffRenderer blobs)
|
output <- runTask (runDiff ToCDiffRenderer [blobs])
|
||||||
toOutput output `shouldBe` ("{\"changes\":{\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.X.rb\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"removed\"},{\"span\":{\"start\":[4,1],\"end\":[5,4]},\"category\":\"Method\",\"term\":\"baz\",\"changeType\":\"removed\"}]},\"errors\":{\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.X.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,1]},\"error\":\"expected end of input nodes, but got ParseError\",\"language\":\"Ruby\"}]}}\n" :: ByteString)
|
runBuilder output `shouldBe` ("{\"changes\":{\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.X.rb\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"removed\"},{\"span\":{\"start\":[4,1],\"end\":[5,4]},\"category\":\"Method\",\"term\":\"baz\",\"changeType\":\"removed\"}]},\"errors\":{\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.X.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,1]},\"error\":\"expected end of input nodes, but got ParseError\",\"language\":\"Ruby\"}]}}\n" :: ByteString)
|
||||||
|
|
||||||
it "ignores anonymous functions" $ do
|
it "ignores anonymous functions" $ do
|
||||||
blobs <- blobsForPaths (both "ruby/toc/lambda.A.rb" "ruby/toc/lambda.B.rb")
|
blobs <- blobsForPaths (both "ruby/toc/lambda.A.rb" "ruby/toc/lambda.B.rb")
|
||||||
output <- runTask (diffBlobPair ToCDiffRenderer blobs)
|
output <- runTask (runDiff ToCDiffRenderer [blobs])
|
||||||
toOutput output `shouldBe` ("{\"changes\":{},\"errors\":{}}\n" :: ByteString)
|
runBuilder output `shouldBe` ("{\"changes\":{},\"errors\":{}}\n" :: ByteString)
|
||||||
|
|
||||||
it "summarizes Markdown headings" $ do
|
it "summarizes Markdown headings" $ do
|
||||||
blobs <- blobsForPaths (both "markdown/toc/headings.A.md" "markdown/toc/headings.B.md")
|
blobs <- blobsForPaths (both "markdown/toc/headings.A.md" "markdown/toc/headings.B.md")
|
||||||
output <- runTask (diffBlobPair ToCDiffRenderer blobs)
|
output <- runTask (runDiff ToCDiffRenderer [blobs])
|
||||||
toOutput output `shouldBe` ("{\"changes\":{\"test/fixtures/markdown/toc/headings.A.md -> test/fixtures/markdown/toc/headings.B.md\":[{\"span\":{\"start\":[1,1],\"end\":[3,16]},\"category\":\"Heading 1\",\"term\":\"Introduction\",\"changeType\":\"removed\"},{\"span\":{\"start\":[5,1],\"end\":[7,4]},\"category\":\"Heading 2\",\"term\":\"Two\",\"changeType\":\"modified\"},{\"span\":{\"start\":[9,1],\"end\":[11,10]},\"category\":\"Heading 3\",\"term\":\"This heading is new\",\"changeType\":\"added\"},{\"span\":{\"start\":[13,1],\"end\":[14,4]},\"category\":\"Heading 1\",\"term\":\"Final\",\"changeType\":\"added\"}]},\"errors\":{}}\n" :: ByteString)
|
runBuilder output `shouldBe` ("{\"changes\":{\"test/fixtures/markdown/toc/headings.A.md -> test/fixtures/markdown/toc/headings.B.md\":[{\"span\":{\"start\":[1,1],\"end\":[3,16]},\"category\":\"Heading 1\",\"term\":\"Introduction\",\"changeType\":\"removed\"},{\"span\":{\"start\":[5,1],\"end\":[7,4]},\"category\":\"Heading 2\",\"term\":\"Two\",\"changeType\":\"modified\"},{\"span\":{\"start\":[9,1],\"end\":[11,10]},\"category\":\"Heading 3\",\"term\":\"This heading is new\",\"changeType\":\"added\"},{\"span\":{\"start\":[13,1],\"end\":[14,4]},\"category\":\"Heading 1\",\"term\":\"Final\",\"changeType\":\"added\"}]},\"errors\":{}}\n" :: ByteString)
|
||||||
|
|
||||||
|
|
||||||
type Diff' = Diff ListableSyntax (Record '[Maybe Declaration, Range, Span]) (Record '[Maybe Declaration, Range, Span])
|
type Diff' = Diff ListableSyntax (Record '[Maybe Declaration, Range, Span]) (Record '[Maybe Declaration, Range, Span])
|
||||||
@ -246,4 +246,4 @@ diffWithParser :: ( HasField fields Data.Span.Span
|
|||||||
=> Parser (Term syntax (Record fields))
|
=> Parser (Term syntax (Record fields))
|
||||||
-> BlobPair
|
-> BlobPair
|
||||||
-> Eff effs (Diff syntax (Record (Maybe Declaration ': fields)) (Record (Maybe Declaration ': fields)))
|
-> Eff effs (Diff syntax (Record (Maybe Declaration ': fields)) (Record (Maybe Declaration ': fields)))
|
||||||
diffWithParser parser blobs = distributeFor blobs (\ blob -> WrapTask $ parse parser blob >>= decorate (declarationAlgebra blob)) >>= diffTermPair diffTerms . runJoin
|
diffWithParser parser blobs = distributeFor blobs (\ blob -> WrapTask $ parse parser blob >>= decorate (declarationAlgebra blob)) >>= SpecHelpers.diff . runJoin
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
module Semantic.CLI.Spec (spec) where
|
module Semantic.CLI.Spec (spec) where
|
||||||
|
|
||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
|
import Data.ByteString.Builder
|
||||||
import Data.Foldable (for_)
|
import Data.Foldable (for_)
|
||||||
import Semantic.CLI
|
import Semantic.CLI
|
||||||
import System.IO (Handle)
|
import Semantic.IO
|
||||||
|
import Semantic.Task
|
||||||
|
|
||||||
import SpecHelpers
|
import SpecHelpers
|
||||||
|
|
||||||
@ -11,49 +13,49 @@ import SpecHelpers
|
|||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = parallel $ do
|
spec = parallel $ do
|
||||||
describe "runDiff" $
|
describe "runDiff" $
|
||||||
for_ diffFixtures $ \ (diffRenderer, diffMode, expected) ->
|
for_ diffFixtures $ \ (diffRenderer, runDiff, files, expected) ->
|
||||||
it ("renders to " <> show diffRenderer <> " in mode " <> show diffMode) $ do
|
it ("renders to " <> diffRenderer <> " with files " <> show files) $ do
|
||||||
output <- runTask $ runDiff diffRenderer diffMode
|
output <- runTask $ readBlobPairs (Right files) >>= runDiff
|
||||||
output `shouldBe'` expected
|
runBuilder output `shouldBe'` expected
|
||||||
|
|
||||||
describe "runParse" $
|
describe "runParse" $
|
||||||
for_ parseFixtures $ \ (parseTreeRenderer, parseMode, expected) ->
|
for_ parseFixtures $ \ (parseTreeRenderer, runParse, files, expected) ->
|
||||||
it ("renders to " <> show parseTreeRenderer <> " in mode " <> show parseMode) $ do
|
it ("renders to " <> parseTreeRenderer <> " with files " <> show files) $ do
|
||||||
output <- runTask $ runParse parseTreeRenderer parseMode
|
output <- runTask $ readBlobs (Right files) >>= runParse
|
||||||
output `shouldBe'` expected
|
runBuilder output `shouldBe'` expected
|
||||||
where
|
where
|
||||||
shouldBe' actual expected = do
|
shouldBe' actual expected = do
|
||||||
when (actual /= expected) $ print actual
|
when (actual /= expected) $ print actual
|
||||||
actual `shouldBe` expected
|
actual `shouldBe` expected
|
||||||
|
|
||||||
parseFixtures :: [(SomeRenderer TermRenderer, Either Handle [File], ByteString)]
|
parseFixtures :: [(String, [Blob] -> TaskEff Builder, [File], ByteString)]
|
||||||
parseFixtures =
|
parseFixtures =
|
||||||
[ (SomeRenderer SExpressionTermRenderer, pathMode, sExpressionParseTreeOutput)
|
[ (show SExpressionTermRenderer, runParse SExpressionTermRenderer, pathMode, sExpressionParseTreeOutput)
|
||||||
, (SomeRenderer JSONTermRenderer, pathMode, jsonParseTreeOutput)
|
, (show JSONTermRenderer, runParse JSONTermRenderer, pathMode, jsonParseTreeOutput)
|
||||||
, (SomeRenderer JSONTermRenderer, pathMode', jsonParseTreeOutput')
|
, (show JSONTermRenderer, runParse JSONTermRenderer, pathMode', jsonParseTreeOutput')
|
||||||
, (SomeRenderer JSONTermRenderer, Right [], emptyJsonParseTreeOutput)
|
, (show JSONTermRenderer, runParse JSONTermRenderer, [], emptyJsonParseTreeOutput)
|
||||||
, (SomeRenderer (SymbolsTermRenderer defaultSymbolFields), Right [File "test/fixtures/ruby/corpus/method-declaration.A.rb" (Just Ruby)], symbolsOutput)
|
, (show (SymbolsTermRenderer defaultSymbolFields), runParse (SymbolsTermRenderer defaultSymbolFields), [File "test/fixtures/ruby/corpus/method-declaration.A.rb" (Just Ruby)], symbolsOutput)
|
||||||
, (SomeRenderer TagsTermRenderer, Right [File "test/fixtures/ruby/corpus/method-declaration.A.rb" (Just Ruby)], tagsOutput)
|
, (show TagsTermRenderer, runParse TagsTermRenderer, [File "test/fixtures/ruby/corpus/method-declaration.A.rb" (Just Ruby)], tagsOutput)
|
||||||
]
|
]
|
||||||
where pathMode = Right [File "test/fixtures/ruby/corpus/and-or.A.rb" (Just Ruby)]
|
where pathMode = [File "test/fixtures/ruby/corpus/and-or.A.rb" (Just Ruby)]
|
||||||
pathMode' = Right [File "test/fixtures/ruby/corpus/and-or.A.rb" (Just Ruby), File "test/fixtures/ruby/corpus/and-or.B.rb" (Just Ruby)]
|
pathMode' = [File "test/fixtures/ruby/corpus/and-or.A.rb" (Just Ruby), File "test/fixtures/ruby/corpus/and-or.B.rb" (Just Ruby)]
|
||||||
|
|
||||||
sExpressionParseTreeOutput = "(Program\n (LowAnd\n (Send\n (Identifier))\n (Send\n (Identifier))))\n"
|
sExpressionParseTreeOutput = "(Program\n (LowAnd\n (Send\n (Identifier))\n (Send\n (Identifier))))\n"
|
||||||
jsonParseTreeOutput = "{\"trees\":[{\"path\":\"test/fixtures/ruby/corpus/and-or.A.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"LowAnd\",\"children\":[{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"foo\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}}],\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"bar\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}}],\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}},\"language\":\"Ruby\"}]}\n"
|
jsonParseTreeOutput = "{\"trees\":[{\"programNode\":{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]},\"children\":[{\"category\":\"LowAnd\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]},\"children\":[{\"category\":\"Send\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]},\"children\":[{\"name\":\"foo\",\"category\":\"Identifier\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]},\"children\":[]}]},{\"category\":\"Send\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]},\"children\":[{\"name\":\"bar\",\"category\":\"Identifier\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]},\"children\":[]}]}]}]},\"path\":\"test/fixtures/ruby/corpus/and-or.A.rb\",\"language\":\"Ruby\"}]}\n"
|
||||||
jsonParseTreeOutput' = "{\"trees\":[{\"path\":\"test/fixtures/ruby/corpus/and-or.A.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"LowAnd\",\"children\":[{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"foo\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}}],\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"bar\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}}],\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}},\"language\":\"Ruby\"},{\"path\":\"test/fixtures/ruby/corpus/and-or.B.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"LowOr\",\"children\":[{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"foo\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}}],\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"bar\",\"sourceRange\":[7,10],\"sourceSpan\":{\"start\":[1,8],\"end\":[1,11]}}],\"sourceRange\":[7,10],\"sourceSpan\":{\"start\":[1,8],\"end\":[1,11]}}],\"sourceRange\":[0,10],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,11]}},{\"category\":\"LowAnd\",\"children\":[{\"category\":\"LowOr\",\"children\":[{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"a\",\"sourceRange\":[11,12],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,2]}}],\"sourceRange\":[11,12],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,2]}},{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"b\",\"sourceRange\":[16,17],\"sourceSpan\":{\"start\":[2,6],\"end\":[2,7]}}],\"sourceRange\":[16,17],\"sourceSpan\":{\"start\":[2,6],\"end\":[2,7]}}],\"sourceRange\":[11,17],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,7]}},{\"category\":\"Send\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"c\",\"sourceRange\":[22,23],\"sourceSpan\":{\"start\":[2,12],\"end\":[2,13]}}],\"sourceRange\":[22,23],\"sourceSpan\":{\"start\":[2,12],\"end\":[2,13]}}],\"sourceRange\":[11,23],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,13]}}],\"sourceRange\":[0,24],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},\"language\":\"Ruby\"}]}\n"
|
jsonParseTreeOutput' = "{\"trees\":[{\"programNode\":{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]},\"children\":[{\"category\":\"LowAnd\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]},\"children\":[{\"category\":\"Send\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]},\"children\":[{\"name\":\"foo\",\"category\":\"Identifier\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]},\"children\":[]}]},{\"category\":\"Send\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]},\"children\":[{\"name\":\"bar\",\"category\":\"Identifier\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]},\"children\":[]}]}]}]},\"path\":\"test/fixtures/ruby/corpus/and-or.A.rb\",\"language\":\"Ruby\"},{\"programNode\":{\"category\":\"Program\",\"sourceRange\":[0,24],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]},\"children\":[{\"category\":\"LowOr\",\"sourceRange\":[0,10],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,11]},\"children\":[{\"category\":\"Send\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]},\"children\":[{\"name\":\"foo\",\"category\":\"Identifier\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]},\"children\":[]}]},{\"category\":\"Send\",\"sourceRange\":[7,10],\"sourceSpan\":{\"start\":[1,8],\"end\":[1,11]},\"children\":[{\"name\":\"bar\",\"category\":\"Identifier\",\"sourceRange\":[7,10],\"sourceSpan\":{\"start\":[1,8],\"end\":[1,11]},\"children\":[]}]}]},{\"category\":\"LowAnd\",\"sourceRange\":[11,23],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,13]},\"children\":[{\"category\":\"LowOr\",\"sourceRange\":[11,17],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,7]},\"children\":[{\"category\":\"Send\",\"sourceRange\":[11,12],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,2]},\"children\":[{\"name\":\"a\",\"category\":\"Identifier\",\"sourceRange\":[11,12],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,2]},\"children\":[]}]},{\"category\":\"Send\",\"sourceRange\":[16,17],\"sourceSpan\":{\"start\":[2,6],\"end\":[2,7]},\"children\":[{\"name\":\"b\",\"category\":\"Identifier\",\"sourceRange\":[16,17],\"sourceSpan\":{\"start\":[2,6],\"end\":[2,7]},\"children\":[]}]}]},{\"category\":\"Send\",\"sourceRange\":[22,23],\"sourceSpan\":{\"start\":[2,12],\"end\":[2,13]},\"children\":[{\"name\":\"c\",\"category\":\"Identifier\",\"sourceRange\":[22,23],\"sourceSpan\":{\"start\":[2,12],\"end\":[2,13]},\"children\":[]}]}]}]},\"path\":\"test/fixtures/ruby/corpus/and-or.B.rb\",\"language\":\"Ruby\"}]}\n"
|
||||||
emptyJsonParseTreeOutput = "{\"trees\":[]}\n"
|
emptyJsonParseTreeOutput = "{\"trees\":[]}\n"
|
||||||
symbolsOutput = "{\"files\":[{\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb\",\"symbols\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"kind\":\"Method\",\"symbol\":\"foo\"}],\"language\":\"Ruby\"}]}\n"
|
symbolsOutput = "{\"files\":[{\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb\",\"symbols\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"kind\":\"Method\",\"symbol\":\"foo\"}],\"language\":\"Ruby\"}]}\n"
|
||||||
tagsOutput = "[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb\",\"kind\":\"Method\",\"symbol\":\"foo\",\"line\":\"def foo\",\"language\":\"Ruby\"}]\n"
|
tagsOutput = "[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb\",\"kind\":\"Method\",\"symbol\":\"foo\",\"line\":\"def foo\",\"language\":\"Ruby\"}]\n"
|
||||||
|
|
||||||
|
|
||||||
diffFixtures :: [(SomeRenderer DiffRenderer, Either Handle [Both File], ByteString)]
|
diffFixtures :: [(String, [BlobPair] -> TaskEff Builder, [Both File], ByteString)]
|
||||||
diffFixtures =
|
diffFixtures =
|
||||||
[ (SomeRenderer JSONDiffRenderer, pathMode, jsonOutput)
|
[ (show JSONDiffRenderer, runDiff JSONDiffRenderer, pathMode, jsonOutput)
|
||||||
, (SomeRenderer SExpressionDiffRenderer, pathMode, sExpressionOutput)
|
, (show SExpressionDiffRenderer, runDiff SExpressionDiffRenderer, pathMode, sExpressionOutput)
|
||||||
, (SomeRenderer ToCDiffRenderer, pathMode, tocOutput)
|
, (show ToCDiffRenderer, runDiff ToCDiffRenderer, pathMode, tocOutput)
|
||||||
]
|
]
|
||||||
where pathMode = Right [both (File "test/fixtures/ruby/corpus/method-declaration.A.rb" (Just Ruby)) (File "test/fixtures/ruby/corpus/method-declaration.B.rb" (Just Ruby))]
|
where pathMode = [both (File "test/fixtures/ruby/corpus/method-declaration.A.rb" (Just Ruby)) (File "test/fixtures/ruby/corpus/method-declaration.B.rb" (Just Ruby))]
|
||||||
|
|
||||||
jsonOutput = "{\"diffs\":[{\"diff\":{\"merge\":{\"after\":{\"category\":\"Program\",\"sourceRange\":[0,21],\"sourceSpan\":{\"start\":[1,1],\"end\":[4,1]}},\"children\":[{\"merge\":{\"after\":{\"category\":\"Method\",\"sourceRange\":[0,20],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,4]}},\"children\":[{\"merge\":{\"after\":{\"category\":\"Empty\",\"sourceRange\":[0,0],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,1]}},\"children\":[],\"before\":{\"category\":\"Empty\",\"sourceRange\":[0,0],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,1]}}}},{\"patch\":{\"replace\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"foo\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"children\":[],\"name\":\"bar\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}]}},{\"patch\":{\"insert\":{\"category\":\"Identifier\",\"children\":[],\"name\":\"a\",\"sourceRange\":[8,9],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,10]}}}},{\"merge\":{\"after\":{\"category\":\"\",\"sourceRange\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}},\"children\":[{\"patch\":{\"insert\":{\"category\":\"Send\",\"children\":[{\"patch\":{\"insert\":{\"category\":\"Identifier\",\"children\":[],\"name\":\"baz\",\"sourceRange\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}}}}],\"sourceRange\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}}}}],\"before\":{\"category\":\"[]\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,4]}}}}],\"before\":{\"category\":\"Method\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}}}],\"before\":{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}}}},\"stat\":{\"replace\":[{\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb\",\"language\":\"Ruby\"},{\"path\":\"test/fixtures/ruby/corpus/method-declaration.B.rb\",\"language\":\"Ruby\"}],\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb -> test/fixtures/ruby/corpus/method-declaration.B.rb\"}}]}\n"
|
jsonOutput = "{\"diffs\":[{\"diff\":{\"merge\":{\"before\":{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},\"after\":{\"category\":\"Program\",\"sourceRange\":[0,21],\"sourceSpan\":{\"start\":[1,1],\"end\":[4,1]}},\"children\":[{\"merge\":{\"before\":{\"category\":\"Method\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}},\"after\":{\"category\":\"Method\",\"sourceRange\":[0,20],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,4]}},\"children\":[{\"merge\":{\"before\":{\"category\":\"Empty\",\"sourceRange\":[0,0],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,1]}},\"after\":{\"category\":\"Empty\",\"sourceRange\":[0,0],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,1]}},\"children\":[]}},{\"patch\":{\"replace\":[{\"category\":\"Identifier\",\"children\":[],\"name\":\"foo\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"children\":[],\"name\":\"bar\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}]}},{\"patch\":{\"insert\":{\"category\":\"Identifier\",\"children\":[],\"name\":\"a\",\"sourceRange\":[8,9],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,10]}}}},{\"merge\":{\"before\":{\"category\":\"[]\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[2,1],\"end\":[2,4]}},\"after\":{\"category\":\"\",\"sourceRange\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}},\"children\":[{\"patch\":{\"insert\":{\"category\":\"Send\",\"children\":[{\"patch\":{\"insert\":{\"category\":\"Identifier\",\"children\":[],\"name\":\"baz\",\"sourceRange\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}}}}],\"sourceRange\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}}}}]}}]}}]}},\"stat\":{\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb -> test/fixtures/ruby/corpus/method-declaration.B.rb\",\"replace\":[{\"path\":\"test/fixtures/ruby/corpus/method-declaration.A.rb\",\"language\":\"Ruby\"},{\"path\":\"test/fixtures/ruby/corpus/method-declaration.B.rb\",\"language\":\"Ruby\"}]}}]}\n"
|
||||||
sExpressionOutput = "(Program\n (Method\n (Empty)\n { (Identifier)\n ->(Identifier) }\n {+(Identifier)+}\n (\n {+(Send\n {+(Identifier)+})+})))\n"
|
sExpressionOutput = "(Program\n (Method\n (Empty)\n { (Identifier)\n ->(Identifier) }\n {+(Identifier)+}\n (\n {+(Send\n {+(Identifier)+})+})))\n"
|
||||||
tocOutput = "{\"changes\":{\"test/fixtures/ruby/corpus/method-declaration.A.rb -> test/fixtures/ruby/corpus/method-declaration.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"}]},\"errors\":{}}\n"
|
tocOutput = "{\"changes\":{\"test/fixtures/ruby/corpus/method-declaration.A.rb -> test/fixtures/ruby/corpus/method-declaration.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"}]},\"errors\":{}}\n"
|
||||||
|
@ -3,7 +3,7 @@ module Semantic.IO.Spec (spec) where
|
|||||||
import Prelude hiding (readFile)
|
import Prelude hiding (readFile)
|
||||||
import Semantic.IO
|
import Semantic.IO
|
||||||
import System.Exit (ExitCode(..))
|
import System.Exit (ExitCode(..))
|
||||||
import System.IO (IOMode(..), openFile)
|
import System.IO (IOMode(..))
|
||||||
|
|
||||||
import SpecHelpers
|
import SpecHelpers
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ spec = parallel $ do
|
|||||||
|
|
||||||
|
|
||||||
it "returns blobs for unsupported language" $ do
|
it "returns blobs for unsupported language" $ do
|
||||||
h <- openFile "test/fixtures/cli/diff-unsupported-language.json" ReadMode
|
h <- openFileForReading "test/fixtures/cli/diff-unsupported-language.json"
|
||||||
blobs <- readBlobPairsFromHandle h
|
blobs <- readBlobPairsFromHandle h
|
||||||
let b' = sourceBlob "test.kt" Nothing "fun main(args: Array<String>) {\nprintln(\"hi\")\n}\n"
|
let b' = sourceBlob "test.kt" Nothing "fun main(args: Array<String>) {\nprintln(\"hi\")\n}\n"
|
||||||
blobs `shouldBe` [blobPairInserting b']
|
blobs `shouldBe` [blobPairInserting b']
|
||||||
@ -53,29 +53,29 @@ spec = parallel $ do
|
|||||||
blobs `shouldBe` [blobPairDiffing a b]
|
blobs `shouldBe` [blobPairDiffing a b]
|
||||||
|
|
||||||
it "throws on blank input" $ do
|
it "throws on blank input" $ do
|
||||||
h <- openFile "test/fixtures/cli/blank.json" ReadMode
|
h <- openFileForReading "test/fixtures/cli/blank.json"
|
||||||
readBlobPairsFromHandle h `shouldThrow` (== ExitFailure 1)
|
readBlobPairsFromHandle h `shouldThrow` (== ExitFailure 1)
|
||||||
|
|
||||||
it "throws if language field not given" $ do
|
it "throws if language field not given" $ do
|
||||||
h <- openFile "test/fixtures/cli/diff-no-language.json" ReadMode
|
h <- openFileForReading "test/fixtures/cli/diff-no-language.json"
|
||||||
readBlobsFromHandle h `shouldThrow` (== ExitFailure 1)
|
readBlobsFromHandle h `shouldThrow` (== ExitFailure 1)
|
||||||
|
|
||||||
it "throws if null on before and after" $ do
|
it "throws if null on before and after" $ do
|
||||||
h <- openFile "test/fixtures/cli/diff-null-both-sides.json" ReadMode
|
h <- openFileForReading "test/fixtures/cli/diff-null-both-sides.json"
|
||||||
readBlobPairsFromHandle h `shouldThrow` (== ExitFailure 1)
|
readBlobPairsFromHandle h `shouldThrow` (== ExitFailure 1)
|
||||||
|
|
||||||
describe "readBlobsFromHandle" $ do
|
describe "readBlobsFromHandle" $ do
|
||||||
it "returns blobs for valid JSON encoded parse input" $ do
|
it "returns blobs for valid JSON encoded parse input" $ do
|
||||||
h <- openFile "test/fixtures/cli/parse.json" ReadMode
|
h <- openFileForReading "test/fixtures/cli/parse.json"
|
||||||
blobs <- readBlobsFromHandle h
|
blobs <- readBlobsFromHandle h
|
||||||
let a = sourceBlob "method.rb" (Just Ruby) "def foo; end"
|
let a = sourceBlob "method.rb" (Just Ruby) "def foo; end"
|
||||||
blobs `shouldBe` [a]
|
blobs `shouldBe` [a]
|
||||||
|
|
||||||
it "throws on blank input" $ do
|
it "throws on blank input" $ do
|
||||||
h <- openFile "test/fixtures/cli/blank.json" ReadMode
|
h <- openFileForReading "test/fixtures/cli/blank.json"
|
||||||
readBlobsFromHandle h `shouldThrow` (== ExitFailure 1)
|
readBlobsFromHandle h `shouldThrow` (== ExitFailure 1)
|
||||||
|
|
||||||
where blobsFromFilePath path = do
|
where blobsFromFilePath path = do
|
||||||
h <- openFile path ReadMode
|
h <- openFileForReading path
|
||||||
blobs <- readBlobPairsFromHandle h
|
blobs <- readBlobPairsFromHandle h
|
||||||
pure blobs
|
pure blobs
|
||||||
|
@ -2,6 +2,7 @@ module Semantic.Spec (spec) where
|
|||||||
|
|
||||||
import Data.Diff
|
import Data.Diff
|
||||||
import Data.Patch
|
import Data.Patch
|
||||||
|
import Semantic.Parse
|
||||||
import System.Exit
|
import System.Exit
|
||||||
|
|
||||||
import SpecHelpers
|
import SpecHelpers
|
||||||
@ -11,22 +12,12 @@ spec :: Spec
|
|||||||
spec = parallel $ do
|
spec = parallel $ do
|
||||||
describe "parseBlob" $ do
|
describe "parseBlob" $ do
|
||||||
it "throws if not given a language" $ do
|
it "throws if not given a language" $ do
|
||||||
runTask (parseBlob SExpressionTermRenderer methodsBlob { blobLanguage = Nothing }) `shouldThrow` (\ code -> case code of
|
runTask (runParse SExpressionTermRenderer [methodsBlob { blobLanguage = Nothing }]) `shouldThrow` (\ code -> case code of
|
||||||
ExitFailure 1 -> True
|
ExitFailure 1 -> True
|
||||||
_ -> False)
|
_ -> False)
|
||||||
|
|
||||||
it "renders with the specified renderer" $ do
|
it "renders with the specified renderer" $ do
|
||||||
output <- runTask $ parseBlob SExpressionTermRenderer methodsBlob
|
output <- fmap runBuilder . runTask $ runParse SExpressionTermRenderer [methodsBlob]
|
||||||
output `shouldBe` "(Program\n (Method\n (Empty)\n (Identifier)\n ([])))\n"
|
output `shouldBe` "(Program\n (Method\n (Empty)\n (Identifier)\n ([])))\n"
|
||||||
|
|
||||||
describe "diffTermPair" $ do
|
|
||||||
it "produces an Insert when the first term is missing" $ do
|
|
||||||
result <- runTask (diffTermPair replacing (That (termIn () [])))
|
|
||||||
result `shouldBe` (Diff (Patch (Insert (In () []))) :: Diff [] () ())
|
|
||||||
|
|
||||||
it "produces a Delete when the second term is missing" $ do
|
|
||||||
result <- runTask (diffTermPair replacing (This (termIn () [])))
|
|
||||||
result `shouldBe` (Diff (Patch (Delete (In () []))) :: Diff [] () ())
|
|
||||||
|
|
||||||
where
|
where
|
||||||
methodsBlob = Blob "def foo\nend\n" "methods.rb" (Just Ruby)
|
methodsBlob = Blob "def foo\nend\n" "methods.rb" (Just Ruby)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module SpecHelpers
|
module SpecHelpers
|
||||||
( module X
|
( module X
|
||||||
|
, runBuilder
|
||||||
, diffFilePaths
|
, diffFilePaths
|
||||||
, parseFilePath
|
, parseFilePath
|
||||||
, readFilePair
|
, readFilePair
|
||||||
@ -26,11 +27,12 @@ import Data.Abstract.ModuleTable as X hiding (lookup)
|
|||||||
import Data.Abstract.Value (Namespace(..), Value, ValueError, injValue, prjValue, runValueError)
|
import Data.Abstract.Value (Namespace(..), Value, ValueError, injValue, prjValue, runValueError)
|
||||||
import Data.Bifunctor (first)
|
import Data.Bifunctor (first)
|
||||||
import Data.Blob as X
|
import Data.Blob as X
|
||||||
|
import Data.ByteString.Builder (toLazyByteString)
|
||||||
|
import Data.ByteString.Lazy (toStrict)
|
||||||
import Data.File as X
|
import Data.File as X
|
||||||
import Data.Functor.Listable as X
|
import Data.Functor.Listable as X
|
||||||
import Data.Language as X
|
import Data.Language as X
|
||||||
import Data.List.NonEmpty as X (NonEmpty(..))
|
import Data.List.NonEmpty as X (NonEmpty(..))
|
||||||
import Data.Output as X
|
|
||||||
import Data.Range as X
|
import Data.Range as X
|
||||||
import Data.Record as X
|
import Data.Record as X
|
||||||
import Data.Source as X
|
import Data.Source as X
|
||||||
@ -59,13 +61,15 @@ import Test.LeanCheck as X
|
|||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import qualified Semantic.IO as IO
|
import qualified Semantic.IO as IO
|
||||||
|
|
||||||
|
runBuilder = toStrict . toLazyByteString
|
||||||
|
|
||||||
-- | Returns an s-expression formatted diff for the specified FilePath pair.
|
-- | Returns an s-expression formatted diff for the specified FilePath pair.
|
||||||
diffFilePaths :: Both FilePath -> IO ByteString
|
diffFilePaths :: Both FilePath -> IO ByteString
|
||||||
diffFilePaths paths = readFilePair paths >>= runTask . diffBlobPair SExpressionDiffRenderer
|
diffFilePaths paths = readFilePair paths >>= fmap runBuilder . runTask . runDiff SExpressionDiffRenderer . pure
|
||||||
|
|
||||||
-- | Returns an s-expression parse tree for the specified FilePath.
|
-- | Returns an s-expression parse tree for the specified FilePath.
|
||||||
parseFilePath :: FilePath -> IO ByteString
|
parseFilePath :: FilePath -> IO ByteString
|
||||||
parseFilePath path = (fromJust <$> IO.readFile (file path)) >>= runTask . parseBlob SExpressionTermRenderer
|
parseFilePath path = (fromJust <$> IO.readFile (file path)) >>= fmap runBuilder . runTask . runParse SExpressionTermRenderer . pure
|
||||||
|
|
||||||
-- | Read two files to a BlobPair.
|
-- | Read two files to a BlobPair.
|
||||||
readFilePair :: Both FilePath -> IO BlobPair
|
readFilePair :: Both FilePath -> IO BlobPair
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
(Composite
|
(Composite
|
||||||
(Slice
|
(Slice
|
||||||
(Identifier))
|
(Identifier))
|
||||||
([]
|
({-(TextElement)-})))
|
||||||
{-(TextElement)-})))
|
|
||||||
(Assignment
|
(Assignment
|
||||||
(Identifier)
|
(Identifier)
|
||||||
(Composite
|
(Composite
|
||||||
|
3
test/fixtures/ruby/corpus/elsif.diffB-A.txt
vendored
3
test/fixtures/ruby/corpus/elsif.diffB-A.txt
vendored
@ -8,7 +8,6 @@
|
|||||||
(If
|
(If
|
||||||
(Send
|
(Send
|
||||||
(Identifier))
|
(Identifier))
|
||||||
([]
|
({-(Send
|
||||||
{-(Send
|
|
||||||
{-(Identifier)-})-})
|
{-(Identifier)-})-})
|
||||||
(Empty))))
|
(Empty))))
|
||||||
|
3
test/fixtures/ruby/corpus/if.diffA-B.txt
vendored
3
test/fixtures/ruby/corpus/if.diffA-B.txt
vendored
@ -3,8 +3,7 @@
|
|||||||
(Send
|
(Send
|
||||||
{ (Identifier)
|
{ (Identifier)
|
||||||
->(Identifier) })
|
->(Identifier) })
|
||||||
([]
|
({-(Send
|
||||||
{-(Send
|
|
||||||
{-(Identifier)-})-})
|
{-(Identifier)-})-})
|
||||||
{ (If
|
{ (If
|
||||||
{-(Send
|
{-(Send
|
||||||
|
@ -4,6 +4,5 @@
|
|||||||
{ (Identifier)
|
{ (Identifier)
|
||||||
->(Identifier) }
|
->(Identifier) }
|
||||||
{-(Identifier)-}
|
{-(Identifier)-}
|
||||||
([]
|
({-(Send
|
||||||
{-(Send
|
|
||||||
{-(Identifier)-})-})))
|
{-(Identifier)-})-})))
|
||||||
|
3
test/fixtures/ruby/corpus/unless.diffA-B.txt
vendored
3
test/fixtures/ruby/corpus/unless.diffA-B.txt
vendored
@ -4,8 +4,7 @@
|
|||||||
(Send
|
(Send
|
||||||
{ (Identifier)
|
{ (Identifier)
|
||||||
->(Identifier) }))
|
->(Identifier) }))
|
||||||
([]
|
({-(Send
|
||||||
{-(Send
|
|
||||||
{-(Identifier)-})-})
|
{-(Identifier)-})-})
|
||||||
{ (Send
|
{ (Send
|
||||||
{-(Identifier)-})
|
{-(Identifier)-})
|
||||||
|
3
test/fixtures/ruby/corpus/when.diffB-A.txt
vendored
3
test/fixtures/ruby/corpus/when.diffB-A.txt
vendored
@ -7,8 +7,7 @@
|
|||||||
(
|
(
|
||||||
(Send
|
(Send
|
||||||
(Identifier)))
|
(Identifier)))
|
||||||
([]
|
({-(Send
|
||||||
{-(Send
|
|
||||||
{-(Identifier)-})-}
|
{-(Identifier)-})-}
|
||||||
{-(Pattern
|
{-(Pattern
|
||||||
{-(
|
{-(
|
||||||
|
Loading…
Reference in New Issue
Block a user