1
1
mirror of https://github.com/github/semantic.git synced 2024-12-01 00:33:59 +03:00

Merge branch 'master' into fix-json-output-again

This commit is contained in:
Timothy Clem 2017-05-05 13:30:48 -07:00 committed by GitHub
commit ab0a6d59dd
13 changed files with 209 additions and 117 deletions

View File

@ -34,6 +34,14 @@ diff = (liftF .) . Diff
diffThese :: These term term -> Algorithm term diff diff
diffThese = these byDeleting byInserting diff
-- | Diff a pair of optional terms without specifying the algorithm to be used.
diffMaybe :: Maybe term -> Maybe term -> Algorithm term diff (Maybe diff)
diffMaybe a b = case (a, b) of
(Just a, Just b) -> Just <$> diff a b
(Just a, _) -> Just <$> byDeleting a
(_, Just b) -> Just <$> byInserting b
_ -> pure Nothing
-- | Diff two terms linearly.
linearly :: term -> term -> Algorithm term diff diff
linearly a b = liftF (Linear a b)

View File

@ -6,67 +6,70 @@ import Data.Align
import Data.These
import GHC.Generics
import Prologue
import Syntax
-- | Functors which can be aligned (structure-unioning-ly zipped). The default implementation will operate generically over the constructors in the aligning type.
class Functor f => GAlign f where
class GAlign f where
galign :: f a -> f b -> Maybe (f (These a b))
default galign :: (Generic1 f, GAlign (Rep1 f)) => f a -> f b -> Maybe (f (These a b))
galign a b = to1 <$> galign (from1 a) (from1 b)
galign = galignWith identity
-- | Perform generic alignment of values of some functor, applying the given function to alignments of elements.
galignWith :: (These a b -> c) -> f a -> f b -> Maybe (f c)
galignWith f = (fmap (fmap f) .) . galign
-- Generically-derived instances
instance Eq a => GAlign (Syntax a)
default galignWith :: (Generic1 f, GAlign (Rep1 f)) => (These a b -> c) -> f a -> f b -> Maybe (f c)
galignWith f a b = to1 <$> galignWith f (from1 a) (from1 b)
-- 'Data.Align.Align' instances
instance GAlign [] where galign = galignAlign
instance GAlign Maybe where galign = galignAlign
instance GAlign [] where
galign = galignAlign
galignWith = galignWithAlign
instance GAlign Maybe where
galign = galignAlign
galignWith = galignWithAlign
instance GAlign Identity where
galignWith f (Identity a) (Identity b) = Just (Identity (f (These a b)))
-- | Implements a function suitable for use as the definition of 'galign' for 'Align'able functors.
galignAlign :: Align f => f a -> f b -> Maybe (f (These a b))
galignAlign a = Just . align a
galignWithAlign :: Align f => (These a b -> c) -> f a -> f b -> Maybe (f c)
galignWithAlign f a b = Just (alignWith f a b)
-- Generics
-- | 'GAlign' over unit constructors.
instance GAlign U1 where
galign _ _ = Just U1
galignWith _ _ _ = Just U1
-- | 'GAlign' over parameters.
instance GAlign Par1 where
galign (Par1 a) (Par1 b) = Just (Par1 (These a b))
galignWith f (Par1 a) (Par1 b) = Just (Par1 (f (These a b)))
-- | 'GAlign' over non-parameter fields. Only equal values are aligned.
instance Eq c => GAlign (K1 i c) where
galign (K1 a) (K1 b) = guard (a == b) >> Just (K1 b)
galignWith _ (K1 a) (K1 b) = guard (a == b) >> Just (K1 b)
-- | 'GAlign' over applications over parameters.
instance GAlign f => GAlign (Rec1 f) where
galign (Rec1 a) (Rec1 b) = Rec1 <$> galign a b
galignWith f (Rec1 a) (Rec1 b) = Rec1 <$> galignWith f a b
-- | 'GAlign' over metainformation (constructor names, etc).
instance GAlign f => GAlign (M1 i c f) where
galign (M1 a) (M1 b) = M1 <$> galign a b
galignWith f (M1 a) (M1 b) = M1 <$> galignWith f a b
-- | 'GAlign' over sums. Returns 'Nothing' for disjoint constructors.
instance (GAlign f, GAlign g) => GAlign (f :+: g) where
galign a b = case (a, b) of
(L1 a, L1 b) -> L1 <$> galign a b
(R1 a, R1 b) -> R1 <$> galign a b
galignWith f a b = case (a, b) of
(L1 a, L1 b) -> L1 <$> galignWith f a b
(R1 a, R1 b) -> R1 <$> galignWith f a b
_ -> Nothing
-- | 'GAlign' over products.
instance (GAlign f, GAlign g) => GAlign (f :*: g) where
galign (a1 :*: b1) (a2 :*: b2) = (:*:) <$> galign a1 a2 <*> galign b1 b2
galignWith f (a1 :*: b1) (a2 :*: b2) = (:*:) <$> galignWith f a1 a2 <*> galignWith f b1 b2
-- | 'GAlign' over type compositions.
instance (Traversable f, Applicative f, GAlign g) => GAlign (f :.: g) where
galign (Comp1 a) (Comp1 b) = Comp1 <$> sequenceA (galign <$> a <*> b)
galignWith f (Comp1 a) (Comp1 b) = Comp1 <$> sequenceA (galignWith f <$> a <*> b)

