diff --git a/demos/tree-sitter/matches.md b/demos/tree-sitter/matches.md
index 1d3e5973c..e9ed8d317 100644
--- a/demos/tree-sitter/matches.md
+++ b/demos/tree-sitter/matches.md
@@ -13,9 +13,18 @@
var vis = await ()
// editor1.value = `let a = 3 + 4`
- editor1.value = `let a = 3`
+ editor1.value = `class Test {
+ foo(i) {
+ if (i == 0) return "Foo!"
+ }
+}`
// editor2.value = `let a = 3 + 4\na++`
- editor2.value = `{let a = 2+4}`
+ editor2.value = `class Test {
+ foo(i) {
+ if (i == 0) return "Bar"
+ else if (i == -1) return "Foo!"
+ }
+}`
editor1.editor.on("change", (() => update()).debounce(500));
editor2.editor.on("change", (() => update()).debounce(500));
diff --git a/src/client/domain-code.js b/src/client/domain-code.js
index 38ffcd20f..d7c7d2391 100644
--- a/src/client/domain-code.js
+++ b/src/client/domain-code.js
@@ -12,10 +12,14 @@ MD*/
import tinycolor from 'src/external/tinycolor.js';
-import {Parser, JavaScript, visit as treeSitterVisit} from "src/client/tree-sitter.js"
+import {Parser, JavaScript, visit as treeSitterVisit, match} from "src/client/tree-sitter.js"
import {loc} from "utils"
+
+import { ChawatheScriptGenerator} from 'src/client/domain-code/chawathe-script-generator.js';
+
+
// from: https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
const cyrb53 = (str, seed = 0) => {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
@@ -162,99 +166,23 @@ export class DomainObject {
let { startIndex, oldEndIndex, newEndIndex } = edit
- // 1. detect editit history (diff oldTree -> newTree)
- // a) deleted nodes from oldTree
- // b) added nodes in new tree
+ let originalAST = rootDomainObject.treeSitter.tree
+ let originalSource = originalAST.rootNode.text
- // 2. apply diff to domain tree
+ let newAST = TreeSitterDomainObject.fromSource(originalSource)
-
- function assert(b) { if (!b) throw new Error() }
-
- // #TODO use incremental re-parse via edit()
- const newTree = TreeSitterDomainObject.astFromSource(sourceNew)
- var tsQueue = [newTree.rootNode]
- var doQueue = [rootDomainObject]
+ if(!originalAST) {throw new Error("originalAST missing")}
+ if(!newAST) {throw new Error("originalAST missing")}
- while (tsQueue.length > 0) {
- const tsNode = tsQueue.pop();
- const doNode = doQueue.pop();
- assert(tsNode.type === doNode.type);
-
- const lostChildren = []
- const missingOldChildren = [...doNode.children]
-
- // go over all new children, if we find a new child without an old child, create a new one
- // if we do find a match, update the treeSitter reference
- for (let i = 0; i < tsNode.childCount; i++) {
- const tsChild = tsNode.child(i)
- const doChild = doNode.children.find(child => tsChild.text === child.treeSitter.text)
- if (!doChild) {
- lostChildren.push([i, tsChild])
- } else {
- doChild.treeSitter = tsChild
- missingOldChildren.splice(missingOldChildren.indexOf(tsChild), 1)
- }
- }
-
- for (const [i, tsChild] of lostChildren) {
- // we didn't find the exact child but maybe we can keep some of its children, if the
- // type of the node is still the same (e.g., it's still a function)
- const candidate = missingOldChildren.find(old => old.treeSitter.type === tsChild.type)
- if (candidate) {
- tsQueue.push(tsChild)
- doQueue.push(candidate)
- missingOldChildren.splice(missingOldChildren.indexOf(candidate), 1)
- } else {
- doNode.children.splice(i, 0, TreeSitterDomainObject.fromTreeSitterAST(tsChild))
- }
- }
-
- for (const missing of missingOldChildren) {
- doNode.children.splice(doNode.children.indexOf(missing), 1)
- }
- }
-
-
- /*
- const treeSitterNodesByLocationAndHash = new Map();
- const remainingNewTreeSitterNodes = new Set()
- treeSitterVisit(newTree.rootNode, node => {
- let hash = cyrb53(node.text)
- let locAndHash = node.startIndex + "-" + node.endIndex + "-" + hash
- treeSitterNodesByLocationAndHash.set(locAndHash, node)
- remainingNewTreeSitterNodes.add(node)
- })
-
- function getTreeSitterNodesByLocationAndHash(from, to, hash) {
- return treeSitterNodesByLocationAndHash.get(from + "-" + to + "-" + hash)
- }
-
- let addedTreeSitterNodes = []
- let removedDomainObjects = []
+ debugger
+ let mappings = match(originalAST.rootNode, newAST.rootNode, 0, 100)
+ var scriptGenerator = new ChawatheScriptGenerator()
+ scriptGenerator.initWith(originalAST.rootNode, newAST.rootNode, mappings)
- rootDomainObject.visit(domainObject => {
- let adjustedStartIndex = this.adjustIndex(domainObject.treeSitter.startIndex, edit)
- let adjustedEndIndex = this.adjustIndex(domainObject.treeSitter.endIndex, edit)
- let hash = cyrb53(domainObject.treeSitter.text)
-
- var correspondingNewTreeSitterNode = getTreeSitterNodesByLocationAndHash(adjustedStartIndex, adjustedEndIndex, hash)
-
- if (correspondingNewTreeSitterNode) {
- // take new correspondingNewTreeSitterNode as your own
- remainingNewTreeSitterNodes.delete(correspondingNewTreeSitterNode)
- } else {
- removedDomainObjects.push(domainObject)
- }
- })
+ scriptGenerator.generate()
- for(let node of remainingNewTreeSitterNodes) {
- // create new domain object and put at right position
- // var newDomainObject = TreeSitterDomainObject.fromTreeSitterAST()
- }*/
-
}
printStructure() {
diff --git a/src/client/tree-sitter.js b/src/client/tree-sitter.js
index 743e672b6..74fbdba16 100644
--- a/src/client/tree-sitter.js
+++ b/src/client/tree-sitter.js
@@ -222,7 +222,7 @@ export function mapTrees(T1, T2, minHeight) {
if (existTxT2 || existTxT1) {
candidateMappings.push([t1, t2]);
} else {
- visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2))
+ visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2, {phase: "mapTrees_01"}))
}
}
}
@@ -247,7 +247,7 @@ export function mapTrees(T1, T2, minHeight) {
while (candidateMappings.length > 0) {
const [t1, t2] = candidateMappings.shift();
- visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2))
+ visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2, {phase:"mapTrees_02"}))
candidateMappings = candidateMappings.filter(pair => pair[0] !== t1);
candidateMappings = candidateMappings.filter(pair => pair[1] !== t2);
@@ -334,6 +334,7 @@ function isLeaf(node) {
function lastChanceMatch(mappings, src, dst, maxSize) {
if (s(src).size < maxSize || s(dst).size < maxSize) {
+ var debugStartTime = performance.now()
let zsMappings = zhangShashaMapping(src, dst,
function children(node) { return node.children },
function insertCost() { return 1 },
@@ -345,10 +346,14 @@ function lastChanceMatch(mappings, src, dst, maxSize) {
return 1
}
});
+ debugLastChanceCounter++
+ var debugTime = performance.now() - debugStartTime
+
for (let candidate of zsMappings) {
if (candidate.t1 && candidate.t2) {
if (!isSrcMapped(mappings, candidate.t1) && !isDstMapped(mappings, candidate.t2)) {
- addMapping(mappings, candidate.t1, candidate.t2);
+ addMapping(mappings, candidate.t1, candidate.t2,
+ {phase: "lastChanceMatch", lastChanceCounter: debugLastChanceCounter, time: debugTime});
}
}
}
@@ -362,7 +367,7 @@ export function hasMapping(mappings, t1, t2) {
return mappings.find(ea => ea.node2.id == t1.id && ea.node2.id == t2.id)
}
-export function addMapping(mappings, t1, t2) {
+export function addMapping(mappings, t1, t2, debugInfo) {
if (!t1) { throw new Error("t1 is null") }
if (!t2) { throw new Error("t2 is null") }
@@ -371,15 +376,18 @@ export function addMapping(mappings, t1, t2) {
debugger
throw new Error("mapping gone wrong?")
}
- mappings.push({ node1: t1, node2: t2 })
+ mappings.push({ node1: t1, node2: t2, debugInfo: debugInfo})
}
-function bottomUpPhase(T1, dst, mappings, minDice, maxSize) {
+var debugLastChanceCounter = 0
+function bottomUpPhase(T1, dst, mappings, minDice, maxSize) {
+ debugLastChanceCounter = 0
+
visitPostorder(T1, t => {
if (!t.parent) {
if (!isSrcMapped(mappings, t)) {
- addMapping(mappings, t, dst)
+ addMapping(mappings, t, dst, {phase: "bottomUpRoot"})
lastChanceMatch(mappings, t, dst, maxSize);
}
} else if (!isSrcMapped(mappings, t) && !isLeaf(t)) {
@@ -396,7 +404,7 @@ function bottomUpPhase(T1, dst, mappings, minDice, maxSize) {
if (best !== null) {
lastChanceMatch(mappings, t, best, maxSize);
- addMapping(mappings, t, best)
+ addMapping(mappings, t, best, {phase: "bottomUp"})
}
}
})
@@ -412,6 +420,7 @@ export function match(tree1, tree2, minHeight = 2, maxSize = 100, minDice=0.5) {
let matches = mapTrees(tree1, tree2, minHeight)
+
bottomUpPhase(tree1, tree2, matches, minDice, maxSize)
return Array.from(matches);
diff --git a/src/components/tools/treesitter-matches.js b/src/components/tools/treesitter-matches.js
index 0b74e20b7..51060c078 100644
--- a/src/components/tools/treesitter-matches.js
+++ b/src/components/tools/treesitter-matches.js
@@ -11,7 +11,7 @@ export default class TreesitterMatches extends Morph {
get livelyUpdateStrategy() { return 'inplace'; }
async update() {
- let graphviz = await ()
+ let graphviz = await ()
function renderTree(rootNode, clusterName) {
let dotEdges = []
@@ -28,11 +28,51 @@ export default class TreesitterMatches extends Morph {
}`
}
+
+ function colorForPhase(phase) {
+ var colors = {
+ mapTrees_01: "green",
+ mapTrees_02: "green",
+ lastChanceMatch: "blue",
+ bottomUp: "red",
+ bottomUpRoot: "orange"
+ }
+
+ if (!colors[phase]) {
+ debugger
+ }
+
+ return colors[phase] || "gray"
+ }
+
+ function labelFor(match) {
+ if (match.debugInfo && match.debugInfo.phase === "lastChanceMatch") {
+ return match.debugInfo.lastChanceCounter
+ }
+ return ""
+ }
+
+ function tooltipFor(match) {
+ if (!match.debugInfo) return ""
+ var s = match.debugInfo.phase
+ if (match.debugInfo.time) {
+ s += " " + match.debugInfo.time +"ms"
+ }
+ return s
+ }
+
+ function widthFor(match) {
+ if (match.debugInfo && match.debugInfo.time) {
+ return match.debugInfo.time * 1
+ }
+ return 1
+ }
+
function renderMatches(matches) {
let dotEdges = []
for(let match of matches) {
- dotEdges.push(`${match.node1.id} -> ${match.node2.id} [color=green]`)
+ dotEdges.push(`${match.node1.id} -> ${match.node2.id} [color="${match.debugInfo ? colorForPhase(match.debugInfo.phase) : "gray"}" penwidth="${widthFor(match)}" tooltip="${tooltipFor(match)}" label="${labelFor(match)}"]`)
}
return dotEdges.join(";\n")
}
@@ -63,10 +103,19 @@ export default class TreesitterMatches extends Morph {
var parser = new Parser();
parser.setLanguage(JavaScript);
- let sourceCode1 = `let a = 3 + 4`
+ let sourceCode1 = `class Test {
+ foo(i) {
+ if (i == 0) return "Foo!"
+ }
+}`
this.tree1 = parser.parse(sourceCode1);
- let sourceCode2 = `let a = 3 + 4\na++`
+ let sourceCode2 = `class Test {
+ foo(i) {
+ if (i == 0) return "Bar"
+ else if (i == -1) return "Foo!"
+ }
+}`
this.tree2 = parser.parse(sourceCode2);
this.matches = match(this.tree1.rootNode, this.tree2.rootNode)
diff --git a/test/domain-code-test.js b/test/domain-code-test.js
index 7fb96e967..412abb757 100644
--- a/test/domain-code-test.js
+++ b/test/domain-code-test.js
@@ -105,7 +105,7 @@ l`
expect(root.children[0].children[0].type).equals("assignment_expression")
})
- xit('reconciles change when removing statement at end', () => {
+ it('reconciles change when removing statement at end', () => {
let sourceOriginal = `a = 3
l`
let sourceNew = `a = 3`