Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Package configuration
PROJECT := bblfsh-sdk
DEPENDENCIES := \
github.com/heetch/lapjv \
github.com/jteeuwen/go-bindata \
golang.org/x/tools/cmd/cover

Expand Down
71 changes: 71 additions & 0 deletions uast/diff/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package diff

import (
"fmt"

"gopkg.in/bblfsh/sdk.v2/uast/nodes"
)

// Apply is a method that takes a tree (nodes.Node) and applies the current changelist to that
// tree.
func (changelist Changelist) Apply(root nodes.Node) nodes.Node {
nodeDict := make(map[ID]nodes.Node)
nodes.WalkPreOrder(root, func(node nodes.Node) bool {
nodeDict[nodes.UniqueKey(node)] = node
return true
})

for _, change := range changelist {
switch ch := change.(type) {
case Create:
// create a node and add to the dictionary
nodeDict[nodes.UniqueKey(ch.Node)] = ch.Node

case Attach:
// get src and chld from the dictionary, attach (modify src)
parent, ok := nodeDict[ch.Parent]
if !ok {
panic("invalid attachment point")
}
child, ok := nodeDict[ch.Child]
if !ok {
child, ok = ch.Child.(nodes.Value)
if !ok {
panic(fmt.Errorf("unknown type of a child: %v (type %T)", ch.Child, ch.Child))
}
}

switch key := ch.Key.(type) {

case String:
parent := parent.(nodes.Object)
parent[string(key)] = child

case Int:
parent := parent.(nodes.Array)
parent[int(key)] = child
}

case Deatach:
// get the src from the dictionary, deatach (modify src)
parent := nodeDict[ch.Parent]

switch key := ch.Key.(type) {

case String:
parent := parent.(nodes.Object)
delete(parent, string(key))

case Int:
panic(fmt.Errorf("cannot deatach from an Array"))
}

case Delete:
panic(fmt.Errorf("delete is not supported in a Changelist"))

default:
panic(fmt.Sprintf("unknown change %v of type %T", change, change))
}
}
return root
}
67 changes: 67 additions & 0 deletions uast/diff/changelist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package diff

import (
"gopkg.in/bblfsh/sdk.v2/uast/nodes"
)

// Changelist is a list of changes, a result of tree difference. Applying all changes from a
// changelist on a source tree will result in it being transformed into the destination tree.
type Changelist []Change

// Change is a single operation performed
type Change interface {
isChange()
TransactionID() uint64
}

type changeBase struct {
txID uint64
}

func (changeBase) isChange() {}
func (ch changeBase) TransactionID() uint64 { return ch.txID }

// ID is a type representing node unique ID that can be compared in O(1)
type ID nodes.Comparable

// Key in a node, string for nodes.Object and int for nodes.Array
type Key interface{ isKey() }

// String is a wrapped string type for the Key interface.
type String string

// Int is a wrapped int type for the Key interface.
type Int int

func (Int) isKey() {}
func (String) isKey() {}

// four change types

// Create a node. Each array and object is created separately.
type Create struct {
changeBase
Node nodes.Node
}

// Delete a node by ID
type Delete struct {
changeBase
NodeID ID
}

// Attach a node as a child of another node with a given key
type Attach struct {
changeBase
Parent ID
Key Key
Child ID
}

// Deatach a child from a node
type Deatach struct {
changeBase
Parent ID
Key Key // Currently deatach semantics are only defined for nodes.Object so the Key is
// practically always a string
}
48 changes: 48 additions & 0 deletions uast/diff/changelist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package diff

import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/bblfsh/sdk.v2/uast/nodes"
uastyml "gopkg.in/bblfsh/sdk.v2/uast/yaml"
)

const dataDir = "./testdata"

func readUAST(t testing.TB, path string) nodes.Node {
data, err := ioutil.ReadFile(path)
require.NoError(t, err)
nd, err := uastyml.Unmarshal(data)
require.NoError(t, err)
return nd
}

func TestChangelist(t *testing.T) {
dir, err := os.Open(dataDir)
require.NoError(t, err)
defer dir.Close()
names, err := dir.Readdirnames(-1)
require.NoError(t, err)

for _, fname := range names {
if strings.HasSuffix(fname, "_src.uast") {
name := fname[:len(fname)-len("_src.uast")]

t.Run(name, func(t *testing.T) {
srcName := filepath.Join(dataDir, name+"_src.uast")
dstName := filepath.Join(dataDir, name+"_dst.uast")
src := readUAST(t, srcName)
dst := readUAST(t, dstName)

changes := Changes(src, dst)
newsrc := changes.Apply(src)
require.True(t, nodes.Equal(newsrc, dst))
})
}
}
}
Loading