1
1
mirror of https://github.com/github/semantic.git synced 2024-12-24 23:42:31 +03:00

Merge branch 'musical-modules' into 🔥-monolithic-syntax

This commit is contained in:
Rob Rix 2017-11-28 11:59:28 -05:00
commit f1c4b58797
68 changed files with 726 additions and 645 deletions

2
.ghci
View File

@ -25,7 +25,7 @@ assignmentExample lang = case lang of
"Markdown" -> mk "md" "markdown"
"JSON" -> mk "json" "json"
_ -> mk "" ""
where mk fileExtension parser = putStrLn ("example: fmap (() <$) . runTask . parse " ++ parser ++ "Parser =<< Semantic.Util.file \"example." ++ fileExtension ++ "\"") >> return ("import Parser\nimport Semantic.Task\nimport Semantic.Util")
where mk fileExtension parser = putStrLn ("example: fmap (() <$) . runTask . parse " ++ parser ++ "Parser =<< Semantic.Util.file \"example." ++ fileExtension ++ "\"") >> return ("import Parsing.Parser\nimport Semantic.Task\nimport Semantic.Util")
:}
:undef assignment
:def assignment assignmentExample

View File

@ -1,3 +1,2 @@
module Main (main)
where
import SemanticCmdLine (main)
module Main (main) where
import Semantic.CLI (main)

View File

@ -13,8 +13,19 @@ cabal-version: >=1.10
library
hs-source-dirs: src
exposed-modules: Algorithm
exposed-modules:
-- Analyses & term annotations
Analysis.ConstructorName
, Analysis.CyclomaticComplexity
, Analysis.Decorator
, Analysis.Declaration
-- Semantic assignment
, Assigning.Assignment
, Assigning.Assignment.Table
-- General datatype definitions & generic algorithms
, Data.Algebra
, Data.Align.Generic
, Data.AST
, Data.Blob
, Data.Diff
, Data.Error
@ -23,6 +34,7 @@ library
, Data.Functor.Classes.Ord.Generic
, Data.Functor.Classes.Show.Generic
, Data.JSON.Fields
, Data.Language
, Data.Mergeable
, Data.Output
, Data.Patch
@ -31,10 +43,8 @@ library
, Data.Source
, Data.Span
, Data.SplitDiff
-- À la carte syntax types
, Data.Syntax
, Data.Syntax.Algebra
, Data.Syntax.Assignment
, Data.Syntax.Assignment.Table
, Data.Syntax.Comment
, Data.Syntax.Declaration
, Data.Syntax.Expression
@ -42,10 +52,13 @@ library
, Data.Syntax.Statement
, Data.Syntax.Type
, Data.Term
, Files
, Interpreter
, Language
, Language.Markdown
-- Diffing algorithms & interpretation thereof
, Diffing.Algorithm
, Diffing.Algorithm.RWS
, Diffing.Algorithm.RWS.FeatureVector
, Diffing.Algorithm.SES
, Diffing.Interpreter
-- Language-specific grammar/syntax types, & assignments
, Language.Markdown.Assignment
, Language.Markdown.Syntax
, Language.Go.Grammar
@ -62,24 +75,26 @@ library
, Language.Python.Assignment
, Language.Python.Grammar
, Language.Python.Syntax
, Parser
-- Parser glue
, Parsing.CMark
, Parsing.Parser
, Parsing.TreeSitter
, Paths_semantic_diff
, Renderer
, Renderer.JSON
, Renderer.SExpression
, Renderer.Tag
, Renderer.TOC
, RWS
, RWS.FeatureVector
-- Rendering formats
, Rendering.JSON
, Rendering.Renderer
, Rendering.SExpression
, Rendering.Tag
, Rendering.TOC
-- High-level flow & operational functionality (logging, stats, etc.)
, Semantic
, Semantic.CLI
, Semantic.IO
, Semantic.Log
, Semantic.Stat
, Semantic.Task
, Semantic.Queue
, Semantic.Util
, SemanticCmdLine
, SES
, TreeSitter
build-depends: base >= 4.8 && < 5
, aeson
, ansi-terminal
@ -142,23 +157,23 @@ test-suite test
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Spec.hs
other-modules: CommandSpec
other-modules: Assigning.Assignment.Spec
, Data.Diff.Spec
, Data.Functor.Classes.Ord.Generic.Spec
, Data.Functor.Listable
, Data.Mergeable.Spec
, Data.RandomWalkSimilarity.Spec
, Data.Syntax.Assignment.Spec
, DiffSpec
, SemanticSpec
, SemanticCmdLineSpec
, Semantic.StatSpec
, InterpreterSpec
, SES.Spec
, SourceSpec
, Data.Source.Spec
, Data.Term.Spec
, Diffing.Algorithm.RWS.Spec
, Diffing.Algorithm.SES.Spec
, Diffing.Interpreter.Spec
, Integration.Spec
, Rendering.TOC.Spec
, Semantic.Spec
, Semantic.CLI.Spec
, Semantic.IO.Spec
, Semantic.Stat.Spec
, SpecHelpers
, TermSpec
, TOCSpec
, IntegrationSpec
, Test.Hspec.LeanCheck
build-depends: aeson
, array

View File

