Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit d3f112d

Browse files
author
Wojciech Jabłoński
committed
added AST diff
1 parent 5f84beb commit d3f112d

2 files changed

Lines changed: 341 additions & 0 deletions

File tree

uast/diff/changelist.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package diff
2+
3+
import (
4+
"gopkg.in/bblfsh/sdk.v2/uast/nodes"
5+
)
6+
7+
type Changelist []Change
8+
9+
type Change interface {
10+
isChange()
11+
}
12+
13+
type changeBase struct {
14+
txID uint64
15+
}
16+
17+
func (_ *changeBase) isChange() {}
18+
19+
// TODO: proper ID of a node somehow
20+
type ID interface{}
21+
22+
// key in a node, string for nodes.Object and int for nodes.Array
23+
type Key interface{ isKey() }
24+
25+
type String string
26+
type Int int
27+
28+
func (_ Int) isKey() {}
29+
func (_ String) isKey() {}
30+
31+
// four change types
32+
33+
// create a node
34+
type Create struct {
35+
changeBase
36+
node nodes.Node
37+
}
38+
39+
// delete a node by ID
40+
type Delete struct {
41+
changeBase
42+
nodeID ID
43+
}
44+
45+
// attach a node as a child of another node with a given key
46+
type Attach struct {
47+
changeBase
48+
parent ID
49+
key Key
50+
child ID
51+
}
52+
53+
// deattach a child from a node
54+
type Deattach struct {
55+
changeBase
56+
parent ID
57+
key Key //or string, how to do alternative?
58+
}

