mirror of
https://github.com/github/semantic.git
synced 2024-11-24 08:54:07 +03:00
Merge remote-tracking branch 'origin/master' into syntax-redux
This commit is contained in:
commit
70f9329f1a
@ -25,7 +25,6 @@ library
|
||||
, Diff
|
||||
, Diff.Arbitrary
|
||||
, Diffing
|
||||
, DiffOutput
|
||||
, Info
|
||||
, Interpreter
|
||||
, Language
|
||||
|
@ -23,7 +23,6 @@ import Diff
|
||||
import Info
|
||||
import Patch
|
||||
import Prologue hiding (fst, snd)
|
||||
import qualified Prologue
|
||||
import Range
|
||||
import Source hiding (break, fromList, uncons, (++))
|
||||
import SplitDiff
|
||||
@ -43,11 +42,11 @@ hasChanges :: SplitDiff leaf annotation -> Bool
|
||||
hasChanges = or . (True <$)
|
||||
|
||||
-- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side.
|
||||
alignDiff :: (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))]
|
||||
alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) (free . Free) getRange sources) (alignPatch sources <$> diff)
|
||||
alignDiff :: HasField fields Range => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))]
|
||||
alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) wrap getRange sources) (alignPatch sources <$> diff)
|
||||
|
||||
-- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff.
|
||||
alignPatch :: forall fields leaf. (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Patch (Term leaf (Record fields)) -> [Join These (SplitDiff leaf (Record fields))]
|
||||
alignPatch :: forall fields leaf. HasField fields Range => Both (Source Char) -> Patch (Term leaf (Record fields)) -> [Join These (SplitDiff leaf (Record fields))]
|
||||
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
|
||||
@ -61,7 +60,7 @@ alignPatch sources patch = case patch of
|
||||
that = Join . That . runIdentity
|
||||
|
||||
-- | The Applicative instance f is either Identity or Both. Identity is for Terms in Patches, Both is for Diffs in unchanged portions of the diff.
|
||||
alignSyntax :: (Applicative f, Show term, HasField fields Range) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) (Record fields) term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term]
|
||||
alignSyntax :: (Applicative f, HasField fields Range) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) (Record fields) term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term]
|
||||
alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax of
|
||||
Leaf s -> catMaybes $ wrapInBranch (const (Leaf s)) . fmap (flip (,) []) <$> sequenceL lineRanges
|
||||
Comment a -> catMaybes $ wrapInBranch (const (Comment a)) . fmap (flip (,) []) <$> sequenceL lineRanges
|
||||
@ -99,10 +98,9 @@ alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax
|
||||
where bothRanges = modifyJoin (fromThese [] []) lineRanges
|
||||
lineRanges = toJoinThese $ actualLineRanges <$> (characterRange <$> infos) <*> sources
|
||||
wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (setCharacterRange info range :< constructor children)) <$> infos)
|
||||
pairWithKey (key, values) = fmap ((,) key) <$> values
|
||||
|
||||
-- | Given a function to get the range, a list of already-aligned children, and the lists of ranges spanned by a branch, return the aligned lines.
|
||||
alignBranch :: Show term => (term -> Range) -> [Join These term] -> Both [Range] -> [Join These (Range, [term])]
|
||||
alignBranch :: (term -> Range) -> [Join These term] -> Both [Range] -> [Join These (Range, [term])]
|
||||
-- There are no more ranges, so we’re done.
|
||||
alignBranch _ _ (Join ([], [])) = []
|
||||
-- There are no more children, so we can just zip the remaining ranges together.
|
||||
|
@ -1,24 +1,33 @@
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
module Data.RandomWalkSimilarity where
|
||||
|
||||
import Control.Arrow ((&&&))
|
||||
import Control.Monad.Random
|
||||
import Control.Monad.State
|
||||
import qualified Data.DList as DList
|
||||
import Data.Functor.Both hiding (fst, snd)
|
||||
import Data.Functor.Foldable as Foldable
|
||||
import Data.Hashable
|
||||
import qualified Data.KdTree.Static as KdTree
|
||||
import qualified Data.List as List
|
||||
import qualified Data.Vector as Vector
|
||||
import Diff
|
||||
import Patch
|
||||
import Prologue
|
||||
import Syntax
|
||||
import Term
|
||||
import Term ()
|
||||
import Test.QuickCheck hiding (Fixed)
|
||||
import Test.QuickCheck.Random
|
||||
|
||||
-- | Given a function comparing two terms recursively, and a function to compute a Hashable label from an annotation, compute the diff of a pair of lists of terms using a random walk similarity metric, which completes in log-linear time. This implementation is based on the paper [_RWS-Diff—Flexible and Efficient Change Detection in Hierarchical Data_](https://github.com/github/semantic-diff/files/325837/RWS-Diff.Flexible.and.Efficient.Change.Detection.in.Hierarchical.Data.pdf).
|
||||
rws :: (Hashable label, Hashable leaf, Eq leaf, Eq annotation) => (Term leaf annotation -> Term leaf annotation -> Maybe (Diff leaf annotation)) -> (annotation -> label) -> [Term leaf annotation] -> [Term leaf annotation] -> [Diff leaf annotation]
|
||||
-- | Given a function comparing two terms recursively, and a function to compute a Hashable label from an unpacked term, compute the diff of a pair of lists of terms using a random walk similarity metric, which completes in log-linear time. This implementation is based on the paper [_RWS-Diff—Flexible and Efficient Change Detection in Hierarchical Data_](https://github.com/github/semantic-diff/files/325837/RWS-Diff.Flexible.and.Efficient.Change.Detection.in.Hierarchical.Data.pdf).
|
||||
rws :: (Hashable label, Eq annotation, Prologue.Foldable f, Functor f, Eq (f (Cofree f annotation))) =>
|
||||
-- | A function which comapres a pair of terms recursively, returning 'Just' their diffed value if appropriate, or 'Nothing' if they should not be compared.
|
||||
(Cofree f annotation -> Cofree f annotation -> Maybe (Free (CofreeF f (Both annotation)) (Patch (Cofree f annotation)))) ->
|
||||
-- | A function to compute a label for an unpacked term.
|
||||
(forall b. CofreeF f annotation b -> label) ->
|
||||
-- | The old list of terms.
|
||||
[Cofree f annotation] ->
|
||||
-- | The new list of terms.
|
||||
[Cofree f annotation] ->
|
||||
[Free (CofreeF f (Both annotation)) (Patch (Cofree f annotation))]
|
||||
rws compare getLabel as bs
|
||||
| null as, null bs = []
|
||||
| null as = insert <$> bs
|
||||
@ -44,29 +53,24 @@ rws compare getLabel as bs
|
||||
deleteRemaining diffs (_, unmapped) = foldl' (flip (List.insertBy (comparing fst))) diffs ((termIndex &&& delete . term) <$> unmapped)
|
||||
|
||||
-- | A term which has not yet been mapped by `rws`, along with its feature vector summary & index.
|
||||
data UnmappedTerm leaf annotation = UnmappedTerm { termIndex :: {-# UNPACK #-} !Int, feature :: !(Vector.Vector Double), term :: !(Term leaf annotation) }
|
||||
data UnmappedTerm a = UnmappedTerm { termIndex :: {-# UNPACK #-} !Int, feature :: !(Vector.Vector Double), term :: !a }
|
||||
deriving Eq
|
||||
|
||||
|
||||
-- | A `Gram` is a fixed-size view of some portion of a tree, consisting of a `stem` of _p_ labels for parent nodes, and a `base` of _q_ labels of sibling nodes. Collectively, the bag of `Gram`s for each node of a tree (e.g. as computed by `pqGrams`) form a summary of the tree.
|
||||
data Gram label = Gram { stem :: [Maybe label], base :: [Maybe label] }
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | Compute the bag of grams with stems of length _p_ and bases of length _q_, with labels computed from annotations, which summarize the entire subtree of a term.
|
||||
pqGrams :: Int -> Int -> (annotation -> label) -> Cofree (Syntax leaf) annotation -> DList.DList (Gram (label, Maybe leaf))
|
||||
pqGrams p q getLabel = cata merge . setRootBase . setRootStem . hylo go project
|
||||
where go (annotation :< functor) = cofree (Gram [] [ Just (getLabel annotation, leafValue functor) ] :< (assignParent (Just (getLabel annotation, leafValue functor)) p <$> functor))
|
||||
leafValue (Leaf s) = Just s
|
||||
leafValue _ = Nothing
|
||||
merge (head :< tail) = DList.cons head (Prologue.fold tail)
|
||||
pqGrams :: (Prologue.Foldable f, Functor f) => Int -> Int -> (forall b. CofreeF f annotation b -> label) -> Cofree f annotation -> DList.DList (Gram label)
|
||||
pqGrams p q getLabel = uncurry DList.cons . cata merge . setRootBase . setRootStem . cata go
|
||||
where go c = cofree (Gram [] [ Just (getLabel c) ] :< (assignParent (Just (getLabel c)) p <$> tailF c))
|
||||
merge (head :< tail) = let tail' = toList tail in (head, DList.fromList (windowed q setBases [] (fst <$> tail')) <> foldMap snd tail')
|
||||
assignParent parentLabel n tree
|
||||
| n > 0 = let gram :< functor = runCofree tree in cofree $ prependParent parentLabel gram :< assignSiblings (assignParent parentLabel (pred n) <$> functor)
|
||||
| n > 0 = let gram :< functor = runCofree tree in cofree $ prependParent parentLabel gram :< (assignParent parentLabel (pred n) <$> functor)
|
||||
| otherwise = tree
|
||||
prependParent parentLabel gram = gram { stem = parentLabel : stem gram }
|
||||
assignSiblings functor = case functor of
|
||||
Leaf a -> Leaf a
|
||||
Indexed a -> Indexed $ windowed q setBases [] a
|
||||
Fixed a -> Fixed $ windowed q setBases [] a
|
||||
setBases child siblings rest = let (gram :< further) = (runCofree child) in cofree (setBase gram (siblings >>= base . extract) :< further) : rest
|
||||
setBases gram siblings rest = setBase gram (siblings >>= base) : rest
|
||||
setBase gram newBase = gram { base = take q (newBase <> repeat Nothing) }
|
||||
setRootBase term = let (a :< f) = runCofree term in cofree (setBase a (base a) :< f)
|
||||
setRootStem = foldr (\ p rest -> assignParent Nothing p . rest) identity [0..p]
|
||||
|
@ -1,48 +0,0 @@
|
||||
module DiffOutput where
|
||||
|
||||
import Prologue
|
||||
import qualified Data.Text.IO as TextIO
|
||||
import Data.Functor.Both
|
||||
import Diffing
|
||||
import Parser
|
||||
import qualified Renderer.JSON as J
|
||||
import qualified Renderer.Patch as P
|
||||
import qualified Renderer.Summary as S
|
||||
import Renderer
|
||||
import Renderer.Split
|
||||
import Source
|
||||
import System.Directory
|
||||
import System.FilePath
|
||||
import qualified System.IO as IO
|
||||
|
||||
-- | Returns a rendered diff given a parser, diff arguments and two source blobs.
|
||||
textDiff :: Parser -> DiffArguments -> Both SourceBlob -> IO Text
|
||||
textDiff parser arguments sources = case format arguments of
|
||||
Split -> diffFiles parser split sources
|
||||
Patch -> diffFiles parser P.patch sources
|
||||
JSON -> diffFiles parser J.json sources
|
||||
Summary -> diffFiles parser S.summary sources
|
||||
|
||||
-- | Returns a truncated diff given diff arguments and two source blobs.
|
||||
truncatedDiff :: DiffArguments -> Both SourceBlob -> IO Text
|
||||
truncatedDiff arguments sources = case format arguments of
|
||||
Split -> pure ""
|
||||
Patch -> pure $ P.truncatePatch arguments sources
|
||||
JSON -> pure "{}"
|
||||
Summary -> pure ""
|
||||
|
||||
-- | Prints a rendered diff to stdio or a filepath given a parser, diff arguments and two source blobs.
|
||||
printDiff :: Parser -> DiffArguments -> Both SourceBlob -> IO ()
|
||||
printDiff parser arguments sources = case format arguments of
|
||||
Split -> put (output arguments) =<< diffFiles parser split sources
|
||||
where
|
||||
put Nothing rendered = TextIO.putStr rendered
|
||||
put (Just path) rendered = do
|
||||
isDir <- doesDirectoryExist path
|
||||
let outputPath = if isDir
|
||||
then path </> (takeFileName outputPath -<.> ".html")
|
||||
else path
|
||||
IO.withFile outputPath IO.WriteMode (`TextIO.hPutStr` rendered)
|
||||
Patch -> TextIO.putStr =<< diffFiles parser P.patch sources
|
||||
JSON -> TextIO.putStr =<< diffFiles parser J.json sources
|
||||
Summary -> TextIO.putStr =<< diffFiles parser S.summary sources
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
module Diffing where
|
||||
|
||||
import Prologue hiding (fst, snd)
|
||||
@ -5,7 +6,7 @@ import qualified Data.ByteString.Char8 as B1
|
||||
import Data.Functor.Both
|
||||
import Data.Functor.Foldable
|
||||
import Data.Record
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as TextIO
|
||||
import qualified Data.Text.ICU.Detect as Detect
|
||||
import qualified Data.Text.ICU.Convert as Convert
|
||||
import Data.These
|
||||
@ -18,15 +19,22 @@ import Parser
|
||||
import Patch
|
||||
import Range
|
||||
import Renderer
|
||||
import Renderer.JSON
|
||||
import Renderer.Patch
|
||||
import Renderer.Split
|
||||
import Renderer.Summary
|
||||
import Source hiding ((++))
|
||||
import Syntax
|
||||
import System.Directory
|
||||
import System.FilePath
|
||||
import qualified System.IO as IO
|
||||
import Term
|
||||
import TreeSitter
|
||||
import Text.Parser.TreeSitter.Language
|
||||
import qualified Data.Text as T
|
||||
|
||||
-- | Return a parser based on the file extension (including the ".").
|
||||
parserForType :: T.Text -> Parser
|
||||
parserForType :: Text -> Parser '[Range, Category, Cost]
|
||||
parserForType mediaType = case languageForType mediaType of
|
||||
Just C -> treeSitterParser C ts_language_c
|
||||
Just JavaScript -> treeSitterParser JavaScript ts_language_javascript
|
||||
@ -34,35 +42,33 @@ parserForType mediaType = case languageForType mediaType of
|
||||
_ -> lineByLineParser
|
||||
|
||||
-- | A fallback parser that treats a file simply as rows of strings.
|
||||
lineByLineParser :: Parser
|
||||
lineByLineParser :: Parser '[Range, Category, Cost]
|
||||
lineByLineParser input = pure . cofree . root $ case foldl' annotateLeaves ([], 0) lines of
|
||||
(leaves, _) -> cofree <$> leaves
|
||||
where
|
||||
lines = actualLines input
|
||||
root children = let size = 1 + fromIntegral (length children) in
|
||||
((Range 0 $ length input) .: Other "program" .: size .: Cost (unSize size) .: RNil) :< Indexed children
|
||||
leaf charIndex line = ((Range charIndex $ charIndex + T.length line) .: Other "program" .: 1 .: 1 .: RNil) :< Leaf line
|
||||
root children = let cost = 1 + fromIntegral (length children) in
|
||||
((Range 0 $ length input) .: Other "program" .: cost .: RNil) :< Indexed children
|
||||
leaf charIndex line = ((Range charIndex $ charIndex + T.length line) .: Other "program" .: 1 .: RNil) :< Leaf line
|
||||
annotateLeaves (accum, charIndex) line =
|
||||
(accum ++ [ leaf charIndex (toText line) ]
|
||||
, charIndex + length line)
|
||||
(accum <> [ leaf charIndex (toText line) ] , charIndex + length line)
|
||||
toText = T.pack . Source.toString
|
||||
|
||||
-- | Return the parser that should be used for a given path.
|
||||
parserForFilepath :: FilePath -> Parser
|
||||
parserForFilepath = parserForType . T.pack . takeExtension
|
||||
parserForFilepath :: FilePath -> Parser '[Range, Category, Cost]
|
||||
parserForFilepath = parserForType . toS . takeExtension
|
||||
|
||||
-- | Replace every string leaf with leaves of the words in the string.
|
||||
breakDownLeavesByWord :: Source Char -> Term T.Text Info -> Term T.Text Info
|
||||
breakDownLeavesByWord :: (HasField fields Category, HasField fields Cost, HasField fields Range) => Source Char -> Term Text (Record fields) -> Term Text (Record fields)
|
||||
breakDownLeavesByWord source = cata replaceIn
|
||||
where
|
||||
replaceIn :: TermF T.Text Info (Term T.Text Info) -> Term T.Text Info
|
||||
replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ setCost (setSize info size') (Cost (unSize size')) :< syntax'
|
||||
replaceIn (info :< syntax) = let cost' = 1 + sum (cost . extract <$> syntax') in cofree $ setCost info cost' :< syntax'
|
||||
where syntax' = case (ranges, syntax) of
|
||||
(_:_:_, Leaf _) | category info /= Regex -> Indexed (makeLeaf info <$> ranges)
|
||||
_ -> syntax
|
||||
ranges = rangesAndWordsInSource (characterRange info)
|
||||
rangesAndWordsInSource range = rangesAndWordsFrom (start range) (toString $ slice range source)
|
||||
makeLeaf info (range, substring) = cofree $ setCharacterRange info range :< Leaf (T.pack substring)
|
||||
makeLeaf info (range, substring) = cofree $ setCharacterRange info range :< Leaf (toS substring)
|
||||
|
||||
-- | Transcode a file to a unicode source.
|
||||
transcode :: B1.ByteString -> IO (Source Char)
|
||||
@ -81,7 +87,7 @@ readAndTranscodeFile path = do
|
||||
-- | result.
|
||||
-- | Returns the rendered result strictly, so it's always fully evaluated
|
||||
-- | with respect to other IO actions.
|
||||
diffFiles :: Parser -> Renderer -> Both SourceBlob -> IO T.Text
|
||||
diffFiles :: (HasField fields Category, HasField fields Cost, HasField fields Range, Eq (Record fields)) => Parser fields -> Renderer (Record fields) -> Both SourceBlob -> IO Text
|
||||
diffFiles parser renderer sourceBlobs = do
|
||||
let sources = source <$> sourceBlobs
|
||||
terms <- sequence $ parser <$> sources
|
||||
@ -92,11 +98,10 @@ diffFiles parser renderer sourceBlobs = do
|
||||
(True, False) -> pure $ Insert (snd terms)
|
||||
(False, True) -> pure $ Delete (fst terms)
|
||||
(_, _) ->
|
||||
runBothWith (diffTerms construct shouldCompareTerms diffCostWithCachedTermSizes) $ replaceLeaves <*> terms
|
||||
runBothWith (diffTerms construct shouldCompareTerms diffCostWithCachedTermCosts) (replaceLeaves <*> terms)
|
||||
|
||||
pure $! renderer textDiff sourceBlobs
|
||||
where construct :: CofreeF (Syntax Text) (Both Info) (Diff Text Info) -> Diff Text Info
|
||||
construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax))
|
||||
where construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax))
|
||||
sumCost = fmap getSum . foldMap (fmap Sum . getCost)
|
||||
getCost diff = case runFree diff of
|
||||
Free (info :< _) -> cost <$> info
|
||||
@ -104,7 +109,37 @@ diffFiles parser renderer sourceBlobs = do
|
||||
shouldCompareTerms = (==) `on` category . extract
|
||||
|
||||
-- | The sum of the node count of the diff’s patches.
|
||||
diffCostWithCachedTermSizes :: Diff a Info -> Integer
|
||||
diffCostWithCachedTermSizes diff = unCost $ case runFree diff of
|
||||
diffCostWithCachedTermCosts :: HasField fields Cost => Diff leaf (Record fields) -> Integer
|
||||
diffCostWithCachedTermCosts diff = unCost $ case runFree diff of
|
||||
Free (info :< _) -> sum (cost <$> info)
|
||||
Pure patch -> sum (cost . extract <$> patch)
|
||||
|
||||
|
||||
-- | Returns a rendered diff given a parser, diff arguments and two source blobs.
|
||||
textDiff :: (Eq (Record fields), HasField fields Category, HasField fields Cost, HasField fields Range) => Parser fields -> DiffArguments -> Both SourceBlob -> IO Text
|
||||
textDiff parser arguments sources = case format arguments of
|
||||
Split -> diffFiles parser split sources
|
||||
Patch -> diffFiles parser patch sources
|
||||
JSON -> diffFiles parser json sources
|
||||
Summary -> diffFiles parser summary sources
|
||||
|
||||
-- | Returns a truncated diff given diff arguments and two source blobs.
|
||||
truncatedDiff :: DiffArguments -> Both SourceBlob -> IO Text
|
||||
truncatedDiff arguments sources = case format arguments of
|
||||
Split -> pure ""
|
||||
Patch -> pure $ truncatePatch arguments sources
|
||||
JSON -> pure "{}"
|
||||
Summary -> pure ""
|
||||
|
||||
-- | Prints a rendered diff to stdio or a filepath given a parser, diff arguments and two source blobs.
|
||||
printDiff :: (Eq (Record fields), HasField fields Category, HasField fields Cost, HasField fields Range) => Parser fields -> DiffArguments -> Both SourceBlob -> IO ()
|
||||
printDiff parser arguments sources = do
|
||||
rendered <- textDiff parser arguments sources
|
||||
case (output arguments) of
|
||||
Nothing -> TextIO.putStr rendered
|
||||
Just path -> do
|
||||
isDir <- doesDirectoryExist path
|
||||
let outputPath = if isDir
|
||||
then path </> (takeFileName outputPath -<.> ".html")
|
||||
else path
|
||||
IO.withFile outputPath IO.WriteMode (`TextIO.hPutStr` rendered)
|
||||
|
17
src/Info.hs
17
src/Info.hs
@ -7,15 +7,9 @@ import Category
|
||||
import Range
|
||||
import Test.QuickCheck
|
||||
|
||||
newtype Size = Size { unSize :: Integer }
|
||||
deriving (Eq, Num, Ord, Show)
|
||||
newtype Cost = Cost { unCost :: Integer }
|
||||
deriving (Eq, Num, Ord, Show)
|
||||
|
||||
type InfoFields = '[ Range, Category, Size, Cost ]
|
||||
|
||||
type Info = Record InfoFields
|
||||
|
||||
characterRange :: HasField fields Range => Record fields -> Range
|
||||
characterRange = getField
|
||||
|
||||
@ -28,12 +22,6 @@ category = getField
|
||||
setCategory :: HasField fields Category => Record fields -> Category -> Record fields
|
||||
setCategory = setField
|
||||
|
||||
size :: HasField fields Size => Record fields -> Size
|
||||
size = getField
|
||||
|
||||
setSize :: HasField fields Size => Record fields -> Size -> Record fields
|
||||
setSize = setField
|
||||
|
||||
cost :: HasField fields Cost => Record fields -> Cost
|
||||
cost = getField
|
||||
|
||||
@ -43,11 +31,6 @@ setCost = setField
|
||||
|
||||
-- Instances
|
||||
|
||||
instance Arbitrary Size where
|
||||
arbitrary = Size <$> arbitrary
|
||||
|
||||
shrink = fmap Size . shrink . unSize
|
||||
|
||||
instance Arbitrary Cost where
|
||||
arbitrary = Cost <$> arbitrary
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
module Interpreter (Comparable, DiffConstructor, diffTerms) where
|
||||
|
||||
import Algorithm
|
||||
import qualified Category as C
|
||||
import Data.Align
|
||||
import Category (Category)
|
||||
import Data.Align.Generic
|
||||
import Data.Functor.Foldable
|
||||
import Data.Functor.Both hiding (fst, snd)
|
||||
@ -26,11 +25,11 @@ type Comparable leaf annotation = Term leaf annotation -> Term leaf annotation -
|
||||
type DiffConstructor leaf annotation = CofreeF (Syntax leaf) (Both annotation) (Diff leaf annotation) -> Diff leaf annotation
|
||||
|
||||
-- | Diff two terms, given a function that determines whether two terms can be compared and a cost function.
|
||||
diffTerms :: (Show leaf, Show (Record fields), Eq leaf, Hashable leaf, Ord (Record fields), HasField fields C.Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Diff leaf (Record fields)
|
||||
diffTerms :: (Eq leaf, Hashable leaf, Eq (Record fields), HasField fields Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Diff leaf (Record fields)
|
||||
diffTerms construct comparable cost a b = fromMaybe (pure $ Replace a b) $ constructAndRun construct comparable cost a b
|
||||
|
||||
-- | Constructs an algorithm and runs it
|
||||
constructAndRun :: (Show leaf, Show (Record fields), Eq leaf, Hashable leaf, Ord (Record fields), HasField fields C.Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Maybe (Diff leaf (Record fields))
|
||||
constructAndRun :: (Eq leaf, Hashable leaf, Eq (Record fields), HasField fields Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Maybe (Diff leaf (Record fields))
|
||||
constructAndRun construct comparable cost t1 t2
|
||||
| not $ comparable t1 t2 = Nothing
|
||||
| (category <$> t1) == (category <$> t2) = hylo construct runCofree <$> zipTerms t1 t2
|
||||
@ -43,17 +42,18 @@ constructAndRun construct comparable cost t1 t2
|
||||
annotate = pure . construct . (both annotation1 annotation2 :<)
|
||||
|
||||
-- | Runs the diff algorithm
|
||||
run :: (Show leaf, Show (Record fields), Eq leaf, Hashable leaf, Ord (Record fields), HasField fields C.Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Algorithm leaf (Record fields) (Diff leaf (Record fields)) -> Maybe (Diff leaf (Record fields))
|
||||
run :: (Eq leaf, Hashable leaf, Eq (Record fields), HasField fields Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Algorithm leaf (Record fields) (Diff leaf (Record fields)) -> Maybe (Diff leaf (Record fields))
|
||||
run construct comparable cost algorithm = case runFree algorithm of
|
||||
Pure diff -> Just diff
|
||||
Free (Recursive t1 t2 f) -> run construct comparable cost . f $ recur a b where
|
||||
(annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2)
|
||||
annotate = construct . (both annotation1 annotation2 :<)
|
||||
diffTerms' = diffTerms construct comparable cost
|
||||
recur a b = maybe (pure (Replace t1 t2)) (annotate . fmap diffThese) (galign a b)
|
||||
diffThese = these (pure . Delete) (pure . Insert) (diffTerms construct comparable cost)
|
||||
|
||||
Free (ByIndex a b f) -> run construct comparable cost . f $ ses (constructAndRun construct comparable cost) cost a b
|
||||
|
||||
Free (ByRandomWalkSimilarity a b f) -> run construct comparable cost . f $ rws (constructAndRun construct comparable cost) getLabel a b
|
||||
where getLabel = category
|
||||
where getLabel (h :< t) = (category h, case t of
|
||||
Leaf s -> Just s
|
||||
_ -> Nothing)
|
||||
|
@ -1,9 +1,12 @@
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
module Parser where
|
||||
|
||||
import Prologue hiding (Constructor)
|
||||
import Data.Record
|
||||
import Data.Text (pack)
|
||||
import Category
|
||||
import Info
|
||||
import Range
|
||||
import qualified Syntax as S
|
||||
import Term
|
||||
import qualified Data.Set as Set
|
||||
@ -12,10 +15,7 @@ import Source
|
||||
-- | A function that takes a source file and returns an annotated AST.
|
||||
-- | The return is in the IO monad because some of the parsers are written in C
|
||||
-- | and aren't pure.
|
||||
type Parser = Source Char -> IO (Term Text Info)
|
||||
|
||||
-- | A function which constructs a term from a source string, annotation, and children.
|
||||
type Constructor = Source Char -> Info -> [Term Text Info] -> Term Text Info
|
||||
type Parser fields = Source Char -> IO (Term Text (Record fields))
|
||||
|
||||
-- | Categories that are treated as fixed nodes.
|
||||
fixedCategories :: Set.Set Category
|
||||
@ -27,11 +27,10 @@ isFixed = flip Set.member fixedCategories
|
||||
|
||||
-- | Given a function that maps production names to sets of categories, produce
|
||||
-- | a Constructor.
|
||||
termConstructor :: Constructor
|
||||
termConstructor :: (Show (Record fields), HasField fields Category, HasField fields Range) => Source Char -> (Record fields) -> [Term Text (Record fields)] -> Term Text (Record fields)
|
||||
termConstructor source info = cofree . construct
|
||||
where
|
||||
withDefaultInfo syntax = (info :< syntax)
|
||||
construct :: [Term Text Info] -> CofreeF (S.Syntax Text) Info (Term Text Info)
|
||||
construct [] = withDefaultInfo . S.Leaf . pack . toString $ slice (characterRange info) source
|
||||
construct children | Assignment == category info = case children of
|
||||
(identifier:value:[]) -> withDefaultInfo $ S.Assignment identifier value
|
||||
@ -66,7 +65,7 @@ termConstructor source info = cofree . construct
|
||||
, [x, y] <- children = withDefaultInfo $ S.VarAssignment x y
|
||||
construct children | VarDecl == category info = withDefaultInfo . S.Indexed $ toVarDecl <$> children
|
||||
where
|
||||
toVarDecl :: Term Text Info -> Term Text Info
|
||||
toVarDecl :: (HasField fields Category) => Term Text (Record fields) -> Term Text (Record fields)
|
||||
toVarDecl child = cofree $ (setCategory (extract child) VarDecl :< S.VarDecl child)
|
||||
|
||||
construct children | Switch == category info, (expr:_) <- children =
|
||||
@ -77,7 +76,7 @@ termConstructor source info = cofree . construct
|
||||
|
||||
construct children | Object == category info = withDefaultInfo . S.Object $ foldMap toTuple children
|
||||
where
|
||||
toTuple :: Term Text Info -> [Term Text Info]
|
||||
toTuple :: Term Text (Record fields) -> [Term Text (Record fields)]
|
||||
toTuple child | S.Indexed [key,value] <- unwrap child = [cofree (extract child :< S.Pair key value)]
|
||||
toTuple child | S.Fixed [key,value] <- unwrap child = [cofree (extract child :< S.Pair key value)]
|
||||
toTuple child | S.Leaf c <- unwrap child = [cofree (extract child :< S.Comment c)]
|
||||
|
@ -3,11 +3,10 @@ module Renderer where
|
||||
import Prologue
|
||||
import Data.Functor.Both
|
||||
import Diff
|
||||
import Info
|
||||
import Source
|
||||
|
||||
-- | A function that will render a diff, given the two source files.
|
||||
type Renderer = Diff Text Info -> Both SourceBlob -> Text
|
||||
type Renderer annotation = Diff Text annotation -> Both SourceBlob -> Text
|
||||
|
||||
data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath, outputPath :: FilePath }
|
||||
deriving (Show)
|
||||
|
@ -10,6 +10,7 @@ import Category
|
||||
import Data.Aeson hiding (json)
|
||||
import Data.Bifunctor.Join
|
||||
import Data.ByteString.Builder
|
||||
import Data.Record
|
||||
import qualified Data.Text as T
|
||||
import Data.These
|
||||
import Data.Vector hiding (toList)
|
||||
@ -22,13 +23,13 @@ import Syntax as S
|
||||
import Term
|
||||
|
||||
-- | Render a diff to a string representing its JSON.
|
||||
json :: Renderer
|
||||
json :: (HasField fields Category, HasField fields Range) => Renderer (Record fields)
|
||||
json diff sources = toS . toLazyByteString . fromEncoding . pairs $ "rows" .= annotateRows (alignDiff (source <$> sources) diff) <> "oids" .= (oid <$> sources) <> "paths" .= (path <$> sources)
|
||||
where annotateRows = fmap (fmap NumberedLine) . numberedRows
|
||||
|
||||
newtype NumberedLine a = NumberedLine (Int, a)
|
||||
|
||||
instance ToJSON (NumberedLine (SplitDiff leaf Info)) where
|
||||
instance (HasField fields Category, HasField fields Range) => ToJSON (NumberedLine (SplitDiff leaf (Record fields))) where
|
||||
toJSON (NumberedLine (n, a)) = object (lineFields n a (getRange a))
|
||||
toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a (getRange a))
|
||||
instance ToJSON Category where
|
||||
@ -43,25 +44,25 @@ instance ToJSON a => ToJSON (Join These a) where
|
||||
instance ToJSON a => ToJSON (Join (,) a) where
|
||||
toJSON (Join (a, b)) = Array . fromList $ toJSON <$> [ a, b ]
|
||||
toEncoding = foldable
|
||||
instance ToJSON (SplitDiff leaf Info) where
|
||||
instance (HasField fields Category, HasField fields Range) => ToJSON (SplitDiff leaf (Record fields)) where
|
||||
toJSON splitDiff = case runFree splitDiff of
|
||||
(Free (info :< syntax)) -> object (termFields info syntax)
|
||||
(Pure patch) -> object (patchFields patch)
|
||||
toEncoding splitDiff = case runFree splitDiff of
|
||||
(Free (info :< syntax)) -> pairs $ mconcat (termFields info syntax)
|
||||
(Pure patch) -> pairs $ mconcat (patchFields patch)
|
||||
instance ToJSON (Term leaf Info) where
|
||||
instance (HasField fields Category, HasField fields Range) => ToJSON (Term leaf (Record fields)) where
|
||||
toJSON term | (info :< syntax) <- runCofree term = object (termFields info syntax)
|
||||
toEncoding term | (info :< syntax) <- runCofree term = pairs $ mconcat (termFields info syntax)
|
||||
|
||||
lineFields :: KeyValue kv => Int -> SplitDiff leaf Info -> Range -> [kv]
|
||||
lineFields :: (HasField fields Category, HasField fields Range) => KeyValue kv => Int -> SplitDiff leaf (Record fields) -> Range -> [kv]
|
||||
lineFields n term range = [ "number" .= n
|
||||
, "terms" .= [ term ]
|
||||
, "range" .= range
|
||||
, "hasChanges" .= hasChanges term
|
||||
]
|
||||
|
||||
termFields :: (ToJSON recur, KeyValue kv) => Info -> Syntax leaf recur -> [kv]
|
||||
termFields :: (ToJSON recur, KeyValue kv, HasField fields Category, HasField fields Range) => Record fields -> Syntax leaf recur -> [kv]
|
||||
termFields info syntax = "range" .= characterRange info : "category" .= category info : case syntax of
|
||||
Leaf _ -> []
|
||||
Indexed c -> childrenFields c
|
||||
@ -86,7 +87,7 @@ termFields info syntax = "range" .= characterRange info : "category" .= category
|
||||
S.Commented comments child -> childrenFields (comments <> maybeToList child)
|
||||
where childrenFields c = [ "children" .= c ]
|
||||
|
||||
patchFields :: KeyValue kv => SplitPatch (Term leaf Info) -> [kv]
|
||||
patchFields :: (KeyValue kv, HasField fields Category, HasField fields Range) => SplitPatch (Term leaf (Record fields)) -> [kv]
|
||||
patchFields patch = case patch of
|
||||
SplitInsert term -> fields "insert" term
|
||||
SplitDelete term -> fields "delete" term
|
||||
|
@ -9,27 +9,28 @@ import Alignment
|
||||
import Data.Bifunctor.Join
|
||||
import Data.Functor.Both as Both
|
||||
import Data.List (span, unzip)
|
||||
import Data.Record
|
||||
import Data.String
|
||||
import Data.Text (pack)
|
||||
import Data.These
|
||||
import Diff
|
||||
import Info
|
||||
import Patch
|
||||
import Prologue hiding (fst, snd)
|
||||
import Range
|
||||
import Renderer
|
||||
import Source hiding ((++), break)
|
||||
import Source hiding (break)
|
||||
import SplitDiff
|
||||
|
||||
-- | Render a timed out file as a truncated diff.
|
||||
truncatePatch :: DiffArguments -> Both SourceBlob -> Text
|
||||
truncatePatch _ blobs = pack $ header blobs ++ "#timed_out\nTruncating diff: timeout reached.\n"
|
||||
truncatePatch _ blobs = pack $ header blobs <> "#timed_out\nTruncating diff: timeout reached.\n"
|
||||
|
||||
-- | Render a diff in the traditional patch format.
|
||||
patch :: Renderer
|
||||
patch :: HasField fields Range => Renderer (Record fields)
|
||||
patch diff blobs = pack $ case getLast (foldMap (Last . Just) string) of
|
||||
Just c | c /= '\n' -> string ++ "\n\\ No newline at end of file\n"
|
||||
Just c | c /= '\n' -> string <> "\n\\ No newline at end of file\n"
|
||||
_ -> string
|
||||
where string = header blobs ++ mconcat (showHunk blobs <$> hunks diff blobs)
|
||||
where string = header blobs <> mconcat (showHunk blobs <$> hunks diff blobs)
|
||||
|
||||
-- | A hunk in a patch, including the offset, changes, and context.
|
||||
data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Join These a] }
|
||||
@ -52,65 +53,65 @@ rowIncrement :: Join These a -> Both (Sum Int)
|
||||
rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$)
|
||||
|
||||
-- | Given the before and after sources, render a hunk to a string.
|
||||
showHunk :: Both SourceBlob -> Hunk (SplitDiff a Info) -> String
|
||||
showHunk blobs hunk = maybeOffsetHeader ++
|
||||
concat (showChange sources <$> changes hunk) ++
|
||||
showHunk :: HasField fields Range => Both SourceBlob -> Hunk (SplitDiff a (Record fields)) -> String
|
||||
showHunk blobs hunk = maybeOffsetHeader <>
|
||||
concat (showChange sources <$> changes hunk) <>
|
||||
showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk)
|
||||
where sources = source <$> blobs
|
||||
maybeOffsetHeader = if lengthA > 0 && lengthB > 0
|
||||
then offsetHeader
|
||||
else mempty
|
||||
offsetHeader = "@@ -" ++ offsetA ++ "," ++ show lengthA ++ " +" ++ offsetB ++ "," ++ show lengthB ++ " @@" ++ "\n"
|
||||
offsetHeader = "@@ -" <> offsetA <> "," <> show lengthA <> " +" <> offsetB <> "," <> show lengthB <> " @@" <> "\n"
|
||||
(lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk
|
||||
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
|
||||
|
||||
-- | Given the before and after sources, render a change to a string.
|
||||
showChange :: Both (Source Char) -> Change (SplitDiff a Info) -> String
|
||||
showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) ++ deleted ++ inserted
|
||||
showChange :: HasField fields Range => Both (Source Char) -> Change (SplitDiff a (Record fields)) -> String
|
||||
showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) <> deleted <> inserted
|
||||
where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> both '-' '+' <*> Join (unzip (fromThese Nothing Nothing . runJoin . fmap Just <$> contents change))
|
||||
|
||||
-- | Given a source, render a set of lines to a string with a prefix.
|
||||
showLines :: Source Char -> Char -> [Maybe (SplitDiff leaf Info)] -> String
|
||||
showLines :: HasField fields Range => Source Char -> Char -> [Maybe (SplitDiff leaf (Record fields))] -> String
|
||||
showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines
|
||||
where prepend "" = ""
|
||||
prepend source = prefix : source
|
||||
|
||||
-- | Given a source, render a line to a string.
|
||||
showLine :: Source Char -> Maybe (SplitDiff leaf Info) -> Maybe String
|
||||
showLine :: HasField fields Range => Source Char -> Maybe (SplitDiff leaf (Record fields)) -> Maybe String
|
||||
showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line
|
||||
| otherwise = Nothing
|
||||
|
||||
-- | Returns the header given two source blobs and a hunk.
|
||||
header :: Both SourceBlob -> String
|
||||
header blobs = intercalate "\n" [filepathHeader, fileModeHeader, beforeFilepath, afterFilepath] ++ "\n"
|
||||
where filepathHeader = "diff --git a/" ++ pathA ++ " b/" ++ pathB
|
||||
header blobs = intercalate "\n" [filepathHeader, fileModeHeader, beforeFilepath, afterFilepath] <> "\n"
|
||||
where filepathHeader = "diff --git a/" <> pathA <> " b/" <> pathB
|
||||
fileModeHeader = case (modeA, modeB) of
|
||||
(Nothing, Just mode) -> intercalate "\n" [ "new file mode " ++ modeToDigits mode, blobOidHeader ]
|
||||
(Just mode, Nothing) -> intercalate "\n" [ "deleted file mode " ++ modeToDigits mode, blobOidHeader ]
|
||||
(Just mode, Just other) | mode == other -> "index " ++ oidA ++ ".." ++ oidB ++ " " ++ modeToDigits mode
|
||||
(Nothing, Just mode) -> intercalate "\n" [ "new file mode " <> modeToDigits mode, blobOidHeader ]
|
||||
(Just mode, Nothing) -> intercalate "\n" [ "deleted file mode " <> modeToDigits mode, blobOidHeader ]
|
||||
(Just mode, Just other) | mode == other -> "index " <> oidA <> ".." <> oidB <> " " <> modeToDigits mode
|
||||
(Just mode1, Just mode2) -> intercalate "\n" [
|
||||
"old mode " ++ modeToDigits mode1,
|
||||
"new mode " ++ modeToDigits mode2,
|
||||
"old mode " <> modeToDigits mode1,
|
||||
"new mode " <> modeToDigits mode2,
|
||||
blobOidHeader
|
||||
]
|
||||
(Nothing, Nothing) -> ""
|
||||
blobOidHeader = "index " ++ oidA ++ ".." ++ oidB
|
||||
blobOidHeader = "index " <> oidA <> ".." <> oidB
|
||||
modeHeader :: String -> Maybe SourceKind -> String -> String
|
||||
modeHeader ty maybeMode path = case maybeMode of
|
||||
Just _ -> ty ++ "/" ++ path
|
||||
Just _ -> ty <> "/" <> path
|
||||
Nothing -> "/dev/null"
|
||||
beforeFilepath = "--- " ++ modeHeader "a" modeA pathA
|
||||
afterFilepath = "+++ " ++ modeHeader "b" modeB pathB
|
||||
beforeFilepath = "--- " <> modeHeader "a" modeA pathA
|
||||
afterFilepath = "+++ " <> modeHeader "b" modeB pathB
|
||||
(pathA, pathB) = runJoin $ path <$> blobs
|
||||
(oidA, oidB) = runJoin $ oid <$> blobs
|
||||
(modeA, modeB) = runJoin $ blobKind <$> blobs
|
||||
|
||||
-- | A hunk representing no changes.
|
||||
emptyHunk :: Hunk (SplitDiff a Info)
|
||||
emptyHunk :: Hunk (SplitDiff a annotation)
|
||||
emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] }
|
||||
|
||||
-- | Render a diff as a series of hunks.
|
||||
hunks :: Show a => Diff a Info -> Both SourceBlob -> [Hunk (SplitDiff a Info)]
|
||||
hunks :: HasField fields Range => Diff a (Record fields) -> Both SourceBlob -> [Hunk (SplitDiff a (Record fields))]
|
||||
hunks _ blobs | sources <- source <$> blobs
|
||||
, sourcesEqual <- runBothWith (==) sources
|
||||
, sourcesNull <- runBothWith (&&) (null <$> sources)
|
||||
@ -120,14 +121,14 @@ hunks diff blobs = hunksInRows (pure 1) $ alignDiff (source <$> blobs) diff
|
||||
|
||||
-- | Given beginning line numbers, turn rows in a split diff into hunks in a
|
||||
-- | patch.
|
||||
hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)]
|
||||
hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> [Hunk (SplitDiff a annotation)]
|
||||
hunksInRows start rows = case nextHunk start rows of
|
||||
Nothing -> []
|
||||
Just (hunk, rest) -> hunk : hunksInRows (offset hunk <> hunkLength hunk) rest
|
||||
|
||||
-- | Given beginning line numbers, return the next hunk and the remaining rows
|
||||
-- | of the split diff.
|
||||
nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Join These (SplitDiff a Info)])
|
||||
nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> Maybe (Hunk (SplitDiff a annotation), [Join These (SplitDiff a annotation)])
|
||||
nextHunk start rows = case nextChange start rows of
|
||||
Nothing -> Nothing
|
||||
Just (offset, change, rest) -> let (changes, rest') = contiguousChanges rest in Just (Hunk offset (change : changes) $ take 3 rest', drop 3 rest')
|
||||
@ -139,7 +140,7 @@ nextHunk start rows = case nextChange start rows of
|
||||
|
||||
-- | Given beginning line numbers, return the number of lines to the next
|
||||
-- | the next change, and the remaining rows of the split diff.
|
||||
nextChange :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Both (Sum Int), Change (SplitDiff a Info), [Join These (SplitDiff a Info)])
|
||||
nextChange :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> Maybe (Both (Sum Int), Change (SplitDiff a annotation), [Join These (SplitDiff a annotation)])
|
||||
nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of
|
||||
Nothing -> Nothing
|
||||
Just (change, afterChanges) -> Just (start <> mconcat (rowIncrement <$> skippedContext), change, afterChanges)
|
||||
@ -149,12 +150,12 @@ nextChange start rows = case changeIncludingContext leadingContext afterLeadingC
|
||||
-- | Return a Change with the given context and the rows from the begginning of
|
||||
-- | the given rows that have changes, or Nothing if the first row has no
|
||||
-- | changes.
|
||||
changeIncludingContext :: [Join These (SplitDiff a Info)] -> [Join These (SplitDiff a Info)] -> Maybe (Change (SplitDiff a Info), [Join These (SplitDiff a Info)])
|
||||
changeIncludingContext :: [Join These (SplitDiff a annotation)] -> [Join These (SplitDiff a annotation)] -> Maybe (Change (SplitDiff a annotation), [Join These (SplitDiff a annotation)])
|
||||
changeIncludingContext leadingContext rows = case changes of
|
||||
[] -> Nothing
|
||||
_ -> Just (Change leadingContext changes, afterChanges)
|
||||
where (changes, afterChanges) = span rowHasChanges rows
|
||||
|
||||
-- | Whether a row has changes on either side.
|
||||
rowHasChanges :: Join These (SplitDiff a Info) -> Bool
|
||||
rowHasChanges :: Join These (SplitDiff a annotation) -> Bool
|
||||
rowHasChanges row = or (hasChanges <$> row)
|
||||
|
@ -1,5 +1,4 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Renderer.Split where
|
||||
module Renderer.Split (split) where
|
||||
|
||||
import Alignment
|
||||
import Category as C
|
||||
@ -7,6 +6,7 @@ import Data.Bifunctor.Join
|
||||
import Data.Foldable
|
||||
import Data.Functor.Both
|
||||
import Data.Functor.Foldable (cata)
|
||||
import Data.Record
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import Data.These
|
||||
import Info
|
||||
@ -14,7 +14,7 @@ import Prologue hiding (div, head, fst, snd, link)
|
||||
import qualified Prologue
|
||||
import Range
|
||||
import Renderer
|
||||
import Source hiding ((++))
|
||||
import Source
|
||||
import SplitDiff
|
||||
import Syntax
|
||||
import Term
|
||||
@ -24,10 +24,6 @@ import Text.Blaze.Html5 hiding (map)
|
||||
import qualified Text.Blaze.Html5.Attributes as A
|
||||
import qualified Text.Blaze.Internal as Blaze
|
||||
|
||||
-- | Return the first item in the Foldable, or Nothing if it's empty.
|
||||
maybeFirst :: Foldable f => f a -> Maybe a
|
||||
maybeFirst = foldr (const . Just) Nothing
|
||||
|
||||
-- | Add the first category from a Foldable of categories as a class name as a
|
||||
-- | class name on the markup, prefixed by `category-`.
|
||||
classifyMarkup :: Category -> Markup -> Markup
|
||||
@ -70,13 +66,13 @@ styleName category = "category-" <> case category of
|
||||
|
||||
-- | Pick the class name for a split patch.
|
||||
splitPatchToClassName :: SplitPatch a -> AttributeValue
|
||||
splitPatchToClassName patch = stringValue $ "patch " ++ case patch of
|
||||
splitPatchToClassName patch = stringValue $ "patch " <> case patch of
|
||||
SplitInsert _ -> "insert"
|
||||
SplitDelete _ -> "delete"
|
||||
SplitReplace _ -> "replace"
|
||||
|
||||
-- | Render a diff as an HTML split diff.
|
||||
split :: Renderer
|
||||
split :: (HasField fields Category, HasField fields Cost, HasField fields Range) => Renderer (Record fields)
|
||||
split diff blobs = TL.toStrict . renderHtml
|
||||
. docTypeHtml
|
||||
. ((head $ link ! A.rel "stylesheet" ! A.href "style.css") <>)
|
||||
@ -99,50 +95,19 @@ split diff blobs = TL.toStrict . renderHtml
|
||||
columnWidth = max (20 + digits maxNumber * 8) 40
|
||||
|
||||
-- | Render a line with numbers as an HTML row.
|
||||
numberedLinesToMarkup :: Join These (Int, SplitDiff a Info) -> Markup
|
||||
numberedLinesToMarkup numberedLines = tr $ runBothWith (<>) (renderLine <$> Join (fromThese Nothing Nothing (runJoin (Just <$> numberedLines))) <*> sources) <> string "\n"
|
||||
|
||||
renderLine :: Maybe (Int, SplitDiff leaf Info) -> Source Char -> Markup
|
||||
renderLine (Just (number, line)) source = toMarkup $ Renderable (hasChanges line, number, Renderable (source, line))
|
||||
renderLine (Just (number, line)) source = toMarkup $ Cell (hasChanges line) number (Renderable source line)
|
||||
renderLine _ _ =
|
||||
td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell")
|
||||
<> td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell")
|
||||
<> string "\n"
|
||||
|
||||
-- | Something that can be rendered as markup.
|
||||
newtype Renderable a = Renderable a
|
||||
-- | A cell in a table, characterized by whether it contains changes & its line number.
|
||||
data Cell a = Cell !Bool !Int !a
|
||||
|
||||
instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where
|
||||
toMarkup (Renderable (source, info, syntax)) = (! A.data_ (textValue (show . unSize $ size info))) . classifyMarkup (category info) $ case syntax of
|
||||
Leaf _ -> span . text . toText $ slice (characterRange info) source
|
||||
Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children
|
||||
Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children
|
||||
Comment _ -> span . text . toText $ slice (characterRange info) source
|
||||
Commented cs child -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (cs <> maybeToList child)
|
||||
Syntax.FunctionCall identifier children -> dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [identifier])) <> (wrapIn dd <$> contentElements source (characterRange info) children)
|
||||
Syntax.Function identifier params expressions -> ul . mconcat $ wrapIn li <$>
|
||||
contentElements source (characterRange info) (catMaybes [identifier, params, Just expressions])
|
||||
Syntax.MethodCall targetId methodId methodParams ->
|
||||
dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [targetId])) <> (wrapIn dd <$> contentElements source (characterRange info) [methodId, methodParams])
|
||||
Syntax.Args children -> ul . mconcat $ wrapIn li <$>
|
||||
contentElements source (characterRange info) children
|
||||
Syntax.MemberAccess memberId property -> ul . mconcat $ wrapIn li <$>
|
||||
contentElements source (characterRange info) [memberId, property]
|
||||
Syntax.Assignment memberId value -> ul . mconcat $ wrapIn li <$>
|
||||
contentElements source (characterRange info) [memberId, value]
|
||||
Syntax.VarDecl decl -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) [decl]
|
||||
Syntax.VarAssignment varId value ->
|
||||
dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [varId])) <> (wrapIn dd <$> contentElements source (characterRange info) [value])
|
||||
Syntax.Switch expr cases -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (expr : cases)
|
||||
Syntax.Case expr body -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) [expr, body]
|
||||
Syntax.Ternary expr cases -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (expr : cases)
|
||||
Syntax.MathAssignment id value ->
|
||||
dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [id])) <> (wrapIn dd <$> contentElements source (characterRange info) [value])
|
||||
Syntax.SubscriptAccess target property ->
|
||||
dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [target])) <> (wrapIn dd <$> contentElements source (characterRange info) [property])
|
||||
Syntax.Operator syntaxes -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) syntaxes
|
||||
Syntax.Object children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children
|
||||
Syntax.Pair a b -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) [a, b]
|
||||
-- | Something that can be rendered as markup with reference to some source.
|
||||
data Renderable a = Renderable !(Source Char) !a
|
||||
|
||||
contentElements :: (Foldable t, ToMarkup f) => Source Char -> Range -> t (f, Range) -> [Markup]
|
||||
contentElements source range children = let (elements, next) = foldr' (markupForContextAndChild source) ([], end range) children in
|
||||
@ -158,17 +123,27 @@ wrapIn _ l@Blaze.Content{} = l
|
||||
wrapIn _ l@Blaze.Comment{} = l
|
||||
wrapIn f p = f p
|
||||
|
||||
instance ToMarkup (Renderable (Source Char, Term a Info)) where
|
||||
toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) term
|
||||
|
||||
instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where
|
||||
toMarkup (Renderable (source, diff)) = Prologue.fst . iter (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) $ toMarkupAndRange <$> diff
|
||||
where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range)
|
||||
toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in
|
||||
((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show (unSize (size info))))) . toMarkup $ Renderable (source, cofree term), characterRange info)
|
||||
-- Instances
|
||||
|
||||
instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where
|
||||
toMarkup (Renderable (hasChanges, num, line)) =
|
||||
instance (ToMarkup f, HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (CofreeF (Syntax leaf) (Record fields) (f, Range))) where
|
||||
toMarkup (Renderable source (info :< syntax)) = classifyMarkup (category info) $ case syntax of
|
||||
Leaf _ -> span . string . toString $ slice (characterRange info) source
|
||||
_ -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (toList syntax)
|
||||
|
||||
instance (HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (Term leaf (Record fields))) where
|
||||
toMarkup (Renderable source term) = Prologue.fst $ cata (\ t -> (toMarkup $ Renderable source t, characterRange (headF t))) term
|
||||
|
||||
instance (HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (SplitDiff leaf (Record fields))) where
|
||||
toMarkup (Renderable source diff) = Prologue.fst . iter (\ t -> (toMarkup $ Renderable source t, characterRange (headF t))) $ toMarkupAndRange <$> diff
|
||||
where toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in
|
||||
((div ! patchAttribute patch `withCostAttribute` cost info) . toMarkup $ Renderable source (cofree term), characterRange info)
|
||||
patchAttribute patch = A.class_ (splitPatchToClassName patch)
|
||||
withCostAttribute a (Cost c) | c > 0 = a ! A.data_ (stringValue (show c))
|
||||
| otherwise = identity
|
||||
|
||||
instance ToMarkup a => ToMarkup (Cell a) where
|
||||
toMarkup (Cell hasChanges num line) =
|
||||
td (string $ show num) ! A.class_ (stringValue $ if hasChanges then "blob-num blob-num-replacement" else "blob-num")
|
||||
<> td (toMarkup line) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code")
|
||||
<> string "\n"
|
||||
|
@ -1,11 +1,14 @@
|
||||
module Renderer.Summary where
|
||||
|
||||
import Category
|
||||
import Prologue
|
||||
import Renderer
|
||||
import DiffSummary
|
||||
import Data.Aeson
|
||||
import Data.Record
|
||||
import Range
|
||||
import DiffSummary
|
||||
import Data.Text (pack)
|
||||
import Text.PrettyPrint.Leijen.Text (pretty)
|
||||
|
||||
summary :: Renderer
|
||||
summary :: (HasField fields Category, HasField fields Range) => Renderer (Record fields)
|
||||
summary diff _ = toS . encode $ pack . show . pretty <$> diffSummary diff
|
||||
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
module TreeSitter where
|
||||
|
||||
import Prologue hiding (Constructor)
|
||||
@ -14,7 +15,7 @@ import Text.Parser.TreeSitter hiding (Language(..))
|
||||
import qualified Text.Parser.TreeSitter as TS
|
||||
|
||||
-- | Returns a TreeSitter parser for the given language and TreeSitter grammar.
|
||||
treeSitterParser :: Language -> Ptr TS.Language -> Parser
|
||||
treeSitterParser :: Language -> Ptr TS.Language -> Parser '[Range, Category, Cost]
|
||||
treeSitterParser language grammar contents = do
|
||||
document <- ts_document_make
|
||||
ts_document_set_language document grammar
|
||||
@ -75,7 +76,7 @@ defaultCategoryForNodeName name = case name of
|
||||
_ -> Other name
|
||||
|
||||
-- | Return a parser for a tree sitter language & document.
|
||||
documentToTerm :: Language -> Ptr Document -> Parser
|
||||
documentToTerm :: Language -> Ptr Document -> Parser '[Range, Category, Cost]
|
||||
documentToTerm language document contents = alloca $ \ root -> do
|
||||
ts_document_root_node_p document root
|
||||
toTerm root
|
||||
@ -87,8 +88,8 @@ documentToTerm language document contents = alloca $ \ root -> do
|
||||
-- Note: The strict application here is semantically important. Without it, we may not evaluate the range until after we’ve exited the scope that `node` was allocated within, meaning `alloca` will free it & other stack data may overwrite it.
|
||||
range <- pure $! Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node }
|
||||
|
||||
let size' = 1 + sum (size . extract <$> children)
|
||||
let info = range .: (categoriesForLanguage language (toS name)) .: size' .: Cost (unSize size') .: RNil
|
||||
let cost' = 1 + sum (cost . extract <$> children)
|
||||
let info = range .: (categoriesForLanguage language (toS name)) .: cost' .: RNil
|
||||
pure $! termConstructor contents info children
|
||||
getChild node n out = do
|
||||
_ <- ts_node_p_named_child node n out
|
||||
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
module AlignmentSpec where
|
||||
|
||||
import Alignment
|
||||
@ -14,7 +15,6 @@ import Data.Record
|
||||
import Data.String
|
||||
import Data.Text.Arbitrary ()
|
||||
import Data.These
|
||||
import Info
|
||||
import Patch
|
||||
import Prologue hiding (fst, snd)
|
||||
import qualified Prologue
|
||||
@ -22,7 +22,6 @@ import Range
|
||||
import qualified Source
|
||||
import SplitDiff
|
||||
import Syntax
|
||||
import Category
|
||||
import Term
|
||||
import Test.Hspec
|
||||
import Test.Hspec.QuickCheck
|
||||
@ -32,7 +31,7 @@ spec :: Spec
|
||||
spec = parallel $ do
|
||||
describe "alignBranch" $ do
|
||||
it "produces symmetrical context" $
|
||||
alignBranch getRange ([] :: [Join These (SplitDiff String Info)]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
|
||||
alignBranch getRange ([] :: [Join These (SplitDiff String (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
|
||||
[ Join (These (Range 0 2, [])
|
||||
(Range 0 2, []))
|
||||
, Join (These (Range 2 4, [])
|
||||
@ -40,7 +39,7 @@ spec = parallel $ do
|
||||
]
|
||||
|
||||
it "produces asymmetrical context" $
|
||||
alignBranch getRange ([] :: [Join These (SplitDiff String Info)]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
|
||||
alignBranch getRange ([] :: [Join These (SplitDiff String (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
|
||||
[ Join (These (Range 0 2, [])
|
||||
(Range 0 1, []))
|
||||
, Join (This (Range 2 4, []))
|
||||
@ -254,13 +253,13 @@ instance Arbitrary BranchElement where
|
||||
counts :: [Join These (Int, a)] -> Both Int
|
||||
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered))
|
||||
|
||||
align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String Info)) (Both Info) -> PrettyDiff (SplitDiff String Info)
|
||||
align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff String (Record '[Range]))
|
||||
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
|
||||
|
||||
info :: Int -> Int -> Info
|
||||
info start end = Range start end .: StringLiteral .: 0 .: 0 .: RNil
|
||||
info :: Int -> Int -> Record '[Range]
|
||||
info start end = Range start end .: RNil
|
||||
|
||||
prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String Info)) Info)] -> PrettyDiff (SplitDiff String Info)
|
||||
prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff String (Record '[Range]))
|
||||
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))
|
||||
|
||||
data PrettyDiff a = PrettyDiff { unPrettySources :: Both (Source.Source Char), unPrettyLines :: [Join These (Range, a)] }
|
||||
@ -279,14 +278,14 @@ newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct ::
|
||||
|
||||
|
||||
class PatchConstructible p where
|
||||
insert :: Term String Info -> p
|
||||
delete :: Term String Info -> p
|
||||
insert :: Term String (Record '[Range]) -> p
|
||||
delete :: Term String (Record '[Range]) -> p
|
||||
|
||||
instance PatchConstructible (Patch (Term String Info)) where
|
||||
instance PatchConstructible (Patch (Term String (Record '[Range]))) where
|
||||
insert = Insert
|
||||
delete = Delete
|
||||
|
||||
instance PatchConstructible (SplitPatch (Term String Info)) where
|
||||
instance PatchConstructible (SplitPatch (Term String (Record '[Range]))) where
|
||||
insert = SplitInsert
|
||||
delete = SplitDelete
|
||||
|
||||
|
@ -1,21 +1,24 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE DataKinds, FlexibleContexts, GeneralizedNewtypeDeriving #-}
|
||||
module CorpusSpec where
|
||||
|
||||
import System.IO
|
||||
import Data.String
|
||||
import Diffing
|
||||
import Renderer
|
||||
import qualified Renderer.JSON as J
|
||||
import qualified Renderer.Patch as P
|
||||
import qualified Renderer.Split as Split
|
||||
|
||||
import Category
|
||||
import Control.DeepSeq
|
||||
import Data.Functor.Both
|
||||
import Data.List as List
|
||||
import Data.Map as Map
|
||||
import Data.Record
|
||||
import Data.Set as Set
|
||||
import qualified Data.Text as T
|
||||
import Info
|
||||
import Prologue hiding (fst, snd)
|
||||
import Range
|
||||
import qualified Source as S
|
||||
import System.FilePath
|
||||
import System.FilePath.Glob
|
||||
@ -39,10 +42,8 @@ spec = parallel $ do
|
||||
let tests = correctTests =<< paths
|
||||
traverse_ (\ (formatName, renderer, paths, output) -> it (normalizeName (fst paths) ++ " (" ++ formatName ++ ")") $ testDiff renderer paths output matcher) tests
|
||||
|
||||
correctTests :: (Both FilePath, Maybe FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer, Both FilePath, Maybe FilePath)]
|
||||
correctTests paths@(_, Nothing, Nothing, Nothing) = testsForPaths paths
|
||||
correctTests paths = List.filter (\(_, _, _, output) -> isJust output) $ testsForPaths paths
|
||||
testsForPaths :: (Both FilePath, Maybe FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer, Both FilePath, Maybe FilePath)]
|
||||
testsForPaths (paths, json, patch, split) = [ ("json", J.json, paths, json), ("patch", P.patch, paths, patch), ("split", Split.split, paths, split) ]
|
||||
|
||||
-- | Return all the examples from the given directory. Examples are expected to
|
||||
@ -69,7 +70,7 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte
|
||||
-- | Given file paths for A, B, and, optionally, a diff, return whether diffing
|
||||
-- | the files will produce the diff. If no diff is provided, then the result
|
||||
-- | is true, but the diff will still be calculated.
|
||||
testDiff :: Renderer -> Both FilePath -> Maybe FilePath -> (Verbatim -> Verbatim -> Expectation) -> Expectation
|
||||
testDiff :: Renderer (Record '[Range, Category, Cost]) -> Both FilePath -> Maybe FilePath -> (Verbatim -> Verbatim -> Expectation) -> Expectation
|
||||
testDiff renderer paths diff matcher = do
|
||||
sources <- sequence $ readAndTranscodeFile <$> paths
|
||||
actual <- Verbatim <$> diffFiles parser renderer (sourceBlobs sources)
|
||||
|
@ -17,10 +17,10 @@ spec :: Spec
|
||||
spec = parallel $ do
|
||||
describe "pqGrams" $ do
|
||||
prop "produces grams with stems of the specified length" . forAll (arbitrary `suchThat` (\ (_, p, q) -> p > 0 && q > 0)) $
|
||||
\ (term, p, q) -> pqGrams p q identity (toTerm term :: Term Text Text) `shouldSatisfy` all ((== p) . length . stem)
|
||||
\ (term, p, q) -> pqGrams p q headF (toTerm term :: Term Text Text) `shouldSatisfy` all ((== p) . length . stem)
|
||||
|
||||
prop "produces grams with bases of the specified length" . forAll (arbitrary `suchThat` (\ (_, p, q) -> p > 0 && q > 0)) $
|
||||
\ (term, p, q) -> pqGrams p q identity (toTerm term :: Term Text Text) `shouldSatisfy` all ((== q) . length . base)
|
||||
\ (term, p, q) -> pqGrams p q headF (toTerm term :: Term Text Text) `shouldSatisfy` all ((== q) . length . base)
|
||||
|
||||
describe "featureVector" $ do
|
||||
prop "produces a vector of the specified dimension" . forAll (arbitrary `suchThat` ((> 0) . Prologue.snd)) $
|
||||
@ -31,5 +31,5 @@ spec = parallel $ do
|
||||
prop "produces correct diffs" . forAll (scale (`div` 4) arbitrary) $
|
||||
\ (as, bs) -> let tas = toTerm <$> as
|
||||
tbs = toTerm <$> bs
|
||||
diff = free (Free (pure Program :< Indexed (rws compare identity tas tbs :: [Diff Text Category]))) in
|
||||
diff = free (Free (pure Program :< Indexed (rws compare headF tas tbs :: [Diff Text Category]))) in
|
||||
(beforeTerm diff, afterTerm diff) `shouldBe` (Just (cofree (Program :< Indexed tas)), Just (cofree (Program :< Indexed tbs)))
|
||||
|
@ -6,23 +6,21 @@ import Data.Record
|
||||
import Test.Hspec
|
||||
import Test.Hspec.QuickCheck
|
||||
import Diff
|
||||
import Info
|
||||
import Interpreter
|
||||
import Term.Arbitrary
|
||||
import Syntax
|
||||
import Patch
|
||||
import Range
|
||||
import Category
|
||||
import DiffSummary
|
||||
import Text.PrettyPrint.Leijen.Text
|
||||
import Term.Arbitrary
|
||||
import Interpreter
|
||||
|
||||
arrayInfo :: Info
|
||||
arrayInfo = rangeAt 0 .: ArrayLiteral .: 2 .: 0 .: RNil
|
||||
arrayInfo :: Record '[Category]
|
||||
arrayInfo = ArrayLiteral .: RNil
|
||||
|
||||
literalInfo :: Info
|
||||
literalInfo = rangeAt 1 .: StringLiteral .: 1 .: 0 .: RNil
|
||||
literalInfo :: Record '[Category]
|
||||
literalInfo = StringLiteral .: RNil
|
||||
|
||||
testDiff :: Diff Text Info
|
||||
testDiff :: Diff Text (Record '[Category])
|
||||
testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ])
|
||||
|
||||
testSummary :: DiffSummary DiffInfo
|
||||
|
@ -1,31 +1,31 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="19">
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="19">
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="19">
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="19">
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="19"></ul></td>
|
||||
<td class="blob-num">7</td><td class="blob-code"><ul class="category-program" data="19"></ul></td>
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">7</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,25 +1,25 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13">{
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13">{
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13"> <li><ul class="category-pair" data="6"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">b</span></li><li><span class="category-string" data="1">"</span></li></ul></li>: <li><div class="patch replace" data="1"><span class="category-number" data="1">4</span></div></li></ul></li>,
|
||||
</tr><tr><td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object"> <li><ul class="category-pair"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">b</span></li><li><span class="category-string">"</span></li></ul></li>: <li><div class="patch replace" data="1"><span class="category-number">4</span></div></li></ul></li>,
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13"> <li><ul class="category-pair" data="6"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">b</span></li><li><span class="category-string" data="1">"</span></li></ul></li>: <li><div class="patch replace" data="1"><span class="category-number" data="1">5</span></div></li></ul></li>,
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object"> <li><ul class="category-pair"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">b</span></li><li><span class="category-string">"</span></li></ul></li>: <li><div class="patch replace" data="1"><span class="category-number">5</span></div></li></ul></li>,
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13"> <li><ul class="category-pair" data="6"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">a</span></li><li><span class="category-string" data="1">"</span></li></ul></li>: <li><span class="category-number" data="1">5</span></li></ul></li>
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object"> <li><ul class="category-pair"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">a</span></li><li><span class="category-string">"</span></li></ul></li>: <li><span class="category-number">5</span></li></ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13"> <li><ul class="category-pair" data="6"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">a</span></li><li><span class="category-string" data="1">"</span></li></ul></li>: <li><span class="category-number" data="1">5</span></li></ul></li>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object"> <li><ul class="category-pair"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">a</span></li><li><span class="category-string">"</span></li></ul></li>: <li><span class="category-number">5</span></li></ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13">}</ul></li>
|
||||
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object">}</ul></li>
|
||||
</ul></li></ul></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><ul class="category-object" data="13">}</ul></li>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-object">}</ul></li>
|
||||
</ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="15"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="15"></ul></td>
|
||||
</tr><tr><td class="blob-num">5</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,14 +1,14 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="10"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="19"><li><div class="patch insert" data="9"><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="9"><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="10"></ul></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="19"></ul></td>
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,27 +1,27 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="31"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="31"><li><div class="patch insert" data="12"><ul class="category-if_statement" data="12">if (<li><span class="category-boolean" data="1">true</span></li>) <li><ul class="category-expression_statements" data="10">{
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="12"><ul class="category-if_statement">if (<li><span class="category-boolean">true</span></li>) <li><ul class="category-expression_statements">{
|
||||
</ul></li></ul></div></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="31"><li><div class="patch insert" data="12"><ul class="category-if_statement" data="12"><li><ul class="category-expression_statements" data="10"> <li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">cruel</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="12"><ul class="category-if_statement"><li><ul class="category-expression_statements"> <li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">cruel</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></div></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">4</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="31"><li><div class="patch insert" data="12"><ul class="category-if_statement" data="12"><li><ul class="category-expression_statements" data="10">}</ul></li></ul></div></li>
|
||||
<td class="blob-num blob-num-replacement">4</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="12"><ul class="category-if_statement"><li><ul class="category-expression_statements">}</ul></li></ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="19"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="31"><li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="19"></ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program" data="31"></ul></td>
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,24 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="13"><li><ul class="category-if_statement" data="12">if (<li><span class="category-boolean" data="1">true</span></li>) <li><ul class="category-expression_statements" data="10">{
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement">if (<li><span class="category-boolean">true</span></li>) <li><ul class="category-expression_statements">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="22"><li><ul class="category-if_statement" data="21">if (<li><span class="category-boolean" data="1">true</span></li>) <li><ul class="category-expression_statements" data="19">{
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement">if (<li><span class="category-boolean">true</span></li>) <li><ul class="category-expression_statements">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="13"><li><ul class="category-if_statement" data="12"><li><ul class="category-expression_statements" data="10"> <li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-expression_statements"> <li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="22"><li><ul class="category-if_statement" data="21"><li><ul class="category-expression_statements" data="19"> <li><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-expression_statements"> <li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="22"><li><ul class="category-if_statement" data="21"><li><ul class="category-expression_statements" data="19"> <li><div class="patch insert" data="9"><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-expression_statements"> <li><div class="patch insert" data="9"><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="13"><li><ul class="category-if_statement" data="12"><li><ul class="category-expression_statements" data="10">}</ul></li></ul></li>
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-expression_statements">}</ul></li></ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="22"><li><ul class="category-if_statement" data="21"><li><ul class="category-expression_statements" data="19">}</ul></li></ul></li>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-expression_statements">}</ul></li></ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="13"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="22"></ul></td>
|
||||
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,18 +1,18 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="12"><li><ul class="category-expression_statement" data="11"><li><ul class="category-method_call" data="10"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="11"><li><ul class="category-method_call" data="10"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="12"></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="21">
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="21"><li><div class="patch insert" data="9"><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">insertion</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="9"><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">insertion</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="21"></ul></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,13 +1,13 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="12"><li><ul class="category-expression_statement" data="11"><li><ul class="category-method_call" data="10"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="11"><li><ul class="category-method_call" data="10"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="21">
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="21"><li><div class="patch insert" data="9"><ul class="category-expression_statement" data="9"><li><ul class="category-method_call" data="8"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">insertion</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li></ul></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="9"><ul class="category-expression_statement"><li><ul class="category-method_call"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">insertion</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,13 +1,13 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2">[
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array">[
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2">[ <li><span class="category-identifier" data="1">bar</span></li> ]</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2"> <li><span class="category-identifier" data="1">bar</span></li>
|
||||
</tr><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array">[ <li><span class="category-identifier">bar</span></li> ]</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array"> <li><span class="category-identifier">bar</span></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2">]</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array">]</ul></li>;</ul></li></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
Loading…
Reference in New Issue
Block a user