mirror of
https://github.com/github/semantic.git
synced 2024-12-25 16:02:43 +03:00
Merge branch 'master' into markdown
This commit is contained in:
commit
b4e6e1dbb8
@ -1,5 +1,5 @@
|
|||||||
name: semantic-diff
|
name: semantic-diff
|
||||||
version: 0.1.0.0
|
version: 0.1.0
|
||||||
synopsis: Initial project template from stack
|
synopsis: Initial project template from stack
|
||||||
description: Please see README.md
|
description: Please see README.md
|
||||||
homepage: http://github.com/github/semantic-diff#readme
|
homepage: http://github.com/github/semantic-diff#readme
|
||||||
@ -15,6 +15,7 @@ library
|
|||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
exposed-modules: Algorithm
|
exposed-modules: Algorithm
|
||||||
, Alignment
|
, Alignment
|
||||||
|
, Arguments
|
||||||
, Category
|
, Category
|
||||||
, Data.Align.Generic
|
, Data.Align.Generic
|
||||||
, Data.Bifunctor.Join.Arbitrary
|
, Data.Bifunctor.Join.Arbitrary
|
||||||
@ -30,6 +31,8 @@ library
|
|||||||
, Info
|
, Info
|
||||||
, Interpreter
|
, Interpreter
|
||||||
, Language
|
, Language
|
||||||
|
, Language.C
|
||||||
|
, Language.JavaScript
|
||||||
, Parser
|
, Parser
|
||||||
, Patch
|
, Patch
|
||||||
, Patch.Arbitrary
|
, Patch.Arbitrary
|
||||||
@ -49,6 +52,7 @@ library
|
|||||||
, TreeSitter
|
, TreeSitter
|
||||||
, DiffSummary
|
, DiffSummary
|
||||||
, Prologue
|
, Prologue
|
||||||
|
, Paths_semantic_diff
|
||||||
build-depends: aeson
|
build-depends: aeson
|
||||||
, base >= 4.8 && < 5
|
, base >= 4.8 && < 5
|
||||||
, bifunctors
|
, bifunctors
|
||||||
|
@ -38,15 +38,15 @@ numberedRows = countUp (both 1 1)
|
|||||||
nextLineNumbers from row = modifyJoin (fromThese identity identity) (succ <$ row) <*> from
|
nextLineNumbers from row = modifyJoin (fromThese identity identity) (succ <$ row) <*> from
|
||||||
|
|
||||||
-- | Determine whether a line contains any patches.
|
-- | Determine whether a line contains any patches.
|
||||||
hasChanges :: SplitDiff leaf annotation -> Bool
|
hasChanges :: (Prologue.Foldable f, Functor f) => SplitDiff f annotation -> Bool
|
||||||
hasChanges = or . (True <$)
|
hasChanges = or . (True <$)
|
||||||
|
|
||||||
-- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side.
|
-- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side.
|
||||||
alignDiff :: HasField fields Range => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))]
|
alignDiff :: HasField fields Range => Both (Source Char) -> SyntaxDiff leaf fields -> [Join These (SplitSyntaxDiff leaf fields)]
|
||||||
alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) wrap getRange sources) (alignPatch sources <$> diff)
|
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.
|
-- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff.
|
||||||
alignPatch :: forall fields leaf. 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 (SyntaxTerm leaf fields) -> [Join These (SplitSyntaxDiff leaf fields)]
|
||||||
alignPatch sources patch = case patch of
|
alignPatch sources patch = case patch of
|
||||||
Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term
|
Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term
|
||||||
Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term
|
Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term
|
||||||
@ -54,13 +54,13 @@ alignPatch sources patch = case patch of
|
|||||||
(alignSyntax' this (fst sources) term1)
|
(alignSyntax' this (fst sources) term1)
|
||||||
(alignSyntax' that (snd sources) term2)
|
(alignSyntax' that (snd sources) term2)
|
||||||
where getRange = characterRange . extract
|
where getRange = characterRange . extract
|
||||||
alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> Term leaf (Record fields) -> [Join These (Term leaf (Record fields))]
|
alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> SyntaxTerm leaf fields -> [Join These (SyntaxTerm leaf fields)]
|
||||||
alignSyntax' side source term = hylo (alignSyntax side cofree getRange (Identity source)) runCofree (Identity <$> term)
|
alignSyntax' side source term = hylo (alignSyntax side cofree getRange (Identity source)) runCofree (Identity <$> term)
|
||||||
this = Join . This . runIdentity
|
this = Join . This . runIdentity
|
||||||
that = Join . That . runIdentity
|
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.
|
-- | 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, 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) -> (SyntaxTermF leaf fields term -> term) -> (term -> Range) -> f (Source Char) -> TermF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term]
|
||||||
alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = catMaybes $ case syntax of
|
alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = catMaybes $ case syntax of
|
||||||
Leaf s -> wrapInBranch (const (Leaf s)) <$> alignBranch getRange [] bothRanges
|
Leaf s -> wrapInBranch (const (Leaf s)) <$> alignBranch getRange [] bothRanges
|
||||||
Syntax.Comment a -> wrapInBranch (const (Syntax.Comment a)) <$> alignBranch getRange [] bothRanges
|
Syntax.Comment a -> wrapInBranch (const (Syntax.Comment a)) <$> alignBranch getRange [] bothRanges
|
||||||
|
23
src/Arguments.hs
Normal file
23
src/Arguments.hs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module Arguments (Arguments(..), args) where
|
||||||
|
|
||||||
|
import Data.Functor.Both
|
||||||
|
import qualified Prelude as P
|
||||||
|
import Prelude
|
||||||
|
import qualified Renderer as R
|
||||||
|
|
||||||
|
-- | The command line arguments to the application.
|
||||||
|
data Arguments = Arguments {
|
||||||
|
format :: R.Format,
|
||||||
|
maybeShas :: Both (Maybe P.String),
|
||||||
|
maybeTimeout :: Maybe Float,
|
||||||
|
output :: Maybe FilePath,
|
||||||
|
filepaths :: [FilePath] }
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
args :: String -> String -> [String] -> R.Format -> Arguments
|
||||||
|
args sha1 sha2 filePaths format = Arguments { format = format
|
||||||
|
, maybeShas = Just <$> both sha1 sha2
|
||||||
|
, filepaths = filePaths
|
||||||
|
, maybeTimeout = Just 10.0
|
||||||
|
, output = Nothing
|
||||||
|
}
|
20
src/Diff.hs
20
src/Diff.hs
@ -6,35 +6,39 @@ import Prologue
|
|||||||
import Data.Functor.Foldable as Foldable
|
import Data.Functor.Foldable as Foldable
|
||||||
import Data.Functor.Both as Both
|
import Data.Functor.Both as Both
|
||||||
import Data.Mergeable
|
import Data.Mergeable
|
||||||
|
import Data.Record
|
||||||
import Patch
|
import Patch
|
||||||
import Syntax
|
import Syntax
|
||||||
import Term
|
import Term
|
||||||
|
|
||||||
-- | An annotated series of patches of terms.
|
-- | An annotated series of patches of terms.
|
||||||
type DiffF leaf annotation = FreeF (CofreeF (Syntax leaf) (Both annotation)) (Patch (Term leaf annotation))
|
type DiffF f annotation = FreeF (TermF f (Both annotation)) (Patch (Term f annotation))
|
||||||
type Diff a annotation = Free (CofreeF (Syntax a) (Both annotation)) (Patch (Term a annotation))
|
type Diff f annotation = Free (TermF f (Both annotation)) (Patch (Term f annotation))
|
||||||
|
|
||||||
|
type SyntaxDiff leaf fields = Diff (Syntax leaf) (Record fields)
|
||||||
|
|
||||||
|
|
||||||
type instance Base (Free f a) = FreeF f a
|
type instance Base (Free f a) = FreeF f a
|
||||||
instance Functor f => Foldable.Foldable (Free f a) where project = runFree
|
instance Functor f => Foldable.Foldable (Free f a) where project = runFree
|
||||||
instance Functor f => Foldable.Unfoldable (Free f a) where embed = free
|
instance Functor f => Foldable.Unfoldable (Free f a) where embed = free
|
||||||
|
|
||||||
diffSum :: (Patch (Term a annotation) -> Int) -> Diff a annotation -> Int
|
diffSum :: (Prologue.Foldable f, Functor f) => (Patch (Term f annotation) -> Int) -> Diff f annotation -> Int
|
||||||
diffSum patchCost diff = sum $ fmap patchCost diff
|
diffSum patchCost diff = sum $ fmap patchCost diff
|
||||||
|
|
||||||
-- | The sum of the node count of the diff’s patches.
|
-- | The sum of the node count of the diff’s patches.
|
||||||
diffCost :: Diff a annotation -> Int
|
diffCost :: (Prologue.Foldable f, Functor f) => Diff f annotation -> Int
|
||||||
diffCost = diffSum $ patchSum termSize
|
diffCost = diffSum $ patchSum termSize
|
||||||
|
|
||||||
-- | Merge a diff using a function to provide the Term (in Maybe, to simplify recovery of the before/after state) for every Patch.
|
-- | Merge a diff using a function to provide the Term (in Maybe, to simplify recovery of the before/after state) for every Patch.
|
||||||
mergeMaybe :: (Patch (Term leaf annotation) -> Maybe (Term leaf annotation)) -> Diff leaf annotation -> Maybe (Term leaf annotation)
|
mergeMaybe :: (Functor f, Mergeable f) => (Patch (Term f annotation) -> Maybe (Term f annotation)) -> Diff f annotation -> Maybe (Term f annotation)
|
||||||
mergeMaybe transform = iter algebra . fmap transform
|
mergeMaybe transform = iter algebra . fmap transform
|
||||||
where algebra :: CofreeF (Syntax leaf) (Both annotation) (Maybe (Term leaf annotation)) -> Maybe (Term leaf annotation)
|
where algebra :: Mergeable f => TermF f (Both annotation) (Maybe (Term f annotation)) -> Maybe (Term f annotation)
|
||||||
algebra (annotations :< syntax) = cofree . (Both.fst annotations :<) <$> sequenceAlt syntax
|
algebra (annotations :< syntax) = cofree . (Both.fst annotations :<) <$> sequenceAlt syntax
|
||||||
|
|
||||||
-- | Recover the before state of a diff.
|
-- | Recover the before state of a diff.
|
||||||
beforeTerm :: Diff leaf annotation -> Maybe (Term leaf annotation)
|
beforeTerm :: (Functor f, Mergeable f) => Diff f annotation -> Maybe (Term f annotation)
|
||||||
beforeTerm = mergeMaybe before
|
beforeTerm = mergeMaybe before
|
||||||
|
|
||||||
-- | Recover the after state of a diff.
|
-- | Recover the after state of a diff.
|
||||||
afterTerm :: Diff leaf annotation -> Maybe (Term leaf annotation)
|
afterTerm :: (Functor f, Mergeable f) => Diff f annotation -> Maybe (Term f annotation)
|
||||||
afterTerm = mergeMaybe after
|
afterTerm = mergeMaybe after
|
||||||
|
@ -3,11 +3,13 @@ module Diff.Arbitrary where
|
|||||||
import Diff
|
import Diff
|
||||||
import Data.Bifunctor.Join
|
import Data.Bifunctor.Join
|
||||||
import Data.Bifunctor.Join.Arbitrary ()
|
import Data.Bifunctor.Join.Arbitrary ()
|
||||||
|
import Data.Functor.Both
|
||||||
import Data.Functor.Foldable (unfold)
|
import Data.Functor.Foldable (unfold)
|
||||||
import Patch
|
import Patch
|
||||||
import Patch.Arbitrary ()
|
import Patch.Arbitrary ()
|
||||||
import Syntax
|
import Syntax
|
||||||
import Prologue
|
import Prologue
|
||||||
|
import Term
|
||||||
import Term.Arbitrary
|
import Term.Arbitrary
|
||||||
import Test.QuickCheck hiding (Fixed)
|
import Test.QuickCheck hiding (Fixed)
|
||||||
|
|
||||||
@ -16,11 +18,11 @@ data ArbitraryDiff leaf annotation
|
|||||||
| ArbitraryPure (Patch (ArbitraryTerm leaf annotation))
|
| ArbitraryPure (Patch (ArbitraryTerm leaf annotation))
|
||||||
deriving (Show, Eq, Generic)
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
unArbitraryDiff :: ArbitraryDiff leaf annotation -> FreeF (CofreeF (Syntax leaf) (Join (,) annotation)) (Patch (ArbitraryTerm leaf annotation)) (ArbitraryDiff leaf annotation)
|
unArbitraryDiff :: ArbitraryDiff leaf annotation -> FreeF (TermF (Syntax leaf) (Both annotation)) (Patch (ArbitraryTerm leaf annotation)) (ArbitraryDiff leaf annotation)
|
||||||
unArbitraryDiff (ArbitraryFree a s) = Free (a :< s)
|
unArbitraryDiff (ArbitraryFree a s) = Free (a :< s)
|
||||||
unArbitraryDiff (ArbitraryPure p) = Pure p
|
unArbitraryDiff (ArbitraryPure p) = Pure p
|
||||||
|
|
||||||
toDiff :: ArbitraryDiff leaf annotation -> Diff leaf annotation
|
toDiff :: ArbitraryDiff leaf annotation -> Diff (Syntax leaf) annotation
|
||||||
toDiff = fmap (fmap toTerm) . unfold unArbitraryDiff
|
toDiff = fmap (fmap toTerm) . unfold unArbitraryDiff
|
||||||
|
|
||||||
diffOfSize :: (Arbitrary leaf, Arbitrary annotation) => Int -> Gen (ArbitraryDiff leaf annotation)
|
diffOfSize :: (Arbitrary leaf, Arbitrary annotation) => Int -> Gen (ArbitraryDiff leaf annotation)
|
||||||
|
@ -22,6 +22,25 @@ import qualified Text.PrettyPrint.Leijen.Text as P
|
|||||||
import SourceSpan
|
import SourceSpan
|
||||||
import Source
|
import Source
|
||||||
|
|
||||||
|
data Identifiable a = Identifiable a | Unidentifiable a
|
||||||
|
|
||||||
|
isIdentifiable :: (HasCategory leaf, HasField fields Category, HasField fields Range) => SyntaxTerm leaf fields -> Bool
|
||||||
|
isIdentifiable term =
|
||||||
|
case unwrap term of
|
||||||
|
S.FunctionCall _ _ -> True
|
||||||
|
S.Function{} -> True
|
||||||
|
S.Assignment{} -> True
|
||||||
|
S.MathAssignment{} -> True
|
||||||
|
S.VarAssignment{} -> True
|
||||||
|
S.SubscriptAccess{} -> True
|
||||||
|
S.Class _ _ _ -> True
|
||||||
|
S.Method _ _ _ -> True
|
||||||
|
S.Leaf _ -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
identifiable :: (HasCategory leaf, HasField fields Category, HasField fields Range) => SyntaxTerm leaf fields -> Identifiable (SyntaxTerm leaf fields)
|
||||||
|
identifiable term = if isIdentifiable term then Identifiable term else Unidentifiable term
|
||||||
|
|
||||||
data DiffInfo = LeafInfo { categoryName :: Text, termName :: Text }
|
data DiffInfo = LeafInfo { categoryName :: Text, termName :: Text }
|
||||||
| BranchInfo { branches :: [ DiffInfo ], categoryName :: Text, branchType :: Branch }
|
| BranchInfo { branches :: [ DiffInfo ], categoryName :: Text, branchType :: Branch }
|
||||||
| ErrorInfo { errorSpan :: SourceSpan, termName :: Text }
|
| ErrorInfo { errorSpan :: SourceSpan, termName :: Text }
|
||||||
@ -35,7 +54,7 @@ data DiffSummary a = DiffSummary {
|
|||||||
} deriving (Eq, Functor, Show, Generic)
|
} deriving (Eq, Functor, Show, Generic)
|
||||||
|
|
||||||
-- Returns a list of diff summary texts given two source blobs and a diff.
|
-- Returns a list of diff summary texts given two source blobs and a diff.
|
||||||
diffSummaries :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Both SourceBlob -> Diff leaf (Record fields) -> [Either Text Text]
|
diffSummaries :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Both SourceBlob -> SyntaxDiff leaf fields -> [Either Text Text]
|
||||||
diffSummaries blobs diff = summaryToTexts =<< diffToDiffSummaries (source <$> blobs) diff
|
diffSummaries blobs diff = summaryToTexts =<< diffToDiffSummaries (source <$> blobs) diff
|
||||||
|
|
||||||
-- Takes a 'DiffSummary' and returns a list of summary texts representing the LeafInfos
|
-- Takes a 'DiffSummary' and returns a list of summary texts representing the LeafInfos
|
||||||
@ -44,7 +63,7 @@ summaryToTexts :: DiffSummary DiffInfo -> [Either Text Text]
|
|||||||
summaryToTexts DiffSummary{..} = runJoin . fmap (show . (P.<> maybeParentContext parentAnnotation)) <$> (Join <$> summaries patch)
|
summaryToTexts DiffSummary{..} = runJoin . fmap (show . (P.<> maybeParentContext parentAnnotation)) <$> (Join <$> summaries patch)
|
||||||
|
|
||||||
-- Returns a list of 'DiffSummary' given two source blobs and a diff.
|
-- Returns a list of 'DiffSummary' given two source blobs and a diff.
|
||||||
diffToDiffSummaries :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Both (Source Char) -> Diff leaf (Record fields) -> [DiffSummary DiffInfo]
|
diffToDiffSummaries :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Both (Source Char) -> SyntaxDiff leaf fields -> [DiffSummary DiffInfo]
|
||||||
diffToDiffSummaries sources = para $ \diff ->
|
diffToDiffSummaries sources = para $ \diff ->
|
||||||
let diff' = free (Prologue.fst <$> diff)
|
let diff' = free (Prologue.fst <$> diff)
|
||||||
annotateWithCategory :: [(Diff leaf (Record fields), [DiffSummary DiffInfo])] -> [DiffSummary DiffInfo]
|
annotateWithCategory :: [(Diff leaf (Record fields), [DiffSummary DiffInfo])] -> [DiffSummary DiffInfo]
|
||||||
@ -85,7 +104,7 @@ toLeafInfos BranchInfo{..} = toLeafInfos =<< branches
|
|||||||
toLeafInfos err@ErrorInfo{} = pure (pretty err)
|
toLeafInfos err@ErrorInfo{} = pure (pretty err)
|
||||||
|
|
||||||
-- Returns a text representing a specific term given a source and a term.
|
-- Returns a text representing a specific term given a source and a term.
|
||||||
toTermName :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Source Char -> Term leaf (Record fields) -> Text
|
toTermName :: forall leaf fields. (HasCategory leaf, HasField fields Category, HasField fields Range) => Source Char -> SyntaxTerm leaf fields -> Text
|
||||||
toTermName source term = case unwrap term of
|
toTermName source term = case unwrap term of
|
||||||
S.AnonymousFunction _ _ -> "anonymous"
|
S.AnonymousFunction _ _ -> "anonymous"
|
||||||
S.Fixed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children
|
S.Fixed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children
|
||||||
@ -95,13 +114,13 @@ toTermName source term = case unwrap term of
|
|||||||
(S.MemberAccess{}, S.AnonymousFunction{..}) -> toTermName' identifier
|
(S.MemberAccess{}, S.AnonymousFunction{..}) -> toTermName' identifier
|
||||||
(_, _) -> toTermName' identifier <> toTermName' value
|
(_, _) -> toTermName' identifier <> toTermName' value
|
||||||
S.Function identifier _ _ -> toTermName' identifier
|
S.Function identifier _ _ -> toTermName' identifier
|
||||||
S.FunctionCall i _ -> toTermName' i
|
S.FunctionCall i args -> toTermName' i <> "(" <> (intercalate ", " (toArgName <$> args)) <> ")"
|
||||||
S.MemberAccess base property -> case (unwrap base, unwrap property) of
|
S.MemberAccess base property -> case (unwrap base, unwrap property) of
|
||||||
(S.FunctionCall{}, S.FunctionCall{}) -> toTermName' base <> "()." <> toTermName' property <> "()"
|
(S.FunctionCall{}, S.FunctionCall{}) -> toTermName' base <> "()." <> toTermName' property <> "()"
|
||||||
(S.FunctionCall{}, _) -> toTermName' base <> "()." <> toTermName' property
|
(S.FunctionCall{}, _) -> toTermName' base <> "()." <> toTermName' property
|
||||||
(_, S.FunctionCall{}) -> toTermName' base <> "." <> toTermName' property <> "()"
|
(_, S.FunctionCall{}) -> toTermName' base <> "." <> toTermName' property <> "()"
|
||||||
(_, _) -> toTermName' base <> "." <> toTermName' property
|
(_, _) -> toTermName' base <> "." <> toTermName' property
|
||||||
S.MethodCall targetId methodId _ -> toTermName' targetId <> sep <> toTermName' methodId <> "()"
|
S.MethodCall targetId methodId methodParams -> toTermName' targetId <> sep <> toTermName' methodId <> "(" <> (intercalate ", " (toArgName <$> methodParams)) <> ")"
|
||||||
where sep = case unwrap targetId of
|
where sep = case unwrap targetId of
|
||||||
S.FunctionCall{} -> "()."
|
S.FunctionCall{} -> "()."
|
||||||
_ -> "."
|
_ -> "."
|
||||||
@ -143,6 +162,10 @@ toTermName source term = case unwrap term of
|
|||||||
termNameFromSource term = termNameFromRange (range term)
|
termNameFromSource term = termNameFromRange (range term)
|
||||||
termNameFromRange range = toText $ Source.slice range source
|
termNameFromRange range = toText $ Source.slice range source
|
||||||
range = characterRange . extract
|
range = characterRange . extract
|
||||||
|
toArgName :: (HasCategory leaf, HasField fields Category, HasField fields Range) => SyntaxTerm leaf fields -> Text
|
||||||
|
toArgName arg = case identifiable arg of
|
||||||
|
Identifiable arg -> toTermName' arg
|
||||||
|
Unidentifiable _ -> "..."
|
||||||
|
|
||||||
maybeParentContext :: Maybe (Category, Text) -> Doc
|
maybeParentContext :: Maybe (Category, Text) -> Doc
|
||||||
maybeParentContext = maybe "" (\annotation ->
|
maybeParentContext = maybe "" (\annotation ->
|
||||||
@ -151,13 +174,12 @@ maybeParentContext = maybe "" (\annotation ->
|
|||||||
toDoc :: Text -> Doc
|
toDoc :: Text -> Doc
|
||||||
toDoc = string . toS
|
toDoc = string . toS
|
||||||
|
|
||||||
termToDiffInfo :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Source Char -> Term leaf (Record fields) -> DiffInfo
|
termToDiffInfo :: (HasCategory leaf, HasField fields Category, HasField fields Range) => Source Char -> SyntaxTerm leaf fields -> DiffInfo
|
||||||
termToDiffInfo blob term = case unwrap term of
|
termToDiffInfo blob term = case unwrap term of
|
||||||
Leaf _ -> LeafInfo (toCategoryName term) (toTermName' term)
|
Leaf _ -> LeafInfo (toCategoryName term) (toTermName' term)
|
||||||
S.AnonymousFunction _ _ -> LeafInfo (toCategoryName term) ("anonymous")
|
S.AnonymousFunction _ _ -> LeafInfo (toCategoryName term) ("anonymous")
|
||||||
S.Indexed children -> BranchInfo (termToDiffInfo' <$> children) (toCategoryName term) BIndexed
|
S.Indexed children -> BranchInfo (termToDiffInfo' <$> children) (toCategoryName term) BIndexed
|
||||||
S.Fixed children -> BranchInfo (termToDiffInfo' <$> children) (toCategoryName term) BFixed
|
S.Fixed children -> BranchInfo (termToDiffInfo' <$> children) (toCategoryName term) BFixed
|
||||||
S.FunctionCall identifier _ -> LeafInfo (toCategoryName term) (toTermName' identifier)
|
|
||||||
S.Ternary ternaryCondition _ -> LeafInfo (toCategoryName term) (toTermName' ternaryCondition)
|
S.Ternary ternaryCondition _ -> LeafInfo (toCategoryName term) (toTermName' ternaryCondition)
|
||||||
S.Function identifier _ _ -> LeafInfo (toCategoryName term) (toTermName' identifier)
|
S.Function identifier _ _ -> LeafInfo (toCategoryName term) (toTermName' identifier)
|
||||||
S.Assignment identifier _ -> LeafInfo (toCategoryName term) (toTermName' identifier)
|
S.Assignment identifier _ -> LeafInfo (toCategoryName term) (toTermName' identifier)
|
||||||
@ -171,22 +193,11 @@ termToDiffInfo blob term = case unwrap term of
|
|||||||
where toTermName' = toTermName blob
|
where toTermName' = toTermName blob
|
||||||
termToDiffInfo' = termToDiffInfo blob
|
termToDiffInfo' = termToDiffInfo blob
|
||||||
|
|
||||||
prependSummary :: (HasCategory leaf, HasField fields Range, HasField fields Category) => Source Char -> Term leaf (Record fields) -> DiffSummary DiffInfo -> DiffSummary DiffInfo
|
prependSummary :: (HasCategory leaf, HasField fields Range, HasField fields Category) => Source Char -> SyntaxTerm leaf fields -> DiffSummary DiffInfo -> DiffSummary DiffInfo
|
||||||
prependSummary source term summary = if (isNothing $ parentAnnotation summary) && hasIdentifier term
|
prependSummary source term summary =
|
||||||
then summary { parentAnnotation = Just (category $ extract term, toTermName source term) }
|
case (parentAnnotation summary, identifiable term) of
|
||||||
else summary
|
(Nothing, Identifiable term) -> summary { parentAnnotation = Just (category . extract $ term, toTermName source term) }
|
||||||
where hasIdentifier term = case unwrap term of
|
(_, _) -> summary
|
||||||
S.FunctionCall{} -> True
|
|
||||||
S.Function _ _ _ -> True
|
|
||||||
S.Assignment{} -> True
|
|
||||||
S.MathAssignment{} -> True
|
|
||||||
S.MemberAccess{} -> True
|
|
||||||
S.MethodCall{} -> True
|
|
||||||
S.VarAssignment{} -> True
|
|
||||||
S.SubscriptAccess{} -> True
|
|
||||||
S.Class{} -> True
|
|
||||||
S.Method{} -> True
|
|
||||||
_ -> False
|
|
||||||
|
|
||||||
isBranchInfo :: DiffInfo -> Bool
|
isBranchInfo :: DiffInfo -> Bool
|
||||||
isBranchInfo info = case info of
|
isBranchInfo info = case info of
|
||||||
@ -259,7 +270,7 @@ instance HasCategory Category where
|
|||||||
C.CommaOperator -> "comma operator"
|
C.CommaOperator -> "comma operator"
|
||||||
C.Empty -> "empty statement"
|
C.Empty -> "empty statement"
|
||||||
|
|
||||||
instance (HasCategory leaf, HasField fields Category) => HasCategory (Term leaf (Record fields)) where
|
instance (HasCategory leaf, HasField fields Category) => HasCategory (SyntaxTerm leaf fields) where
|
||||||
toCategoryName = toCategoryName . category . extract
|
toCategoryName = toCategoryName . category . extract
|
||||||
|
|
||||||
instance Arbitrary Branch where
|
instance Arbitrary Branch where
|
||||||
|
@ -119,7 +119,7 @@ parserForFilepath path blob = decorateTerm termCostDecorator <$> do
|
|||||||
pure $! breakDownLeavesByWord (source blob) parsed
|
pure $! breakDownLeavesByWord (source blob) parsed
|
||||||
|
|
||||||
-- | Replace every string leaf with leaves of the words in the string.
|
-- | Replace every string leaf with leaves of the words in the string.
|
||||||
breakDownLeavesByWord :: (HasField fields Category, HasField fields Range) => Source Char -> Term Text (Record fields) -> Term Text (Record fields)
|
breakDownLeavesByWord :: (HasField fields Category, HasField fields Range) => Source Char -> Term (Syntax Text) (Record fields) -> Term (Syntax Text) (Record fields)
|
||||||
breakDownLeavesByWord source = cata replaceIn
|
breakDownLeavesByWord source = cata replaceIn
|
||||||
where
|
where
|
||||||
replaceIn (info :< syntax) = cofree $ info :< syntax'
|
replaceIn (info :< syntax) = cofree $ info :< syntax'
|
||||||
@ -132,7 +132,7 @@ breakDownLeavesByWord source = cata replaceIn
|
|||||||
-- Some Category constructors should retain their original structure, and not be sliced
|
-- Some Category constructors should retain their original structure, and not be sliced
|
||||||
-- into words. This Set represents those Category constructors for which we want to
|
-- into words. This Set represents those Category constructors for which we want to
|
||||||
-- preserve the original Syntax.
|
-- preserve the original Syntax.
|
||||||
preserveSyntax = Set.fromList [Regex, Category.Comment]
|
preserveSyntax = Set.fromList [Regex, Category.Comment, Category.TemplateString]
|
||||||
|
|
||||||
-- | Transcode a file to a unicode source.
|
-- | Transcode a file to a unicode source.
|
||||||
transcode :: B1.ByteString -> IO (Source Char)
|
transcode :: B1.ByteString -> IO (Source Char)
|
||||||
@ -159,11 +159,11 @@ termCostDecorator :: (Prologue.Foldable f, Functor f) => TermDecorator f a Cost
|
|||||||
termCostDecorator c = 1 + sum (cost <$> tailF c)
|
termCostDecorator c = 1 + sum (cost <$> tailF c)
|
||||||
|
|
||||||
-- | Determine whether two terms are comparable based on the equality of their categories.
|
-- | Determine whether two terms are comparable based on the equality of their categories.
|
||||||
compareCategoryEq :: HasField fields Category => Term leaf (Record fields) -> Term leaf (Record fields) -> Bool
|
compareCategoryEq :: Functor f => HasField fields Category => Term f (Record fields) -> Term f (Record fields) -> Bool
|
||||||
compareCategoryEq = (==) `on` category . extract
|
compareCategoryEq = (==) `on` category . extract
|
||||||
|
|
||||||
-- | The sum of the node count of the diff’s patches.
|
-- | The sum of the node count of the diff’s patches.
|
||||||
diffCostWithCachedTermCosts :: HasField fields Cost => Diff leaf (Record fields) -> Int
|
diffCostWithCachedTermCosts :: Functor f => HasField fields Cost => Diff f (Record fields) -> Int
|
||||||
diffCostWithCachedTermCosts diff = unCost $ case runFree diff of
|
diffCostWithCachedTermCosts diff = unCost $ case runFree diff of
|
||||||
Free (info :< _) -> sum (cost <$> info)
|
Free (info :< _) -> sum (cost <$> info)
|
||||||
Pure patch -> sum (cost . extract <$> patch)
|
Pure patch -> sum (cost . extract <$> patch)
|
||||||
|
@ -19,23 +19,23 @@ import Syntax as S
|
|||||||
import Term
|
import Term
|
||||||
|
|
||||||
-- | Returns whether two terms are comparable
|
-- | Returns whether two terms are comparable
|
||||||
type Comparable leaf annotation = Term leaf annotation -> Term leaf annotation -> Bool
|
type Comparable f annotation = Term f annotation -> Term f annotation -> Bool
|
||||||
|
|
||||||
-- | Constructs a diff from the CofreeF containing its annotation and syntax. This function has the opportunity to, for example, cache properties in the annotation.
|
-- | Constructs a diff from the CofreeF containing its annotation and syntax. This function has the opportunity to, for example, cache properties in the annotation.
|
||||||
type DiffConstructor leaf annotation = CofreeF (Syntax leaf) (Both annotation) (Diff leaf annotation) -> Diff leaf annotation
|
type DiffConstructor f annotation = TermF f (Both annotation) (Diff f annotation) -> Diff f annotation
|
||||||
|
|
||||||
-- | Diff two terms recursively, given functions characterizing the diffing.
|
-- | Diff two terms recursively, given functions characterizing the diffing.
|
||||||
diffTerms :: (Eq leaf, Eq (Record fields), HasField fields Category, HasField fields (Vector.Vector Double))
|
diffTerms :: (Eq leaf, Eq (Record fields), HasField fields Category, HasField fields (Vector.Vector Double))
|
||||||
=> DiffConstructor leaf (Record fields) -- ^ A function to wrap up & possibly annotate every produced diff.
|
=> DiffConstructor (Syntax leaf) (Record fields) -- ^ A function to wrap up & possibly annotate every produced diff.
|
||||||
-> Comparable leaf (Record fields) -- ^ A function to determine whether or not two terms should even be compared.
|
-> Comparable (Syntax leaf) (Record fields) -- ^ A function to determine whether or not two terms should even be compared.
|
||||||
-> SES.Cost (Diff leaf (Record fields)) -- ^ A function to compute the cost of a given diff node.
|
-> SES.Cost (SyntaxDiff leaf fields) -- ^ A function to compute the cost of a given diff node.
|
||||||
-> Term leaf (Record fields) -- ^ A term representing the old state.
|
-> SyntaxTerm leaf fields -- ^ A term representing the old state.
|
||||||
-> Term leaf (Record fields) -- ^ A term representing the new state.
|
-> SyntaxTerm leaf fields -- ^ A term representing the new state.
|
||||||
-> Diff leaf (Record fields)
|
-> SyntaxDiff leaf fields
|
||||||
diffTerms construct comparable cost a b = fromMaybe (replacing a b) $ diffComparableTerms construct comparable cost a b
|
diffTerms construct comparable cost a b = fromMaybe (replacing a b) $ diffComparableTerms construct comparable cost a b
|
||||||
|
|
||||||
-- | Diff two terms recursively, given functions characterizing the diffing. If the terms are incomparable, returns 'Nothing'.
|
-- | Diff two terms recursively, given functions characterizing the diffing. If the terms are incomparable, returns 'Nothing'.
|
||||||
diffComparableTerms :: (Eq leaf, Eq (Record fields), HasField fields Category, HasField fields (Vector.Vector Double)) => 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))
|
diffComparableTerms :: (Eq leaf, Eq (Record fields), HasField fields Category, HasField fields (Vector.Vector Double)) => DiffConstructor (Syntax leaf) (Record fields) -> Comparable (Syntax leaf) (Record fields) -> SES.Cost (SyntaxDiff leaf fields) -> SyntaxTerm leaf fields -> SyntaxTerm leaf fields -> Maybe (SyntaxDiff leaf fields)
|
||||||
diffComparableTerms construct comparable cost = recur
|
diffComparableTerms construct comparable cost = recur
|
||||||
where recur a b
|
where recur a b
|
||||||
| (category <$> a) == (category <$> b) = hylo construct runCofree <$> zipTerms a b
|
| (category <$> a) == (category <$> b) = hylo construct runCofree <$> zipTerms a b
|
||||||
@ -43,7 +43,7 @@ diffComparableTerms construct comparable cost = recur
|
|||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
|
||||||
-- | Construct an algorithm to diff a pair of terms.
|
-- | Construct an algorithm to diff a pair of terms.
|
||||||
algorithmWithTerms :: (TermF leaf (Both a) diff -> diff) -> Term leaf a -> Term leaf a -> Algorithm (Term leaf a) diff diff
|
algorithmWithTerms :: (TermF (Syntax leaf) (Both a) diff -> diff) -> Term (Syntax leaf) a -> Term (Syntax leaf) a -> Algorithm (Term (Syntax leaf) a) diff diff
|
||||||
algorithmWithTerms construct t1 t2 = case (unwrap t1, unwrap t2) of
|
algorithmWithTerms construct t1 t2 = case (unwrap t1, unwrap t2) of
|
||||||
(Indexed a, Indexed b) -> branch Indexed a b
|
(Indexed a, Indexed b) -> branch Indexed a b
|
||||||
(S.FunctionCall identifierA argsA, S.FunctionCall identifierB argsB) -> do
|
(S.FunctionCall identifierA argsA, S.FunctionCall identifierB argsB) -> do
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
module Language where
|
module Language where
|
||||||
|
|
||||||
|
import Data.Record
|
||||||
|
import Info
|
||||||
import Prologue
|
import Prologue
|
||||||
|
import Source
|
||||||
|
import SourceSpan
|
||||||
|
import qualified Syntax as S
|
||||||
|
import Term
|
||||||
|
|
||||||
-- | A programming language.
|
-- | A programming language.
|
||||||
data Language =
|
data Language =
|
||||||
C
|
C
|
||||||
| CoffeeScript
|
| CoffeeScript
|
||||||
| CPlusPlus
|
| CPlusPlus
|
||||||
| CSharp
|
| CSharp
|
||||||
@ -32,3 +39,17 @@ languageForType mediaType = case mediaType of
|
|||||||
".md" -> Just Markdown
|
".md" -> Just Markdown
|
||||||
".rb" -> Just Ruby
|
".rb" -> Just Ruby
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
|
termConstructor
|
||||||
|
:: Source Char -- ^ The source that the term occurs within.
|
||||||
|
-> IO SourceSpan -- ^ The span that the term occupies. This is passed in 'IO' to guarantee some access constraints & encourage its use only when needed (improving performance).
|
||||||
|
-> Text -- ^ The name of the production for this node.
|
||||||
|
-> Range -- ^ The character range that the term occupies.
|
||||||
|
-> [Term (S.Syntax Text) (Record '[Range, Category])] -- ^ The child nodes of the term.
|
||||||
|
-> IO (Term (S.Syntax Text) (Record '[Range, Category])) -- ^ The resulting term, in IO.
|
||||||
|
termConstructor source sourceSpan name range children =
|
||||||
|
withDefaultInfo <$> case (name, children) of
|
||||||
|
("ERROR", _) -> S.Error <$> sourceSpan <*> pure children
|
||||||
|
(_, []) -> S.Leaf <$> pure (toText $ slice range source)
|
||||||
|
_ -> S.Indexed <$> pure children
|
||||||
|
where withDefaultInfo syntax = cofree ((range .: Other name .: RNil) :< syntax)
|
||||||
|
29
src/Language/C.hs
Normal file
29
src/Language/C.hs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
module Language.C where
|
||||||
|
|
||||||
|
import Data.Record
|
||||||
|
import Info
|
||||||
|
import Prologue
|
||||||
|
import Source
|
||||||
|
import SourceSpan
|
||||||
|
import Syntax
|
||||||
|
import qualified Syntax as S
|
||||||
|
import Term
|
||||||
|
|
||||||
|
termConstructor
|
||||||
|
:: Source Char -- ^ The source that the term occurs within.
|
||||||
|
-> IO SourceSpan -- ^ The span that the term occupies. This is passed in 'IO' to guarantee some access constraints & encourage its use only when needed (improving performance).
|
||||||
|
-> Text -- ^ The name of the production for this node.
|
||||||
|
-> Range -- ^ The character range that the term occupies.
|
||||||
|
-> [Term (Syntax Text) (Record '[Range, Category])] -- ^ The child nodes of the term.
|
||||||
|
-> IO (Term (Syntax Text) (Record '[Range, Category])) -- ^ The resulting term, in IO.
|
||||||
|
termConstructor source sourceSpan name range children
|
||||||
|
| name == "ERROR" = sourceSpan >>= withDefaultInfo . (`S.Error` children)
|
||||||
|
| otherwise = withDefaultInfo $ case (name, children) of
|
||||||
|
(_, []) -> S.Leaf . toText $ slice range source
|
||||||
|
_ -> S.Indexed children
|
||||||
|
where withDefaultInfo syntax = pure $! cofree ((range .: categoryForCProductionName name .: RNil) :< syntax)
|
||||||
|
|
||||||
|
categoryForCProductionName :: Text -> Category
|
||||||
|
categoryForCProductionName name = case name of
|
||||||
|
_ -> Other name
|
151
src/Language/JavaScript.hs
Normal file
151
src/Language/JavaScript.hs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
module Language.JavaScript where
|
||||||
|
|
||||||
|
import Data.Record
|
||||||
|
import Info
|
||||||
|
import Prologue
|
||||||
|
import Source
|
||||||
|
import SourceSpan
|
||||||
|
import qualified Syntax as S
|
||||||
|
import Term
|
||||||
|
|
||||||
|
operators :: [Text]
|
||||||
|
operators = [ "op", "bool_op", "math_op", "delete_op", "type_op", "void_op", "rel_op", "bitwise_op" ]
|
||||||
|
|
||||||
|
functions :: [Text]
|
||||||
|
functions = [ "arrow_function", "generator_function", "function" ]
|
||||||
|
|
||||||
|
forStatements :: [Text]
|
||||||
|
forStatements = [ "for_statement", "for_of_statement", "for_in_statement" ]
|
||||||
|
|
||||||
|
termConstructor
|
||||||
|
:: Source Char -- ^ The source that the term occurs within.
|
||||||
|
-> IO SourceSpan -- ^ The span that the term occupies. This is passed in 'IO' to guarantee some access constraints & encourage its use only when needed (improving performance).
|
||||||
|
-> Text -- ^ The name of the production for this node.
|
||||||
|
-> Range -- ^ The character range that the term occupies.
|
||||||
|
-> [Term (S.Syntax Text) (Record '[Range, Category])] -- ^ The child nodes of the term.
|
||||||
|
-> IO (Term (S.Syntax Text) (Record '[Range, Category])) -- ^ The resulting term, in IO.
|
||||||
|
termConstructor source sourceSpan name range children
|
||||||
|
| name == "ERROR" = sourceSpan >>= withDefaultInfo . (`S.Error` children)
|
||||||
|
| otherwise = withDefaultInfo $ case (name, children) of
|
||||||
|
("return_statement", _) -> S.Return (listToMaybe children)
|
||||||
|
("assignment", [ identifier, value ]) -> S.Assignment identifier value
|
||||||
|
("math_assignment", [ identifier, value ]) -> S.MathAssignment identifier value
|
||||||
|
("member_access", [ base, property ]) -> S.MemberAccess base property
|
||||||
|
("subscript_access", [ base, element ]) -> S.SubscriptAccess base element
|
||||||
|
("comma_op", [ a, b ]) -> case unwrap b of
|
||||||
|
S.Indexed rest -> S.Indexed $ a : rest
|
||||||
|
_ -> S.Indexed children
|
||||||
|
("function_call", _) -> case runCofree <$> children of
|
||||||
|
[ (_ :< S.MemberAccess{..}), (_ :< S.Args args) ] -> S.MethodCall memberId property args
|
||||||
|
[ (_ :< S.MemberAccess{..}) ] -> S.MethodCall memberId property []
|
||||||
|
[ function, (_ :< S.Args args) ] -> S.FunctionCall (cofree function) args
|
||||||
|
(x:xs) -> S.FunctionCall (cofree x) (cofree <$> xs)
|
||||||
|
_ -> S.Indexed children
|
||||||
|
("ternary", (condition:cases)) -> S.Ternary condition cases
|
||||||
|
("arguments", _) -> S.Args children
|
||||||
|
("var_assignment", [ x, y ]) -> S.VarAssignment x y
|
||||||
|
("var_declaration", _) -> S.Indexed $ toVarDecl <$> children
|
||||||
|
("switch_statement", (expr:rest)) -> S.Switch expr rest
|
||||||
|
("case", [ expr, body ]) -> S.Case expr body
|
||||||
|
("object", _) -> S.Object $ foldMap toTuple children
|
||||||
|
("pair", _) -> S.Fixed children
|
||||||
|
("if_statement", [ expr, clause1, clause2 ]) -> S.If expr clause1 (Just clause2)
|
||||||
|
("if_statement", [ expr, clause ]) -> S.If expr clause Nothing
|
||||||
|
("while_statement", [ expr, body ]) -> S.While expr body
|
||||||
|
("do_statement", [ expr, body ]) -> S.DoWhile expr body
|
||||||
|
("throw_statement", [ expr ]) -> S.Throw expr
|
||||||
|
("new_expression", [ expr ]) -> S.Constructor expr
|
||||||
|
("try_statement", [ body ]) -> S.Try body Nothing Nothing
|
||||||
|
("try_statement", [ body, catch ]) | Catch <- category (extract catch) -> S.Try body (Just catch) Nothing
|
||||||
|
("try_statement", [ body, finally ]) | Finally <- category (extract finally) -> S.Try body Nothing (Just finally)
|
||||||
|
("try_statement", [ body, catch, finally ])
|
||||||
|
| Catch <- category (extract catch)
|
||||||
|
, Finally <- category (extract finally) -> S.Try body (Just catch) (Just finally)
|
||||||
|
("array", _) -> S.Array children
|
||||||
|
("method_definition", [ identifier, params, exprs ]) -> S.Method identifier (toList (unwrap params)) (toList (unwrap exprs))
|
||||||
|
("method_definition", [ identifier, exprs ]) -> S.Method identifier [] (toList (unwrap exprs))
|
||||||
|
("class", [ identifier, superclass, definitions ]) -> S.Class identifier (Just superclass) (toList (unwrap definitions))
|
||||||
|
("class", [ identifier, definitions ]) -> S.Class identifier Nothing (toList (unwrap definitions))
|
||||||
|
_ | name `elem` forStatements, Just (exprs, body) <- unsnoc children -> S.For exprs body
|
||||||
|
_ | name `elem` operators -> S.Operator children
|
||||||
|
_ | name `elem` functions -> case children of
|
||||||
|
[ body ] -> S.AnonymousFunction Nothing body
|
||||||
|
[ params, body ] -> S.AnonymousFunction (Just params) body
|
||||||
|
[ id, params, body ] -> S.Function id (Just params) body
|
||||||
|
_ -> S.Indexed children
|
||||||
|
(_, []) -> S.Leaf . toText $ slice range source
|
||||||
|
_ -> S.Indexed children
|
||||||
|
where withDefaultInfo syntax@(S.MethodCall _ _ _) = pure $! cofree ((range .: MethodCall .: RNil) :< syntax)
|
||||||
|
withDefaultInfo syntax = pure $! cofree ((range .: categoryForJavaScriptProductionName name .: RNil) :< syntax)
|
||||||
|
|
||||||
|
categoryForJavaScriptProductionName :: Text -> Category
|
||||||
|
categoryForJavaScriptProductionName name = case name of
|
||||||
|
"object" -> Object
|
||||||
|
"expression_statement" -> ExpressionStatements
|
||||||
|
"this_expression" -> Identifier
|
||||||
|
"null" -> Identifier
|
||||||
|
"undefined" -> Identifier
|
||||||
|
"arrow_function" -> Function
|
||||||
|
"generator_function" -> Function
|
||||||
|
"math_op" -> BinaryOperator -- bitwise operator, e.g. +, -, *, /.
|
||||||
|
"bool_op" -> BinaryOperator -- boolean operator, e.g. ||, &&.
|
||||||
|
"comma_op" -> CommaOperator -- comma operator, e.g. expr1, expr2.
|
||||||
|
"delete_op" -> Operator -- delete operator, e.g. delete x[2].
|
||||||
|
"type_op" -> Operator -- type operator, e.g. typeof Object.
|
||||||
|
"void_op" -> Operator -- void operator, e.g. void 2.
|
||||||
|
"for_in_statement" -> For
|
||||||
|
"for_of_statement" -> For
|
||||||
|
"new_expression" -> Constructor
|
||||||
|
"class" -> Class
|
||||||
|
"catch" -> Catch
|
||||||
|
"finally" -> Finally
|
||||||
|
"if_statement" -> If
|
||||||
|
"empty_statement" -> Empty
|
||||||
|
"program" -> Program
|
||||||
|
"ERROR" -> Error
|
||||||
|
"function_call" -> FunctionCall
|
||||||
|
"pair" -> Pair
|
||||||
|
"string" -> StringLiteral
|
||||||
|
"integer" -> IntegerLiteral
|
||||||
|
"symbol" -> SymbolLiteral
|
||||||
|
"array" -> ArrayLiteral
|
||||||
|
"function" -> Function
|
||||||
|
"identifier" -> Identifier
|
||||||
|
"formal_parameters" -> Params
|
||||||
|
"arguments" -> Args
|
||||||
|
"statement_block" -> ExpressionStatements
|
||||||
|
"assignment" -> Assignment
|
||||||
|
"member_access" -> MemberAccess
|
||||||
|
"op" -> Operator
|
||||||
|
"subscript_access" -> SubscriptAccess
|
||||||
|
"regex" -> Regex
|
||||||
|
"template_string" -> TemplateString
|
||||||
|
"var_assignment" -> VarAssignment
|
||||||
|
"var_declaration" -> VarDecl
|
||||||
|
"switch_statement" -> Switch
|
||||||
|
"math_assignment" -> MathAssignment
|
||||||
|
"case" -> Case
|
||||||
|
"true" -> Boolean
|
||||||
|
"false" -> Boolean
|
||||||
|
"ternary" -> Ternary
|
||||||
|
"for_statement" -> For
|
||||||
|
"while_statement" -> While
|
||||||
|
"do_statement" -> DoWhile
|
||||||
|
"return_statement" -> Return
|
||||||
|
"throw_statement" -> Throw
|
||||||
|
"try_statement" -> Try
|
||||||
|
"method_definition" -> Method
|
||||||
|
"comment" -> Comment
|
||||||
|
"bitwise_op" -> BitwiseOperator
|
||||||
|
"rel_op" -> RelationalOperator
|
||||||
|
_ -> Other name
|
||||||
|
|
||||||
|
toVarDecl :: (HasField fields Category) => Term (S.Syntax Text) (Record fields) -> Term (S.Syntax Text) (Record fields)
|
||||||
|
toVarDecl child = cofree $ (setCategory (extract child) VarDecl :< S.VarDecl child)
|
||||||
|
|
||||||
|
toTuple :: Term (S.Syntax Text) (Record fields) -> [Term (S.Syntax 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)]
|
||||||
|
toTuple child = pure child
|
143
src/Parser.hs
143
src/Parser.hs
@ -1,148 +1,9 @@
|
|||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
module Parser where
|
module Parser where
|
||||||
|
|
||||||
import Prologue hiding (Constructor)
|
import Prologue
|
||||||
import Data.Record
|
import Source
|
||||||
import Data.Text (pack)
|
|
||||||
import Category as C
|
|
||||||
import Info
|
|
||||||
import qualified Syntax as S
|
|
||||||
import Term
|
|
||||||
import qualified Data.Set as Set
|
|
||||||
import Source hiding (uncons)
|
|
||||||
import SourceSpan
|
|
||||||
|
|
||||||
-- | A function that takes a source blob and returns an annotated AST.
|
-- | A function that takes a source blob and returns an annotated AST.
|
||||||
-- | The return is in the IO monad because some of the parsers are written in C
|
-- | The return is in the IO monad because some of the parsers are written in C
|
||||||
-- | and aren't pure.
|
-- | and aren't pure.
|
||||||
type Parser f a = SourceBlob -> IO (Cofree f a)
|
type Parser f a = SourceBlob -> IO (Cofree f a)
|
||||||
|
|
||||||
-- | Whether a category is an Operator Category
|
|
||||||
isOperator :: Category -> Bool
|
|
||||||
isOperator = flip Set.member (Set.fromList [ Operator, BinaryOperator, BitwiseOperator, RelationalOperator ])
|
|
||||||
|
|
||||||
-- | Construct a term given source, the span covered, the annotation for the term, and its children.
|
|
||||||
--
|
|
||||||
-- This is typically called during parsing, building terms up leaf-to-root.
|
|
||||||
termConstructor :: forall fields. (Show (Record fields), HasField fields Category, HasField fields Range)
|
|
||||||
=> Source Char -- ^ The source that the term occurs within.
|
|
||||||
-> IO SourceSpan -- ^ The span that the term occupies. This is passed in 'IO' to guarantee some access constraints & encourage its use only when needed (improving performance).
|
|
||||||
-> Record fields -- ^ The annotation for the term.
|
|
||||||
-> [Term Text (Record fields)] -- ^ The child nodes of the term.
|
|
||||||
-> IO (Term Text (Record fields)) -- ^ The resulting term, in IO.
|
|
||||||
termConstructor source sourceSpan info = fmap cofree . construct
|
|
||||||
where
|
|
||||||
withDefaultInfo syntax = pure (info :< syntax)
|
|
||||||
errorWith children = do
|
|
||||||
sourceSpan' <- sourceSpan
|
|
||||||
withDefaultInfo (S.Error sourceSpan' children)
|
|
||||||
construct :: (Show (Record fields), HasField fields Category, HasField fields Range) => [Term Text (Record fields)] -> IO (CofreeF (S.Syntax Text) (Record fields) (Term Text (Record fields)))
|
|
||||||
construct [] = case category info of
|
|
||||||
Return -> withDefaultInfo $ S.Return Nothing -- Map empty return statements to Return Nothing
|
|
||||||
_ -> withDefaultInfo . S.Leaf . pack . toString $ slice (characterRange info) source
|
|
||||||
construct children | Return == category info =
|
|
||||||
withDefaultInfo $ S.Return (listToMaybe children)
|
|
||||||
construct children | Assignment == category info = case children of
|
|
||||||
(identifier:value:[]) -> withDefaultInfo $ S.Assignment identifier value
|
|
||||||
children -> errorWith children
|
|
||||||
construct children | MathAssignment == category info = case children of
|
|
||||||
(identifier:value:[]) -> withDefaultInfo $ S.MathAssignment identifier value
|
|
||||||
children -> errorWith children
|
|
||||||
construct children | MemberAccess == category info = case children of
|
|
||||||
(base:property:[]) -> withDefaultInfo $ S.MemberAccess base property
|
|
||||||
children -> errorWith children
|
|
||||||
construct children | SubscriptAccess == category info = case children of
|
|
||||||
(base:element:[]) -> withDefaultInfo $ S.SubscriptAccess base element
|
|
||||||
_ -> errorWith children
|
|
||||||
construct children | isOperator (category info) = withDefaultInfo $ S.Operator children
|
|
||||||
construct children | CommaOperator == category info = withDefaultInfo $ case children of
|
|
||||||
[child, rest] | S.Indexed cs <- unwrap rest -> S.Indexed $ child : toList cs
|
|
||||||
_ -> S.Indexed children
|
|
||||||
construct children | Function == category info = case children of
|
|
||||||
(body:[]) -> withDefaultInfo $ S.AnonymousFunction Nothing body
|
|
||||||
(params:body:[]) | (info :< _) <- runCofree params, Params == category info ->
|
|
||||||
withDefaultInfo $ S.AnonymousFunction (Just params) body
|
|
||||||
(id:body:[]) | (info :< _) <- runCofree id, Identifier == category info ->
|
|
||||||
withDefaultInfo $ S.Function id Nothing body
|
|
||||||
(id:params:body:[]) | (info :< _) <- runCofree id, Identifier == category info ->
|
|
||||||
withDefaultInfo $ S.Function id (Just params) body
|
|
||||||
_ -> errorWith children
|
|
||||||
|
|
||||||
construct children | FunctionCall == category info = case runCofree <$> children of
|
|
||||||
[ (_ :< S.MemberAccess{..}), (_ :< S.Args args) ] ->
|
|
||||||
pure $! setCategory info MethodCall :< S.MethodCall memberId property args
|
|
||||||
[ (_ :< S.MemberAccess{..}) ] ->
|
|
||||||
pure $! setCategory info MethodCall :< S.MethodCall memberId property []
|
|
||||||
(x:xs) ->
|
|
||||||
withDefaultInfo $ S.FunctionCall (cofree x) (cofree <$> xs)
|
|
||||||
_ -> errorWith children
|
|
||||||
|
|
||||||
construct children | Ternary == category info = case children of
|
|
||||||
(condition:cases) -> withDefaultInfo $ S.Ternary condition cases
|
|
||||||
_ -> errorWith children
|
|
||||||
construct children | Args == category info = withDefaultInfo $ S.Args children
|
|
||||||
construct children | VarAssignment == category info
|
|
||||||
, [x, y] <- children = withDefaultInfo $ S.VarAssignment x y
|
|
||||||
construct children | VarDecl == category info = withDefaultInfo . S.Indexed $ toVarDecl <$> children
|
|
||||||
where
|
|
||||||
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 =
|
|
||||||
withDefaultInfo $ S.Switch expr children
|
|
||||||
|
|
||||||
construct children | Case == category info, [expr, body] <- children =
|
|
||||||
withDefaultInfo $ S.Case expr body
|
|
||||||
|
|
||||||
construct children | Object == category info = withDefaultInfo . S.Object $ foldMap toTuple children
|
|
||||||
where
|
|
||||||
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)]
|
|
||||||
toTuple child = pure child
|
|
||||||
|
|
||||||
construct children | Pair == (category info) = withDefaultInfo $ S.Fixed children
|
|
||||||
construct children | C.Error == category info =
|
|
||||||
errorWith children
|
|
||||||
construct children | If == category info, Just (expr, clauses) <- uncons children =
|
|
||||||
case clauses of
|
|
||||||
[clause1, clause2] -> withDefaultInfo $ S.If expr clause1 (Just clause2)
|
|
||||||
[clause] -> withDefaultInfo $ S.If expr clause Nothing
|
|
||||||
_ -> errorWith children
|
|
||||||
construct children | For == category info, Just (exprs, body) <- unsnoc children =
|
|
||||||
withDefaultInfo $ S.For exprs body
|
|
||||||
construct children | While == category info, [expr, body] <- children =
|
|
||||||
withDefaultInfo $ S.While expr body
|
|
||||||
construct children | DoWhile == category info, [expr, body] <- children =
|
|
||||||
withDefaultInfo $ S.DoWhile expr body
|
|
||||||
construct children | Throw == category info, [expr] <- children =
|
|
||||||
withDefaultInfo $ S.Throw expr
|
|
||||||
construct children | Constructor == category info, [expr] <- children =
|
|
||||||
withDefaultInfo $ S.Constructor expr
|
|
||||||
construct children | Try == category info = case children of
|
|
||||||
[body] -> withDefaultInfo $ S.Try body Nothing Nothing
|
|
||||||
[body, catch] | Catch <- category (extract catch) -> withDefaultInfo $ S.Try body (Just catch) Nothing
|
|
||||||
[body, finally] | Finally <- category (extract finally) -> withDefaultInfo $ S.Try body Nothing (Just finally)
|
|
||||||
[body, catch, finally] | Catch <- category (extract catch),
|
|
||||||
Finally <- category (extract finally) ->
|
|
||||||
withDefaultInfo $ S.Try body (Just catch) (Just finally)
|
|
||||||
_ -> errorWith children
|
|
||||||
construct children | ArrayLiteral == category info =
|
|
||||||
withDefaultInfo $ S.Array children
|
|
||||||
construct children | Method == category info = case children of
|
|
||||||
[identifier, params, exprs] |
|
|
||||||
Params == category (extract params),
|
|
||||||
S.Indexed params' <- unwrap params ->
|
|
||||||
withDefaultInfo $ S.Method identifier params' (toList (unwrap exprs))
|
|
||||||
[identifier, exprs] ->
|
|
||||||
withDefaultInfo $ S.Method identifier mempty (toList (unwrap exprs))
|
|
||||||
_ -> errorWith children
|
|
||||||
construct children | Class == category info = case children of
|
|
||||||
[identifier, superclass, definitions] ->
|
|
||||||
withDefaultInfo $ S.Class identifier (Just superclass) (toList (unwrap definitions))
|
|
||||||
[identifier, definitions] ->
|
|
||||||
withDefaultInfo $ S.Class identifier Nothing (toList (unwrap definitions))
|
|
||||||
_ -> errorWith children
|
|
||||||
construct children =
|
|
||||||
withDefaultInfo $ S.Indexed children
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
module Renderer (Renderer, DiffArguments(..), Output(..), concatOutputs, toSummaryKey, Format(..)) where
|
module Renderer (Renderer, DiffArguments(..), Output(..), concatOutputs, toSummaryKey, Format(..)) where
|
||||||
|
|
||||||
import Prologue
|
|
||||||
import Data.Functor.Both
|
|
||||||
import Diff
|
|
||||||
import Source (SourceBlob)
|
|
||||||
import Data.Text as T (intercalate)
|
|
||||||
import Data.Aeson (Value, toEncoding)
|
import Data.Aeson (Value, toEncoding)
|
||||||
import Data.Aeson.Encoding (encodingToLazyByteString)
|
import Data.Aeson.Encoding (encodingToLazyByteString)
|
||||||
|
import Data.Functor.Both
|
||||||
import Data.Map as Map hiding (null)
|
import Data.Map as Map hiding (null)
|
||||||
|
import Data.Text as T (intercalate)
|
||||||
|
import Diff
|
||||||
|
import Prologue
|
||||||
|
import Source (SourceBlob)
|
||||||
|
import Syntax
|
||||||
|
|
||||||
-- | A function that will render a diff, given the two source blobs.
|
-- | A function that will render a diff, given the two source blobs.
|
||||||
type Renderer annotation = Both SourceBlob -> Diff Text annotation -> Output
|
type Renderer annotation = Both SourceBlob -> Diff (Syntax Text) annotation -> Output
|
||||||
|
|
||||||
data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath, outputPath :: FilePath }
|
data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath, outputPath :: FilePath }
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
@ -19,7 +20,7 @@ data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath,
|
|||||||
data Format = Split | Patch | JSON | Summary
|
data Format = Split | Patch | JSON | Summary
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
data Output = SplitOutput Text | PatchOutput Text | JSONOutput (Map Text Value) | SummaryOutput (Map Text (Map Text [Text]))
|
data Output = SplitOutput Text | PatchOutput Text | JSONOutput (Map Text Value) | SummaryOutput (Map Text (Map Text [Text]))
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
-- Returns a key representing the filename. If the filenames are different,
|
-- Returns a key representing the filename. If the filenames are different,
|
||||||
|
@ -32,7 +32,7 @@ json blobs diff = JSONOutput $ Map.fromList [
|
|||||||
-- | A numbered 'a'.
|
-- | A numbered 'a'.
|
||||||
newtype NumberedLine a = NumberedLine (Int, a)
|
newtype NumberedLine a = NumberedLine (Int, a)
|
||||||
|
|
||||||
instance (HasField fields Category, HasField fields Range) => ToJSON (NumberedLine (SplitDiff leaf (Record fields))) where
|
instance (HasField fields Category, HasField fields Range) => ToJSON (NumberedLine (SplitSyntaxDiff leaf fields)) where
|
||||||
toJSON (NumberedLine (n, a)) = object (lineFields n a (getRange a))
|
toJSON (NumberedLine (n, a)) = object (lineFields n a (getRange a))
|
||||||
toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a (getRange a))
|
toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a (getRange a))
|
||||||
instance ToJSON Category where
|
instance ToJSON Category where
|
||||||
@ -40,24 +40,24 @@ instance ToJSON Category where
|
|||||||
toJSON s = String . T.pack $ show s
|
toJSON s = String . T.pack $ show s
|
||||||
instance ToJSON Range where
|
instance ToJSON Range where
|
||||||
toJSON (Range start end) = A.Array . Vector.fromList $ toJSON <$> [ start, end ]
|
toJSON (Range start end) = A.Array . Vector.fromList $ toJSON <$> [ start, end ]
|
||||||
toEncoding (Range start end) = foldable [ start, end ]
|
toEncoding (Range start end) = foldable [ start, end ]
|
||||||
instance ToJSON a => ToJSON (Join These a) where
|
instance ToJSON a => ToJSON (Join These a) where
|
||||||
toJSON (Join vs) = A.Array . Vector.fromList $ toJSON <$> these pure pure (\ a b -> [ a, b ]) vs
|
toJSON (Join vs) = A.Array . Vector.fromList $ toJSON <$> these pure pure (\ a b -> [ a, b ]) vs
|
||||||
toEncoding = foldable
|
toEncoding = foldable
|
||||||
instance ToJSON a => ToJSON (Join (,) a) where
|
instance ToJSON a => ToJSON (Join (,) a) where
|
||||||
toJSON (Join (a, b)) = A.Array . Vector.fromList $ toJSON <$> [ a, b ]
|
toJSON (Join (a, b)) = A.Array . Vector.fromList $ toJSON <$> [ a, b ]
|
||||||
instance (HasField fields Category, HasField fields Range) => ToJSON (SplitDiff leaf (Record fields)) where
|
instance (HasField fields Category, HasField fields Range) => ToJSON (SplitSyntaxDiff leaf fields) where
|
||||||
toJSON splitDiff = case runFree splitDiff of
|
toJSON splitDiff = case runFree splitDiff of
|
||||||
(Free (info :< syntax)) -> object (termFields info syntax)
|
(Free (info :< syntax)) -> object (termFields info syntax)
|
||||||
(Pure patch) -> object (patchFields patch)
|
(Pure patch) -> object (patchFields patch)
|
||||||
toEncoding splitDiff = case runFree splitDiff of
|
toEncoding splitDiff = case runFree splitDiff of
|
||||||
(Free (info :< syntax)) -> pairs $ mconcat (termFields info syntax)
|
(Free (info :< syntax)) -> pairs $ mconcat (termFields info syntax)
|
||||||
(Pure patch) -> pairs $ mconcat (patchFields patch)
|
(Pure patch) -> pairs $ mconcat (patchFields patch)
|
||||||
instance (HasField fields Category, HasField fields Range) => ToJSON (Term leaf (Record fields)) where
|
instance (HasField fields Category, HasField fields Range) => ToJSON (SyntaxTerm leaf fields) where
|
||||||
toJSON term | (info :< syntax) <- runCofree term = object (termFields info syntax)
|
toJSON term | (info :< syntax) <- runCofree term = object (termFields info syntax)
|
||||||
toEncoding term | (info :< syntax) <- runCofree term = pairs $ mconcat (termFields info syntax)
|
toEncoding term | (info :< syntax) <- runCofree term = pairs $ mconcat (termFields info syntax)
|
||||||
|
|
||||||
lineFields :: (HasField fields Category, HasField fields Range) => KeyValue kv => Int -> SplitDiff leaf (Record fields) -> Range -> [kv]
|
lineFields :: (HasField fields Category, HasField fields Range) => KeyValue kv => Int -> SplitSyntaxDiff leaf fields -> Range -> [kv]
|
||||||
lineFields n term range = [ "number" .= n
|
lineFields n term range = [ "number" .= n
|
||||||
, "terms" .= [ term ]
|
, "terms" .= [ term ]
|
||||||
, "range" .= range
|
, "range" .= range
|
||||||
@ -102,7 +102,7 @@ termFields info syntax = "range" .= characterRange info : "category" .= category
|
|||||||
S.Method identifier params definitions -> [ "methodIdentifier" .= identifier ] <> [ "params" .= params ] <> [ "definitions" .= definitions ]
|
S.Method identifier params definitions -> [ "methodIdentifier" .= identifier ] <> [ "params" .= params ] <> [ "definitions" .= definitions ]
|
||||||
where childrenFields c = [ "children" .= c ]
|
where childrenFields c = [ "children" .= c ]
|
||||||
|
|
||||||
patchFields :: (KeyValue kv, HasField fields Category, HasField fields Range) => SplitPatch (Term leaf (Record fields)) -> [kv]
|
patchFields :: (KeyValue kv, HasField fields Category, HasField fields Range) => SplitPatch (SyntaxTerm leaf fields) -> [kv]
|
||||||
patchFields patch = case patch of
|
patchFields patch = case patch of
|
||||||
SplitInsert term -> fields "insert" term
|
SplitInsert term -> fields "insert" term
|
||||||
SplitDelete term -> fields "delete" term
|
SplitDelete term -> fields "delete" term
|
||||||
|
@ -53,7 +53,7 @@ rowIncrement :: Join These a -> Both (Sum Int)
|
|||||||
rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$)
|
rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$)
|
||||||
|
|
||||||
-- | Given the before and after sources, render a hunk to a string.
|
-- | Given the before and after sources, render a hunk to a string.
|
||||||
showHunk :: HasField fields Range => Both SourceBlob -> Hunk (SplitDiff a (Record fields)) -> String
|
showHunk :: Functor f => HasField fields Range => Both SourceBlob -> Hunk (SplitDiff f (Record fields)) -> String
|
||||||
showHunk blobs hunk = maybeOffsetHeader <>
|
showHunk blobs hunk = maybeOffsetHeader <>
|
||||||
concat (showChange sources <$> changes hunk) <>
|
concat (showChange sources <$> changes hunk) <>
|
||||||
showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk)
|
showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk)
|
||||||
@ -66,18 +66,18 @@ showHunk blobs hunk = maybeOffsetHeader <>
|
|||||||
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
|
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
|
||||||
|
|
||||||
-- | Given the before and after sources, render a change to a string.
|
-- | Given the before and after sources, render a change to a string.
|
||||||
showChange :: HasField fields Range => Both (Source Char) -> Change (SplitDiff a (Record fields)) -> String
|
showChange :: Functor f => HasField fields Range => Both (Source Char) -> Change (SplitDiff f (Record fields)) -> String
|
||||||
showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) <> deleted <> inserted
|
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))
|
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.
|
-- | Given a source, render a set of lines to a string with a prefix.
|
||||||
showLines :: HasField fields Range => Source Char -> Char -> [Maybe (SplitDiff leaf (Record fields))] -> String
|
showLines :: Functor f => HasField fields Range => Source Char -> Char -> [Maybe (SplitDiff f (Record fields))] -> String
|
||||||
showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines
|
showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines
|
||||||
where prepend "" = ""
|
where prepend "" = ""
|
||||||
prepend source = prefix : source
|
prepend source = prefix : source
|
||||||
|
|
||||||
-- | Given a source, render a line to a string.
|
-- | Given a source, render a line to a string.
|
||||||
showLine :: HasField fields Range => Source Char -> Maybe (SplitDiff leaf (Record fields)) -> Maybe String
|
showLine :: Functor f => HasField fields Range => Source Char -> Maybe (SplitDiff f (Record fields)) -> Maybe String
|
||||||
showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line
|
showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ emptyHunk :: Hunk (SplitDiff a annotation)
|
|||||||
emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] }
|
emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] }
|
||||||
|
|
||||||
-- | Render a diff as a series of hunks.
|
-- | Render a diff as a series of hunks.
|
||||||
hunks :: HasField fields Range => Diff a (Record fields) -> Both SourceBlob -> [Hunk (SplitDiff a (Record fields))]
|
hunks :: HasField fields Range => SyntaxDiff leaf fields -> Both SourceBlob -> [Hunk (SplitSyntaxDiff leaf fields)]
|
||||||
hunks _ blobs | sources <- source <$> blobs
|
hunks _ blobs | sources <- source <$> blobs
|
||||||
, sourcesEqual <- runBothWith (==) sources
|
, sourcesEqual <- runBothWith (==) sources
|
||||||
, sourcesNull <- runBothWith (&&) (null <$> sources)
|
, sourcesNull <- runBothWith (&&) (null <$> sources)
|
||||||
@ -126,14 +126,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
|
-- | Given beginning line numbers, turn rows in a split diff into hunks in a
|
||||||
-- | patch.
|
-- | patch.
|
||||||
hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> [Hunk (SplitDiff a annotation)]
|
hunksInRows :: (Prologue.Foldable f, Functor f) => Both (Sum Int) -> [Join These (SplitDiff f annotation)] -> [Hunk (SplitDiff f annotation)]
|
||||||
hunksInRows start rows = case nextHunk start rows of
|
hunksInRows start rows = case nextHunk start rows of
|
||||||
Nothing -> []
|
Nothing -> []
|
||||||
Just (hunk, rest) -> hunk : hunksInRows (offset hunk <> hunkLength hunk) rest
|
Just (hunk, rest) -> hunk : hunksInRows (offset hunk <> hunkLength hunk) rest
|
||||||
|
|
||||||
-- | Given beginning line numbers, return the next hunk and the remaining rows
|
-- | Given beginning line numbers, return the next hunk and the remaining rows
|
||||||
-- | of the split diff.
|
-- | of the split diff.
|
||||||
nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> Maybe (Hunk (SplitDiff a annotation), [Join These (SplitDiff a annotation)])
|
nextHunk :: (Prologue.Foldable f, Functor f) => Both (Sum Int) -> [Join These (SplitDiff f annotation)] -> Maybe (Hunk (SplitDiff f annotation), [Join These (SplitDiff f annotation)])
|
||||||
nextHunk start rows = case nextChange start rows of
|
nextHunk start rows = case nextChange start rows of
|
||||||
Nothing -> Nothing
|
Nothing -> Nothing
|
||||||
Just (offset, change, rest) -> let (changes, rest') = contiguousChanges rest in Just (Hunk offset (change : changes) $ take 3 rest', drop 3 rest')
|
Just (offset, change, rest) -> let (changes, rest') = contiguousChanges rest in Just (Hunk offset (change : changes) $ take 3 rest', drop 3 rest')
|
||||||
@ -145,7 +145,7 @@ nextHunk start rows = case nextChange start rows of
|
|||||||
|
|
||||||
-- | Given beginning line numbers, return the number of lines to the next
|
-- | Given beginning line numbers, return the number of lines to the next
|
||||||
-- | the next change, and the remaining rows of the split diff.
|
-- | the next change, and the remaining rows of the split diff.
|
||||||
nextChange :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> Maybe (Both (Sum Int), Change (SplitDiff a annotation), [Join These (SplitDiff a annotation)])
|
nextChange :: (Prologue.Foldable f, Functor f) => Both (Sum Int) -> [Join These (SplitDiff f annotation)] -> Maybe (Both (Sum Int), Change (SplitDiff f annotation), [Join These (SplitDiff f annotation)])
|
||||||
nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of
|
nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of
|
||||||
Nothing -> Nothing
|
Nothing -> Nothing
|
||||||
Just (change, afterChanges) -> Just (start <> mconcat (rowIncrement <$> skippedContext), change, afterChanges)
|
Just (change, afterChanges) -> Just (start <> mconcat (rowIncrement <$> skippedContext), change, afterChanges)
|
||||||
@ -155,12 +155,12 @@ nextChange start rows = case changeIncludingContext leadingContext afterLeadingC
|
|||||||
-- | Return a Change with the given context and the rows from the begginning of
|
-- | 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
|
-- | the given rows that have changes, or Nothing if the first row has no
|
||||||
-- | changes.
|
-- | changes.
|
||||||
changeIncludingContext :: [Join These (SplitDiff a annotation)] -> [Join These (SplitDiff a annotation)] -> Maybe (Change (SplitDiff a annotation), [Join These (SplitDiff a annotation)])
|
changeIncludingContext :: (Prologue.Foldable f, Functor f) => [Join These (SplitDiff f annotation)] -> [Join These (SplitDiff f annotation)] -> Maybe (Change (SplitDiff f annotation), [Join These (SplitDiff f annotation)])
|
||||||
changeIncludingContext leadingContext rows = case changes of
|
changeIncludingContext leadingContext rows = case changes of
|
||||||
[] -> Nothing
|
[] -> Nothing
|
||||||
_ -> Just (Change leadingContext changes, afterChanges)
|
_ -> Just (Change leadingContext changes, afterChanges)
|
||||||
where (changes, afterChanges) = span rowHasChanges rows
|
where (changes, afterChanges) = span rowHasChanges rows
|
||||||
|
|
||||||
-- | Whether a row has changes on either side.
|
-- | Whether a row has changes on either side.
|
||||||
rowHasChanges :: Join These (SplitDiff a annotation) -> Bool
|
rowHasChanges :: (Prologue.Foldable f, Functor f) => Join These (SplitDiff f annotation) -> Bool
|
||||||
rowHasChanges row = or (hasChanges <$> row)
|
rowHasChanges row = or (hasChanges <$> row)
|
||||||
|
@ -77,6 +77,7 @@ styleName category = "category-" <> case category of
|
|||||||
C.Method -> "method"
|
C.Method -> "method"
|
||||||
C.If -> "if_statement"
|
C.If -> "if_statement"
|
||||||
C.Empty -> "empty_statement"
|
C.Empty -> "empty_statement"
|
||||||
|
C.CommaOperator -> "comma_operator"
|
||||||
Other string -> string
|
Other string -> string
|
||||||
|
|
||||||
-- | Pick the class name for a split patch.
|
-- | Pick the class name for a split patch.
|
||||||
@ -141,15 +142,15 @@ wrapIn f p = f p
|
|||||||
|
|
||||||
-- Instances
|
-- Instances
|
||||||
|
|
||||||
instance (ToMarkup f, HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (CofreeF (Syntax leaf) (Record fields) (f, Range))) where
|
instance (ToMarkup f, HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (SyntaxTermF leaf fields (f, Range))) where
|
||||||
toMarkup (Renderable source (info :< syntax)) = classifyMarkup (category info) $ case syntax of
|
toMarkup (Renderable source (info :< syntax)) = classifyMarkup (category info) $ case syntax of
|
||||||
Leaf _ -> span . string . toString $ slice (characterRange info) source
|
Leaf _ -> span . string . toString $ slice (characterRange info) source
|
||||||
_ -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (toList syntax)
|
_ -> 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
|
instance (HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (SyntaxTerm leaf fields)) where
|
||||||
toMarkup (Renderable source term) = Prologue.fst $ cata (\ t -> (toMarkup $ Renderable source t, characterRange (headF t))) term
|
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
|
instance (HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (SplitSyntaxDiff leaf fields)) where
|
||||||
toMarkup (Renderable source diff) = Prologue.fst . iter (\ t -> (toMarkup $ Renderable source t, characterRange (headF t))) $ toMarkupAndRange <$> diff
|
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
|
where toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in
|
||||||
((div ! patchAttribute patch `withCostAttribute` cost info) . toMarkup $ Renderable source (cofree term), characterRange info)
|
((div ! patchAttribute patch `withCostAttribute` cost info) . toMarkup $ Renderable source (cofree term), characterRange info)
|
||||||
|
@ -4,7 +4,7 @@ import Data.Record
|
|||||||
import Info
|
import Info
|
||||||
import Prologue
|
import Prologue
|
||||||
import Syntax
|
import Syntax
|
||||||
import Term (Term)
|
import Term (Term, TermF)
|
||||||
|
|
||||||
-- | A patch to only one side of a diff.
|
-- | A patch to only one side of a diff.
|
||||||
data SplitPatch a = SplitInsert a | SplitDelete a | SplitReplace a
|
data SplitPatch a = SplitInsert a | SplitDelete a | SplitReplace a
|
||||||
@ -17,10 +17,11 @@ getSplitTerm (SplitDelete a) = a
|
|||||||
getSplitTerm (SplitReplace a) = a
|
getSplitTerm (SplitReplace a) = a
|
||||||
|
|
||||||
-- | Get the range of a SplitDiff.
|
-- | Get the range of a SplitDiff.
|
||||||
getRange :: HasField fields Range => SplitDiff leaf (Record fields) -> Range
|
getRange :: Functor f => HasField fields Range => SplitDiff f (Record fields) -> Range
|
||||||
getRange diff = characterRange $ case runFree diff of
|
getRange diff = characterRange $ case runFree diff of
|
||||||
Free annotated -> headF annotated
|
Free annotated -> headF annotated
|
||||||
Pure patch -> extract (getSplitTerm patch)
|
Pure patch -> extract (getSplitTerm patch)
|
||||||
|
|
||||||
-- | A diff with only one side’s annotations.
|
-- | A diff with only one side’s annotations.
|
||||||
type SplitDiff leaf annotation = Free (CofreeF (Syntax leaf) annotation) (SplitPatch (Term leaf annotation))
|
type SplitDiff f annotation = Free (TermF f annotation) (SplitPatch (Term f annotation))
|
||||||
|
type SplitSyntaxDiff leaf fields = SplitDiff (Syntax leaf) (Record fields)
|
||||||
|
24
src/Term.hs
24
src/Term.hs
@ -6,36 +6,40 @@ import Prologue
|
|||||||
import Data.Align.Generic
|
import Data.Align.Generic
|
||||||
import Data.Functor.Foldable as Foldable
|
import Data.Functor.Foldable as Foldable
|
||||||
import Data.Functor.Both
|
import Data.Functor.Both
|
||||||
|
import Data.Record
|
||||||
import Data.These
|
import Data.These
|
||||||
import Syntax
|
import Syntax
|
||||||
|
|
||||||
-- | An annotated node (Syntax) in an abstract syntax tree.
|
-- | An annotated node (Syntax) in an abstract syntax tree.
|
||||||
type TermF a annotation = CofreeF (Syntax a) annotation
|
type TermF = CofreeF
|
||||||
type Term a annotation = Cofree (Syntax a) annotation
|
type Term f = Cofree f
|
||||||
|
|
||||||
type instance Base (Cofree f a) = CofreeF f a
|
type SyntaxTermF leaf fields = TermF (Syntax leaf) (Record fields)
|
||||||
instance Functor f => Foldable.Foldable (Cofree f a) where project = runCofree
|
type SyntaxTerm leaf fields = Term (Syntax leaf) (Record fields)
|
||||||
instance Functor f => Foldable.Unfoldable (Cofree f a) where embed = cofree
|
|
||||||
|
type instance Base (Term f a) = TermF f a
|
||||||
|
instance Functor f => Foldable.Foldable (Term f a) where project = runCofree
|
||||||
|
instance Functor f => Foldable.Unfoldable (Term f a) where embed = cofree
|
||||||
|
|
||||||
-- | Zip two terms by combining their annotations into a pair of annotations.
|
-- | Zip two terms by combining their annotations into a pair of annotations.
|
||||||
-- | If the structure of the two terms don't match, then Nothing will be returned.
|
-- | If the structure of the two terms don't match, then Nothing will be returned.
|
||||||
|
|
||||||
zipTerms :: (Eq a, Eq annotation) => Term a annotation -> Term a annotation -> Maybe (Term a (Both annotation))
|
zipTerms :: (Eq annotation, Traversable f, GAlign f) => Term f annotation -> Term f annotation -> Maybe (Term f (Both annotation))
|
||||||
zipTerms t1 t2 = iter go (alignCofreeWith galign (const Nothing) both (These t1 t2))
|
zipTerms t1 t2 = iter go (alignCofreeWith galign (const Nothing) both (These t1 t2))
|
||||||
where go (a :< s) = cofree . (a :<) <$> sequenceA s
|
where go (a :< s) = cofree . (a :<) <$> sequenceA s
|
||||||
|
|
||||||
-- | Return the node count of a term.
|
-- | Return the node count of a term.
|
||||||
termSize :: (Prologue.Foldable f, Functor f) => Cofree f annotation -> Int
|
termSize :: (Prologue.Foldable f, Functor f) => Term f annotation -> Int
|
||||||
termSize = cata size where
|
termSize = cata size where
|
||||||
size (_ :< syntax) = 1 + sum syntax
|
size (_ :< syntax) = 1 + sum syntax
|
||||||
|
|
||||||
-- | Aligns (zips, retaining non-overlapping portions of the structure) a pair of terms.
|
-- | Aligns (zips, retaining non-overlapping portions of the structure) a pair of terms.
|
||||||
alignCofreeWith :: Functor f
|
alignCofreeWith :: Functor f
|
||||||
=> (forall a b. f a -> f b -> Maybe (f (These a b))) -- ^ A function comparing a pair of structures, returning `Just` the combined structure if they are comparable (e.g. if they have the same constructor), and `Nothing` otherwise. The 'Data.Align.Generic.galign' function is usually what you want here.
|
=> (forall a b. f a -> f b -> Maybe (f (These a b))) -- ^ A function comparing a pair of structures, returning `Just` the combined structure if they are comparable (e.g. if they have the same constructor), and `Nothing` otherwise. The 'Data.Align.Generic.galign' function is usually what you want here.
|
||||||
-> (These (Cofree f a) (Cofree f b) -> contrasted) -- ^ A function mapping a 'These' of incomparable terms into 'Pure' values in the resulting tree.
|
-> (These (Term f a) (Term f b) -> contrasted) -- ^ A function mapping a 'These' of incomparable terms into 'Pure' values in the resulting tree.
|
||||||
-> (a -> b -> combined) -- ^ A function mapping the input terms’ annotations into annotations in the 'Free' values in the resulting tree.
|
-> (a -> b -> combined) -- ^ A function mapping the input terms’ annotations into annotations in the 'Free' values in the resulting tree.
|
||||||
-> These (Cofree f a) (Cofree f b) -- ^ The input terms.
|
-> These (Term f a) (Term f b) -- ^ The input terms.
|
||||||
-> Free (CofreeF f combined) contrasted
|
-> Free (TermF f combined) contrasted
|
||||||
alignCofreeWith compare contrast combine = go
|
alignCofreeWith compare contrast combine = go
|
||||||
where go terms = fromMaybe (pure (contrast terms)) $ case terms of
|
where go terms = fromMaybe (pure (contrast terms)) $ case terms of
|
||||||
These t1 t2 -> wrap . (combine (extract t1) (extract t2) :<) . fmap go <$> compare (unwrap t1) (unwrap t2)
|
These t1 t2 -> wrap . (combine (extract t1) (extract t2) :<) . fmap go <$> compare (unwrap t1) (unwrap t2)
|
||||||
|
@ -11,10 +11,10 @@ import Test.QuickCheck hiding (Fixed)
|
|||||||
data ArbitraryTerm leaf annotation = ArbitraryTerm { annotation :: annotation, syntax :: Syntax leaf (ArbitraryTerm leaf annotation)}
|
data ArbitraryTerm leaf annotation = ArbitraryTerm { annotation :: annotation, syntax :: Syntax leaf (ArbitraryTerm leaf annotation)}
|
||||||
deriving (Show, Eq, Generic)
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
unArbitraryTerm :: ArbitraryTerm leaf annotation -> TermF leaf annotation (ArbitraryTerm leaf annotation)
|
unArbitraryTerm :: ArbitraryTerm leaf annotation -> TermF (Syntax leaf) annotation (ArbitraryTerm leaf annotation)
|
||||||
unArbitraryTerm (ArbitraryTerm a s) = a :< s
|
unArbitraryTerm (ArbitraryTerm a s) = a :< s
|
||||||
|
|
||||||
toTerm :: ArbitraryTerm leaf annotation -> Term leaf annotation
|
toTerm :: ArbitraryTerm leaf annotation -> Term (Syntax leaf) annotation
|
||||||
toTerm = unfold unArbitraryTerm
|
toTerm = unfold unArbitraryTerm
|
||||||
|
|
||||||
termOfSize :: (Arbitrary leaf, Arbitrary annotation) => Int -> Gen (ArbitraryTerm leaf annotation)
|
termOfSize :: (Arbitrary leaf, Arbitrary annotation) => Int -> Gen (ArbitraryTerm leaf annotation)
|
||||||
@ -26,7 +26,7 @@ arbitraryTermSize = cata (succ . sum) . toTerm
|
|||||||
|
|
||||||
-- Instances
|
-- Instances
|
||||||
|
|
||||||
type instance Base (ArbitraryTerm leaf annotation) = TermF leaf annotation
|
type instance Base (ArbitraryTerm leaf annotation) = TermF (Syntax leaf) annotation
|
||||||
instance Unfoldable (ArbitraryTerm leaf annotation) where embed (a :< s) = ArbitraryTerm a s
|
instance Unfoldable (ArbitraryTerm leaf annotation) where embed (a :< s) = ArbitraryTerm a s
|
||||||
|
|
||||||
instance (Eq leaf, Eq annotation, Arbitrary leaf, Arbitrary annotation) => Arbitrary (ArbitraryTerm leaf annotation) where
|
instance (Eq leaf, Eq annotation, Arbitrary leaf, Arbitrary annotation) => Arbitrary (ArbitraryTerm leaf annotation) where
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
{-# LANGUAGE DataKinds #-}
|
{-# LANGUAGE DataKinds #-}
|
||||||
module TreeSitter where
|
module TreeSitter (treeSitterParser) where
|
||||||
|
|
||||||
import Prologue hiding (Constructor)
|
import Prologue hiding (Constructor)
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Category
|
import Category
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Language
|
import Language
|
||||||
|
import qualified Language.JavaScript as JS
|
||||||
|
import qualified Language.C as C
|
||||||
import Parser
|
import Parser
|
||||||
import Range
|
import Range
|
||||||
import Source
|
import Source
|
||||||
@ -29,97 +31,32 @@ treeSitterParser language grammar blob = do
|
|||||||
ts_document_free document
|
ts_document_free document
|
||||||
pure term)
|
pure term)
|
||||||
|
|
||||||
-- Given a language and a node name, return the correct categories.
|
|
||||||
categoriesForLanguage :: Language -> Text -> Category
|
|
||||||
categoriesForLanguage language name = case (language, name) of
|
|
||||||
(JavaScript, "object") -> Object
|
|
||||||
(JavaScript, "expression_statement") -> ExpressionStatements
|
|
||||||
(JavaScript, "this_expression") -> Identifier
|
|
||||||
(JavaScript, "null") -> Identifier
|
|
||||||
(JavaScript, "undefined") -> Identifier
|
|
||||||
(JavaScript, "arrow_function") -> Function
|
|
||||||
(JavaScript, "generator_function") -> Function
|
|
||||||
(JavaScript, "math_op") -> BinaryOperator -- bitwise operator, e.g. +, -, *, /.
|
|
||||||
(JavaScript, "bool_op") -> BinaryOperator -- boolean operator, e.g. ||, &&.
|
|
||||||
(JavaScript, "comma_op") -> CommaOperator -- comma operator, e.g. expr1, expr2.
|
|
||||||
(JavaScript, "delete_op") -> Operator -- delete operator, e.g. delete x[2].
|
|
||||||
(JavaScript, "type_op") -> Operator -- type operator, e.g. typeof Object.
|
|
||||||
(JavaScript, "void_op") -> Operator -- void operator, e.g. void 2.
|
|
||||||
(JavaScript, "for_in_statement") -> For
|
|
||||||
(JavaScript, "for_of_statement") -> For
|
|
||||||
(JavaScript, "new_expression") -> Constructor
|
|
||||||
(JavaScript, "class") -> Class
|
|
||||||
(JavaScript, "catch") -> Catch
|
|
||||||
(JavaScript, "finally") -> Finally
|
|
||||||
(JavaScript, "if_statement") -> If
|
|
||||||
(JavaScript, "empty_statement") -> Empty
|
|
||||||
|
|
||||||
(Ruby, "hash") -> Object
|
|
||||||
_ -> defaultCategoryForNodeName name
|
|
||||||
{-# INLINE categoriesForLanguage #-}
|
|
||||||
|
|
||||||
-- | Given a node name from TreeSitter, return the correct categories.
|
|
||||||
defaultCategoryForNodeName :: Text -> Category
|
|
||||||
defaultCategoryForNodeName name = case name of
|
|
||||||
"program" -> Program
|
|
||||||
"ERROR" -> Error
|
|
||||||
"function_call" -> FunctionCall
|
|
||||||
"pair" -> Pair
|
|
||||||
"string" -> StringLiteral
|
|
||||||
"integer" -> IntegerLiteral
|
|
||||||
"symbol" -> SymbolLiteral
|
|
||||||
"array" -> ArrayLiteral
|
|
||||||
"function" -> Function
|
|
||||||
"identifier" -> Identifier
|
|
||||||
"formal_parameters" -> Params
|
|
||||||
"arguments" -> Args
|
|
||||||
"statement_block" -> ExpressionStatements
|
|
||||||
"assignment" -> Assignment
|
|
||||||
"member_access" -> MemberAccess
|
|
||||||
"op" -> Operator
|
|
||||||
"subscript_access" -> SubscriptAccess
|
|
||||||
"regex" -> Regex
|
|
||||||
"template_string" -> TemplateString
|
|
||||||
"var_assignment" -> VarAssignment
|
|
||||||
"var_declaration" -> VarDecl
|
|
||||||
"switch_statement" -> Switch
|
|
||||||
"math_assignment" -> MathAssignment
|
|
||||||
"case" -> Case
|
|
||||||
"true" -> Boolean
|
|
||||||
"false" -> Boolean
|
|
||||||
"ternary" -> Ternary
|
|
||||||
"for_statement" -> For
|
|
||||||
"while_statement" -> While
|
|
||||||
"do_statement" -> DoWhile
|
|
||||||
"return_statement" -> Return
|
|
||||||
"throw_statement" -> Throw
|
|
||||||
"try_statement" -> Try
|
|
||||||
"method_definition" -> Method
|
|
||||||
"comment" -> Comment
|
|
||||||
"bitwise_op" -> BitwiseOperator
|
|
||||||
"rel_op" -> RelationalOperator
|
|
||||||
_ -> Other name
|
|
||||||
{-# INLINE defaultCategoryForNodeName #-}
|
|
||||||
|
|
||||||
-- | Return a parser for a tree sitter language & document.
|
-- | Return a parser for a tree sitter language & document.
|
||||||
documentToTerm :: Language -> Ptr Document -> Parser (Syntax.Syntax Text) (Record '[Range, Category])
|
documentToTerm :: Language -> Ptr Document -> Parser (Syntax.Syntax Text) (Record '[Range, Category])
|
||||||
documentToTerm language document blob = alloca $ \ root -> do
|
documentToTerm language document SourceBlob{..} = alloca $ \ root -> do
|
||||||
ts_document_root_node_p document root
|
ts_document_root_node_p document root
|
||||||
toTerm root
|
toTerm root
|
||||||
where toTerm node = do
|
where toTerm node = do
|
||||||
name <- ts_node_p_name node document
|
name <- ts_node_p_name node document
|
||||||
name <- peekCString name
|
name <- peekCString name
|
||||||
count <- ts_node_p_named_child_count node
|
count <- ts_node_p_named_child_count node
|
||||||
children <- traverse (alloca . getChild node) $ take (fromIntegral count) [0..]
|
children <- filter isNonEmpty <$> traverse (alloca . getChild node) (take (fromIntegral count) [0..])
|
||||||
|
|
||||||
let range = Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node }
|
let range = Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node }
|
||||||
|
|
||||||
let sourceSpan = SourceSpan { spanName = toS (path blob)
|
let sourceSpan = SourceSpan { spanName = toS path
|
||||||
, spanStart = SourcePos (fromIntegral $! ts_node_p_start_point_row node) (fromIntegral $! ts_node_p_start_point_column node)
|
, spanStart = SourcePos (fromIntegral $! ts_node_p_start_point_row node) (fromIntegral $! ts_node_p_start_point_column node)
|
||||||
, spanEnd = SourcePos (fromIntegral $! ts_node_p_end_point_row node) (fromIntegral $! ts_node_p_end_point_column node) }
|
, spanEnd = SourcePos (fromIntegral $! ts_node_p_end_point_row node) (fromIntegral $! ts_node_p_end_point_column node) }
|
||||||
|
|
||||||
-- Note: The strict application here is semantically important. Without it, we may not evaluate the range until after we’ve exited the scope that `node` was allocated within, meaning `alloca` will free it & other stack data may overwrite it.
|
-- Note: The strict application here is semantically important.
|
||||||
let info = range `seq` range .: categoriesForLanguage language (toS name) .: RNil
|
-- Without it, we may not evaluate the range until after we’ve exited
|
||||||
termConstructor (source blob) (sourceSpan `seq` pure sourceSpan) info (filter (\child -> category (extract child) /= Empty) children)
|
-- the scope that `node` was allocated within, meaning `alloca` will
|
||||||
|
-- free it & other stack data may overwrite it.
|
||||||
|
range `seq` termConstructor source (pure $! sourceSpan) (toS name) range children
|
||||||
getChild node n out = ts_node_p_named_child node n out >> toTerm out
|
getChild node n out = ts_node_p_named_child node n out >> toTerm out
|
||||||
{-# INLINE getChild #-}
|
{-# INLINE getChild #-}
|
||||||
|
termConstructor = case language of
|
||||||
|
JavaScript -> JS.termConstructor
|
||||||
|
C -> C.termConstructor
|
||||||
|
_ -> Language.termConstructor
|
||||||
|
isNonEmpty child = category (extract child) /= Empty
|
||||||
|
@ -32,7 +32,7 @@ spec :: Spec
|
|||||||
spec = parallel $ do
|
spec = parallel $ do
|
||||||
describe "alignBranch" $ do
|
describe "alignBranch" $ do
|
||||||
it "produces symmetrical context" $
|
it "produces symmetrical context" $
|
||||||
alignBranch getRange ([] :: [Join These (SplitDiff String (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
|
alignBranch getRange ([] :: [Join These (SplitDiff (Syntax String) (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
|
||||||
[ Join (These (Range 0 2, [])
|
[ Join (These (Range 0 2, [])
|
||||||
(Range 0 2, []))
|
(Range 0 2, []))
|
||||||
, Join (These (Range 2 4, [])
|
, Join (These (Range 2 4, [])
|
||||||
@ -40,7 +40,7 @@ spec = parallel $ do
|
|||||||
]
|
]
|
||||||
|
|
||||||
it "produces asymmetrical context" $
|
it "produces asymmetrical context" $
|
||||||
alignBranch getRange ([] :: [Join These (SplitDiff String (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
|
alignBranch getRange ([] :: [Join These (SplitDiff (Syntax String) (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
|
||||||
[ Join (These (Range 0 2, [])
|
[ Join (These (Range 0 2, [])
|
||||||
(Range 0 1, []))
|
(Range 0 1, []))
|
||||||
, Join (This (Range 2 4, []))
|
, Join (This (Range 2 4, []))
|
||||||
@ -231,7 +231,7 @@ toAlignBranchInputs elements = (sources, join . (`evalState` both 0 0) . travers
|
|||||||
branchElementContents (Margin contents) = contents
|
branchElementContents (Margin contents) = contents
|
||||||
|
|
||||||
keysOfAlignedChildren :: [Join These (Range, [(String, Range)])] -> [String]
|
keysOfAlignedChildren :: [Join These (Range, [(String, Range)])] -> [String]
|
||||||
keysOfAlignedChildren lines = lines >>= these identity identity (++) . runJoin . fmap (fmap Prologue.fst . Prologue.snd)
|
keysOfAlignedChildren lines = lines >>= these identity identity (<>) . runJoin . fmap (fmap Prologue.fst . Prologue.snd)
|
||||||
|
|
||||||
joinCrosswalk :: Bicrosswalk p => Align f => (a -> f b) -> Join p a -> f (Join p b)
|
joinCrosswalk :: Bicrosswalk p => Align f => (a -> f b) -> Join p a -> f (Join p b)
|
||||||
joinCrosswalk f = fmap Join . bicrosswalk f f . runJoin
|
joinCrosswalk f = fmap Join . bicrosswalk f f . runJoin
|
||||||
@ -257,13 +257,13 @@ instance Arbitrary BranchElement where
|
|||||||
counts :: [Join These (Int, a)] -> Both Int
|
counts :: [Join These (Int, a)] -> Both Int
|
||||||
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered))
|
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered))
|
||||||
|
|
||||||
align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff String (Record '[Range]))
|
align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term (Syntax String) (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff (Syntax String) (Record '[Range]))
|
||||||
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
|
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
|
||||||
|
|
||||||
info :: Int -> Int -> Record '[Range]
|
info :: Int -> Int -> Record '[Range]
|
||||||
info start end = Range start end .: RNil
|
info start end = Range start end .: RNil
|
||||||
|
|
||||||
prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff String (Record '[Range]))
|
prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term (Syntax String) (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff (Syntax String) (Record '[Range]))
|
||||||
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))
|
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))
|
||||||
|
|
||||||
data PrettyDiff a = PrettyDiff { unPrettySources :: Both (Source.Source Char), unPrettyLines :: [Join These (Range, a)] }
|
data PrettyDiff a = PrettyDiff { unPrettySources :: Both (Source.Source Char), unPrettyLines :: [Join These (Range, a)] }
|
||||||
@ -273,23 +273,23 @@ instance Show a => Show (PrettyDiff a) where
|
|||||||
showsPrec _ (PrettyDiff sources lines) = (prettyPrinted ++) -- . (("\n" ++ show lines) ++)
|
showsPrec _ (PrettyDiff sources lines) = (prettyPrinted ++) -- . (("\n" ++ show lines) ++)
|
||||||
where prettyPrinted = showLine (maximum (0 : (maximum . fmap length <$> shownLines))) <$> shownLines >>= ('\n':)
|
where prettyPrinted = showLine (maximum (0 : (maximum . fmap length <$> shownLines))) <$> shownLines >>= ('\n':)
|
||||||
shownLines = catMaybes $ toBoth <$> lines
|
shownLines = catMaybes $ toBoth <$> lines
|
||||||
showLine n line = uncurry ((++) . (++ " | ")) (fromThese (replicate n ' ') (replicate n ' ') (runJoin (pad n <$> line)))
|
showLine n line = uncurry ((<>) . (++ " | ")) (fromThese (replicate n ' ') (replicate n ' ') (runJoin (pad n <$> line)))
|
||||||
showDiff (range, _) = filter (/= '\n') . toList . Source.slice range
|
showDiff (range, _) = filter (/= '\n') . toList . Source.slice range
|
||||||
pad n string = (++) (take n string) (replicate (max 0 (n - length string)) ' ')
|
pad n string = (<>) (take n string) (replicate (max 0 (n - length string)) ' ')
|
||||||
toBoth them = showDiff <$> them `applyThese` modifyJoin (uncurry These) sources
|
toBoth them = showDiff <$> them `applyThese` modifyJoin (uncurry These) sources
|
||||||
|
|
||||||
newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct :: Free (CofreeF (Syntax String) annotation) patch }
|
newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct :: Free (CofreeF (Syntax String) annotation) patch }
|
||||||
|
|
||||||
|
|
||||||
class PatchConstructible p where
|
class PatchConstructible p where
|
||||||
insert :: Term String (Record '[Range]) -> p
|
insert :: Term (Syntax String) (Record '[Range]) -> p
|
||||||
delete :: Term String (Record '[Range]) -> p
|
delete :: Term (Syntax String) (Record '[Range]) -> p
|
||||||
|
|
||||||
instance PatchConstructible (Patch (Term String (Record '[Range]))) where
|
instance PatchConstructible (Patch (Term (Syntax String) (Record '[Range]))) where
|
||||||
insert = Insert
|
insert = Insert
|
||||||
delete = Delete
|
delete = Delete
|
||||||
|
|
||||||
instance PatchConstructible (SplitPatch (Term String (Record '[Range]))) where
|
instance PatchConstructible (SplitPatch (Term (Syntax String) (Record '[Range]))) where
|
||||||
insert = SplitInsert
|
insert = SplitInsert
|
||||||
delete = SplitDelete
|
delete = SplitDelete
|
||||||
|
|
||||||
|
@ -21,14 +21,14 @@ spec = parallel $ do
|
|||||||
let positively = succ . abs
|
let positively = succ . abs
|
||||||
describe "pqGramDecorator" $ do
|
describe "pqGramDecorator" $ do
|
||||||
prop "produces grams with stems of the specified length" $
|
prop "produces grams with stems of the specified length" $
|
||||||
\ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (toTerm term :: Term Text (Record '[Text])) `shouldSatisfy` all ((== (positively p)) . length . stem . rhead)
|
\ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (toTerm term :: Term (Syntax Text) (Record '[Text])) `shouldSatisfy` all ((== (positively p)) . length . stem . rhead)
|
||||||
|
|
||||||
prop "produces grams with bases of the specified width" $
|
prop "produces grams with bases of the specified width" $
|
||||||
\ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (toTerm term :: Term Text (Record '[Text])) `shouldSatisfy` all ((== (positively q)) . length . base . rhead)
|
\ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (toTerm term :: Term (Syntax Text) (Record '[Text])) `shouldSatisfy` all ((== (positively q)) . length . base . rhead)
|
||||||
|
|
||||||
describe "featureVectorDecorator" $ do
|
describe "featureVectorDecorator" $ do
|
||||||
prop "produces a vector of the specified dimension" $
|
prop "produces a vector of the specified dimension" $
|
||||||
\ (term, p, q, d) -> featureVectorDecorator (rhead . headF) (positively p) (positively q) (positively d) (toTerm term :: Term Text (Record '[Text])) `shouldSatisfy` all ((== (positively d)) . length . rhead)
|
\ (term, p, q, d) -> featureVectorDecorator (rhead . headF) (positively p) (positively q) (positively d) (toTerm term :: Term (Syntax Text) (Record '[Text])) `shouldSatisfy` all ((== (positively d)) . length . rhead)
|
||||||
|
|
||||||
describe "rws" $ do
|
describe "rws" $ do
|
||||||
let decorate = defaultFeatureVectorDecorator (category . headF)
|
let decorate = defaultFeatureVectorDecorator (category . headF)
|
||||||
|
@ -26,7 +26,7 @@ arrayInfo = ArrayLiteral .: Range 0 3 .: RNil
|
|||||||
literalInfo :: Record '[Category, Range]
|
literalInfo :: Record '[Category, Range]
|
||||||
literalInfo = StringLiteral .: Range 1 2 .: RNil
|
literalInfo = StringLiteral .: Range 1 2 .: RNil
|
||||||
|
|
||||||
testDiff :: Diff Text (Record '[Category, Range])
|
testDiff :: Diff (Syntax Text) (Record '[Category, Range])
|
||||||
testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ])
|
testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ])
|
||||||
|
|
||||||
testSummary :: DiffSummary DiffInfo
|
testSummary :: DiffSummary DiffInfo
|
||||||
@ -67,7 +67,7 @@ spec = parallel $ do
|
|||||||
extractLeaves (BranchInfo children _ _) = join $ extractLeaves <$> children
|
extractLeaves (BranchInfo children _ _) = join $ extractLeaves <$> children
|
||||||
extractLeaves leaf = [ leaf ]
|
extractLeaves leaf = [ leaf ]
|
||||||
|
|
||||||
extractDiffLeaves :: Term Text (Record '[Category, Range]) -> [ Term Text (Record '[Category, Range]) ]
|
extractDiffLeaves :: Term (Syntax Text) (Record '[Category, Range]) -> [ Term (Syntax Text) (Record '[Category, Range]) ]
|
||||||
extractDiffLeaves term = case unwrap term of
|
extractDiffLeaves term = case unwrap term of
|
||||||
(Indexed children) -> join $ extractDiffLeaves <$> children
|
(Indexed children) -> join $ extractDiffLeaves <$> children
|
||||||
(Fixed children) -> join $ extractDiffLeaves <$> children
|
(Fixed children) -> join $ extractDiffLeaves <$> children
|
||||||
@ -81,7 +81,7 @@ spec = parallel $ do
|
|||||||
in
|
in
|
||||||
length listOfLeaves `shouldBe` length listOfDiffLeaves
|
length listOfLeaves `shouldBe` length listOfDiffLeaves
|
||||||
|
|
||||||
isIndexedOrFixed :: Patch (Term a annotation) -> Bool
|
isIndexedOrFixed :: Patch (Term (Syntax a) annotation) -> Bool
|
||||||
isIndexedOrFixed = any (isIndexedOrFixed' . unwrap)
|
isIndexedOrFixed = any (isIndexedOrFixed' . unwrap)
|
||||||
|
|
||||||
isIndexedOrFixed' :: Syntax a f -> Bool
|
isIndexedOrFixed' :: Syntax a f -> Bool
|
||||||
|
100
weekly/2016-09-09.md
Normal file
100
weekly/2016-09-09.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# September 9th, 2016
|
||||||
|
|
||||||
|
- Hack week was last week and we skipped the weekly.
|
||||||
|
- We moved the weekly to the end of the week to try to cut down on the “wait, what _did_ happen last week?” thing.
|
||||||
|
- This ended up being @tclem’s first weekly 👋
|
||||||
|
|
||||||
|
|
||||||
|
#### What went well?
|
||||||
|
|
||||||
|
@joshvera:
|
||||||
|
|
||||||
|
Hack week:
|
||||||
|
|
||||||
|
- Made a lot of progress on the TypeScript parser.
|
||||||
|
- May be able to use that as the basis for a more rigorous JavaScript parser as well.
|
||||||
|
|
||||||
|
This week:
|
||||||
|
|
||||||
|
- Understand RWS & some other diffing algos a lot better than before.
|
||||||
|
|
||||||
|
|
||||||
|
@rewinfrey:
|
||||||
|
|
||||||
|
- Pairing w/ @tclem.
|
||||||
|
- Updating test cases is much more efficient.
|
||||||
|
- Modelling effects in Free.
|
||||||
|
|
||||||
|
|
||||||
|
@tclem:
|
||||||
|
|
||||||
|
- Little fixes.
|
||||||
|
- Static linking of ICU in dev.
|
||||||
|
- statsd client.
|
||||||
|
|
||||||
|
|
||||||
|
@robrix:
|
||||||
|
|
||||||
|
- semantic-diffd
|
||||||
|
- Docker
|
||||||
|
- kubes
|
||||||
|
- Markdown
|
||||||
|
|
||||||
|
|
||||||
|
#### What were the challenges?
|
||||||
|
|
||||||
|
@joshvera:
|
||||||
|
|
||||||
|
- Integrating a pass before RWS. Using constant-time (per-subtree)SES before RWS to match up equal things. There are some ordering problems with the result.
|
||||||
|
- Ambiguities in the TypeScript grammar. Possibly due to JS actually being context-sensitive. @maxbrunsfeld advises parsing a superset of the language… but which superset?
|
||||||
|
|
||||||
|
|
||||||
|
@rewinfrey:
|
||||||
|
|
||||||
|
- Trying to get an effect system in Free. Got it, but tricky.
|
||||||
|
- Also picked up an issue with template strings which defies debugging.
|
||||||
|
|
||||||
|
|
||||||
|
@tclem:
|
||||||
|
|
||||||
|
- > My head hurts by the end of the day.
|
||||||
|
- Being challenged by some of the new concepts, the vernacular &c.
|
||||||
|
- Hard to know when to jump down the rabbit hole and learn a thing or when to gloss over it.
|
||||||
|
- Have a queue of things to read.
|
||||||
|
|
||||||
|
|
||||||
|
@robrix:
|
||||||
|
|
||||||
|
- Converting between line/column ranges and character ranges.
|
||||||
|
|
||||||
|
|
||||||
|
#### What did you learn?
|
||||||
|
|
||||||
|
@joshvera:
|
||||||
|
|
||||||
|
- > I became one with the TypeScript grammar.
|
||||||
|
- > Maybe we should find a way to have the grammars write themselves.
|
||||||
|
- Working on this `effects` package (formerly called `freer`) & its `Eff` type. It uses an open union data structure, which `Data.Record` is sort of an approximation of. `Eff` has this detail where it’s list of function types are “type-aligned.” Every item in the list is a function type where they all chain together, a -> b, b -> c, etc. Adding, removing, & replacing effects is constant-time.
|
||||||
|
|
||||||
|
@rewinfrey:
|
||||||
|
|
||||||
|
- Free, and how to model effects in Free.
|
||||||
|
- Straight-up category theory stuff. Compositions of natural transformations &c.
|
||||||
|
|
||||||
|
@tclem:
|
||||||
|
|
||||||
|
- Post about functors, applicatives, and monads shown in pictures.
|
||||||
|
- How this manages to be a bridge between pure & stateful functions.
|
||||||
|
- Helped explain the optparse-applicative syntax.
|
||||||
|
|
||||||
|
@robrix:
|
||||||
|
|
||||||
|
- CMark exists, which is cool.
|
||||||
|
- Parsing/term ingestion had been producing errors for weirdly-formatted for loops &c. Not only were we hitting this on a regular basis, it was also causing confusing/poor change summaries.
|
||||||
|
|
||||||
|
|
||||||
|
#### Meta
|
||||||
|
|
||||||
|
“What were the challenges?” can be a bit redundant with “what did you learn?” Should we focus this on challenges that we need help with? Gonna give that a try.
|
||||||
|
|
||||||
|
Next week: @joshvera, @rewinfrey, & @robrix are off to ICFP. @tclem may or may not hold weekly solo at his discretion.
|
Loading…
Reference in New Issue
Block a user