1
1
mirror of https://github.com/github/semantic.git synced 2024-11-28 18:23:44 +03:00

Merge branch 'master' into scope-effects-to-evaluation

This commit is contained in:
Rob Rix 2018-05-06 22:49:42 -04:00
commit 4f69f5e3de
12 changed files with 230 additions and 143 deletions

View File

@ -19,7 +19,7 @@ library
, Analysis.Abstract.Collecting , Analysis.Abstract.Collecting
, Analysis.Abstract.Dead , Analysis.Abstract.Dead
, Analysis.Abstract.Evaluating , Analysis.Abstract.Evaluating
, Analysis.Abstract.ImportGraph , Analysis.Abstract.Graph
, Analysis.Abstract.Tracing , Analysis.Abstract.Tracing
, Analysis.CallGraph , Analysis.CallGraph
, Analysis.ConstructorName , Analysis.ConstructorName

View File

@ -1,15 +1,22 @@
{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-} {-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
module Analysis.Abstract.ImportGraph module Analysis.Abstract.Graph
( ImportGraph(..) ( Graph(..)
, renderImportGraph , Vertex(..)
, renderGraph
, appendGraph
, variableDefinition
, moduleInclusion
, packageInclusion
, packageGraph
, graphingTerms , graphingTerms
, graphingLoadErrors , graphingLoadErrors
, graphingModules , graphingModules
, importGraphing , graphing
) where ) where
import qualified Algebra.Graph as G import qualified Algebra.Graph as G
import Algebra.Graph.Class hiding (Vertex) 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.Evaluator import Control.Abstract.Evaluator
import Data.Abstract.Address import Data.Abstract.Address
@ -28,21 +35,21 @@ import Data.Text.Encoding as T
import Prologue hiding (empty, packageName) import Prologue hiding (empty, packageName)
-- | The graph of function variableDefinitions to symbols used in a given program. -- | The graph of function variableDefinitions to symbols used in a given program.
newtype ImportGraph term = ImportGraph { unImportGraph :: G.Graph (Vertex term) } newtype Graph = Graph { unGraph :: G.Graph Vertex }
deriving (Eq, Graph, Show) deriving (Eq, GC.Graph, Show)
-- | A vertex of some specific type. -- | A vertex of some specific type.
data Vertex term data Vertex
= Package { vertexName :: ByteString } = Package { vertexName :: ByteString }
| Module { vertexName :: ByteString } | Module { vertexName :: ByteString }
| Variable { vertexName :: ByteString } | Variable { vertexName :: ByteString }
deriving (Eq, Ord, Show) deriving (Eq, Ord, Show)
-- | Render a 'ImportGraph' to a 'ByteString' in DOT notation. -- | Render a 'Graph' to a 'ByteString' in DOT notation.
renderImportGraph :: ImportGraph term -> ByteString renderGraph :: Graph -> ByteString
renderImportGraph = export style . unImportGraph renderGraph = export style . unGraph
style :: Style (Vertex term) ByteString style :: Style Vertex ByteString
style = (defaultStyle vertexName) style = (defaultStyle vertexName)
{ vertexAttributes = vertexAttributes { vertexAttributes = vertexAttributes
, edgeAttributes = edgeAttributes , edgeAttributes = edgeAttributes
@ -55,12 +62,13 @@ style = (defaultStyle vertexName)
edgeAttributes Variable{} Module{} = [ "color" := "blue" ] edgeAttributes Variable{} Module{} = [ "color" := "blue" ]
edgeAttributes _ _ = [] edgeAttributes _ _ = []
graphingTerms :: ( Element Syntax.Identifier syntax graphingTerms :: ( Element Syntax.Identifier syntax
, Members '[ Reader (Environment (Located location) value) , Members '[ Reader (Environment (Located location) value)
, Reader ModuleInfo , Reader ModuleInfo
, Reader PackageInfo , Reader PackageInfo
, State (Environment (Located location) value) , State (Environment (Located location) value)
, State (ImportGraph term) , State Graph
] effects ] effects
, term ~ Term (Sum syntax) ann , term ~ Term (Sum syntax) ann
) )
@ -77,7 +85,7 @@ graphingTerms recur term@(In _ syntax) = do
graphingLoadErrors :: forall location term value effects a graphingLoadErrors :: forall location term value effects a
. Members '[ Reader ModuleInfo . Members '[ Reader ModuleInfo
, Resumable (LoadError term) , Resumable (LoadError term)
, State (ImportGraph term) , State Graph
] effects ] effects
=> SubtermAlgebra (Base term) term (Evaluator location term value effects a) => SubtermAlgebra (Base term) term (Evaluator location term value effects a)
-> SubtermAlgebra (Base term) term (Evaluator location term value effects a) -> SubtermAlgebra (Base term) term (Evaluator location term value effects a)
@ -87,7 +95,7 @@ graphingLoadErrors recur term = resume @(LoadError term)
graphingModules :: Members '[ Reader ModuleInfo graphingModules :: Members '[ Reader ModuleInfo
, Reader PackageInfo , Reader PackageInfo
, State (ImportGraph term) , State Graph
] effects ] effects
=> SubtermAlgebra Module term (Evaluator location term value effects a) => SubtermAlgebra Module term (Evaluator location term value effects a)
-> SubtermAlgebra Module term (Evaluator location term value effects a) -> SubtermAlgebra Module term (Evaluator location term value effects a)
@ -98,28 +106,34 @@ graphingModules recur m = do
recur m recur m
packageGraph :: PackageInfo -> ImportGraph term packageGraph :: PackageInfo -> Graph
packageGraph = vertex . Package . unName . packageName packageGraph = vertex . Package . unName . packageName
moduleGraph :: ModuleInfo -> ImportGraph term moduleGraph :: ModuleInfo -> Graph
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 :: Members '[ Reader PackageInfo packageInclusion :: ( Effectful m
, State (ImportGraph term) , Members '[ Reader PackageInfo
] effects , State Graph
=> Vertex term ] effects
-> Evaluator location term value effects () , Monad (m effects)
)
=> Vertex
-> m effects ()
packageInclusion v = do packageInclusion v = do
p <- currentPackage p <- currentPackage
appendGraph (packageGraph p `connect` vertex v) appendGraph (packageGraph p `connect` vertex v)
-- | Add an edge from the current module to the passed vertex. -- | Add an edge from the current module to the passed vertex.
moduleInclusion :: Members '[ Reader ModuleInfo moduleInclusion :: ( Effectful m
, State (ImportGraph term) , Members '[ Reader ModuleInfo
] effects , State Graph
=> Vertex term ] effects
-> Evaluator location term value effects () , Monad (m effects)
)
=> Vertex
-> m effects ()
moduleInclusion v = do moduleInclusion v = do
m <- currentModule m <- currentModule
appendGraph (moduleGraph m `connect` vertex v) appendGraph (moduleGraph m `connect` vertex v)
@ -127,7 +141,7 @@ 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 (ImportGraph term)) effects , Member (State Graph) effects
) )
=> Name => Name
-> Evaluator (Located location) term value effects () -> Evaluator (Located location) term value effects ()
@ -135,49 +149,49 @@ variableDefinition name = do
graph <- maybe empty (moduleGraph . locationModule . unAddress) <$> lookupEnv name graph <- maybe empty (moduleGraph . locationModule . unAddress) <$> lookupEnv name
appendGraph (vertex (Variable (unName name)) `connect` graph) appendGraph (vertex (Variable (unName name)) `connect` graph)
appendGraph :: Member (State (ImportGraph term)) effects => ImportGraph term -> Evaluator location term value effects () appendGraph :: (Effectful m, Member (State Graph) effects) => Graph -> m effects ()
appendGraph = raise . modify' . (<>) appendGraph = raise . modify' . (<>)
instance Semigroup (ImportGraph term) where instance Semigroup Graph where
(<>) = overlay (<>) = overlay
instance Monoid (ImportGraph term) where instance Monoid Graph where
mempty = empty mempty = empty
mappend = (<>) mappend = (<>)
instance Ord (ImportGraph term) where instance Ord Graph where
compare (ImportGraph G.Empty) (ImportGraph G.Empty) = EQ compare (Graph G.Empty) (Graph G.Empty) = EQ
compare (ImportGraph G.Empty) _ = LT compare (Graph G.Empty) _ = LT
compare _ (ImportGraph G.Empty) = GT compare _ (Graph G.Empty) = GT
compare (ImportGraph (G.Vertex a)) (ImportGraph (G.Vertex b)) = compare a b compare (Graph (G.Vertex a)) (Graph (G.Vertex b)) = compare a b
compare (ImportGraph (G.Vertex _)) _ = LT compare (Graph (G.Vertex _)) _ = LT
compare _ (ImportGraph (G.Vertex _)) = GT compare _ (Graph (G.Vertex _)) = GT
compare (ImportGraph (G.Overlay a1 a2)) (ImportGraph (G.Overlay b1 b2)) = (compare `on` ImportGraph) a1 b1 <> (compare `on` ImportGraph) a2 b2 compare (Graph (G.Overlay a1 a2)) (Graph (G.Overlay b1 b2)) = (compare `on` Graph) a1 b1 <> (compare `on` Graph) a2 b2
compare (ImportGraph (G.Overlay _ _)) _ = LT compare (Graph (G.Overlay _ _)) _ = LT
compare _ (ImportGraph (G.Overlay _ _)) = GT compare _ (Graph (G.Overlay _ _)) = GT
compare (ImportGraph (G.Connect a1 a2)) (ImportGraph (G.Connect b1 b2)) = (compare `on` ImportGraph) a1 b1 <> (compare `on` ImportGraph) a2 b2 compare (Graph (G.Connect a1 a2)) (Graph (G.Connect b1 b2)) = (compare `on` Graph) a1 b1 <> (compare `on` Graph) a2 b2
instance Output (ImportGraph term) where instance Output Graph where
toOutput = toStrict . (<> "\n") . encode toOutput = toStrict . (<> "\n") . encode
instance ToJSON (ImportGraph term) where instance ToJSON Graph where
toJSON ImportGraph{..} = object [ "vertices" .= vertices, "edges" .= edges ] toJSON Graph{..} = object [ "vertices" .= vertices, "edges" .= edges ]
where where
vertices = toJSON (G.vertexList unImportGraph) vertices = toJSON (G.vertexList unGraph)
edges = fmap (\(a, b) -> object [ "source" .= vertexToText a, "target" .= vertexToText b ]) (G.edgeList unImportGraph) edges = fmap (\(a, b) -> object [ "source" .= vertexToText a, "target" .= vertexToText b ]) (G.edgeList unGraph)
instance ToJSON (Vertex term) where instance ToJSON Vertex where
toJSON v = object [ "name" .= vertexToText v, "type" .= vertexToType v ] toJSON v = object [ "name" .= vertexToText v, "type" .= vertexToType v ]
vertexToText :: Vertex termt -> Text vertexToText :: Vertex -> Text
vertexToText = decodeUtf8 . vertexName vertexToText = decodeUtf8 . vertexName
vertexToType :: Vertex termt -> Text vertexToType :: Vertex -> Text
vertexToType Package{} = "package" vertexToType Package{} = "package"
vertexToType Module{} = "module" vertexToType Module{} = "module"
vertexToType Variable{} = "variable" vertexToType Variable{} = "variable"
importGraphing :: Effectful m => m (State (ImportGraph term) ': effects) result -> m effects (result, ImportGraph term) graphing :: Effectful m => m (State Graph ': effects) result -> m effects (result, Graph)
importGraphing = runState mempty graphing = runState mempty

