1
1
mirror of https://github.com/github/semantic.git synced 2024-12-23 23:11:50 +03:00

Merge branch 'master' into bump-tree-sitter-python

This commit is contained in:
Rick Winfrey 2017-08-03 14:15:44 -07:00 committed by GitHub
commit 1c72b23cea
79 changed files with 536 additions and 381 deletions

View File

@ -19,3 +19,15 @@ error "use wrap" = free . Free ==> wrap
error "use extract" = headF . runCofree ==> extract
error "use unwrap" = tailF . runCofree ==> unwrap
error "avoid head" = head
where note = "head is partial; consider using Data.Maybe.listToMaybe"
error "avoid tail" = tail
where note = "tail is partial; consider pattern-matching"
error "avoid init" = init
where note = "init is partial; consider pattern-matching"
error "avoid last" = last
where note = "last is partial; consider pattern-matching"

View File

@ -63,7 +63,6 @@ library
, Parser
, Patch
, Paths_semantic_diff
, Prologue
, Renderer
, Renderer.JSON
, Renderer.Patch
@ -87,9 +86,10 @@ library
, async
, bifunctors
, bytestring
, cmark
, cmark-gfm
, comonad
, containers
, deepseq
, directory
, effects
, filepath
@ -104,7 +104,6 @@ library
, mtl
, optparse-applicative
, parallel
, protolude
, recursion-schemes
, semigroups
, split
@ -121,7 +120,7 @@ library
, python
, json
default-language: Haskell2010
default-extensions: DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric, FlexibleContexts, FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards, StrictData
default-extensions: DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric, FlexibleContexts, FlexibleInstances, OverloadedStrings, RecordWildCards, StrictData
ghc-options: -Wall -fno-warn-name-shadowing -O -j
ghc-prof-options: -fprof-auto
@ -134,7 +133,7 @@ executable semantic
build-depends: base
, semantic-diff
default-language: Haskell2010
default-extensions: FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards
default-extensions: FlexibleInstances, OverloadedStrings, RecordWildCards
test-suite test
type: exitcode-stdio-1.0
@ -162,7 +161,10 @@ test-suite test
, base
, bifunctors
, bytestring
, comonad
, deepseq
, filepath
, free
, Glob
, haskell-tree-sitter
, hspec >= 2.4.1
@ -171,7 +173,6 @@ test-suite test
, HUnit
, leancheck
, mtl
, protolude
, containers
, recursion-schemes >= 4.1
, semantic-diff
@ -179,7 +180,7 @@ test-suite test
, these
ghc-options: -threaded -rtsopts -with-rtsopts=-N -j
default-language: Haskell2010
default-extensions: DeriveFunctor, DeriveGeneric, FlexibleContexts, FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards
default-extensions: DeriveFunctor, DeriveGeneric, FlexibleContexts, FlexibleInstances, OverloadedStrings, RecordWildCards
custom-setup
setup-depends: base >= 4.8 && < 5

View File

