TouchDevelop/ast/refactor.ts

1221 строка
49 KiB
TypeScript

///<reference path='refs.ts'/>
module TDev.AST {
// update library references
export class SplitAppIntoAppAndLibrary {
constructor() {
this.ddManager = new DeclAndDepsManager();
}
private ddManager: DeclAndDepsManager = null;
private theApp: App = null;
private targetLibrary: LibraryRef = null;
private declsSelectedToMove: DeclAndDeps[] = [];
private otherDeclsToMove: DeclAndDeps[] = [];
private remainingDecls: DeclAndDeps[] = null;
public appRewritten: string = null
public library: string = null;
public getAllDeclsToMove(): DeclAndDeps[]{
return this.declsSelectedToMove.concat(this.otherDeclsToMove);
}
public getRemainingDecls(): DeclAndDeps[] {
return this.remainingDecls;
}
private programChanged(): boolean {
var all = this.ddManager.getAll().filter(dd => !(dd.decl instanceof LibraryRef));
if (all.length == this.theApp.things.length) {
var ret = true;
this.theApp.things.forEach(d => { ret = ret && d.cachedSerialized.idx >= 0; });
this.ddManager.getAll().forEach(dd => {
ret = ret && this.theApp.things.indexOf(dd.decl) >= 0;
});
return ret;
} else {
return false;
}
}
public invalidate() {
// we should only do this if the program changed (it's an expensive operation)
if (this.targetLibrary != null && this.programChanged()) {
this.ddManager.invalidate(); // get rid of all derived information
this.otherDeclsToMove = []; // ""
this.remainingDecls = null; // ""
var remove = [];
this.ddManager.getAll().forEach(dd => {
if (this.theApp.things.indexOf(dd.decl) < 0) {
remove.push(dd);
}
});
remove.forEach(dd => {
this.ddManager.remove(dd);
var idx = this.declsSelectedToMove.indexOf(dd);
if (idx >= 0) this.declsSelectedToMove.splice(idx);
});
// add back the require elements
this.addToDeclsToMove(this.declsSelectedToMove);
}
}
public getAll(): DeclAndDeps[] {
return this.ddManager.getAll();
}
public getter(d: Decl): DeclAndDeps {
return this.ddManager.getter(d);
}
public setAppAndLib(a: App, l: LibraryRef): boolean {
if (this.targetLibrary == null) {
this.theApp = a;
this.targetLibrary = l;
return true;
} else {
return false;
}
}
public getApp(): App {
return this.theApp;
}
public getLib(): LibraryRef {
return this.targetLibrary;
}
public addToDeclsToMove(newOnes: DeclAndDeps[]) {
// we ensure that declsToMove always is downwards closed,
// which means that the split into app/library is well-defined
var theRest = this.computeClosure(newOnes);
newOnes.forEach(dd => {
if (this.declsSelectedToMove.indexOf(dd) < 0) {
this.declsSelectedToMove.push(dd)
}
});
theRest.forEach(dd => {
if (this.declsSelectedToMove.indexOf(dd) < 0 && this.otherDeclsToMove.indexOf(dd) < 0) {
this.otherDeclsToMove.push(dd)
}
});
this.computeRemaining();
}
public computeClosure(roots: DeclAndDeps[]): DeclAndDeps[] {
return this.computeClosureRaw(roots, this.getAllDeclsToMove());
}
private computeRemaining() {
if (this.remainingDecls == null) {
this.remainingDecls = [];
this.theApp.things.forEach(t => {
if (!(t instanceof Action && (<Action>t).isEvent())) {
var dd = this.ddManager.getter(t);
if (this.getAllDeclsToMove().indexOf(dd) < 0) {
this.remainingDecls.push(dd);
}
}
});
} else {
this.remainingDecls = this.remainingDecls.filter((a) => this.getAllDeclsToMove().indexOf(a) < 0);
}
}
private computeClosureRaw(roots: DeclAndDeps[], alreadyIn: DeclAndDeps[]) {
var theRest = [];
DeclAndDeps.computeClosure(roots, alreadyIn, theRest);
return theRest.filter(dd => dd.decl != this.targetLibrary);
}
public makeSplit() {
// sanity checks
if (this.targetLibrary == null || this.getAllDeclsToMove() == [])
return;
// which library actions will be public?
var actions = this.getAllDeclsToMove().filter(dd => dd.decl instanceof Action);
actions.forEach(dd => (<Action>dd.decl).isPrivate = true);
this.remainingDecls.filter(dd => dd.decl instanceof Action).forEach(a => {
// check for calls into library from app and make actions public as needed
a.getActions().forEach(b => {
if (actions.indexOf(b) >= 0) {
var act = <Action>b.decl;
act.isPrivate = false;
// if parameter of b is a callback, make it public
// (this is a special case that we might generalize later
// if record types and other types can be exported from
// library concretely)
act.getInParameters().forEach(p => {
if (p.local.getKind() instanceof UserActionKind) {
(<UserActionKind>p.local.getKind()).userAction.isPrivate = false;
}
});
}
});
});
// keep a copy of originals in case user wants to revert
this.getAllDeclsToMove().forEach(dd => { dd.serialized = dd.decl.serialize(); });
// we need to rewrite the decls that depend on target library to remove the library reference
var removeTarget = new RemoveLibraryReference(this.targetLibrary);
this.getAllDeclsToMove().forEach(dd => removeTarget.dispatch(dd.decl));
// create the accessor functions that will permit application to
// access the fields/properties of the hidden data
var accessors = new CreateAccessors(
this.theApp,
this.getAllDeclsToMove().filter(dd => !(dd.decl instanceof Action)).map(dd => dd.decl),
this.remainingDecls);
// add the accessors to the library
accessors.getAccessorActions().forEach(act => this.targetLibrary.resolved.addDecl(act));
// rewrite the application (given the accessor information)
var rewriter = new RewriteApp(this.targetLibrary, actions.map(dd => dd.decl), accessors);
rewriter.performRewrite(this.theApp);
// move everything to library
// NOTE: we are assuming that there are no name conflicts with things already in the library
var libraryReferences = [];
this.getAllDeclsToMove().forEach((dd) => {
if (dd.decl != this.targetLibrary && dd.decl.parent == this.theApp) {
var decls = AST.Parser.parseDecls(dd.decl.serialize(), this.targetLibrary.resolved);
decls.forEach(d => {
if (d instanceof LibraryRef) {
if (libraryReferences.indexOf(d) < 0) {
libraryReferences.push(d);
this.targetLibrary.resolved.addDecl(d);
}
} else {
this.targetLibrary.resolved.addDecl(d);
}
});
}
});
// resolve any new library references we've added to the target library
this.targetLibrary.initializeResolves();
// delete the decls that have been copied ('cept for LibraryRefs)
this.getAllDeclsToMove().forEach(dd => {
if (!(dd.decl instanceof LibraryRef)) {
this.theApp.deleteDecl(dd.decl);
dd.decl = null;
} else {
// in order to delete a library reference, we need
// to show that it's no longer needed in the application
}
});
// save the app and library
InitIdVisitor.ensureOK(this.theApp)
InitIdVisitor.ensureOK(this.targetLibrary.resolved)
this.appRewritten = this.theApp.serialize();
this.library = this.targetLibrary.resolved.serialize();
// TODO: for testing, we will want to check that the above are syntactically
// TODO: and semantically correct.
}
}
class CreateAccessors {
constructor(
public theApp: App,
public declsToLibrary: Decl[],
public remaining: DeclAndDeps[]) {
// go through the actions that remain in the application to see
// what globals/fields/properties they access of decls that are
// moving to the library
remaining.forEach(dd => {
dd.direct.readGlobals.forEach(rg => this.addGlobal(rg, "R"));
dd.direct.writtenGlobals.forEach(wg => this.addGlobal(wg, "W"));
dd.direct.readFields.forEach(rf => {
var stmt = rf.forwardsToStmt();
this.addField(<RecordField>stmt, "R");
});
dd.direct.readProps.forEach((rp, i) => {
this.addProperty(rp, dd.direct.readPropsWhat[i]);
});
dd.direct.writtenFields.forEach(wp => {
var stmt = wp.forwardsToStmt();
this.addField(<RecordField>stmt, "W")
});
dd.direct.asIEnumerable.forEach(e => {
if (this.declsToLibrary.indexOf(e) >= 0 && this.collectionOfRecords.indexOf(e) < 0)
this.collectionOfRecords.push(e);
});
});
}
// these are all derived from dd
private rewriteFields: RecordField[] = [];
private fieldModes: string[] = [];
private rewriteGlobals: Decl[] = [];
private globalModes: string[] = [];
private globalThings: string[] = [];
private rewriteProperties: IProperty[] = [];
private rewritePropertiesExpr: Expr[] = [];
private collectionOfRecords: RecordDef[] = [];
private getRecordName(prop: IProperty): string {
var rd = <RecordDef>prop.parentKind.getRecord();
if (rd && this.declsToLibrary.indexOf(rd) >= 0) {
return rd.getName() + " " + prop.getName();
}
return null;
}
// this should be part of accessor.
public getAccessorName(e: Expr, mode: string): string {
if (e.referencedData()) {
var glob = e.referencedData();
if (this.declsToLibrary.indexOf(e.referencedData()) >= 0) {
return (mode == "R" ? "" : "set ") + glob.getName();
}
} else if (e.referencedRecordField()) {
var field = e.referencedRecordField();
var recordDef = field.def();
if (this.declsToLibrary.indexOf(recordDef) >= 0) {
return (mode == "R" ? "" : "set ") + field.getName();
}
} else if (e.calledProp()) {
var c = <Call>e;
if (e.calledProp() instanceof RecordDef) {
var rd2 = <RecordDef>c.prop();
if (this.declsToLibrary.indexOf(rd2) >= 0)
return rd2.getName() + (mode == "E" ? " collection" : "");
} else {
return this.getRecordName(c.prop());
}
}
return null;
}
private getName(name: string, mode: string): string {
return (mode == "W" ? "set " : "") + name;
}
// either glob nonnull iff field null
private makeActionFromGlobOrField(thing: string, glob: Decl, field: RecordField, mode: string): Action {
var writer = new TokenWriter();
writer.keyword("action").id(this.getName(glob ? glob.getName() : field.getName(), mode));
writer.op("(");
if (!glob) {
writer.id("r").op(":").kind(this.theApp, field.def().entryKind);
if (mode == "W")
writer.op(",");
}
if (mode == "W") {
writer.id("val").op(":").kind(this.theApp, glob ? glob.getKind() : field.dataKind)
}
writer.op(")");
if (mode == "R") {
writer.keyword("returns").op("(").id("val").op(":").kind(this.theApp, glob ? glob.getKind() : field.dataKind).op(")");
}
writer.op("{");
if (mode == "R") {
writer.id("val").op(":=");
}
if (glob) {
writer.id(thing);
} else {
writer.id("r");
}
writer.op("→").id(glob ? glob.getName() : field.getName());
if (mode == "W") {
writer.op(":=").id("val");
}
writer.op("}");
return <Action> Parser.parseDecl(writer.finalize());
}
// TODO: we need to generate different names if we have the same property
private makeActionFromProperty(prop: IProperty, expr: Expr): Action {
var record = prop.parentKind.getRecord();
// property return value?
// property arguments
var writer = new TokenWriter();
writer.keyword("action");
// put in the parameters
var name = this.getRecordName(prop);
if (name == null)
name = prop.getName();
writer.id(name).op("(");
var parms = prop.getParameters();
var first = true;
if (expr.getKind() instanceof RecordEntryKind) {
writer.id("r").op(":").id(record.entryKind.getName());
first = false;
}
// always skip the implicit this
var i = 1;
while (i < parms.length) {
if (!first) writer.op(",");
var p = parms[i];
writer.id(p.getName()).op(":").kind(this.theApp, p.getKind());
i++;
}
writer.op(")");
var res = prop.getResult();
if (res) {
writer.keyword("returns").op("(");
writer.id("res").op(":").kind(this.theApp, res.getKind());
writer.op(")");
}
writer.op("{");
// now call the property on record
if (res) {
writer.id("res").op(":=");
}
if (expr.getKind() instanceof RecordEntryKind) {
writer.id("r");
} else {
writer.id(record.thingSetKindName()).op("→").id(record.getName());
}
writer.op("→").id(prop.getName());
i = 1;
if (i < parms.length && parms.length > 0) {
writer.op("(");
var first = true;
while (i < parms.length) {
if (!first) writer.op(",");
writer.id(parms[i].getName());
i++;
first = false;
}
writer.op(")");
}
writer.op("}");
return <Action> Parser.parseDecl(writer.finalize());
}
private collectionTemplate =
"action $NAME_collection() returns(coll: Collection[ * $ENTRY]) { " +
"$coll := records→$NAME→create_collection; " +
"foreach e in records→$NAME where true do { $coll→add($e); } " +
"}";
private makeCollectionAccessor(rd: RecordDef): Action {
var text = this.collectionTemplate;
text = text.replace(/\$NAME/g, Lexer.quoteId(rd.getName()))
.replace(/\$ENTRY/g, Lexer.quoteId(rd.entryKind.getName()));
return <Action> Parser.parseDecl(text);
}
public getAccessorActions(): Action[] {
var actions: Action[] = [];
for (var i = 0; i < this.rewriteGlobals.length; i++) {
for (var j = 0; j < this.globalModes[i].length; j++) {
actions.push(this.makeActionFromGlobOrField(this.globalThings[i], this.rewriteGlobals[i], null, this.globalModes[i][j]));
}
}
for (var i = 0; i < this.rewriteFields.length; i++) {
for (var j = 0; j < this.fieldModes[i].length; j++) {
actions.push(this.makeActionFromGlobOrField(null, null, this.rewriteFields[i], this.fieldModes[i][j]));
}
}
this.rewriteProperties.forEach((p, i) => actions.push(this.makeActionFromProperty(p, this.rewritePropertiesExpr[i])));
this.collectionOfRecords.forEach(r => actions.push(this.makeCollectionAccessor(r)));
return actions;
}
private addField(field: RecordField, mode: string) {
var recordDef = field.def();
if (this.declsToLibrary.indexOf(recordDef) >= 0) {
var index = this.rewriteFields.indexOf(field);
if (index < 0) {
this.rewriteFields.push(field);
this.fieldModes.push(mode);
} else {
if (this.fieldModes[index].indexOf(mode) < 0) {
this.fieldModes[index] += mode;
}
}
}
}
private addGlobal(decl: Decl, mode: string) {
if (this.declsToLibrary.indexOf(decl) >= 0 && decl instanceof GlobalDef) {
var glob = <GlobalDef>decl;
var index = this.rewriteGlobals.indexOf(glob);
if (index < 0) {
this.rewriteGlobals.push(glob);
this.globalModes.push(mode);
this.globalThings.push(glob.thingSetKindName());
} else {
if (this.globalModes[index].indexOf(mode) < 0) {
this.globalModes[index] += mode;
}
}
}
}
private addProperty(p: IProperty, e: Expr) {
var rd = p.parentKind.getRecord();
if (rd && this.declsToLibrary.indexOf(rd) >= 0 && this.rewriteProperties.indexOf(p) < 0) {
this.rewriteProperties.push(p);
this.rewritePropertiesExpr.push(e);
}
}
}
// rewrite a kind, which may depend on types moved to the library
class RewriteKind {
// two modes for rewriting
// 1. declsToMove.length > 0 && replaceTarget==false
// 2. declsToMove.length == 0 && replaceTarget==true
constructor(public targetLibrary: LibraryRef, public declsToMove: Decl[], public replaceTarget: boolean) {
}
public rewriteKind(k: Kind): Kind {
if (k.getRecord()) {
var rec = k.getRecord();
if (this.declsToMove.indexOf(rec) >= 0) {
return this.targetLibrary.getAbstractKind(k.getName());
}
} else if (k instanceof UserActionKind) {
var act = (<UserActionKind>k).userAction;
if (this.declsToMove.indexOf(act) >= 0) {
// nothing to do, since all dependent types are in downwards closure
} else {
act.getParameters().forEach(p => {
p._kind = this.rewriteKind(p.getKind())
});
}
} else if (k instanceof LibraryRefAbstractKind) {
// we are taking a dependence on a library
var absKind = <LibraryRefAbstractKind>k;
var lib = absKind.parentLibrary();
if (this.replaceTarget && lib == this.targetLibrary) {
return new UnresolvedKind(absKind.getName());
}
} else if (k instanceof ParametricKind) {
var pk = <ParametricKind>k;
pk.parameters.forEach((p, i) => pk.parameters[i] = this.rewriteKind(pk.parameters[i]));
} else if (k instanceof ActionKind) {
var params = (<ActionKind>k).getInParameters().concat((<ActionKind>k).getOutParameters());
params.forEach(p => { p._kind = this.rewriteKind(p.getKind()) });
}
return k;
}
}
// for removing reference to target library
// TODO: we may want to create a "rewriting visitor" base class at some point
class RemoveLibraryReference
extends NodeVisitor {
constructor(public targetLibrary: LibraryRef) {
super();
this.rewriteKind = new RewriteKind(this.targetLibrary, [], true);
}
private rewriteKind: RewriteKind;
private lastExprHolder: ExprHolder;
visitAstNode(n: AstNode) {
this.visitChildren(n);
return null;
}
visitExprHolder(n: ExprHolder) {
this.lastExprHolder = n;
if (n.parsed)
this.dispatch(n.parsed);
return null;
}
visitAction(n: Action) {
super.visitAction(n);
n.getInParameters().concat(n.getOutParameters()).
forEach((p) => {
p.local.setKind(this.rewriteKind.rewriteKind(p.local.getKind()));
});
}
visitGlobalDef(n: GlobalDef) {
n.setKind(this.rewriteKind.rewriteKind(n.getKind()));
}
visitRecordDef(n: RecordDef) {
n.getFields().forEach(f => { f.dataKind = this.rewriteKind.rewriteKind(f.dataKind) });
}
// are we calling
visitCall(n: Call) {
n.args.forEach(e => { this.dispatch(e); });
var prop = n.prop();
var thingref = <ThingRef>n.args[0];
if (prop && prop.forwardsTo() instanceof LibraryRefAction) {
var lra = <LibraryRefAction>prop.forwardsTo();
if (lra.parentLibrary() == this.targetLibrary) {
// TODO: this is pretty ugly and repeated (encapsulate, if you dare)
var subcall = <Call>n.args[0];
var tokens = this.lastExprHolder.tokens;
var leftmost = getLeftmost(n.args[0]);
var leftIdx = tokens.indexOf(leftmost);
var rightIdx = tokens.indexOf(subcall.propRef);
if (!(lra instanceof LibExtensionAction) && leftmost instanceof ThingRef) {
var writer = new TokenWriter();
writer.keyword("code");
var newExprHolder = Parser.parseExprHolder(writer.finalize());
tokens.spliceArr(leftIdx, rightIdx - leftIdx + 1, newExprHolder.tokens);
}
}
}
}
}
class RewriteApp
extends NodeVisitor {
constructor(public targetLibrary: LibraryRef,
public libActions: Decl[],
public acc: CreateAccessors) {
super();
this.rewriteKind = new RewriteKind(this.targetLibrary, this.acc.declsToLibrary, false);
}
private lastExprHolder: ExprHolder = null;
private mode: string = "";
private rewriteKind: RewriteKind;
visitExprHolder(n: ExprHolder) {
this.lastExprHolder = n;
this.mode = "R";
if (n.parsed)
this.dispatch(n.parsed);
return null;
}
visitForeach(n: Foreach) {
if (n.collection.parsed) {
this.mode = "E";
this.lastExprHolder = n.collection;
this.dispatch(n.collection.parsed);
}
[<AstNode> n.conditions, n.body].forEach(c => c.accept(this));
}
visitAstNode(n: AstNode) {
this.visitChildren(n);
return null;
}
// only rewrite actions in app
visitAction(n: Action) {
if (this.libActions.indexOf(n) < 0) {
super.visitAction(n);
n.getInParameters().concat(n.getOutParameters()).
forEach((p) => this.rewriteActionParameter(p));
}
}
rewriteActionParameter(n: ActionParameter) {
n.local.setKind(this.rewriteKind.rewriteKind(n.local.getKind()));
}
// for globals and records remaining in the app, we may need to rewrite
// some of their reference types, if those moved to the library
visitGlobalDef(n: GlobalDef) {
if (this.acc.declsToLibrary.indexOf(n) < 0) {
n.setKind(this.rewriteKind.rewriteKind(n.getKind()));
}
}
visitRecordDef(n: RecordDef) {
if (this.acc.declsToLibrary.indexOf(n) < 0) {
// for a record still in the app, check it fields to see what needs to be written
n.getFields().forEach(f => { f.dataKind = this.rewriteKind.rewriteKind(f.dataKind) });
}
}
// helper method for rewriting call
rewriteThingRef(t: ThingRef) {
var tokens = this.lastExprHolder.tokens;
var idx = tokens.indexOf(t);
var writer = new TokenWriter();
this.targetLibrary.writeRef(writer);
var newExprHolder = Parser.parseExprHolder(writer.finalize());
this.lastExprHolder.tokens.spliceArr(idx, 1, newExprHolder.tokens);
}
// when rewriting a call, we need to be careful to rewrite bottom up,
// as calls can be nested under calls. we also need to track mode, as
// done in DirectAccessFinder
visitCall(n: Call) {
var prop = n.prop();
if (!prop)
return;
// assignment guaranteed to be outermost in token stream because assignments aren't expressions
if (prop == api.core.AssignmentProp) {
var lhs = n.args[0].flatten(api.core.TupleProp);
lhs.forEach(((e) => {
this.mode = "W";
// this trick works because AssignmentProp is not nested even though calls are
this.dispatch(e);
}));
this.mode = "R";
var rhs = n.args[1];
this.dispatch(rhs);
// TODO: need to wait for Michal in order to support lhs.length > 1
if (lhs.length == 1) {
var oneLHS = <Call>lhs[0];
// does the LHS require a rewrite?
var accessor = this.acc.getAccessorName(oneLHS, "W");
if (accessor != null) {
var writer = new TokenWriter();
var tokens = this.lastExprHolder.tokens;
if (oneLHS.referencedRecordField()) {
var oneLHStok = tokens.filter(t => t == oneLHS.propRef)[0]
var oneLHSidx = tokens.indexOf(oneLHStok);
tokens.slice(0, oneLHSidx).forEach(t => t.writeTo(writer));
} else {
this.targetLibrary.writeRef(writer);
}
writer.op("→").id(accessor).op("(");
var assign = tokens.filter(t => t.getOperator() == ":=")[0]
var idx = tokens.indexOf(assign);
tokens.slice(idx + 1, tokens.length).forEach(t => t.writeTo(writer));
writer.op(")");
var newExprHolder = Parser.parseExprHolder(writer.finalize());
// replacement OK here because we're at top level (otherwise, need to splice)
this.lastExprHolder.tokens = newExprHolder.tokens;
}
}
} else if (n.referencedData() || n.referencedRecordField()) {
// remember what was set up one level in AST (by Call/Assignment)
var mode = this.mode;
this.mode = "R";
n.args.forEach(e => this.dispatch(e));
// read of global or field
// do rewriting on Call via PropertyRef of Call, which is a Token
var accessor = this.acc.getAccessorName(n, mode);
if (accessor != null && mode != "W") {
n.propRef.data = accessor;
if (n.referencedData()) {
this.rewriteThingRef(<ThingRef>n.args[0]);
} else {
// use extension syntax because record will be first arg to accessor (no library reference needed)
n.propRef.prop = null;
}
}
} else if (prop instanceof RecordDef) {
if (this.mode == "E") {
var accessor = this.acc.getAccessorName(n, "E");
if (accessor != null) {
var tokens = this.lastExprHolder.tokens;
var leftmost = getLeftmost(n);
var leftIdx = tokens.indexOf(leftmost);
var rightIdx = tokens.indexOf(n.propRef);
// create the library thingref
var writer = new TokenWriter();
this.targetLibrary.writeRef(writer).op("→").id(accessor);
var newExprHolder = Parser.parseExprHolder(writer.finalize());
tokens.spliceArr(leftIdx, rightIdx - leftIdx + 1, newExprHolder.tokens);
}
}
} else if (prop && (prop.forwardsTo() instanceof Action || prop instanceof ExtensionProperty)) {
this.mode = "R";
n.args.forEach(e => this.dispatch(e));
var callee = <Action>prop.forwardsTo();
if (callee && this.libActions.indexOf(callee) >= 0) {
this.rewriteThingRef(<ThingRef>n.args[0]);
} else {
callee = (<ExtensionProperty>prop).shortcutTo;
if (this.libActions.indexOf(callee) >= 0) {
// TODO: finish this case
}
}
} else {
n.args.forEach(e => { this.mode = "R"; this.dispatch(e); });
var accessor = this.acc.getAccessorName(n, "");
if (accessor != null && prop) {
n.propRef.data = accessor;
n.propRef.prop = null;
var kind = prop.parentKind;
if (kind instanceof RecordDefKind) {
var rd = (<RecordDefKind>kind).getRecord();
if (rd) {
var subcall = <Call>n.args[0];
var leftmost = getLeftmost(subcall);
// TODO: are we sure about thingref check???
if (leftmost instanceof ThingRef) {
// replace the subcall by thingref
// identify the token subsequence associated with subcall
var tokens = this.lastExprHolder.tokens;
var leftIdx = tokens.indexOf(leftmost);
var rightIdx = tokens.indexOf(subcall.propRef);
// create the library thingref
var writer = new TokenWriter();
this.targetLibrary.writeRef(writer);
var newExprHolder = Parser.parseExprHolder(writer.finalize());
// replace the subcall sequence with the library thingref
tokens.spliceArr(leftIdx, rightIdx - leftIdx + 1, newExprHolder.tokens);
} else {
// it's a local variable or parameter,
}
}
} else {
// nothing to do here
}
}
}
return null;
}
performRewrite(n: AstNode) {
this.dispatch(n);
}
}
// given a record or record field R, find all the other user defined types reachable from R
class UserDefinedTypeFinder
extends NodeVisitor {
private recurse: boolean = false;
decls: Decl[] = [];
visitGlobalDef(n: GlobalDef) {
this.visitKind(n.getKind(), this.recurse);
}
visitLocalDef(n: LocalDef) {
this.visitKind(n.getKind(), this.recurse);
}
visitRecordDef(r: RecordDef) {
if (this.decls.indexOf(r) < 0) {
this.decls.push(r);
if (this.recurse) {
r.values.fields().concat(r.keys.fields()).forEach((f) => {
this.visitRecordField(f);
});
}
}
}
visitRecordField(n: RecordField) {
this.visitKind(n.dataKind, this.recurse);
}
public visitKind(k: Kind, recurse: boolean) {
this.recurse = recurse;
if (k.isUserDefined()) {
if (k.getRecord())
this.visitRecordDef(k.getRecord());
else if (k instanceof UserActionKind) {
var act = (<UserActionKind>k).userAction;
if (this.decls.indexOf(act) < 0) {
this.decls.push(act);
if (recurse) {
act.getParameters().forEach(p => this.visitKind(p.getKind(), recurse));
}
}
} else if (k instanceof LibraryRefAbstractKind) {
// we are taking a dependence on a library
var lib = (<LibraryRefAbstractKind>k).parentLibrary();
if (this.decls.indexOf(lib) < 0) {
this.decls.push(lib);
}
} else if (k instanceof ParametricKind) {
if (recurse) {
var pk = <ParametricKind>k;
var i = 0;
while (i < pk.getParameterCount()) {
this.visitKind(pk.getParameter(i), recurse);
i++;
}
}
} else if (k instanceof ActionKind) {
if (recurse) {
var params = (<ActionKind>k).getInParameters().concat((<ActionKind>k).getInParameters());
params.forEach(p => this.visitKind(p.getKind(), recurse));
}
} else {
// Question: do we need to recurse over SimpleProperties and their param/return types
// Answer: depends if we can reach a userdefined type from a Simple Property, which I
// don't think is possible.
}
}
}
public traverse(d: AstNode, recurse: boolean) {
this.recurse = recurse;
this.dispatch(d);
}
}
export class DeclAndDepsManager {
private cache: DeclAndDeps[] = [];
private last: DeclAndDeps = null;
public resetCache() {
this.cache = [];
this.last = null;
}
public invalidate() {
this.cache.forEach(dd => dd.reset());
}
private getFromCache(d: Decl): DeclAndDeps {
if (this.last != null && this.last.decl == d) {
return this.last;
} else {
var filter = this.cache.filter(dd => dd.decl == d);
if (filter.length > 0) {
this.last = filter[0];
return this.last;
} else
return null;
}
}
public remove(dd: DeclAndDeps) {
this.last = null;
var idx = this.cache.indexOf(dd);
if (idx >= 0) {
this.cache.splice(idx);
}
}
public getter(decl: Decl): DeclAndDeps {
Util.assert(decl != null);
var res = this.getFromCache(decl);
if (res == null) {
res = new DeclAndDeps(this);
res.decl = decl;
this.cache.push(res);
}
return res;
}
public getAll(): DeclAndDeps[] {
return this.cache;
}
}
// wrap decl in the AST to keep track of its dependences
export class DeclAndDeps {
private manager: DeclAndDepsManager;
constructor(m: DeclAndDepsManager) {
this.manager = m;
}
public decl: Decl = null;
public serialized: string; // remember the decl for restoring later
public direct: DirectAccessFinder = null;
// which actions are called by decl?
private actions: DeclAndDeps[] = null;
// types directly referenced by decl
private otherDecls: DeclAndDeps[] = [];
// decls for which this action actually reads/write a field or property
private accessedTypes: DeclAndDeps[] = [];
private accessedGlobals: DeclAndDeps[] = [];
private transitiveClosure: DeclAndDeps[] = null;
public reset() {
this.actions = null;
this.serialized = null;
this.otherDecls = [];
this.direct = null;
this.accessedGlobals = [];
this.accessedTypes = [];
this.transitiveClosure = [];
}
public getActions(): DeclAndDeps[] {
this.fillDeclDependencies();
return this.actions;
}
public getOthers(): DeclAndDeps[] {
this.fillDeclDependencies();
return this.otherDecls;
}
public getAllDirectAccesses(): DeclAndDeps[] {
this.fillDeclDependencies();
if (this.decl instanceof Action)
return this.accessedTypes.concat(this.accessedGlobals).concat(this.getActions());
else
return this.getOthers();
}
public getTransitiveClosure(): DeclAndDeps[] {
this.fillDeclDependencies();
if (!this.transitiveClosure) {
this.transitiveClosure = [];
DeclAndDeps.computeClosure([this], [], this.transitiveClosure);
}
return this.transitiveClosure;
}
// for user selection
private include: boolean = false;
public setInclude(b: boolean) { this.include = b; }
public getInclude(): boolean { return this.include; }
public count: number;
// for ranking the declarations in order of suitability for library
public numberDirectDeclsToMove: number;
static rateTargetAgainstDecls(pending: DeclAndDeps[], tgt: DeclAndDeps) {
tgt.numberDirectDeclsToMove = 0;
tgt.getAllDirectAccesses().filter(dd => !(dd.decl instanceof LibraryRef)).forEach((t) => {
if (pending.indexOf(t) >= 0) tgt.numberDirectDeclsToMove++;
});
}
static compareSize(d1: DeclAndDeps, d2: DeclAndDeps): number {
var len1 = d1.getTransitiveClosure().length;
var len2 = d2.getTransitiveClosure().length;
return len1 - len2;
}
private fillDeclDependencies() {
if (this.actions != null)
return;
var directDecls: Decl[] = [];
function add(decl: Decl) {
if (directDecls.indexOf(decl) < 0) directDecls.push(decl);
}
function processDeclDirect(decl: Decl) {
// local defs aren't moveable decls
if (!(decl instanceof LocalDef))
add(decl);
// now, find the types directly accessed
var finder = new UserDefinedTypeFinder();
finder.traverse(decl, false);
finder.decls.forEach(d => add(d));
}
var finder = new DirectAccessFinder();
finder.traverse(this.decl);
this.direct = finder;
finder.referencedLibraries.forEach(l => add(l));
if (this.decl instanceof Action) {
this.actions = finder.calledActions.map(a => this.manager.getter(a));
finder.readGlobals.concat(finder.writtenGlobals).forEach(g => {
var ng = this.manager.getter(g);
if (this.accessedGlobals.indexOf(ng) < 0) this.accessedGlobals.push(ng);
processDeclDirect(g);
});
finder.inParams.concat(finder.outParams).forEach(processDeclDirect);
finder.readProps.concat(finder.readFields).concat(finder.writtenFields).forEach(p => {
var rd = p.parentKind.getRecord();
if (rd) {
var newOne = this.manager.getter(rd);
if (this.accessedTypes.indexOf(newOne) < 0) this.accessedTypes.push(newOne);
}
});
} else {
this.actions = [];
finder.referencedRecords.forEach(processDeclDirect);
}
this.otherDecls = directDecls.map(a => this.manager.getter(a));
}
public static computeClosure(roots: DeclAndDeps[], alreadyIn: DeclAndDeps[], newOnes: DeclAndDeps[]) {
var add = (dd2: DeclAndDeps) => {
if (roots.indexOf(dd2) < 0 && alreadyIn.indexOf(dd2) < 0 && newOnes.indexOf(dd2) < 0) {
newOnes.push(dd2);
return true;
} else {
return false;
}
}
var recurseNotAction = (r: DeclAndDeps) => {
if (add(r)) {
var finder = new UserDefinedTypeFinder();
finder.traverse(r.decl, true);
finder.decls.forEach(d => add(r.manager.getter(d)));
}
}
roots.forEach(dd => {
if (dd.decl instanceof Action) {
var closure = new CallGraphClosure(dd);
closure.allActions.forEach((a) => {
if (add(a)) a.getOthers().forEach(recurseNotAction);
});
} else {
recurseNotAction(dd);
}
dd.getOthers().forEach(recurseNotAction);
});
}
}
class CallGraphClosure {
constructor(action: DeclAndDeps) {
var wl: DeclAndDeps[] = action.getActions().map(x=> x);
while (wl.length > 0) {
var a = wl.pop();
this.allActions.push(a);
a.getActions().forEach(b => {
if (this.allActions.indexOf(b) < 0 && wl.indexOf(b) < 0) wl.push(b)
});
}
}
public allActions: DeclAndDeps[] = [];
}
function getLeftmost(e: Expr): Expr {
if (e instanceof Call) {
return getLeftmost((<Call>e).args[0]);
} else {
return e;
}
}
// the stuff below here should be generically useful for lots of other
// refactoring and analyses that need to find out what is referenced by
// a declaration (action, global, record, etc.)
// given an action A, find all the other top-level entities referenced directly by A
export class DirectAccessFinder
extends NodeVisitor {
referencedLibraries: LibraryRef[] = [];
calledActions: Action[] = [];
inParams: LocalDef[] = [];
outParams: LocalDef[] = [];
readGlobals: Decl[] = [];
writtenGlobals: Decl[] = [];
readFields: IProperty[] = [];
writtenFields: IProperty[] = [];
readProps: IProperty[] = [];
readPropsWhat: Expr[] = [];
referencedRecords: RecordDef[] = [];
asIEnumerable: RecordDef[] = [];
addDecl(l: Decl, lst: Decl[]) {
if (lst.indexOf(l) < 0) lst.push(l);
}
addField(l: IProperty, lst: IProperty[]) {
if (lst.indexOf(l) < 0) lst.push(l);
}
addProp(l: IProperty, e: Expr) {
if (this.readProps.indexOf(l) < 0) {
this.readProps.push(l);
this.readPropsWhat.push(e);
}
}
private getGlobal(e: AstNode) {
if (e instanceof Call) {
var prop = (<Call>e).prop();
if (prop instanceof GlobalDef || prop instanceof RecordDef)
return <PropertyDecl><any>prop;
}
return null;
}
visitAstNode(n: AstNode) {
this.visitChildren(n);
return null;
}
visitAction(n: Action) {
n.getInParameters().forEach((p) => this.addDecl(p.local, this.inParams));
n.getOutParameters().forEach((p) => this.addDecl(p.local, this.outParams));
super.visitAction(n);
}
visitGlobalDef(n: GlobalDef) {
var finder = new UserDefinedTypeFinder();
finder.traverse(n, false);
finder.decls.forEach(t => this.addDecl(t, this.referencedRecords));
}
visitRecordDef(r: RecordDef) {
var finder = new UserDefinedTypeFinder();
r.values.fields().concat(r.keys.fields()).forEach((f) => {
finder.traverse(f, false);
});
finder.decls.forEach(t => this.addDecl(t, this.referencedRecords));
}
visitForeach(n: Foreach) {
if (n.collection.parsed) {
this.mode = "E";
this.dispatch(n.collection.parsed);
}
[<AstNode> n.conditions, n.body].forEach(c => c.accept(this));
}
private mode: string = "";
visitCall(n: Call) {
var prop = n.prop();
if (prop == api.core.AssignmentProp) {
n.args[0].flatten(api.core.TupleProp).forEach((e) => {
this.mode = "W";
var g = this.getGlobal(e);
if (g && this.writtenGlobals.indexOf(g) < 0)
this.addDecl(g, this.writtenGlobals);
else
this.dispatch(e);
});
this.mode = "R";
this.dispatch(n.args[1]);
} else if (n.referencedRecordField() || n.referencedData()) {
if (n.referencedRecordField()) {
this.addField(n.referencedRecordField().asProperty(), this.mode == "R" ? this.readFields : this.writtenFields);
} else {
this.addDecl(n.referencedData(), this.mode == "R" ? this.readGlobals : this.writtenGlobals);
}
} else if (prop instanceof RecordDef) {
// direct use of (built-in property of) a table/index
if (this.mode == "E") {
this.addDecl(<RecordDef>prop, this.asIEnumerable);
}
this.addDecl(<RecordDef>prop, this.readGlobals);
} else if (prop.forwardsTo() instanceof Action || prop instanceof ExtensionProperty) {
var act = prop.forwardsTo() ? <Action> prop.forwardsTo() : (<ExtensionProperty>prop).shortcutTo;
var lib = act.parentLibrary();
if (lib && !lib.isThis()) {
if (this.referencedLibraries.indexOf(lib) < 0)
this.referencedLibraries.push(lib);
} else {
if (this.calledActions.indexOf(act) < 0)
this.calledActions.push(act);
}
} else if (prop.parentKind.getRecord()) {
this.addProp(prop, getLeftmost(n));
} else if (prop.parentKind instanceof UserActionKind) {
// we have a run call (look for dependence on a callback)
var act = (<UserActionKind>(prop.parentKind)).userAction
if (this.calledActions.indexOf(act) < 0)
this.calledActions.push(act);
}
// recurse and collect from arguments
if (prop != api.core.AssignmentProp) {
n.args.forEach(e => { this.mode = "R"; this.dispatch(e) });
}
}
visitExprHolder(n: ExprHolder) {
if (n.parsed) {
this.mode = "R";
this.dispatch(n.parsed);
}
return null;
}
traverse(node: AstNode) {
this.dispatch(node);
}
}
}