uast/diff/diff.go

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package diff
2+
3+
import (
4+
// TODO: integrate with https://github.com/src-d/lapjv if perf is unacceptable
5+
"fmt"
6+
"github.com/heetch/lapjv"
7+
"gopkg.in/bblfsh/sdk.v2/uast/nodes"
8+
)
9+
10+
type decisionType interface {
11+
isDecisionType()
12+
}
13+
14+
type basicDecisionType struct{}
15+
16+
func (_ basicDecisionType) isDecisionType() {}
17+
18+
type decision struct {
19+
cost int
20+
decision decisionType
21+
}
22+
23+
// match decision types together with their params
24+
type sameDecision struct{ basicDecisionType }
25+
type replaceDecision struct{ basicDecisionType }
26+
type matchDecision struct{ basicDecisionType }
27+
type permuteDecision struct {
28+
basicDecisionType
29+
permutation []int
30+
}
31+
32+
//convinience method for choosing the cheapest decision
33+
func (self *decision) minEq(candidate *decision) {
34+
if _, ok := candidate.decision.(replaceDecision); ok || self.cost > candidate.cost {
35+
self.cost, self.decision = candidate.cost, candidate.decision
36+
}
37+
}
38+
39+
//type for cache
40+
type keyType struct{ k1, k2 ID }
41+
42+
//cache for diff computation
43+
type cacheStorage struct {
44+
decisions map[keyType]decision
45+
sizes map[ID]int
46+
changes Changelist
47+
}
48+
49+
func emptyCacheStorage() cacheStorage {
50+
return cacheStorage{
51+
make(map[keyType]decision),
52+
make(map[ID]int),
53+
make([]Change, 0),
54+
}
55+
}
56+
57+
// caches (for perf) tree size (node count)
58+
func (ds *cacheStorage) nodeSize(n nodes.Node) int {
59+
label := nodes.UniqueKey(n)
60+
if cnt, ok := ds.sizes[label]; ok {
61+
return cnt
62+
}
63+
ret := nodes.Count(n, nodes.KindsNotNil)
64+
ds.sizes[label] = ret
65+
return ret
66+
}
67+
68+
// find the cheapest way to naively match src and dst and return the action with its combined cost
69+
func (ds *cacheStorage) decideAction(src, dst nodes.Node) decision {
70+
label := keyType{nodes.UniqueKey(src), nodes.UniqueKey(dst)}
71+
72+
if val, ok := ds.decisions[label]; ok {
73+
return val
74+
}
75+
76+
// one can always just create the dst ignoring the src
77+
cost := ds.nodeSize(dst) + 1
78+
bestDecision := decision{cost, replaceDecision{}}
79+
80+
if nodes.KindOf(src) != nodes.KindOf(dst) {
81+
ds.decisions[label] = bestDecision
82+
return bestDecision
83+
}
84+
85+
switch src.(type) {
86+
87+
case nodes.Value:
88+
src, dst := src.(nodes.Value), dst.(nodes.Value)
89+
if src == dst {
90+
bestDecision.minEq(&decision{0, sameDecision{}})
91+
} else {
92+
bestDecision.minEq(&decision{1, replaceDecision{}})
93+
}
94+
95+
case nodes.Object:
96+
src, dst := src.(nodes.Object), dst.(nodes.Object)
97+
cost = 0
98+
99+
keys := make(map[string]bool)
100+
iterate := func(keyset nodes.Object) {
101+
for key := range keyset {
102+
if in := keys[key]; !in {
103+
keys[key] = true
104+
cost += ds.decideAction(src[key], dst[key]).cost
105+
}
106+
}
107+
}
108+
iterate(src)
109+
iterate(dst)
110+
111+
if cost == 0 {
112+
bestDecision.minEq(&decision{cost, sameDecision{}})
113+
} else {
114+
bestDecision.minEq(&decision{cost, matchDecision{}})
115+
}
116+
117+
case nodes.Array:
118+
src, dst := src.(nodes.Array), dst.(nodes.Array)
119+
cost = 0
120+
if len(src) == len(dst) && func() int {
121+
sum := 0
122+
for i := range src {
123+
sum += ds.decideAction(src[i], dst[i]).cost
124+
}
125+
return sum
126+
}() == 0 {
127+
bestDecision.minEq(&decision{cost, sameDecision{}})
128+
break
129+
}
130+
131+
if len(src) != len(dst) {
132+
cost = 2
133+
}
134+
135+
if len(src) < len(dst) {
136+
l := len(dst) - len(src)
137+
for i := 0; i < l; i++ {
138+
src = append(src, nil)
139+
}
140+
} else {
141+
l := len(src) - len(dst)
142+
for i := 0; i < l; i++ {
143+
dst = append(dst, nil)
144+
}
145+
}
146+
n := len(src)
147+
m := make([][]int, n)
148+
for i := 0; i < n; i++ {
149+
m[i] = make([]int, n)
150+
for j := 0; j < n; j++ {
151+
m[i][j] = ds.decideAction(src[i], dst[j]).cost
152+
}
153+
}
154+
155+
res := lapjv.Lapjv(m)
156+
157+
for i1, i2 := range res.InRow {
158+
if i1 != i2 {
159+
cost = 2
160+
break
161+
}
162+
}
163+
164+
cost += res.Cost
165+
166+
bestDecision.minEq(&decision{cost, permuteDecision{permutation: res.InRow}})
167+
168+
case nil:
169+
bestDecision.minEq(&decision{0, sameDecision{}})
170+
171+
default:
172+
panic(fmt.Sprintf("unknown node type %T", src))
173+
}
174+
175+
ds.decisions[label] = bestDecision
176+
return ds.decisions[label]
177+
}
178+
179+
func (ds *cacheStorage) push(change Change) {
180+
ds.changes = append(ds.changes, change)
181+
}
182+
183+
func (ds *cacheStorage) createRec(node nodes.Node) {
184+
switch n := node.(type) {
185+
case nodes.Object:
186+
for _, v := range n {
187+
ds.createRec(v)
188+
}
189+
190+
case nodes.Array:
191+
for _, v := range n {
192+
ds.createRec(v)
193+
}
194+
195+
default:
196+
//values and nils are not saved separately
197+
return
198+
}
199+
ds.push(&Create{node: node})
200+
}
201+
202+
func (ds *cacheStorage) generateDifference(src, dst nodes.Node, parentID ID, parentKey Key) {
203+
decision := ds.decideAction(src, dst)
204+
switch d := decision.decision.(type) {
205+
206+
//no action required if same
207+
case sameDecision:
208+
209+
case replaceDecision:
210+
//remove src (no action?) and create dst + attach it
211+
if dst != nil {
212+
ds.createRec(dst)
213+
ds.push(&Attach{parent: parentID, key: parentKey, child: nodes.UniqueKey(dst)})
214+
} else {
215+
ds.push(&Attach{parent: parentID, key: parentKey, child: nil})
216+
}
217+
218+
case matchDecision:
219+
src, dst := src.(nodes.Object), dst.(nodes.Object)
220+
keys := make(map[string]bool)
221+
iterate := func(keyset nodes.Object) {
222+
for key := range keyset {
223+
if in := keys[key]; !in {
224+
keys[key] = true
225+
if _, ok := dst[key]; !ok {
226+
ds.push(&Deattach{parent: nodes.UniqueKey(src), key: String(key)})
227+
} else {
228+
ds.generateDifference(src[key], dst[key], nodes.UniqueKey(src), String(key))
229+
}
230+
}
231+
}
232+
}
233+
iterate(src)
234+
iterate(dst)
235+
236+
case permuteDecision:
237+
src, dst := src.(nodes.Array), dst.(nodes.Array)
238+
l := len(dst) - len(src)
239+
//add possible nils to src
240+
if l > 0 {
241+
for i := 0; i < l; i++ {
242+
src = append(src, nil)
243+
}
244+
}
245+
recreate := false
246+
if l != 0 {
247+
recreate = true
248+
}
249+
for i1, i2 := range d.permutation {
250+
if i1 != i2 {
251+
recreate = true
252+
break
253+
}
254+
}
255+
if recreate {
256+
//recreate src with right perm
257+
newsrc := make([]nodes.Node, 0, len(dst))
258+
for i := 0; i < len(dst); i++ {
259+
newsrc = append(newsrc, src[d.permutation[i]])
260+
}
261+
src = newsrc
262+
ds.push(&Create{node: src}) // TODO: not create, only mutate...
263+
ds.push(&Attach{parent: parentID, key: parentKey, child: nodes.UniqueKey(src)})
264+
}
265+
for i := 0; i < len(dst); i++ {
266+
ds.generateDifference(src[i], dst[i], nodes.UniqueKey(src), Int(i))
267+
}
268+
269+
default:
270+
panic(fmt.Sprintf("unknown decision %v", d))
271+
}
272+
}
273+
274+
func Cost(src, dst nodes.Node) int {
275+
ds := emptyCacheStorage()
276+
return ds.decideAction(src, dst).cost
277+
}
278+
279+
func Changes(src, dst nodes.Node) Changelist {
280+
ds := emptyCacheStorage()
281+
ds.generateDifference(src, dst, nil, Int(0))
282+
return ds.changes
283+
}

0 commit comments

Comments
 (0)