@ -1,16 +1,18 @@
{-# LANGUAGE DataKinds, DefaultSignatures, GADTs, RankNTypes, TypeOperators #-}
module Algorithm where
import Control.Applicative (liftA2)
import Control.Monad (guard)
import Control.Monad.Free.Freer
import Data.Function (on)
import Data.Functor.Both
import Data.Functor.Classes
import Data.Maybe
import Data.These
import Data.Union
import Diff
import GHC.Generics
import Prologue hiding (liftF)
import Term
import Text.Show
-- | A single step in a diffing algorithm, parameterized by the types of terms, diffs, and the result of the applicable algorithm.
data AlgorithmF term diff result where

View File

@ -8,19 +8,29 @@ module Alignment
, modifyJoin
) where
import Prologue hiding (fst, snd)
import Data.Bifunctor (bimap, first, second)
import Control.Arrow ((***))
import Control.Comonad (extract)
import Control.Monad (join)
import Control.Monad.Free
import Data.Align
import Data.Bifunctor.Join
import Data.Foldable (toList)
import Data.Function (on)
import Data.Functor.Both
import Data.List (partition)
import Data.Maybe (fromJust)
import Data.Functor.Foldable
import Data.Functor.Identity
import Data.List (partition, sortBy)
import Data.Maybe (catMaybes, fromJust, listToMaybe)
import Data.Range
import Data.Semigroup ((<>))
import Data.Source
import Data.Record
import Data.These
import Diff
import Info
import Patch
import Prelude hiding (fst, snd)
import SplitDiff
import Term
@ -30,7 +40,7 @@ numberedRows = countUp (both 1 1)
where countUp _ [] = []
countUp from (row : rows) = numberedLine from row : countUp (nextLineNumbers from row) rows
numberedLine from row = fromJust ((,) <$> modifyJoin (uncurry These) from `applyThese` row)
nextLineNumbers from row = modifyJoin (fromThese identity identity) (succ <$ row) <*> from
nextLineNumbers from row = modifyJoin (fromThese id id) (succ <$ row) <*> from
-- | Determine whether a line contains any patches.
hasChanges :: (Foldable f, Functor f) => SplitDiff f annotation -> Bool
@ -45,7 +55,7 @@ alignPatch :: forall fields f. (Traversable f, HasField fields Range) => Both So
alignPatch sources patch = case patch of
Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term
Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term
Replace term1 term2 -> fmap (pure . SplitReplace) <$> alignWith (fmap (these identity identity const . runJoin) . Join)
Replace term1 term2 -> fmap (pure . SplitReplace) <$> alignWith (fmap (these id id const . runJoin) . Join)
(alignSyntax' this (fst sources) term1)
(alignSyntax' that (snd sources) term2)
where getRange = byteRange . extract
@ -93,7 +103,7 @@ alignBranch getRange children ranges = case intersectingChildren of
(leftRange, rightRange) = splitThese headRanges
alignAsymmetrically range advanceBy = let (line, remaining) = lineAndRemaining asymmetricalChildren range in
line $ alignBranch getRange (remaining <> symmetricalChildren <> nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges)
lineAndRemaining _ Nothing = (identity, [])
lineAndRemaining _ Nothing = (id, [])
lineAndRemaining children (Just ranges) = let (intersections, remaining) = alignChildren getRange children ranges in
((:) $ (,) <$> ranges `applyToBoth` (sortBy (compare `on` getRange) <$> intersections), remaining)
@ -106,9 +116,9 @@ alignChildren getRange (first:rest) headRanges
-- It intersects on both sides, so we can just take the first line whole.
(True, True) -> ((<>) <$> toTerms first <*> firstRemaining, restRemaining)
-- It only intersects on the left, so split it up.
(True, False) -> ((<>) <$> toTerms (fromJust l) <*> firstRemaining, maybe identity (:) r restRemaining)
(True, False) -> ((<>) <$> toTerms (fromJust l) <*> firstRemaining, maybe id (:) r restRemaining)
-- It only intersects on the right, so split it up.
(False, True) -> ((<>) <$> toTerms (fromJust r) <*> firstRemaining, maybe identity (:) l restRemaining)
(False, True) -> ((<>) <$> toTerms (fromJust r) <*> firstRemaining, maybe id (:) l restRemaining)
-- It doesnt intersect at all, so skip it and move along.
(False, False) -> (firstRemaining, first:restRemaining)
| otherwise = alignChildren getRange rest headRanges

View File

@ -3,10 +3,11 @@
{-# OPTIONS_GHC -funbox-strict-fields #-}
module Category where
import Prologue
import Control.DeepSeq
import Data.Functor.Listable
import Data.Text (pack)
import Data.Text.Encoding (encodeUtf8)
import Data.Hashable
import Data.Text (Text)
import GHC.Generics
-- | A standardized category of AST node. Used to determine the semantics for
-- | semantic diffing and define comparability of nodes.
@ -243,12 +244,6 @@ data Category
instance Hashable Category
instance (StringConv Category Text) where
strConv _ = pack . show
instance (StringConv Category ByteString) where
strConv _ = encodeUtf8 . show
instance Listable Category where
tiers = cons0 Program
\/ cons0 ParseError

View File

@ -3,10 +3,10 @@ module Data.Align.Generic where
import Control.Monad
import Data.Align
import Data.Functor.Identity
import Data.These
import Data.Union
import GHC.Generics
import Prologue
-- | Functors which can be aligned (structure-unioning-ly zipped). The default implementation will operate generically over the constructors in the aligning type.
class GAlign f where
@ -16,7 +16,7 @@ class GAlign f where
galignWith f a b = to1 <$> galignWith f (from1 a) (from1 b)
galign :: GAlign f => f a -> f b -> Maybe (f (These a b))
galign = galignWith identity
galign = galignWith id
-- 'Data.Align.Align' instances

View File

@ -10,10 +10,12 @@ module Data.Blob
, nullOid
) where
import Data.ByteString.Char8 (ByteString, pack)
import Data.Maybe (isJust)
import Data.Source as Source
import Data.Word
import Language
import Numeric
import Prologue
-- | The source, oid, path, and Maybe BlobKind of a blob.
data Blob = Blob
@ -30,9 +32,9 @@ data BlobKind = PlainBlob Word32 | ExecutableBlob Word32 | SymlinkBlob Word32
deriving (Show, Eq)
modeToDigits :: BlobKind -> ByteString
modeToDigits (PlainBlob mode) = toS $ showOct mode ""
modeToDigits (ExecutableBlob mode) = toS $ showOct mode ""
modeToDigits (SymlinkBlob mode) = toS $ showOct mode ""
modeToDigits (PlainBlob mode) = pack $ showOct mode ""
modeToDigits (ExecutableBlob mode) = pack $ showOct mode ""
modeToDigits (SymlinkBlob mode) = pack $ showOct mode ""
-- | The default plain blob mode
defaultPlainBlob :: BlobKind

View File

@ -1,9 +1,11 @@
{-# OPTIONS_GHC -fno-warn-orphans -funbox-strict-fields #-}
module Data.Functor.Both (Both, both, runBothWith, fst, snd, module X) where
import Control.DeepSeq
import Data.Bifunctor.Join as X
import Prologue hiding (fst, snd)
import qualified Prologue
import Data.Semigroup
import Prelude hiding (fst, snd)
import qualified Prelude
-- | A computation over both sides of a pair.
type Both a = Join (,) a
@ -18,11 +20,11 @@ runBothWith f = uncurry f . runJoin
-- | Runs the left side of a `Both`.
fst :: Both a -> a
fst = Prologue.fst . runJoin
fst = Prelude.fst . runJoin
-- | Runs the right side of a `Both`.
snd :: Both a -> a
snd = Prologue.snd . runJoin
snd = Prelude.snd . runJoin
instance (Semigroup a, Monoid a) => Monoid (Join (,) a) where
mempty = pure mempty

View File

@ -8,7 +8,6 @@ module Data.Functor.Classes.Eq.Generic
import Control.Comonad.Cofree as Cofree
import Data.Functor.Classes
import GHC.Generics
import Prologue
-- | Generically-derivable lifting of the 'Eq' class to unary type constructors.
class GEq1 f where

View File

@ -9,7 +9,6 @@ module Data.Functor.Classes.Show.Generic
import Data.Functor.Classes
import GHC.Generics
import Prologue
import Text.Show
-- | Generically-derivable lifting of the 'Show' class to unary type constructors.
@ -41,7 +40,7 @@ instance Show a => GShow1 (Either a) where gliftShowsPrec = liftShowsPrec
-- Generics
instance GShow1 U1 where
gliftShowsPrec _ _ _ _ = identity
gliftShowsPrec _ _ _ _ = id
instance GShow1 Par1 where
gliftShowsPrec sp _ d (Par1 a) = sp d a

View File

@ -24,9 +24,12 @@ module Data.Functor.Listable
, ofWeight
) where
import Control.Comonad.Cofree as Cofree
import Control.Comonad.Trans.Cofree as CofreeF
import Control.Monad.Free as Free
import Control.Monad.Trans.Free as FreeF
import Data.Bifunctor.Join
import Data.These
import Prologue
import Test.LeanCheck
type Tier a = [a]
@ -114,24 +117,27 @@ instance Listable2 These where
liftTiers2 this that = liftCons1 this This \/ liftCons1 that That \/ liftCons2 this that These
instance Listable1 f => Listable2 (CofreeF f) where
liftTiers2 annotationTiers recurTiers = liftCons2 annotationTiers (liftTiers recurTiers) (:<)
liftTiers2 annotationTiers recurTiers = liftCons2 annotationTiers (liftTiers recurTiers) (CofreeF.:<)
instance (Listable1 f, Listable a) => Listable1 (CofreeF f a) where
liftTiers = liftTiers2 tiers
instance (Functor f, Listable1 f) => Listable1 (Cofree f) where
instance (Functor f, Listable1 f) => Listable1 (Cofree.Cofree f) where
liftTiers annotationTiers = go
where go = liftCons1 (liftTiers2 annotationTiers go) cofree
cofree (a CofreeF.:< f) = a Cofree.:< f
instance Listable1 f => Listable2 (FreeF f) where
liftTiers2 pureTiers recurTiers = liftCons1 pureTiers Pure \/ liftCons1 (liftTiers recurTiers) Free
liftTiers2 pureTiers recurTiers = liftCons1 pureTiers FreeF.Pure \/ liftCons1 (liftTiers recurTiers) FreeF.Free
instance (Listable1 f, Listable a) => Listable1 (FreeF f a) where
liftTiers = liftTiers2 tiers
instance (Functor f, Listable1 f) => Listable1 (Free f) where
instance (Functor f, Listable1 f) => Listable1 (Free.Free f) where
liftTiers pureTiers = go
where go = liftCons1 (liftTiers2 pureTiers go) free
free (FreeF.Free f) = Free.Free f
free (FreeF.Pure a) = Free.Pure a
instance (Listable1 f, Listable a) => Listable (ListableF f a) where
tiers = ListableF `mapT` tiers1

View File

@ -1,10 +1,10 @@
{-# LANGUAGE DefaultSignatures #-}
module Data.Mergeable where
import Control.Applicative
import Data.Functor.Identity
import Data.Mergeable.Generic
import GHC.Generics
import Prologue
-- Classes
@ -25,7 +25,7 @@ class Functor t => Mergeable t where
-- | Sequnce a 'Mergeable' functor by 'merge'ing the 'Alternative' values.
sequenceAlt :: Alternative f => t (f a) -> f (t a)
sequenceAlt = merge identity
sequenceAlt = merge id
-- Instances

View File

@ -1,8 +1,8 @@
{-# LANGUAGE TypeOperators #-}
module Data.Mergeable.Generic where
import Control.Applicative
import GHC.Generics
import Prologue
-- Classes
@ -38,7 +38,7 @@ instance (GMergeable f, GMergeable g) => GMergeable (f :*: g) where
gmerge f (a :*: b) = (:*:) <$> gmerge f a <*> gmerge f b
instance GMergeable [] where
gmerge f (x:xs) = ((:) <$> f x <|> pure identity) <*> gmerge f xs
gmerge f (x:xs) = ((:) <$> f x <|> pure id) <*> gmerge f xs
gmerge _ [] = pure []
instance GMergeable Maybe where

View File

@ -1,6 +1,6 @@
module Data.Output where
import Prologue
import Data.ByteString (ByteString)
class Monoid o => Output o where
toOutput :: o -> ByteString

View File

@ -6,8 +6,9 @@ module Data.Range
, intersectsRange
) where
import Control.DeepSeq
import Data.Semigroup
import Prologue
import GHC.Generics
import Test.LeanCheck
-- | A half-open interval of integers, defined by start & end indices.

View File

@ -1,9 +1,10 @@
{-# LANGUAGE ConstraintKinds, DataKinds, GADTs, KindSignatures, MultiParamTypeClasses, TypeFamilies, TypeOperators #-}
module Data.Record where
import Control.DeepSeq
import Data.Kind
import Data.Functor.Listable
import GHC.Show
import Prologue
import Data.Semigroup
-- | A type-safe, extensible record structure.
-- |

View File

@ -27,22 +27,25 @@ module Data.Source
, ListableByteString(..)
) where
import Control.Arrow ((&&&))
import Data.Array
import qualified Data.ByteString as B
import Data.Char (ord)
import Data.Char (chr, ord)
import Data.List (span)
import Data.Monoid (First(..), Last(..))
import Data.Range
import Data.Semigroup hiding (First(..), Last(..))
import Data.Span
import Data.String (IsString(..))
import qualified Data.Text as T
import Prologue
import qualified Data.Text.Encoding as T
import Test.LeanCheck
-- | The contents of a source file, represented as a 'ByteString'.
newtype Source = Source { sourceBytes :: B.ByteString }
deriving (Eq, IsString, Show)
fromBytes :: ByteString -> Source
fromBytes :: B.ByteString -> Source
fromBytes = Source
@ -67,13 +70,13 @@ totalSpan source = Span (Pos 1 1) (Pos (length ranges) (succ (end lastRange - st
-- En/decoding
-- | Return a 'Source' from a 'ByteString'.
-- | Return a 'Source' from a 'Text'.
fromText :: T.Text -> Source
fromText = Source . encodeUtf8
fromText = Source . T.encodeUtf8
-- | Return the ByteString contained in the 'Source'.
toText :: Source -> Text
toText = decodeUtf8 . sourceBytes
-- | Return the Text contained in the 'Source'.
toText :: Source -> T.Text
toText = T.decodeUtf8 . sourceBytes
-- | Return a 'Source' that contains a slice of the given 'Source'.
@ -148,12 +151,9 @@ instance Listable Source where
newtype ListableByteString = ListableByteString { unListableByteString :: B.ByteString }
instance Listable ListableByteString where
tiers = (ListableByteString . encodeUtf8 . T.pack) `mapT` strings
tiers = (ListableByteString . T.encodeUtf8 . T.pack) `mapT` strings
where strings = foldr ((\\//) . listsOf . toTiers) []
[ ['a'..'z'] <> ['A'..'Z'] <> ['0'..'9']
, [' '..'/'] <> [':'..'@'] <> ['['..'`'] <> ['{'..'~']
, [chr 0x00..chr 0x1f] <> [chr 127] -- Control characters.
, [chr 0xa0..chr 0x24f] ] -- Non-ASCII.
instance StringConv Source ByteString where
strConv _ = sourceBytes

View File

@ -9,10 +9,12 @@ module Data.Span
, emptySpan
) where
import Control.DeepSeq
import Data.Aeson ((.=), (.:))
import qualified Data.Aeson as A
import Data.Hashable (Hashable)
import Data.Semigroup
import Prologue
import GHC.Generics
import Test.LeanCheck
-- | Source position information

View File

@ -3,11 +3,10 @@ module Data.Syntax where
import Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue
import Text.Show
-- Undifferentiated

View File

@ -8,13 +8,15 @@ module Data.Syntax.Algebra
, cyclomaticComplexityAlgebra
) where
import Control.Comonad (extract)
import Data.Bifunctor (second)
import Data.ByteString (ByteString)
import Data.Functor.Foldable
import Data.Record
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Statement as Statement
import Data.Union
import Prologue
import Term
-- | An F-algebra on some carrier functor 'f'.

View File

@ -67,6 +67,9 @@ module Data.Syntax.Assignment
, Node(..)
, nodeLocation
-- Combinators
, Alternative(..)
, optional
, MonadError(..)
, location
, Data.Syntax.Assignment.project
, symbol
@ -85,23 +88,30 @@ module Data.Syntax.Assignment
, makeState
) where
import Control.Arrow
import Control.Applicative
import Control.Comonad.Cofree
import Control.Monad (guard)
import Control.Monad.Error.Class hiding (Error)
import Control.Monad.Free.Freer
import Data.Blob
import Data.ByteString (isSuffixOf)
import Data.ByteString.Char8 (ByteString, pack, unpack)
import Data.Foldable
import Data.Function
import Data.Functor.Classes
import Data.Functor.Foldable as F hiding (Nil)
import qualified Data.IntMap.Lazy as IntMap
import Data.Ix (inRange)
import Data.List.NonEmpty (nonEmpty)
import Data.Maybe
import Data.Record
import Data.Semigroup
import qualified Data.Source as Source (Source, fromBytes, slice, sourceBytes, sourceLines)
import Data.String
import GHC.Stack
import qualified Info
import Prologue hiding (Alt, get, Location, State, state)
import System.Console.ANSI
import Text.Parser.TreeSitter.Language
import Text.Show hiding (show)
-- | Assignment from an AST with some set of 'symbol's onto some other value.
--
@ -192,11 +202,11 @@ formatErrorWithOptions includeSource colourize Blob{..} Error{..}
$ withSGRCode colourize [SetConsoleIntensity BoldIntensity] (showPos (maybe Nothing (const (Just blobPath)) blobKind) errorPos . showString ": ")
. withSGRCode colourize [SetColor Foreground Vivid Red] (showString "error" . showString ": " . showExpectation errorExpected errorActual . showChar '\n')
. (if includeSource
then showString (toS context) . (if "\n" `isSuffixOf` context then identity else showChar '\n')
then showString (unpack context) . (if "\n" `isSuffixOf` context then id else showChar '\n')
. showString (replicate (succ (Info.posColumn errorPos + lineNumberDigits)) ' ') . withSGRCode colourize [SetColor Foreground Vivid Green] (showChar '^' . showChar '\n')
else identity)
else id)
. showString (prettyCallStack callStack) . showChar '\n'
where context = maybe "\n" (Source.sourceBytes . sconcat) (nonEmpty [ Source.fromBytes (toS (showLineNumber i)) <> Source.fromBytes ": " <> l | (i, l) <- zip [1..] (Source.sourceLines blobSource), inRange (Info.posLine errorPos - 2, Info.posLine errorPos) i ])
where context = maybe "\n" (Source.sourceBytes . sconcat) (nonEmpty [ Source.fromBytes (pack (showLineNumber i)) <> Source.fromBytes ": " <> l | (i, l) <- zip [1..] (Source.sourceLines blobSource), inRange (Info.posLine errorPos - 2, Info.posLine errorPos) i ])
showLineNumber n = let s = show n in replicate (lineNumberDigits - length s) ' ' <> s
lineNumberDigits = succ (floor (logBase 10 (fromIntegral (Info.posLine errorPos) :: Double)))
@ -326,9 +336,9 @@ instance Enum grammar => Alternative (Assignment ast grammar) where
(Children l `Then` continueL) <|> (Children r `Then` continueR) = Children (Left <$> l <|> Right <$> r) `Then` either continueL continueR
(Location `Then` continueL) <|> (Location `Then` continueR) = Location `Then` uncurry (<|>) . (continueL &&& continueR)
(Source `Then` continueL) <|> (Source `Then` continueR) = Source `Then` uncurry (<|>) . (continueL &&& continueR)
l <|> r | Just c <- (liftA2 (IntMap.unionWith (<|>)) `on` choices) l r = Choose c (atEnd l <|> atEnd r) `Then` identity
l <|> r | Just c <- (liftA2 (IntMap.unionWith (<|>)) `on` choices) l r = Choose c (atEnd l <|> atEnd r) `Then` id
| otherwise = wrap $ Alt l r
where choices :: Assignment ast grammar a -> Maybe (IntMap (Assignment ast grammar a))
where choices :: Assignment ast grammar a -> Maybe (IntMap.IntMap (Assignment ast grammar a))
choices (Choose choices _ `Then` continue) = Just (continue <$> choices)
choices (Many rule `Then` continue) = ((Many rule `Then` continue) <$) <$> choices rule
choices (Catch during handler `Then` continue) = ((Catch during handler `Then` continue) <$) <$> choices during

View File

@ -3,10 +3,10 @@ module Data.Syntax.Comment where
import Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue
-- | An unnested comment (line or block).
newtype Comment a = Comment { commentContent :: ByteString }

View File

@ -6,7 +6,6 @@ import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue
data Function a = Function { functionName :: !a, functionParameters :: ![a], functionBody :: !a }
deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)

View File

@ -6,7 +6,6 @@ import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue
-- | Typical prefix function application, like `f(x)` in many languages, or `f x` in Haskell.
data Call a = Call { callFunction :: !a, callParams :: ![a], callBlock :: !a }

View File

@ -3,10 +3,11 @@ module Data.Syntax.Literal where
import Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue hiding (Set)
import Prelude hiding (String)
-- Boolean

View File

@ -3,10 +3,10 @@ module Data.Syntax.Markup where
import Algorithm
import Data.Align.Generic
import Data.ByteString (ByteString)
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue hiding (Text)
newtype Document a = Document [a]

View File

@ -6,7 +6,6 @@ import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue
-- | Conditional. This must have an else block, which can be filled with some default value when omitted in the source, e.g. 'pure ()' for C-style if-without-else or 'pure Nothing' for Ruby-style, in both cases assuming some appropriate Applicative context into which the If will be lifted.
data If a = If { ifCondition :: !a, ifThenBody :: !a, ifElseBody :: !a }

View File

@ -6,7 +6,6 @@ import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue hiding (Product)
data Annotation a = Annotation { annotationSubject :: !a, annotationType :: !a }
deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)

View File

@ -2,7 +2,6 @@ module Data.Text.Listable where
import Data.Functor.Listable
import Data.Text
import Prologue
newtype ListableText = ListableText { unListableText :: Text }

View File

@ -6,35 +6,33 @@ module Decorators
) where
import Data.Aeson
import Data.ByteString.Char8 (ByteString, pack, unpack)
import Data.Functor.Classes (Show1 (liftShowsPrec))
import Data.Text.Encoding (decodeUtf8)
import Data.Union
import Data.String
import GHC.Generics
import Prologue
import Renderer.JSON
import Term
import Text.Show
-- | 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 (_ :< f) = toS (liftShowsPrec (const (const identity)) (const identity) 0 f "")
constructorNameAndConstantFields (_ :< f) = pack (liftShowsPrec (const (const id)) (const id) 0 f "")
-- | Compute a 'ConstructorLabel' label for a 'Union' of syntax 'Term's.
constructorLabel :: ConstructorName f => TermF f a b -> ConstructorLabel
constructorLabel (_ :< f) = ConstructorLabel $ toS (constructorName f)
constructorLabel (_ :< f) = ConstructorLabel $ pack (constructorName f)
newtype ConstructorLabel = ConstructorLabel ByteString
instance Show ConstructorLabel where
showsPrec _ (ConstructorLabel s) = showString (toS s)
showsPrec _ (ConstructorLabel s) = showString (unpack s)
instance ToJSONFields ConstructorLabel where
toJSONFields (ConstructorLabel s) = [ "category" .= (toS s :: Text) ]
toJSONFields (ConstructorLabel s) = [ "category" .= decodeUtf8 s ]
class ConstructorName f where

View File

@ -2,7 +2,11 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Diff where
import Prologue
import qualified Control.Comonad.Trans.Cofree as CofreeF
import Control.DeepSeq
import qualified Control.Monad.Free as Free
import qualified Control.Monad.Trans.Free as FreeF
import Data.Bifunctor
import Data.Functor.Both as Both
import Data.Mergeable
import Data.Record
@ -11,8 +15,8 @@ import Syntax
import Term
-- | An annotated series of patches of terms.
type DiffF f annotation = FreeF (TermF f (Both annotation)) (Patch (Term f annotation))
type Diff f annotation = Free (TermF f (Both annotation)) (Patch (Term f annotation))
type DiffF f annotation = FreeF.FreeF (TermF f (Both annotation)) (Patch (Term f annotation))
type Diff f annotation = Free.Free (TermF f (Both annotation)) (Patch (Term f annotation))
type SyntaxDiff fields = Diff Syntax (Record fields)
@ -25,8 +29,8 @@ diffCost = diffSum $ patchSum termSize
-- | Merge a diff using a function to provide the Term (in Maybe, to simplify recovery of the before/after state) for every Patch.
mergeMaybe :: Mergeable f => (Patch (Term f annotation) -> Maybe (Term f annotation)) -> (Both annotation -> annotation) -> Diff f annotation -> Maybe (Term f annotation)
mergeMaybe transform extractAnnotation = iter algebra . fmap transform
where algebra (annotations :< syntax) = cofree . (extractAnnotation annotations :<) <$> sequenceAlt syntax
mergeMaybe transform extractAnnotation = Free.iter algebra . fmap transform
where algebra (annotations CofreeF.:< syntax) = cofree . (extractAnnotation annotations CofreeF.:<) <$> sequenceAlt syntax
-- | Recover the before state of a diff.
beforeTerm :: Mergeable f => Diff f annotation -> Maybe (Term f annotation)
@ -41,12 +45,21 @@ afterTerm = mergeMaybe after Both.snd
-- Typed using Free so as to accommodate Free structures derived from diffs that dont fit into the Diff type synonym.
mapAnnotations :: (Functor f, Functor g)
=> (annotation -> annotation')
-> Free (TermF f (g annotation)) (Patch (Term f annotation))
-> Free (TermF f (g annotation')) (Patch (Term f annotation'))
mapAnnotations f = hoistFree (first (fmap f)) . fmap (fmap (fmap f))
-> Free.Free (TermF f (g annotation)) (Patch (Term f annotation))
-> Free.Free (TermF f (g annotation')) (Patch (Term f annotation'))
mapAnnotations f = Free.hoistFree (first (fmap f)) . fmap (fmap (fmap f))
instance (NFData (f (Diff f a)), NFData (Cofree f a), NFData a, Functor f) => NFData (Diff f a) where
instance (NFData (f (Diff f a)), NFData (Term f a), NFData a, Functor f) => NFData (Diff f a) where
rnf fa = case runFree fa of
Free f -> rnf f `seq` ()
Pure a -> rnf a `seq` ()
FreeF.Free f -> rnf f `seq` ()
FreeF.Pure a -> rnf a `seq` ()
free :: FreeF.FreeF f a (Free.Free f a) -> Free.Free f a
free (FreeF.Free f) = Free.Free f
free (FreeF.Pure a) = Free.Pure a
runFree :: Free.Free f a -> FreeF.FreeF f a (Free.Free f a)
runFree (Free.Free f) = FreeF.Free f
runFree (Free.Pure a) = FreeF.Pure a

View File

@ -9,27 +9,33 @@ module Files
import Control.Exception (catch, IOException)
import Control.Monad.IO.Class
import Data.Aeson
import Data.These
import Data.Functor.Both
import qualified Data.Blob as Blob
import Data.Functor.Both
import Data.Maybe
import Data.Semigroup
import Data.Source
import Data.String
import Data.Text
import Data.These
import GHC.Generics
import Language
import Prologue hiding (readFile)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Prelude (fail)
import Prelude hiding (readFile)
import System.Exit
import System.FilePath
import System.IO (Handle)
import Text.Read
-- | Read a utf8-encoded file to a 'Blob'.
readFile :: forall m. MonadIO m => FilePath -> Maybe Language -> m Blob.Blob
readFile path language = do
raw <- liftIO $ (Just <$> B.readFile path) `catch` (const (pure Nothing) :: IOException -> IO (Maybe ByteString))
raw <- liftIO $ (Just <$> B.readFile path) `catch` (const (pure Nothing) :: IOException -> IO (Maybe B.ByteString))
pure $ fromMaybe (Blob.emptyBlob path) (Blob.sourceBlob path language . fromBytes <$> raw)
-- | Return a language based on a FilePath's extension, or Nothing if extension is not found or not supported.
languageForFilePath :: FilePath -> Maybe Language
languageForFilePath = languageForType . toS . takeExtension
languageForFilePath = languageForType . takeExtension
-- | Read JSON encoded blob pairs from a handle.
readBlobPairsFromHandle :: MonadIO m => Handle -> m [Both Blob.Blob]

View File

@ -7,17 +7,22 @@ module Interpreter
) where
import Algorithm
import Control.Comonad (extract)
import Control.Comonad.Cofree (unwrap)
import Control.Monad.Free (cutoff)
import Control.Monad.Free.Freer hiding (cutoff)
import Data.Align.Generic
import Data.Functor.Both
import Data.Functor.Classes (Eq1)
import RWS
import Data.Hashable (Hashable)
import Data.Maybe (isJust)
import Data.Record
import Data.Text (Text)
import Data.These
import Diff
import Info hiding (Return)
import Patch (inserting, deleting, replacing, patchSum)
import Prologue hiding (lookup)
import RWS
import Syntax as S hiding (Return)
import Term
@ -107,7 +112,7 @@ algorithmWithTerms t1 t2 = case (unwrap t1, unwrap t2) of
-- | Test whether two terms are comparable by their Category.
comparableByCategory :: HasField fields Category => ComparabilityRelation f fields
comparableByCategory a b = category (headF a) == category (headF b)
comparableByCategory (a :< _) (b :< _) = category a == category b
-- | Test whether two terms are comparable by their constructor.
comparableByConstructor :: GAlign f => ComparabilityRelation f fields

View File

@ -1,11 +1,14 @@
{-# LANGUAGE DataKinds, DeriveGeneric, DeriveAnyClass #-}
module Language where
import Control.Comonad
import Control.Comonad.Trans.Cofree hiding (cofree)
import Control.DeepSeq
import Data.Aeson
import Data.Foldable
import Data.Record
import Data.String
import GHC.Generics
import Info
import Prologue
import qualified Syntax as S
import Term

View File

@ -1,9 +1,13 @@
{-# LANGUAGE DataKinds #-}
module Language.Go where
import Control.Comonad
import Control.Comonad.Cofree
import Data.Foldable (toList)
import Data.Maybe
import Data.Source
import Data.Text
import Info
import Prologue
import qualified Syntax as S
import Term
@ -131,4 +135,4 @@ categoryForGoName name = case name of
"method_declaration" -> Method
"import_spec" -> Import
"block" -> ExpressionStatements
s -> Other (toS s)
s -> Other s

View File

@ -6,6 +6,7 @@ module Language.JSON.Syntax
, Term)
where
import Control.Comonad.Cofree (Cofree(..))
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Literal as Literal
import Data.Syntax.Assignment hiding (Assignment, Error)
@ -15,7 +16,6 @@ import qualified Term
import Data.Record
import Data.Union
import GHC.Stack
import Prologue hiding (Location)
type Syntax =
[ Literal.Array
@ -35,7 +35,7 @@ type Assignment = HasCallStack => Assignment.Assignment (AST Grammar) Grammar Te
makeTerm :: (HasCallStack, f :< fs) => a -> f (Term.Term (Union fs) a) -> Term.Term (Union fs) a
makeTerm a f = cofree (a :< inj f)
makeTerm a f = a :< inj f
parseError :: Assignment
parseError = makeTerm <$> symbol ParseError <*> (Syntax.Error [] <$ source)
@ -65,4 +65,3 @@ boolean = makeTerm <$> symbol Grammar.True <*> (Literal.true <$ source)
none :: Assignment
none = makeTerm <$> symbol Null <*> (Literal.Null <$ source)

View File

@ -5,11 +5,11 @@ module Language.Markdown
, toGrammar
) where
import CMark
import Control.Comonad.Cofree
import CMarkGFM
import Data.Source
import qualified Data.Syntax.Assignment as A (AST, Node(..))
import Info
import Prologue hiding (Location)
import Text.Parser.TreeSitter.Language (Symbol(..), SymbolType(..))
data Grammar
@ -33,15 +33,27 @@ data Grammar
| Strong
| Link
| Image
| Strikethrough
| Table
| TableRow
| TableCell
deriving (Bounded, Enum, Eq, Ord, Show)
exts :: [CMarkExtension]
exts = [
extStrikethrough
, extTable
, extAutolink
, extTagfilter
]
cmarkParser :: Source -> A.AST NodeType
cmarkParser source = toTerm (totalRange source) (totalSpan source) $ commonmarkToNode [ optSourcePos, optSafe ] (toText source)
cmarkParser source = toTerm (totalRange source) (totalSpan source) $ commonmarkToNode [ optSourcePos, optSafe ] exts (toText source)
where toTerm :: Range -> Span -> Node -> A.AST NodeType
toTerm within withinSpan (Node position t children) =
let range = maybe within (spanToRangeInLineRanges lineRanges . toSpan) position
span = maybe withinSpan toSpan position
in cofree $ (A.Node t range span) :< (toTerm range span <$> children)
in (A.Node t range span) :< (toTerm range span <$> children)
toSpan PosInfo{..} = Span (Pos startLine startColumn) (Pos (max startLine endLine) (succ (if endLine <= startLine then max startColumn endColumn else endColumn)))
@ -68,6 +80,10 @@ toGrammar EMPH{} = Emphasis
toGrammar STRONG{} = Strong
toGrammar LINK{} = Link
toGrammar IMAGE{} = Image
toGrammar STRIKETHROUGH{} = Strikethrough
toGrammar TABLE{} = Table
toGrammar TABLE_ROW{} = TableRow
toGrammar TABLE_CELL{} = TableCell
instance Symbol Grammar where

View File

@ -6,17 +6,20 @@ module Language.Markdown.Syntax
, Term
) where
import qualified CMark
import Control.Comonad.Cofree (Cofree(..), unwrap)
import qualified CMarkGFM
import Data.ByteString (ByteString)
import Data.Function (on)
import Data.Record
import Data.Syntax.Assignment hiding (Assignment, Error)
import qualified Data.Syntax.Assignment as Assignment
import qualified Data.Syntax.Markup as Markup
import qualified Data.Syntax as Syntax
import qualified Data.Text as Text
import Data.Text.Encoding (encodeUtf8)
import Data.Union
import GHC.Stack
import Language.Markdown as Grammar (Grammar(..))
import Prologue hiding (Location, link, list, section)
import qualified Term
type Syntax =
@ -44,7 +47,7 @@ type Syntax =
]
type Term = Term.Term (Union Syntax) (Record Location)
type Assignment = HasCallStack => Assignment.Assignment (AST CMark.NodeType) Grammar Term
type Assignment = HasCallStack => Assignment.Assignment (AST CMarkGFM.NodeType) Grammar Term
assignment :: Assignment
@ -60,16 +63,16 @@ paragraph :: Assignment
paragraph = makeTerm <$> symbol Paragraph <*> children (Markup.Paragraph <$> many inlineElement)
list :: Assignment
list = (cofree .) . (:<) <$> symbol List <*> (project (\ (Node (CMark.LIST CMark.ListAttributes{..}) _ _ :< _) -> case listType of
CMark.BULLET_LIST -> inj . Markup.UnorderedList
CMark.ORDERED_LIST -> inj . Markup.OrderedList) <*> children (many item))
list = (:<) <$> symbol List <*> (project (\ (Node (CMarkGFM.LIST CMarkGFM.ListAttributes{..}) _ _ Term.:< _) -> case listType of
CMarkGFM.BULLET_LIST -> inj . Markup.UnorderedList
CMarkGFM.ORDERED_LIST -> inj . Markup.OrderedList) <*> children (many item))
item :: Assignment
item = makeTerm <$> symbol Item <*> children (many blockElement)
section :: Assignment
section = makeTerm <$> symbol Heading <*> (heading >>= \ headingTerm -> Markup.Section (level headingTerm) headingTerm <$> while (((<) `on` level) headingTerm) blockElement)
where heading = makeTerm <$> symbol Heading <*> (project (\ (Node (CMark.HEADING level) _ _ :< _) -> Markup.Heading level) <*> children (many inlineElement))
where heading = makeTerm <$> symbol Heading <*> (project (\ (Node (CMarkGFM.HEADING level) _ _ Term.:< _) -> Markup.Heading level) <*> children (many inlineElement))
level term = case term of
_ | Just section <- prj (unwrap term) -> level (Markup.sectionHeading section)
_ | Just heading <- prj (unwrap term) -> Markup.headingLevel heading
@ -79,7 +82,7 @@ blockQuote :: Assignment
blockQuote = makeTerm <$> symbol BlockQuote <*> children (Markup.BlockQuote <$> many blockElement)
codeBlock :: Assignment
codeBlock = makeTerm <$> symbol CodeBlock <*> (project (\ (Node (CMark.CODE_BLOCK language _) _ _ :< _) -> Markup.Code (nullText language)) <*> source)
codeBlock = makeTerm <$> symbol CodeBlock <*> (project (\ (Node (CMarkGFM.CODE_BLOCK language _) _ _ Term.:< _) -> Markup.Code (nullText language)) <*> source)
thematicBreak :: Assignment
thematicBreak = makeTerm <$> symbol ThematicBreak <*> pure Markup.ThematicBreak <* source
@ -106,10 +109,10 @@ htmlInline :: Assignment
htmlInline = makeTerm <$> symbol HTMLInline <*> (Markup.HTMLBlock <$> source)
link :: Assignment
link = makeTerm <$> symbol Link <*> project (\ (Node (CMark.LINK url title) _ _ :< _) -> Markup.Link (toS url) (nullText title)) <* source
link = makeTerm <$> symbol Link <*> project (\ (Node (CMarkGFM.LINK url title) _ _ Term.:< _) -> Markup.Link (encodeUtf8 url) (nullText title)) <* source
image :: Assignment
image = makeTerm <$> symbol Image <*> project (\ (Node (CMark.IMAGE url title) _ _ :< _) -> Markup.Image (toS url) (nullText title)) <* source
image = makeTerm <$> symbol Image <*> project (\ (Node (CMarkGFM.IMAGE url title) _ _ Term.:< _) -> Markup.Image (encodeUtf8 url) (nullText title)) <* source
code :: Assignment
code = makeTerm <$> symbol Code <*> (Markup.Code Nothing <$> source)
@ -124,7 +127,7 @@ softBreak = makeTerm <$> symbol SoftBreak <*> pure Markup.LineBreak <* source
-- Implementation details
makeTerm :: (f :< fs, HasCallStack) => a -> f (Term.Term (Union fs) a) -> Term.Term (Union fs) a
makeTerm a f = cofree $ a :< inj f
makeTerm a f = a :< inj f
nullText :: Text.Text -> Maybe ByteString
nullText text = if Text.null text then Nothing else Just (toS text)
nullText text = if Text.null text then Nothing else Just (encodeUtf8 text)