View File

@ -6,6 +6,7 @@ module Data.Functor.Union
, InUnion(..)
) where
import Data.Align.Generic
import Data.Functor.Classes
import Data.Kind
import GHC.Show
@ -111,3 +112,11 @@ instance (Show1 f, Show1 (Union fs)) => Show1 (Union (f ': fs)) where
instance Show1 (Union '[]) where
liftShowsPrec _ _ _ _ = identity
instance (GAlign f, GAlign (Union fs)) => GAlign (Union (f ': fs)) where
galignWith f (Here a) (Here b) = Here <$> galignWith f a b
galignWith f (There a) (There b) = There <$> galignWith f a b
galignWith _ _ _ = Nothing
instance GAlign (Union '[]) where
galignWith _ _ _ = Nothing

View File

@ -1,5 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax where
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
@ -9,13 +11,13 @@ import Text.Show
-- Undifferentiated
newtype Leaf a = Leaf { leafContent :: ByteString }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Leaf where liftEq = genericLiftEq
instance Show1 Leaf where liftShowsPrec = genericLiftShowsPrec
newtype Branch a = Branch { branchElements :: [a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Branch where liftEq = genericLiftEq
instance Show1 Branch where liftShowsPrec = genericLiftShowsPrec
@ -25,7 +27,7 @@ instance Show1 Branch where liftShowsPrec = genericLiftShowsPrec
-- | An identifier of some other construct, whether a containing declaration (e.g. a class name) or a reference (e.g. a variable).
newtype Identifier a = Identifier ByteString
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Identifier where liftEq = genericLiftEq
instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
@ -35,7 +37,7 @@ instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
--
-- This can be used to represent an implicit no-op, e.g. the alternative in an 'if' statement without an 'else'.
data Empty a = Empty
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Empty where liftEq _ _ _ = True
instance Show1 Empty where liftShowsPrec _ _ _ _ = showString "Empty"

View File

@ -1,4 +1,64 @@
{-# LANGUAGE DataKinds, GADTs, ScopedTypeVariables, TypeFamilies #-}
-- | Assignment of AST onto some other structure (typically terms).
--
-- Parsing yields an AST represented as a Rose tree labelled with symbols in the languages grammar and source locations (byte Range and SourceSpan). An Assignment represents a (partial) map from AST nodes onto some other structure; in essence, its a parser that operates over trees. (For our purposes, this structure is typically Terms annotated with source locations.) Assignments are able to match based on symbol, sequence, and hierarchy; thus, in @x = y@, both @x@ and @y@ might have the same symbol, @Identifier@, the left can be assigned to a variable declaration, while the right can be assigned to a variable reference.
--
-- Assignments can be any of the following primitive rules:
--
-- 1. 'symbol' rules match a node against a specific symbol in the source languages grammar; they succeed iff a) there is a current node, and b) its symbol is equal to the argument symbol. Matching a 'symbol' rule does not advance past the current node, meaning that you can match a node against a symbol and also e.g. match against the nodes 'children'. This also means that some care must be taken, as repeating a symbol with 'many' or 'some' (see below) will never advance past the current node and could therefore loop forever.
--
-- 2. 'location' rules always succeed, and produce the current nodes Location (byte Range and SourceSpan). If there is no current node (i.e. if matching has advanced past the root node or past the last child node when operating within a 'children' rule), the location is instead the end of the most recently matched node, specified as a zero-width Range and SourceSpan. 'location' rules do not advance past the current node, meaning that you can both match a nodes 'location' and other properties.
--
-- 3. 'source' rules succeed whenever there is a current node (i.e. matching has not advanced past the root node or the last child node when operating within a 'children' rule), and produce its source as a ByteString. 'source' is intended to match leaf nodes such as e.g. comments. 'source' rules advance past the current node.
--
-- 4. 'children' rules apply their argument (an assignment) to the children of the current node, succeeding iff a) there is a current node, b) the argument assignment matches the children, and c) there are no (regular) nodes left over (see below re: tokens), producing the result of matching the argument assignment against the children. 'children' rules can match a node with no child nodes if their argument can successfully match at the end of input.
--
-- 5. Via the 'Alternative' instance, 'empty' assignments always fail. This can be used (in combination with the 'Monad' instance) to (for example) fail if a 'source' assignment produces an ill-formatted ByteString. However, see below re: committed choice.
--
-- 6. Via the 'Applicative' instance, 'pure' (or via the 'Monad' instance, 'return') assignments always succeed, producing the passed value. They do not advance past the current node. In combination with the 'Alternative' instance, 'pure' can provide default values when optional syntax is not present in the AST.
--
-- Assignments can further be combined in a few different ways:
--
-- 1. The 'Functor' instance maps values from the AST (Location, ByteString, etc.) into another structure.
--
-- 2. The 'Applicative' instance assigns sequences of (sibling) AST nodes in order, as well as providing 'pure' assignments (see above). Most assignments of a single piece of syntax consist of an 'Applicative' chain of assignments.
--
-- 3. The 'Alternative' instance chooses between a set of assignments, as well as providing 'empty' assignments (see above). See below re: committed choice for best practices for efficiency & error reporting when it comes to assigning multiple alternatives. Most high-level assignments (e.g. “declaration” or “statement” assignments) consist of choices among two or more 'Applicative' chains of assignments, mirroring the structure of the parsers choices. The 'Alternative' instance also enables repetitions via the 'many' (≥ 0 repetitions) and 'some' (≥ 1 repetition) methods. Finally, the 'optional' function uses the 'Alternative' instance to assign a value in 'Maybe', succeeding with 'Nothing' when unmatched.
--
-- 4. The 'Monad' instance allows assignments to depend on the results of earlier assignments. In general, most assignments should not be written using the 'Monad' instance; however, some specific situations require it, e.g. assigning 'x += y' to be equivalent to 'x = x + y'.
--
--
-- == Best practices
--
-- Because of their flexibility, the same assignment can often be written in multiple different ways. The following best practices should ensure efficient assignment with clear error messages for ill-formed AST.
--
-- === Committed choice
--
-- Assignments can represent alternatives as either committed or uncommitted choices, both written with '<|>'. “Committed” in this context means that a failure in one of the alternatives will not result in backtracking followed by an attempt of one of the other alternatives; thus, committed choice is more efficient. (By the same token, it enables much better error messages since backtracking erases most of the relevant context.) Committed choices are constructed via the following rules:
--
-- 1. 'empty' is dropped from choices:
-- prop> empty <|> a = a -- empty is the left-identity of <|>
-- prop> a <|> empty = a -- empty is the right-identity of <|>
--
-- 2. 'symbol' rules construct a committed choice (with only a single alternative).
--
-- 3. 'fmap' (and by extension '<$>' and '<$') of a committed choice is a committed choice.
--
-- 4. '<*>' (and by extension '*>' and '<*') with a committed choice on the left is a committed choice.
--
-- 5. '>>=' (and by extension '>>', '=<<', and '<<') of a committed choice is a committed choice. It may be helpful to think of this and the above rule for '<*>' as “sequences starting with committed choices remain committed choices.”
--
-- 6. '<|>' of two committed choices is a committed choice.
--
-- Finally, if a given choice is not a committed choice, it is an uncommitted choice.
--
-- Distilling the above, the rule of thumb is to always start an assignment for a given piece of syntax with either a 'symbol' rule or an 'fmap' over a 'symbol' rule. When assigning multiple pieces of syntax, place any known uncommitted choices at the (rightmost) end of the chain; '<|>' is left-associative, so this guarantees that youre adding at most one uncommitted choice on top of the ones already present.
--
-- === Matching tokens
--
-- AST symbols are classified by their 'symbolType' as either 'Regular', 'Anonymous', or 'Auxiliary'. 'Auxiliary' never appears in ASTs; 'Regular' is for the symbols of explicitly named productions in the grammar, and 'Anonymous' is for unnamed productions of content such as tokens. Most of the time, assignments are only concerned with the named productions, and thus will be using 'Regular' symbols. Therefore, when matching a committed choice of all-'Regular' symbols, nodes with 'Anonymous' symbols will be skipped. However, in some cases grammars dont provide a named symbol for e.g. every kind of infix operator, and thus the only way to differentiate between them is by means of a 'symbol' rule for an 'Anonymous' token. In these cases, and before every other kind of assignment, the 'Anonymous' nodes will not be skipped so that matching can succeed.
--
-- Therefore, in addition to the rule of thumb for committed choices (see above), try to match 'Regular' symbols up front, and only match 'Anonymous' ones in the middle of a chain. That will ensure that you dont have to make redundant effort to explicitly skip 'Anonymous' nodes ahead of multiple alternatives, and can instead rely on them being automatically skipped except when explicitly required.
module Data.Syntax.Assignment
( Assignment
, Location

View File

@ -1,5 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Comment where
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
@ -7,7 +9,7 @@ import Prologue
-- | An unnested comment (line or block).
newtype Comment a = Comment { commentContent :: ByteString }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Comment where liftEq = genericLiftEq
instance Show1 Comment where liftShowsPrec = genericLiftShowsPrec

View File

@ -1,12 +1,14 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Declaration where
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue
data Function a = Function { functionName :: !a, functionParameters :: ![a], functionBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Function where liftEq = genericLiftEq
instance Show1 Function where liftShowsPrec = genericLiftShowsPrec
@ -14,7 +16,7 @@ instance Show1 Function where liftShowsPrec = genericLiftShowsPrec
-- TODO: How should we represent function types, where applicable?
data Method a = Method { methodName :: !a, methodParameters :: ![a], methodBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Method where liftEq = genericLiftEq
instance Show1 Method where liftShowsPrec = genericLiftShowsPrec
@ -24,7 +26,7 @@ instance Show1 Method where liftShowsPrec = genericLiftShowsPrec
data Class a = Class { classIdentifier :: !a, classSuperclasses :: ![a], classScope :: ![a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Class where liftEq = genericLiftEq
instance Show1 Class where liftShowsPrec = genericLiftShowsPrec
@ -34,14 +36,14 @@ instance Show1 Class where liftShowsPrec = genericLiftShowsPrec
-- | An ADT, i.e. a disjoint sum of products, like 'data' in Haskell, or 'enum' in Rust or Swift.
data Datatype a = Datatype { datatypeName :: !a, datatypeConstructors :: ![a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Data.Syntax.Declaration.Datatype where liftEq = genericLiftEq
instance Show1 Data.Syntax.Declaration.Datatype where liftShowsPrec = genericLiftShowsPrec
-- | A single constructor in a datatype, or equally a 'struct' in C, Rust, or Swift.
data Constructor a = Constructor { constructorName :: !a, constructorFields :: ![a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Data.Syntax.Declaration.Constructor where liftEq = genericLiftEq
instance Show1 Data.Syntax.Declaration.Constructor where liftShowsPrec = genericLiftShowsPrec

View File

@ -1,5 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Expression where
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
@ -7,7 +9,7 @@ import Prologue
-- | Typical prefix function application, like `f(x)` in many languages, or `f x` in Haskell.
data Call a = Call { callFunction :: a, callParams :: [a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Call where liftEq = genericLiftEq
instance Show1 Call where liftShowsPrec = genericLiftShowsPrec
@ -15,7 +17,7 @@ instance Show1 Call where liftShowsPrec = genericLiftShowsPrec
-- | Unary boolean negation, like '!x' in many languages.
data Not a = Not a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Not where liftEq = genericLiftEq
instance Show1 Not where liftShowsPrec = genericLiftShowsPrec
@ -23,7 +25,7 @@ instance Show1 Not where liftShowsPrec = genericLiftShowsPrec
-- | Binary addition.
data Plus a = Plus a a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Plus where liftEq = genericLiftEq
instance Show1 Plus where liftShowsPrec = genericLiftShowsPrec

View File

@ -1,6 +1,7 @@
{-# LANGUAGE DataKinds, DeriveAnyClass, DeriveGeneric #-}
module Data.Syntax.Literal where
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import Data.Syntax.Comment
@ -11,7 +12,7 @@ import Prologue
-- Boolean
newtype Boolean a = Boolean Bool
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
true :: Boolean a
true = Boolean True
@ -27,7 +28,7 @@ instance Show1 Boolean where liftShowsPrec = genericLiftShowsPrec
-- | A literal integer of unspecified width. No particular base is implied.
newtype Integer a = Integer { integerContent :: ByteString }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Data.Syntax.Literal.Integer where liftEq = genericLiftEq
instance Show1 Data.Syntax.Literal.Integer where liftShowsPrec = genericLiftShowsPrec
@ -39,7 +40,7 @@ instance Show1 Data.Syntax.Literal.Integer where liftShowsPrec = genericLiftShow
data Range a = Range { rangeStart :: a, rangeEnd :: a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Range where liftEq = genericLiftEq
instance Show1 Range where liftShowsPrec = genericLiftShowsPrec
@ -48,7 +49,7 @@ instance Show1 Range where liftShowsPrec = genericLiftShowsPrec
-- Strings, symbols
newtype String a = String { stringElements :: [Union '[InterpolationElement, TextElement] a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 String where liftEq = genericLiftEq
instance Show1 String where liftShowsPrec = genericLiftShowsPrec
@ -57,7 +58,7 @@ instance Show1 String where liftShowsPrec = genericLiftShowsPrec
-- | An interpolation element within a string literal.
newtype InterpolationElement a = InterpolationElement { interpolationBody :: a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 InterpolationElement where liftEq = genericLiftEq
instance Show1 InterpolationElement where liftShowsPrec = genericLiftShowsPrec
@ -65,14 +66,14 @@ instance Show1 InterpolationElement where liftShowsPrec = genericLiftShowsPrec
-- | A sequence of textual contents within a string literal.
newtype TextElement a = TextElement { textElementContent :: ByteString }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 TextElement where liftEq = genericLiftEq
instance Show1 TextElement where liftShowsPrec = genericLiftShowsPrec
newtype Symbol a = Symbol { symbolContent :: ByteString }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Symbol where liftEq = genericLiftEq
instance Show1 Symbol where liftShowsPrec = genericLiftShowsPrec
@ -85,21 +86,21 @@ instance Show1 Symbol where liftShowsPrec = genericLiftShowsPrec
-- Collections
newtype Array a = Array { arrayElements :: [Union '[Identity, Comment] a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Array where liftEq = genericLiftEq
instance Show1 Array where liftShowsPrec = genericLiftShowsPrec
newtype Hash a = Hash { hashElements :: [Union '[KeyValue, Comment] a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Hash where liftEq = genericLiftEq
instance Show1 Hash where liftShowsPrec = genericLiftShowsPrec
data KeyValue a = KeyValue { key :: !a, value :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 KeyValue where liftEq = genericLiftEq
instance Show1 KeyValue where liftShowsPrec = genericLiftShowsPrec

View File

@ -1,6 +1,7 @@
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveAnyClass, StandaloneDeriving #-}
module Data.Syntax.Statement where
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
@ -8,7 +9,7 @@ import Prologue
-- | Conditional. This must have an else block, which can be filled with some default value when omitted in the source, e.g. 'pure ()' for C-style if-without-else or 'pure Nothing' for Ruby-style, in both cases assuming some appropriate Applicative context into which the If will be lifted.
data If a = If { ifCondition :: !a, ifThenBody :: !a, ifElseBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 If where liftEq = genericLiftEq
instance Show1 If where liftShowsPrec = genericLiftShowsPrec
@ -17,14 +18,14 @@ instance Show1 If where liftShowsPrec = genericLiftShowsPrec
-- | A pattern-matching or computed jump control-flow statement, like 'switch' in C or JavaScript, or 'case' in Ruby or Haskell.
data Match with a = Match { matchSubject :: !a, matchPatterns :: ![with a] }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 with => Eq1 (Match with) where liftEq = genericLiftEq
instance Show1 with => Show1 (Match with) where liftShowsPrec = genericLiftShowsPrec
-- | A pattern in a pattern-matching or computed jump control-flow statement, like 'case' in C or JavaScript, 'when' in Ruby, or the left-hand side of '->' in the body of Haskell 'case' expressions.
newtype Pattern a = Pattern a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Pattern where liftEq = genericLiftEq
instance Show1 Pattern where liftShowsPrec = genericLiftShowsPrec
@ -34,7 +35,7 @@ instance Show1 Pattern where liftShowsPrec = genericLiftShowsPrec
-- | Assignment to a variable or other lvalue.
data Assignment a = Assignment { assignmentTarget :: !a, assignmentValue :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Assignment where liftEq = genericLiftEq
instance Show1 Assignment where liftShowsPrec = genericLiftShowsPrec
@ -43,25 +44,25 @@ instance Show1 Assignment where liftShowsPrec = genericLiftShowsPrec
-- Returns
newtype Return a = Return a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Return where liftEq = genericLiftEq
instance Show1 Return where liftShowsPrec = genericLiftShowsPrec
newtype Yield a = Yield a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Yield where liftEq = genericLiftEq
instance Show1 Yield where liftShowsPrec = genericLiftShowsPrec
newtype Break a = Break a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Break where liftEq = genericLiftEq
instance Show1 Break where liftShowsPrec = genericLiftShowsPrec
newtype Continue a = Continue a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Continue where liftEq = genericLiftEq
instance Show1 Continue where liftShowsPrec = genericLiftShowsPrec
@ -70,25 +71,25 @@ instance Show1 Continue where liftShowsPrec = genericLiftShowsPrec
-- Loops
data For a = For { forBefore :: !a, forCondition :: !a, forStep :: !a, forBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 For where liftEq = genericLiftEq
instance Show1 For where liftShowsPrec = genericLiftShowsPrec
data ForEach a = ForEach { forEachBinding :: !a, forEachSubject :: !a, forEachBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 ForEach where liftEq = genericLiftEq
instance Show1 ForEach where liftShowsPrec = genericLiftShowsPrec
data While a = While { whileCondition :: !a, whileBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 While where liftEq = genericLiftEq
instance Show1 While where liftShowsPrec = genericLiftShowsPrec
data DoWhile a = DoWhile { doWhileCondition :: !a, doWhileBody :: !a }
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 DoWhile where liftEq = genericLiftEq
instance Show1 DoWhile where liftShowsPrec = genericLiftShowsPrec
@ -97,13 +98,13 @@ instance Show1 DoWhile where liftShowsPrec = genericLiftShowsPrec
-- Exception handling
newtype Throw a = Throw a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Throw where liftEq = genericLiftEq
instance Show1 Throw where liftShowsPrec = genericLiftShowsPrec
data Try with a = Try !a ![with a]
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
-- deriving instance (Eq a, Eq (with a)) => Eq (Try with a)
-- deriving instance (Show a, Show (with a)) => Show (Try with a)
@ -111,13 +112,13 @@ instance Eq1 with => Eq1 (Try with) where liftEq = genericLiftEq
instance Show1 with => Show1 (Try with) where liftShowsPrec = genericLiftShowsPrec
data Catch a = Catch !(Maybe a) !a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Catch where liftEq = genericLiftEq
instance Show1 Catch where liftShowsPrec = genericLiftShowsPrec
newtype Finally a = Finally a
deriving (Eq, Foldable, Functor, Generic1, Show, Traversable)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Finally where liftEq = genericLiftEq
instance Show1 Finally where liftShowsPrec = genericLiftShowsPrec

View File

@ -1,9 +1,17 @@
{-# LANGUAGE DeriveAnyClass #-}
module Data.Syntax.Type where
import Prologue
import Data.Align.Generic
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Classes.Show.Generic
import GHC.Generics
import Prologue hiding (Product)
data Annotation a = Annotation { annotationSubject :: !a, annotationType :: !a }
deriving (Eq, Show)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
newtype Product a = Product { productElements :: [a] }
deriving (Eq, Show)
deriving (Eq, Foldable, Functor, GAlign, Generic1, Show, Traversable)
instance Eq1 Product where liftEq = genericLiftEq
instance Show1 Product where liftShowsPrec = genericLiftShowsPrec

View File

@ -1,10 +1,11 @@
{-# LANGUAGE GADTs, RankNTypes #-}
module Interpreter (diffTerms, run, runSteps, runStep) where
{-# LANGUAGE GADTs, RankNTypes, ScopedTypeVariables #-}
module Interpreter (diffTerms, runAlgorithm, runAlgorithmSteps) where
import Algorithm
import Control.Monad.Free.Freer
import Data.Align.Generic
import Data.Functor.Both
import Data.Functor.Classes (Eq1)
import RWS
import Data.Record
import Data.These
@ -20,36 +21,32 @@ diffTerms :: (Eq leaf, HasField fields Category, HasField fields (Maybe FeatureV
=> SyntaxTerm leaf fields -- ^ A term representing the old state.
-> SyntaxTerm leaf fields -- ^ A term representing the new state.
-> SyntaxDiff leaf fields
diffTerms = (run .) . diff
diffTerms = (runAlgorithm (decomposeWith algorithmWithTerms) .) . 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 by repeated application of a stepping operation and return its result.
runAlgorithm :: forall f result
. (forall x. f x -> Freer f x)
-> Freer f result
-> result
runAlgorithm decompose = go
where go :: Freer f x -> x
go = iterFreer (\ algorithm yield -> yield (go (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 an Algorithm to completion by repeated application of a stepping operation, returning the list of steps taken up to and including the final result.
runAlgorithmSteps :: (forall x. f x -> Freer f x)
-> Freer f result
-> [Freer f result]
runAlgorithmSteps decompose = go
where go algorithm = case algorithm of
Return a -> [Return a]
step `Then` yield -> algorithm : go (decompose step >>= yield)
-- | 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
-- | Decompose a step of an algorithm into the next steps to perform using a helper function.
decomposeWith :: (Traversable f, GAlign f, Eq1 f, HasField fields (Maybe FeatureVector), HasField fields Category)
=> (Term f (Record fields) -> Term f (Record fields) -> Algorithm (Term f (Record fields)) (Diff f (Record fields)) (Diff f (Record fields)))
-> AlgorithmF (Term f (Record fields)) (Diff f (Record fields)) result
-> Algorithm (Term f (Record fields)) (Diff f (Record fields)) result
decomposeWith algorithmWithTerms 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
@ -64,51 +61,45 @@ decompose step = case step of
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
algorithmWithTerms t1 t2 = case (unwrap t1, unwrap t2) of
(Indexed a, Indexed b) ->
Just $ Indexed <$> byRWS a b
annotate . 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 $
(annotate .) . S.Module <$> linearly idA idB <*> byRWS a b
(S.FunctionCall identifierA typeParamsA argsA, S.FunctionCall identifierB typeParamsB argsB) -> fmap annotate $
S.FunctionCall <$> linearly identifierA identifierB
<*> byRWS typeParamsA typeParamsB
<*> byRWS argsA argsB
(S.Switch exprA casesA, S.Switch exprB casesB) -> Just $
(S.Switch exprA casesA, S.Switch exprB casesB) -> fmap annotate $
S.Switch <$> byRWS exprA exprB
<*> byRWS casesA casesB
(S.Object tyA a, S.Object tyB b) -> Just $
S.Object <$> maybeLinearly tyA tyB
(S.Object tyA a, S.Object tyB b) -> fmap annotate $
S.Object <$> diffMaybe tyA tyB
<*> byRWS a b
(Commented commentsA a, Commented commentsB b) -> Just $
(Commented commentsA a, Commented commentsB b) -> fmap annotate $
Commented <$> byRWS commentsA commentsB
<*> maybeLinearly a b
(Array tyA a, Array tyB b) -> Just $
Array <$> maybeLinearly tyA tyB
<*> diffMaybe a b
(Array tyA a, Array tyB b) -> fmap annotate $
Array <$> diffMaybe tyA tyB
<*> byRWS a b
(S.Class identifierA clausesA expressionsA, S.Class identifierB clausesB expressionsB) -> Just $
(S.Class identifierA clausesA expressionsA, S.Class identifierB clausesB expressionsB) -> fmap annotate $
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 clausesA identifierA receiverA paramsA expressionsA, S.Method clausesB identifierB receiverB paramsB expressionsB) -> fmap annotate $
S.Method <$> byRWS clausesA clausesB
<*> linearly identifierA identifierB
<*> maybeLinearly receiverA receiverB
<*> diffMaybe receiverA receiverB
<*> byRWS paramsA paramsB
<*> byRWS expressionsA expressionsB
(S.Function idA paramsA bodyA, S.Function idB paramsB bodyB) -> Just $
(S.Function idA paramsA bodyA, S.Function idB paramsB bodyB) -> fmap annotate $
S.Function <$> linearly idA idB
<*> byRWS paramsA paramsB
<*> byRWS bodyA bodyB
_ -> Nothing
_ -> linearly t1 t2
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
@ -121,7 +112,7 @@ 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 :: (GAlign f, Foldable f, Functor f) => 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))

View File

@ -2,6 +2,7 @@
module Syntax where
import Data.Aeson
import Data.Align.Generic
import Data.Functor.Classes
import Data.Functor.Classes.Eq.Generic
import Data.Functor.Listable
@ -184,3 +185,5 @@ instance (Listable leaf, Listable recur) => Listable (Syntax leaf recur) where
instance Eq leaf => Eq1 (Syntax leaf) where
liftEq = genericLiftEq
instance Eq leaf => GAlign (Syntax leaf)