View File

@ -252,11 +252,11 @@ modifyModuleTable = raise . modify'
-- Context -- Context
-- | Get the currently evaluating 'ModuleInfo'. -- | Get the currently evaluating 'ModuleInfo'.
currentModule :: Member (Reader ModuleInfo) effects => Evaluator location term value effects ModuleInfo currentModule :: (Effectful m, Member (Reader ModuleInfo) effects) => m effects ModuleInfo
currentModule = raise ask currentModule = raise ask
-- | Get the currently evaluating 'PackageInfo'. -- | Get the currently evaluating 'PackageInfo'.
currentPackage :: Member (Reader PackageInfo) effects => Evaluator location term value effects PackageInfo currentPackage :: (Effectful m, Member (Reader PackageInfo) effects) => m effects PackageInfo
currentPackage = raise ask currentPackage = raise ask

View File

@ -6,6 +6,11 @@ import Data.Record
import Data.Span import Data.Span
import Data.Term import Data.Term
import Data.Aeson
import Data.ByteString.Char8 (pack)
import Data.JSON.Fields
import Data.Text.Encoding (decodeUtf8)
-- | An AST node labelled with symbols and source location. -- | An AST node labelled with symbols and source location.
type AST syntax grammar = Term syntax (Node grammar) type AST syntax grammar = Term syntax (Node grammar)
@ -16,6 +21,12 @@ data Node grammar = Node
} }
deriving (Eq, Show) deriving (Eq, Show)
instance Show grammar => ToJSONFields (Node grammar) where
toJSONFields Node{..} =
[ "symbol" .= decodeUtf8 (pack (show nodeSymbol))
, "span" .= nodeSpan ]
-- | A location specified as possibly-empty intervals of bytes and line/column positions. -- | A location specified as possibly-empty intervals of bytes and line/column positions.
type Location = '[Range, Span] type Location = '[Range, Span]