View File

@ -7,9 +7,11 @@ module Language.Python.Syntax
) where
import Algorithm
import Control.Comonad.Cofree (Cofree(..))
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import Data.Maybe (fromMaybe)
import Data.Record
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
@ -24,7 +26,6 @@ import Data.Union
import GHC.Generics
import GHC.Stack
import Language.Python.Grammar as Grammar
import Prologue hiding (Location)
import qualified Term
type Syntax =
@ -236,7 +237,7 @@ tryStatement = makeTerm <$> symbol TryStatement <*> children (Statement.Try <$>
exceptClause :: Assignment
exceptClause = makeTerm <$> symbol ExceptClause <*> children
(Statement.Catch <$> ((makeTerm <$> location <*> (uncurry Statement.Let . swap <$> ((,) <$> expression <* symbol AnonAs <*> expression) <*> emptyTerm))
(Statement.Catch <$> ((makeTerm <$> location <*> (uncurry (flip Statement.Let) <$> ((,) <$> expression <* symbol AnonAs <*> expression) <*> emptyTerm))
<|> makeTerm <$> location <*> many expression)
<*> expressions)
@ -245,8 +246,8 @@ functionDefinition = (symbol FunctionDefinition >>= \ loc -> children (makeFunc
<|> (symbol AsyncFunctionDefinition >>= \ loc -> children (makeAsyncFunctionDeclaration loc <$> async' <*> expression <*> (symbol Parameters *> children (many expression)) <*> optional (symbol Type *> children expression) <*> expressions))
<|> (symbol Lambda >>= \ loc -> children (makeFunctionDeclaration loc <$> (makeTerm <$> symbol AnonLambda <*> (Syntax.Identifier <$> source)) <*> ((symbol LambdaParameters *> children (many expression)) <|> pure []) <*> optional (symbol Type *> children expression) <*> expressions))
where
makeFunctionDeclaration loc functionName' functionParameters ty functionBody = makeTerm loc $ Type.Annotation (makeTerm loc $ Declaration.Function functionName' functionParameters functionBody) (maybe (makeTerm loc Syntax.Empty) identity ty)
makeAsyncFunctionDeclaration loc async' functionName' functionParameters ty functionBody = makeTerm loc $ Type.Annotation (makeTerm loc $ Type.Annotation (makeTerm loc $ Declaration.Function functionName' functionParameters functionBody) (maybe (makeTerm loc Syntax.Empty) identity ty)) async'
makeFunctionDeclaration loc functionName' functionParameters ty functionBody = makeTerm loc $ Type.Annotation (makeTerm loc $ Declaration.Function functionName' functionParameters functionBody) (maybe (makeTerm loc Syntax.Empty) id ty)
makeAsyncFunctionDeclaration loc async' functionName' functionParameters ty functionBody = makeTerm loc $ Type.Annotation (makeTerm loc $ Type.Annotation (makeTerm loc $ Declaration.Function functionName' functionParameters functionBody) (maybe (makeTerm loc Syntax.Empty) id ty)) async'
async' :: Assignment
async' = makeTerm <$> symbol AnonAsync <*> (Syntax.Identifier <$> source)
@ -476,7 +477,7 @@ conditionalExpression = makeTerm <$> symbol ConditionalExpression <*> children (
Statement.If conditional thenBranch <$> expressions)
makeTerm :: (HasCallStack, f :< fs) => a -> f (Term.Term (Union fs) a) -> Term.Term (Union fs) a
makeTerm a f = cofree (a :< inj f)
makeTerm a f = a :< inj f
emptyTerm :: Assignment
emptyTerm = makeTerm <$> location <*> pure Syntax.Empty

View File

@ -1,13 +1,17 @@
{-# LANGUAGE DataKinds #-}
module Language.Ruby where
import Control.Comonad
import Control.Comonad.Cofree
import Data.Foldable (toList)
import Data.List (partition)
import Data.Semigroup
import Data.Source
import Data.Text (Text)
import Info
import Prologue
import Language
import qualified Syntax as S
import Term
import Term hiding ((:<))
termAssignment
:: Source -- ^ The source of the term.
@ -44,7 +48,7 @@ termAssignment _ category children
-> Just $ S.Class constant [superclass] body
(Class, constant : rest) -> Just $ S.Class constant [] rest
(SingletonClass, identifier : rest) -> Just $ S.Class identifier [] rest
(Case, _) -> Just $ uncurry S.Switch (Prologue.break ((== When) . Info.category . extract) children)
(Case, _) -> Just $ uncurry S.Switch (break ((== When) . Info.category . extract) children)
(When, expr : body) -> Just $ S.Case expr body
(Ternary, condition : cases) -> Just $ S.Ternary condition cases
(MethodCall, fn : args)
@ -55,10 +59,10 @@ termAssignment _ category children
-> Just $ S.FunctionCall fn [] (toList . unwrap =<< args)
(Object, _ ) -> Just . S.Object Nothing $ foldMap toTuple children
(Modifier If, [ lhs, condition ]) -> Just $ S.If condition [lhs]
(Modifier Unless, [lhs, rhs]) -> Just $ S.If (withRecord (setCategory (extract rhs) Negate) (S.Negate rhs)) [lhs]
(Unless, expr : rest) -> Just $ S.If (withRecord (setCategory (extract expr) Negate) (S.Negate expr)) rest
(Modifier Until, [ lhs, rhs ]) -> Just $ S.While (withRecord (setCategory (extract rhs) Negate) (S.Negate rhs)) [lhs]
(Until, expr : rest) -> Just $ S.While (withRecord (setCategory (extract expr) Negate) (S.Negate expr)) rest
(Modifier Unless, [lhs, rhs]) -> Just $ S.If (setCategory (extract rhs) Negate :< S.Negate rhs) [lhs]
(Unless, expr : rest) -> Just $ S.If ((setCategory (extract expr) Negate) :< S.Negate expr) rest
(Modifier Until, [ lhs, rhs ]) -> Just $ S.While (setCategory (extract rhs) Negate :< S.Negate rhs) [lhs]
(Until, expr : rest) -> Just $ S.While (setCategory (extract expr) Negate :< S.Negate expr) rest
(Elsif, condition : body ) -> Just $ S.If condition body
(SubscriptAccess, [ base, element ]) -> Just $ S.SubscriptAccess base element
(For, lhs : expr : rest ) -> Just $ S.For [lhs, expr] rest
@ -92,8 +96,6 @@ termAssignment _ category children
(Modifier While, [ lhs, condition ]) -> Just $ S.While condition [lhs]
_ | category `elem` [ BeginBlock, EndBlock ] -> Just $ S.BlockStatement children
_ -> Nothing
where
withRecord record syntax = cofree (record :< syntax)
categoryForRubyName :: Text -> Category
categoryForRubyName name = case name of

View File

@ -6,6 +6,8 @@ module Language.Ruby.Syntax
, Term
) where
import Control.Comonad.Cofree (Cofree(..))
import Data.Maybe (fromMaybe)
import Data.Record
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment hiding (Assignment, Error)
@ -18,7 +20,6 @@ import qualified Data.Syntax.Statement as Statement
import Data.Union
import GHC.Stack
import Language.Ruby.Grammar as Grammar
import Prologue hiding (for, get, Location, state, unless)
import qualified Term
-- | The type of Ruby syntax.
@ -388,7 +389,7 @@ emptyStatement = makeTerm <$> symbol EmptyStatement <*> (Syntax.Empty <$ source
-- Helper functions
makeTerm :: (f :< fs, HasCallStack) => a -> f (Term.Term (Union fs) a) -> Term.Term (Union fs) a
makeTerm a f = cofree $ a :< inj f
makeTerm a f = a :< inj f
emptyTerm :: Assignment
emptyTerm = makeTerm <$> location <*> pure Syntax.Empty

View File

@ -1,10 +1,13 @@
{-# LANGUAGE DataKinds #-}
module Language.TypeScript where
import Control.Comonad (extract)
import Control.Comonad.Cofree (unwrap)
import Data.Foldable (toList)
import Data.Source
import Data.Text (Text)
import Info
import Language
import Prologue
import qualified Syntax as S
import Term
@ -22,7 +25,7 @@ termAssignment _ category children =
(CommaOperator, [ a, b ])
| S.Indexed rest <- unwrap b
-> Just $ S.Indexed $ a : rest
(FunctionCall, id : rest) -> case Prologue.break ((== Args) . Info.category . extract) rest of
(FunctionCall, id : rest) -> case break ((== Args) . Info.category . extract) rest of
(typeArgs, [ args ]) -> let flatArgs = toList (unwrap args) in
Just $ case unwrap id of
S.MemberAccess target method -> S.MethodCall target method typeArgs flatArgs
@ -49,12 +52,12 @@ termAssignment _ category children =
, Finally <- Info.category (extract finally)
-> Just $ S.Try [body] [catch] Nothing (Just finally)
(ArrayLiteral, _) -> Just $ S.Array Nothing children
(Method, children) -> case Prologue.break ((== ExpressionStatements) . Info.category . extract) children of
(prev, [body]) -> case Prologue.break ((== Identifier) . Info.category . extract) prev of
(Method, children) -> case break ((== ExpressionStatements) . Info.category . extract) children of
(prev, [body]) -> case break ((== Identifier) . Info.category . extract) prev of
(prev, [id, callSignature]) -> Just $ S.Method prev id Nothing (toList (unwrap callSignature)) (toList (unwrap body))
_ -> Nothing -- No identifier found or callSignature found.
_ -> Nothing -- No body found.``
(Class, identifier : rest) -> case Prologue.break ((== Other "class_body") . Info.category . extract) rest of
(Class, identifier : rest) -> case break ((== Other "class_body") . Info.category . extract) rest of
(clauses, [ definitions ]) -> Just $ S.Class identifier clauses (toList (unwrap definitions))
_ -> Nothing
(Module, [ identifier, definitions ]) -> Just $ S.Module identifier (toList (unwrap definitions))
@ -66,10 +69,8 @@ termAssignment _ category children =
| S.Indexed _ <- unwrap statements
-> Just $ S.Export Nothing (toList (unwrap statements))
| otherwise -> Just $ S.Export (Just statements) []
(For, _)
| Just (exprs, body) <- unsnoc children
-> Just $ S.For exprs [body]
(Function, children) -> case Prologue.break ((== ExpressionStatements) . Info.category . extract) children of
(For, _:_) -> Just $ S.For (init children) [last children]
(Function, children) -> case break ((== ExpressionStatements) . Info.category . extract) children of
(inits, [body]) -> case inits of
[id, callSignature] -> Just $ S.Function id (toList (unwrap callSignature)) (toList (unwrap body))
[callSignature] -> Just $ S.AnonymousFunction (toList (unwrap callSignature)) (toList (unwrap body))

View File

@ -11,13 +11,15 @@ module Parser
, rubyParser
) where
import qualified CMark
import Control.Comonad.Trans.Cofree (headF)
import qualified CMarkGFM
import Data.Functor.Foldable hiding (fold, Nil)
import Data.Record
import Data.Source as Source
import qualified Data.Syntax as Syntax
import Data.Syntax.Assignment
import Data.Union
import Foreign.Ptr
import Info hiding (Empty, Go)
import Language
import Language.Markdown
@ -25,7 +27,6 @@ import qualified Language.JSON.Syntax as JSON
import qualified Language.Markdown.Syntax as Markdown
import qualified Language.Python.Syntax as Python
import qualified Language.Ruby.Syntax as Ruby
import Prologue hiding (Location)
import Syntax hiding (Go)
import Term
import qualified Text.Parser.TreeSitter as TS
@ -49,7 +50,7 @@ data Parser term where
-- | A tree-sitter parser.
TreeSitterParser :: Ptr TS.Language -> Parser (SyntaxTerm DefaultFields)
-- | A parser for 'Markdown' using cmark.
MarkdownParser :: Parser (AST CMark.NodeType)
MarkdownParser :: Parser (AST CMarkGFM.NodeType)
-- | A parser which will parse any input 'Source' into a top-level 'Term' whose children are leaves consisting of the 'Source's lines.
LineByLineParser :: Parser (SyntaxTerm DefaultFields)

View File

@ -14,10 +14,11 @@ module Patch
, mapPatch
) where
import Control.DeepSeq
import Data.Align
import Data.Functor.Listable
import Data.These
import Prologue
import GHC.Generics
-- | An operation to replace, insert, or delete an item.
data Patch a

View File

@ -1,41 +0,0 @@
module Prologue
( module X
, lookup
, (&&&)
, (***)
, hylo, cata, para, ana
, cofree, runCofree, free, runFree
, module Data.Hashable
) where
import Protolude as X
import Data.List (lookup)
import Control.Comonad.Cofree as X hiding ((:<), unfold, unfoldM)
import Control.Monad.Free as X (Free())
import Control.Monad.Free as X hiding (Free(Free, Pure), unfold, unfoldM)
import Control.Comonad.Trans.Cofree as X (CofreeF(..), headF, tailF)
import Control.Monad.Trans.Free as X (FreeF(..))
import Control.Comonad as X
import qualified Control.Comonad.Cofree as Cofree
import qualified Control.Monad.Free as Free
import Control.Arrow ((&&&), (***))
import Data.Functor.Foldable (hylo, cata, para, ana)
import Data.Hashable
cofree :: CofreeF f a (Cofree f a) -> Cofree f a
cofree (a :< f) = a Cofree.:< f
runCofree :: Cofree f a -> CofreeF f a (Cofree f a)
runCofree (a Cofree.:< f) = a :< f
free :: FreeF f a (Free f a) -> Free f a
free (Free f) = Free.Free f
free (Pure a) = Free.Pure a
runFree :: Free f a -> FreeF f a (Free f a)
runFree (Free.Free f) = Free f
runFree (Free.Pure a) = Pure a

View File

@ -12,10 +12,23 @@ module RWS (
, defaultD
) where
import Prologue hiding (State, evalState, runState)
import Control.Applicative (empty)
import Control.Arrow ((&&&))
import Control.Comonad
import Control.Comonad.Trans.Cofree hiding (cofree, runCofree)
import Control.Monad.Free
import Control.Monad.State.Strict
import Data.Foldable
import Data.Function ((&), on)
import Data.Functor.Foldable
import Data.Hashable
import Data.List (sortOn)
import Data.Maybe
import Data.Monoid (First(..))
import Data.Record
import Data.Semigroup hiding (First(..))
import Data.These
import Data.Traversable
import Patch
import Term
import Data.Array.Unboxed
@ -23,9 +36,8 @@ import Data.Functor.Classes
import SES
import qualified Data.Functor.Both as Both
import Data.Functor.Listable
import Data.KdTree.Static hiding (toList)
import Data.KdTree.Static hiding (empty, toList)
import qualified Data.IntMap as IntMap
import Data.Semigroup (Min(..), Option(..))
import Control.Monad.Random
import System.Random.Mersenne.Pure64
@ -68,7 +80,7 @@ rws editDistance canCompare as bs =
in fmap snd rwsDiffs
-- | An IntMap of unmapped terms keyed by their position in a list of terms.
type UnmappedTerms f fields = IntMap (UnmappedTerm f fields)
type UnmappedTerms f fields = IntMap.IntMap (UnmappedTerm f fields)
type Diff f fields = These (Term f (Record fields)) (Term f (Record fields))
@ -228,8 +240,8 @@ setFeatureVector = setField
minimumTermIndex :: [RWS.UnmappedTerm f fields] -> Int
minimumTermIndex = pred . maybe 0 getMin . getOption . foldMap (Option . Just . Min . termIndex)
toMap :: [UnmappedTerm f fields] -> IntMap (UnmappedTerm f fields)
toMap = IntMap.fromList . fmap (termIndex &&& identity)
toMap :: [UnmappedTerm f fields] -> IntMap.IntMap (UnmappedTerm f fields)
toMap = IntMap.fromList . fmap (termIndex &&& id)
toKdTree :: [UnmappedTerm f fields] -> KdTree Double (UnmappedTerm f fields)
toKdTree = build (elems . feature)
@ -284,13 +296,13 @@ pqGramDecorator getLabel p q = cata algebra
pure $! cofree ((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)
padToSize n list = take n (list <> repeat Prologue.empty)
padToSize n list = take n (list <> repeat empty)
-- | Computes a unit vector of the specified dimension from a hash.
unitVector :: Int -> Int -> FeatureVector
unitVector d hash = listArray (0, d - 1) ((* invMagnitude) <$> components)
where
invMagnitude = 1 / sqrtDouble (sum (fmap (** 2) components))
invMagnitude = 1 / sqrt (sum (fmap (** 2) components))
components = evalRand (sequenceA (replicate d (liftRand randomDouble))) (pureMT (fromIntegral hash))
-- | Test the comparability of two root 'Term's in O(1).

View File

@ -18,13 +18,18 @@ module Renderer
, File(..)
) where
import Control.Comonad.Cofree (Cofree, unwrap)
import Control.Comonad.Trans.Cofree (CofreeF(..))
import Control.DeepSeq
import Data.Aeson (Value, (.=))
import Data.ByteString (ByteString)
import Data.Foldable (asum)
import qualified Data.Map as Map
import Data.Output
import Data.Syntax.Algebra (RAlgebra)
import Data.Text (Text)
import Diff (SyntaxDiff)
import Info (DefaultFields)
import Prologue
import Renderer.JSON as R
import Renderer.Patch as R
import Renderer.SExpression as R

View File

@ -6,20 +6,28 @@ module Renderer.JSON
, ToJSONFields(..)
) where
import Control.Comonad.Cofree
import qualified Control.Comonad.Trans.Cofree as CofreeF
import Control.Monad.Free
import qualified Control.Monad.Trans.Free as FreeF
import Data.Aeson (ToJSON, toJSON, encode, object, (.=))
import Data.Aeson as A hiding (json)
import Data.Bifunctor.Join
import Data.Blob
import Data.ByteString.Lazy (toStrict)
import Data.Foldable (toList)
import Data.Functor.Both (Both)
import qualified Data.Map as Map
import Data.Output
import Data.Record
import Data.Semigroup ((<>))
import Data.Text (pack, Text)
import Data.Text.Encoding (decodeUtf8)
import Data.Union
import GHC.Generics
import Info
import Language
import Patch
import Prologue hiding ((++), toStrict)
import Syntax as S
--
@ -34,7 +42,7 @@ renderJSONDiff blobs diff = Map.fromList
, ("paths", toJSON (blobPath <$> toList blobs))
]
instance Output (Map Text Value) where
instance Output (Map.Map Text Value) where
toOutput = toStrict . (<> "\n") . encode
instance ToJSON a => ToJSONFields (Join (,) a) where
@ -45,16 +53,12 @@ instance ToJSON a => ToJSON (Join (,) a) where
toEncoding = foldable
instance (ToJSONFields a, ToJSONFields (f (Free f a))) => ToJSON (Free f a) where
toJSON splitDiff = case runFree splitDiff of
(Free f) -> object (toJSONFields f)
(Pure p) -> object (toJSONFields p)
toEncoding splitDiff = case runFree splitDiff of
(Free f) -> pairs $ mconcat (toJSONFields f)
(Pure p) -> pairs $ mconcat (toJSONFields p)
toJSON = object . toJSONFields
toEncoding = pairs . mconcat . toJSONFields
instance ToJSONFields (CofreeF f a (Cofree f a)) => ToJSON (Cofree f a) where
toJSON = object . toJSONFields . runCofree
toEncoding = pairs . mconcat . toJSONFields . runCofree
instance (ToJSONFields a, ToJSONFields (f (Cofree f a))) => ToJSON (Cofree f a) where
toJSON (a :< f) = object (toJSONFields a <> toJSONFields f)
toEncoding (a :< f) = pairs (mconcat (toJSONFields a <> toJSONFields f))
class ToJSONFields a where
toJSONFields :: KeyValue kv => a -> [kv]
@ -73,7 +77,7 @@ instance ToJSONFields Range where
toJSONFields Range{..} = ["sourceRange" .= [ start, end ]]
instance ToJSONFields Category where
toJSONFields c = ["category" .= case c of { Other s -> s ; _ -> toS c }]
toJSONFields c = ["category" .= case c of { Other s -> s ; _ -> pack (show c) }]
instance ToJSONFields Span where
toJSONFields sourceSpan = [ "sourceSpan" .= sourceSpan ]
@ -82,18 +86,19 @@ 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 (CofreeF.CofreeF f a b) where
toJSONFields (a CofreeF.:< f) = toJSONFields a <> toJSONFields f
instance (ToJSONFields a, ToJSONFields (f b)) => ToJSONFields (FreeF f a b) where
instance (ToJSONFields a, ToJSONFields (f (Free f a))) => ToJSONFields (Free f a) where
toJSONFields (Free f) = toJSONFields f
toJSONFields (Pure a) = toJSONFields a
instance (ToJSONFields a, ToJSONFields (f b)) => ToJSONFields (FreeF.FreeF f a b) where
toJSONFields (FreeF.Free f) = toJSONFields f
toJSONFields (FreeF.Pure a) = toJSONFields a
instance ToJSON a => ToJSONFields (Patch a) where
toJSONFields (Insert a) = [ "insert" .= a ]
toJSONFields (Delete a) = [ "delete" .= a ]

View File

@ -10,17 +10,21 @@ module Renderer.Patch
import Alignment
import Data.Bifunctor.Join
import Data.Blob
import Data.ByteString.Char8 (ByteString, pack)
import qualified Data.ByteString.Char8 as ByteString
import Data.Functor.Both as Both
import Data.List (span, unzip)
import Data.Maybe (fromMaybe)
import Data.Monoid (Sum(..))
import Data.Output
import Data.Range
import Data.Record
import Data.Semigroup ((<>))
import Data.Source
import Data.These
import Diff
import Patch
import Prologue hiding (fst, snd)
import Prelude hiding (fst, snd)
import SplitDiff
-- | Render a timed out file as a truncated diff.
@ -74,9 +78,9 @@ showHunk blobs hunk = maybeOffsetHeader <>
maybeOffsetHeader = if lengthA > 0 && lengthB > 0
then offsetHeader
else mempty
offsetHeader = "@@ -" <> offsetA <> "," <> show lengthA <> " +" <> offsetB <> "," <> show lengthB <> " @@" <> "\n"
offsetHeader = "@@ -" <> offsetA <> "," <> pack (show lengthA) <> " +" <> offsetB <> "," <> pack (show lengthB) <> " @@" <> "\n"
(lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
(offsetA, offsetB) = runJoin . fmap (pack . show . getSum) $ offset hunk
-- | Given the before and after sources, render a change to a string.
showChange :: Functor f => HasField fields Range => Both Source -> Change (SplitDiff f (Record fields)) -> ByteString
@ -117,7 +121,7 @@ header blobs = ByteString.intercalate "\n" ([filepathHeader, fileModeHeader] <>
beforeFilepath = "--- " <> modeHeader "a" modeA pathA
afterFilepath = "+++ " <> modeHeader "b" modeB pathB
sources = blobSource <$> blobs
(pathA, pathB) = case runJoin $ toS . blobPath <$> blobs of
(pathA, pathB) = case runJoin $ pack . blobPath <$> blobs of
("", path) -> (path, path)
(path, "") -> (path, path)
paths -> paths

View File

@ -4,12 +4,15 @@ module Renderer.SExpression
, renderSExpressionTerm
) where
import Control.Comonad.Trans.Cofree hiding (runCofree)
import Control.Monad.Trans.Free hiding (runFree)
import Data.Bifunctor.Join
import Data.ByteString hiding (foldr, spanEnd)
import Data.ByteString.Char8 hiding (foldr, spanEnd)
import Data.Record
import Prologue hiding (replicate, encodeUtf8)
import Data.Semigroup
import Diff
import Patch
import Prelude hiding (replicate)
import Term
-- | Returns a ByteString SExpression formatted diff.
@ -33,22 +36,19 @@ printDiff diff level = case runFree diff of
pad :: Int -> ByteString
pad n | n < 0 = ""
| n < 1 = "\n"
| otherwise = "\n" <> replicate (2 * n) space
| otherwise = "\n" <> replicate (2 * n) ' '
printTerm :: (ConstrainAll Show fields, Foldable f) => Term f (Record fields) -> Int -> ByteString
printTerm term level = go term level 0
where
pad :: Int -> Int -> ByteString
pad p n | n < 1 = ""
| otherwise = "\n" <> replicate (2 * (p + n)) space
| otherwise = "\n" <> replicate (2 * (p + n)) ' '
go :: (ConstrainAll Show fields, Foldable f) => Term f (Record fields) -> Int -> Int -> ByteString
go term parentLevel level = case runCofree term of
(annotation :< syntax) -> pad parentLevel level <> "(" <> showAnnotation annotation <> foldr (\t acc -> go t parentLevel (level + 1) <> acc) "" syntax <> ")"
showAnnotation :: ConstrainAll Show fields => Record fields -> ByteString
showAnnotation Nil = ""
showAnnotation (only :. Nil) = show only
showAnnotation (first :. rest) = show first <> " " <> showAnnotation rest
space :: Word8
space = fromIntegral $ ord ' '
showAnnotation (only :. Nil) = pack (show only)
showAnnotation (first :. rest) = pack (show first) <> " " <> showAnnotation rest

View File

@ -17,16 +17,26 @@ module Renderer.TOC
, entrySummary
) where
import Control.Comonad (extract)
import Control.Comonad.Cofree (unwrap)
import Control.DeepSeq
import Control.Monad.Free (iter)
import Data.Aeson
import Data.Align (crosswalk)
import Data.Bifunctor (bimap, first)
import Data.Blob
import Data.ByteString.Lazy (toStrict)
import Data.Foldable (fold, foldl', toList)
import Data.Functor.Both hiding (fst, snd)
import qualified Data.Functor.Both as Both
import Data.Functor.Foldable (cata)
import Data.Functor.Listable
import Data.Function (on)
import Data.List.NonEmpty (nonEmpty)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Output
import Data.Record
import Data.Semigroup ((<>), sconcat)
import Data.Source as Source
import Data.Text (toLower)
import qualified Data.Text as T
@ -34,10 +44,10 @@ import Data.Text.Listable
import Data.These
import Data.Union
import Diff
import GHC.Generics
import Info
import Language
import Patch
import Prologue hiding (toStrict)
import qualified Data.List as List
import qualified Data.Map as Map hiding (null)
import Syntax as S
@ -47,7 +57,7 @@ import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Markup as Markup
import Term
data Summaries = Summaries { changes, errors :: !(Map Text [Value]) }
data Summaries = Summaries { changes, errors :: !(Map.Map T.Text [Value]) }
deriving (Eq, Show)
instance Monoid Summaries where
@ -62,12 +72,12 @@ instance ToJSON Summaries where
data JSONSummary
= JSONSummary
{ summaryCategoryName :: Text
, summaryTermName :: Text
{ summaryCategoryName :: T.Text
, summaryTermName :: T.Text
, summarySpan :: Span
, summaryChangeType :: Text
, summaryChangeType :: T.Text
}
| ErrorSummary { error :: Text, errorSpan :: Span, errorLanguage :: Maybe Language }
| ErrorSummary { error :: T.Text, errorSpan :: Span, errorLanguage :: Maybe Language }
deriving (Generic, Eq, Show)
instance ToJSON JSONSummary where
@ -80,10 +90,10 @@ isValidSummary _ = True
-- | A declarations identifier and type.
data Declaration
= MethodDeclaration { declarationIdentifier :: Text }
| FunctionDeclaration { declarationIdentifier :: Text }
| SectionDeclaration { declarationIdentifier :: Text, declarationLevel :: Int }
| ErrorDeclaration { declarationIdentifier :: Text, declarationLanguage :: Maybe Language }
= MethodDeclaration { declarationIdentifier :: T.Text }
| FunctionDeclaration { declarationIdentifier :: T.Text }
| SectionDeclaration { declarationIdentifier :: T.Text, declarationLevel :: Int }
| ErrorDeclaration { declarationIdentifier :: T.Text, declarationLanguage :: Maybe Language }
deriving (Eq, Generic, NFData, Show)
getDeclaration :: HasField fields (Maybe Declaration) => Record fields -> Maybe Declaration
@ -96,14 +106,14 @@ declaration (annotation :< _) = annotation <$ (getField annotation :: Maybe Decl
-- | Compute 'Declaration's for methods and functions in 'Syntax'.
syntaxDeclarationAlgebra :: HasField fields Range => Blob -> RAlgebra (SyntaxTermF fields) (SyntaxTerm fields) (Maybe Declaration)
syntaxDeclarationAlgebra Blob{..} r = case tailF r of
syntaxDeclarationAlgebra Blob{..} (a :< r) = case r of
S.Function (identifier, _) _ _ -> Just $ FunctionDeclaration (getSource identifier)
S.Method _ (identifier, _) Nothing _ _ -> Just $ MethodDeclaration (getSource identifier)
S.Method _ (identifier, _) (Just (receiver, _)) _ _
| S.Indexed [receiverParams] <- unwrap receiver
, S.ParameterDecl (Just ty) _ <- unwrap receiverParams -> Just $ MethodDeclaration ("(" <> getSource ty <> ") " <> getSource identifier)
| otherwise -> Just $ MethodDeclaration (getSource receiver <> "." <> getSource identifier)
S.ParseError{} -> Just $ ErrorDeclaration (toText (Source.slice (byteRange (headF r)) blobSource)) blobLanguage
S.ParseError{} -> Just $ ErrorDeclaration (toText (Source.slice (byteRange a) blobSource)) blobLanguage
_ -> Nothing
where getSource = toText . flip Source.slice blobSource . byteRange . extract
@ -111,10 +121,10 @@ syntaxDeclarationAlgebra Blob{..} r = case tailF r of
declarationAlgebra :: (Declaration.Function :< fs, Declaration.Method :< fs, Syntax.Error :< fs, Functor (Union fs), HasField fields Range)
=> Blob
-> RAlgebra (TermF (Union fs) (Record fields)) (Term (Union fs) (Record fields)) (Maybe Declaration)
declarationAlgebra Blob{..} r
| Just (Declaration.Function (identifier, _) _ _) <- prj (tailF r) = Just $ FunctionDeclaration (getSource (extract identifier))
| Just (Declaration.Method _ (identifier, _) _ _) <- prj (tailF r) = Just $ MethodDeclaration (getSource (extract identifier))
| Just Syntax.Error{} <- prj (tailF r) = Just $ ErrorDeclaration (getSource (headF r)) blobLanguage
declarationAlgebra Blob{..} (a :< r)
| Just (Declaration.Function (identifier, _) _ _) <- prj r = Just $ FunctionDeclaration (getSource (extract identifier))
| Just (Declaration.Method _ (identifier, _) _ _) <- prj r = Just $ MethodDeclaration (getSource (extract identifier))
| Just Syntax.Error{} <- prj r = Just $ ErrorDeclaration (getSource a) blobLanguage
| otherwise = Nothing
where getSource = toText . flip Source.slice blobSource . byteRange
@ -122,9 +132,9 @@ declarationAlgebra Blob{..} r
markupSectionAlgebra :: (Markup.Section :< fs, Syntax.Error :< fs, HasField fields Range, Functor (Union fs), Foldable (Union fs))
=> Blob
-> RAlgebra (TermF (Union fs) (Record fields)) (Term (Union fs) (Record fields)) (Maybe Declaration)
markupSectionAlgebra Blob{..} r
| Just (Markup.Section level (heading, _) _) <- prj (tailF r) = Just $ SectionDeclaration (maybe (getSource (extract heading)) (firstLine . toText . flip Source.slice blobSource . sconcat) (nonEmpty (byteRange . extract <$> toList (unwrap heading)))) level
| Just Syntax.Error{} <- prj (tailF r) = Just $ ErrorDeclaration (getSource (headF r)) blobLanguage
markupSectionAlgebra Blob{..} (a :< r)
| Just (Markup.Section level (heading, _) _) <- prj r = Just $ SectionDeclaration (maybe (getSource (extract heading)) (firstLine . toText . flip Source.slice blobSource . sconcat) (nonEmpty (byteRange . extract <$> toList (unwrap heading)))) level
| Just Syntax.Error{} <- prj r = Just $ ErrorDeclaration (getSource a) blobLanguage
| otherwise = Nothing
where getSource = firstLine . toText . flip Source.slice blobSource . byteRange
firstLine = T.takeWhile (/= '\n')
@ -183,7 +193,7 @@ entrySummary entry = case entry of
Replaced a -> recordSummary a "modified"
-- | Construct a 'JSONSummary' from a node annotation and a change type label.
recordSummary :: (HasField fields (Maybe Declaration), HasField fields Span) => Record fields -> Text -> Maybe JSONSummary
recordSummary :: (HasField fields (Maybe Declaration), HasField fields Span) => Record fields -> T.Text -> Maybe JSONSummary
recordSummary record = case getDeclaration record of
Just (ErrorDeclaration text language) -> Just . const (ErrorSummary text (sourceSpan record) language)
Just declaration -> Just . JSONSummary (toCategoryName declaration) (declarationIdentifier declaration) (sourceSpan record)
@ -193,7 +203,7 @@ renderToCDiff :: (HasField fields (Maybe Declaration), HasField fields Span, Tra
renderToCDiff blobs = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . diffTOC
where toMap [] = mempty
toMap as = Map.singleton summaryKey (toJSON <$> as)
summaryKey = toS $ case runJoin (blobPath <$> blobs) of
summaryKey = T.pack $ case runJoin (blobPath <$> blobs) of
(before, after) | null before -> after
| null after -> before
| before == after -> after
@ -202,7 +212,7 @@ renderToCDiff blobs = uncurry Summaries . bimap toMap toMap . List.partition isV
renderToCTerm :: (HasField fields (Maybe Declaration), HasField fields Span, Traversable 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 (toS blobPath) (toJSON <$> as)
toMap as = Map.singleton (T.pack blobPath) (toJSON <$> as)
diffTOC :: (HasField fields (Maybe Declaration), HasField fields Span, Traversable f) => Diff f (Record fields) -> [JSONSummary]
diffTOC = mapMaybe entrySummary . dedupe . tableOfContentsBy declaration
@ -211,11 +221,11 @@ termToC :: (HasField fields (Maybe Declaration), HasField fields Span, Traversab
termToC = mapMaybe (flip recordSummary "unchanged") . termTableOfContentsBy declaration
-- The user-facing category name
toCategoryName :: Declaration -> Text
toCategoryName :: Declaration -> T.Text
toCategoryName declaration = case declaration of
FunctionDeclaration _ -> "Function"
MethodDeclaration _ -> "Method"
SectionDeclaration _ l -> "Heading " <> show l
SectionDeclaration _ l -> "Heading " <> T.pack (show l)
ErrorDeclaration{} -> "ParseError"
instance Listable Declaration where

View File

@ -4,7 +4,6 @@ module SES
, Myers.ses
) where
import Prologue
import qualified SES.Myers as Myers
-- | Edit constructor for two terms, if comparable. Otherwise returns Nothing.

View File

@ -6,10 +6,9 @@ module SES.Myers
import Data.Array ((!))
import qualified Data.Array as Array
import Data.Foldable (find, toList)
import Data.Ix
import Data.These
import GHC.Show hiding (show)
import Prologue hiding (error)
-- | An edit script, i.e. a sequence of changes/copies of elements.
type EditScript a b = [These a b]

View File

@ -8,8 +8,12 @@ module Semantic
) where
import Algorithm hiding (diff)
import Control.Applicative ((<|>))
import Control.Comonad.Cofree (hoistCofree)
import Control.Monad ((<=<))
import Data.Align.Generic (GAlign)
import Data.Blob
import Data.ByteString (ByteString)
import Data.Functor.Both as Both
import Data.Functor.Classes (Eq1, Show1)
import Data.Output
@ -23,7 +27,6 @@ import Interpreter
import qualified Language
import Patch
import Parser
import Prologue
import Renderer
import Semantic.Task as Task
import Term

View File

@ -1,14 +1,15 @@
module Semantic.Log where
import Data.String
import Prologue hiding (Location, show)
import Data.Bifunctor (second)
import Data.Foldable (toList)
import Data.List (intersperse)
import Data.Semigroup ((<>))
import qualified Data.Time.Format as Time
import qualified Data.Time.LocalTime as LocalTime
import System.Console.ANSI
import System.IO (hIsTerminalDevice)
import System.IO (Handle, hIsTerminalDevice)
import System.Posix.Process
import System.Posix.Types
import Text.Show
import Text.Printf
-- | A log message at a specific level.
@ -42,7 +43,7 @@ logfmtFormatter Options{..} (Message level message pairs time) =
. showChar '\n' $ ""
where
kv k v = showString k . showChar '=' . v
showPairs = foldr (.) identity . intersperse (showChar ' ')
showPairs = foldr (.) id . intersperse (showChar ' ')
showTime = showString . Time.formatTime Time.defaultTimeLocale "%FT%XZ%z"
-- | Format log messages to a terminal. Suitable for local development.
@ -62,7 +63,7 @@ terminalFormatter Options{..} (Message level message pairs time) =
showLevel Warning = withSGRCode colourize [SetColor Foreground Vivid Yellow, SetConsoleIntensity BoldIntensity] (showString " WARN")
showLevel Info = withSGRCode colourize [SetColor Foreground Vivid Cyan, SetConsoleIntensity BoldIntensity] (showString " INFO")
showLevel Debug = withSGRCode colourize [SetColor Foreground Vivid White, SetConsoleIntensity BoldIntensity] (showString "DEBUG")
showPairs pairs = foldr (.) identity $ intersperse (showChar ' ') (showPair <$> pairs)
showPairs pairs = foldr (.) id $ intersperse (showChar ' ') (showPair <$> pairs)
showPair (k, v) = showString k . showChar '=' . withSGRCode colourize [SetConsoleIntensity BoldIntensity] (showString v)
showTime = showString . Time.formatTime Time.defaultTimeLocale "%X"

View File

@ -26,15 +26,19 @@ module Semantic.Task
import Control.Concurrent.STM.TMQueue
import Control.Exception
import Control.Monad (join, when)
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.Blob
import qualified Data.ByteString as B
import Data.Foldable (fold)
import Data.Functor.Both as Both
import Data.Functor.Foldable (cata)
import Data.Record
import Data.String
import Data.Semigroup ((<>))
import qualified Data.Syntax as Syntax
import Data.Syntax.Algebra (RAlgebra, decoratorWithAlgebra)
import qualified Data.Syntax.Assignment as Assignment
@ -44,20 +48,20 @@ import qualified Data.Time.LocalTime as LocalTime
import Data.Union
import Diff
import qualified Files
import GHC.Conc (atomically)
import Language
import Language.Markdown
import Parser
import Prologue hiding (Location, show)
import System.IO (hPutStr)
import System.Exit (die)
import System.IO (Handle, hPutStr, stderr)
import Term
import Text.Show
import TreeSitter
import Semantic.Log
data TaskF output where
ReadBlobs :: Either Handle [(FilePath, Maybe Language)] -> TaskF [Blob]
ReadBlobPairs :: Either Handle [Both (FilePath, Maybe Language)] -> TaskF [Both Blob]
WriteToOutput :: Either Handle FilePath -> ByteString -> TaskF ()
WriteToOutput :: Either Handle FilePath -> B.ByteString -> TaskF ()
WriteLog :: Level -> String -> [(String, String)] -> TaskF ()
Time :: String -> [(String, String)] -> Task output -> TaskF output
Parse :: Parser term -> Blob -> TaskF term
@ -84,8 +88,8 @@ readBlobs from = ReadBlobs from `Then` return
readBlobPairs :: Either Handle [Both (FilePath, Maybe Language)] -> Task [Both Blob]
readBlobPairs from = ReadBlobPairs from `Then` return
-- | A 'Task' which writes a 'ByteString' to a 'Handle' or a 'FilePath'.
writeToOutput :: Either Handle FilePath -> ByteString -> Task ()
-- | A 'Task' which writes a 'B.ByteString' to a 'Handle' or a 'FilePath'.
writeToOutput :: Either Handle FilePath -> B.ByteString -> Task ()
writeToOutput path contents = WriteToOutput path contents `Then` return
@ -142,11 +146,11 @@ runTaskWithOptions :: Options -> Task a -> IO a
runTaskWithOptions options task = do
options <- configureOptionsForHandle stderr options
logQueue <- newTMQueueIO
logging <- async (logSink options logQueue)
logging <- Async.async (logSink options logQueue)
result <- run options logQueue task
atomically (closeTMQueue logQueue)
wait logging
Async.wait logging
either die pure result
where logSink options@Options{..} queue = do
message <- atomically (readTMQueue queue)

View File

@ -6,20 +6,25 @@ module SemanticCmdLine
, runParse
) where
import Control.Monad ((<=<))
import Files (languageForFilePath)
import Data.ByteString (ByteString)
import Data.Foldable (find)
import Data.Functor.Both hiding (fst, snd)
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 Prologue hiding (concurrently, readFile)
import Renderer
import qualified Paths_semantic_diff as Library (version)
import qualified Semantic.Task as Task
import qualified Semantic.Log as Log
import System.IO (stdin)
import System.IO (Handle, stdin, stdout)
import qualified Semantic (parseBlobs, diffBlobPairs)
import Text.Read
main :: IO ()
main = customExecParser (prefs showHelpOnEmpty) arguments >>= uncurry Task.runTaskWithOptions

View File

@ -1,25 +1,24 @@
module SplitDiff where
import Control.Comonad
import Control.Comonad.Trans.Cofree
import Control.Monad.Free
import Data.Record
import Info
import Prologue
import Term (Term, TermF)
-- | A patch to only one side of a diff.
data SplitPatch a = SplitInsert a | SplitDelete a | SplitReplace a
data SplitPatch a
= SplitInsert { splitTerm :: a }
| SplitDelete { splitTerm :: a }
| SplitReplace { splitTerm :: a }
deriving (Show, Eq, Functor)
-- | Get the term from a split patch.
getSplitTerm :: SplitPatch a -> a
getSplitTerm (SplitInsert a) = a
getSplitTerm (SplitDelete a) = a
getSplitTerm (SplitReplace a) = a
-- | Get the range of a SplitDiff.
getRange :: Functor f => HasField fields Range => SplitDiff f (Record fields) -> Range
getRange diff = byteRange $ case runFree diff of
getRange diff = byteRange $ case diff of
Free annotated -> headF annotated
Pure patch -> extract (getSplitTerm patch)
Pure patch -> extract (splitTerm patch)
-- | A diff with only one sides annotations.
type SplitDiff f annotation = Free (TermF f annotation) (SplitPatch (Term f annotation))

View File

@ -1,15 +1,15 @@
{-# LANGUAGE DeriveAnyClass #-}
module Syntax where
import Control.DeepSeq
import Data.Aeson
import Data.Align.Generic
import Data.Functor.Classes
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Listable
import Data.Mergeable
import Data.Text (pack)
import Data.Text (pack, Text)
import GHC.Generics
import Prologue
-- | A node in an abstract syntax tree.
--

View File

@ -1,38 +1,54 @@
{-# LANGUAGE RankNTypes, TypeFamilies, TypeSynonymInstances, UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Term where
module Term
( Term
, TermF
, SyntaxTerm
, SyntaxTermF
, zipTerms
, termSize
, alignCofreeWith
, cofree
, runCofree
, CofreeF.CofreeF(..)
) where
import Prologue
import qualified Control.Comonad.Cofree as Cofree
import qualified Control.Comonad.Trans.Cofree as CofreeF
import Control.DeepSeq
import Control.Monad.Free
import Data.Align.Generic
import Data.Functor.Both
import Data.Functor.Foldable
import Data.Maybe
import Data.Record
import Data.These
import Syntax
-- | A Term with an abstract syntax tree and an annotation.
type Term f = Cofree f
type TermF = CofreeF
type Term f = Cofree.Cofree f
type TermF = CofreeF.CofreeF
-- | A Term with a Syntax leaf and a record of fields.
type SyntaxTerm fields = Term Syntax (Record fields)
type SyntaxTermF fields = TermF Syntax (Record fields)
instance (NFData (f (Cofree f a)), NFData a, Functor f) => NFData (Cofree f a) where
instance (NFData (f (Cofree.Cofree f a)), NFData a, Functor f) => NFData (Cofree.Cofree f a) where
rnf = rnf . runCofree
instance (NFData a, NFData (f b)) => NFData (CofreeF f a b) where
rnf (a :< s) = rnf a `seq` rnf s `seq` ()
instance (NFData a, NFData (f b)) => NFData (CofreeF.CofreeF f a b) where
rnf (a CofreeF.:< s) = rnf a `seq` rnf s `seq` ()
-- | Zip two terms by combining their annotations into a pair of annotations.
-- | If the structure of the two terms don't match, then Nothing will be returned.
zipTerms :: (Traversable f, GAlign f) => Term f annotation -> Term f annotation -> Maybe (Term f (Both annotation))
zipTerms t1 t2 = iter go (alignCofreeWith galign (const Nothing) both (These t1 t2))
where go (a :< s) = cofree . (a :<) <$> sequenceA s
where go (a CofreeF.:< s) = cofree . (a CofreeF.:<) <$> sequenceA s
-- | Return the node count of a term.
termSize :: (Foldable f, Functor f) => Term f annotation -> Int
termSize = cata size where
size (_ :< syntax) = 1 + sum syntax
size (_ CofreeF.:< syntax) = 1 + sum syntax
-- | Aligns (zips, retaining non-overlapping portions of the structure) a pair of terms.
alignCofreeWith :: Functor f
@ -43,5 +59,12 @@ alignCofreeWith :: Functor f
-> Free (TermF f combined) contrasted
alignCofreeWith compare contrast combine = go
where go terms = fromMaybe (pure (contrast terms)) $ case terms of
These t1 t2 -> wrap . (combine (extract t1) (extract t2) :<) . fmap go <$> compare (unwrap t1) (unwrap t2)
These (a1 Cofree.:< f1) (a2 Cofree.:< f2) -> wrap . (combine a1 a2 CofreeF.:<) . fmap go <$> compare f1 f2
_ -> Nothing
cofree :: CofreeF.CofreeF f a (Cofree.Cofree f a) -> Cofree.Cofree f a
cofree (a CofreeF.:< f) = a Cofree.:< f
runCofree :: Cofree.Cofree f a -> CofreeF.CofreeF f a (Cofree.Cofree f a)
runCofree (a Cofree.:< f) = a CofreeF.:< f

View File

@ -5,8 +5,10 @@ module TreeSitter
, defaultTermAssignment
) where
import Prologue hiding (Constructor)
import Category
import Control.Comonad (extract)
import Control.Exception
import Control.Monad ((<=<))
import Data.Blob
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Functor.Foldable hiding (Nil)
@ -15,6 +17,7 @@ import Data.Record
import Data.Source
import Data.Span
import qualified Data.Syntax.Assignment as A
import Data.Text (Text, pack)
import Language
import qualified Language.Go as Go
import qualified Language.TypeScript as TS
@ -82,7 +85,7 @@ documentToTerm language document Blob{..} = do
let allChildren = getChildren (fromIntegral (nodeChildCount node)) copyAll
let source = slice (nodeRange node) blobSource
assignTerm language source (range :. categoryForLanguageProductionName language (toS name) :. nodeSpan node :. Nil) children allChildren
assignTerm language source (range :. categoryForLanguageProductionName language (pack name) :. nodeSpan node :. Nil) children allChildren
where getChildren count copy = do
nodes <- allocaArray count $ \ childNodesPtr -> do
_ <- with (nodeTSNode node) (\ nodePtr -> copy nodePtr childNodesPtr (fromIntegral count))
@ -127,7 +130,7 @@ defaultTermAssignment source category children allChildren
-- Control flow statements
(If, condition : body) -> S.If condition body
(Switch, _) -> uncurry S.Switch (Prologue.break ((== Case) . Info.category . extract) children)
(Switch, _) -> uncurry S.Switch (break ((== Case) . Info.category . extract) children)
(Case, expr : body) -> S.Case expr body
(While, expr : rest) -> S.While expr rest

View File

@ -2,22 +2,26 @@
module AlignmentSpec where
import Alignment
import Control.Arrow ((&&&))
import Control.Comonad.Cofree (Cofree, hoistCofree)
import Control.Monad.Free (Free, wrap)
import Control.Monad.State
import Data.Align hiding (align)
import Data.Bifunctor
import Data.Bifunctor.Join
import Data.Functor.Both as Both
import Data.Foldable (toList)
import Data.Functor.Both as Both hiding (fst, snd)
import Data.Functor.Listable
import Data.List (nub)
import Data.List (nub, sort)
import Data.Maybe (catMaybes, fromMaybe)
import Data.Monoid hiding ((<>))
import Data.Range
import Data.Record
import Data.Semigroup ((<>))
import qualified Data.Source as Source
import qualified Data.Text as Text
import Data.These
import Patch
import Prologue hiding (fst, snd)
import qualified Prologue
import SplitDiff
import Syntax
import Term
@ -47,17 +51,17 @@ spec = parallel $ do
prop "covers every input line" $
\ elements -> let (_, children, ranges) = toAlignBranchInputs elements in
join <$> traverse (modifyJoin (fromThese [] []) . fmap (pure . Prologue.fst)) (alignBranch Prologue.snd children ranges) `shouldBe` ranges
join <$> traverse (modifyJoin (fromThese [] []) . fmap (pure . fst)) (alignBranch snd children ranges) `shouldBe` ranges
prop "covers every input child" $
\ elements -> let (_, children, ranges) = toAlignBranchInputs elements in
sort (nub (keysOfAlignedChildren (alignBranch Prologue.snd children ranges))) `shouldBe` sort (nub (catMaybes (branchElementKey <$> elements)))
sort (nub (keysOfAlignedChildren (alignBranch snd children ranges))) `shouldBe` sort (nub (catMaybes (branchElementKey <$> elements)))
prop "covers every line of every input child" $
\ elements -> let (_, children, ranges) = toAlignBranchInputs elements in
sort (keysOfAlignedChildren (alignBranch Prologue.snd children ranges)) `shouldBe` sort (do
sort (keysOfAlignedChildren (alignBranch snd children ranges)) `shouldBe` sort (do
line <- children
these (pure . Prologue.fst) (pure . Prologue.fst) (\ (k1, _) (k2, _) -> [ k1, k2 ]) . runJoin $ line)
these (pure . fst) (pure . fst) (\ (k1, _) (k2, _) -> [ k1, k2 ]) . runJoin $ line)
describe "alignDiff" $ do
it "aligns identical branches on a single line" $
@ -197,17 +201,17 @@ spec = parallel $ do
\ xs -> counts (numberedRows (unListableF <$> xs :: [Join These Char])) `shouldBe` length . catMaybes <$> Join (unalign (runJoin . unListableF <$> xs))
data BranchElement
= Child Text (Join These Text)
| Margin (Join These Text)
= Child Text.Text (Join These Text.Text)
| Margin (Join These Text.Text)
deriving Show
branchElementKey :: BranchElement -> Maybe Text
branchElementKey :: BranchElement -> Maybe Text.Text
branchElementKey (Child key _) = Just key
branchElementKey _ = Nothing
toAlignBranchInputs :: [BranchElement] -> (Both Source.Source, [Join These (Text, Range)], Both [Range])
toAlignBranchInputs :: [BranchElement] -> (Both Source.Source, [Join These (Text.Text, Range)], Both [Range])
toAlignBranchInputs elements = (sources, join . (`evalState` both 0 0) . traverse go $ elements, ranges)
where go :: BranchElement -> State (Both Int) [Join These (Text, Range)]
where go :: BranchElement -> State (Both Int) [Join These (Text.Text, Range)]
go child@(Child key _) = do
lines <- traverse (\ (Child _ contents) -> do
prev <- get
@ -229,8 +233,8 @@ toAlignBranchInputs elements = (sources, join . (`evalState` both 0 0) . travers
branchElementContents (Child _ contents) = contents
branchElementContents (Margin contents) = contents
keysOfAlignedChildren :: [Join These (Range, [(Text, Range)])] -> [Text]
keysOfAlignedChildren lines = lines >>= these identity identity (<>) . runJoin . fmap (fmap Prologue.fst . Prologue.snd)
keysOfAlignedChildren :: [Join These (Range, [(Text.Text, Range)])] -> [Text.Text]
keysOfAlignedChildren lines = lines >>= these id id (<>) . runJoin . fmap (fmap fst . snd)
joinCrosswalk :: Bicrosswalk p => Align f => (a -> f b) -> Join p a -> f (Join p b)
joinCrosswalk f = fmap Join . bicrosswalk f f . runJoin
@ -240,7 +244,7 @@ instance Listable BranchElement where
, Margin `mapT` joinTheseOf (Text.singleton `mapT` padding '-') ]
where key = Text.singleton `mapT` [['a'..'z'] <> ['A'..'Z'] <> ['0'..'9']]
contents key = (wrap key . Text.singleton) `mapT` padding '*'
wrap key contents = "(" <> key <> contents <> ")" :: Text
wrap key contents = "(" <> key <> contents <> ")" :: Text.Text
padding :: Char -> [Tier Char]
padding char = frequency [ (10, [[char]])
, (1, [['\n']]) ]
@ -254,16 +258,16 @@ instance Listable BranchElement where
counts :: [Join These (Int, a)] -> Both Int
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered))
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap fst <$> numbered))
align :: Both Source.Source -> ConstructibleFree Syntax (Patch (Term Syntax (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff [] (Record '[Range]))
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
align sources = PrettyDiff sources . fmap (fmap (getRange &&& id)) . alignDiff sources . deconstruct
info :: Int -> Int -> Record '[Range]
info start end = Range start end :. Nil
prettyDiff :: Both Source.Source -> [Join These (ConstructibleFree [] (SplitPatch (Term [] (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff [] (Record '[Range]))
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& id) . deconstruct))
data PrettyDiff a = PrettyDiff { unPrettySources :: Both Source.Source, unPrettyLines :: [Join These (Range, a)] }
deriving Eq
@ -301,16 +305,16 @@ instance (Functor f, PatchConstructible patch) => PatchConstructible (Constructi
delete = ConstructibleFree . pure . delete
class SyntaxConstructible s where
leaf :: annotation -> Text -> s annotation
leaf :: annotation -> Text.Text -> s annotation
branch :: annotation -> [s annotation] -> s annotation
instance SyntaxConstructible (ConstructibleFree Syntax patch) where
leaf info = ConstructibleFree . free . Free . (info :<) . Leaf
branch info = ConstructibleFree . free . Free . (info :<) . Indexed . fmap deconstruct
leaf info = ConstructibleFree . wrap . (info :<) . Leaf
branch info = ConstructibleFree . wrap . (info :<) . Indexed . fmap deconstruct
instance SyntaxConstructible (ConstructibleFree [] patch) where
leaf info = ConstructibleFree . free . Free . (info :<) . const []
branch info = ConstructibleFree . free . Free . (info :<) . fmap deconstruct
leaf info = ConstructibleFree . wrap . (info :<) . const []
branch info = ConstructibleFree . wrap . (info :<) . fmap deconstruct
instance SyntaxConstructible (Cofree Syntax) where
info `leaf` value = cofree $ info :< Leaf value

View File

@ -6,7 +6,9 @@ import Data.Functor.Both as Both
import Data.Maybe
import Data.String
import Language
import Prologue hiding (readFile, toList)
import Prelude hiding (readFile)
import System.Exit (ExitCode(..))
import System.IO (IOMode(..), openFile)
import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall)
import Test.Hspec.Expectations.Pretty

View File

@ -1,12 +1,11 @@
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
module Data.Mergeable.Spec where
import Control.Applicative (Alternative(..))
import Data.Functor.Identity
import Data.Functor.Listable
import Data.Maybe (catMaybes)
import Data.Mergeable
import Data.String (String)
import GHC.Show
import Prologue
import Syntax
import Test.Hspec
import Test.Hspec.LeanCheck
@ -44,7 +43,7 @@ sequenceAltLaws value function = describe "sequenceAlt" $ do
\ a -> sequenceAlt (pure <$> a) `shouldNotBe` (empty :: g (f a))
prop "relationship with merge" . forAll (productWith ((Blind .) . fmap . getBlind) function value :: [Tier (Blind (f (g a)))]) $
\ a -> sequenceAlt (getBlind a) `shouldBe` merge identity (getBlind a)
\ a -> sequenceAlt (getBlind a) `shouldBe` merge id (getBlind a)
withAlternativeInstances :: forall f a. (Listable a, Eq (f a), Show (f a)) => (forall g. (Alternative g, Eq (g (f a)), Show (g (f a))) => [Tier (f a)] -> [Tier (Blind (a -> g a))] -> Spec) -> [Tier (f a)] -> Spec

View File

@ -2,16 +2,17 @@
module Data.RandomWalkSimilarity.Spec where
import Category
import Control.Comonad.Trans.Cofree (headF)
import Control.Monad.Free (wrap)
import Data.Array.IArray
import Data.Bifunctor
import Data.Functor.Listable
import RWS
import Data.Record
import Data.These
import Diff
import Info
import Patch
import Prologue
import RWS
import Syntax
import Term
import Test.Hspec

View File

@ -1,11 +1,16 @@
{-# LANGUAGE DataKinds #-}
module Data.Syntax.Assignment.Spec where
import Data.ByteString.Char8 as B (words, length)
import Control.Comonad.Cofree (Cofree(..))
import Control.Comonad.Trans.Cofree (headF)
import Data.Bifunctor (first)
import Data.ByteString.Char8 as B (ByteString, length, words)
import Data.Semigroup ((<>))
import Data.Source
import Data.Syntax.Assignment
import GHC.Stack (getCallStack)
import Info
import Prologue hiding (State)
import Prelude hiding (words)
import Test.Hspec
import Text.Parser.TreeSitter.Language (Symbol(..), SymbolType(..))
@ -276,7 +281,7 @@ spec = do
Left [ "symbol", "red" ]
node :: symbol -> Int -> Int -> [AST symbol] -> AST symbol
node symbol start end children = cofree $ Node symbol (Range start end) (Info.Span (Info.Pos 1 (succ start)) (Info.Pos 1 (succ end))) :< children
node symbol start end children = Node symbol (Range start end) (Info.Span (Info.Pos 1 (succ start)) (Info.Pos 1 (succ end))) :< children
data Grammar = Palette | Red | Green | Blue | Magenta
deriving (Enum, Eq, Show)
@ -285,7 +290,7 @@ instance Symbol Grammar where
symbolType Magenta = Anonymous
symbolType _ = Regular
data Out = Out ByteString | OutError ByteString
data Out = Out B.ByteString | OutError B.ByteString
deriving (Eq, Show)
red :: HasCallStack => Assignment (AST Grammar) Grammar Out

View File

@ -2,13 +2,13 @@
module DiffSpec where
import Category
import Control.Comonad.Trans.Cofree (headF)
import Data.Functor.Both
import Data.Functor.Listable
import RWS
import Diff
import Info
import Interpreter
import Prologue
import SpecHelpers
import Term
import Test.Hspec

View File

@ -1,13 +1,15 @@
{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving, OverloadedStrings #-}
module IntegrationSpec where
import Control.DeepSeq
import qualified Data.ByteString as B
import Data.Foldable (find, traverse_)
import Data.Functor.Both
import Data.List (union, concat, transpose)
import Data.Maybe (fromMaybe)
import Data.Semigroup ((<>))
import qualified Data.Text as T
import qualified Data.ByteString as B
import Data.Text.Encoding (decodeUtf8)
import GHC.Show (Show(..))
import Prologue hiding (fst, snd, readFile)
import System.FilePath
import System.FilePath.Glob
import SpecHelpers
@ -87,7 +89,7 @@ examples directory = do
lookupNormalized :: FilePath -> [FilePath] -> FilePath
lookupNormalized name xs = fromMaybe
(panic ("cannot find " <> T.pack name <> " make sure .A, .B and exist." :: Text))
(error ("cannot find " <> name <> " make sure .A, .B and exist."))
(lookupNormalized' name xs)
lookupNormalized' :: FilePath -> [FilePath] -> Maybe FilePath
@ -112,17 +114,17 @@ testDiff paths expectedOutput = do
expected <- verbatim <$> B.readFile expectedOutput
actual `shouldBe` expected
stripWhitespace :: ByteString -> ByteString
stripWhitespace :: B.ByteString -> B.ByteString
stripWhitespace = B.foldl' go B.empty
where go acc x | x `B.elem` " \t\n" = acc
| otherwise = B.snoc acc x
-- | A wrapper around `ByteString` with a more readable `Show` instance.
newtype Verbatim = Verbatim ByteString
-- | A wrapper around 'B.ByteString' with a more readable 'Show' instance.
newtype Verbatim = Verbatim B.ByteString
deriving (Eq, NFData)
instance Show Verbatim where
showsPrec _ (Verbatim byteString) = ('\n':) . (T.unpack (decodeUtf8 byteString) ++)
verbatim :: ByteString -> Verbatim
verbatim :: B.ByteString -> Verbatim
verbatim = Verbatim . stripWhitespace

View File

@ -2,6 +2,7 @@
module InterpreterSpec where
import Category
import Control.Monad.Free (wrap)
import Data.Functor.Both
import Data.Functor.Foldable hiding (Nil)
import Data.Functor.Listable
@ -9,7 +10,6 @@ import Data.Record
import Diff
import Interpreter
import Patch
import Prologue
import Syntax
import Term
import Test.Hspec (Spec, describe, it, parallel)

View File

@ -1,6 +1,7 @@
module PatchOutputSpec where
import Prologue
import Control.Comonad.Trans.Cofree (CofreeF(..))
import Control.Monad.Free (wrap)
import Data.Blob
import Data.Functor.Both
import Data.Range
@ -14,4 +15,4 @@ spec :: Spec
spec = parallel $ do
describe "hunks" $ do
it "empty diffs have empty hunks" $
hunks (wrap $ pure (Range 0 0 :. Nil) :< Leaf ("" :: Text)) (both (Blob mempty "abcde" "path2.txt" (Just defaultPlainBlob) Nothing) (Blob mempty "xyz" "path2.txt" (Just defaultPlainBlob) Nothing)) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}]
hunks (wrap $ pure (Range 0 0 :. Nil) :< Leaf "") (both (Blob mempty "abcde" "path2.txt" (Just defaultPlainBlob) Nothing) (Blob mempty "xyz" "path2.txt" (Just defaultPlainBlob) Nothing)) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}]

View File

@ -1,7 +1,6 @@
module SES.Myers.Spec where
import Data.These
import Prologue
import SES.Myers
import Test.Hspec
import Test.Hspec.LeanCheck

View File

@ -1,12 +1,16 @@
{-# LANGUAGE DuplicateRecordFields #-}
module SemanticCmdLineSpec where
import Control.Monad (when)
import Data.ByteString (ByteString)
import Data.Foldable (for_)
import Data.Functor.Both
import Data.Semigroup ((<>))
import Language
import Prologue
import Renderer
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,10 +1,11 @@
module SemanticSpec where
import Control.Comonad.Cofree (Cofree(..))
import Data.Blob
import Data.Functor (void)
import Data.Functor.Both as Both
import Language
import Patch
import Prologue
import Renderer
import Semantic
import Semantic.Task
@ -17,11 +18,11 @@ spec = parallel $ do
describe "parseBlob" $ do
it "parses in the specified language" $ do
Just term <- runTask $ parseBlob IdentityTermRenderer methodsBlob
void term `shouldBe` cofree (() :< Indexed [ cofree (() :< Method [] (cofree (() :< Leaf "foo")) Nothing [] []) ])
void term `shouldBe` (() :< Indexed [ () :< Method [] (() :< Leaf "foo") Nothing [] [] ])
it "parses line by line if not given a language" $ do
Just term <- runTask $ parseBlob IdentityTermRenderer methodsBlob { blobLanguage = Nothing }
void term `shouldBe` cofree (() :< Indexed [ cofree (() :< Leaf "def foo\n"), cofree (() :< Leaf "end\n"), cofree (() :< Leaf "") ])
void term `shouldBe` (() :< Indexed [ () :< Leaf "def foo\n", () :< Leaf "end\n", () :< Leaf "" ])
it "renders with the specified renderer" $ do
output <- runTask $ parseBlob SExpressionTermRenderer methodsBlob
@ -29,11 +30,11 @@ spec = parallel $ do
describe "diffTermPair" $ do
it "produces an Insert when the first blob is missing" $ do
result <- runTask (diffTermPair (both (emptyBlob "/foo") (sourceBlob "/foo" Nothing "")) (runBothWith replacing) (pure (cofree (() :< []))))
result <- runTask (diffTermPair (both (emptyBlob "/foo") (sourceBlob "/foo" Nothing "")) (runBothWith replacing) (pure (() :< [])))
(() <$) <$> result `shouldBe` pure (Insert ())
it "produces a Delete when the second blob is missing" $ do
result <- runTask (diffTermPair (both (sourceBlob "/foo" Nothing "") (emptyBlob "/foo")) (runBothWith replacing) (pure (cofree (() :< []))))
result <- runTask (diffTermPair (both (sourceBlob "/foo" Nothing "") (emptyBlob "/foo")) (runBothWith replacing) (pure (() :< [])))
(() <$) <$> result `shouldBe` pure (Delete ())
where

View File

@ -1,10 +1,11 @@
module SourceSpec where
import Data.Char (chr)
import Data.Range
import Data.Semigroup
import Data.Source
import Data.Span
import qualified Data.Text as Text
import Prologue hiding (list)
import Test.Hspec
import Test.Hspec.LeanCheck
import Test.LeanCheck

View File

@ -1,6 +1,5 @@
module Main where
import Prologue
import qualified AlignmentSpec
import qualified CommandSpec
import qualified Data.Mergeable.Spec

View File

@ -7,15 +7,19 @@ module SpecHelpers
, unListableDiff
) where
import Control.Exception
import Control.Monad.Free (Free, hoistFree)
import Data.Bifunctor (first)
import Data.Blob
import qualified Data.ByteString as B
import Data.Functor.Both
import Data.Functor.Listable
import Data.Maybe (fromMaybe)
import Data.Source
import Diff
import Language
import Patch
import Prologue hiding (readFile)
import Prelude hiding (readFile)
import Renderer
import Semantic
import Semantic.Task
@ -23,13 +27,13 @@ import System.FilePath
import Term
-- | Returns an s-expression formatted diff for the specified FilePath pair.
diffFilePaths :: Both FilePath -> IO ByteString
diffFilePaths :: Both FilePath -> IO B.ByteString
diffFilePaths paths = do
blobs <- traverse readFile paths
runTask (diffBlobPair SExpressionDiffRenderer blobs)
-- | Returns an s-expression parse tree for the specified FilePath.
parseFilePath :: FilePath -> IO ByteString
parseFilePath :: FilePath -> IO B.ByteString
parseFilePath path = do
blob <- readFile path
runTask (parseBlob SExpressionTermRenderer blob)
@ -46,7 +50,7 @@ readFile path = do
-- | Returns a Maybe Language based on the FilePath's extension.
languageForFilePath :: FilePath -> Maybe Language
languageForFilePath = languageForType . toS . takeExtension
languageForFilePath = languageForType . takeExtension
-- | Extract a 'Diff' from a 'ListableF' enumerated by a property test.
unListableDiff :: Functor f => ListableF (Free (TermF f (ListableF (Join (,)) annotation))) (Patch (ListableF (Term f) annotation)) -> Diff f annotation

View File

@ -2,14 +2,23 @@
{-# LANGUAGE DataKinds, TypeOperators #-}
module TOCSpec where
import Data.Aeson
import Category as C
import Control.Comonad (extract)
import Control.Comonad.Trans.Cofree (headF)
import Control.Monad.Free (wrap)
import Data.Aeson
import Data.Blob
import Data.ByteString (ByteString)
import Data.Function (on)
import Data.Functor.Both
import Data.Functor.Listable
import Data.Maybe (fromMaybe)
import Data.Monoid (Last(..))
import Data.Output
import Data.Record
import Data.Semigroup ((<>))
import Data.Source
import Data.Text (Text)
import Data.Text.Listable
import Data.These
import Diff
@ -17,7 +26,7 @@ import Info
import Interpreter
import Language
import Patch
import Prologue hiding (fst, snd, readFile)
import Prelude hiding (readFile)
import Renderer
import Renderer.TOC
import RWS
@ -51,7 +60,7 @@ spec = parallel $ do
prop "produces changed entries for relevant nodes containing irrelevant patches" $
\ diff -> let diff' = fmap (1 <$) <$> mapAnnotations (const (0 :: Int)) (wrap (pure 0 :< Indexed [unListableDiff diff :: Diff Syntax Int])) in
tableOfContentsBy (\ (n :< _) -> if n == 0 then Just n else Nothing) diff' `shouldBe`
if Prologue.null diff' then [Unchanged 0]
if null diff' then [Unchanged 0]
else replicate (length diff') (Changed 0)
describe "diffTOC" $ do

View File

@ -3,7 +3,6 @@ module TermSpec where
import Category
import Data.Functor.Listable
import Prologue
import Term
import Test.Hspec (Spec, describe, parallel)
import Test.Hspec.Expectations.Pretty

View File

@ -6,9 +6,10 @@ module Test.Hspec.LeanCheck
import Control.Exception
import Data.Bifunctor (first)
import Data.String (String)
import Data.List (intercalate)
import Data.Maybe (listToMaybe)
import Data.Typeable
import GHC.Show as Show (showsPrec)
import Prologue
import Test.Hspec
import Test.Hspec.Core.Spec as Hspec
import qualified Test.HUnit.Lang as HUnit