1
1
mirror of https://github.com/github/semantic.git synced 2024-11-29 02:44:36 +03:00

Merge branch 'master' into better-error-reporting-for-repetitions

This commit is contained in:
Rob Rix 2017-05-01 10:06:01 -04:00 committed by GitHub
commit 7fcd50b512
7 changed files with 164 additions and 258 deletions

View File

@ -58,11 +58,8 @@ deriving instance Show ParseArguments
sExpressionParseTree :: ParseMode -> FilePath -> [FilePath] -> ParseArguments
sExpressionParseTree = ParseArguments (SExpressionParseTreeRenderer TreeOnly)
jsonParseTree :: Bool -> ParseMode -> FilePath -> [FilePath] -> ParseArguments
jsonParseTree = ParseArguments . JSONParseTreeRenderer
jsonIndexParseTree :: Bool -> ParseMode -> FilePath -> [FilePath] -> ParseArguments
jsonIndexParseTree = ParseArguments . JSONIndexParseTreeRenderer
jsonParseTree :: ParseMode -> FilePath -> [FilePath] -> ParseArguments
jsonParseTree = ParseArguments JSONParseTreeRenderer
data ProgramMode = Parse ParseArguments | Diff DiffArguments
deriving Show

View File

@ -1,8 +1,6 @@
{-# LANGUAGE DataKinds, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators #-}
module Data.Record where
import Data.Aeson
import Data.Aeson.Types
import Data.Functor.Listable
import GHC.Show
import Prologue
@ -57,22 +55,6 @@ instance (Show h, Show (Record t)) => Show (Record (h ': t)) where
instance Show (Record '[]) where
showsPrec n Nil = showParen (n > 0) ("Nil" <>)
instance (ToJSON h, ToJSONList (Record t)) => ToJSON (Record (h ': t)) where
toJSON r = toJSONList (toJSONValues r)
instance ToJSON (Record '[]) where
toJSON _ = emptyArray
class ToJSONList t where
toJSONValues :: t -> [Value]
instance (ToJSON h, ToJSONList (Record t)) => ToJSONList (Record (h ': t)) where
toJSONValues (h :. t) = toJSON h : toJSONValues t
instance ToJSONList (Record '[]) where
toJSONValues _ = []
instance (Eq h, Eq (Record t)) => Eq (Record (h ': t)) where
(h1 :. t1) == (h2 :. t2) = h1 == h2 && t1 == t2

View File

@ -10,14 +10,15 @@ module Renderer
, File(..)
) where
import Data.Aeson (ToJSON, Value)
import Data.Aeson (Value, (.=))
import Data.Functor.Both
import Data.Functor.Classes
import Text.Show
import Data.Map as Map hiding (null)
import Data.Record
import Diff
import Info
import Info hiding (Identifier)
import Language.Ruby.Syntax (decoratorWithAlgebra, fToR)
import Prologue
import Renderer.JSON as R
import Renderer.Patch as R
@ -25,7 +26,7 @@ import Renderer.SExpression as R
import Renderer.Split as R
import Renderer.Summary as R
import Renderer.TOC as R
import Source (SourceBlob)
import Source (SourceBlob(..))
import Syntax
import Term
@ -33,7 +34,7 @@ import Term
data DiffRenderer fields output where
SplitRenderer :: (HasField fields Category, HasField fields Range) => DiffRenderer fields File
PatchRenderer :: HasField fields Range => DiffRenderer fields File
JSONDiffRenderer :: (ToJSON (Record fields), HasField fields Category, HasField fields Range) => DiffRenderer fields (Map Text Value)
JSONDiffRenderer :: (ToJSONFields (Record fields), HasField fields Range) => DiffRenderer fields (Map Text Value)
SummaryRenderer :: HasDefaultFields fields => DiffRenderer fields Summaries
SExpressionDiffRenderer :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> DiffRenderer fields ByteString
ToCRenderer :: HasDefaultFields fields => DiffRenderer fields Summaries
@ -53,14 +54,21 @@ runDiffRenderer = foldMap . uncurry . resolveDiffRenderer
data ParseTreeRenderer fields output where
SExpressionParseTreeRenderer :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> ParseTreeRenderer fields ByteString
JSONParseTreeRenderer :: HasDefaultFields fields => Bool -> ParseTreeRenderer fields Value
JSONIndexParseTreeRenderer :: HasDefaultFields fields => Bool -> ParseTreeRenderer fields Value
JSONParseTreeRenderer :: (ToJSONFields (Record fields), HasField fields Range) => ParseTreeRenderer fields Value
resolveParseTreeRenderer :: (Monoid output, StringConv output ByteString) => ParseTreeRenderer fields output -> SourceBlob -> Term (Syntax Text) (Record fields) -> output
resolveParseTreeRenderer renderer blob = case renderer of
SExpressionParseTreeRenderer format -> R.sExpressionParseTree format blob
JSONParseTreeRenderer -> R.jsonFile blob . decoratorWithAlgebra (fToR identifierAlg)
where identifierAlg = fmap Identifier . maybeIdentifier . fmap (fmap unIdentifier)
newtype Identifier = Identifier { unIdentifier :: Text }
deriving (Eq, Show)
instance ToJSONFields Identifier where
toJSONFields (Identifier i) = ["identifier" .= i]
resolveParseTreeRenderer :: (Monoid output, StringConv output ByteString) => ParseTreeRenderer fields output -> (SourceBlob -> Term (Syntax Text) (Record fields) -> output)
resolveParseTreeRenderer renderer = case renderer of
SExpressionParseTreeRenderer format -> R.sExpressionParseTree format
JSONParseTreeRenderer debug -> R.jsonParseTree debug
JSONIndexParseTreeRenderer debug -> R.jsonIndexParseTree debug
runParseTreeRenderer :: (Monoid output, StringConv output ByteString) => ParseTreeRenderer fields output -> [(SourceBlob, Term (Syntax Text) (Record fields))] -> output
runParseTreeRenderer = foldMap . uncurry . resolveParseTreeRenderer
@ -82,8 +90,7 @@ instance Show (DiffRenderer fields output) where
instance Show (ParseTreeRenderer fields output) where
showsPrec d (SExpressionParseTreeRenderer format) = showsUnaryWith showsPrec "SExpressionParseTreeRenderer" d format
showsPrec d (JSONParseTreeRenderer debug) = showsUnaryWith showsPrec "JSONParseTreeRenderer" d debug
showsPrec d (JSONIndexParseTreeRenderer debug) = showsUnaryWith showsPrec "JSONIndexParseTreeRenderer" d debug
showsPrec _ JSONParseTreeRenderer = showString "JSONParseTreeRenderer"
instance Monoid File where
mempty = File mempty

View File

@ -1,20 +1,18 @@
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds, GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeFamilies, TypeOperators #-}
{-# LANGUAGE DataKinds, GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Renderer.JSON
( json
, jsonParseTree
, jsonIndexParseTree
, ParseTreeFile(..)
, jsonFile
, ToJSONFields(..)
) where
import Alignment
import Data.Aeson (ToJSON, toJSON, encode, object, (.=))
import Data.Aeson as A hiding (json)
import Data.Aeson.Types (Pair, emptyArray)
import Data.Aeson.Types (emptyArray)
import Data.Bifunctor.Join
import Data.Functor.Both
import Data.Functor.Foldable hiding (Nil)
import Data.Record
import Data.These
import Data.Vector as Vector hiding (toList)
@ -22,18 +20,16 @@ import Diff
import Info
import Prologue
import qualified Data.Map as Map
import qualified Data.Text as T
import Source
import SplitDiff
import Syntax as S
import Term
--
-- Diffs
--
-- | Render a diff to a string representing its JSON.
json :: (ToJSON (Record fields), HasField fields Category, HasField fields Range) => Both SourceBlob -> Diff (Syntax Text) (Record fields) -> Map Text Value
json :: (ToJSONFields (Record fields), HasField fields Range) => Both SourceBlob -> Diff (Syntax Text) (Record fields) -> Map Text Value
json blobs diff = Map.fromList [
("rows", toJSON (annotateRows (alignDiff (source <$> blobs) diff))),
("oids", toJSON (oid <$> blobs)),
@ -47,17 +43,9 @@ newtype NumberedLine a = NumberedLine (Int, a)
instance StringConv (Map Text Value) ByteString where
strConv _ = toS . (<> "\n") . encode
instance (ToJSON leaf, ToJSON (Record fields), HasField fields Category, HasField fields Range) => ToJSON (NumberedLine (SplitSyntaxDiff leaf fields)) where
toJSON (NumberedLine (n, a)) = object (lineFields n a (getRange a))
toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a (getRange a))
instance ToJSON Category where
toJSON (Other s) = String s
toJSON s = String (toS s)
instance ToJSON Range where
toJSON (Range start end) = A.Array . Vector.fromList $ toJSON <$> [ start, end ]
toEncoding (Range start end) = foldable [ start, end ]
instance ToJSONFields a => ToJSON (NumberedLine a) where
toJSON (NumberedLine (n, a)) = object $ "number" .= n : toJSONFields a
toEncoding (NumberedLine (n, a)) = pairs $ "number" .= n <> mconcat (toJSONFields a)
instance ToJSON a => ToJSON (Join These a) where
toJSON (Join vs) = A.Array . Vector.fromList $ toJSON <$> these pure pure (\ a b -> [ a, b ]) vs
@ -66,124 +54,130 @@ instance ToJSON a => ToJSON (Join These a) where
instance ToJSON a => ToJSON (Join (,) a) where
toJSON (Join (a, b)) = A.Array . Vector.fromList $ toJSON <$> [ a, b ]
instance (ToJSON leaf, ToJSON (Record fields), HasField fields Category, HasField fields Range) => ToJSON (SplitSyntaxDiff leaf fields) where
instance (ToJSONFields a, ToJSONFields (f (Free f a))) => ToJSON (Free f a) where
toJSON splitDiff = case runFree splitDiff of
(Free (info :< syntax)) -> object (termFields info syntax)
(Pure patch) -> object (patchFields patch)
(Free f) -> object (toJSONFields f)
(Pure p) -> object (toJSONFields p)
toEncoding splitDiff = case runFree splitDiff of
(Free (info :< syntax)) -> pairs $ mconcat (termFields info syntax)
(Pure patch) -> pairs $ mconcat (patchFields patch)
(Free f) -> pairs $ mconcat (toJSONFields f)
(Pure p) -> pairs $ mconcat (toJSONFields p)
instance (ToJSON (Record fields), ToJSON leaf, HasField fields Category, HasField fields Range) => ToJSON (SyntaxTerm leaf fields) where
toJSON term |
(info :< syntax) <- runCofree term = object (termFields info syntax)
toEncoding term |
(info :< syntax) <- runCofree term = pairs $ mconcat (termFields info syntax)
instance ToJSONFields (CofreeF f a (Cofree f a)) => ToJSON (Cofree f a) where
toJSON = object . toJSONFields . runCofree
toEncoding = pairs . mconcat . toJSONFields . runCofree
lineFields :: (ToJSON leaf, ToJSON (Record fields), HasField fields Category, HasField fields Range, KeyValue kv) =>
Int ->
SplitSyntaxDiff leaf fields ->
Range ->
[kv]
lineFields n term range = [ "number" .= n
, "terms" .= [ term ]
, "range" .= range
, "hasChanges" .= hasChanges term
]
class ToJSONFields a where
toJSONFields :: KeyValue kv => a -> [kv]
termFields :: (ToJSON recur, KeyValue kv, HasField fields Category, HasField fields Range) =>
Record fields ->
Syntax leaf recur ->
[kv]
termFields info syntax = "range" .= byteRange info : "category" .= Info.category info : syntaxToTermField syntax
instance (ToJSONFields h, ToJSONFields (Record t)) => ToJSONFields (Record (h ': t)) where
toJSONFields (h :. t) = toJSONFields h <> toJSONFields t
patchFields :: (ToJSON (Record fields), ToJSON leaf, KeyValue kv, HasField fields Category, HasField fields Range) =>
SplitPatch (SyntaxTerm leaf fields) ->
[kv]
patchFields patch = case patch of
SplitInsert term -> fields "insert" term
SplitDelete term -> fields "delete" term
SplitReplace term -> fields "replace" term
where
fields kind term |
(info :< syntax) <- runCofree term = "patch" .= T.pack kind : termFields info syntax
instance ToJSONFields (Record '[]) where
toJSONFields _ = []
syntaxToTermField :: (ToJSON recur, KeyValue kv) =>
Syntax leaf recur ->
[kv]
syntaxToTermField syntax = case syntax of
Leaf _ -> []
Indexed c -> childrenFields c
Fixed c -> childrenFields c
S.FunctionCall identifier typeParameters parameters -> [ "identifier" .= identifier ] <> [ "typeArguments" .= typeParameters] <> [ "parameters" .= parameters ]
S.Ternary expression cases -> [ "expression" .= expression ] <> [ "cases" .= cases ]
S.AnonymousFunction callSignature c -> [ "callSignature" .= callSignature ] <> childrenFields c
S.Function identifier callSignature c -> [ "identifier" .= identifier ] <> [ "callSignature" .= callSignature ] <> childrenFields c
S.Assignment assignmentId value -> [ "identifier" .= assignmentId ] <> [ "value" .= value ]
S.OperatorAssignment identifier value -> [ "identifier" .= identifier ] <> [ "value" .= value ]
S.MemberAccess identifier value -> [ "identifier" .= identifier ] <> [ "value" .= value ]
S.MethodCall identifier methodIdentifier typeParameters parameters -> [ "identifier" .= identifier ] <> [ "methodIdentifier" .= methodIdentifier ] <> [ "typeParameters" .= typeParameters ] <> [ "parameters" .= parameters ]
S.Operator syntaxes -> [ "operatorSyntaxes" .= syntaxes ]
S.VarDecl children -> childrenFields children
S.VarAssignment identifier value -> [ "identifier" .= identifier ] <> [ "value" .= value ]
S.SubscriptAccess identifier property -> [ "identifier" .= identifier ] <> [ "property" .= property ]
S.Switch expression cases -> [ "expression" .= expression ] <> [ "cases" .= cases ]
S.Case expression statements -> [ "expression" .= expression ] <> [ "statements" .= statements ]
S.Object ty keyValuePairs -> [ "type" .= ty ] <> childrenFields keyValuePairs
S.Pair a b -> childrenFields [a, b]
S.Comment _ -> []
S.Commented comments child -> childrenFields (comments <> maybeToList child)
S.ParseError c -> childrenFields c
S.For expressions body -> [ "expressions" .= expressions ] <> [ "body" .= body ]
S.DoWhile expression body -> [ "expression" .= expression ] <> [ "body" .= body ]
S.While expression body -> [ "expression" .= expression ] <> [ "body" .= body ]
S.Return expression -> [ "expression" .= expression ]
S.Throw c -> [ "expression" .= c ]
S.Constructor expression -> [ "expression" .= expression ]
S.Try body catchExpression elseExpression finallyExpression -> [ "body" .= body ] <> [ "catchExpression" .= catchExpression ] <> [ "elseExpression" .= elseExpression ] <> [ "finallyExpression" .= finallyExpression ]
S.Array ty c -> [ "type" .= ty ] <> childrenFields c
S.Class identifier superclass definitions -> [ "identifier" .= identifier ] <> [ "superclass" .= superclass ] <> [ "definitions" .= definitions ]
S.Method clauses identifier receiver callSignature definitions -> [ "clauses" .= clauses ] <> [ "identifier" .= identifier ] <> [ "receiver" .= receiver ] <> [ "callSignature" .= callSignature ] <> [ "definitions" .= definitions ]
S.If expression clauses -> [ "expression" .= expression ] <> childrenFields clauses
S.Module identifier definitions -> [ "identifier" .= identifier ] <> [ "definitions" .= definitions ]
S.Namespace identifier definitions -> [ "identifier" .= identifier ] <> [ "definitions" .= definitions ]
S.Interface identifier clauses definitions -> [ "identifier" .= identifier ] <> [ "clauses" .= clauses ] <> [ "definitions" .= definitions ]
S.Import identifier statements -> [ "identifier" .= identifier ] <> [ "statements" .= statements ]
S.Export identifier statements -> [ "identifier" .= identifier ] <> [ "statements" .= statements ]
S.Yield expr -> [ "yieldExpression" .= expr ]
S.Negate expr -> [ "negate" .= expr ]
S.Rescue args expressions -> [ "args" .= args ] <> childrenFields expressions
S.Select cases -> childrenFields cases
S.Go cases -> childrenFields cases
S.Defer cases -> childrenFields cases
S.TypeAssertion a b -> childrenFields [a, b]
S.TypeConversion a b -> childrenFields [a, b]
S.Struct ty fields -> [ "type" .= ty ] <> childrenFields fields
S.Break expr -> [ "expression" .= expr ]
S.Continue expr -> [ "expression" .= expr ]
S.BlockStatement c -> childrenFields c
S.ParameterDecl ty field -> [ "type" .= ty ] <> [ "identifier" .= field ]
S.DefaultCase c -> childrenFields c
S.TypeDecl id ty -> [ "type" .= ty ] <> [ "identifier" .= id ]
S.FieldDecl children -> childrenFields children
S.Ty ty -> [ "type" .= ty ]
S.Send channel expr -> [ "channel" .= channel ] <> [ "expression" .= expr ]
where childrenFields c = [ "children" .= c ]
instance ToJSONFields Range where
toJSONFields Range{..} = ["range" .= [ start, end ]]
instance ToJSONFields Category where
toJSONFields c = ["category" .= case c of { Other s -> s ; _ -> toS c }]
instance ToJSONFields SourceSpan where
toJSONFields sourceSpan = [ "sourceSpan" .= sourceSpan ]
instance ToJSONFields SourceText where
toJSONFields (SourceText t) = [ "sourceText" .= t ]
instance ToJSONFields a => ToJSONFields (Maybe a) where
toJSONFields = maybe [] toJSONFields
instance (ToJSONFields a, ToJSONFields (f (Cofree f a))) => ToJSONFields (Cofree f a) where
toJSONFields = toJSONFields . runCofree
instance (ToJSONFields a, ToJSONFields (f b)) => ToJSONFields (CofreeF f a b) where
toJSONFields (a :< f) = toJSONFields a <> toJSONFields f
instance (ToJSONFields a, ToJSONFields (f (Free f a))) => ToJSONFields (Free f a) where
toJSONFields = toJSONFields . runFree
instance (ToJSONFields a, ToJSONFields (f b)) => ToJSONFields (FreeF f a b) where
toJSONFields (Free f) = toJSONFields f
toJSONFields (Pure a) = toJSONFields a
instance ToJSON a => ToJSONFields (SplitPatch a) where
toJSONFields (SplitInsert a) = [ "insert" .= a ]
toJSONFields (SplitDelete a) = [ "delete" .= a ]
toJSONFields (SplitReplace a) = [ "replace" .= a ]
instance ToJSON recur => ToJSONFields (Syntax leaf recur) where
toJSONFields syntax = case syntax of
Leaf _ -> []
Indexed c -> childrenFields c
Fixed c -> childrenFields c
S.FunctionCall identifier typeParameters parameters -> [ "identifier" .= identifier, "typeArguments" .= typeParameters, "parameters" .= parameters ]
S.Ternary expression cases -> [ "expression" .= expression, "cases" .= cases ]
S.AnonymousFunction callSignature c -> "callSignature" .= callSignature : childrenFields c
S.Function identifier callSignature c -> "identifier" .= identifier : "callSignature" .= callSignature : childrenFields c
S.Assignment assignmentId value -> [ "identifier" .= assignmentId, "value" .= value ]
S.OperatorAssignment identifier value -> [ "identifier" .= identifier, "value" .= value ]
S.MemberAccess identifier value -> [ "identifier" .= identifier, "value" .= value ]
S.MethodCall identifier methodIdentifier typeParameters parameters -> [ "identifier" .= identifier, "methodIdentifier" .= methodIdentifier, "typeParameters" .= typeParameters, "parameters" .= parameters ]
S.Operator syntaxes -> [ "operatorSyntaxes" .= syntaxes ]
S.VarDecl children -> childrenFields children
S.VarAssignment identifier value -> [ "identifier" .= identifier, "value" .= value ]
S.SubscriptAccess identifier property -> [ "identifier" .= identifier, "property" .= property ]
S.Switch expression cases -> [ "expression" .= expression, "cases" .= cases ]
S.Case expression statements -> [ "expression" .= expression, "statements" .= statements ]
S.Object ty keyValuePairs -> "type" .= ty : childrenFields keyValuePairs
S.Pair a b -> childrenFields [a, b]
S.Comment _ -> []
S.Commented comments child -> childrenFields (comments <> maybeToList child)
S.ParseError c -> childrenFields c
S.For expressions body -> [ "expressions" .= expressions, "body" .= body ]
S.DoWhile expression body -> [ "expression" .= expression, "body" .= body ]
S.While expression body -> [ "expression" .= expression, "body" .= body ]
S.Return expression -> [ "expression" .= expression ]
S.Throw c -> [ "expression" .= c ]
S.Constructor expression -> [ "expression" .= expression ]
S.Try body catchExpression elseExpression finallyExpression -> [ "body" .= body, "catchExpression" .= catchExpression, "elseExpression" .= elseExpression, "finallyExpression" .= finallyExpression ]
S.Array ty c -> "type" .= ty : childrenFields c
S.Class identifier superclass definitions -> [ "identifier" .= identifier, "superclass" .= superclass, "definitions" .= definitions ]
S.Method clauses identifier receiver callSignature definitions -> [ "clauses" .= clauses, "identifier" .= identifier, "receiver" .= receiver, "callSignature" .= callSignature, "definitions" .= definitions ]
S.If expression clauses -> "expression" .= expression : childrenFields clauses
S.Module identifier definitions -> [ "identifier" .= identifier, "definitions" .= definitions ]
S.Namespace identifier definitions -> [ "identifier" .= identifier, "definitions" .= definitions ]
S.Interface identifier clauses definitions -> [ "identifier" .= identifier, "clauses" .= clauses, "definitions" .= definitions ]
S.Import identifier statements -> [ "identifier" .= identifier, "statements" .= statements ]
S.Export identifier statements -> [ "identifier" .= identifier, "statements" .= statements ]
S.Yield expr -> [ "yieldExpression" .= expr ]
S.Negate expr -> [ "negate" .= expr ]
S.Rescue args expressions -> "args" .= args : childrenFields expressions
S.Select cases -> childrenFields cases
S.Go cases -> childrenFields cases
S.Defer cases -> childrenFields cases
S.TypeAssertion a b -> childrenFields [a, b]
S.TypeConversion a b -> childrenFields [a, b]
S.Struct ty fields -> "type" .= ty : childrenFields fields
S.Break expr -> [ "expression" .= expr ]
S.Continue expr -> [ "expression" .= expr ]
S.BlockStatement c -> childrenFields c
S.ParameterDecl ty field -> [ "type" .= ty, "identifier" .= field ]
S.DefaultCase c -> childrenFields c
S.TypeDecl id ty -> [ "type" .= ty, "identifier" .= id ]
S.FieldDecl children -> childrenFields children
S.Ty ty -> [ "type" .= ty ]
S.Send channel expr -> [ "channel" .= channel, "expression" .= expr ]
where childrenFields c = [ "children" .= c ]
--
-- Parse Trees
--
data ParseTreeFile = ParseTreeFile { parseTreeFilePath :: FilePath, node :: Rose ParseNode } deriving (Show)
data File a = File { filePath :: FilePath, fileContent :: a }
deriving (Generic, Show)
data Rose a = Rose a [Rose a]
deriving (Eq, Show)
instance ToJSON ParseTreeFile where
toJSON ParseTreeFile{..} = object [ "filePath" .= parseTreeFilePath, "programNode" .= cata algebra node ]
where algebra (RoseF a as) = object $ parseNodeToJSONFields a <> [ "children" .= as ]
instance ToJSON a => ToJSON (File a) where
toJSON File{..} = object [ "filePath" .= filePath, "programNode" .= fileContent ]
instance Monoid Value where
mempty = emptyArray
@ -192,74 +186,5 @@ instance Monoid Value where
instance StringConv Value ByteString where
strConv _ = toS . (<> "\n") . encode
data IndexFile = IndexFile { indexFilePath :: FilePath, nodes :: [ParseNode] } deriving (Show)
instance ToJSON IndexFile where
toJSON IndexFile{..} = object [ "filePath" .= indexFilePath, "programNodes" .= foldMap (singleton . object . parseNodeToJSONFields) nodes ]
where singleton a = [a]
data ParseNode = ParseNode
{ category :: Category
, sourceRange :: Range
, sourceText :: Maybe SourceText
, sourceSpan :: SourceSpan
, identifier :: Maybe Text
}
deriving (Show)
-- | Produce a list of JSON 'Pair's for the fields in a given ParseNode.
parseNodeToJSONFields :: ParseNode -> [Pair]
parseNodeToJSONFields ParseNode{..} =
[ "category" .= (toS category :: Text), "sourceRange" .= sourceRange, "sourceSpan" .= sourceSpan ]
<> [ "sourceText" .= sourceText | isJust sourceText ]
<> [ "identifier" .= identifier | isJust identifier ]
jsonParseTree :: HasDefaultFields fields => Bool -> SourceBlob -> Term (Syntax Text) (Record fields) -> Value
jsonParseTree = jsonParseTree' ParseTreeFile Rose
jsonIndexParseTree :: HasDefaultFields fields => Bool -> SourceBlob -> Term (Syntax Text) (Record fields) -> Value
jsonIndexParseTree = jsonParseTree' IndexFile combine
where combine node siblings = node : Prologue.concat siblings
jsonParseTree' :: (ToJSON root, HasDefaultFields fields) => (FilePath -> a -> root) -> (ParseNode -> [a] -> a) -> Bool -> SourceBlob -> Term (Syntax Text) (Record fields) -> Value
jsonParseTree' constructor combine debug SourceBlob{..} term = toJSON $ constructor path (para algebra term')
where
term' = decorateTerm (parseDecorator debug source) term
algebra (annotation :< syntax) = combine (makeNode annotation (Prologue.fst <$> syntax)) (toList (Prologue.snd <$> syntax))
makeNode :: HasDefaultFields fields => Record (Maybe SourceText ': fields) -> Syntax Text (Term (Syntax Text) (Record (Maybe SourceText ': fields))) -> ParseNode
makeNode (sourceText :. record) syntax = ParseNode (getField record) (getField record) sourceText (getField record) (identifierFor syntax)
-- | Determines the term decorator to use when parsing.
parseDecorator :: (Functor f, HasField fields Range) => Bool -> (Source -> TermDecorator f fields (Maybe SourceText))
parseDecorator True = termSourceTextDecorator
parseDecorator False = const . const Nothing
-- | Returns a Just identifier text if the given Syntax term contains an identifier (leaf) syntax. Otherwise returns Nothing.
identifierFor :: (HasField fields (Maybe SourceText), HasField fields Category, StringConv leaf Text) => Syntax leaf (Term (Syntax leaf) (Record fields)) -> Maybe Text
identifierFor = fmap toS . extractLeafValue . unwrap <=< maybeIdentifier
-- | Decorate a 'Term' using a function to compute the annotation values at every node.
decorateTerm :: (Functor f, HasDefaultFields fields) => TermDecorator f fields field -> Term f (Record fields) -> Term f (Record (field ': fields))
decorateTerm decorator = cata $ \ term -> cofree ((decorator term :. headF term) :< tailF term)
-- | Term decorator extracting the source text for a term.
termSourceTextDecorator :: (Functor f, HasField fields Range) => Source -> TermDecorator f fields (Maybe SourceText)
termSourceTextDecorator source (ann :< _) = Just (SourceText (toText (Source.slice (byteRange ann) source)))
-- | A function computing a value to decorate terms with. This can be used to cache synthesized attributes on terms.
type TermDecorator f fields field = TermF f (Record fields) (Term f (Record (field ': fields))) -> field
newtype Identifier = Identifier Text
deriving (Eq, Show, ToJSON)
data RoseF a b = RoseF a [b]
deriving (Eq, Functor, Show)
type instance Base (Rose a) = RoseF a
instance Recursive (Rose a) where
project (Rose a tree) = RoseF a tree
instance Corecursive (Rose a) where
embed (RoseF a tree) = Rose a tree
jsonFile :: ToJSON a => SourceBlob -> a -> Value
jsonFile SourceBlob{..} = toJSON . File path

View File

@ -92,8 +92,7 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc
parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print parse trees for a commit or paths"))
parseArgumentsParser = Parse
<$> ( ( flag sExpressionParseTree sExpressionParseTree (long "sexpression" <> help "Output s-expression parse trees (default)")
<|> (flag' jsonParseTree (long "json" <> help "Output JSON parse trees") <*> switch (long "debug"))
<|> (flag' jsonIndexParseTree (long "index" <> help "Output JSON parse trees in index format") <*> switch (long "debug")) )
<|> flag' jsonParseTree (long "json" <> help "Output JSON parse trees") )
<*> ( ParsePaths
<$> some (argument str (metavar "FILES..."))
<|> ParseCommit

View File

@ -1,8 +1,6 @@
{-# LANGUAGE DeriveAnyClass #-}
module Syntax where
import Data.Record
import qualified Info
import Data.Aeson
import Data.Functor.Classes
import Data.Functor.Classes.Eq.Generic
@ -119,21 +117,22 @@ extractLeafValue syntax = case syntax of
Leaf a -> Just a
_ -> Nothing
maybeIdentifier :: HasField fields Info.Category => Syntax leaf (Cofree (Syntax leaf) (Record fields)) -> Maybe (Cofree (Syntax leaf) (Record fields))
maybeIdentifier syntax = case syntax of
Assignment f _ -> Just f
Class f _ _ -> Just f
Export f _ -> f
Function f _ _ -> Just f
FunctionCall f _ _ -> Just f
Import f _ -> Just f
Method _ f _ _ _ -> Just f
MethodCall _ f _ _ -> Just f
Module f _ -> Just f
OperatorAssignment f _ -> Just f
SubscriptAccess f _ -> Just f
TypeDecl f _ -> Just f
VarAssignment f _ -> find ((== Info.Identifier) . Info.category . extract) f
maybeIdentifier :: CofreeF (Syntax leaf) a (Maybe leaf) -> Maybe leaf
maybeIdentifier (_ :< syntax) = case syntax of
Leaf f -> Just f
Assignment f _ -> f
Class f _ _ -> f
Export f _ -> join f
Function f _ _ -> f
FunctionCall f _ _ -> f
Import f _ -> f
Method _ f _ _ _ -> f
MethodCall _ f _ _ -> f
Module f _ -> f
OperatorAssignment f _ -> f
SubscriptAccess f _ -> f
TypeDecl f _ -> f
VarAssignment f _ -> asum f
_ -> Nothing
-- Instances

View File

@ -32,19 +32,16 @@ data ParseFixture = ParseFixture
instance Listable ParseFixture where
tiers = cons0 (ParseFixture (sExpressionParseTree pathMode "" []) sExpressionParseTreeOutput)
\/ cons0 (ParseFixture (jsonParseTree False pathMode "" []) jsonParseTreeOutput)
\/ cons0 (ParseFixture (jsonIndexParseTree False pathMode "" []) jsonIndexParseTreeOutput)
\/ cons0 (ParseFixture (jsonParseTree pathMode "" []) jsonParseTreeOutput)
\/ cons0 (ParseFixture (sExpressionParseTree commitMode repo []) "(Program\n (Method\n (Identifier)))")
\/ cons0 (ParseFixture (jsonParseTree False commitMode repo []) "[{\"filePath\":\"methods.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}],\"identifier\":\"foo\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}],\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}}},[]]\n")
\/ cons0 (ParseFixture (jsonIndexParseTree False commitMode repo []) "[{\"programNodes\":[{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},{\"category\":\"Method\",\"identifier\":\"foo\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}},{\"category\":\"Identifier\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}],\"filePath\":\"methods.rb\"},[]]\n")
\/ cons0 (ParseFixture (jsonParseTree commitMode repo []) "[{\"filePath\":\"methods.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"identifier\":{\"category\":\"Identifier\",\"identifier\":\"foo\",\"range\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},\"clauses\":[],\"receiver\":null,\"range\":[0,11],\"callSignature\":[],\"definitions\":[],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}],\"range\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}}},[]]\n")
where
pathMode = ParsePaths ["test/fixtures/ruby/and-or.A.rb"]
commitMode = ParseCommit "2e4144eb8c44f007463ec34cb66353f0041161fe" ["methods.rb"]
sExpressionParseTreeOutput = "(Program\n (Binary\n (Identifier)\n (Other \"and\")\n (Identifier)))"
jsonParseTreeOutput = "[{\"filePath\":\"test/fixtures/ruby/and-or.A.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"Binary\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Other \\\"and\\\"\",\"children\":[],\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"children\":[],\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}}],\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}}},[]]\n"
jsonIndexParseTreeOutput = "[{\"programNodes\":[{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}},{\"category\":\"Binary\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}},{\"category\":\"Identifier\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Other \\\"and\\\"\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"filePath\":\"test/fixtures/ruby/and-or.A.rb\"},[]]\n"
jsonParseTreeOutput = "[{\"filePath\":\"test/fixtures/ruby/and-or.A.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"operatorSyntaxes\":[{\"category\":\"Identifier\",\"identifier\":\"foo\",\"range\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"and\",\"identifier\":\"and\",\"range\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"identifier\":\"bar\",\"range\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"category\":\"Binary\",\"range\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}}],\"range\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}}},[]]\n"
data DiffFixture = DiffFixture
@ -76,8 +73,8 @@ instance Listable DiffFixture where
splitOutput' = "<!DOCTYPE HTML>\n<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body><table class=\"diff\"><colgroup><col width=\"40\"><col><col width=\"40\"><col></colgroup><tr><td class=\"blob-num blob-num-empty empty-cell\"></td><td class=\"blob-code blob-code-empty empty-cell\"></td>\n<td class=\"blob-num blob-num-replacement\">1</td><td class=\"blob-code blob-code-replacement\"><div class=\"patch insert\"><ul class=\"category-program\"><li><ul class=\"category-method\">def <li><span class=\"category-identifier\">foo</span></li>\n</ul></li></ul></div></td>\n\n</tr><tr><td class=\"blob-num blob-num-empty empty-cell\"></td><td class=\"blob-code blob-code-empty empty-cell\"></td>\n<td class=\"blob-num blob-num-replacement\">2</td><td class=\"blob-code blob-code-replacement\"><div class=\"patch insert\"><ul class=\"category-program\"><li><ul class=\"category-method\">end</ul></li>\n</ul></div></td>\n\n</tr><tr><td class=\"blob-num blob-num-empty empty-cell\"></td><td class=\"blob-code blob-code-empty empty-cell\"></td>\n<td class=\"blob-num blob-num-replacement\">3</td><td class=\"blob-code blob-code-replacement\"><div class=\"patch insert\"><ul class=\"category-program\"></ul></div></td>\n\n</tr></table></body></html>\n"
summaryOutput = "{\"changes\":{\"test/fixtures/ruby/method-declaration.A.rb -> test/fixtures/ruby/method-declaration.B.rb\":[{\"span\":{\"replace\":[{\"start\":[1,5],\"end\":[1,8]},{\"start\":[1,5],\"end\":[1,8]}]},\"summary\":\"Replaced the 'foo' identifier with the 'bar' identifier in the 'bar(\226\128\166)' method\"},{\"span\":{\"insert\":{\"start\":[1,9],\"end\":[1,10]}},\"summary\":\"Added the 'a' identifier in the 'bar(\226\128\166)' method\"},{\"span\":{\"insert\":{\"start\":[2,3],\"end\":[2,6]}},\"summary\":\"Added the 'baz' identifier in the 'bar(\226\128\166)' method\"}]},\"errors\":{}}\n"
summaryOutput' = "{\"changes\":{\"methods.rb\":[{\"span\":{\"insert\":{\"start\":[1,1],\"end\":[2,4]}},\"summary\":\"Added the 'foo()' method\"}]},\"errors\":{}}\n"
jsonOutput = "{\"oids\":[\"0000000000000000000000000000000000000000\",\"0000000000000000000000000000000000000000\"],\"paths\":[\"test/fixtures/ruby/method-declaration.A.rb\",\"test/fixtures/ruby/method-declaration.B.rb\"],\"rows\":[[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"patch\":\"replace\",\"range\":[4,7]}],\"range\":[0,8]}],\"range\":[0,8]}],\"hasChanges\":true,\"range\":[0,8],\"number\":1},{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"patch\":\"replace\",\"range\":[4,7]},{\"category\":\"Params\",\"children\":[{\"category\":\"Identifier\",\"range\":[8,9]}],\"patch\":\"insert\",\"range\":[7,11]}],\"range\":[0,11]}],\"range\":[0,11]}],\"hasChanges\":true,\"range\":[0,11],\"number\":1}],[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Params\",\"children\":[],\"patch\":\"insert\",\"range\":[11,13]},{\"category\":\"Identifier\",\"patch\":\"insert\",\"range\":[13,16]}],\"range\":[11,17]}],\"range\":[11,17]}],\"hasChanges\":true,\"range\":[11,17],\"number\":2}],[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[8,11]}],\"range\":[8,12]}],\"hasChanges\":false,\"range\":[8,12],\"number\":2},{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[17,20]}],\"range\":[17,21]}],\"hasChanges\":false,\"range\":[17,21],\"number\":3}],[{\"terms\":[{\"category\":\"Program\",\"children\":[],\"range\":[12,12]}],\"hasChanges\":false,\"range\":[12,12],\"number\":3},{\"terms\":[{\"category\":\"Program\",\"children\":[],\"range\":[21,21]}],\"hasChanges\":false,\"range\":[21,21],\"number\":4}]]}\n"
jsonOutput' = "{\"oids\":[\"0000000000000000000000000000000000000000\",\"ff7bbbe9495f61d9e1e58c597502d152bab1761e\"],\"paths\":[\"methods.rb\",\"methods.rb\"],\"rows\":[[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"range\":[4,7]}],\"range\":[0,8]}],\"patch\":\"insert\",\"range\":[0,8]}],\"hasChanges\":true,\"range\":[0,8],\"number\":1}],[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[8,11]}],\"patch\":\"insert\",\"range\":[8,12]}],\"hasChanges\":true,\"range\":[8,12],\"number\":2}],[{\"terms\":[{\"category\":\"Program\",\"children\":[],\"patch\":\"insert\",\"range\":[12,12]}],\"hasChanges\":true,\"range\":[12,12],\"number\":3}]]}\n"
jsonOutput = "{\"oids\":[\"0000000000000000000000000000000000000000\",\"0000000000000000000000000000000000000000\"],\"paths\":[\"test/fixtures/ruby/method-declaration.A.rb\",\"test/fixtures/ruby/method-declaration.B.rb\"],\"rows\":[[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"replace\":{\"category\":\"Identifier\",\"range\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}}],\"range\":[0,8],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}],\"range\":[0,8],\"number\":1,\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"replace\":{\"category\":\"Identifier\",\"range\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}},{\"insert\":{\"category\":\"Params\",\"children\":[{\"category\":\"Identifier\",\"range\":[8,9],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,10]}}],\"range\":[7,11],\"sourceSpan\":{\"start\":[1,8],\"end\":[2,3]}}}],\"range\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,4]}}],\"range\":[0,11],\"number\":1,\"sourceSpan\":{\"start\":[1,1],\"end\":[4,1]}}],[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"insert\":{\"category\":\"Params\",\"children\":[],\"range\":[11,13],\"sourceSpan\":{\"start\":[1,8],\"end\":[2,3]}}},{\"insert\":{\"category\":\"Identifier\",\"range\":[13,16],\"sourceSpan\":{\"start\":[2,3],\"end\":[2,6]}}}],\"range\":[11,17],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,4]}}],\"range\":[11,17],\"number\":2,\"sourceSpan\":{\"start\":[1,1],\"end\":[4,1]}}],[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[8,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}],\"range\":[8,12],\"number\":2,\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[17,20],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,4]}}],\"range\":[17,21],\"number\":3,\"sourceSpan\":{\"start\":[1,1],\"end\":[4,1]}}],[{\"category\":\"Program\",\"children\":[],\"range\":[12,12],\"number\":3,\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},{\"category\":\"Program\",\"children\":[],\"range\":[21,21],\"number\":4,\"sourceSpan\":{\"start\":[1,1],\"end\":[4,1]}}]]}\n"
jsonOutput' = "{\"oids\":[\"0000000000000000000000000000000000000000\",\"ff7bbbe9495f61d9e1e58c597502d152bab1761e\"],\"paths\":[\"methods.rb\",\"methods.rb\"],\"rows\":[[{\"insert\":{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"range\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}}],\"range\":[0,8],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}],\"range\":[0,8],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},\"number\":1}],[{\"insert\":{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[8,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,4]}}],\"range\":[8,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},\"number\":2}],[{\"insert\":{\"category\":\"Program\",\"children\":[],\"range\":[12,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[3,1]}},\"number\":3}]]}\n"
sExpressionOutput = "(Program\n (Method\n { (Identifier)\n ->(Identifier) }\n {+(Params\n (Identifier))+}\n {+(Identifier)+}))"
sExpressionOutput' = "{+(Program\n (Method\n (Identifier)))+}"
tocOutput = "{\"changes\":{\"test/fixtures/ruby/method-declaration.A.rb -> test/fixtures/ruby/method-declaration.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"}]},\"errors\":{}}\n"