View File

@ -3,7 +3,9 @@ module Parsing.Parser
( Parser(..) ( Parser(..)
, SomeParser(..) , SomeParser(..)
, SomeAnalysisParser(..) , SomeAnalysisParser(..)
, SomeASTParser(..)
, someParser , someParser
, someASTParser
, someAnalysisParser , someAnalysisParser
, ApplyAll , ApplyAll
, ApplyAll' , ApplyAll'
@ -148,3 +150,21 @@ typescriptParser = AssignmentParser (ASTParser tree_sitter_typescript) TypeScrip
markdownParser :: Parser Markdown.Term markdownParser :: Parser Markdown.Term
markdownParser = AssignmentParser MarkdownParser Markdown.assignment markdownParser = AssignmentParser MarkdownParser Markdown.assignment
-- | A parser for producing specialized (tree-sitter) ASTs.
data SomeASTParser where
SomeASTParser :: forall grammar. (Bounded grammar, Enum grammar, Show grammar)
=> Parser (AST [] grammar)
-> SomeASTParser
someASTParser :: Language -> SomeASTParser
someASTParser Go = SomeASTParser (ASTParser tree_sitter_go :: Parser (AST [] Go.Grammar))
someASTParser JavaScript = SomeASTParser (ASTParser tree_sitter_typescript :: Parser (AST [] TypeScript.Grammar))
someASTParser JSON = SomeASTParser (ASTParser tree_sitter_json :: Parser (AST [] JSON.Grammar))
someASTParser JSX = SomeASTParser (ASTParser tree_sitter_typescript :: Parser (AST [] TypeScript.Grammar))
someASTParser Python = SomeASTParser (ASTParser tree_sitter_python :: Parser (AST [] Python.Grammar))
someASTParser Ruby = SomeASTParser (ASTParser tree_sitter_ruby :: Parser (AST [] Ruby.Grammar))
someASTParser TypeScript = SomeASTParser (ASTParser tree_sitter_typescript :: Parser (AST [] TypeScript.Grammar))
someASTParser PHP = SomeASTParser (ASTParser tree_sitter_php :: Parser (AST [] PHP.Grammar))
someASTParser l = error $ "Tree-Sitter AST parsing not supported for: " <> show l

View File

@ -2,6 +2,7 @@ module Rendering.JSON
( renderJSONDiff ( renderJSONDiff
, renderJSONDiffs , renderJSONDiffs
, renderJSONTerm , renderJSONTerm
, renderJSONTerm'
, renderJSONTerms , renderJSONTerms
) where ) where
@ -29,5 +30,8 @@ renderJSONDiffs = Map.singleton "diffs" . toJSON
renderJSONTerm :: ToJSON a => Blob -> a -> [Value] renderJSONTerm :: ToJSON a => Blob -> a -> [Value]
renderJSONTerm blob content = pure $ toJSON (object ("programNode" .= content : toJSONFields blob)) renderJSONTerm blob content = pure $ toJSON (object ("programNode" .= content : toJSONFields blob))
renderJSONTerm' :: (ToJSON a) => Blob -> a -> [Value]
renderJSONTerm' blob content = pure $ toJSON (object ("ast" .= content : toJSONFields blob))
renderJSONTerms :: [Value] -> Map.Map Text Value renderJSONTerms :: [Value] -> Map.Map Text Value
renderJSONTerms = Map.singleton "trees" . toJSON renderJSONTerms = Map.singleton "trees" . toJSON

View File

@ -6,9 +6,11 @@ module Rendering.Renderer
, SomeRenderer(..) , SomeRenderer(..)
, renderSExpressionDiff , renderSExpressionDiff
, renderSExpressionTerm , renderSExpressionTerm
, renderSExpressionAST
, renderJSONDiff , renderJSONDiff
, renderJSONDiffs , renderJSONDiffs
, renderJSONTerm , renderJSONTerm
, renderJSONTerm'
, renderJSONTerms , renderJSONTerms
, renderToCDiff , renderToCDiff
, renderToCTerm , renderToCTerm
@ -24,14 +26,14 @@ module Rendering.Renderer
, defaultSymbolFields , defaultSymbolFields
) where ) where
import Prologue
import Data.Aeson (Value) import Data.Aeson (Value)
import Data.Output import Data.Output
import Prologue
import Rendering.DOT as R import Rendering.DOT as R
import Rendering.Imports as R
import Rendering.JSON as R import Rendering.JSON as R
import Rendering.SExpression as R import Rendering.SExpression as R
import Rendering.Symbol as R import Rendering.Symbol as R
import Rendering.Imports as R
import Rendering.TOC as R import Rendering.TOC as R
-- | Specification of renderers for diffs, producing output in the parameter type. -- | Specification of renderers for diffs, producing output in the parameter type.
@ -69,7 +71,7 @@ deriving instance Show (TermRenderer output)
-- | Specification of renderers for graph analysis, producing output in the parameter type. -- | Specification of renderers for graph analysis, producing output in the parameter type.
data GraphRenderer output where data GraphRenderer output where
JSONGraphRenderer :: GraphRenderer ByteString JSONGraphRenderer :: GraphRenderer ByteString
DOTGraphRenderer :: GraphRenderer ByteString DOTGraphRenderer :: GraphRenderer ByteString
deriving instance Eq (GraphRenderer output) deriving instance Eq (GraphRenderer output)
deriving instance Show (GraphRenderer output) deriving instance Show (GraphRenderer output)

