1
1
mirror of https://github.com/github/semantic.git synced 2024-12-22 22:31:36 +03:00
semantic/src/Interpreter.hs
2017-04-24 15:53:11 -04:00

128 lines
6.1 KiB
Haskell

{-# LANGUAGE GADTs, RankNTypes #-}
module Interpreter (diffTerms, run, runSteps, runStep) where
import Algorithm
import Control.Monad.Free.Freer
import Data.Align.Generic
import Data.Functor.Both
import RWS
import Data.Record
import Data.These
import Diff
import Info hiding (Return)
import Patch (inserting, deleting, replacing, patchSum)
import Prologue hiding (lookup)
import Syntax as S hiding (Return)
import Term
-- | Diff two terms recursively, given functions characterizing the diffing.
diffTerms :: (Eq leaf, HasField fields Category, HasField fields (Maybe FeatureVector))
=> SyntaxTerm leaf fields -- ^ A term representing the old state.
-> SyntaxTerm leaf fields -- ^ A term representing the new state.
-> SyntaxDiff leaf fields
diffTerms = (run .) . diff
-- | Run an Algorithm to completion, returning its result.
run :: (Eq leaf, HasField fields Category, HasField fields (Maybe FeatureVector))
=> Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result
-> result
run = iterFreer (\ algorithm cont -> cont (run (decompose algorithm)))
-- | Run an Algorithm to completion, returning the list of steps taken.
runSteps :: (Eq leaf, HasField fields Category, HasField fields (Maybe FeatureVector))
=> Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result
-> [Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result]
runSteps algorithm = case runStep algorithm of
Left a -> [Return a]
Right next -> next : runSteps next
-- | Run a single step of an Algorithm, returning Either its result if it has finished, or the next step otherwise.
runStep :: (Eq leaf, HasField fields Category, HasField fields (Maybe FeatureVector))
=> Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result
-> Either result (Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result)
runStep step = case step of
Return a -> Left a
algorithm `Then` cont -> Right $ decompose algorithm >>= cont
-- | Decompose a step of an algorithm into the next steps to perform.
decompose :: (Eq leaf, HasField fields Category, HasField fields (Maybe FeatureVector))
=> AlgorithmF (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result -- ^ The step in an algorithm to decompose into its next steps.
-> Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) result -- ^ The sequence of next steps to undertake to continue the algorithm.
decompose step = case step of
Diff t1 t2 -> algorithmWithTerms t1 t2
Linear t1 t2 -> case galignWith diffThese (unwrap t1) (unwrap t2) of
Just result -> wrap . (both (extract t1) (extract t2) :<) <$> sequenceA result
_ -> byReplacing t1 t2
RWS as bs -> traverse diffThese (rws (editDistanceUpTo defaultM) comparable as bs)
Delete a -> pure (deleting a)
Insert b -> pure (inserting b)
Replace a b -> pure (replacing a b)
-- | Construct an algorithm to diff a pair of terms.
algorithmWithTerms :: SyntaxTerm leaf fields
-> SyntaxTerm leaf fields
-> Algorithm (SyntaxTerm leaf fields) (SyntaxDiff leaf fields) (SyntaxDiff leaf fields)
algorithmWithTerms t1 t2 = maybe (linearly t1 t2) (fmap annotate) $ case (unwrap t1, unwrap t2) of
(Indexed a, Indexed b) ->
Just $ Indexed <$> byRWS a b
(S.Module idA a, S.Module idB b) ->
Just $ S.Module <$> linearly idA idB <*> byRWS a b
(S.FunctionCall identifierA typeParamsA argsA, S.FunctionCall identifierB typeParamsB argsB) -> Just $
S.FunctionCall <$> linearly identifierA identifierB
<*> byRWS typeParamsA typeParamsB
<*> byRWS argsA argsB
(S.Switch exprA casesA, S.Switch exprB casesB) -> Just $
S.Switch <$> byRWS exprA exprB
<*> byRWS casesA casesB
(S.Object tyA a, S.Object tyB b) -> Just $
S.Object <$> maybeLinearly tyA tyB
<*> byRWS a b
(Commented commentsA a, Commented commentsB b) -> Just $
Commented <$> byRWS commentsA commentsB
<*> maybeLinearly a b
(Array tyA a, Array tyB b) -> Just $
Array <$> maybeLinearly tyA tyB
<*> byRWS a b
(S.Class identifierA clausesA expressionsA, S.Class identifierB clausesB expressionsB) -> Just $
S.Class <$> linearly identifierA identifierB
<*> byRWS clausesA clausesB
<*> byRWS expressionsA expressionsB
(S.Method clausesA identifierA receiverA paramsA expressionsA, S.Method clausesB identifierB receiverB paramsB expressionsB) -> Just $
S.Method <$> byRWS clausesA clausesB
<*> linearly identifierA identifierB
<*> maybeLinearly receiverA receiverB
<*> byRWS paramsA paramsB
<*> byRWS expressionsA expressionsB
(S.Function idA paramsA bodyA, S.Function idB paramsB bodyB) -> Just $
S.Function <$> linearly idA idB
<*> byRWS paramsA paramsB
<*> byRWS bodyA bodyB
_ -> Nothing
where
annotate = wrap . (both (extract t1) (extract t2) :<)
maybeLinearly a b = case (a, b) of
(Just a, Just b) -> Just <$> linearly a b
(Nothing, Just b) -> Just <$> byInserting b
(Just a, Nothing) -> Just <$> byDeleting a
(Nothing, Nothing) -> pure Nothing
-- | Test whether two terms are comparable.
comparable :: (Functor f, HasField fields Category) => Term f (Record fields) -> Term f (Record fields) -> Bool
comparable = (==) `on` category . extract
-- | How many nodes to consider for our constant-time approximation to tree edit distance.
defaultM :: Integer
defaultM = 10
-- | Return an edit distance as the sum of it's term sizes, given an cutoff and a syntax of terms 'f a'.
-- | Computes a constant-time approximation to the edit distance of a diff. This is done by comparing at most _m_ nodes, & assuming the rest are zero-cost.
editDistanceUpTo :: (GAlign f, Foldable f, Functor f, HasField fields Category) => Integer -> These (Term f (Record fields)) (Term f (Record fields)) -> Int
editDistanceUpTo m = these termSize termSize (\ a b -> diffSum (patchSum termSize) (cutoff m (approximateDiff a b)))
where diffSum patchCost = sum . fmap (maybe 0 patchCost)
approximateDiff a b = maybe (replacing a b) wrap (galignWith (these deleting inserting approximateDiff) (unwrap a) (unwrap b))