1553 строки
62 KiB
TypeScript
1553 строки
62 KiB
TypeScript
///<reference path='refs.ts'/>
|
|
|
|
module TDev.AST {
|
|
export module Merge {
|
|
//var mergeLog = Util.log;
|
|
var mergeLog = (x) => {return};
|
|
var getTime = () => Util.perfNow();
|
|
var seqTime = [];
|
|
var sccTime = 0;
|
|
var treeTime = [];
|
|
var imTime = 0;
|
|
var oldSeqMerge = false;
|
|
var numChecks = 0;
|
|
|
|
export var badAstMsg = "malformed ASTs";
|
|
|
|
function enc(s : string) : string {
|
|
return "!"+s
|
|
}
|
|
|
|
function dec(s : string) : string {
|
|
return s.substr(1)
|
|
}
|
|
|
|
function getStableName(x : Stmt) : string {
|
|
if(x instanceof App) {
|
|
return "***"; // TODO XXX - is this an okay globally-unique symbol for the app ID?
|
|
} else {
|
|
return enc(x.getStableName());
|
|
}
|
|
}
|
|
|
|
function setStableName(x : Stmt, name : string) {
|
|
x.setStableName(dec(name));
|
|
}
|
|
|
|
function containsId(
|
|
table : { [s:string]:HashNode},
|
|
id : string
|
|
) : boolean {
|
|
if(table[id]) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function containsArrow(
|
|
table : { [s:string]:HashNode},
|
|
id1 : string,
|
|
id2 : string
|
|
) : boolean {
|
|
var a = table[id1];
|
|
|
|
if(oldSeqMerge) {
|
|
if(a && a.successors.indexOf(id2) >= 0) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
var b = table[id2];
|
|
if(a && b && a.order < b.order) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getNodeHash(sh : HashNode[]) : {[s:string]:HashNode} {
|
|
var result : {[s:string]:HashNode} = {};
|
|
sh.forEach(x => {
|
|
result[x.name] = x;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function getChildren(s : HashNode) : HashNode[] {
|
|
if(!s) return[];
|
|
|
|
var stmts : Stmt[] = <Stmt[]>(s.stmt.children().filter(x => x instanceof Stmt));
|
|
|
|
if (s.stmt instanceof If &&
|
|
stmts.length == 2 &&
|
|
stmts[1] instanceof CodeBlock &&
|
|
(<CodeBlock>stmts[1]).isBlockPlaceholder())
|
|
stmts.pop()
|
|
|
|
|
|
//if(s.stmt instanceof Block) {
|
|
return stmts.map(function(x : Stmt, i : number) {
|
|
return new HashNode(s.name, getStableName(x), [], x);
|
|
});
|
|
/*} else {
|
|
return stmts.map(function(x : Stmt, i : number) {
|
|
return new HashNode(s.name, s.name+"_"+i, [], x);
|
|
});
|
|
}*/
|
|
}
|
|
|
|
export class HashNode {
|
|
public parent : string;
|
|
public name : string;
|
|
public successors : string[];
|
|
public stmt : Stmt;
|
|
public order : number;
|
|
public used : boolean;
|
|
|
|
constructor(parent : string, name : string, successors : string[], stmt : Stmt) {
|
|
this.parent = parent;
|
|
this.name = name;
|
|
this.successors = successors;
|
|
this.stmt = stmt;
|
|
}
|
|
|
|
public toString() : string {
|
|
return ""+this.name+":("+this.parent+"):["+/*this.successors.join(",")+*/"]"
|
|
}
|
|
}
|
|
|
|
function getStmt(table : { [s:string]:HashNode}, key : string) : Stmt {
|
|
if(table[key]) {
|
|
return table[key].stmt;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function getIds(sl : Stmt[], allIds : string[]) : {[s:string]:HashNode} {
|
|
var nodeIndex = 0;
|
|
// returns the flattened list
|
|
function flatten(
|
|
sl : HashNode[],
|
|
table : { [s:string]:HashNode},
|
|
arr : string[]
|
|
) : string[] {
|
|
return sl.reduce(
|
|
function(acc2 : string[], x : HashNode, i : number) {
|
|
//mergeLog("getIds: "+x.name+" => "+x.toString());
|
|
x.order = nodeIndex++;
|
|
table[x.name] = null;
|
|
var theParent : string = undefined;
|
|
|
|
theParent = x.parent;
|
|
|
|
table[x.name] = x;
|
|
allIds.push(x.name);
|
|
|
|
acc2.push(x.name);
|
|
return flatten(getChildren(x), table, acc2);
|
|
},
|
|
arr
|
|
);
|
|
}
|
|
|
|
var result : { [s:string]:HashNode } = {};
|
|
var flat = flatten(sl.map((x : Stmt) => new HashNode(undefined, getStableName(x), [], x)), result, []);
|
|
flat.forEach(function(x,i) {
|
|
result[x].successors = flat.slice(i+1,flat.length)
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function transitiveClosure(
|
|
succMap : { [s : string] : {[t:string] : number } }
|
|
) {
|
|
var keys = Object.keys(succMap);
|
|
mergeLog(">>> transitive closure: "+keys.length)
|
|
keys.forEach(k => {
|
|
keys.forEach(i => {
|
|
keys.forEach (j => {
|
|
succMap[i][j] = succMap[i][j] ||
|
|
(succMap[i][k] && succMap[k][j]);
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
export function stronglyConnectedComponents(
|
|
succMap : { [s : string] : {[t:string] : number } },
|
|
keys : string[],
|
|
resultAssignment : { [s : string] : number },
|
|
debug = false
|
|
) : string[][] {
|
|
//if(debug) console.log("SCC: "+keys.length);
|
|
var index = 0;
|
|
var stack = [];
|
|
var stackVals = {};
|
|
var indices = {};
|
|
var lowlink = {};
|
|
var result : string[][] = [];
|
|
var currentComponent : string[] = [];
|
|
|
|
keys.forEach(v => {
|
|
if(!indices[v]) strongConnect(v);
|
|
});
|
|
|
|
function strongConnect(v : string, depth = 0) {
|
|
//mergeLog("strongConnect("+v+"): "+depth);
|
|
//if(debug) numChecks++;
|
|
indices[v] = index;
|
|
lowlink[v] = index;
|
|
index++;
|
|
stack.push(v);
|
|
stackVals[v] = true;
|
|
|
|
// for each edge (v,w) ...
|
|
var temp = succMap[v];
|
|
if(temp) {
|
|
Object.keys(temp).forEach(w => {
|
|
//var check = temp[w];
|
|
//Util.assert(check > 0);
|
|
//if(check) {
|
|
// do the following:
|
|
if(indices[w] == undefined) {
|
|
strongConnect(w, depth+1);
|
|
lowlink[v] = Math.min(lowlink[v], lowlink[w]);
|
|
} else {
|
|
//var sccStart = getTime();
|
|
//var ind = stack.indexOf(w);
|
|
var ind = stackVals[w]
|
|
//var sccEnd = getTime();
|
|
//if(debug) sccTime += (sccEnd-sccStart);
|
|
if(ind >= 0) {
|
|
lowlink[v] = Math.min(lowlink[v], indices[w]);
|
|
}
|
|
}
|
|
//}
|
|
});
|
|
}
|
|
|
|
if(lowlink[v] == indices[v]) {
|
|
currentComponent = [];
|
|
do {
|
|
var w = stack.pop();
|
|
delete stackVals[w];
|
|
currentComponent.push(w);
|
|
} while(w != v);
|
|
result.push(currentComponent);
|
|
}
|
|
}
|
|
|
|
result.forEach((comp,index) => {
|
|
comp.forEach(v => {
|
|
resultAssignment[v] = index;
|
|
});
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function initMatrix(
|
|
succMap : { [s : string] : {[t:string] : number } },
|
|
keys : string[],
|
|
init : number = 0
|
|
) {
|
|
keys.forEach(i => {
|
|
succMap[i] = {};
|
|
/*keys.forEach(j => {
|
|
succMap[i][j] = init;
|
|
});*/
|
|
});
|
|
}
|
|
|
|
function printMatrix(m : { [s : string] : {[t:string] : number } }) {
|
|
var str = "";
|
|
Object.keys(m).forEach(j => {
|
|
str += ", "+j;
|
|
});
|
|
mergeLog(str);
|
|
|
|
Object.keys(m).forEach(i => {
|
|
str = "";
|
|
Object.keys(m).forEach(j => {
|
|
str += ", "+m[i][j];
|
|
});
|
|
mergeLog(i+str);
|
|
})
|
|
}
|
|
|
|
function tokensToStr(a:Token[]) : string {
|
|
return a.map((x:Token) => x.getText()).join(" ");
|
|
}
|
|
|
|
function strToTokens(s:string) : Token[] {
|
|
return s.split(/\s+/).map((y:string) => {var t = new Literal(); t.data = y; return t});
|
|
}
|
|
|
|
export function merge3(o:Stmt, a:Stmt, b:Stmt, datacollector?:(IMergeData)=>void) : Stmt {
|
|
if(datacollector) {
|
|
sccTime = 0;
|
|
imTime = 0;
|
|
seqTime = [0,0,0,0,0,0,0,0,0,0];
|
|
treeTime = [0,0,0,0,0,0,0];
|
|
numChecks = 0;
|
|
}
|
|
|
|
/*if(!noprint) {
|
|
myO = o;
|
|
myA = a;
|
|
myB = b;
|
|
}*/
|
|
|
|
var start = getTime();
|
|
mergeLog(">>> Merge: "+getTime()+" begin merge3");
|
|
|
|
mergeLog(">>> Merge: "+getTime()+" get IDs");
|
|
|
|
var lo = [];
|
|
var la = [];
|
|
var lb = [];
|
|
var hashO = getIds([o], lo);
|
|
var hashA = getIds([a], la);
|
|
var hashB = getIds([b], lb);
|
|
|
|
function combineTokens(tl:Token[]) : Token[] {
|
|
return TDev.AST.ExprParser.parse0(tl).map(
|
|
(x:StackOp) => {
|
|
if(x.expr) {
|
|
return <Token>(x.expr);
|
|
} else {
|
|
return TDev.AST.mkOp(x.op);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
function merge3tokens(eOt:Token[], eAt:Token[], eBt:Token[], combine) : Token[] {
|
|
//mergeLog(">>> merge3tokens: "+tokensToStr(eOt)+"; "+tokensToStr(eAt)+"; "+tokensToStr(eBt));
|
|
var i = 0;
|
|
//var com = combine ? ((x:Token[]) => combineTokens(x)) : ((x:Token[]) => x);
|
|
var com = ((x:Token[]) => x);
|
|
var eO = new ExprHolder(); eO.tokens = com(eOt);
|
|
var eA = new ExprHolder(); eA.tokens = com(eAt);
|
|
var eB = new ExprHolder(); eB.tokens = com(eBt);
|
|
var tmap : { [s : string] : Token } = {};
|
|
var index = 0; // unique id for tokens
|
|
// the following function compares ThingRef tokens by Decl ID,
|
|
// and otherwise uses the default token-distance function
|
|
var f = (tokenDist) => {
|
|
return (a:Token, b:Token) => {
|
|
if((!a) || (!b)) {
|
|
return tokenDist(a,b);
|
|
// TODO XXX - something is wrong with the following
|
|
} /*else if((a instanceof ThingRef) && (b instanceof ThingRef)) {
|
|
console.log(">>> comparing: "+a+", "+b+" -> ");
|
|
console.log(">>> "+(<ThingRef>a).def.getStableName());
|
|
console.log(">>> "+(<ThingRef>b).def.getStableName());
|
|
if((<ThingRef>a).def.getStableName() == (<ThingRef>b).def.getStableName()) {
|
|
return 0;
|
|
} else {
|
|
return 3;
|
|
}
|
|
}*/ else {
|
|
return tokenDist(a,b);
|
|
}
|
|
};
|
|
};
|
|
TDev.AST.Diff.diffExprs(eO, eA, {}, f);
|
|
TDev.AST.Diff.diffExprs(eO, eB, {}, f);
|
|
var da = eA.diffTokens;
|
|
var db = eB.diffTokens;
|
|
// compute IDs for the base tokens
|
|
var arrO = [];
|
|
for(var i = 0; i < da.length; i+=2) {
|
|
if(da[i]) {
|
|
var name = ""+index;
|
|
tmap[name] = da[i];
|
|
index++;
|
|
arrO.push(name);
|
|
}
|
|
}
|
|
// compute IDs for the A tokens
|
|
var arrA = [];
|
|
var j = 0; // keeps track of position in base
|
|
for(i = 1; i < da.length; i+=2) {
|
|
if(da[i]) {
|
|
if(da[i-1]) {
|
|
arrA.push(""+j);
|
|
} else {
|
|
var name = ""+index;
|
|
tmap[""+index] = da[i];
|
|
index++;
|
|
arrA.push(name);
|
|
}
|
|
}
|
|
|
|
if(da[i-1]) j++;
|
|
}
|
|
// compute IDs for the B tokens
|
|
var arrB = [];
|
|
j = 0; // keeps track of position in base
|
|
for(i = 1; i < db.length; i+=2) {
|
|
if(db[i]) {
|
|
if(db[i-1]) {
|
|
arrB.push(""+j);
|
|
} else {
|
|
var name = ""+index;
|
|
tmap[""+index] = db[i];
|
|
index++;
|
|
arrB.push(name);
|
|
}
|
|
}
|
|
|
|
if(db[i-1]) j++;
|
|
}
|
|
//mergeLog(">>>> O=["+arrO.join(",")+"], A=["+arrA.join(",")+"], B=["+arrB.join(",")+"]");
|
|
|
|
function mapper(x:string[]) : App {
|
|
var v = new App(null); // TODO - use Block
|
|
v.things = x.map((y:string)=>{
|
|
var z = new Decl();
|
|
z.setStableName(y);
|
|
return z;
|
|
});
|
|
return v;
|
|
}
|
|
|
|
var result : App = <App>merge3(mapper(arrO),mapper(arrA),mapper(arrB));
|
|
|
|
return result.things.map((x:Decl)=>tmap[x.getStableName()]);
|
|
}
|
|
|
|
var maxDepth = 0;
|
|
|
|
function merge3node(
|
|
oN : HashNode,
|
|
aN : HashNode,
|
|
bN : HashNode,
|
|
cmap : { [s : string] : string[] },
|
|
depth = 0
|
|
) : Stmt {
|
|
// NOTE - oN,aN,bN must agree on the name (stableName)
|
|
maxDepth = Math.max(depth, maxDepth);
|
|
var id;
|
|
if(oN) {
|
|
id = oN.name;
|
|
} else if(aN) {
|
|
id = aN.name;
|
|
} else if(bN) {
|
|
id = bN.name;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
//mergeLog("merge3node("+id+"): "+maxDepth);
|
|
|
|
// "id" should be defined by now
|
|
var childs = cmap[id];
|
|
if(!childs) {
|
|
childs = [];
|
|
}
|
|
|
|
var newChilds = [];
|
|
//if(!id) {
|
|
// mergeLog("BAD!"); // TODO - get rid of
|
|
//} else {
|
|
newChilds = childs.map(function(x : string) {
|
|
//mergeLog(" trying: "+x+" : "+[hashO,hashA,hashB].map(y=>y[x]).join(", "));
|
|
return merge3node(hashO[x], hashA[x], hashB[x], cmap, depth+1);
|
|
});
|
|
//}
|
|
|
|
function getNewChild(index : number) : Stmt {
|
|
var rc = newChilds[index];
|
|
if(rc) {
|
|
return rc;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function merge3inputs(f, combine) {
|
|
var oTok = []; if(oN) oTok = f(oN.stmt);
|
|
var aTok = []; if(aN) aTok = f(aN.stmt);
|
|
var bTok = []; if(bN) bTok = f(bN.stmt);
|
|
|
|
return merge3tokens(oTok, aTok, bTok, combine);
|
|
}
|
|
|
|
|
|
/*
|
|
(Stmt)
|
|
-RecordField
|
|
-ResolveClause
|
|
Binding
|
|
-ActionBinding
|
|
-KindBinding
|
|
|
|
*(Comment)
|
|
(Block)
|
|
-(CodeBlock)
|
|
-(ConditionBlock)
|
|
-(ParameterBlock)
|
|
-(BindingBlock)
|
|
-(ResolveBlock)
|
|
-(FieldBlock)
|
|
-(InlineActionBlock)
|
|
*(For)
|
|
*(Foreach)
|
|
*(While)
|
|
*(If)
|
|
*(Box)
|
|
*(ExprStmt)
|
|
*(InlineActions)
|
|
*(InlineAction)
|
|
(ForeachClause)
|
|
*(Where)
|
|
-(ActionParameter)
|
|
-(ActionHeader)
|
|
(Decl)
|
|
(PropertyDecl)
|
|
*(GlobalDef)
|
|
*(Action)
|
|
*(RecordDef)
|
|
*(LibraryRef)
|
|
(SingletonDef)
|
|
(PlaceholderDef)
|
|
-(LocalDef)
|
|
-(App)
|
|
|
|
|
|
*/
|
|
|
|
// construct a new node containing newChilds
|
|
function test(x) {
|
|
var t1 = !oN || (oN.stmt instanceof x);
|
|
var t2 = !aN || (aN.stmt instanceof x);
|
|
var t3 = !bN || (bN.stmt instanceof x);
|
|
return t1 && t2 && t3;
|
|
}
|
|
|
|
function getChange(f) {
|
|
if(!aN && !bN) {
|
|
return undefined; // TODO - this should never happen?
|
|
} else if(!aN) {
|
|
return f(bN.stmt);
|
|
} else if(!bN) {
|
|
return f(aN.stmt);
|
|
} else if(!oN) {
|
|
return f(aN.stmt);
|
|
}
|
|
|
|
// at this point, oN.stmt,aN.stmt,bN.stmt are all defined
|
|
if(f(oN.stmt) == f(aN.stmt)) {
|
|
return f(bN.stmt);
|
|
} else {
|
|
return f(aN.stmt);
|
|
}
|
|
}
|
|
|
|
function mergeExprHolder(f) {
|
|
var temp = new ExprHolder();
|
|
temp.tokens = merge3inputs(x => f(x).tokens, true);
|
|
return temp
|
|
}
|
|
|
|
var result : Stmt = null;
|
|
|
|
var newName = undefined;
|
|
|
|
if(test(RecordField)) {
|
|
var dataKind = getChange(x => (<RecordField>x).dataKind);
|
|
var isKey = getChange(x => (<RecordField>x).isKey);
|
|
var nm = getChange(x => (<RecordField>x).getName());
|
|
result = new RecordField(nm, dataKind, isKey); // TODO - correct args?
|
|
} else if(test(ResolveClause)) {
|
|
var nm = getChange(x => (<ResolveClause>x).name);
|
|
result = new ResolveClause(nm);
|
|
(<ResolveClause>result).kindBindings = <BindingBlock>getNewChild(0); // TODO - check cast?
|
|
(<ResolveClause>result).actionBindings = <BindingBlock>getNewChild(1);
|
|
(<ResolveClause>result).defaultLib = getChange(x => (<ResolveClause>x).defaultLib);
|
|
// TODO - more properties?
|
|
} else if(test(ActionBinding)) {
|
|
var nm = getChange(x => (<ActionBinding>x).formalName);
|
|
result = new ActionBinding(nm);
|
|
(<ActionBinding>result).actualLib = getChange(x => (<ActionBinding>x).actualLib);
|
|
(<ActionBinding>result).actualName = getChange(x => (<ActionBinding>x).actualName);
|
|
(<ActionBinding>result).actual = getChange(x => (<ActionBinding>x).actual);
|
|
(<ActionBinding>result).isExplicit = getChange(x => (<ActionBinding>x).isExplicit);
|
|
} else if(test(KindBinding)) {
|
|
var nm = getChange(x => (<KindBinding>x).formalName);
|
|
result = new KindBinding(nm);
|
|
(<KindBinding>result).actual = getChange(x => (<KindBinding>x).actual);
|
|
(<KindBinding>result).isExplicit = getChange(x => (<KindBinding>x).isExplicit);
|
|
} else if(test(Binding)) {
|
|
var nm = getChange(x => (<Binding>x).formalName);
|
|
result = new Binding(nm);
|
|
(<Binding>result).isExplicit = getChange(x => (<Binding>x).isExplicit);
|
|
} else if(test(Comment)) {
|
|
result = new Comment();
|
|
(<Comment>result).text = tokensToStr(merge3inputs(x => strToTokens((<Comment>x).text), false));
|
|
//(<Comment>result).text = getChange(x => (<Comment>x).text);
|
|
} else if(test(CodeBlock)) {
|
|
//mergeLog("CodeBlock");
|
|
result = new CodeBlock();
|
|
(<CodeBlock>result).stmts = newChilds;
|
|
(<CodeBlock>result).flags = getChange(x => (<CodeBlock>x).flags);
|
|
} else if(test(ConditionBlock)) {
|
|
//mergeLog("ConditionBlock");
|
|
result = new ConditionBlock();
|
|
(<ConditionBlock>result).stmts = newChilds;
|
|
} else if(test(ParameterBlock)) {
|
|
//mergeLog("ParameterBlock");
|
|
result = new ParameterBlock();
|
|
(<ParameterBlock>result).stmts = newChilds;
|
|
} else if(test(BindingBlock)) {
|
|
//mergeLog("BindingBlock");
|
|
result = new BindingBlock();
|
|
(<BindingBlock>result).stmts = newChilds;
|
|
} else if(test(ResolveBlock)) {
|
|
//mergeLog("ResolveBlock");
|
|
result = new ResolveBlock();
|
|
(<ResolveBlock>result).stmts = newChilds;
|
|
} else if(test(FieldBlock)) {
|
|
//mergeLog("FieldBlock");
|
|
result = new FieldBlock();
|
|
(<FieldBlock>result).stmts = newChilds;
|
|
(<FieldBlock>result).parentDef = getChange(x => (<FieldBlock>x).parentDef);
|
|
} else if(test(InlineActionBlock)) {
|
|
//mergeLog("InlineActionBlock");
|
|
result = new InlineActionBlock();
|
|
(<InlineActionBlock>result).stmts = newChilds;
|
|
} else if(test(Block)) {
|
|
throw Error(badAstMsg);
|
|
//mergeLog("Block");
|
|
result = new Block();
|
|
(<Block>result).stmts = newChilds;
|
|
} else if(test(For)) {
|
|
//mergeLog("For");
|
|
result = new For();
|
|
(<For>result).body = <CodeBlock>getNewChild(0); // TODO - check cast?
|
|
(<For>result).boundLocal = getChange(x => (<For>x).boundLocal);
|
|
(<For>result).upperBound = mergeExprHolder(x => (<For>x).upperBound);
|
|
} else if(test(Foreach)) {
|
|
//mergeLog("Foreach");
|
|
result = new Foreach();
|
|
(<Foreach>result).conditions = <ConditionBlock>getNewChild(0); // TODO - check cast?
|
|
(<Foreach>result).body = <CodeBlock>getNewChild(1);
|
|
(<Foreach>result).boundLocal = getChange(x => (<Foreach>x).boundLocal);
|
|
(<Foreach>result).collection = mergeExprHolder(x => (<Foreach>x).collection);
|
|
} else if(test(While)) {
|
|
//mergeLog("While");
|
|
result = new While();
|
|
(<While>result).body = <CodeBlock>getNewChild(0); // TODO - check cast?
|
|
(<While>result).condition = mergeExprHolder(x => (<While>x).condition);
|
|
} else if(test(OptionalParameter)) {
|
|
result = new OptionalParameter();
|
|
(<OptionalParameter>result)._opt_name = getChange(x => (<OptionalParameter>x).getName());
|
|
(<OptionalParameter>result).expr = mergeExprHolder(x => (<OptionalParameter>x).expr);
|
|
} else if(test(If)) {
|
|
//mergeLog("If");
|
|
result = new If();
|
|
(<If>result).rawThenBody = <CodeBlock>getNewChild(0); // TODO - check cast?
|
|
var elseBody = <CodeBlock>getNewChild(1);
|
|
if (!elseBody) {
|
|
elseBody = Parser.emptyBlock()
|
|
elseBody.stmts[0].initStableName()
|
|
}
|
|
(<If>result).rawElseBody = elseBody;
|
|
(<If>result).rawCondition = mergeExprHolder(x => (<If>x).rawCondition);
|
|
(<If>result).isElseIf = getChange(x => (<If>x).isElseIf);
|
|
(<If>result).displayElse = getChange(x => (<If>x).displayElse);
|
|
} else if(test(Box)) {
|
|
//mergeLog("Box");
|
|
result = new Box();
|
|
(<Box>result).body = <CodeBlock>getNewChild(0); // TODO - check cast?
|
|
} else if(test(InlineActions)) {
|
|
//mergeLog("InlineActions");
|
|
result = new InlineActions();
|
|
(<InlineActions>result).actions = <InlineActionBlock>getNewChild(0); // TODO - check cast?
|
|
(<InlineActions>result).expr = mergeExprHolder(x => (<InlineActions>x).expr)
|
|
} else if(test(ExprStmt)) {
|
|
//mergeLog("ExprStmt");
|
|
result = new ExprStmt();
|
|
(<ExprStmt>result).expr = mergeExprHolder(x => (<ExprStmt>x).expr)
|
|
} else if(test(InlineAction)) {
|
|
//mergeLog("InlineAction");
|
|
result = new InlineAction();
|
|
(<InlineAction>result).body = <CodeBlock>getNewChild(0); // TODO - check cast?
|
|
(<InlineAction>result).name = getChange(x => (<InlineAction>x).name);
|
|
(<InlineAction>result).isOptional = getChange(x => (<InlineAction>x).isOptional);
|
|
(<InlineAction>result).isImplicit = getChange(x => (<InlineAction>x).isImplicit);
|
|
(<InlineAction>result).inParameters = getChange(x => (<InlineAction>x).inParameters);
|
|
(<InlineAction>result).outParameters = getChange(x => (<InlineAction>x).outParameters);
|
|
} else if(test(Where)) {
|
|
//mergeLog("Where");
|
|
result = new Where();
|
|
(<Where>result).condition = mergeExprHolder(x => (<Where>x).condition);
|
|
} else if(test(ForeachClause)) {
|
|
//mergeLog("ForeachClause");
|
|
result = new ForeachClause();
|
|
} else if(test(ActionParameter)) {
|
|
//mergeLog("ActionParameter");
|
|
result = new ActionParameter(mkLocal(
|
|
getChange(x => (<ActionParameter>x).getName()),
|
|
getChange(x => (<ActionParameter>x).getKind())
|
|
));
|
|
} else if(test(ActionHeader)) {
|
|
//mergeLog("ActionHeader");
|
|
result = new ActionHeader(new Action());
|
|
(<ActionHeader>result).inParameters = (<ParameterBlock>getNewChild(0)); // TODO - check cast?
|
|
(<ActionHeader>result).outParameters = (<ParameterBlock>getNewChild(1));
|
|
(<ActionHeader>result).action.body = (<CodeBlock>getNewChild(2));
|
|
} else if(test(GlobalDef)) {
|
|
//mergeLog("GlobalDef");
|
|
result = new GlobalDef();
|
|
(<GlobalDef>result).readonly = getChange(x => (<GlobalDef>x).readonly);
|
|
(<GlobalDef>result).comment = getChange(x => (<GlobalDef>x).comment);
|
|
(<GlobalDef>result).url = getChange(x => (<GlobalDef>x).url);
|
|
(<GlobalDef>result).isResource = getChange(x => (<GlobalDef>x).isResource);
|
|
(<GlobalDef>result).isTransient = getChange(x => (<GlobalDef>x).isTransient);
|
|
(<GlobalDef>result).cloudEnabled = getChange(x => (<GlobalDef>x).cloudEnabled);
|
|
(<GlobalDef>result).debuggingData = getChange(x => (<GlobalDef>x).debuggingData);
|
|
(<GlobalDef>result).cloudEnabled = getChange(x => (<GlobalDef>x).cloudEnabled);
|
|
} else if(test(Action)) {
|
|
//mergeLog("Action");
|
|
result = new Action();
|
|
var ah : ActionHeader = (<ActionHeader>getNewChild(0)); // TODO - check cast?
|
|
(<Action>result).body = ah.action.body;
|
|
ah.action = (<Action>result);
|
|
ah.inParameters.parent = result;
|
|
ah.outParameters.parent = result;
|
|
(<Action>result).header = ah;
|
|
(<Action>result).isPrivate = getChange(x => (<Action>x).isPrivate);
|
|
(<Action>result)._isPage = getChange(x => (<Action>x)._isPage);
|
|
(<Action>result)._isTest = getChange(x => (<Action>x)._isTest);
|
|
(<Action>result)._isActionTypeDef = getChange(x => (<Action>x)._isActionTypeDef);
|
|
(<Action>result).isAtomic = getChange(x => (<Action>x).isAtomic);
|
|
(<Action>result).eventInfo = getChange(x => (<Action>x).eventInfo);
|
|
(<Action>result).isOffline = getChange(x => (<Action>x).isOffline);
|
|
(<Action>result).isQuery = getChange(x => (<Action>x).isQuery);
|
|
(<Action>result)._isTest = getChange(x => (<Action>x)._isTest);
|
|
// TODO XXX make sure the following is okay
|
|
if(getChange(x => (<Action>x).modelParameter)) {
|
|
(<Action>result).modelParameter = <ActionParameter>ah.inParameters.stmts.shift();
|
|
}
|
|
} else if(test(RecordDef)) {
|
|
result = new RecordDef();
|
|
(<RecordDef>result).keys = (<FieldBlock>getNewChild(0)); // TODO - check cast?
|
|
(<RecordDef>result).values = (<FieldBlock>getNewChild(1));
|
|
(<RecordDef>result).description = getChange(x => (<RecordDef>x).description);
|
|
(<RecordDef>result).recordType = getChange(x => (<RecordDef>x).recordType);
|
|
(<RecordDef>result).cloudEnabled = getChange(x => (<RecordDef>x).cloudEnabled);
|
|
(<RecordDef>result).persistent = getChange(x => (<RecordDef>x).persistent);
|
|
(<RecordDef>result)._isExported = getChange(x => (<RecordDef>x)._isExported);
|
|
newName = getChange(x => (<RecordDef>x).getCoreName());
|
|
// TODO - what other things do we need to set here?
|
|
} else if(test(LibraryRef)) {
|
|
result = new LibraryRef();
|
|
(<LibraryRef>result).resolveClauses = (<ResolveBlock>getNewChild(0));
|
|
(<LibraryRef>result).guid = getChange(x => (<LibraryRef>x).guid);
|
|
(<LibraryRef>result).pubid = getChange(x => (<LibraryRef>x).pubid);
|
|
(<LibraryRef>result)._publicActions = getChange(x => (<LibraryRef>x)._publicActions);
|
|
(<LibraryRef>result)._publicKinds = getChange(x => (<LibraryRef>x)._publicKinds);
|
|
// TODO - what other things do we need to set here?
|
|
} else if(test(PropertyDecl)) {
|
|
throw Error(badAstMsg);
|
|
//mergeLog("PropertyDecl");
|
|
result = new PropertyDecl();
|
|
} else if(test(SingletonDef)) {
|
|
throw Error(badAstMsg);
|
|
//mergeLog("SingletonDef");
|
|
result = new SingletonDef();
|
|
(<SingletonDef>result)._isBrowsable = getChange(x => (<SingletonDef>x)._isBrowsable);
|
|
} else if(test(PlaceholderDef)) {
|
|
throw Error(badAstMsg);
|
|
//mergeLog("PlaceholderDef");
|
|
result = new PlaceholderDef();
|
|
} else if(test(LocalDef)) {
|
|
//mergeLog("LocalDef");
|
|
result = new LocalDef();
|
|
} else if(test(App)) {
|
|
//mergeLog("App");
|
|
result = new App(null);
|
|
(<App>result).rootId = getChange(x => (<App>x).rootId);
|
|
(<App>result).icon = getChange(x => (<App>x).icon);
|
|
(<App>result).color = getChange(x => (<App>x).color);
|
|
(<App>result).comment = getChange(x => (<App>x).comment);
|
|
(<App>result).iconArtId = getChange(x => (<App>x).iconArtId);
|
|
(<App>result).splashArtId = getChange(x => (<App>x).splashArtId);
|
|
App.metaMapping.forEach(k => {
|
|
result[k] = getChange(x => x[k])
|
|
});
|
|
(<App>result).setPlatform(getChange(x => (<App>x).getPlatformRaw()));
|
|
(<App>result).things = <Decl[]>newChilds;
|
|
// TODO XXX
|
|
} else if(test(Decl)) {
|
|
//mergeLog("Decl");
|
|
result = new Decl();
|
|
(<Decl>result)._wasTypechecked = getChange(x => (<Decl>x)._wasTypechecked);
|
|
(<Decl>result).deleted = getChange(x => (<Decl>x).deleted);
|
|
// TODO - do we need to handle this parent separately?
|
|
(<Decl>result).wasAutoNamed = getChange(x => (<Decl>x).wasAutoNamed);
|
|
(<Decl>result).diffStatus = getChange(x => (<Decl>x).diffStatus);
|
|
(<Decl>result).diffAltDecl = getChange(x => (<Decl>x).diffAltDecl);
|
|
} else if(test(Stmt)) {
|
|
throw Error(badAstMsg);
|
|
//mergeLog("Stmt");
|
|
result = new Stmt();
|
|
} else {
|
|
//mergeLog("<<NONE>>");
|
|
throw Error(badAstMsg); // TODO - can't merge - what should we do here?
|
|
//result = new CodeBlock();
|
|
//(<CodeBlock>result).stmts = newChilds;
|
|
}
|
|
|
|
result._kind = getChange(x => x._kind); // TODO XXX
|
|
|
|
setStableName(result, id);
|
|
|
|
if(newName) result.setName(newName)
|
|
else result.setName(getChange(x => x.getName()));
|
|
|
|
newChilds.forEach(function(x:Stmt) {
|
|
x.parent = result;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
mergeLog(">>> Merge: "+getTime()+" init maps");
|
|
|
|
var tt = getTime();
|
|
|
|
var parentMap : { [s : string] : string } = {};
|
|
var parentTemp : { [s : string] : boolean } = {};
|
|
var added : { [s : string] : boolean } = {};
|
|
var succMap : { [s : string] : {[t:string] : number } } = {};
|
|
|
|
// TODO XXX - don't use "for"!
|
|
Object.keys(hashO).forEach(key => { parentMap[key] = null; });
|
|
Object.keys(hashA).forEach(key => { parentMap[key] = null; });
|
|
Object.keys(hashB).forEach(key => { parentMap[key] = null; });
|
|
|
|
var tt2 = getTime(); treeTime[0]+=(tt2-tt); tt = tt2; // 0
|
|
|
|
mergeLog(">>> Merge: "+getTime()+" step 1");
|
|
|
|
// step 1 - perform additions and deletions
|
|
|
|
Object.keys(parentMap).forEach(x => {
|
|
var intersect = (containsId(hashA,x) &&
|
|
containsId(hashB,x) && containsId(hashO,x));
|
|
if(
|
|
!(((containsId(hashA,x) || containsId(hashB,x)) &&
|
|
!(containsId(hashO,x))) || intersect)
|
|
) {
|
|
delete parentMap[x];
|
|
} else if(!intersect) {
|
|
added[x] = true;
|
|
}
|
|
});
|
|
|
|
var tt2 = getTime(); treeTime[1]+=(tt2-tt); tt = tt2; // 1
|
|
|
|
//if(!noprint) console.log("added: "+Object.keys(added));
|
|
|
|
mergeLog(">>> Merge: "+getTime()+" step 2");
|
|
|
|
function applyChanges(f) {
|
|
Object.keys(parentMap).forEach(x => {
|
|
var pO = hashO[x];
|
|
var pA = hashA[x];
|
|
var pB = hashB[x];
|
|
|
|
f(x,parentMap,(pO ? pO.parent : undefined),(pA ? pA.parent : undefined),(pB ? pB.parent : undefined));
|
|
});
|
|
}
|
|
|
|
applyChanges((x,parentMap,pO,pA,pB) => { if(pA != pO) {/*console.log("locking: "+x);*/ parentTemp[x]=true;} parentMap[x]=pA });
|
|
|
|
applyChanges((x,parentMap,pO,pA,pB) => { if(pB != pA && !parentTemp[x]) parentMap[x]=pB });
|
|
|
|
var tt2 = getTime(); treeTime[2]+=(tt2-tt); tt = tt2; // 2
|
|
|
|
var roots = [];
|
|
var pk = Object.keys(parentMap)
|
|
pk.forEach(k => {
|
|
var src = parentMap[k];
|
|
var dst = k;
|
|
/*if(!succMap[src] && src) {
|
|
succMap[src] = {};
|
|
}
|
|
if(!succMap[dst] && dst) {
|
|
succMap[dst] = {};
|
|
}*/
|
|
if(src && dst) {
|
|
if(!succMap[src]) succMap[src] = {};
|
|
succMap[src][dst] = 1;
|
|
}
|
|
if(!src) roots.push(dst);
|
|
// TODO - get rid of:
|
|
//console.log("--- item="+k+", parent="+parentMap[k])
|
|
});
|
|
|
|
var tt2 = getTime(); treeTime[3]+=(tt2-tt); tt = tt2; // 3
|
|
|
|
var table : { [s : string] : number } = {};
|
|
var conn = stronglyConnectedComponents(succMap, pk, table);
|
|
|
|
succMap = null;
|
|
var tt2 = getTime(); treeTime[4]+=(tt2-tt); tt = tt2; // 4
|
|
|
|
//mergeLog("strongly connected components: ");
|
|
//mergeLog(table);
|
|
//mergeLog("roots = "+roots.map(x => x+"("+table[x]+")").join(", "));
|
|
|
|
function processChildren(cl : string[], done : {[t:string] : boolean }) {
|
|
cl.forEach((x:string) => {
|
|
//mergeLog("processing: "+x);
|
|
|
|
var pA = hashA[x];
|
|
var childA = getNodeHash(getChildren(pA));
|
|
|
|
var childAll = Object.keys(childA);
|
|
//var childAll = Object.keys(succMap).map(k => succMap[x][k] ? k : undefined).filter(k => !!k);
|
|
//mergeLog(" childs: "+childAll.join(","));
|
|
//childAll.sort(); // TODO - get rid of
|
|
|
|
childAll.forEach(y => {
|
|
if(table[x] != table[y] && !done[table[y]]) {
|
|
if(parentMap[y]) parentMap[y] = x;
|
|
done[table[y]] = true;
|
|
}
|
|
});
|
|
|
|
processChildren(childAll, done);
|
|
});
|
|
}
|
|
|
|
var initDone : {[t:string] : boolean } = {};
|
|
conn.forEach(comp => {
|
|
comp.forEach(x => {
|
|
if(comp.length <= 1) initDone[table[x]] = true;
|
|
});
|
|
});
|
|
|
|
var tt2 = getTime(); treeTime[5]+=(tt2-tt); tt = tt2; // 5
|
|
|
|
//mergeLog("initDone:");
|
|
//mergeLog(initDone);
|
|
processChildren(roots, initDone);
|
|
|
|
//mergeLog(parentMap);
|
|
|
|
// step 2 - determine parents
|
|
/*Object.keys(parentMap).forEach(x => {
|
|
var temp;
|
|
if(
|
|
containsId(hashO,x) && containsId(hashA,x) &&
|
|
containsId(hashB,x)
|
|
) {
|
|
var p1 = hashO[x];
|
|
var p2 = hashA[x];
|
|
var p3 = hashB[x];
|
|
mergeLog(" check 1: ("+p1.parent+" = "+p2.parent+", "+p3.parent+")");
|
|
if(p1.parent==p2.parent) {
|
|
temp = p3;
|
|
} else {
|
|
temp = p2;
|
|
}
|
|
} else if(containsId(hashA,x)) {
|
|
mergeLog(" check 2:");
|
|
var p2 = hashA[x];
|
|
temp = p2;
|
|
} else { // containsId(hashB,x)
|
|
mergeLog(" check 3:");
|
|
var p3 = hashB[x];
|
|
temp = p3;
|
|
}
|
|
mergeLog("setting parent of "+x+" to "+temp.parent);
|
|
parentMap[x] = temp.parent;
|
|
});*/
|
|
|
|
var tt2 = getTime(); treeTime[6]+=(tt2-tt); tt = tt2; // 6
|
|
|
|
mergeLog(">>> Merge: "+getTime()+" step 3: "+Object.keys(parentMap).length);
|
|
|
|
// step 3 - determine ordering
|
|
var startX = getTime();
|
|
var childMap : { [s : string] : string[] } = {};
|
|
|
|
Object.keys(parentMap).forEach(x => {
|
|
var p = parentMap[x];
|
|
if(childMap[p]) {
|
|
if(childMap[p].indexOf(x) < 0) {
|
|
childMap[p].push(x);
|
|
}
|
|
} else {
|
|
childMap[p] = [x];
|
|
}
|
|
});
|
|
|
|
var tt2 = getTime(); seqTime[0]+=(tt2-tt); tt = tt2; // 0
|
|
|
|
function addEdge(theX : string, theY : string, edgeMap : { [s : string] : {[t:string] : number } }, v : number = 1) {
|
|
if(!edgeMap[theY][theX]) {
|
|
edgeMap[theX][theY] = v;
|
|
}
|
|
}
|
|
|
|
Object.keys(childMap).forEach(px => {
|
|
var childs = childMap[px];
|
|
mergeLog(">>> processing children: "+px+": "+getTime());
|
|
|
|
// now we want to order "childs"
|
|
|
|
////////////////////////////////////
|
|
|
|
var addMap : { [s : string] : HashNode[] } = {};
|
|
var edgeMap : { [s : string] : {[t : string] : number} } = {};
|
|
initMatrix(edgeMap, childs);
|
|
|
|
if(oldSeqMerge) {
|
|
// part (a) - take the ones where A (left) disagrees
|
|
//if(ck.length > 2000) throw new Error("Too many children: "+ck.length) // TODO XXX - get rid of
|
|
mergeLog(">>> part A: "+childs.length);
|
|
childs.forEach(i => {
|
|
childs.forEach(j => {
|
|
if(i != j) {
|
|
var theX = i;
|
|
var theY = j;
|
|
|
|
if (
|
|
containsArrow(hashA, theX, theY) &&
|
|
(
|
|
!containsArrow(hashO, theX, theY)
|
|
||
|
|
!containsArrow(hashB, theY, theX)
|
|
)
|
|
) addEdge(theX, theY, edgeMap);
|
|
|
|
//if(theRes != temp) console.log(">>>>>>>>>>>>>>>> theRes != temp: ("+theRes+" != "+temp+"): "+msg);
|
|
}
|
|
});
|
|
});
|
|
|
|
transitiveClosure(edgeMap);
|
|
|
|
mergeLog(">>> part C");
|
|
// part (c) - take the ones where B (right) disagrees
|
|
|
|
childs.forEach(i => {
|
|
childs.forEach(j => {
|
|
if(i != j) {
|
|
var theX = i;
|
|
var theY = j;
|
|
|
|
/*if(
|
|
containsArrow(hashB, theX, theY) &&
|
|
(
|
|
containsArrow(hashO, theY, theX) &&
|
|
containsArrow(hashA, theY, theX)
|
|
)
|
|
) {
|
|
addEdge(theX, theY, edgeMap);
|
|
}*/
|
|
|
|
if(
|
|
containsArrow(hashB, theX, theY) &&
|
|
(
|
|
(
|
|
containsArrow(hashO, theY, theX) &&
|
|
containsArrow(hashA, theY, theX)
|
|
) ||
|
|
(
|
|
!containsArrow(hashO, theY, theX) &&
|
|
!containsArrow(hashA, theY, theX)
|
|
)
|
|
)
|
|
) {
|
|
addEdge(theX, theY, edgeMap);
|
|
}
|
|
}
|
|
})
|
|
});
|
|
|
|
transitiveClosure(edgeMap);
|
|
|
|
mergeLog(">>> part E");
|
|
// part (e) - add edges from A to B where ordering is unknown
|
|
|
|
childs.forEach(i => {
|
|
childs.forEach(j => {
|
|
if(i != j) {
|
|
var theX = i;
|
|
var theY = j;
|
|
|
|
if(
|
|
(
|
|
containsId(hashA, theX) &&
|
|
!containsId(hashB, theX)
|
|
) &&
|
|
(
|
|
containsId(hashB, theY) &&
|
|
!containsId(hashA, theY)
|
|
)
|
|
) {
|
|
addEdge(theX, theY, edgeMap);
|
|
}
|
|
}
|
|
})
|
|
});
|
|
|
|
transitiveClosure(edgeMap);
|
|
} else {
|
|
// part (a) - take the ones where A (left) disagrees
|
|
|
|
var imStart = getTime();
|
|
// slow
|
|
var sla = childs.reduce((acc:HashNode[],x:string) => {
|
|
var y = hashA[x];
|
|
if(y) {
|
|
y.used = false;
|
|
acc.push(y);
|
|
}
|
|
return acc;
|
|
}, []).sort((x,y) => x.order < y.order ? -1 : 1) //.map(x => x.name)
|
|
|
|
var slb = childs.reduce((acc:HashNode[],x:string) => {
|
|
var y = hashB[x];
|
|
if(y) {
|
|
y.used = false;
|
|
acc.push(y);
|
|
}
|
|
return acc
|
|
}, []).sort((x,y) => x.order < y.order ? -1 : 1) //.map(x => x.name)
|
|
|
|
/*var sla2 = la.filter(x => !!edgeMap[x])
|
|
var slb2 = lb.filter(x => !!edgeMap[x])
|
|
var theCheck = (sla2.toString() == sla.toString() && slb2.toString() == slb.toString());
|
|
if(!theCheck) {
|
|
console.log("sla = "+sla+"\nsla2 = "+sla2);
|
|
console.log("slb = "+slb+"\nslb2 = "+slb2);
|
|
}
|
|
Util.assert(theCheck);*/
|
|
var imEnd = getTime();
|
|
imTime += (imEnd-imStart);
|
|
|
|
var tt2 = getTime(); seqTime[1]+=(tt2-tt); tt = tt2; // 1
|
|
|
|
sla.reduce((prevs:HashNode[],curr:HashNode) => {
|
|
if(added[curr ? curr.name : undefined]) {
|
|
prevs.forEach((prev:HashNode) => {
|
|
if(!addMap[prev ? prev.name : undefined]) addMap[prev ? prev.name : undefined] = [];
|
|
//addMap[prev].push(curr);
|
|
addMap[prev ? prev.name : undefined].unshift(curr);
|
|
})
|
|
}
|
|
prevs.push(curr);
|
|
return prevs;
|
|
}, [undefined]);
|
|
slb.reduce((prevs:HashNode[],curr:HashNode) => {
|
|
if(added[curr ? curr.name : undefined]) {
|
|
prevs.forEach((prev:HashNode) => {
|
|
if(!addMap[prev ? prev.name : undefined]) addMap[prev ? prev.name : undefined] = [];
|
|
//addMap[prev].push(curr);
|
|
addMap[prev ? prev.name : undefined].unshift(curr);
|
|
})
|
|
}
|
|
prevs.push(curr);
|
|
return prevs;
|
|
}, [undefined]);
|
|
childs = childs.filter(x => !added[x]);
|
|
|
|
var tt2 = getTime(); seqTime[2]+=(tt2-tt); tt = tt2; // 2
|
|
|
|
mergeLog(">>> part A: "+childs.length);
|
|
childs.forEach(i => {
|
|
childs.forEach(j => {
|
|
if(i != j) {
|
|
var theX = i;
|
|
var theY = j;
|
|
|
|
if (
|
|
containsArrow(hashA, theX, theY) &&
|
|
(
|
|
!containsArrow(hashO, theX, theY)
|
|
||
|
|
!containsArrow(hashB, theY, theX)
|
|
)
|
|
) addEdge(theX, theY, edgeMap, 1);
|
|
|
|
//if(theRes != temp) console.log(">>>>>>>>>>>>>>>> theRes != temp: ("+theRes+" != "+temp+"): "+msg);
|
|
}
|
|
});
|
|
});
|
|
|
|
var tt2 = getTime(); seqTime[3]+=(tt2-tt); tt = tt2; // 3
|
|
|
|
mergeLog(">>> part B");
|
|
// part (c) - take the ones where B (right) disagrees
|
|
|
|
childs.forEach(i => {
|
|
childs.forEach(j => {
|
|
if(i != j) {
|
|
var theX = i;
|
|
var theY = j;
|
|
|
|
if(
|
|
containsArrow(hashB, theX, theY) &&
|
|
(
|
|
(
|
|
containsArrow(hashO, theY, theX) &&
|
|
containsArrow(hashA, theY, theX)
|
|
)
|
|
)
|
|
) {
|
|
addEdge(theX, theY, edgeMap, 2);
|
|
}
|
|
}
|
|
})
|
|
});
|
|
|
|
var tt2 = getTime(); seqTime[4]+=(tt2-tt); tt = tt2; // 4
|
|
|
|
mergeLog(">>> SCC: "+childs.length);
|
|
var nums : { [s : string] : number} = {};
|
|
var comps = stronglyConnectedComponents(edgeMap, childs, nums, (childs.length > 100));
|
|
|
|
var tt2 = getTime(); seqTime[5]+=(tt2-tt); tt = tt2; // 5
|
|
|
|
mergeLog(">>> comps");
|
|
|
|
// delete all B edges which are in a non-trivial strongly-connected component,
|
|
// replacing them with opposing A edges
|
|
comps.forEach(comp => {
|
|
if(comp.length <= 1) return; // ignore
|
|
comp.forEach(x => {
|
|
comp.forEach(y => {
|
|
if(edgeMap[x][y] >= 2) {
|
|
edgeMap[x][y] = 0
|
|
edgeMap[y][x] = 1
|
|
}
|
|
})
|
|
})
|
|
});
|
|
|
|
var tt2 = getTime(); seqTime[6]+=(tt2-tt); tt = tt2; // 6
|
|
}
|
|
|
|
////////////////////////////////////
|
|
|
|
//printMatrix(edgeMap);
|
|
|
|
mergeLog(">>> sorting");
|
|
childs.sort((a,b) => {
|
|
if(true || oldSeqMerge) { // TODO XXX - remove
|
|
if(edgeMap[a][b] > 0) {
|
|
return -1;
|
|
} else if(edgeMap[b][a] > 0) {
|
|
return 1;
|
|
} else {
|
|
throw "merge exception: ("+a+", "+b+") not ordered";
|
|
}
|
|
}
|
|
});
|
|
|
|
edgeMap = null;
|
|
|
|
var tt2 = getTime(); seqTime[7]+=(tt2-tt); tt = tt2; // 7
|
|
|
|
function pop(answer : string[]) {
|
|
//if(!first && !init) return answer;
|
|
if(childs.length == 0) return answer;
|
|
var first = childs.pop();
|
|
|
|
var a = addMap[first];
|
|
if(a) {
|
|
// NOTE - var "a" is in backwards order, i.e. the last element should appear
|
|
// first in the result
|
|
a.forEach(x => {
|
|
if(x && !x.used) {
|
|
x.used = true;
|
|
answer.unshift(x.name);
|
|
}
|
|
});
|
|
//childs = a.concat(childs);
|
|
}
|
|
|
|
if(first) answer.unshift(first);
|
|
|
|
return pop(answer);
|
|
}
|
|
|
|
if(!oldSeqMerge) {
|
|
childs.unshift(undefined);
|
|
childs = pop([]);
|
|
childMap[px] = childs;
|
|
}
|
|
|
|
sla.forEach(x => x.used = false);
|
|
slb.forEach(x => x.used = false);
|
|
|
|
addMap = null;
|
|
var tt2 = getTime(); seqTime[8]+=(tt2-tt); tt = tt2; // 8
|
|
});
|
|
var endX = getTime();
|
|
//seqTime += (endX-startX);
|
|
|
|
var theTemp : string = undefined;
|
|
delete childMap[theTemp];
|
|
Object.keys(childMap).forEach(x => {
|
|
var childs = childMap[x];
|
|
var s = "";
|
|
if(x == getStableName(o)) {
|
|
s = "<<ROOT>>";
|
|
}
|
|
mergeLog(">>> Merge: parent="+x+", child="+childs.join(", ")+" | "+s);
|
|
});
|
|
|
|
// TODO - childMap can now be used directly
|
|
// to produce the new merged AST
|
|
|
|
var end1 = getTime();
|
|
|
|
mergeLog(">>> Merge: "+getTime()+" doing node-level merge...");
|
|
//var rtemp = o; // TODO - see the above note
|
|
var rtemp = merge3node(
|
|
new HashNode(undefined, getStableName(o), [], o),
|
|
new HashNode(undefined, getStableName(a), [], a),
|
|
new HashNode(undefined, getStableName(b), [], b),
|
|
childMap
|
|
);
|
|
|
|
var end = getTime();
|
|
|
|
if (datacollector) {
|
|
var mergedata = <IMergeData> {};
|
|
mergedata.totaltime = (end - start);
|
|
mergedata.nodeleveltime = (end - end1);
|
|
mergedata.seqtime = seqTime;
|
|
mergedata.treetime = treeTime;
|
|
mergedata.scctime = sccTime;
|
|
mergedata.numchecks = numChecks;
|
|
mergedata.deviceinfo = Browser.platformCaps;
|
|
mergedata.releaseid = Cloud.currentReleaseId;
|
|
|
|
datacollector(mergedata);
|
|
// TODO XXX - print this?
|
|
//mergeLog(">>> Merge: " + getTime() + " >>> TOTAL TIME: " + mergedata.totaltime + ", NODE-LEVEL TIME: " + mergedata.nodeleveltime + ", seqTime =" + seqTime + " (" + seqTime + "), treeTime = " + treeTime + ", sccTime = " + sccTime + ", numChecks = " + numChecks);
|
|
}
|
|
return rtemp;
|
|
}
|
|
|
|
export interface IMergeData {
|
|
totaltime: number;
|
|
nodeleveltime: number;
|
|
seqtime: number[];
|
|
treetime: number[];
|
|
scctime: number;
|
|
numchecks: number;
|
|
deviceinfo: string[];
|
|
releaseid: string;
|
|
}
|
|
|
|
export var theO = undefined;
|
|
export var theA = undefined;
|
|
export var theB = undefined;
|
|
export var theM = undefined;
|
|
|
|
function getApp(text:string) {
|
|
var app = (<any>TDev).AST.Parser.parseScript(text);
|
|
(<any>TDev).AST.TypeChecker.tcApp(app);
|
|
var v = new TDev.AST.InitIdVisitor(false);
|
|
v.dispatch(app);
|
|
return app;
|
|
}
|
|
|
|
export function testMerge(idO="yqum", idA="yqum", idB="xspn") {
|
|
(<any>TDev).ScriptCache.getScriptAsync(idO).then(textO =>
|
|
(<any>TDev).ScriptCache.getScriptAsync(idA).then(textA =>
|
|
(<any>TDev).ScriptCache.getScriptAsync(idB).then(textB => {
|
|
var table = {};
|
|
[[idO,textO],[idA,textA],[idB,textB]].forEach(x => {
|
|
if(!table[x[0]]) {
|
|
table[x[0]] = getApp(x[1])
|
|
}
|
|
})
|
|
|
|
function doMerge(idO:string, idA:string, idB:string, compareTo:string) {
|
|
theO = table[idO];
|
|
theA = table[idA];
|
|
theB = table[idB];
|
|
|
|
var m = (<any>TDev).AST.Merge.merge3(theO,theA,theB);
|
|
(<any>TDev).AST.TypeChecker.tcApp(m);
|
|
theM = m;
|
|
|
|
var success = false;
|
|
var str = "merge("+idO+","+idA+","+idB+")";
|
|
if(table[compareTo] && m.serialize().replace(/\s*/g,"") != table[compareTo].serialize().replace(/\s*/g,"")) {
|
|
str += (" =/= ");
|
|
m.things.forEach((th,i) => {
|
|
if(m.things[i].serialize().replace(/\s*/g,"") != table[compareTo].things[i].serialize().replace(/\s*/g,"")) {
|
|
console.log("unequal: "+i);
|
|
}
|
|
});
|
|
} else {
|
|
success = true;
|
|
str += (" = ");
|
|
}
|
|
console.log(str+""+compareTo+(!success ? " ((FAIL!))" : " (success)"));
|
|
}
|
|
|
|
console.log("testing merge(x,x,x)");
|
|
doMerge(idO,idO,idO,idO);
|
|
doMerge(idA,idA,idA,idA);
|
|
doMerge(idB,idB,idB,idB);
|
|
|
|
console.log("testing merge(x,y,x)");
|
|
doMerge(idO,idA,idO,idA);
|
|
doMerge(idO,idB,idO,idB);
|
|
doMerge(idA,idB,idA,idB);
|
|
doMerge(idA,idO,idA,idO);
|
|
doMerge(idB,idA,idB,idA);
|
|
doMerge(idB,idO,idB,idO);
|
|
|
|
console.log("testing merge(x,x,y)");
|
|
doMerge(idO,idO,idA,idA);
|
|
doMerge(idO,idO,idB,idB);
|
|
doMerge(idA,idA,idB,idB);
|
|
doMerge(idA,idA,idO,idO);
|
|
doMerge(idB,idB,idA,idA);
|
|
doMerge(idB,idB,idO,idO);
|
|
}
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
export function basicTest(o:string, a:string, b:string) {
|
|
if(!o) {
|
|
o =
|
|
"meta version \"v2.2,js,ctx,refs,localcloud,unicodemodel,allasync\";\n"+
|
|
"meta name \"awe-inspiring script\";\n"+
|
|
"meta rootId \"nLWNALhDdDnO6mTvydoNe8aI\";\n"+
|
|
"meta allowExport \"yes\";\n"+
|
|
"meta platform \"current\";\n"+
|
|
"meta parentIds \"\";\n"+
|
|
"#main\n"+
|
|
"action main() {\n"+
|
|
" #x1QgKIXXV6P3GL4v $x1 := 1;\n"+
|
|
" #nrxemFg0cPs5bQh7 $x2 := 12;\n"+
|
|
"}\n"+
|
|
"#main2\n"+
|
|
"action main2() {\n"+
|
|
" #x1QgKIXXV6P3GL4v1 $x1 := 1;\n"+
|
|
" #nrxemFg0cPs5bQh72 $x2 := 12;\n"+
|
|
" #rpYjyryilrIApEv14 $x4 := 123;\n"+
|
|
"}\n"
|
|
}
|
|
if(!a) {
|
|
a =
|
|
"meta version \"v2.2,js,ctx,refs,localcloud,unicodemodel,allasync\";\n"+
|
|
"meta name \"awe-inspiring script\";\n"+
|
|
"meta rootId \"nLWNALhDdDnO6mTvydoNe8aI\";\n"+
|
|
"meta allowExport \"yes\";\n"+
|
|
"meta platform \"current\";\n"+
|
|
"meta parentIds \"\";\n"+
|
|
"#main\n"+
|
|
"action main() {\n"+
|
|
" #nrxemFg0cPs5bQh7 $x2 := 12;\n"+
|
|
" #x1QgKIXXV6P3GL4v $x1 := 1;\n"+
|
|
"}\n"+
|
|
"#main2\n"+
|
|
"action main2() {\n"+
|
|
" #x1QgKIXXV6P3GL4v1 $x1 := 1;\n"+
|
|
" #nrxemFg0cPs5bQh72 $x2 := 12;\n"+
|
|
" #rpYjyryilrIApEv03 $x3 := 123;\n"+
|
|
"}\n"
|
|
}
|
|
if(!b) {
|
|
b =
|
|
"meta version \"v2.2,js,ctx,refs,localcloud,unicodemodel,allasync\";\n"+
|
|
"meta name \"awe-inspiring script\";\n"+
|
|
"meta rootId \"nLWNALhDdDnO6mTvydoNe8aI\";\n"+
|
|
"meta allowExport \"yes\";\n"+
|
|
"meta platform \"current\";\n"+
|
|
"meta parentIds \"\";\n"+
|
|
"#main\n"+
|
|
"action main() {\n"+
|
|
" #x1QgKIXXV6P3GL4v $x1 := 1;\n"+
|
|
" #nrxemFg0cPs5bQh7 $x2 := 12;\n"+
|
|
" #rpYjyryilrIApEv0 $x3 := 123;\n"+
|
|
" #rpYjyryilrIApEv1 $x4 := 123;\n"+
|
|
" #rpYjyryilrIApEv2 $x5 := 123;\n"+
|
|
" #rpYjyryilrIApEv3 $x6 := 123;\n"+
|
|
"}\n"+
|
|
"#main2\n"+
|
|
"action main2() {\n"+
|
|
" #nrxemFg0cPs5bQh72 $x2 := 12;\n"+
|
|
" #x1QgKIXXV6P3GL4v1 $x1 := 1;\n"+
|
|
"}\n"
|
|
}
|
|
|
|
theO = getApp(o);
|
|
theA = getApp(a);
|
|
theB = getApp(b);
|
|
|
|
var m = (<any>TDev).AST.Merge.merge3(theO,theA,theB);
|
|
(<any>TDev).AST.TypeChecker.tcApp(m);
|
|
theM = m;
|
|
}
|
|
}
|
|
|
|
class Normalizer
|
|
extends NodeVisitor
|
|
{
|
|
visitStmt(s:Stmt)
|
|
{
|
|
this.visitChildren(s)
|
|
}
|
|
|
|
visitCodeBlock(b:CodeBlock)
|
|
{
|
|
var newStmts = []
|
|
var lastPlace = false
|
|
b.stmts.forEach(s => {
|
|
if (s.isPlaceholder() && lastPlace) return
|
|
lastPlace = s.isPlaceholder()
|
|
newStmts.push(s)
|
|
})
|
|
b.stmts = newStmts
|
|
this.visitChildren(b)
|
|
}
|
|
}
|
|
|
|
export function mergeScripts(baseText:string, ourText:string, otherText:string)
|
|
{
|
|
var prep = (s:string) => {
|
|
var app = Parser.parseScript(s, [])
|
|
TypeChecker.tcScript(app, true)
|
|
new InitIdVisitor(false).dispatch(app)
|
|
return app
|
|
}
|
|
|
|
var baseApp = prep(baseText)
|
|
var ourApp = prep(ourText)
|
|
var otherApp = prep(otherText)
|
|
|
|
var mergedApp = <App>Merge.merge3(baseApp, ourApp, otherApp)
|
|
mergedApp.parentIds = ourApp.parentIds.slice(0)
|
|
// new Normalizer().dispatch(mergedApp)
|
|
|
|
Diff.diffApps(ourApp, mergedApp, { useStableNames: false })
|
|
|
|
return mergedApp
|
|
}
|
|
}
|