View File

@ -2,6 +2,7 @@
module Rendering.SExpression module Rendering.SExpression
( renderSExpressionDiff ( renderSExpressionDiff
, renderSExpressionTerm , renderSExpressionTerm
, renderSExpressionAST
) where ) where
import Prologue import Prologue
@ -9,6 +10,7 @@ import Data.ByteString.Char8
import Data.Diff import Data.Diff
import Data.Patch import Data.Patch
import Data.Record import Data.Record
import Data.AST
import Data.Term import Data.Term
import Prelude hiding (replicate) import Prelude hiding (replicate)
@ -16,20 +18,28 @@ import Prelude hiding (replicate)
renderSExpressionDiff :: (ConstrainAll Show fields, Foldable syntax, Functor syntax) => Diff syntax (Record fields) (Record fields) -> ByteString renderSExpressionDiff :: (ConstrainAll Show fields, Foldable syntax, Functor syntax) => Diff syntax (Record fields) (Record fields) -> ByteString
renderSExpressionDiff diff = cata printDiffF diff 0 <> "\n" renderSExpressionDiff diff = cata printDiffF diff 0 <> "\n"
-- | Returns a ByteString SExpression formatted term. -- | Returns a ByteString SExpression formatted term (generalized).
renderSExpressionTerm :: (ConstrainAll Show fields, Foldable syntax, Functor syntax) => Term syntax (Record fields) -> ByteString renderSExpressionTerm :: (ConstrainAll Show fields, Foldable syntax, Functor syntax) => Term syntax (Record fields) -> ByteString
renderSExpressionTerm term = cata (\ term n -> nl n <> replicate (2 * n) ' ' <> printTermF term n) term 0 <> "\n" 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 :: (ConstrainAll Show fields, Foldable syntax) => DiffF syntax (Record fields) (Record fields) (Int -> ByteString) -> Int -> ByteString
printDiffF diff n = case diff of printDiffF diff n = case diff of
Patch (Delete term) -> nl n <> pad (n - 1) <> "{-" <> printTermF term n <> "-}" Patch (Delete term) -> nl n <> pad (n - 1) <> "{-" <> printTermF showRecord term n <> "-}"
Patch (Insert term) -> nl n <> pad (n - 1) <> "{+" <> printTermF term n <> "+}" Patch (Insert term) -> nl n <> pad (n - 1) <> "{+" <> printTermF showRecord term n <> "+}"
Patch (Replace term1 term2) -> nl n <> pad (n - 1) <> "{ " <> printTermF term1 n Patch (Replace term1 term2) -> nl n <> pad (n - 1) <> "{ " <> printTermF showRecord term1 n
<> nl (n + 1) <> pad (n - 1) <> "->" <> printTermF term2 n <> " }" <> nl (n + 1) <> pad (n - 1) <> "->" <> printTermF showRecord term2 n <> " }"
Merge (In (_, ann) syntax) -> nl n <> pad n <> "(" <> showAnnotation ann <> foldMap (\ d -> d (n + 1)) syntax <> ")" Merge (In (_, ann) syntax) -> nl n <> pad n <> "(" <> showRecord ann <> foldMap (\ d -> d (n + 1)) syntax <> ")"
printTermF :: (ConstrainAll Show fields, Foldable syntax) => TermF syntax (Record fields) (Int -> ByteString) -> Int -> ByteString printTermF :: Foldable syntax => (ann -> ByteString) -> TermF syntax ann (Int -> ByteString) -> Int -> ByteString
printTermF (In annotation syntax) n = "(" <> showAnnotation annotation <> foldMap (\t -> t (n + 1)) syntax <> ")" printTermF f (In ann syntax) n = "(" <> f ann <> foldMap (\t -> t (succ n)) syntax <> ")"
nl :: Int -> ByteString nl :: Int -> ByteString
nl n | n <= 0 = "" nl n | n <= 0 = ""
@ -38,8 +48,7 @@ nl n | n <= 0 = ""
pad :: Int -> ByteString pad :: Int -> ByteString
pad n = replicate (2 * n) ' ' pad n = replicate (2 * n) ' '
showRecord :: ConstrainAll Show fields => Record fields -> ByteString
showAnnotation :: ConstrainAll Show fields => Record fields -> ByteString showRecord Nil = ""
showAnnotation Nil = "" showRecord (only :. Nil) = pack (show only)
showAnnotation (only :. Nil) = pack (show only) showRecord (first :. rest) = pack (show first) <> " " <> showRecord rest
showAnnotation (first :. rest) = pack (show first) <> " " <> showAnnotation rest

