1
1
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:
joshvera 2016-07-22 12:36:03 -04:00
commit 70f9329f1a
26 changed files with 261 additions and 313 deletions

View File

@ -25,7 +25,6 @@ library
, Diff
, Diff.Arbitrary
, Diffing
, DiffOutput
, Info
, Interpreter
, Language

View File

@ -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 were done.
alignBranch _ _ (Join ([], [])) = []
-- There are no more children, so we can just zip the remaining ranges together.

View File

@ -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]

View File

@ -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

View File

@ -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 diffs 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)

View File

@ -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

View File

@ -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)

View File

@ -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)]

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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 weve 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

View File

@ -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

View File

@ -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)

View File

@ -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)))

View File

@ -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

View File

@ -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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</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>

View File

@ -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">&quot;</span></li><li><span class="category-string" data="1">b</span></li><li><span class="category-string" data="1">&quot;</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">&quot;</span></li><li><span class="category-string">b</span></li><li><span class="category-string">&quot;</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">&quot;</span></li><li><span class="category-string" data="1">b</span></li><li><span class="category-string" data="1">&quot;</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">&quot;</span></li><li><span class="category-string">b</span></li><li><span class="category-string">&quot;</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">&quot;</span></li><li><span class="category-string" data="1">a</span></li><li><span class="category-string" data="1">&quot;</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">&quot;</span></li><li><span class="category-string">a</span></li><li><span class="category-string">&quot;</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">&quot;</span></li><li><span class="category-string" data="1">a</span></li><li><span class="category-string" data="1">&quot;</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">&quot;</span></li><li><span class="category-string">a</span></li><li><span class="category-string">&quot;</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>

View File

@ -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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</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>

View File

@ -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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">cruel</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">cruel</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</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>

View File

@ -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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</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">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</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">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</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>

View File

@ -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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</span></li><li><span class="category-string" data="1">insertion</span></li><li><span class="category-string" data="1">&quot;</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">&quot;</span></li><li><span class="category-string">insertion</span></li><li><span class="category-string">&quot;</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>

View File

@ -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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</span></li><li><span class="category-string" data="1">insertion</span></li><li><span class="category-string" data="1">&quot;</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">&quot;</span></li><li><span class="category-string">insertion</span></li><li><span class="category-string">&quot;</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li></ul></td>
</tr></table></body></html>

View File

@ -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>