@ -0,0 +1,78 @@
{-# LANGUAGE DataKinds, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
module Analysis.ConstructorName
( ConstructorName(..)
, ConstructorLabel(..)
, constructorLabel
) where
import Data.Aeson
import Data.ByteString.Char8 (ByteString, pack, unpack)
import Data.JSON.Fields
import Data.Proxy
import Data.Term
import Data.Text.Encoding (decodeUtf8)
import Data.Union
import GHC.Generics
-- | Compute a 'ConstructorLabel' label for a 'Term'.
constructorLabel :: ConstructorName syntax => TermF syntax a b -> ConstructorLabel
constructorLabel (In _ s) = ConstructorLabel $ pack (constructorName s)
newtype ConstructorLabel = ConstructorLabel ByteString
instance Show ConstructorLabel where
showsPrec _ (ConstructorLabel s) = showString (unpack s)
instance ToJSONFields ConstructorLabel where
toJSONFields (ConstructorLabel s) = [ "category" .= decodeUtf8 s ]
-- | A typeclass to retrieve the name of the data constructor for a value.
--
-- This typeclass employs the Advanced Overlap techniques designed by Oleg Kiselyov & Simon Peyton Jones: https://wiki.haskell.org/GHC/AdvancedOverlap; see also src/Analysis/Declaration.hs for discussion of the details of the mechanism.
class ConstructorName syntax where
constructorName :: syntax a -> String
instance (ConstructorNameStrategy syntax ~ strategy, ConstructorNameWithStrategy strategy syntax) => ConstructorName syntax where
constructorName = constructorNameWithStrategy (Proxy :: Proxy strategy)
class CustomConstructorName syntax where
customConstructorName :: syntax a -> String
instance Apply ConstructorName fs => CustomConstructorName (Union fs) where
customConstructorName = apply (Proxy :: Proxy ConstructorName) constructorName
instance CustomConstructorName [] where
customConstructorName [] = "[]"
customConstructorName _ = ""
data Strategy = Default | Custom
type family ConstructorNameStrategy syntax where
ConstructorNameStrategy (Union _) = 'Custom
ConstructorNameStrategy [] = 'Custom
ConstructorNameStrategy syntax = 'Default
class ConstructorNameWithStrategy (strategy :: Strategy) syntax where
constructorNameWithStrategy :: proxy strategy -> syntax a -> String
instance (Generic1 syntax, GConstructorName (Rep1 syntax)) => ConstructorNameWithStrategy 'Default syntax where
constructorNameWithStrategy _ = gconstructorName . from1
instance CustomConstructorName syntax => ConstructorNameWithStrategy 'Custom syntax where
constructorNameWithStrategy _ = customConstructorName
class GConstructorName f where
gconstructorName :: f a -> String
instance GConstructorName f => GConstructorName (M1 D c f) where
gconstructorName = gconstructorName . unM1
instance (GConstructorName f, GConstructorName g) => GConstructorName (f :+: g) where
gconstructorName (L1 l) = gconstructorName l
gconstructorName (R1 r) = gconstructorName r
instance Constructor c => GConstructorName (M1 C c f) where
gconstructorName = conName

View File

@ -0,0 +1,26 @@
{-# LANGUAGE GeneralizedNewtypeDeriving, TypeOperators #-}
module Analysis.CyclomaticComplexity
( cyclomaticComplexityAlgebra
) where
import Data.Algebra (FAlgebra)
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Statement as Statement
import Data.Term
import Data.Union
-- | The cyclomatic complexity of a (sub)term.
newtype CyclomaticComplexity = CyclomaticComplexity Int
deriving (Enum, Eq, Num, Ord, Show)
-- | Compute the cyclomatic complexity of a (sub)term, measured as the number places where control exits scope, e.g. returns and yields.
--
-- TODO: Explicit returns at the end of methods should only count once.
-- TODO: Anonymous functions should not increase parent scopes complexity.
-- TODO: Inner functions should not increase parent scopes complexity.
cyclomaticComplexityAlgebra :: (Declaration.Method :< fs, Statement.Return :< fs, Statement.Yield :< fs, Apply Foldable fs, Apply Functor fs) => FAlgebra (Term (Union fs) a) CyclomaticComplexity
cyclomaticComplexityAlgebra (In _ union) = case union of
_ | Just Declaration.Method{} <- prj union -> succ (sum union)
_ | Just Statement.Return{} <- prj union -> succ (sum union)
_ | Just Statement.Yield{} <- prj union -> succ (sum union)
_ -> sum union

View File

@ -1,92 +1,30 @@
{-# LANGUAGE DataKinds, MultiParamTypeClasses, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
module Renderer.TOC
( renderToCDiff
, renderToCTerm
, diffTOC
, Summaries(..)
, TOCSummary(..)
, isValidSummary
, Declaration(..)
, getDeclaration
, declaration
{-# LANGUAGE DataKinds, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, UndecidableInstances #-}
module Analysis.Declaration
( Declaration(..)
, HasDeclaration
, declarationAlgebra
, Entry(..)
, tableOfContentsBy
, termTableOfContentsBy
, dedupe
, entrySummary
, toCategoryName
) where
import Data.Aeson
import Data.Align (bicrosswalk)
import Data.Bifoldable (bifoldMap)
import Data.Bifunctor (bimap)
import Data.Algebra
import Data.Blob
import Data.ByteString.Lazy (toStrict)
import Data.Diff
import Data.Error as Error (Error(..), showExpectation)
import Data.Foldable (fold, foldl', toList)
import Data.Functor.Both hiding (fst, snd)
import Data.Functor.Foldable (cata)
import Data.Function (on)
import Data.Error (Error(..), showExpectation)
import Data.Foldable (toList)
import Data.Language
import Data.List.NonEmpty (nonEmpty)
import Data.List (sortOn)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Output
import Data.Patch
import Data.Proxy
import Data.Proxy (Proxy(..))
import Data.Range
import Data.Record
import Data.Semigroup ((<>), sconcat)
import Data.Source as Source
import Data.Semigroup (sconcat)
import Data.Span
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Declaration as Declaration
import Data.Term
import Data.Text (toLower, stripEnd)
import qualified Data.Text as T
import Data.Union
import GHC.Generics
import Language
import qualified Data.List as List
import qualified Data.Map as Map hiding (null)
import Data.Syntax.Algebra (RAlgebra)
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Declaration as Declaration
import qualified Language.Markdown.Syntax as Markdown
data Summaries = Summaries { changes, errors :: !(Map.Map T.Text [Value]) }
deriving (Eq, Show)
instance Monoid Summaries where
mempty = Summaries mempty mempty
mappend (Summaries c1 e1) (Summaries c2 e2) = Summaries (Map.unionWith (<>) c1 c2) (Map.unionWith (<>) e1 e2)
instance Output Summaries where
toOutput = toStrict . (<> "\n") . encode
instance ToJSON Summaries where
toJSON Summaries{..} = object [ "changes" .= changes, "errors" .= errors ]
data TOCSummary
= TOCSummary
{ summaryCategoryName :: T.Text
, summaryTermName :: T.Text
, summarySpan :: Span
, summaryChangeType :: T.Text
}
| ErrorSummary { error :: T.Text, errorSpan :: Span, errorLanguage :: Maybe Language }
deriving (Generic, Eq, Show)
instance ToJSON TOCSummary where
toJSON TOCSummary{..} = object [ "changeType" .= summaryChangeType, "category" .= summaryCategoryName, "term" .= summaryTermName, "span" .= summarySpan ]
toJSON ErrorSummary{..} = object [ "error" .= error, "span" .= errorSpan, "language" .= errorLanguage ]
isValidSummary :: TOCSummary -> Bool
isValidSummary ErrorSummary{} = False
isValidSummary _ = True
-- | A declarations identifier and type.
data Declaration
= MethodDeclaration { declarationIdentifier :: T.Text, declarationText :: T.Text, declarationLanguage :: Maybe Language, declarationReceiver :: Maybe T.Text }
@ -107,7 +45,7 @@ data Declaration
-- If youre getting errors about missing a 'CustomHasDeclaration' instance for your syntax type, you probably forgot step 1.
--
-- If youre getting 'Nothing' for your syntax node at runtime, you probably forgot step 2.
declarationAlgebra :: (HasField fields Range, HasField fields Span, Foldable syntax, HasDeclaration syntax) => Blob -> RAlgebra (TermF syntax (Record fields)) (Term syntax (Record fields)) (Maybe Declaration)
declarationAlgebra :: (HasField fields Range, HasField fields Span, Foldable syntax, HasDeclaration syntax) => Blob -> RAlgebra (Term syntax (Record fields)) (Maybe Declaration)
declarationAlgebra blob (In ann syntax) = toDeclaration blob ann syntax
@ -116,7 +54,7 @@ declarationAlgebra blob (In ann syntax) = toDeclaration blob ann syntax
-- This typeclass employs the Advanced Overlap techniques designed by Oleg Kiselyov & Simon Peyton Jones: https://wiki.haskell.org/GHC/AdvancedOverlap.
class HasDeclaration syntax where
-- | Compute a 'Declaration' for a syntax type using its 'CustomHasDeclaration' instance, if any, or else falling back to the default definition (which simply returns 'Nothing').
toDeclaration :: (Foldable whole, HasField fields Range, HasField fields Span) => Blob -> Record fields -> RAlgebra syntax (Term whole (Record fields)) (Maybe Declaration)
toDeclaration :: (Foldable whole, HasField fields Range, HasField fields Span) => Blob -> Record fields -> syntax (Term whole (Record fields), Maybe Declaration) -> Maybe Declaration
-- | Define 'toDeclaration' using the 'CustomHasDeclaration' instance for a type if there is one or else use the default definition.
--
@ -130,7 +68,7 @@ instance (DeclarationStrategy syntax ~ strategy, HasDeclarationWithStrategy stra
-- | Types for which we can produce a customized 'Declaration'. This returns in 'Maybe' so that some values can be opted out (e.g. anonymous functions).
class CustomHasDeclaration syntax where
-- | Produce a customized 'Declaration' for a given syntax node.
customToDeclaration :: (Foldable whole, HasField fields Range, HasField fields Span) => Blob -> Record fields -> RAlgebra syntax (Term whole (Record fields)) (Maybe Declaration)
customToDeclaration :: (Foldable whole, HasField fields Range, HasField fields Span) => Blob -> Record fields -> syntax (Term whole (Record fields), Maybe Declaration) -> Maybe Declaration
-- | Produce a 'HeadingDeclaration' from the first line of the heading of a 'Markdown.Heading' node.
@ -146,6 +84,7 @@ instance CustomHasDeclaration Markdown.Heading where
instance CustomHasDeclaration Syntax.Error where
customToDeclaration Blob{..} ann err@Syntax.Error{}
= Just $ ErrorDeclaration (T.pack (formatTOCError (Syntax.unError (getField ann) err))) mempty blobLanguage
where formatTOCError e = showExpectation False (errorExpected e) (errorActual e) ""
-- | Produce a 'FunctionDeclaration' for 'Declaration.Function' nodes so long as their identifier is non-empty (defined as having a non-empty 'Range').
instance CustomHasDeclaration Declaration.Function where
@ -163,7 +102,7 @@ instance CustomHasDeclaration Declaration.Method where
-- Methods without a receiver
| isEmpty receiverAnn = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage Nothing
-- Methods with a receiver type and an identifier (e.g. (a *Type) in Go).
| blobLanguage == Just Language.Go
| blobLanguage == Just Go
, [ _, Term (In receiverType _) ] <- toList receiverF = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource receiverType))
-- Methods with a receiver (class methods) are formatted like `receiver.method_name`
| otherwise = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource receiverAnn))
@ -189,7 +128,7 @@ data Strategy = Default | Custom
--
-- You should probably be using 'CustomHasDeclaration' instead of this class; and you should not define new instances of this class.
class HasDeclarationWithStrategy (strategy :: Strategy) syntax where
toDeclarationWithStrategy :: (Foldable whole, HasField fields Range, HasField fields Span) => proxy strategy -> Blob -> Record fields -> RAlgebra syntax (Term whole (Record fields)) (Maybe Declaration)
toDeclarationWithStrategy :: (Foldable whole, HasField fields Range, HasField fields Span) => proxy strategy -> Blob -> Record fields -> syntax (Term whole (Record fields), Maybe Declaration) -> Maybe Declaration
-- | A predicate on syntax types selecting either the 'Custom' or 'Default' strategy.
@ -216,142 +155,23 @@ instance CustomHasDeclaration syntax => HasDeclarationWithStrategy 'Custom synta
toDeclarationWithStrategy _ = customToDeclaration
getDeclaration :: HasField fields (Maybe Declaration) => Record fields -> Maybe Declaration
getDeclaration = getField
-- | Produce the annotations of nodes representing declarations.
declaration :: HasField fields (Maybe Declaration) => TermF f (Record fields) a -> Maybe (Record fields)
declaration (In annotation _) = annotation <$ getDeclaration annotation
getMethodSource :: HasField fields Range => Blob -> TermF Declaration.Method (Record fields) (Term syntax (Record fields), a) -> T.Text
getMethodSource Blob{..} (In a r)
= let declRange = getField a
bodyRange = getField <$> case r of
Declaration.Method _ _ _ _ (Term (In a' _), _) -> Just a'
in maybe mempty (stripEnd . toText . flip Source.slice blobSource . subtractRange declRange) bodyRange
in maybe mempty (T.stripEnd . toText . flip Source.slice blobSource . subtractRange declRange) bodyRange
getFunctionSource :: HasField fields Range => Blob -> TermF Declaration.Function (Record fields) (Term syntax (Record fields), a) -> T.Text
getFunctionSource Blob{..} (In a r)
= let declRange = getField a
bodyRange = getField <$> case r of
Declaration.Function _ _ _ (Term (In a' _), _) -> Just a'
in maybe mempty (stripEnd . toText . flip Source.slice blobSource . subtractRange declRange) bodyRange
in maybe mempty (T.stripEnd . toText . flip Source.slice blobSource . subtractRange declRange) bodyRange
getClassSource :: (HasField fields Range) => Blob -> TermF Declaration.Class (Record fields) (Term syntax (Record fields), a) -> T.Text
getClassSource Blob{..} (In a r)
= let declRange = getField a
bodyRange = getField <$> case r of
Declaration.Class _ _ _ (Term (In a' _), _) -> Just a'
in maybe mempty (stripEnd . toText . flip Source.slice blobSource . subtractRange declRange) bodyRange
formatTOCError :: Error.Error String -> String
formatTOCError e = showExpectation False (errorExpected e) (errorActual e) ""
-- | An entry in a table of contents.
data Entry a
= Changed { entryPayload :: a } -- ^ An entry for a node containing changes.
| Inserted { entryPayload :: a } -- ^ An entry for a change occurring inside an 'Insert' 'Patch'.
| Deleted { entryPayload :: a } -- ^ An entry for a change occurring inside a 'Delete' 'Patch'.
| Replaced { entryPayload :: a } -- ^ An entry for a change occurring on the insertion side of a 'Replace' 'Patch'.
deriving (Eq, Show)
-- | Compute a table of contents for a diff characterized by a function mapping relevant nodes onto values in Maybe.
tableOfContentsBy :: (Foldable f, Functor f)
=> (forall b. TermF f ann b -> Maybe a) -- ^ A function mapping relevant nodes onto values in Maybe.
-> Diff f ann ann -- ^ The diff to compute the table of contents for.
-> [Entry a] -- ^ A list of entries for relevant changed nodes in the diff.
tableOfContentsBy selector = fromMaybe [] . cata (\ r -> case r of
Patch patch -> (pure . patchEntry <$> bicrosswalk selector selector patch) <> bifoldMap fold fold patch <> Just []
Merge (In (_, ann2) r) -> case (selector (In ann2 r), fold r) of
(Just a, Just entries) -> Just (Changed a : entries)
(_ , entries) -> entries)
where patchEntry = patch Deleted Inserted (const Replaced)
termTableOfContentsBy :: (Foldable f, Functor f)
=> (forall b. TermF f annotation b -> Maybe a)
-> Term f annotation
-> [a]
termTableOfContentsBy selector = cata termAlgebra
where termAlgebra r | Just a <- selector r = a : fold r
| otherwise = fold r
newtype DedupeKey = DedupeKey (Maybe T.Text, Maybe T.Text) deriving (Eq, Ord)
-- Dedupe entries in a final pass. This catches two specific scenarios with
-- different behaviors:
-- 1. Identical entries are in the list.
-- Action: take the first one, drop all subsequent.
-- 2. Two similar entries (defined by a case insensitive comparision of their
-- identifiers) are in the list.
-- Action: Combine them into a single Replaced entry.
dedupe :: forall fields. HasField fields (Maybe Declaration) => [Entry (Record fields)] -> [Entry (Record fields)]
dedupe = let tuples = sortOn fst . Map.elems . snd . foldl' go (0, Map.empty) in (fmap . fmap) snd tuples
where
go :: HasField fields (Maybe Declaration)
=> (Int, Map.Map DedupeKey (Int, Entry (Record fields)))
-> Entry (Record fields)
-> (Int, Map.Map DedupeKey (Int, Entry (Record fields)))
go (index, m) x | Just (_, similar) <- Map.lookup (dedupeKey x) m
= if exactMatch similar x
then (succ index, m)
else
let replacement = Replaced (entryPayload similar)
in (succ index, Map.insert (dedupeKey replacement) (index, replacement) m)
| otherwise = (succ index, Map.insert (dedupeKey x) (index, x) m)
dedupeKey entry = DedupeKey ((fmap toCategoryName . getDeclaration . entryPayload) entry, (fmap (toLower . declarationIdentifier) . getDeclaration . entryPayload) entry)
exactMatch = (==) `on` (getDeclaration . entryPayload)
-- | Construct a 'TOCSummary' from an 'Entry'.
entrySummary :: (HasField fields (Maybe Declaration), HasField fields Span) => Entry (Record fields) -> Maybe TOCSummary
entrySummary entry = case entry of
Changed a -> recordSummary "modified" a
Deleted a -> recordSummary "removed" a
Inserted a -> recordSummary "added" a
Replaced a -> recordSummary "modified" a
-- | Construct a 'TOCSummary' from a node annotation and a change type label.
recordSummary :: (HasField fields (Maybe Declaration), HasField fields Span) => T.Text -> Record fields -> Maybe TOCSummary
recordSummary changeText record = case getDeclaration record of
Just (ErrorDeclaration text _ language) -> Just $ ErrorSummary text (getField record) language
Just declaration -> Just $ TOCSummary (toCategoryName declaration) (formatIdentifier declaration) (getField record) changeText
Nothing -> Nothing
where
formatIdentifier (MethodDeclaration identifier _ (Just Language.Go) (Just receiverTy)) = "(" <> receiverTy <> ") " <> identifier
formatIdentifier (MethodDeclaration identifier _ _ (Just receiver)) = receiver <> "." <> identifier
formatIdentifier declaration = declarationIdentifier declaration
renderToCDiff :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Both Blob -> Diff f (Record fields) (Record fields) -> Summaries
renderToCDiff blobs = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . diffTOC
where toMap [] = mempty
toMap as = Map.singleton summaryKey (toJSON <$> as)
summaryKey = T.pack $ case runJoin (blobPath <$> blobs) of
(before, after) | null before -> after
| null after -> before
| before == after -> after
| otherwise -> before <> " -> " <> after
diffTOC :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Diff f (Record fields) (Record fields) -> [TOCSummary]
diffTOC = mapMaybe entrySummary . dedupe . tableOfContentsBy declaration
renderToCTerm :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Blob -> Term f (Record fields) -> Summaries
renderToCTerm Blob{..} = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . termToC
where
toMap [] = mempty
toMap as = Map.singleton (T.pack blobPath) (toJSON <$> as)
termToC :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Term f (Record fields) -> [TOCSummary]
termToC = mapMaybe (recordSummary "unchanged") . termTableOfContentsBy declaration
-- The user-facing category name
toCategoryName :: Declaration -> T.Text
toCategoryName declaration = case declaration of
ClassDeclaration{} -> "Class"
FunctionDeclaration{} -> "Function"
MethodDeclaration{} -> "Method"
HeadingDeclaration _ _ _ l -> "Heading " <> T.pack (show l)
ErrorDeclaration{} -> "ParseError"
in maybe mempty (T.stripEnd . toText . flip Source.slice blobSource . subtractRange declRange) bodyRange

37
src/Analysis/Decorator.hs Normal file
View File

@ -0,0 +1,37 @@
{-# LANGUAGE DataKinds, TypeOperators #-}
module Analysis.Decorator
( decoratorWithAlgebra
, constructorNameAndConstantFields
) where
import Data.Aeson
import Data.Algebra
import Data.Bifunctor (second)
import Data.ByteString.Char8 (ByteString, pack)
import Data.Functor.Classes (Show1 (liftShowsPrec))
import Data.Functor.Foldable
import Data.JSON.Fields
import Data.Record
import Data.Term
import Data.Text.Encoding (decodeUtf8)
-- | Lift an algebra into a decorator for terms annotated with records.
decoratorWithAlgebra :: Functor syntax
=> RAlgebra (Term syntax (Record fs)) a -- ^ An R-algebra on terms.
-> Term syntax (Record fs) -- ^ A term to decorate with values produced by the R-algebra.
-> Term syntax (Record (a ': fs)) -- ^ A term decorated with values produced by the R-algebra.
decoratorWithAlgebra alg = para $ \ c@(In a f) -> termIn (alg (fmap (second (rhead . termAnnotation)) c) :. a) (fmap snd f)
newtype Identifier = Identifier ByteString
deriving (Eq, Show)
instance ToJSONFields Identifier where
toJSONFields (Identifier i) = [ "identifier" .= decodeUtf8 i ]
-- | Compute a 'ByteString' label for a 'Show1'able 'Term'.
--
-- This uses 'liftShowsPrec' to produce the 'ByteString', with the effect that
-- constant fields will be included and parametric fields will not be.
constructorNameAndConstantFields :: Show1 f => TermF f a b -> ByteString
constructorNameAndConstantFields (In _ f) = pack (liftShowsPrec (const (const id)) (const id) 0 f "")

View File

@ -59,13 +59,10 @@
-- AST symbols are classified by their 'symbolType' as either 'Regular', 'Anonymous', or 'Auxiliary'. 'Auxiliary' never appears in ASTs; 'Regular' is for the symbols of explicitly named productions in the grammar, and 'Anonymous' is for unnamed productions of content such as tokens. Most of the time, assignments are only concerned with the named productions, and thus will be using 'Regular' symbols. Therefore, when matching a committed choice of all-'Regular' symbols, nodes with 'Anonymous' symbols will be skipped. However, in some cases grammars dont provide a named symbol for e.g. every kind of infix operator, and thus the only way to differentiate between them is by means of a 'symbol' rule for an 'Anonymous' token. In these cases, and before every other kind of assignment, the 'Anonymous' nodes will not be skipped so that matching can succeed.
--
-- Therefore, in addition to the rule of thumb for committed choices (see above), try to match 'Regular' symbols up front, and only match 'Anonymous' ones in the middle of a chain. That will ensure that you dont have to make redundant effort to explicitly skip 'Anonymous' nodes ahead of multiple alternatives, and can instead rely on them being automatically skipped except when explicitly required.
module Data.Syntax.Assignment
module Assigning.Assignment
-- Types
( Assignment
, Location
, AST
, Node(..)
, nodeLocation
-- Combinators
, Alternative(..)
, MonadError(..)
@ -95,11 +92,13 @@ module Data.Syntax.Assignment
, module Parsers
) where
import qualified Assigning.Assignment.Table as Table
import Control.Applicative
import Control.Monad ((<=<), guard)
import Control.Monad.Error.Class hiding (Error)
import Control.Monad.Fail
import Control.Monad.Free.Freer
import Data.AST
import Data.Bifunctor
import Data.ByteString (ByteString)
import Data.Error
@ -114,7 +113,6 @@ import Data.Record
import Data.Semigroup
import qualified Data.Source as Source (Source, slice, sourceBytes)
import Data.Span
import qualified Data.Syntax.Assignment.Table as Table
import Data.Term
import GHC.Stack
import Prelude hiding (fail, until)
@ -216,22 +214,6 @@ manyThrough step stop = go
where go = (,) [] <$> stop <|> first . (:) <$> step <*> go
-- | A location specified as possibly-empty intervals of bytes and line/column positions.
type Location = '[Range, Span]
-- | An AST node labelled with symbols and source location.
type AST f grammar = Term f (Node grammar)
data Node grammar = Node
{ nodeSymbol :: !grammar
, nodeByteRange :: {-# UNPACK #-} !Range
, nodeSpan :: {-# UNPACK #-} !Span
}
deriving (Eq, Show)
nodeLocation :: Node grammar -> Record Location
nodeLocation Node{..} = nodeByteRange :. nodeSpan :. Nil
nodeError :: HasCallStack => [Either String grammar] -> Node grammar -> Error (Either String grammar)
nodeError expected Node{..} = Error nodeSpan expected (Just (Right nodeSymbol))
@ -304,7 +286,7 @@ withStateCallStack :: Maybe (String, SrcLoc) -> State ast grammar -> (HasCallSta
withStateCallStack callSite state action = withCallStack (freezeCallStack (fromCallSiteList (maybe id (:) callSite (stateCallSites state)))) action
skipTokens :: Symbol grammar => State ast grammar -> State ast grammar
skipTokens state = state { stateNodes = dropWhile ((/= Regular) . symbolType . nodeSymbol . termAnnotation . unTerm) (stateNodes state) }
skipTokens state = state { stateNodes = dropWhile ((/= Regular) . symbolType . nodeSymbol . termAnnotation) (stateNodes state) }
-- | Advances the state past the current (head) node (if any), dropping it off stateNodes, and updating stateOffset & statePos to its end; or else returns the state unchanged.
advanceState :: State ast grammar -> State ast grammar

View File

@ -1,4 +1,4 @@
module Data.Syntax.Assignment.Table
module Assigning.Assignment.Table
( Table(tableAddresses)
, singleton
, fromListWith

23
src/Data/AST.hs Normal file
View File

@ -0,0 +1,23 @@
{-# LANGUAGE DataKinds #-}
module Data.AST where
import Data.Range
import Data.Record
import Data.Span
import Data.Term
-- | An AST node labelled with symbols and source location.
type AST syntax grammar = Term syntax (Node grammar)
data Node grammar = Node
{ nodeSymbol :: !grammar
, nodeByteRange :: {-# UNPACK #-} !Range
, nodeSpan :: {-# UNPACK #-} !Span
}
deriving (Eq, Show)
-- | A location specified as possibly-empty intervals of bytes and line/column positions.
type Location = '[Range, Span]
nodeLocation :: Node grammar -> Record Location
nodeLocation Node{..} = nodeByteRange :. nodeSpan :. Nil

41
src/Data/Algebra.hs Normal file
View File

@ -0,0 +1,41 @@
{-# LANGUAGE RankNTypes #-}
module Data.Algebra
( FAlgebra
, RAlgebra
, OpenFAlgebra
, OpenRAlgebra
, fToR
, fToOpenR
, rToOpenR
, openFToOpenR
) where
import Data.Functor.Foldable (Base)
-- | An F-algebra on some 'Recursive' type @t@.
type FAlgebra t a = Base t a -> a
-- | An R-algebra on some 'Recursive' type @t@.
type RAlgebra t a = Base t (t, a) -> a
-- | An open-recursive F-algebra on some 'Recursive' type @t@.
type OpenFAlgebra t a = forall b . (b -> a) -> Base t b -> a
-- | An open-recursive R-algebra on some 'Recursive' type @t@.
type OpenRAlgebra t a = forall b . (b -> (t, a)) -> Base t b -> a
-- | Promote an 'FAlgebra' into an 'RAlgebra' (by dropping the original parameter).
fToR :: Functor (Base t) => FAlgebra t a -> RAlgebra t a
fToR f = f . fmap snd
-- | Promote an 'FAlgebra' into an 'OpenRAlgebra' (by 'fmap'ing the action over the structure and dropping the original parameter).
fToOpenR :: Functor (Base t) => FAlgebra t a -> OpenRAlgebra t a
fToOpenR alg f = alg . fmap (snd . f)
-- | Promote an 'RAlgebra' into an 'OpenRAlgebra' (by 'fmap'ing the action over the structure).
rToOpenR :: Functor (Base t) => RAlgebra t a -> OpenRAlgebra t a
rToOpenR alg f = alg . fmap f
-- | Promote an 'OpenFAlgebra' into an 'OpenRAlgebra' (by dropping the original parameter).
openFToOpenR :: OpenFAlgebra t a -> OpenRAlgebra t a
openFToOpenR alg = alg . fmap snd

View File

@ -11,10 +11,10 @@ module Data.Blob
) where
import Data.ByteString.Char8 (ByteString, pack)
import Data.Language
import Data.Maybe (isJust)
import Data.Source as Source
import Data.Word
import Language
import Numeric
-- | The source, oid, path, and Maybe BlobKind of a blob.

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds, DeriveGeneric, DeriveAnyClass #-}
module Language where
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
module Data.Language where
import Data.Aeson
import GHC.Generics
@ -22,7 +22,7 @@ languageForType mediaType = case mediaType of
".json" -> Just JSON
".md" -> Just Markdown
".rb" -> Just Ruby
".go" -> Just Language.Go
".go" -> Just Go
".js" -> Just TypeScript
".ts" -> Just TypeScript
".tsx" -> Just TypeScript

View File

@ -15,8 +15,8 @@ data SplitPatch a
-- | Get the range of a SplitDiff.
getRange :: Functor f => HasField fields Range => SplitDiff f (Record fields) -> Range
getRange diff = getField $ case diff of
Free annotated -> termAnnotation annotated
Pure patch -> extract (splitTerm patch)
Free annotated -> termFAnnotation annotated
Pure patch -> termAnnotation (splitTerm patch)
-- | A diff with only one sides annotations.
type SplitDiff syntax ann = Free (TermF syntax ann) (SplitPatch (Term syntax ann))

View File

@ -1,10 +1,11 @@
{-# LANGUAGE DeriveAnyClass, GADTs, TypeOperators #-}
module Data.Syntax where
import Algorithm hiding (Empty)
import qualified Assigning.Assignment as Assignment
import Control.Applicative
import Control.Monad.Error.Class hiding (Error)
import Data.Align.Generic
import Data.AST
import Data.ByteString (ByteString)
import qualified Data.Error as Error
import Data.Foldable (asum, toList)
@ -19,9 +20,9 @@ import Data.Range
import Data.Record
import Data.Semigroup
import Data.Span
import qualified Data.Syntax.Assignment as Assignment
import Data.Term
import Data.Union
import Diffing.Algorithm hiding (Empty)
import GHC.Generics
import GHC.Stack
@ -33,7 +34,7 @@ makeTerm a = makeTerm' a . inj
-- | Lift a union and an annotation into a term, ensuring the annotation encompasses all children.
makeTerm' :: (HasCallStack, Semigroup a, Foldable f) => a -> f (Term f a) -> Term f a
makeTerm' a f = termIn (sconcat (a :| (termAnnotation . unTerm <$> toList f))) f
makeTerm' a f = termIn (sconcat (a :| (termAnnotation <$> toList f))) f
-- | Lift non-empty syntax into a term, injecting the syntax into a union & appending all subterms.annotations to make the new terms annotation.
makeTerm1 :: (HasCallStack, f :< fs, Semigroup a, Apply Foldable fs) => f (Term (Union fs) a) -> Term (Union fs) a
@ -42,20 +43,20 @@ makeTerm1 = makeTerm1' . inj
-- | Lift a non-empty union into a term, appending all subterms.annotations to make the new terms annotation.
makeTerm1' :: (HasCallStack, Semigroup a, Foldable f) => f (Term f a) -> Term f a
makeTerm1' f = case toList f of
a : _ -> makeTerm' (termAnnotation (unTerm a)) f
a : _ -> makeTerm' (termAnnotation a) f
_ -> error "makeTerm1': empty structure"
-- | Construct an empty term at the current position.
emptyTerm :: (HasCallStack, Empty :< fs, Apply Foldable fs) => Assignment.Assignment ast grammar (Term (Union fs) (Record Assignment.Location))
emptyTerm :: (HasCallStack, Empty :< fs, Apply Foldable fs) => Assignment.Assignment ast grammar (Term (Union fs) (Record Location))
emptyTerm = makeTerm . startLocation <$> Assignment.location <*> pure Empty
where startLocation ann = Range (start (getField ann)) (start (getField ann)) :. Span (spanStart (getField ann)) (spanStart (getField ann)) :. Nil
-- | Catch assignment errors into an error term.
handleError :: (HasCallStack, Error :< fs, Enum grammar, Eq1 ast, Ix grammar, Show grammar, Apply Foldable fs) => Assignment.Assignment ast grammar (Term (Union fs) (Record Assignment.Location)) -> Assignment.Assignment ast grammar (Term (Union fs) (Record Assignment.Location))
handleError :: (HasCallStack, Error :< fs, Enum grammar, Eq1 ast, Ix grammar, Show grammar, Apply Foldable fs) => Assignment.Assignment ast grammar (Term (Union fs) (Record Location)) -> Assignment.Assignment ast grammar (Term (Union fs) (Record Location))
handleError = flip catchError (\ err -> makeTerm <$> Assignment.location <*> pure (errorSyntax (either id show <$> err) []) <* Assignment.source)
-- | Catch parse errors into an error term.
parseError :: (HasCallStack, Error :< fs, Bounded grammar, Enum grammar, Ix grammar, Apply Foldable fs) => Assignment.Assignment ast grammar (Term (Union fs) (Record Assignment.Location))
parseError :: (HasCallStack, Error :< fs, Bounded grammar, Enum grammar, Ix grammar, Apply Foldable fs) => Assignment.Assignment ast grammar (Term (Union fs) (Record Location))
parseError = makeTerm <$> Assignment.token maxBound <*> pure (Error (ErrorStack (getCallStack (freezeCallStack callStack))) [] (Just "ParseError") [])

View File

@ -1,123 +0,0 @@
{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving, TypeOperators, UndecidableInstances #-}
module Data.Syntax.Algebra
( FAlgebra
, RAlgebra
, fToR
, decoratorWithAlgebra
, identifierAlgebra
, cyclomaticComplexityAlgebra
, ConstructorName(..)
, ConstructorLabel(..)
, constructorNameAndConstantFields
, constructorLabel
) where
import Data.Aeson
import Data.Bifunctor (second)
import Data.ByteString.Char8 (ByteString, pack, unpack)
import Data.Functor.Classes (Show1 (liftShowsPrec))
import Data.Functor.Foldable
import Data.JSON.Fields
import Data.Record
import Data.Proxy
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Statement as Statement
import Data.Term
import Data.Text.Encoding (decodeUtf8)
import Data.Union
import GHC.Generics
-- | An F-algebra on some carrier functor 'f'.
type FAlgebra f a = f a -> a
-- | An R-algebra on some carrier functor 'f' of its fixpoint type 't'.
type RAlgebra f t a = f (t, a) -> a
-- | Promote an FAlgebra into an RAlgebra (by dropping the original parameter).
fToR :: Functor (Base t) => FAlgebra (Base t) a -> RAlgebra (Base t) t a
fToR f = f . fmap snd
-- | Lift an algebra into a decorator for terms annotated with records.
decoratorWithAlgebra :: Functor f
=> RAlgebra (Base (Term f (Record fs))) (Term f (Record fs)) a -- ^ An R-algebra on terms.
-> Term f (Record fs) -- ^ A term to decorate with values produced by the R-algebra.
-> Term f (Record (a ': fs)) -- ^ A term decorated with values produced by the R-algebra.
decoratorWithAlgebra alg = para $ \ c@(In a f) -> termIn (alg (fmap (second (rhead . extract)) c) :. a) (fmap snd f)
newtype Identifier = Identifier ByteString
deriving (Eq, Show)
instance ToJSONFields Identifier where
toJSONFields (Identifier i) = [ "identifier" .= decodeUtf8 i ]
-- | Produce the identifier for a given term, if any.
--
-- Identifier syntax is labelled, as well as declaration syntax identified by these, but other uses of these identifiers are not, e.g. the declaration of a class or method or binding of a variable will be labelled, but a function call will not.
identifierAlgebra :: (Syntax.Identifier :< fs, Declaration.Method :< fs, Declaration.Class :< fs, Apply Foldable fs, Apply Functor fs) => FAlgebra (Base (Term (Union fs) a)) (Maybe Identifier)
identifierAlgebra (In _ union) = case union of
_ | Just (Syntax.Identifier s) <- prj union -> Just (Identifier s)
_ | Just Declaration.Class{..} <- prj union -> classIdentifier
_ | Just Declaration.Method{..} <- prj union -> methodName
_ -> Nothing
-- | The cyclomatic complexity of a (sub)term.
newtype CyclomaticComplexity = CyclomaticComplexity Int
deriving (Enum, Eq, Num, Ord, Show)
-- | Compute the cyclomatic complexity of a (sub)term, measured as the number places where control exits scope, e.g. returns and yields.
--
-- TODO: Explicit returns at the end of methods should only count once.
-- TODO: Anonymous functions should not increase parent scopes complexity.
-- TODO: Inner functions should not increase parent scopes complexity.
cyclomaticComplexityAlgebra :: (Declaration.Method :< fs, Statement.Return :< fs, Statement.Yield :< fs, Apply Foldable fs, Apply Functor fs) => FAlgebra (Base (Term (Union fs) a)) CyclomaticComplexity
cyclomaticComplexityAlgebra (In _ union) = case union of
_ | Just Declaration.Method{} <- prj union -> succ (sum union)
_ | Just Statement.Return{} <- prj union -> succ (sum union)
_ | Just Statement.Yield{} <- prj union -> succ (sum union)
_ -> sum union
-- | Compute a 'ByteString' label for a 'Show1'able 'Term'.
--
-- This uses 'liftShowsPrec' to produce the 'ByteString', with the effect that
-- constant fields will be included and parametric fields will not be.
constructorNameAndConstantFields :: Show1 f => TermF f a b -> ByteString
constructorNameAndConstantFields (In _ f) = pack (liftShowsPrec (const (const id)) (const id) 0 f "")
-- | Compute a 'ConstructorLabel' label for a 'Union' of syntax 'Term's.
constructorLabel :: Apply ConstructorName fs => TermF (Union fs) a b -> ConstructorLabel
constructorLabel (In _ u) = ConstructorLabel $ pack (apply (Proxy :: Proxy ConstructorName) constructorName u)
newtype ConstructorLabel = ConstructorLabel ByteString
instance Show ConstructorLabel where
showsPrec _ (ConstructorLabel s) = showString (unpack s)
instance ToJSONFields ConstructorLabel where
toJSONFields (ConstructorLabel s) = [ "category" .= decodeUtf8 s ]
class ConstructorName f where
constructorName :: f a -> String
instance (Generic1 f, GConstructorName (Rep1 f)) => ConstructorName f where
constructorName = gconstructorName . from1
class GConstructorName f where
gconstructorName :: f a -> String
instance GConstructorName f => GConstructorName (M1 D c f) where
gconstructorName = gconstructorName . unM1
instance (GConstructorName f, GConstructorName g) => GConstructorName (f :+: g) where
gconstructorName (L1 l) = gconstructorName l
gconstructorName (R1 r) = gconstructorName r
instance Constructor c => GConstructorName (M1 C c f) where
gconstructorName x = case conName x of
":" -> ""
n -> n

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Comment where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Declaration where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Expression where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DataKinds, DeriveAnyClass, DeriveGeneric #-}
module Data.Syntax.Literal where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass, StandaloneDeriving #-}
module Data.Syntax.Statement where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Type where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic

View File

@ -2,17 +2,15 @@
module Data.Term
( Term(..)
, termIn
, termAnnotation
, termOut
, TermF(..)
, termSize
, extract
, unwrap
, hoistTerm
, hoistTermF
, stripTerm
) where
import Control.Comonad
import Control.Comonad.Cofree.Class
import Data.Aeson
import Data.Bifoldable
import Data.Bifunctor
@ -27,7 +25,14 @@ import Text.Show
-- | A Term with an abstract syntax tree and an annotation.
newtype Term syntax ann = Term { unTerm :: TermF syntax ann (Term syntax ann) }
data TermF syntax ann recur = In { termAnnotation :: ann, termOut :: syntax recur }
termAnnotation :: Term syntax ann -> ann
termAnnotation = termFAnnotation . unTerm
termOut :: Term syntax ann -> syntax (Term syntax ann)
termOut = termFOut . unTerm
data TermF syntax ann recur = In { termFAnnotation :: ann, termFOut :: syntax recur }
deriving (Eq, Foldable, Functor, Show, Traversable)
@ -57,11 +62,6 @@ type instance Base (Term f a) = TermF f a
instance Functor f => Recursive (Term f a) where project = unTerm
instance Functor f => Corecursive (Term f a) where embed = Term
instance Functor f => Comonad (Term f) where
extract = termAnnotation . unTerm
duplicate w = termIn w (fmap duplicate (unwrap w))
extend f = go where go w = termIn (f w) (fmap go (unwrap w))
instance Functor f => Functor (Term f) where
fmap f = go where go = Term . bimap f go . unTerm
@ -71,10 +71,6 @@ instance Foldable f => Foldable (Term f) where
instance Traversable f => Traversable (Term f) where
traverse f = go where go = fmap Term . bitraverse f go . unTerm
instance Functor f => ComonadCofree f (Term f) where
unwrap = termOut . unTerm
{-# INLINE unwrap #-}
instance Eq1 f => Eq1 (Term f) where
liftEq eqA = go where go t1 t2 = liftEq2 eqA go (unTerm t1) (unTerm t2)

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds, DefaultSignatures, GADTs, RankNTypes, TypeOperators, UndecidableInstances #-}
module Algorithm where
module Diffing.Algorithm where
import Control.Applicative (Alternative(..))
import Control.Monad (guard)
@ -41,7 +41,7 @@ type Algorithm term1 term2 result = Freer (AlgorithmF term1 term2 result)
-- | Diff two terms without specifying the algorithm to be used.
diff :: term1 -> term2 -> Algorithm term1 term2 result result
diff a1 a2 = Algorithm.Diff a1 a2 `Then` return
diff a1 a2 = Diffing.Algorithm.Diff a1 a2 `Then` return
-- | Diff a These of terms without specifying the algorithm to be used.
diffThese :: These term1 term2 -> Algorithm term1 term2 result result
@ -77,7 +77,7 @@ byReplacing a1 a2 = Replace a1 a2 `Then` return
instance (Show term1, Show term2) => Show1 (AlgorithmF term1 term2 result) where
liftShowsPrec sp _ d algorithm = case algorithm of
Algorithm.Diff t1 t2 -> showsBinaryWith showsPrec showsPrec "Diff" d t1 t2
Diffing.Algorithm.Diff t1 t2 -> showsBinaryWith showsPrec showsPrec "Diff" d t1 t2
Linear t1 t2 -> showsBinaryWith showsPrec showsPrec "Linear" d t1 t2
RWS as bs -> showsBinaryWith (liftShowsPrec showsPrec showList) (liftShowsPrec showsPrec showList) "RWS" d as bs
Delete t1 -> showsUnaryWith showsPrec "Delete" d t1

View File

@ -1,5 +1,5 @@
{-# LANGUAGE GADTs, DataKinds, RankNTypes, TypeOperators #-}
module RWS
module Diffing.Algorithm.RWS
( rws
, Options(..)
, defaultOptions
@ -32,8 +32,8 @@ import Data.Semigroup hiding (First(..))
import Data.Term as Term
import Data.These
import Data.Traversable
import RWS.FeatureVector
import SES
import Diffing.Algorithm.RWS.FeatureVector
import Diffing.Algorithm.SES
type Label f fields label = forall b. TermF f (Record fields) b -> label
@ -89,7 +89,7 @@ rws canCompare equivalent as bs
--
-- cf §4.2 of RWS-Diff
mostSimilarMatching isEligible tree term = listToMaybe (sortOn (editDistanceUpTo optionsNodeComparisons term . snd) candidates)
where candidates = filter (uncurry isEligible) (snd <$> KdMap.kNearest tree optionsMaxSimilarTerms (rhead (extract term)))
where candidates = filter (uncurry isEligible) (snd <$> KdMap.kNearest tree optionsMaxSimilarTerms (rhead (termAnnotation term)))
data Options = Options
{ optionsLookaheadPlaces :: {-# UNPACK #-} !Int -- ^ How many places ahead should we look for similar terms?
@ -110,7 +110,7 @@ defaultQ = 3
toKdMap :: Functor syntax => [(Int, Term syntax (Record (FeatureVector ': fields)))] -> KdMap.KdMap Double FeatureVector (Int, Term syntax (Record (FeatureVector ': fields)))
toKdMap = KdMap.build unFV . fmap (rhead . extract . snd &&& id)
toKdMap = KdMap.build unFV . fmap (rhead . termAnnotation . snd &&& id)
-- | A `Gram` is a fixed-size view of some portion of a tree, consisting of a `stem` of _p_ labels for parent nodes, and a `base` of _q_ labels of sibling nodes. Collectively, the bag of `Gram`s for each node of a tree (e.g. as computed by `pqGrams`) form a summary of the tree.
data Gram label = Gram { stem :: [Maybe label], base :: [Maybe label] }
@ -128,7 +128,7 @@ defaultFeatureVectorDecorator getLabel = featureVectorDecorator . pqGramDecorato
featureVectorDecorator :: (Foldable f, Functor f, Hashable label) => Term f (Record (Gram label ': fields)) -> Term f (Record (FeatureVector ': fields))
featureVectorDecorator = cata (\ (In (gram :. rest) functor) ->
termIn (foldl' addSubtermVector (unitVector (hash gram)) functor :. rest) functor)
where addSubtermVector v term = addVectors v (rhead (extract term))
where addSubtermVector v term = addVectors v (rhead (termAnnotation term))
-- | Annotates a term with the corresponding p,q-gram at each node.
pqGramDecorator
@ -141,7 +141,7 @@ pqGramDecorator
pqGramDecorator getLabel p q = cata algebra
where
algebra term = let label = getLabel term in
termIn (gram label :. termAnnotation term) (assignParentAndSiblingLabels (termOut term) label)
termIn (gram label :. termFAnnotation term) (assignParentAndSiblingLabels (termFOut term) label)
gram label = Gram (padToSize p []) (padToSize q (pure (Just label)))
assignParentAndSiblingLabels functor label = (`evalState` (replicate (q `div` 2) Nothing <> siblingLabels functor)) (for functor (assignLabels label))
@ -154,7 +154,7 @@ pqGramDecorator getLabel p q = cata algebra
put (drop 1 labels)
pure $! termIn (gram { stem = padToSize p (Just label : stem gram), base = padToSize q labels } :. rest) functor
siblingLabels :: Traversable f => f (Term f (Record (Gram label ': fields))) -> [Maybe label]
siblingLabels = foldMap (base . rhead . extract)
siblingLabels = foldMap (base . rhead . termAnnotation)
padToSize n list = take n (list <> repeat empty)
-- | Test the comparability of two root 'Term's in O(1).
@ -164,7 +164,7 @@ canCompareTerms canCompare t1 t2 = canCompare (unTerm t1) (unTerm t2)
-- | Recursively test the equality of two 'Term's in O(n).
equalTerms :: Eq1 syntax => ComparabilityRelation syntax ann1 ann2 -> Term syntax ann1 -> Term syntax ann2 -> Bool
equalTerms canCompare = go
where go a b = canCompareTerms canCompare a b && liftEq go (termOut (unTerm a)) (termOut (unTerm b))
where go a b = canCompareTerms canCompare a b && liftEq go (termOut a) (termOut b)
-- | Return an edit distance between two terms, up to a certain depth.
@ -176,7 +176,7 @@ editDistanceUpTo m a b = diffCost m (approximateDiff a b)
_ | m <= 0 -> 0
Merge body -> sum (fmap ($ pred m) body)
body -> succ (sum (fmap ($ pred m) body))
approximateDiff a b = maybe (replacing a b) (merge (extract a, extract b)) (galignWith (Just . these deleting inserting approximateDiff) (unwrap a) (unwrap b))
approximateDiff a b = maybe (replacing a b) (merge (termAnnotation a, termAnnotation b)) (galignWith (Just . these deleting inserting approximateDiff) (termOut a) (termOut b))
-- Instances

View File

@ -1,5 +1,5 @@
{-# LANGUAGE BangPatterns, MagicHash #-}
module RWS.FeatureVector
module Diffing.Algorithm.RWS.FeatureVector
( FeatureVector
, unFV
, unitVector

View File

@ -1,5 +1,5 @@
{-# LANGUAGE BangPatterns, GADTs, ImplicitParams, MultiParamTypeClasses, ScopedTypeVariables #-}
module SES
module Diffing.Algorithm.SES
( EditScript
, ses
) where

View File

@ -1,9 +1,9 @@
{-# LANGUAGE DataKinds, GADTs, RankNTypes, ScopedTypeVariables, TypeOperators #-}
module Interpreter
module Diffing.Interpreter
( diffTerms
) where
import Algorithm
import Analysis.Decorator
import Control.Applicative (Alternative(..))
import Control.Monad.Free.Freer
import Data.Align.Generic
@ -11,9 +11,9 @@ import Data.Diff
import Data.Functor.Classes
import Data.Maybe (fromMaybe)
import Data.Record
import Data.Syntax.Algebra
import Data.Term
import RWS
import Diffing.Algorithm
import Diffing.Algorithm.RWS
-- | Diff two à la carte terms recursively.
diffTerms :: (Diffable syntax, Eq1 syntax, Foldable syntax, Functor syntax, GAlign syntax, Show1 syntax, Traversable syntax)
@ -34,7 +34,7 @@ runAlgorithm :: forall syntax fields1 fields2 m result
result
-> m result
runAlgorithm = iterFreerA (\ step yield -> case step of
Algorithm.Diff t1 t2 -> runAlgorithm (algorithmForTerms t1 t2) <|> pure (replacing t1 t2) >>= yield
Diffing.Algorithm.Diff t1 t2 -> runAlgorithm (algorithmForTerms t1 t2) <|> pure (replacing t1 t2) >>= yield
Linear (Term (In ann1 f1)) (Term (In ann2 f2)) -> merge (ann1, ann2) <$> galignWith (runAlgorithm . diffThese) f1 f2 >>= yield
RWS as bs -> traverse (runAlgorithm . diffThese) (rws comparableTerms equivalentTerms as bs) >>= yield
Delete a -> yield (deleting a)

View File

@ -6,13 +6,13 @@ module Language.Go.Assignment
, Term
) where
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import Data.Functor (void)
import Data.List.NonEmpty (some1)
import Data.Record
import Data.Syntax (contextualize, emptyTerm, parseError, handleError, infixContext, makeTerm, makeTerm', makeTerm1)
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import qualified Data.Syntax.Comment as Comment
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Expression as Expression

View File

@ -1,13 +1,13 @@
{-# LANGUAGE DeriveAnyClass #-}
module Language.Go.Syntax where
import Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic
import Data.Functor.Classes.Show.Generic
import Data.Mergeable
import Diffing.Algorithm
import GHC.Generics

View File

@ -1,12 +1,12 @@
{-# LANGUAGE DeriveAnyClass #-}
module Language.Go.Type where
import Algorithm
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic
import Data.Functor.Classes.Show.Generic
import Data.Mergeable
import Diffing.Algorithm
import GHC.Generics
-- | A Bidirectional channel in Go (e.g. `chan`).

View File

@ -6,11 +6,11 @@ module Language.JSON.Assignment
, Term)
where
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import Data.Record
import Data.Syntax (makeTerm, parseError)
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import qualified Data.Syntax.Literal as Literal
import qualified Data.Term as Term
import Data.Union

View File

@ -6,20 +6,20 @@ module Language.Markdown.Assignment
, Language.Markdown.Assignment.Term
) where
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import qualified CMarkGFM
import Data.ByteString (ByteString)
import Data.Functor (void)
import Data.Record
import Data.Syntax (makeTerm)
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import Data.Term as Term (Term(..), TermF(..), termIn)
import Data.Term as Term (Term(..), TermF(..), termFAnnotation, termFOut, termIn)
import qualified Data.Text as Text
import Data.Text.Encoding (encodeUtf8)
import Data.Union
import GHC.Stack
import Language.Markdown as Grammar (Grammar(..))
import Parsing.CMark as Grammar (Grammar(..))
import qualified Language.Markdown.Syntax as Markup
type Syntax =
@ -77,19 +77,19 @@ paragraph = makeTerm <$> symbol Paragraph <*> children (Markup.Paragraph <$> man
list :: Assignment
list = termIn <$> symbol List <*> ((\ (CMarkGFM.LIST CMarkGFM.ListAttributes{..}) -> case listType of
CMarkGFM.BULLET_LIST -> inj . Markup.UnorderedList
CMarkGFM.ORDERED_LIST -> inj . Markup.OrderedList) . termAnnotation . termOut <$> currentNode <*> children (many item))
CMarkGFM.ORDERED_LIST -> inj . Markup.OrderedList) . termFAnnotation . termFOut <$> currentNode <*> children (many item))
item :: Assignment
item = makeTerm <$> symbol Item <*> children (many blockElement)
heading :: Assignment
heading = makeTerm <$> symbol Heading <*> ((\ (CMarkGFM.HEADING level) -> Markup.Heading level) . termAnnotation . termOut <$> currentNode <*> children (many inlineElement) <*> manyTill blockElement (void (symbol Heading) <|> eof))
heading = makeTerm <$> symbol Heading <*> ((\ (CMarkGFM.HEADING level) -> Markup.Heading level) . termFAnnotation . termFOut <$> currentNode <*> children (many inlineElement) <*> manyTill blockElement (void (symbol Heading) <|> eof))
blockQuote :: Assignment
blockQuote = makeTerm <$> symbol BlockQuote <*> children (Markup.BlockQuote <$> many blockElement)
codeBlock :: Assignment
codeBlock = makeTerm <$> symbol CodeBlock <*> ((\ (CMarkGFM.CODE_BLOCK language _) -> Markup.Code (nullText language)) . termAnnotation . termOut <$> currentNode <*> source)
codeBlock = makeTerm <$> symbol CodeBlock <*> ((\ (CMarkGFM.CODE_BLOCK language _) -> Markup.Code (nullText language)) . termFAnnotation . termFOut <$> currentNode <*> source)
thematicBreak :: Assignment
thematicBreak = makeTerm <$> token ThematicBreak <*> pure Markup.ThematicBreak
@ -138,10 +138,10 @@ htmlInline :: Assignment
htmlInline = makeTerm <$> symbol HTMLInline <*> (Markup.HTMLBlock <$> source)
link :: Assignment
link = makeTerm <$> symbol Link <*> ((\ (CMarkGFM.LINK url title) -> Markup.Link (encodeUtf8 url) (nullText title)) . termAnnotation . termOut <$> currentNode) <* advance
link = makeTerm <$> symbol Link <*> ((\ (CMarkGFM.LINK url title) -> Markup.Link (encodeUtf8 url) (nullText title)) . termFAnnotation . termFOut <$> currentNode) <* advance
image :: Assignment
image = makeTerm <$> symbol Image <*> ((\ (CMarkGFM.IMAGE url title) -> Markup.Image (encodeUtf8 url) (nullText title)) . termAnnotation . termOut <$> currentNode) <* advance
image = makeTerm <$> symbol Image <*> ((\ (CMarkGFM.IMAGE url title) -> Markup.Image (encodeUtf8 url) (nullText title)) . termFAnnotation . termFOut <$> currentNode) <* advance
code :: Assignment
code = makeTerm <$> symbol Code <*> (Markup.Code Nothing <$> source)

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Language.Markdown.Syntax where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic

View File

@ -6,14 +6,14 @@ module Language.Python.Assignment
, Term
) where
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import Data.Functor (void)
import Data.List.NonEmpty (some1)
import Data.Maybe (fromMaybe)
import Data.Record
import Data.Syntax (contextualize, emptyTerm, handleError, infixContext, makeTerm, makeTerm', makeTerm1, parseError, postContextualize)
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import qualified Data.Syntax.Comment as Comment
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Expression as Expression

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Language.Python.Syntax where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Ord.Generic

View File

@ -6,14 +6,14 @@ module Language.Ruby.Assignment
, Term
) where
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import Data.Maybe (fromMaybe)
import Data.Record
import Data.Functor (void)
import Data.List.NonEmpty (some1)
import Data.Syntax (contextualize, postContextualize, emptyTerm, parseError, handleError, infixContext, makeTerm, makeTerm', makeTerm1)
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import qualified Data.Syntax.Comment as Comment
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Expression as Expression

View File

@ -6,12 +6,12 @@ module Language.TypeScript.Assignment
, Term
) where
import Assigning.Assignment hiding (Assignment, Error)
import qualified Assigning.Assignment as Assignment
import Data.Maybe (fromMaybe, catMaybes)
import Data.Record
import Data.Syntax (emptyTerm, handleError, parseError, infixContext, makeTerm, makeTerm', makeTerm1, contextualize, postContextualize)
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import qualified Data.Syntax.Comment as Comment
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Expression as Expression

View File

@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Language.TypeScript.Syntax where
import Algorithm
import Diffing.Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic

View File

@ -1,17 +1,17 @@
{-# LANGUAGE DataKinds, TypeOperators #-}
module Language.Markdown
module Parsing.CMark
( Grammar(..)
, cmarkParser
, toGrammar
) where
import CMarkGFM
import qualified Data.AST as A
import Data.Ix
import Data.Range
import Data.Span
import Data.Source
import Data.Term
import qualified Data.Syntax.Assignment as A (AST, Node(..))
import TreeSitter.Language (Symbol(..), SymbolType(..))
data Grammar

View File

@ -1,8 +1,10 @@
{-# LANGUAGE ConstraintKinds, DataKinds, GADTs, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators #-}
module Parser
module Parsing.Parser
( Parser(..)
, SomeParser(..)
, someParser
, ApplyAll
-- À la carte parsers
, goParser
, jsonParser
, markdownParser
@ -11,17 +13,18 @@ module Parser
, typescriptParser
) where
import Assigning.Assignment
import qualified CMarkGFM
import Data.AST
import Data.Functor.Classes (Eq1)
import Data.Kind
import Data.Ix
import Data.Kind
import Data.Language
import Data.Record
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment
import Data.Term
import Data.Union
import Foreign.Ptr
import Language
import qualified Language.Go.Assignment as Go
import qualified Language.JSON.Assignment as JSON
import qualified Language.Markdown.Assignment as Markdown
@ -48,31 +51,31 @@ data Parser term where
MarkdownParser :: Parser (Term (TermF [] CMarkGFM.NodeType) (Node Markdown.Grammar))
-- | 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]) (functors :: [* -> *]) :: Constraint where
ApplyAll (typeclass ': typeclasses) functors = (Apply typeclass functors, ApplyAll typeclasses functors)
ApplyAll '[] functors = ()
type family ApplyAll (typeclasses :: [(* -> *) -> Constraint]) (syntax :: * -> *) :: Constraint where
ApplyAll (typeclass ': typeclasses) syntax = (typeclass syntax, ApplyAll typeclasses syntax)
ApplyAll '[] syntax = ()
-- | A parser for some specific language, producing 'Term's whose syntax satisfies a list of typeclass constraints.
--
-- 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 where
SomeParser :: ApplyAll typeclasses fs => { unSomeParser :: Parser (Term (Union fs) (Record Location)) } -> SomeParser 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:
--
-- > case someParser (Proxy :: Proxy '[Show1]) <$> blobLanguage language of { Just (SomeParser parser) -> runTask (parse parser blob) >>= putStrLn . show ; _ -> return () }
someParser :: ( ApplyAll typeclasses Go.Syntax
, ApplyAll typeclasses JSON.Syntax
, ApplyAll typeclasses Markdown.Syntax
, ApplyAll typeclasses Python.Syntax
, ApplyAll typeclasses Ruby.Syntax
, ApplyAll typeclasses TypeScript.Syntax
someParser :: ( ApplyAll typeclasses (Union Go.Syntax)
, ApplyAll typeclasses (Union JSON.Syntax)
, ApplyAll typeclasses (Union Markdown.Syntax)
, ApplyAll typeclasses (Union Python.Syntax)
, ApplyAll typeclasses (Union Ruby.Syntax)
, ApplyAll typeclasses (Union TypeScript.Syntax)
)
=> proxy typeclasses -- ^ A proxy for the list of typeclasses required, e.g. @(Proxy :: Proxy '[Show1])@.
-> Language -- ^ The 'Language' to select.
-> SomeParser typeclasses -- ^ A 'SomeParser' abstracting the syntax type to be produced.
=> proxy typeclasses -- ^ A proxy for the list of typeclasses required, e.g. @(Proxy :: Proxy '[Show1])@.
-> Language -- ^ The 'Language' to select.
-> SomeParser typeclasses (Record Location) -- ^ A 'SomeParser' abstracting the syntax type to be produced.
someParser _ Go = SomeParser goParser
someParser _ JavaScript = SomeParser typescriptParser
someParser _ JSON = SomeParser jsonParser

View File

@ -1,17 +1,17 @@
{-# LANGUAGE DataKinds, ScopedTypeVariables, TypeOperators #-}
module TreeSitter
module Parsing.TreeSitter
( parseToAST
) where
import Control.Exception
import Control.Monad ((<=<))
import Data.AST (AST, Node(Node))
import Data.Blob
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Functor.Foldable hiding (Nil)
import Data.Range
import Data.Source
import Data.Span
import qualified Data.Syntax.Assignment as A
import Data.Term
import Foreign
import Foreign.Marshal.Array (allocaArray)
@ -20,7 +20,7 @@ import qualified TreeSitter.Node as TS
import qualified TreeSitter.Language as TS
-- | Parse 'Source' with the given 'TS.Language' and return its AST.
parseToAST :: (Bounded grammar, Enum grammar) => Ptr TS.Language -> Blob -> IO (A.AST [] grammar)
parseToAST :: (Bounded grammar, Enum grammar) => Ptr TS.Language -> Blob -> IO (AST [] grammar)
parseToAST language Blob{..} = bracket TS.ts_document_new TS.ts_document_free $ \ document -> do
TS.ts_document_set_language document language
root <- unsafeUseAsCStringLen (sourceBytes blobSource) $ \ (source, len) -> do
@ -32,13 +32,13 @@ parseToAST language Blob{..} = bracket TS.ts_document_new TS.ts_document_free $
anaM toAST root
toAST :: forall grammar . (Bounded grammar, Enum grammar) => TS.Node -> IO (Base (A.AST [] grammar) TS.Node)
toAST :: forall grammar . (Bounded grammar, Enum grammar) => TS.Node -> IO (Base (AST [] grammar) TS.Node)
toAST node@TS.Node{..} = do
let count = fromIntegral nodeChildCount
children <- allocaArray count $ \ childNodesPtr -> do
_ <- with nodeTSNode (\ nodePtr -> TS.ts_node_copy_child_nodes nullPtr nodePtr childNodesPtr (fromIntegral count))
peekArray count childNodesPtr
pure $! In (A.Node (toEnum (min (fromIntegral nodeSymbol) (fromEnum (maxBound :: grammar)))) (nodeRange node) (nodeSpan node)) children
pure $! In (Node (toEnum (min (fromIntegral nodeSymbol) (fromEnum (maxBound :: grammar)))) (nodeRange node) (nodeSpan node)) children
anaM :: (Corecursive t, Monad m, Traversable (Base t)) => (a -> m (Base t a)) -> a -> m t
anaM g = a where a = pure . embed <=< traverse a <=< g

View File

@ -1,4 +1,4 @@
module Renderer.JSON
module Rendering.JSON
( renderJSONDiff
, renderJSONTerm
) where
@ -8,11 +8,11 @@ import Data.Aeson as A hiding (json)
import Data.Blob
import Data.Foldable (toList)
import Data.Functor.Both (Both)
import Data.Language
import qualified Data.Map as Map
import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8)
import GHC.Generics
import Language
--
-- Diffs

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds, GADTs, GeneralizedNewtypeDeriving, MultiParamTypeClasses, StandaloneDeriving, TypeOperators #-}
module Renderer
module Rendering.Renderer
( DiffRenderer(..)
, TermRenderer(..)
, SomeRenderer(..)
@ -10,8 +10,6 @@ module Renderer
, renderToCDiff
, renderToCTerm
, renderToTags
, HasDeclaration
, declarationAlgebra
, Summaries(..)
) where
@ -20,10 +18,10 @@ import Data.ByteString (ByteString)
import qualified Data.Map as Map
import Data.Output
import Data.Text (Text)
import Renderer.JSON as R
import Renderer.SExpression as R
import Renderer.Tag as R
import Renderer.TOC as R
import Rendering.JSON as R
import Rendering.SExpression as R
import Rendering.Tag as R
import Rendering.TOC as R
-- | Specification of renderers for diffs, producing output in the parameter type.
data DiffRenderer output where

View File

@ -1,5 +1,5 @@
{-# LANGUAGE GADTs, RankNTypes, ScopedTypeVariables #-}
module Renderer.SExpression
module Rendering.SExpression
( renderSExpressionDiff
, renderSExpressionTerm
) where

192
src/Rendering/TOC.hs Normal file
View File

@ -0,0 +1,192 @@
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
module Rendering.TOC
( renderToCDiff
, renderToCTerm
, diffTOC
, Summaries(..)
, TOCSummary(..)
, isValidSummary
, getDeclaration
, declaration
, Entry(..)
, tableOfContentsBy
, termTableOfContentsBy
, dedupe
, entrySummary
, toCategoryName
) where
import Analysis.Declaration
import Data.Aeson
import Data.Align (bicrosswalk)
import Data.Bifoldable (bifoldMap)
import Data.Bifunctor (bimap)
import Data.Blob
import Data.ByteString.Lazy (toStrict)
import Data.Diff
import Data.Foldable (fold, foldl')
import Data.Functor.Both hiding (fst, snd)
import Data.Functor.Foldable (cata)
import Data.Function (on)
import Data.Language as Language
import Data.List (sortOn)
import qualified Data.List as List
import qualified Data.Map as Map hiding (null)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Output
import Data.Patch
import Data.Record
import Data.Semigroup ((<>))
import Data.Span
import Data.Term
import qualified Data.Text as T
import GHC.Generics
data Summaries = Summaries { changes, errors :: !(Map.Map T.Text [Value]) }
deriving (Eq, Show)
instance Monoid Summaries where
mempty = Summaries mempty mempty
mappend (Summaries c1 e1) (Summaries c2 e2) = Summaries (Map.unionWith (<>) c1 c2) (Map.unionWith (<>) e1 e2)
instance Output Summaries where
toOutput = toStrict . (<> "\n") . encode
instance ToJSON Summaries where
toJSON Summaries{..} = object [ "changes" .= changes, "errors" .= errors ]
data TOCSummary
= TOCSummary
{ summaryCategoryName :: T.Text
, summaryTermName :: T.Text
, summarySpan :: Span
, summaryChangeType :: T.Text
}
| ErrorSummary { error :: T.Text, errorSpan :: Span, errorLanguage :: Maybe Language }
deriving (Generic, Eq, Show)
instance ToJSON TOCSummary where
toJSON TOCSummary{..} = object [ "changeType" .= summaryChangeType, "category" .= summaryCategoryName, "term" .= summaryTermName, "span" .= summarySpan ]
toJSON ErrorSummary{..} = object [ "error" .= error, "span" .= errorSpan, "language" .= errorLanguage ]
isValidSummary :: TOCSummary -> Bool
isValidSummary ErrorSummary{} = False
isValidSummary _ = True
getDeclaration :: HasField fields (Maybe Declaration) => Record fields -> Maybe Declaration
getDeclaration = getField
-- | Produce the annotations of nodes representing declarations.
declaration :: HasField fields (Maybe Declaration) => TermF f (Record fields) a -> Maybe (Record fields)
declaration (In annotation _) = annotation <$ getDeclaration annotation
-- | An entry in a table of contents.
data Entry a
= Changed { entryPayload :: a } -- ^ An entry for a node containing changes.
| Inserted { entryPayload :: a } -- ^ An entry for a change occurring inside an 'Insert' 'Patch'.
| Deleted { entryPayload :: a } -- ^ An entry for a change occurring inside a 'Delete' 'Patch'.
| Replaced { entryPayload :: a } -- ^ An entry for a change occurring on the insertion side of a 'Replace' 'Patch'.
deriving (Eq, Show)
-- | Compute a table of contents for a diff characterized by a function mapping relevant nodes onto values in Maybe.
tableOfContentsBy :: (Foldable f, Functor f)
=> (forall b. TermF f ann b -> Maybe a) -- ^ A function mapping relevant nodes onto values in Maybe.
-> Diff f ann ann -- ^ The diff to compute the table of contents for.
-> [Entry a] -- ^ A list of entries for relevant changed nodes in the diff.
tableOfContentsBy selector = fromMaybe [] . cata (\ r -> case r of
Patch patch -> (pure . patchEntry <$> bicrosswalk selector selector patch) <> bifoldMap fold fold patch <> Just []
Merge (In (_, ann2) r) -> case (selector (In ann2 r), fold r) of
(Just a, Just entries) -> Just (Changed a : entries)
(_ , entries) -> entries)
where patchEntry = patch Deleted Inserted (const Replaced)
termTableOfContentsBy :: (Foldable f, Functor f)
=> (forall b. TermF f annotation b -> Maybe a)
-> Term f annotation
-> [a]
termTableOfContentsBy selector = cata termAlgebra
where termAlgebra r | Just a <- selector r = a : fold r
| otherwise = fold r
newtype DedupeKey = DedupeKey (Maybe T.Text, Maybe T.Text) deriving (Eq, Ord)
-- Dedupe entries in a final pass. This catches two specific scenarios with
-- different behaviors:
-- 1. Identical entries are in the list.
-- Action: take the first one, drop all subsequent.
-- 2. Two similar entries (defined by a case insensitive comparision of their
-- identifiers) are in the list.
-- Action: Combine them into a single Replaced entry.
dedupe :: forall fields. HasField fields (Maybe Declaration) => [Entry (Record fields)] -> [Entry (Record fields)]
dedupe = let tuples = sortOn fst . Map.elems . snd . foldl' go (0, Map.empty) in (fmap . fmap) snd tuples
where
go :: HasField fields (Maybe Declaration)
=> (Int, Map.Map DedupeKey (Int, Entry (Record fields)))
-> Entry (Record fields)
-> (Int, Map.Map DedupeKey (Int, Entry (Record fields)))
go (index, m) x | Just (_, similar) <- Map.lookup (dedupeKey x) m
= if exactMatch similar x
then (succ index, m)
else
let replacement = Replaced (entryPayload similar)
in (succ index, Map.insert (dedupeKey replacement) (index, replacement) m)
| otherwise = (succ index, Map.insert (dedupeKey x) (index, x) m)
dedupeKey entry = DedupeKey ((fmap toCategoryName . getDeclaration . entryPayload) entry, (fmap (T.toLower . declarationIdentifier) . getDeclaration . entryPayload) entry)
exactMatch = (==) `on` (getDeclaration . entryPayload)
-- | Construct a 'TOCSummary' from an 'Entry'.
entrySummary :: (HasField fields (Maybe Declaration), HasField fields Span) => Entry (Record fields) -> Maybe TOCSummary
entrySummary entry = case entry of
Changed a -> recordSummary "modified" a
Deleted a -> recordSummary "removed" a
Inserted a -> recordSummary "added" a
Replaced a -> recordSummary "modified" a
-- | Construct a 'TOCSummary' from a node annotation and a change type label.
recordSummary :: (HasField fields (Maybe Declaration), HasField fields Span) => T.Text -> Record fields -> Maybe TOCSummary
recordSummary changeText record = case getDeclaration record of
Just (ErrorDeclaration text _ language) -> Just $ ErrorSummary text (getField record) language
Just declaration -> Just $ TOCSummary (toCategoryName declaration) (formatIdentifier declaration) (getField record) changeText
Nothing -> Nothing
where
formatIdentifier (MethodDeclaration identifier _ (Just Language.Go) (Just receiver)) = "(" <> receiver <> ") " <> identifier
formatIdentifier (MethodDeclaration identifier _ _ (Just receiver)) = receiver <> "." <> identifier
formatIdentifier declaration = declarationIdentifier declaration
renderToCDiff :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Both Blob -> Diff f (Record fields) (Record fields) -> Summaries
renderToCDiff blobs = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . diffTOC
where toMap [] = mempty
toMap as = Map.singleton summaryKey (toJSON <$> as)
summaryKey = T.pack $ case runJoin (blobPath <$> blobs) of
(before, after) | null before -> after
| null after -> before
| before == after -> after
| otherwise -> before <> " -> " <> after
diffTOC :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Diff f (Record fields) (Record fields) -> [TOCSummary]
diffTOC = mapMaybe entrySummary . dedupe . tableOfContentsBy declaration
renderToCTerm :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Blob -> Term f (Record fields) -> Summaries
renderToCTerm Blob{..} = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . termToC
where
toMap [] = mempty
toMap as = Map.singleton (T.pack blobPath) (toJSON <$> as)
termToC :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Term f (Record fields) -> [TOCSummary]
termToC = mapMaybe (recordSummary "unchanged") . termTableOfContentsBy declaration
-- The user-facing category name
toCategoryName :: Declaration -> T.Text
toCategoryName declaration = case declaration of
ClassDeclaration{} -> "Class"
FunctionDeclaration{} -> "Function"
MethodDeclaration{} -> "Method"
HeadingDeclaration _ _ _ l -> "Heading " <> T.pack (show l)
ErrorDeclaration{} -> "ParseError"

View File

@ -1,8 +1,9 @@
{-# LANGUAGE DataKinds, MultiParamTypeClasses, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
module Renderer.Tag
module Rendering.Tag
( renderToTags
) where
import Analysis.Declaration
import Data.Aeson
import Data.Blob
import Data.Maybe (mapMaybe)
@ -11,7 +12,7 @@ import Data.Span
import Data.Term
import GHC.Generics
import qualified Data.Text as T
import Renderer.TOC
import Rendering.TOC
-- | Render a 'Term' to a ctags like output (See 'Tag').
renderToTags :: (HasField fields (Maybe Declaration), HasField fields Span, Foldable f, Functor f) => Blob -> Term f (Record fields) -> [Value]

View File

@ -7,27 +7,29 @@ module Semantic
, diffTermPair
) where
import Algorithm (Diffable)
import Analysis.ConstructorName (ConstructorName, constructorLabel)
import Analysis.Declaration (HasDeclaration, declarationAlgebra)
import Control.Exception
import Control.Monad ((<=<))
import Control.Monad ((>=>))
import Control.Monad.Error.Class
import Data.Align.Generic
import Data.Bifoldable
import Data.Blob
import Data.ByteString (ByteString)
import Data.Diff
import Data.Functor.Both as Both
import Data.Functor.Classes
import Data.JSON.Fields
import Data.Output
import Data.Bifoldable
import Data.Record
import Data.Syntax.Algebra
import Data.Term
import Data.Typeable
import Interpreter
import Parser
import Renderer
import Semantic.Task as Task
import Diffing.Algorithm (Diffable)
import Diffing.Interpreter
import Parsing.Parser
import Rendering.Renderer
import Semantic.Stat as Stat
import Semantic.Task as Task
-- This is the primary interface to the Semantic library which provides two
-- major classes of functionality: semantic parsing and diffing of source code
@ -43,24 +45,15 @@ parseBlobs renderer = fmap toOutput . distributeFoldMap (parseBlob renderer) . f
-- | A task to parse a 'Blob' and render the resulting 'Term'.
parseBlob :: TermRenderer output -> Blob -> Task output
parseBlob renderer blob@Blob{ blobLanguage = Just lang } = case renderer of
ToCTermRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[HasDeclaration, Foldable, Functor]) lang ->
parse parser blob >>= decorate (declarationAlgebra blob) >>= render (renderToCTerm blob)
parseBlob renderer blob@Blob{..}
| Just (SomeParser parser) <- someParser (Proxy :: Proxy '[ConstructorName, HasDeclaration, Foldable, Functor, ToJSONFields1]) <$> blobLanguage
= parse parser blob >>= case renderer of
ToCTermRenderer -> decorate (declarationAlgebra blob) >=> render (renderToCTerm blob)
JSONTermRenderer -> decorate constructorLabel >=> render (renderJSONTerm blob)
SExpressionTermRenderer -> decorate constructorLabel . (Nil <$) >=> render renderSExpressionTerm
TagsTermRenderer -> decorate (declarationAlgebra blob) >=> render (renderToTags blob)
JSONTermRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[ConstructorName, Foldable, Functor]) lang ->
parse parser blob >>= decorate constructorLabel >>= render (renderJSONTerm blob)
SExpressionTermRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[ConstructorName, Foldable, Functor]) lang ->
parse parser blob >>= decorate constructorLabel . (Nil <$) >>= render renderSExpressionTerm
TagsTermRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[HasDeclaration, Foldable, Functor]) lang ->
parse parser blob >>= decorate (declarationAlgebra blob) >>= render (renderToTags blob)
parseBlob _ Blob { blobPath = blobPath, blobLanguage = Nothing } = throwError (SomeException (NoLanguageForBlob blobPath))
| otherwise = throwError (SomeException (NoLanguageForBlob blobPath))
data NoLanguageForBlob = NoLanguageForBlob FilePath
deriving (Eq, Exception, Ord, Show, Typeable)
@ -71,34 +64,31 @@ diffBlobPairs renderer = fmap toOutput . distributeFoldMap (diffBlobPair rendere
-- | A task to parse a pair of 'Blob's, diff them, and render the 'Diff'.
diffBlobPair :: DiffRenderer output -> Both Blob -> Task output
diffBlobPair renderer blobs@(Join (Blob { blobLanguage = Just lang }, Blob { blobLanguage = Just _ })) = case renderer of
ToCDiffRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[Diffable, Eq1, Foldable, Functor, GAlign, HasDeclaration, Show1, Traversable]) lang ->
run (\ blob -> parse parser blob >>= decorate (declarationAlgebra blob)) diffTerms (renderToCDiff blobs)
diffBlobPair renderer blobs
| Just (SomeParser parser) <- someParser (Proxy :: Proxy '[ConstructorName, Diffable, Eq1, GAlign, HasDeclaration, Show1, ToJSONFields1, Traversable]) <$> effectiveLanguage
= case renderer of
ToCDiffRenderer -> run (\ blob -> parse parser blob >>= decorate (declarationAlgebra blob)) diffTerms renderToCDiff
JSONDiffRenderer -> run ( parse parser) diffTerms renderJSONDiff
SExpressionDiffRenderer -> run ( parse parser >=> decorate constructorLabel . (Nil <$)) diffTerms (const renderSExpressionDiff)
JSONDiffRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[Diffable, Eq1, Foldable, Functor, GAlign, Show1, Traversable]) lang ->
run (parse parser) diffTerms (renderJSONDiff blobs)
| otherwise = throwError (SomeException (NoLanguageForBlob effectivePath))
where (effectivePath, effectiveLanguage) = case runJoin blobs of
(Blob { blobLanguage = Just lang, blobPath = path }, _) -> (path, Just lang)
(_, Blob { blobLanguage = Just lang, blobPath = path }) -> (path, Just lang)
(Blob { blobPath = path }, _) -> (path, Nothing)
SExpressionDiffRenderer
| SomeParser parser <- someParser (Proxy :: Proxy '[ConstructorName, Diffable, Eq1, Foldable, Functor, GAlign, Show1, Traversable]) lang ->
run (decorate constructorLabel . (Nil <$) <=< parse parser) diffTerms renderSExpressionDiff
where run :: (Foldable syntax, Functor syntax) => (Blob -> Task (Term syntax ann)) -> (Term syntax ann -> Term syntax ann -> Diff syntax ann ann) -> (Diff syntax ann ann -> output) -> Task output
run :: (Foldable syntax, Functor syntax) => (Blob -> Task (Term syntax ann)) -> (Term syntax ann -> Term syntax ann -> Diff syntax ann ann) -> (Both Blob -> Diff syntax ann ann -> output) -> Task output
run parse diff renderer = do
terms <- distributeFor blobs parse
time "diff" languageTag $ do
diff <- runBothWith (diffTermPair blobs diff) terms
writeStat (Stat.count "diff.nodes" (bilength diff) languageTag)
render renderer diff
render (renderer blobs) diff
where
showLanguage = pure . (,) "language" . show
languageTag = let (a, b) = runJoin blobs
in maybe (maybe [] showLanguage (blobLanguage b)) showLanguage (blobLanguage a)
diffBlobPair _ (Join (Blob { blobPath = path, blobLanguage = Nothing }, _)) = throwError (SomeException (NoLanguageForBlob path))
diffBlobPair _ (Join (_, Blob { blobPath = path, blobLanguage = Nothing })) = throwError (SomeException (NoLanguageForBlob path))
-- | A task to diff a pair of 'Term's, producing insertion/deletion 'Patch'es for non-existent 'Blob's.
diffTermPair :: Functor syntax => Both Blob -> Differ syntax ann1 ann2 -> Term syntax ann1 -> Term syntax ann2 -> Task (Diff syntax ann1 ann2)
diffTermPair blobs differ t1 t2 = case runJoin (blobExists <$> blobs) of

View File

@ -1,5 +1,5 @@
{-# LANGUAGE TemplateHaskell #-}
module SemanticCmdLine
module Semantic.CLI
( main
-- Testing
, runDiff
@ -7,21 +7,21 @@ module SemanticCmdLine
) where
import Control.Monad ((<=<))
import Files (languageForFilePath)
import Data.ByteString (ByteString)
import Data.Foldable (find)
import Data.Functor.Both hiding (fst, snd)
import Data.Language
import Data.List (intercalate)
import Data.List.Split (splitWhen)
import Data.Semigroup ((<>))
import Data.Version (showVersion)
import Development.GitRev
import Language
import Options.Applicative hiding (action)
import Renderer
import Rendering.Renderer
import qualified Paths_semantic_diff as Library (version)
import qualified Semantic.Task as Task
import Semantic.IO (languageForFilePath)
import qualified Semantic.Log as Log
import qualified Semantic.Task as Task
import System.IO (Handle, stdin, stdout)
import qualified Semantic (parseBlobs, diffBlobPairs)
import Text.Read

View File

@ -1,5 +1,5 @@
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, DeriveAnyClass, DuplicateRecordFields, ScopedTypeVariables, TupleSections #-}
module Files
module Semantic.IO
( readFile
, isDirectory
, readBlobPairsFromHandle
@ -14,6 +14,7 @@ import Control.Monad.IO.Class
import Data.Aeson
import qualified Data.Blob as Blob
import Data.Functor.Both
import Data.Language
import Data.Maybe
import Data.Semigroup
import Data.Source
@ -21,7 +22,6 @@ import Data.String
import Data.Text
import Data.These
import GHC.Generics
import Language
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Prelude hiding (readFile)
@ -60,7 +60,7 @@ readBlobsFromHandle = fmap toBlobs . readFromHandle
where toBlobs BlobParse{..} = fmap toBlob blobs
readBlobsFromPaths :: MonadIO m => [(FilePath, Maybe Language)] -> m [Blob.Blob]
readBlobsFromPaths = traverse (uncurry Files.readFile)
readBlobsFromPaths = traverse (uncurry Semantic.IO.readFile)
readBlobsFromDir :: MonadIO m => FilePath -> m [Blob.Blob]
readBlobsFromDir path = do

View File

@ -25,12 +25,15 @@ module Semantic.Task
, runTaskWithOptions
) where
import Analysis.Decorator (decoratorWithAlgebra)
import qualified Assigning.Assignment as Assignment
import Control.Exception
import Control.Monad.Error.Class
import Control.Monad.IO.Class
import Control.Parallel.Strategies
import qualified Control.Concurrent.Async as Async
import Control.Monad.Free.Freer
import Data.Algebra (RAlgebra)
import Data.Blob
import Data.Bool
import qualified Data.ByteString as B
@ -39,19 +42,17 @@ import qualified Data.Error as Error
import Data.Foldable (fold, for_)
import Data.Functor.Both as Both hiding (snd)
import Data.Functor.Foldable (cata)
import Data.Language
import Data.Record
import qualified Data.Syntax as Syntax
import Data.Syntax.Algebra (RAlgebra, decoratorWithAlgebra)
import qualified Data.Syntax.Assignment as Assignment
import Data.Term
import Data.Union
import qualified Files
import Language
import Language.Markdown
import Parser
import Parsing.Parser
import Parsing.CMark
import Parsing.TreeSitter
import System.Exit (die)
import System.IO (Handle, stderr)
import TreeSitter
import qualified Semantic.IO as IO
import Semantic.Log
import Semantic.Stat as Stat
import Semantic.Queue
@ -65,7 +66,7 @@ data TaskF output where
WriteStat :: Stat -> TaskF ()
Time :: String -> [(String, String)] -> Task output -> TaskF output
Parse :: Parser term -> Blob -> TaskF term
Decorate :: Functor f => RAlgebra (TermF f (Record fields)) (Term f (Record fields)) field -> Term f (Record fields) -> TaskF (Term f (Record (field ': fields)))
Decorate :: Functor f => RAlgebra (Term f (Record fields)) field -> Term f (Record fields) -> TaskF (Term f (Record (field ': fields)))
Diff :: Differ syntax ann1 ann2 -> Term syntax ann1 -> Term syntax ann2 -> TaskF (Diff syntax ann1 ann2)
Render :: Renderer input output -> input -> TaskF output
Distribute :: Traversable t => t (Task output) -> TaskF (t output)
@ -115,7 +116,7 @@ parse :: Parser term -> Blob -> Task term
parse parser blob = Parse parser blob `Then` return
-- | A 'Task' which decorates a 'Term' with values computed using the supplied 'RAlgebra' function.
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 (Term f (Record fields)) field -> Term f (Record fields) -> Task (Term f (Record (field ': fields)))
decorate algebra term = Decorate algebra term `Then` return
-- | A 'Task' which diffs a pair of terms using the supplied 'Differ' function.
@ -175,10 +176,10 @@ runTaskWithOptions options task = do
where
go :: Task a -> IO (Either SomeException a)
go = iterFreerA (\ task yield -> case task of
ReadBlobs (Left handle) -> (Files.readBlobsFromHandle handle >>= yield) `catchError` (pure . Left . toException)
ReadBlobs (Right paths@[(path, Nothing)]) -> (Files.isDirectory path >>= bool (Files.readBlobsFromPaths paths) (Files.readBlobsFromDir path) >>= yield) `catchError` (pure . Left . toException)
ReadBlobs (Right paths) -> (Files.readBlobsFromPaths paths >>= yield) `catchError` (pure . Left . toException)
ReadBlobPairs source -> (either Files.readBlobPairsFromHandle (traverse (traverse (uncurry Files.readFile))) source >>= yield) `catchError` (pure . Left . toException)
ReadBlobs (Left handle) -> (IO.readBlobsFromHandle handle >>= yield) `catchError` (pure . Left . toException)
ReadBlobs (Right paths@[(path, Nothing)]) -> (IO.isDirectory path >>= bool (IO.readBlobsFromPaths paths) (IO.readBlobsFromDir path) >>= yield) `catchError` (pure . Left . toException)
ReadBlobs (Right paths) -> (IO.readBlobsFromPaths paths >>= yield) `catchError` (pure . Left . toException)
ReadBlobPairs source -> (either IO.readBlobPairsFromHandle (traverse (traverse (uncurry IO.readFile))) source >>= yield) `catchError` (pure . Left . toException)
WriteToOutput destination contents -> either B.hPutStr B.writeFile destination contents >>= yield
WriteLog level message pairs -> queueLogMessage logger level message pairs >>= yield
WriteStat stat -> queue statter stat >>= yield

View File

@ -2,26 +2,26 @@
{-# LANGUAGE DataKinds, MonoLocalBinds, TypeOperators #-}
module Semantic.Util where
import Analysis.Declaration
import Control.Monad.IO.Class
import Data.Blob
import Files
import Data.Record
import Data.Functor.Classes
import Algorithm
import Data.Align.Generic
import Interpreter
import Parser
import Data.Functor.Both
import Data.Term
import Data.Blob
import Data.Diff
import Semantic
import Semantic.Task
import Renderer.TOC
import Data.Functor.Both
import Data.Functor.Classes
import Data.Range
import Data.Record
import Data.Span
import Data.Term
import Diffing.Algorithm
import Diffing.Interpreter
import Parsing.Parser
import Semantic
import Semantic.IO as IO
import Semantic.Task
file :: MonadIO m => FilePath -> m Blob
file path = Files.readFile path (languageForFilePath path)
file path = IO.readFile path (languageForFilePath path)
diffWithParser :: (HasField fields Data.Span.Span,
HasField fields Range,

View File

@ -1,6 +1,8 @@
{-# LANGUAGE DataKinds #-}
module Data.Syntax.Assignment.Spec where
module Assigning.Assignment.Spec where
import Assigning.Assignment
import Data.AST
import Data.Bifunctor (first)
import Data.ByteString.Char8 as B (ByteString, length, words)
import Data.Ix
@ -8,7 +10,6 @@ import Data.Range
import Data.Semigroup ((<>))
import Data.Source
import Data.Span
import Data.Syntax.Assignment
import Data.Term
import GHC.Stack (getCallStack)
import Prelude hiding (words)

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds #-}
module DiffSpec where
module Data.Diff.Spec where
import Data.Diff
import Data.Functor.Listable (ListableSyntax)

View File

@ -27,12 +27,14 @@ module Data.Functor.Listable
, ListableSyntax
) where
import Analysis.Declaration
import Control.Monad.Free as Free
import Control.Monad.Trans.Free as FreeF
import Data.ByteString (ByteString)
import Data.Char (chr)
import Data.Diff
import Data.Functor.Both
import qualified Data.Language as Language
import Data.List.NonEmpty
import Data.Patch
import Data.Range
@ -49,10 +51,8 @@ import Data.Text as T (Text, pack)
import qualified Data.Text.Encoding as T
import Data.These
import Data.Union
import Renderer.TOC
import RWS
import Diffing.Algorithm.RWS
import Test.LeanCheck
import qualified Language
type Tier a = [a]

View File

@ -1,4 +1,4 @@
module SourceSpec where
module Data.Source.Spec where
import Data.Char (chr)
import Data.Functor.Listable

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds #-}
module TermSpec where
module Data.Term.Spec where
import Data.Functor.Listable
import Data.Term

View File

@ -1,19 +1,19 @@
{-# LANGUAGE DataKinds #-}
module Data.RandomWalkSimilarity.Spec where
module Diffing.Algorithm.RWS.Spec where
import Algorithm
import Analysis.Decorator
import Data.Array.IArray
import Data.Bifunctor
import Data.Diff
import Data.Functor.Listable (ListableSyntax)
import Data.Record
import qualified Data.Syntax as Syntax
import Data.Syntax.Algebra
import Data.Term
import Data.These
import Data.Union
import Interpreter
import RWS
import Diffing.Algorithm
import Diffing.Algorithm.RWS
import Diffing.Interpreter
import Test.Hspec
import Test.Hspec.LeanCheck

View File

@ -1,7 +1,7 @@
module SES.Spec where
module Diffing.Algorithm.SES.Spec where
import Data.These
import SES
import Diffing.Algorithm.SES
import Test.Hspec
import Test.Hspec.LeanCheck

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds #-}
module InterpreterSpec where
module Diffing.Interpreter.Spec where
import Data.Diff
import Data.Functor.Both
@ -9,7 +9,7 @@ import Data.Record
import qualified Data.Syntax as Syntax
import Data.Term
import Data.Union
import Interpreter
import Diffing.Interpreter
import Test.Hspec (Spec, describe, it, parallel)
import Test.Hspec.Expectations.Pretty
import Test.Hspec.LeanCheck

View File

@ -1,5 +1,5 @@
{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving, OverloadedStrings #-}
module IntegrationSpec where
module Integration.Spec where
import qualified Data.ByteString as B
import Data.Foldable (find, traverse_)

View File

@ -1,7 +1,8 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DataKinds, TypeOperators #-}
module TOCSpec where
module Rendering.TOC.Spec where
import Analysis.Decorator (constructorNameAndConstantFields)
import Analysis.Declaration
import Data.Aeson
import Data.Bifunctor
import Data.Blob
@ -10,6 +11,7 @@ import Data.Diff
import Data.Functor.Both
import Data.Functor.Foldable (cata)
import Data.Functor.Listable
import Data.Language
import Data.Maybe (fromMaybe, isJust)
import Data.Monoid (Last(..))
import Data.Output
@ -19,7 +21,6 @@ import Data.Record
import Data.Semigroup ((<>))
import Data.Source
import Data.Span
import Data.Syntax.Algebra (constructorNameAndConstantFields)
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Declaration as Declaration
import Data.Term
@ -27,11 +28,11 @@ import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Data.These
import Data.Union
import Interpreter
import Language
import Diffing.Interpreter
import Parsing.Parser
import Prelude hiding (readFile)
import Renderer
import Renderer.TOC
import Rendering.Renderer
import Rendering.TOC
import Semantic
import Semantic.Task
import Semantic.Util
@ -40,7 +41,6 @@ import Test.Hspec (Spec, describe, it, parallel)
import Test.Hspec.Expectations.Pretty
import Test.Hspec.LeanCheck
import Test.LeanCheck
import Parser
spec :: Spec
spec = parallel $ do
@ -49,10 +49,10 @@ spec = parallel $ do
\ diff -> tableOfContentsBy (const Nothing :: a -> Maybe ()) (diff :: Diff ListableSyntax () ()) `shouldBe` []
prop "produces no entries for identity diffs" $
\ term -> tableOfContentsBy (Just . termAnnotation) (diffTerms term (term :: Term ListableSyntax (Record '[Range, Span]))) `shouldBe` []
\ term -> tableOfContentsBy (Just . termFAnnotation) (diffTerms term (term :: Term ListableSyntax (Record '[Range, Span]))) `shouldBe` []
prop "produces inserted/deleted/replaced entries for relevant nodes within patches" $
\ p -> tableOfContentsBy (Just . termAnnotation) (patch deleting inserting replacing p)
\ p -> tableOfContentsBy (Just . termFAnnotation) (patch deleting inserting replacing p)
`shouldBe`
patch (fmap Deleted) (fmap Inserted) (\ as bs -> Replaced (head bs) : fmap Deleted (tail as) <> fmap Inserted (tail bs)) (bimap (foldMap pure) (foldMap pure) (p :: Patch (Term ListableSyntax Int) (Term ListableSyntax Int)))
@ -224,17 +224,17 @@ emptyInfo = Range 0 0 :. Span (Pos 0 0) (Pos 0 0) :. Nil
-- Filter tiers for terms that we consider "meaniningful" in TOC summaries.
isMeaningfulTerm :: Term ListableSyntax a -> Bool
isMeaningfulTerm a
| Just (_:_) <- prj (termOut (unTerm a)) = False
| Just [] <- prj (termOut (unTerm a)) = False
| otherwise = True
| Just (_:_) <- prj (termOut a) = False
| Just [] <- prj (termOut a) = False
| otherwise = True
-- Filter tiers for terms if the Syntax is a Method or a Function.
isMethodOrFunction :: Term' -> Bool
isMethodOrFunction a
| Just Declaration.Method{} <- prj (termOut (unTerm a)) = True
| Just Declaration.Function{} <- prj (termOut (unTerm a)) = True
| any isJust (foldMap ((:[]) . rhead) a) = True
| otherwise = False
| Just Declaration.Method{} <- prj (termOut a) = True
| Just Declaration.Function{} <- prj (termOut a) = True
| any isJust (foldMap ((:[]) . rhead) a) = True
| otherwise = False
blobsForPaths :: Both FilePath -> IO (Both Blob)
blobsForPaths = traverse (readFile . ("test/fixtures/toc/" <>))

View File

@ -1,15 +1,15 @@
{-# LANGUAGE DuplicateRecordFields #-}
module SemanticCmdLineSpec where
module Semantic.CLI.Spec where
import Control.Monad (when)
import Data.ByteString (ByteString)
import Data.Foldable (for_)
import Data.Functor.Both
import Data.Language
import Data.Semigroup ((<>))
import Language
import Renderer
import Rendering.Renderer
import Semantic.CLI
import Semantic.Task
import SemanticCmdLine
import System.IO (Handle)
import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall)
import Test.Hspec.Expectations.Pretty

View File

@ -1,12 +1,12 @@
module CommandSpec where
module Semantic.IO.Spec where
import Files
import Data.Blob
import Data.Functor.Both as Both
import Data.Language
import Data.Maybe
import Data.String
import Language
import Prelude hiding (readFile)
import Semantic.IO
import System.Exit (ExitCode(..))
import System.IO (IOMode(..), openFile)
import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall)

View File

@ -1,13 +1,13 @@
module SemanticSpec where
module Semantic.Spec where
import Data.Blob
import Data.Diff
import Data.Functor (void)
import Data.Functor.Both as Both
import Data.Language
import Data.Patch
import Data.Term
import Language
import Renderer
import Rendering.Renderer
import Semantic
import Semantic.Task
import System.Exit

View File

@ -1,4 +1,4 @@
module Semantic.StatSpec where
module Semantic.Stat.Spec where
import Semantic.Stat
import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall)

View File

@ -1,37 +1,37 @@
module Main where
import qualified CommandSpec
import qualified Assigning.Assignment.Spec
import qualified Data.Diff.Spec
import qualified Data.Functor.Classes.Ord.Generic.Spec
import qualified Data.Mergeable.Spec
import qualified Data.RandomWalkSimilarity.Spec
import qualified Data.Syntax.Assignment.Spec
import qualified DiffSpec
import qualified InterpreterSpec
import qualified SES.Spec
import qualified SourceSpec
import qualified TermSpec
import qualified TOCSpec
import qualified IntegrationSpec
import qualified SemanticCmdLineSpec
import qualified SemanticSpec
import qualified Semantic.StatSpec
import qualified Data.Source.Spec
import qualified Data.Term.Spec
import qualified Diffing.Algorithm.RWS.Spec
import qualified Diffing.Algorithm.SES.Spec
import qualified Diffing.Interpreter.Spec
import qualified Integration.Spec
import qualified Rendering.TOC.Spec
import qualified Semantic.Spec
import qualified Semantic.CLI.Spec
import qualified Semantic.IO.Spec
import qualified Semantic.Stat.Spec
import Test.Hspec
main :: IO ()
main = hspec $ do
describe "Semantic.Stat" Semantic.StatSpec.spec
describe "Semantic.Stat" Semantic.Stat.Spec.spec
parallel $ do
describe "Command" CommandSpec.spec
describe "Assigning.Assignment" Assigning.Assignment.Spec.spec
describe "Data.Diff" Data.Diff.Spec.spec
describe "Data.Functor.Classes.Ord.Generic" Data.Functor.Classes.Ord.Generic.Spec.spec
describe "Data.Mergeable" Data.Mergeable.Spec.spec
describe "Data.RandomWalkSimilarity" Data.RandomWalkSimilarity.Spec.spec
describe "Data.Syntax.Assignment" Data.Syntax.Assignment.Spec.spec
describe "Diff" DiffSpec.spec
describe "Interpreter" InterpreterSpec.spec
describe "SES" SES.Spec.spec
describe "Source" SourceSpec.spec
describe "Term" TermSpec.spec
describe "Semantic" SemanticSpec.spec
describe "SemanticCmdLine" SemanticCmdLineSpec.spec
describe "TOC" TOCSpec.spec
describe "Integration" IntegrationSpec.spec
describe "Data.Source" Data.Source.Spec.spec
describe "Data.Term" Data.Term.Spec.spec
describe "Diffing.Algorithm.RWS" Diffing.Algorithm.RWS.Spec.spec
describe "Diffing.Algorithm.SES" Diffing.Algorithm.SES.Spec.spec
describe "Diffing.Interpreter" Diffing.Interpreter.Spec.spec
describe "Rendering.TOC" Rendering.TOC.Spec.spec
describe "Semantic" Semantic.Spec.spec
describe "Semantic.CLI" Semantic.CLI.Spec.spec
describe "Semantic.IO" Semantic.IO.Spec.spec
describe "Integration" Integration.Spec.spec

View File

@ -10,11 +10,11 @@ import Control.Exception
import Data.Blob
import qualified Data.ByteString as B
import Data.Functor.Both
import Data.Language
import Data.Maybe (fromMaybe)
import Data.Source
import Language
import Prelude hiding (readFile)
import Renderer
import Rendering.Renderer
import Semantic
import Semantic.Task
import System.FilePath