View File

@ -17,10 +17,10 @@ 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.Diff as Semantic (diffBlobPairs)
import qualified Semantic.Graph as Semantic (graph) import Semantic.Graph as Semantic (graph, GraphType(..))
import Semantic.IO (languageForFilePath) import Semantic.IO (languageForFilePath)
import qualified Semantic.Log as Log import qualified Semantic.Log as Log
import qualified Semantic.Parse as Semantic (parseBlobs) import qualified Semantic.Parse as Semantic (parseBlobs, astParseBlobs)
import qualified Semantic.Task as Task import qualified Semantic.Task as Task
import System.IO (Handle, stdin, stdout) import System.IO (Handle, stdin, stdout)
import Text.Read import Text.Read
@ -34,8 +34,11 @@ runDiff (SomeRenderer diffRenderer) = Semantic.diffBlobPairs diffRenderer <=< Ta
runParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString runParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString
runParse (SomeRenderer parseTreeRenderer) = Semantic.parseBlobs parseTreeRenderer <=< Task.readBlobs runParse (SomeRenderer parseTreeRenderer) = Semantic.parseBlobs parseTreeRenderer <=< Task.readBlobs
runGraph :: SomeRenderer GraphRenderer -> Maybe FilePath -> FilePath -> Language -> [FilePath] -> Task.TaskEff ByteString runASTParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString
runGraph (SomeRenderer r) rootDir dir excludeDirs = Semantic.graph r <=< Task.readProject rootDir dir excludeDirs 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.
-- --
@ -56,20 +59,20 @@ arguments = info (version <*> helper <*> ((,) <$> optionsParser <*> argumentsPar
pure $ Log.Options disableColour logLevel requestId False False Log.logfmtFormatter 0 failOnWarning pure $ Log.Options disableColour logLevel requestId False False Log.logfmtFormatter 0 failOnWarning
argumentsParser = do argumentsParser = do
subparser <- hsubparser (diffCommand <> parseCommand <> graphCommand) subparser <- hsubparser (diffCommand <> parseCommand <> tsParseCommand <> graphCommand)
output <- Right <$> strOption (long "output" <> short 'o' <> help "Output path, defaults to stdout") <|> pure (Left stdout) output <- Right <$> strOption (long "output" <> short 'o' <> help "Output path, defaults to stdout") <|> pure (Left stdout)
pure $ subparser >>= Task.writeToOutput output pure $ subparser >>= Task.writeToOutput output
diffCommand = command "diff" (info diffArgumentsParser (progDesc "Show changes between commits or 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") renderer <- flag (SomeRenderer SExpressionDiffRenderer) (SomeRenderer SExpressionDiffRenderer) (long "sexpression" <> help "Output s-expression diff tree (default)")
<|> flag' (SomeRenderer JSONDiffRenderer) (long "json" <> help "Output JSON diff trees") <|> flag' (SomeRenderer JSONDiffRenderer) (long "json" <> help "Output JSON diff trees")
<|> flag' (SomeRenderer ToCDiffRenderer) (long "toc" <> help "Output JSON table of contents diff summary") <|> flag' (SomeRenderer 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' (SomeRenderer 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 $ runDiff renderer filesOrStdin
parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print 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 (SomeRenderer SExpressionTermRenderer) (SomeRenderer SExpressionTermRenderer) (long "sexpression" <> help "Output s-expression parse trees (default)")
<|> flag' (SomeRenderer JSONTermRenderer) (long "json" <> help "Output JSON parse trees") <|> flag' (SomeRenderer JSONTermRenderer) (long "json" <> help "Output JSON parse trees")
@ -84,15 +87,26 @@ arguments = info (version <*> helper <*> ((,) <$> optionsParser <*> argumentsPar
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 $ runParse renderer filesOrStdin
graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute an import graph a directory or entry point")) tsParseCommand = command "ts-parse" (info tsParseArgumentsParser (progDesc "Print specialized tree-sitter ASTs for path(s)"))
graphArgumentsParser = do tsParseArgumentsParser = do
renderer <- flag (SomeRenderer DOTGraphRenderer) (SomeRenderer DOTGraphRenderer) (long "dot" <> help "Output in DOT graph format (default)") renderer <- flag (SomeRenderer SExpressionTermRenderer) (SomeRenderer SExpressionTermRenderer) (long "sexpression" <> help "Output s-expression ASTs (default)")
<|> flag' (SomeRenderer JSONGraphRenderer) (long "json" <> help "Output JSON graph") <|> flag' (SomeRenderer JSONTermRenderer) (long "json" <> help "Output JSON ASTs")
rootDir <- optional (strOption (long "root" <> help "Root directory of project. Optional, defaults to entry file/directory." <> metavar "DIRECTORY")) filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin)
excludeDirs <- many (strOption (long "exclude-dir" <> help "Exclude a directory (e.g. vendor)")) pure $ runASTParse renderer filesOrStdin
File{..} <- argument filePathReader (metavar "DIRECTORY:LANGUAGE")
pure $ runGraph renderer rootDir filePath (fromJust fileLanguage) excludeDirs
graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute a graph for a directory or entry point"))
graphArgumentsParser = do
graphType <- flag ImportGraph ImportGraph (long "imports" <> help "Compute an import graph (default)")
<|> flag' CallGraph (long "calls" <> help "Compute a call graph")
renderer <- flag (SomeRenderer DOTGraphRenderer) (SomeRenderer DOTGraphRenderer) (long "dot" <> help "Output in DOT graph format (default)")
<|> flag' (SomeRenderer JSONGraphRenderer) (long "json" <> help "Output JSON graph")
rootDir <- rootDirectoryOption
excludeDirs <- excludeDirsOption
File{..} <- argument filePathReader (metavar "DIR:LANGUAGE | FILE")
pure $ runGraph graphType renderer rootDir filePath (fromJust fileLanguage) excludeDirs
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"))
filePathReader = eitherReader parseFilePath filePathReader = eitherReader parseFilePath
parseFilePath arg = case splitWhen (== ':') arg of parseFilePath arg = case splitWhen (== ':') arg of
[a, b] | lang <- readMaybe b -> Right (File a lang) [a, b] | lang <- readMaybe b -> Right (File a lang)

View File

@ -1,8 +1,8 @@
{-# LANGUAGE GADTs, ScopedTypeVariables, TypeOperators #-} {-# LANGUAGE GADTs, TypeOperators #-}
module Semantic.Graph where module Semantic.Graph where
import Analysis.Abstract.Evaluating import Analysis.Abstract.Evaluating
import Analysis.Abstract.ImportGraph import Analysis.Abstract.Graph
import qualified Control.Exception as Exc import qualified Control.Exception as Exc
import Data.Abstract.Address import Data.Abstract.Address
import Data.Abstract.Evaluatable import Data.Abstract.Evaluatable
@ -14,30 +14,35 @@ import Data.ByteString.Char8 (pack)
import Data.File import Data.File
import Data.Output import Data.Output
import Data.Semilattice.Lower import Data.Semilattice.Lower
import qualified Data.Syntax as Syntax
import Data.Term
import Parsing.Parser import Parsing.Parser
import Prologue hiding (MonadError (..)) import Prologue hiding (MonadError (..))
import Rendering.Renderer import Rendering.Renderer
import Semantic.IO (Files) import Semantic.IO (Files)
import Semantic.Task as Task import Semantic.Task as Task
data GraphType = ImportGraph | CallGraph
graph :: Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Telemetry] effs graph :: Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Telemetry] effs
=> GraphRenderer output => GraphType
-> GraphRenderer output
-> Project -> Project
-> Eff effs ByteString -> Eff effs ByteString
graph renderer project graph graphType renderer 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
parsePackage parser prelude project >>= graphImports >>= case renderer of package <- parsePackage parser prelude project
let graph package = case graphType of
ImportGraph -> analyze runGraphAnalysis (evaluatePackageWith graphingModules graphingLoadErrors package) >>= extractGraph
CallGraph -> analyze runGraphAnalysis (evaluatePackageWith graphingModules (graphingLoadErrors . graphingTerms) package) >>= extractGraph
graph package >>= case renderer of
JSONGraphRenderer -> pure . toOutput JSONGraphRenderer -> pure . toOutput
DOTGraphRenderer -> pure . renderImportGraph DOTGraphRenderer -> pure . renderGraph
-- | Parse a list of files into a 'Package'. -- | Parse a list of files into a 'Package'.
parsePackage :: Members '[Distribute WrappedTask, Files, Task] effs parsePackage :: Members '[Distribute WrappedTask, Files, Task] effs
=> Parser term -- ^ A parser. => Parser term -- ^ A parser.
-> Maybe File -- ^ Prelude (optional). -> Maybe File -- ^ Prelude (optional).
-> Project -- ^ Project to parse into a package. -> Project -- ^ Project to parse into a package.
-> Eff effs (Package term) -> Eff effs (Package term)
parsePackage parser preludeFile project@Project{..} = do parsePackage parser preludeFile project@Project{..} = do
prelude <- traverse (parseModule parser Nothing) preludeFile prelude <- traverse (parseModule parser Nothing) preludeFile
@ -57,31 +62,30 @@ parseModule parser rootDir file = do
moduleForBlob rootDir blob <$> parse parser blob moduleForBlob rootDir blob <$> parse parser blob
importGraphAnalysis :: forall term syntax ann a runGraphAnalysis :: Evaluator (Located Precise) term (Value (Located Precise))
. Evaluator (Located Precise) term (Value (Located Precise)) '[ State Graph
'[ State (ImportGraph (Term (Sum syntax) ann)) , Resumable (AddressError (Located Precise) (Value (Located Precise)))
, Resumable (AddressError (Located Precise) (Value (Located Precise))) , Resumable ResolutionError
, Resumable ResolutionError , Resumable (EvalError (Value (Located Precise)))
, Resumable (EvalError (Value (Located Precise))) , State [Name]
, State [Name] , Resumable (ValueError (Located Precise) (Value (Located Precise)))
, Resumable (ValueError (Located Precise) (Value (Located Precise))) , Resumable (Unspecialized (Value (Located Precise)))
, Resumable (Unspecialized (Value (Located Precise))) , Resumable (LoadError term)
, Resumable (LoadError term) , Fail
, Fail , Fresh
, Fresh , Reader (Environment (Located Precise) (Value (Located Precise)))
, Reader (Environment (Located Precise) (Value (Located Precise))) , State (Environment (Located Precise) (Value (Located Precise)))
, State (Environment (Located Precise) (Value (Located Precise))) , State (Heap (Located Precise) (Value (Located Precise)))
, State (Heap (Located Precise) (Value (Located Precise))) , State (ModuleTable (Environment (Located Precise) (Value (Located Precise)), Value (Located Precise)))
, State (ModuleTable (Environment (Located Precise) (Value (Located Precise)), Value (Located Precise))) , State (Exports (Located Precise) (Value (Located Precise)))
, State (Exports (Located Precise) (Value (Located Precise))) , State (JumpTable term)
, State (JumpTable term) ] a
] a -> ( Either String -- 'fail' calls
-> ( Either String -- 'fail' calls ( ( a -- the result value
( ( a -- the result value , Graph) -- the import graph
, ImportGraph (Term (Sum syntax) ann)) -- the import graph , [Name]) -- the list of bad names
, [Name]) -- the list of bad names , EvaluatingState (Located Precise) term (Value (Located Precise))) -- the final state
, EvaluatingState (Located Precise) term (Value (Located Precise))) -- the final state runGraphAnalysis
importGraphAnalysis
= run = run
. evaluating . evaluating
. resumingLoadError . resumingLoadError
@ -90,7 +94,7 @@ importGraphAnalysis
. resumingEvalError . resumingEvalError
. resumingResolutionError . resumingResolutionError
. resumingAddressError . resumingAddressError
. importGraphing . graphing
resumingResolutionError :: (Applicative (m effects), Effectful m) => m (Resumable ResolutionError ': effects) a -> m effects a resumingResolutionError :: (Applicative (m effects), Effectful m) => m (Resumable ResolutionError ': effects) a -> m effects a
resumingResolutionError = runResolutionErrorWith (\ err -> traceM ("ResolutionError:" <> show err) *> case err of resumingResolutionError = runResolutionErrorWith (\ err -> traceM ("ResolutionError:" <> show err) *> case err of
@ -137,20 +141,7 @@ resumingValueError = runValueErrorWith (\ err -> traceM ("ValueError" <> show er
KeyValueError{} -> pure (hole, hole) KeyValueError{} -> pure (hole, hole)
ArithmeticError{} -> pure hole) ArithmeticError{} -> pure hole)
-- | Render the import graph for a given 'Package'. extractGraph :: (Member (Exc SomeException) effects, Show result, Show state) => (Either String ((result, Graph), [Name]), state) -> Eff effects Graph
graphImports :: ( Show ann extractGraph result = case result of
, Apply Declarations1 syntax (Right ((_, graph), _), _) -> pure graph
, Apply Evaluatable syntax _ -> Task.throwError (toException (Exc.ErrorCall ("graphImports: import graph rendering failed " <> show result)))
, Apply FreeVariables1 syntax
, Apply Functor syntax
, Apply Show1 syntax
, Element Syntax.Identifier syntax
, Members '[Exc SomeException, Task] effs
)
=> Package (Term (Sum syntax) ann)
-> Eff effs (ImportGraph (Term (Sum syntax) ann))
graphImports package = analyze importGraphAnalysis (evaluatePackageWith graphingModules (graphingLoadErrors . graphingTerms) package) >>= extractGraph
where
extractGraph result = case result of
(Right ((_, graph), _), _) -> pure graph
_ -> Task.throwError (toException (Exc.ErrorCall ("graphImports: import graph rendering failed " <> show result)))

View File

@ -11,6 +11,7 @@ module Semantic.IO
, findFiles , findFiles
, languageForFilePath , languageForFilePath
, NoLanguageForBlob(..) , NoLanguageForBlob(..)
, FormatNotSupported(..)
, readBlob , readBlob
, readProject , readProject
, readBlobs , readBlobs
@ -178,6 +179,10 @@ 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
newtype FormatNotSupported = FormatNotSupported String
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 . ReadBlob

View File

@ -12,7 +12,7 @@ import Data.Record
import Parsing.Parser import Parsing.Parser
import Prologue hiding (MonadError(..)) import Prologue hiding (MonadError(..))
import Rendering.Renderer import Rendering.Renderer
import Semantic.IO (NoLanguageForBlob(..)) import Semantic.IO (NoLanguageForBlob(..), FormatNotSupported(..))
import Semantic.Task import Semantic.Task
parseBlobs :: (Members '[Distribute WrappedTask, Task, Exc SomeException] effs, Output output) => TermRenderer output -> [Blob] -> Eff effs ByteString parseBlobs :: (Members '[Distribute WrappedTask, Task, Exc SomeException] effs, Output output) => TermRenderer output -> [Blob] -> Eff effs ByteString
@ -34,3 +34,20 @@ parseBlob renderer blob@Blob{..}
SymbolsTermRenderer fields -> decorate (declarationAlgebra blob) >=> render (renderToSymbols fields blob) SymbolsTermRenderer fields -> decorate (declarationAlgebra blob) >=> render (renderToSymbols fields blob)
DOTTermRenderer -> render (renderDOTTerm blob) DOTTermRenderer -> render (renderDOTTerm blob)
| otherwise = throwError (SomeException (NoLanguageForBlob blobPath)) | otherwise = throwError (SomeException (NoLanguageForBlob blobPath))
astParseBlobs :: (Members '[Distribute WrappedTask, Task, Exc SomeException] effs, Output output) => TermRenderer output -> [Blob] -> Eff effs ByteString
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))