1
1
mirror of https://github.com/github/semantic.git synced 2024-12-28 01:11:52 +03:00
semantic/src/Alignment.hs

147 lines
7.9 KiB
Haskell
Raw Normal View History

2016-03-14 21:13:06 +03:00
{-# LANGUAGE RankNTypes #-}
2016-03-11 20:29:17 +03:00
module Alignment
2016-03-14 21:13:06 +03:00
( hasChanges
2016-03-11 20:29:17 +03:00
, numberedRows
2016-03-22 15:20:56 +03:00
, AlignedDiff
, alignDiff
, groupChildrenByLine
2016-03-11 20:29:17 +03:00
) where
import Control.Arrow ((&&&), (***))
import Control.Comonad.Cofree
2016-03-05 04:18:49 +03:00
import Control.Monad
import Control.Monad.Free
import Data.Align
import Data.Biapplicative
2016-03-18 21:30:22 +03:00
import Data.Bifunctor.Join
2016-03-03 07:11:24 +03:00
import Data.Copointed
2016-03-12 01:59:58 +03:00
import Data.Foldable
2016-04-04 23:25:16 +03:00
import Data.Functor.Both as Both
import Data.Functor.Identity
import Data.Maybe
import Data.Monoid
import Data.These
import Diff
2016-03-31 00:26:52 +03:00
import Info
import Patch
2016-03-01 03:39:04 +03:00
import Prelude hiding (fst, snd)
import Range
import Source hiding (fromList, uncons, (++))
import SplitDiff
import Syntax
import Term
-- | Assign line numbers to the lines on each side of a list of rows.
numberedRows :: [Join These a] -> [Join These (Int, a)]
numberedRows = countUp (Join $ These 1 1)
where countUp from (row : rows) = fromJust ((,) <$> from `applyThese` row) : countUp (succ <$> from) rows
2016-03-08 01:13:51 +03:00
countUp _ [] = []
-- | Determine whether a line contains any patches.
hasChanges :: SplitDiff leaf Info -> Bool
hasChanges = or . (True <$)
2016-03-09 09:36:40 +03:00
2016-03-22 15:20:56 +03:00
type AlignedDiff leaf = [Join These (SplitDiff leaf Info)]
2016-04-15 00:03:53 +03:00
alignDiff :: Both (Source Char) -> Diff leaf Info -> AlignedDiff leaf
2016-04-15 03:27:24 +03:00
alignDiff sources diff = iter (uncurry (alignSyntax (runBothWith ((Join .) . These)) ((Free .) . Annotated) getRange sources) . (annotation &&& syntax)) (alignPatch sources <$> diff)
2016-04-15 00:03:53 +03:00
2016-03-18 21:15:49 +03:00
alignPatch :: Both (Source Char) -> Patch (Term leaf Info) -> AlignedDiff leaf
2016-04-15 03:27:24 +03:00
alignPatch sources patch = case patch of
Delete term -> fmap (Pure . SplitDelete) <$> hylo (alignSyntax (Join . This . runIdentity) (:<) getRange (Identity (fst sources))) unCofree (Identity <$> term)
Insert term -> fmap (Pure . SplitInsert) <$> hylo (alignSyntax (Join . That . runIdentity) (:<) getRange (Identity (snd sources))) unCofree (Identity <$> term)
Replace term1 term2 -> fmap (Pure . SplitReplace) <$> alignWith (fmap (these id id const . runJoin) . Join)
(hylo (alignSyntax this (:<) getRange (Identity (fst sources))) unCofree (Identity <$> term1))
(hylo (alignSyntax that (:<) getRange (Identity (snd sources))) unCofree (Identity <$> term2))
where getRange = characterRange . copoint
this = Join . This . runIdentity
that = Join . That . runIdentity
alignSyntax :: Applicative f => (forall a. f a -> Join These a) -> (Info -> Syntax leaf term -> term) -> (term -> Range) -> f (Source Char) -> f Info -> Syntax leaf [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
2016-04-15 03:27:24 +03:00
Indexed children -> catMaybes $ wrapInBranch Indexed <$> groupChildrenByLine getRange lineRanges children
Fixed children -> catMaybes $ wrapInBranch Fixed <$> groupChildrenByLine getRange lineRanges children
Keyed children -> catMaybes $ wrapInBranch Fixed <$> groupChildrenByLine getRange lineRanges (toList children)
where lineRanges = toJoinThese $ actualLineRanges <$> (characterRange <$> infos) <*> sources
2016-04-15 03:27:24 +03:00
wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (info { characterRange = range }) (constructor children)) <$> infos)
2016-03-22 15:20:56 +03:00
2016-04-15 03:27:24 +03:00
groupChildrenByLine :: (term -> Range) -> Join These [Range] -> [[Join These term]] -> [Join These (Range, [term])]
groupChildrenByLine getRange ranges children | not (and $ null <$> ranges)
, (nextRanges, nextChildren, lines) <- alignChildrenInRanges getRange ranges children
= lines ++ groupChildrenByLine getRange nextRanges nextChildren
| otherwise = []
2016-04-15 03:27:24 +03:00
alignChildrenInRanges :: (term -> Range) -> Join These [Range] -> [[Join These term]] -> (Join These [Range], [[Join These term]], [Join These (Range, [term])])
alignChildrenInRanges getRange ranges children
| Just headRanges <- sequenceL $ listToMaybe <$> ranges
2016-04-15 03:27:24 +03:00
, (intersecting, nonintersecting) <- spanAndSplitFirstLines (intersects getRange headRanges) children
, (thisLine, nextLines) <- foldr (\ (this, next) (these, nexts) -> (this : these, next ++ nexts)) ([], []) intersecting
, thisRanges <- fromMaybe headRanges $ const <$> headRanges `applyThese` Alignment.catThese (thisLine ++ nextLines)
, merged <- pairRangesWithLine thisRanges (modifyJoin (uncurry These . fromThese [] []) (Alignment.catThese thisLine))
, advance <- fromThese id id . runJoin . (drop 1 <$) $ Alignment.catThese nextLines
2016-04-15 03:27:24 +03:00
, (nextRanges, nextChildren, nextLines) <- alignChildrenInRanges getRange (modifyJoin (uncurry bimap advance) ranges) (nextLines : nonintersecting)
= (nextRanges, nextChildren, merged : nextLines)
2016-04-15 03:27:24 +03:00
| ([]:rest) <- children = alignChildrenInRanges getRange ranges rest
| otherwise = ([] <$ ranges, children, fmap (flip (,) []) <$> sequenceL ranges)
spanAndSplitFirstLines :: (Join These a -> Join These Bool) -> [[Join These a]] -> ([(Join These a, [Join These a])], [[Join These a]])
spanAndSplitFirstLines pred = foldr go ([], [])
where go child (intersecting, nonintersecting)
2016-04-15 16:31:43 +03:00
| (first : rest) <- child = let ~(l, r) = splitThese first in
case fromThese False False . runJoin $ pred first of
(True, True) -> ((first, rest) : intersecting, nonintersecting)
(True, False) -> ((fromJust l, maybeToList r ++ rest) : intersecting, nonintersecting)
(False, True) -> ((fromJust r, maybeToList l ++ rest) : intersecting, nonintersecting)
_ -> (intersecting, (first : rest) : nonintersecting)
2016-04-13 23:05:56 +03:00
| otherwise = (intersecting, nonintersecting)
2016-04-14 18:54:50 +03:00
catThese :: [Join These a] -> Join These [a]
2016-04-14 19:05:52 +03:00
catThese as = maybe (Join (These [] [])) Join $ getUnion $ mconcat $ Union . Just . runJoin . fmap pure <$> as
pairRangesWithLine :: Monoid b => Join These a -> Join These b -> Join These (a, b)
pairRangesWithLine headRanges childLine = fromMaybe (flip (,) mempty <$> headRanges) $ (,) <$> headRanges `applyThese` childLine
2016-04-15 03:27:24 +03:00
intersects :: (term -> Range) -> Join These Range -> Join These term -> Join These Bool
intersects getRange ranges line = fromMaybe (False <$ line) $ intersectsRange <$> ranges `applyThese` (getRange <$> line)
2016-04-15 16:31:28 +03:00
-- | Split a These value up into independent These values representing the left and right sides, if any.
splitThese :: Join These a -> (Maybe (Join These a), Maybe (Join These a))
splitThese these = fromThese Nothing Nothing $ bimap (Just . Join . This) (Just . Join . That) (runJoin these)
infixl 4 `applyThese`
2016-04-15 16:29:23 +03:00
-- | Like `<*>`, but it returns its result in `Maybe` since the result is the intersection of the shapes of the inputs.
applyThese :: Join These (a -> b) -> Join These a -> Maybe (Join These b)
2016-04-15 16:29:23 +03:00
applyThese (Join fg) (Join ab) = fmap Join . uncurry maybeThese $ uncurry (***) (bimap (<*>) (<*>) (unpack fg)) (unpack ab)
where unpack = fromThese Nothing Nothing . bimap Just Just
2016-03-29 16:58:31 +03:00
2016-04-15 16:30:20 +03:00
-- Map over the bifunctor inside a Join, producing another Join.
modifyJoin :: (p a a -> q b b) -> Join p a -> Join q b
modifyJoin f = Join . f . runJoin
-- | Given a pair of Maybes, produce a These containing Just their values, or Nothing if they havent any.
maybeThese :: Maybe a -> Maybe b -> Maybe (These a b)
maybeThese (Just a) (Just b) = Just (These a b)
maybeThese (Just a) _ = Just (This a)
maybeThese _ (Just b) = Just (That b)
maybeThese _ _ = Nothing
2016-04-15 16:27:41 +03:00
-- | A Monoid wrapping These, for which mappend is the smallest shape covering both arguments.
2016-04-15 16:23:41 +03:00
newtype Union a b = Union { getUnion :: Maybe (These a b) }
deriving (Eq, Show)
2016-04-15 16:26:16 +03:00
-- | Instances
instance (Monoid a, Monoid b) => Monoid (Union a b) where
mempty = Union Nothing
Union (Just a) `mappend` Union (Just b) = Union $ uncurry maybeThese $ uncurry (***) (bimap mappend mappend (unpack a)) (unpack b)
where unpack = fromThese Nothing Nothing . bimap Just Just
Union (Just a) `mappend` _ = Union $ Just a
Union _ `mappend` Union (Just b) = Union $ Just b
_ `mappend` _ = Union Nothing
2016-04-15 16:26:16 +03:00
instance Bicrosswalk t => Crosswalk (Join t) where
crosswalk f = fmap Join . bicrosswalk f f . runJoin