TouchDevelop/ast/ast.ts

5441 строка
176 KiB
TypeScript

///<reference path='refs.ts'/>
module TDev {
//declare class RenderState;
export interface IndexedString
{
idx:number;
s:string;
}
}
module TDev.AST {
export var api = TDev.api;
var currentNodeId = 1;
export var proMode = false;
export var blockMode = false;
export var legacyMode = true;
export var propRenames:StringMap<string> = {};
export var crossKindRenames:StringMap<string> = {};
export function reset()
{
currentNodeId = 1;
api.cleanup()
}
export function stableReset(entropy:string)
{
reset()
var r = new Random.RC4()
r.addEntropy(Util.stringToUint8Array(Util.toUTF8(entropy)))
uniqueAstId = (len) => r.uniqueId(len);
}
export class AstNode
{
public nodeId:number = currentNodeId++;
public stableId:string;
public errorIsOk:boolean;
public _kind:Kind;
public _error:string = null;
public isInvisible:boolean;
public annotations:RT.AstAnnotation[];
private isAstNode() { return true; }
public isPlaceholder() { return false; }
public hasValue() { return false; }
public isStmt() { return false; }
public getKind():Kind { return this._kind; }
public nodeType():string { return null; }
public children():AstNode[] { return []; }
public errorOf(e:ExprHolder)
{
if (this._error != null) return this._error;
else return e.getError();
}
private shouldCopy(propName:string)
{
return propName != "kind" && propName != "type";
}
public getError() { return this._error; }
public setError(m:string) { this._error = m; }
public clearError() { this._error = null; }
public writeTo(tw:TokenWriter) : void
{
Util.oops("writeTo not implemented");
}
public serialize() : string
{
var tw = TokenWriter.forStorage();
this.writeTo(tw);
return tw.finalize();
}
public accept(v:NodeVisitor):any
{
Util.oops("accept not implemented");
return null;
}
static freshNameCore(n:string, nameExists:(s:string) => boolean)
{
if (n == "") n = "x";
if (!nameExists(n)) return n;
if (n == "i") {
var better = ["j", "k", "l", "m", "n"].filter((n) => !nameExists(n))[0];
if (better) return better;
}
var prefix = TDev.RT.String_.trim_end(n, "0123456789");
var k = parseFloat(n.substr(prefix.length)) || 2;
while (nameExists(prefix + k)) k++;
return prefix + k;
}
}
export interface IStableNameEntry
{
getName():string;
initStableName(refresh:boolean);
getStableName():string;
setStableName(s:any);
deriveStableName(st:Stmt,ix:number);
}
export var uniqueAstId : (len:number) => string = (len) => Random.uniqueId(len);
// -------------------------------------------------------------------------------------------------------
// Statements
// -------------------------------------------------------------------------------------------------------
export class Stmt
extends AstNode
implements IStableNameEntry
{
private _name: string;
private stableName: string;
private stableVersions : string[];
public tutorialWarning: string;
public isUnreachable:boolean;
public _hint:string;
public _compilerBreakLabel:any;
public _compilerContinueLabel:any;
constructor() {
super()
}
public isStmt() { return true; }
public isJump() { return false; }
public isExecutableStmt() { return false; }
public primaryBody():Block { return null; }
public calcNode():ExprHolder { return null; }
public children():AstNode[] { return this.calcNode() != null ? [this.calcNode()] : []; }
public getError()
{
var c = this.calcNode();
if (c != null)
return this.errorOf(c);
else
return this._error;
}
public getHint()
{
var r:string = null
if (this.calcNode())
r = this.calcNode().hint
if (!r) return this._hint
if (this._hint) return r + "\n" + this._hint
return r
}
public addHint(msg:string)
{
if (!this._hint) this._hint = msg
else this._hint += "\n" + msg
}
public clearError()
{
this._error = null
this._hint = null
this.isUnreachable = false
}
public debuggerRenderContext: {
isOnStackTrace ?: boolean;
isBreakPoint?: boolean;
isCurrentExecPoint?: boolean;
} = {};
public renderedAs:HTMLElement;
public renderedAsId:string;
public renderState:any; //RenderState;
public loopVariable():LocalDef { return null }
public setupForEdit() { }
// if this instanceof Block, these are attached at the beginning of the body, and otherwise after the the current statement
public diffStmts:Stmt[];
public diffAltStmt:Stmt;
public diffStatus:number; // -1, 0, +1, -N for moved old statements
public diffFeatures:any;
public stepState:any;
public nextStmt():Stmt
{
if (this.parent instanceof Block) {
var stmts = (<Block>this.parent).stmts
var idx = stmts.indexOf(this)
if (idx >= 0)
return stmts[idx + 1]
}
return null
}
public isCommentedOut()
{
return this.parent && (this.parent.isTopCommentedOut() || this.parent.isCommentedOut())
}
public isTopCommentedOut() { return false }
public parent:Stmt;
public parentBlock():AST.Block { return this.parent instanceof AST.Block ? <AST.Block> this.parent : null; }
public isLoneChild() { return this.parentBlock().stmts.length == 1; }
public isLastChild() { return this.parentBlock().stmts.peek() == this; }
public isFirstChild() { return this.parentBlock().stmts[0] == this; }
public allowSimplify() { return false }
public onDelete() { }
public isFirstNonComment() { return this.parentBlock().firstNonComment() == this; }
public isLastNonComment() { return this.parentBlock().lastNonComment() == this; }
public getName(): string { return this._name; }
public setName(s: string) {
this._name = s;
}
public getStableName() : string {
return this.stableName;
}
// input should be a string or an array of strings
public setStableName(s : any) {
if(s instanceof Array) {
this.stableName = s ? s.pop() : undefined;
// this.stableVersions = s;
this.stableVersions = undefined;
} else {
this.stableName = s;
this.stableVersions = undefined;
}
//console.log(">>>>> setStableName: "+this+" -> "+this.stableName+", ["+this.stableVersions+"]");
//if(this.stableName=="main") console.log(">>>>> "+(Script ? Script.serialize() : "undef"));
}
public initStableName(refresh = false) {
if(!this.stableName || refresh) {
this.setStableName(uniqueAstId(16));
}
}
public deriveStableName(s:Stmt, ix:number) {
this.setStableName(s.getStableName()+"$"+ix);
}
public writeId(tw:TokenWriter)
{
if(this.stableVersions) {
this.stableVersions.forEach(x => tw.uniqueId(x));
}
tw.uniqueId(this.getStableName());
}
public writeIdOpt(tw:TokenWriter)
{
this.writeId(tw);
}
public parentApp():App
{
var pa = this.parentAction()
if (pa) return pa.parent;
return null;
}
public parentAction():Action
{
for (var p = this.parent; p && !(p instanceof ActionHeader); p = p.parent)
;
if (p) return (<ActionHeader>p).action;
else return null;
}
public helpTopic()
{
return this.nodeType();
}
public forSearch() { return ""; }
public notifyChange()
{
if (this.parent) this.parent.notifyChange();
}
public innerBlocks()
{
return <Block[]>this.children().filter(c => c instanceof Block)
}
public isDescendant(stmt: Stmt) : boolean {
while (!!stmt) {
stmt = stmt.parent;
if (stmt == this) return true;
}
return false;
}
public matches(d:AstNode) {
var n = this.calcNode()
if (n)
for (var i = 0; i < n.tokens.length; ++i)
if (n.tokens[i].matches(d)) return true;
return false
}
public docText():string { return null }
}
export class Comment
extends Stmt
{
public text:string;
public mdDecl:AST.Decl;
constructor() {
super()
}
public nodeType() { return "comment"; }
public accept(v:NodeVisitor) { return v.visitComment(this); }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.comment(this.text);
}
public forSearch() { return this.text.toLowerCase(); }
public docText() { return this.text }
}
export class FieldComment
extends Comment
{
constructor (private field: AST.RecordField) {
super()
}
public notifyChange() {
this.field.description =
this.parent.children().map(c => (<Comment> c).text)
.join("\n");
if (!this.field.description)
(<CodeBlock> this.parent).setChildren([]);
super.notifyChange();
}
}
export class Block
extends Stmt
{
public stmts:Stmt[];
constructor() {
super()
}
public nodeType() { return "block"; }
public children() { return this.stmts; }
public immutableReason = null;
public count() { return this.stmts.length; }
public newChild(s:Stmt) { s.parent = this; }
public setChild(i:number, s:Stmt)
{
this.stmts[i] = s;
this.newChild(s);
this.notifyChange();
}
public setChildren(s:Stmt[])
{
this.stmts = s;
this.stmts.forEach((q:Stmt) => this.newChild(q))
this.notifyChange();
}
public push(s:Stmt)
{
this.stmts.push(s);
this.newChild(s);
this.notifyChange();
}
public pushRange(s:Stmt[])
{
this.stmts.pushRange(s);
this.stmts.forEach((q:Stmt) => this.newChild(q))
this.notifyChange();
}
public writeTo(tw:TokenWriter)
{
tw.beginBlock();
this.stmts.forEach((s:Stmt) => { s.writeTo(tw); });
tw.endBlock();
}
public emptyStmt():Stmt { return Util.abstract() }
public allowEmpty() { return false; }
public allowAdding() { return true; }
public allowAddOf(n:Stmt) { return false }
public isBlockPlaceholder()
{
return this.stmts.length == 0 || (this.stmts.length == 1 && this.stmts[0].isPlaceholder());
}
public forEach(f:(s:Stmt)=>void):void { this.stmts.forEach(f) }
public map(f: (s: Stmt) => any): any[]{ return this.stmts.map(f); }
public firstNonComment(): Stmt {
return this.stmts.filter(stmt => stmt.nodeType() !== "comment")[0];
}
public lastNonComment(): Stmt {
return this.stmts.filter(stmt => stmt.nodeType() !== "comment").peek();
}
public stmtsWithDiffs(): Stmt[]
{
if (this.diffStmts || this.stmts.some(s => !!s.diffStmts)) {
var res:AST.Stmt[] = []
if (this.diffStmts) res.pushRange(this.diffStmts)
this.stmts.forEach(s => {
res.push(s)
if (s.diffStmts)
res.pushRange(s.diffStmts)
})
return res;
} else {
return this.stmts;
}
}
public forEachInnerBlock(f:(b:Block)=>void)
{
for (var i = 0; i < this.stmts.length; ++i) {
var ch = this.stmts[i].children()
for (var j = 0; j < ch.length; ++j) {
if (ch[j] instanceof Block)
f(<Block>ch[j])
}
}
}
}
export enum BlockFlags {
None = 0x0000,
IsPageRender = 0x0001,
IsPageInit = 0x0002,
}
export class CodeBlock
extends Block
{
constructor() {
super()
}
public flags:BlockFlags;
public emptyStmt() { return Parser.emptyExprStmt(); }
public accept(v:NodeVisitor) { return v.visitCodeBlock(this); }
public nodeType() { return "codeBlock"; }
public newlyWrittenLocals:LocalDef[];
public allowAddOf(n:Stmt) { return n && n.isExecutableStmt() }
public writeTo(tw:TokenWriter)
{
tw.beginBlock();
for (var i = 0; i < this.stmts.length; ++i) {
var s = this.stmts[i]
if (s instanceof If && isElseIf(this.stmts[i+1])) {
var si = <If>s;
si.writeCore(tw)
var numOpen = 1
tw.keyword("else").op("{")
while (true) {
if (!isElseIf(this.stmts[i+1]))
break;
i++;
si = <If>this.stmts[i];
si.writeCore(tw)
tw.keyword("else");
if (!si.rawElseBody.isBlockPlaceholder()) {
tw.node(si.rawElseBody);
break;
}
tw.op("{");
numOpen++;
}
while (numOpen-- > 0)
tw.op("}");
tw.nl();
} else {
s.writeTo(tw)
}
}
tw.endBlock();
}
}
export class ConditionBlock
extends Block
{
constructor() {
super()
}
public emptyStmt() { return Parser.emptyCondition(); }
public accept(v:NodeVisitor) { return v.visitConditionBlock(this); }
public nodeType() { return "conditionBlock"; }
public allowAddOf(n:Stmt) { return n instanceof ForeachClause }
public allowEmpty() { return true; }
}
export class ParameterBlock
extends Block
{
constructor() {
super()
this.stmts = [];
}
public allowEmpty() { return true; }
public emptyStmt(name? : string, kind? : Kind)
{
var isOut = (<ActionHeader>this.parent).outParameters == this;
var r = new ActionParameter(mkLocal((<AST.ActionHeader>this.parent).action.nameLocal(name || (isOut ? "r" : "p")), kind || api.core.Number));
r.parent = this;
return r;
}
public accept(v:NodeVisitor) { return v.visitParameterBlock(this); }
public allowAddOf(n:Stmt) { return n instanceof ActionParameter }
// no need to offload parameters
public nodeType() { return "parameterBlock"; }
public children()
{
if (this.parent && (<ActionHeader>this.parent).inParameters == this) {
var act = (<ActionHeader>this.parent).action
if (act && act.modelParameter)
return [<Stmt>act.modelParameter].concat(this.stmts)
}
return this.stmts
}
}
export class BindingBlock
extends Block
{
constructor() {
super()
this.stmts = [];
}
public allowEmpty() { return true; }
public emptyStmt():Stmt { return null; } // no can do
public accept(v:NodeVisitor) { return v.visitBindingBlock(this); }
public nodeType() { return "bindingBlock"; }
public allowAddOf(n:Stmt) { return n instanceof Binding }
}
export class ResolveBlock
extends Block
{
constructor() {
super()
this.stmts = [];
}
public allowEmpty() { return true; }
public emptyStmt():Stmt { return null; } // no can do
public accept(v:NodeVisitor) { return v.visitResolveBlock(this); }
public push(s:Stmt) { this.stmts.push(s); s.parent = this; }
public nodeType() { return "resolveBlock"; }
public allowAddOf(n:Stmt) { return n instanceof ResolveClause }
}
export class FieldBlock
extends Block
{
constructor() {
super()
this.stmts = [];
}
public allowEmpty() { return true; }
public isKeyBlock() { return this.parentDef.keys == this; }
public allowAddOf(n:Stmt) { return n instanceof RecordField }
public mkUniqName(basename: string) {
var names = this.parentDef.getFields().map((f) => f.getName());
return AstNode.freshNameCore(basename, (n) => names.indexOf(n) >= 0)
}
public mkField(basename:string, k:Kind)
{
var n = this.mkUniqName(basename);
var r = new RecordField(n, k, this.isKeyBlock());
return r;
}
public emptyStmt()
{
var basename = "f"
var k = api.core.String
if (this.isKeyBlock()) {
if (this.parentDef.recordType == RecordType.Table) {
var kinds = Script.getKinds().filter((k: Kind) =>
(k != this.parentDef.entryKind &&
k.isData && k.hasContext(KindContext.RowKey)) &&
(!this.parentDef.locallypersisted() || (<RecordEntryKind>k).getRecord().locallypersisted()) &&
(!this.parentDef.cloudEnabled || (<RecordEntryKind>k).getRecord().cloudEnabled));
if (kinds.length == 0) return null;
k = kinds[0];
basename = "l"
} else {
basename = "k"
}
}
return this.mkField(basename, k);
}
public accept(v:NodeVisitor) { return v.visitFieldBlock(this); }
public parentDef:RecordDef;
public notifyChange()
{
super.notifyChange();
this.parentDef.notifyChange();
}
public fields():RecordField[] { return <RecordField[]>this.stmts; }
public nodeType() { return "fieldBlock"; }
}
export class InlineActionBlock
extends Block
{
constructor() {
super()
this.stmts = [];
}
public allowAdding() { return !!(<InlineActions>this.parent).getOptionsParameter() }
public allowEmpty() { return true; }
public emptyStmt() { return OptionalParameter.mk(""); }
public accept(v:NodeVisitor) { return v.visitInlineActionBlock(this); }
public push(s:Stmt) { this.stmts.push(s); s.parent = this; }
public nodeType() { return "inlineActionBlock"; }
public allowAddOf(n:Stmt) { return n instanceof InlineAction }
}
export class Loop
extends Stmt
{
public body:CodeBlock;
public primaryBody() { return this.body; }
public isExecutableStmt() { return true; }
public loopVariable():LocalDef { return null }
}
export class For
extends Loop
{
public boundLocal:LocalDef;
public upperBound:ExprHolder;
public nodeType() { return "for"; }
public calcNode() { return this.upperBound; }
public children() { return [<AstNode> this.upperBound, this.body]; }
public accept(v:NodeVisitor) { return v.visitFor(this); }
public allowSimplify() { return true }
public loopVariable():LocalDef { return this.boundLocal }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("for").op("0").op("\u2264").id(this.boundLocal.getName()).op("<");
tw.node(this.upperBound).keyword("do").node(this.body);
}
private parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
p.skipOp("0");
p.skipOp("\u2264");
this.boundLocal = mkLocal(p.parseId("i"), api.core.Number);
p.skipOp("<");
this.upperBound = p.parseExpr();
p.skipKw("do");
this.body = p.parseBlock();
this.body.parent = this;
}
public forSearch() { return "for do " + this.upperBound.forSearch(); }
}
export class Foreach
extends Loop
{
public boundLocal:LocalDef;
public collection:ExprHolder;
// the block contains Where stmts only (at the moment; in future there might be OrderBy etc)
public conditions:ConditionBlock;
public nodeType() { return "foreach"; }
public calcNode() { return this.collection; }
public accept(v:NodeVisitor) { return v.visitForeach(this); }
public children() { return [<AstNode> this.collection, this.conditions, this.body]; }
public allowSimplify() { return true }
public loopVariable():LocalDef { return this.boundLocal }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("foreach").id(this.boundLocal.getName()).keyword("in").node(this.collection).nl();
this.conditions.stmts.forEach((c:Stmt) => { c.writeTo(tw) });
tw.keyword("do").node(this.body);
}
private parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
this.boundLocal = mkLocal(p.parseId("e"), api.core.Unknown);
p.skipKw("in");
this.collection = p.parseExpr();
var conds:Stmt[] = []
p.shiftLabel();
while (p.gotKw("where")) {
p.shift();
var wh = mkWhere(p.parseExpr());
wh.setStableName(p.consumeLabel());
conds.push(wh);
p.shiftLabel();
}
if (conds.every(s => s instanceof Where && (<Where>s).condition.getLiteral() === true))
conds = []
this.conditions = new ConditionBlock();
this.conditions.setChildren(conds);
this.conditions.parent = this;
p.skipKw("do");
this.body = p.parseBlock();
this.body.parent = this;
}
public forSearch() { return "foreach for each in do " + this.collection.forSearch(); }
}
export class While
extends Loop
{
public condition:ExprHolder;
public nodeType() { return "while"; }
public calcNode() { return this.condition; }
public accept(v:NodeVisitor) { return v.visitWhile(this); }
public children() { return [<AstNode> this.condition, this.body]; }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("while").node(this.condition);
tw.keyword("do").node(this.body);
}
private parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
this.condition = p.parseExpr();
p.skipKw("do");
this.body = p.parseBlock();
this.body.parent = this;
}
public forSearch() { return "while do " + this.condition.forSearch(); }
}
export interface IfBranch
{
condition: ExprHolder;
body: CodeBlock;
}
export class If
extends Stmt
{
public rawCondition:ExprHolder;
public rawThenBody:CodeBlock;
public rawElseBody:CodeBlock;
// this is filled out by every type check when not isElseIf
// the last branch has condition==null - it's the final else branch
public branches: IfBranch[];
public parentIf: If;
public isElseIf:boolean;
public displayElse:boolean = true;
public allowSimplify() { return !this.isElseIf }
public nodeType() { return this.isElseIf ? "elseIf" : "if"; }
public accept(v:NodeVisitor) { return this.isElseIf ? v.visitElseIf(this) : v.visitIf(this); }
constructor() {
super()
}
public calcNode() { return this.rawCondition; }
public children() { return [<AstNode> this.rawCondition, this.rawThenBody, this.rawElseBody]; }
public setElse(s:CodeBlock)
{
this.rawElseBody = s;
s.parent = this;
}
public isTopCommentedOut()
{
if (!this.isElseIf && this.rawElseBody.isBlockPlaceholder() && this.rawCondition.getLiteral() === false) {
var n = this.nextStmt()
if (n instanceof If && (<If>n).isElseIf)
return false
return true
}
return false
}
public primaryBody() { return this.rawThenBody; }
public isExecutableStmt() { return true; }
public writeTo(tw:TokenWriter)
{
if (this.isElseIf)
tw.keyword("else")
this.writeCore(tw)
if (!this.rawElseBody.isBlockPlaceholder())
tw.keyword("else").node(this.rawElseBody);
}
public writeCore(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("if").node(this.rawCondition).keyword("then").node(this.rawThenBody);
}
public parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
this.rawCondition = p.parseExpr();
p.skipKw("then");
this.rawThenBody = p.parseBlock();
this.rawThenBody.parent = this;
this.setElse(Parser.emptyBlock())
}
public forSearch() { return "if then " + this.rawCondition.forSearch(); }
public bodies():CodeBlock[]
{
if (this.isElseIf) return []
return this.branches.map(b => b.body)
}
}
export function isElseIf(s:Stmt) {
return s instanceof If && (<If>s).isElseIf;
}
export class Box
extends Stmt
{
public body:CodeBlock;
constructor() {
super()
}
private emptyExpr = Parser.emptyExpr();
public nodeType() { return "boxed"; }
public calcNode() { return this.emptyExpr; }
public primaryBody() { return this.body; }
public accept(v:NodeVisitor) { return v.visitBox(this); }
public children() { return [<AstNode>this.body]; }
public isExecutableStmt() { return true; }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("do").id("box").node(this.body);
}
private parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
this.body = p.parseBlock();
this.body.parent = this;
}
public forSearch() { return "boxed"; }
}
export class ExprStmt
extends Stmt
{
public expr:ExprHolder;
constructor() {
super()
}
public isVarDef() {
return this.expr.looksLikeVarDef ||
(!!this.expr.assignmentInfo() && this.expr.assignmentInfo().definedVars.length > 0);
}
public isPagePush() { return !!this.expr.assignmentInfo() && this.expr.assignmentInfo().isPagePush; }
public nodeType() { return "exprStmt"; }
public calcNode() { return this.expr; }
public isPlaceholder() { return this.expr.isPlaceholder(); }
public accept(v:NodeVisitor) { return v.visitExprStmt(this); }
public isExecutableStmt() { return true; }
public allowSimplify() { return true }
public isJump()
{
if (this.expr.parsed) {
var p = this.expr.parsed.getCalledProperty()
if (p == api.core.ReturnProp ||
p == api.core.BreakProp ||
p == api.core.ContinueProp)
return true
}
return false
}
public helpTopic() { return this.isVarDef() ? "var" : "commands"; }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
if (this.isPlaceholder()) tw.keyword("skip");
else tw.node(this.expr);
tw.op0(";").nl();
}
public forSearch() { return (this.isVarDef() ? "var " : "") + this.expr.forSearch(); }
public docText():string
{
var prop = this.expr.parsed ? this.expr.parsed.getCalledProperty() : null
if (!prop || prop.getName() != "docs render") return null
var c = <Call>this.expr.parsed
var arg0 = <GlobalDef>c.args[0].getCalledProperty()
if (arg0 instanceof GlobalDef) {
var url = arg0.url
}
if (!url) return null
if (arg0.getKind() == api.core.Picture) {
var h = c.args[1].getNumberLiteral() || 12
var cap = c.args[2].getStringLiteral() || ""
return "{pic:" + arg0.getName() + ":" + h + "x" + h + (cap ? ":" + cap : "") + "}"
}
if (arg0.getKind().getName() == "Document") {
var cap = c.args[1].getStringLiteral() || arg0.getName()
return "[" + cap + "] (" + url + ")"
}
return null
}
}
export class InlineActionBase
extends Stmt
{
public recordField:RecordField;
public _sort_key:number;
public getName() { return "" }
}
export class OptionalParameter
extends InlineActionBase
{
public _opt_name:string;
public expr:ExprHolder;
public getName()
{
return this.recordField ? this.recordField.getName() : this._opt_name;
}
constructor() {
super()
}
static mk(name:string)
{
var opt = new AST.OptionalParameter()
opt.expr = AST.Parser.emptyExpr()
opt._opt_name = name
return opt
}
public nodeType() { return "optionalParameter"; }
public calcNode() { return this.expr; }
public accept(v:NodeVisitor) { return v.visitOptionalParameter(this); }
public children() { return [<AstNode> this.expr]; }
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("where").id(this.getName()).op(":=").node(this.expr).op0(";").nl();
}
public parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
this._opt_name = p.parseId()
p.skipOp(":=")
this.expr = p.parseExpr();
p.skipOp(";")
}
public forSearch() { return "with " + this.getName() + " := " + this.expr.forSearch(); }
static optionsParameter(prop:IProperty) : PropertyParameter
{
if (!prop) return null
var last = prop.getParameters().peek()
if (!last) return null
if (!/\?$/.test(last.getName())) return null
var lk = last.getKind()
if (!lk.isUserDefined()) return null
var rec = lk.getRecord()
if (rec && rec.tableKind.getProperty("create"))
return last
return null
}
}
export class InlineAction
extends InlineActionBase
{
public name:LocalDef;
public inParameters:LocalDef[] = [];
public outParameters:LocalDef[] = [];
public body:CodeBlock;
public isImplicit:boolean;
public isOptional:boolean;
public children() { return [<AstNode> this.body] }
public nodeType() { return "inlineAction"; }
public helpTopic() { return "inlineActions"; }
public getName() { return this.recordField ? this.recordField.getName() : this.name.getName() }
constructor() { super() }
static mk(name:LocalDef)
{
var inl = new InlineAction();
inl.name = name;
inl.body = new CodeBlock();
inl.body.parent = inl;
inl.body.setChildren([inl.body.emptyStmt()]);
return inl;
}
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
var writeParms = (parms:LocalDef[]) =>
{
var first = true;
parms.forEach((l) => {
if (!first) tw.op0(",").space();
l.writeWithType(this.parentApp(), tw);
first = false;
});
}
tw.keyword("where");
if (this.isImplicit)
tw.op("implicit")
if (this.isOptional)
tw.op("optional")
tw.id(this.name.getName()).op0("(");
writeParms(this.inParameters);
tw.op0(")");
if (this.outParameters.length > 0) {
tw.space().keyword("returns").space().op0("(");
writeParms(this.outParameters);
tw.op0(")");
}
this.body.writeTo(tw);
}
public parseFrom(p:Parser)
{
this.setStableName(p.consumeLabel());
if (p.gotOp("implicit")) {
this.isImplicit = true
p.shift()
}
if (p.gotOp("optional")) {
this.isOptional = true
p.shift()
}
var hd = p.parseActionHeader();
this.name = mkLocal(hd.name, api.core.Unknown);
if (this.isImplicit)
this.name.isSynthetic = true;
this.inParameters = hd.inParameters.map((p) => p.local);
this.outParameters = hd.outParameters.map((p) => p.local);
this.body = p.parseBlock();
this.body.parent = this;
}
public forSearch() { return "where " + this.name.getName(); } // TODO add parameters?
public accept(v:NodeVisitor) { return v.visitInlineAction(this); }
// public isExecutableStmt() { return true; } ??
}
export class InlineActions
extends ExprStmt
{
public actions:InlineActionBlock = new InlineActionBlock();
constructor() {
super()
this.actions = new InlineActionBlock();
this.actions.parent = this;
}
public nodeType() { return "inlineActions"; }
public isPlaceholder() { return false; }
public accept(v:NodeVisitor) { return v.visitInlineActions(this); }
public children() { return [<AstNode> this.expr, this.actions]; }
public getOptionsParameter():PropertyParameter
{
var topCall = this.calcNode().topCall()
if (!topCall) return null
var optionsParm = AST.OptionalParameter.optionsParameter(topCall.getCalledProperty())
return optionsParm
}
public optionalParameters():InlineActionBase[]
{
return (<InlineActionBase[]>this.actions.stmts).filter(s => {
if (s instanceof InlineAction)
return (<InlineAction>s).isOptional
else if (s instanceof OptionalParameter)
return true
else
Util.oops("bad element " + s.nodeType())
})
}
public normalActions():InlineAction[]
{
return <InlineAction[]>this.actions.stmts.filter(s => s instanceof InlineAction && !(<InlineAction>s).isOptional)
}
public implicitAction():InlineAction
{
var last = this.actions.stmts.peek()
if (last instanceof InlineAction) {
var i = <InlineAction>last
return i.isImplicit ? i : null
}
return null
}
static isImplicitActionKind(k:Kind)
{
if (k instanceof ActionKind) {
var ak = <ActionKind>k
return ak.getInParameters().length == 0 && ak.getOutParameters().length == 0
}
return false
}
public writeTo(tw:TokenWriter)
{
super.writeTo(tw);
this.actions.forEach((a) => a.writeTo(tw));
}
public sortOptionals()
{
var opt0 = this.optionalParameters()[0]
if (!opt0 || !opt0.recordField) return
var lst = opt0.recordField.def().values.stmts
var problem = false
this.actions.forEach((iab:InlineActionBase) => {
if (iab.recordField) {
iab._sort_key = lst.indexOf(iab.recordField)
if (iab._sort_key < 0) problem = true
} else if (iab instanceof InlineAction) {
iab._sort_key = (<InlineAction>iab).isImplicit ? 1.1e10 : 1e10;
} else {
problem = true
}
})
if (problem) return
this.actions.stmts.sort((a:InlineActionBase, b:InlineActionBase) => a._sort_key - b._sort_key)
this.actions.notifyChange()
}
static implicitActionParameter(prop:IProperty) : PropertyParameter
{
if (!prop) return null
var parms = prop.getParameters()
if (OptionalParameter.optionsParameter(prop)) parms.pop()
var last = parms.peek()
if (!last) return null
if (last.getName() == "body" && InlineActions.isImplicitActionKind(last.getKind()))
return last
return null
}
}
export class ForeachClause
extends Stmt
{
constructor() {
super()
}
}
export class Where
extends ForeachClause
{
public condition:ExprHolder;
constructor() {
super()
}
public nodeType() { return "where"; }
public calcNode() { return this.condition; }
public accept(v:NodeVisitor) { return v.visitWhere(this); }
public isPlaceholder()
{
return this.condition.isPlaceholder() ||
(this.condition.tokens.length == 1 && this.condition.tokens[0].getText() == "true");
}
public writeTo(tw:TokenWriter)
{
this.writeIdOpt(tw);
tw.keyword("where").node(this.condition).nl();
}
public forSearch() { return "where " + this.condition.forSearch(); }
}
// pseudo-statements - they can be selected just like statements in the code editor
export class ActionParameter
extends Stmt
{
// We're using the same tricks as for the [RecordField] to turn this
// into an editable statement.
private exprHolder = new AST.ExprHolder();
constructor(public local:LocalDef) {
super();
this.setupForEdit()
}
public setupForEdit() {
var name = new FieldName();
name.data = this.getName();
this.exprHolder.tokens = [ <AST.Token> name ].concat(propertyRefsForKind(this.local.getKind()));
this.exprHolder.parsed = new AST.Literal(); // placeholder
this.exprHolder.locals = [];
}
public calcNode() {
return this.exprHolder;
}
public notifyChange() {
// See [RecordField] for comments. In essence, we're reflecting the
// changes performed via the editor onto our [LocalDef]. Further
// calls to [getName] and [getKind] will fetch the value from the
// [LocalDef].
var toks = this.exprHolder.tokens;
if (toks.length == 0 && this.parent) {
var pb = <ParameterBlock> this.parent;
var ch = pb.children().filter(x => x != this);
pb.setChildren(ch);
} else if (toks[0] && toks[0] instanceof FieldName) {
// Propagate the new name, if any.
var newName = (<FieldName>toks[0]).data;
if (this.getName() != newName) {
var uniqName = this.parentAction().nameLocal(newName);
this.local.rename(uniqName);
(<AST.FieldName> toks[0]).data = uniqName;
}
// Propagate the kind, if any.
if (toks.length > 1) {
var k = this.exprHolder.getKind();
if (isProperKind(k) && k != this.local.getKind()) {
this.local.setKind(k);
TypeChecker.tcApp(Script);
}
}
}
if (toks.length > 1 && toks[1] instanceof AST.PropertyRef)
(<AST.PropertyRef>toks[1]).skipArrow = true;
super.notifyChange();
}
public getName() { return this.local.getName(); }
public getKind() { return this.local.getKind(); }
public nodeType() { return "actionParameter"; }
public accept(v:NodeVisitor) { return v.visitActionParameter(this); }
public theAction() { return (<AST.ActionHeader>this.parent.parent).action; }
public forSearch() { return this.getName() + " " + this.getKind().toString(); }
public writeTo(tw:TokenWriter)
{
tw.keyword("var");
this.local.writeWithType(this.parentApp(), tw);
tw.op0(";").nl();
}
public matches(d:AstNode)
{
return this.getKind() && this.getKind().matches(d)
}
}
export class ActionHeader
extends Stmt
{
public inParameters:ParameterBlock = new ParameterBlock();
public outParameters:ParameterBlock = new ParameterBlock();
constructor(public action:Action) {
super()
this.inParameters.parent = this;
this.outParameters.parent = this;
}
public getName() { return this.action.getName(); }
public nodeType() { return "actionHeader"; }
public children() {
return [<AST.Stmt>this.inParameters, this.outParameters, this.action.body];
}
public primaryBody() { return this.action.body; }
public accept(v:NodeVisitor) { return v.visitActionHeader(this); }
public writeTo(tw:TokenWriter)
{
this.action.writeHeader(tw)
}
public notifyChange()
{
super.notifyChange();
this.action.notifyChange();
}
}
export interface ProfilingAstNodeData {
count: number; // number of node executions
duration: number; // total duration of node executions
}
export interface DebuggingAstNodeInfo {
alwaysTrue?: boolean;
alwaysFalse?: boolean;
visited?: boolean;
critical?: number;
max?: { critical: number };
errorMessage?: string;
// other stuff may go here
}
// -------------------------------------------------------------------------------------------------------
// Declarations
// -------------------------------------------------------------------------------------------------------
export class Decl
extends Stmt
{
public _wasTypechecked = false;
public deleted:boolean;
public parent:App;
public wasAutoNamed = false;
public visitorState:any;
public diffStatus:number;
public diffAltDecl:Decl;
public isExternal:boolean;
constructor() {
super()
}
public getCoreName() { return this.getName(); }
public getIconArtId(): string { return null; }
public hasErrors() { return !!this.getError(); }
public hasWarnings() { return false }
public getDescription(skip?:boolean) { return ""; }
public isBrowsable() { return true; }
public propertyForSearch():IProperty { return null; }
public toString() { return this.getName(); }
public helpTopic(): string { return null; }
public usageKey():string { return null; }
public freshlyCreated() { }
public getDefinedKind():Kind { return null }
public matches(d:AstNode)
{
if (this.getKind() && this.getKind().matches(d))
return true
return this == d
}
public cachedSerialized:IndexedString;
public canRename() { return true; }
public notifyChange()
{
super.notifyChange();
if (this.cachedSerialized) {
var idx = this.cachedSerialized.idx;
if (idx > 0)
this.cachedSerialized.idx = -idx;
}
}
public nodeType() { return "decl"; }
public accept(v:NodeVisitor) { return v.visitDecl(this); }
}
export class PropertyDecl
extends Decl
{
constructor() {
super()
this._usage = new TokenUsage(this);
}
public getSignature():string { return Property.getSignatureCore(<any>this); }
public canCacheSearch() { return false; }
public thingSetKindName() { return null; }
public getInfixPriority() { return 0; }
public forwardsTo() { return this; }
public forwardsToStmt():Stmt { return null; }
public propertyForSearch():IProperty { return <IProperty> (<any> this); }
public parentKind:Kind;
public getCapability() { return PlatformCapability.None }
public getExplicitCapability() { return this.getCapability() }
public isBeta() { return false }
public showIntelliButton() { return true }
public getImports() : IImport[] { return undefined; }
public shouldPauseInterperter() { return false; }
public isImplemented() { return true; }
public isImplementedAnywhere() { return this.isImplemented() }
public isSupported() { return true; }
public getSpecialApply():string { return null; }
public isBrowsable() { return true; }
private _usage:TokenUsage;
public getUsage() { return this._usage; }
public lastMatchScore:number;
public useFullName:boolean;
public helpTopic() { return this.nodeType(); }
public getFlags() { return PropertyFlags.None }
public getArrow()
{
return "\u200A";
}
public runtimeName()
{
return Api.runtimeName(this.getName())
}
// tracing
public needsSpecialTracing() { return false; }
public needsTracing() { return false; }
public needsTimestamping() { return false; }
public hasPauseContinue() { return false; }
static mkPPext(s:IProperty, name:string, k:Kind)
{
var pp = new PropertyParameter(name, k);
pp.parentProperty = s;
return pp;
}
public mkPP(name:string, k:Kind) { return PropertyDecl.mkPPext(<any>this, name, k); }
public getParameters():PropertyParameter[] { return [this.mkPP("_this_", this.parentKind)]; }
public getResult():PropertyParameter { return this.mkPP(this.getName(), this.getKind()); }
public getNamespace()
{
var pkn = this.thingSetKindName()
if (!pkn) return null
var k = api.getKind(pkn)
if (!k) return null
return k.shortName() + "\u200A";
}
public setName(s:string)
{
super.setName(s);
if (this.parent) {
this.parent.notifyChangeAll();
}
}
/*
getCategory():PropertyCategory;
*/
}
export var artSymbol = "\u273f";
export var dataSymbol = "\u25f3";
export var codeSymbol = "\u25b7";
export class GlobalDef
extends PropertyDecl
implements IProperty
{
public readonly:boolean = false;
public comment:string = "";
public url:string = "";
public isResource:boolean = false;
public isTransient: boolean = true;
public cloudEnabled: boolean = false;
public debuggingData: { critical: number; max: { critical: number }; };
public usageLevel:number;
constructor() {
super()
}
public children() : AstNode[] { return []; }
public nodeType() { return "globalDef"; }
public helpTopic() { return this.thingSetKindName() }
public accept(v:NodeVisitor) { return v.visitGlobalDef(this); }
public getDescription()
{
if (this.isResource && this.getKind() == api.core.Color)
return lf("color #{0}", this.url);
return lf("a global variable")
}
public forSearch()
{
return this.stringResourceValue() || this.url || ""
}
public stringResourceValue()
{
if (this.isResource && this.getKind() == api.core.String && this.url) {
return RT.String_.valueFromArtUrl(this.url)
}
return null
}
public thingSetKindName() { return this.isResource ? "art" : "data"; }
public getCategory() { return PropertyCategory.Data; }
public setKind(k:Kind)
{
this._kind = k;
//if (!k.isSerializable)
// this.isTransient = true;
}
public writeTo(tw:TokenWriter)
{
this.writeId(tw);
tw.nl().keyword("var").id(this.getName()).op(":").kind(this.parent, this.getKind());
tw.beginBlock();
if (!!this.comment)
tw.comment(this.comment);
if (this.isResource) {
tw.boolOptAttr("is_resource", this.isResource);
if (!!this.url) tw.stringAttr("url", this.url);
} else
tw.boolOptAttr("readonly", this.readonly);
tw.boolOptAttr("transient", this.isTransient);
tw.boolOptAttr("cloudenabled", this.cloudEnabled);
tw.endBlock();
}
static parse(p:Parser)
{
var v = new GlobalDef();
v.setStableName(p.consumeLabel());
v.isTransient = false;
p.addDecl(v);
v.setName(p.parseId());
v.setKind(p.parseTypeAnnotation());
p.parseBraced(() => {
if (p.gotKey("readonly")) v.readonly = p.parseBool();
if (p.gotKey("transient")) v.isTransient = p.parseBool();
if (p.gotKey("cloudenabled")) v.cloudEnabled = p.parseBool();
if (p.gotKey("is_resource")) {
v.isResource = p.parseBool();
if (v.isResource) v.readonly = true;
}
if (p.gotKey("url")) v.url = p.parseString();
if (p.got(TokenType.Comment)) v.comment += p.shift().data + "\n";
});
if (v.cloudEnabled) v.isTransient = false;
}
public getRecordPersistence()
{
if (this.cloudEnabled) return RecordPersistence.Cloud;
if (!this.isTransient) return RecordPersistence.Local;
return RecordPersistence.Temporary;
}
public notifyChange() {
super.notifyChange();
// we are editing data structure definitions! Must ensure to remove all stale state.
if (Runtime.theRuntime)
Runtime.theRuntime.resetData();
}
}
export class SingletonDef
extends Decl
{
public isExtension = false;
public _isBrowsable = true;
constructor() {
super()
this.usage = new TokenUsage(this);
}
public usage:TokenUsage;
public getUsage() { return this.usage; }
public nodeType() { return "singletonDef"; }
public accept(v:NodeVisitor) { return v.visitSingletonDef(this); }
public getDescription(skip?:boolean) { return this.getKind().getHelp(!skip); }
public isBrowsable() { return this._isBrowsable; }
public usageMult() { return 1; }
public usageKey() {
return Util.tagify(this.getKind().getName());
}
}
export class PlaceholderDef
extends Decl
{
constructor() {
super()
}
public nodeType() { return "placeholderDef"; }
public getDescription() { return "need " + this.getKind().toString() + " here"; }
public isBrowsable() { return false; }
public escapeDef:any;
public getName() { return "need " + this.getKind().serialize() + (this.label ? ":" + this.label : ""); }
public label:string;
public longError()
{
return lf("TD100: insert {0:a} here", this.label || this.getKind().toString())
}
}
export interface ParameterAnnotations {
hints?:string[];
enumMap?:StringMap<string>;
pichints?:StringMap<string>;
langugage?:string;
}
export class Action
extends PropertyDecl
implements IPropertyWithNamespaces
{
constructor() {
super()
this.header = new ActionHeader(this);
}
public nodeType() { return "action"; }
public header:ActionHeader;
public _isTest:boolean;
public isTest():boolean { return this._isTest && (this.isCompilerTest() || !this.hasInParameters()) }
public isNormalAction() { return !this.isPage() && !this.isEvent() && !this.isLambda }
public isEvent() { return !!this.eventInfo; }
public isPage() { return this._isPage; }
public isPrivate: boolean;
public isQuery: boolean;
public isOffline: boolean;
public isLambda:boolean;
public numUnsupported = 0;
private isShareTarget:boolean;
private definedKind:UserActionKind;
public body:CodeBlock;
public _hasErrors:boolean;
public _errorsOK:boolean;
public _isPage:boolean;
public _isActionTypeDef:boolean;
public _compilerInlineAction:InlineAction;
public _compilerParentAction:Action; // this is only set for synthetic actions created in compiler for lambda expressions
public _skipIntelliProfile:boolean;
public allLocals:LocalDef[];
public accept(v:NodeVisitor) { return v.visitAction(this); }
public children() { return [this.header]; }
public hasErrors() { return this._hasErrors; }
public isMainAction() { return this.parent && this.parent.mainAction() == this; }
public eventInfo:EventInfo;
public parentLibrary():LibraryRef { return this.parent && this.parent.isTopLevel && this.parent.thisLibRef; }
public thingSetKindName() { return this.isEvent() ? null : "code"; }
public isActionTypeDef() { return this._isActionTypeDef; }
public isExtensionAction() : boolean { return false }
public extensionForward():Action { return this }
public hasWarnings() { return this.numUnsupported > 0 }
public getExtensionKind():Kind
{
var p = this.getInParameters()
if (p.length >= 1 && !/\?$/.test(p[0].getName()) &&
p[0].getKind().isExtensionEnabled())
return p[0].getKind()
return null
}
public getHelpPath() : string {
var desc = this.getDescription()
var m = /{help:([^}]+)}/i.exec(desc);
return m ? m[1] : undefined;
}
public getNamespaces():string[]
{
var desc = this.getDescription()
if (!/{namespace:/.test(desc)) return []
var res = [];
desc.replace(/{namespace:([^}]+)}/g, (m, ns) => { res.push(ns); return "" })
return res
}
public getFlags()
{
var flags = !this.isAtomic ? PropertyFlags.Async : PropertyFlags.None
if (this.body)
for (var i = 0; i < this.body.stmts.length; ++i) {
var s = this.body.stmts[i].docText()
if (s != null) {
if (/{action:ignoreReturn}/i.test(s)) {
flags |= PropertyFlags.IgnoreReturnValue
}
} else {
break;
}
}
return flags
}
public clearError()
{
super.clearError();
this.numUnsupported = 0
}
public isInLibrary() { return false }
public markUsed() { }
public isLibInit()
{
return !this.isPrivate && this.getName() == "_libinit" && this.getOutParameters().length == 0 && this.getInParameters().length == 0;
}
public modelParameter:ActionParameter;
public helpTopic() {
return this.isActionTypeDef() ? "action types" :
this.isPage() ? "pages" :
this.isEvent() ? "events" :
"code";
}
public isAtomic: boolean = false;
public canBeInlined: boolean = false;
public debuggingData: { critical: number; max: { critical: number }; };
public isCompilerTest()
{
return this._isTest && /^E: /.test(this.getName())
}
public getModelDef()
{
if (!this.modelParameter) return null
if (this.modelParameter.getKind() instanceof RecordEntryKind) {
return (<RecordEntryKind>this.modelParameter.getKind()).getRecord()
}
return null
}
public getInlineHelp():string
{
if (this.eventInfo) return this.eventInfo.type.help;
if (!this.body) return "";
var desc = ""
for (var i = 0; i < this.body.stmts.length; ++i) {
var s = this.body.stmts[i]
if (s instanceof Comment) {
var c = <Comment>s;
if (desc) desc += "\n";
desc += c.text;
} else {
break;
}
}
return desc;
}
public getDescription():string
{
var desc = this.getInlineHelp();
if (desc) return desc;
return this.isActionTypeDef() ? lf("an function type definition")
: this.isEvent() ? lf("an event handler")
: this.isPage() ? lf("a page") : lf("a function")
}
// IProperty
public getCategory():PropertyCategory { return PropertyCategory.Action; }
public getParameters() : PropertyParameter[]
{
return super.getParameters().concat(
this.getInParameters().map((p:ActionParameter) => this.mkPP(p.getName(), p.getKind())));
}
public getResult() : PropertyParameter
{
var op = this.getOutParameters();
if (op.length == 1)
return this.mkPP(op[0].getName(), op[0].getKind());
else
return this.mkPP("result", api.core.Nothing);
}
public getDefinedKind()
{
if (!this.isActionTypeDef()) return null
if (!this.definedKind) this.definedKind = new UserActionKind(this)
return this.definedKind
}
public setEventInfo(ei:EventInfo)
{
this.eventInfo = ei;
this.isAtomic = false;
if (!!ei) {
this.header.inParameters.immutableReason = lf("Types, order, and number of event parameters cannot be edited.");
this.header.outParameters.immutableReason = lf("Events cannot have out parameters.");
}
}
public getPageBlock(init:boolean) : CodeBlock
{
var ch = this.body.children();
if (ch.length != 2 || !(ch[0] instanceof If) || !(ch[1] instanceof If))
return null;
var ifInit = <If>ch[0];
var renderIf = <If>ch[1];
var ifToks = ifInit.rawCondition.tokens;
if (!ifInit.rawElseBody.isBlockPlaceholder()) return null;
if (!renderIf.rawElseBody.isBlockPlaceholder()) return null;
if (ifToks.length != 2 || !(ifToks[0] instanceof ThingRef) || !(ifToks[1] instanceof PropertyRef))
return null;
if ((<ThingRef>ifToks[0]).data != "box" || (<PropertyRef>ifToks[1]).data != "is init")
return null;
this.body.isInvisible = true;
function markInvisible(i:If) {
i.isInvisible = true;
i.rawElseBody.isInvisible = true;
var s0 = i.rawElseBody.stmts[0]
if (s0) s0.isInvisible = true;
}
markInvisible(ifInit);
markInvisible(renderIf);
return init ? ifInit.rawThenBody : renderIf.rawThenBody;
}
public getInParameters():ActionParameter[] { return <ActionParameter[]>this.header.inParameters.stmts; }
public getOutParameters():ActionParameter[] { return <ActionParameter[]>this.header.outParameters.stmts; }
public getAllParameters():ActionParameter[] { return this.getInParameters().concat(this.getOutParameters()); }
public hasInParameters() { return this.getInParameters().length > 0; }
public hasOutParameters() { return this.getOutParameters().length > 0; }
public nameLocal(n:string, usedNames:any = {})
{
return AstNode.freshNameCore(n,
(n:string) =>
usedNames.hasOwnProperty(n) ||
this.allLocals.some((l:LocalDef) => l.getName() == n) ||
api.getThing(n) != null);
}
public isPlugin()
{
if (this.getName() != "plugin")
return false
var parms = this.getInParameters()
if (parms.length != 1)
return false
if (parms[0].getKind() == api.core.String ||
parms[0].getKind() == api.core.Editor)
return true
return false
}
public isButtonPlugin()
{
if (this.isPrivate) return false
var parms = this.getInParameters()
if (parms.length != 1) return false
// the type may be unresolved
return parms[0].getKind().getName() == "Editor"
}
public isRunnable() {
if (this.isActionTypeDef() || this.isEvent() || this.isPrivate) return false;
if (Cloud.isRestricted() && this.hasInParameters()) return false; // input parameters not support in restricted
return this.isPlugin()
|| this.isButtonPlugin()
|| !this.hasInParameters()
|| (!this.isPage() && this.getInParameters().every((a) => !!a.getKind().picker));
}
public writeHeader(tw:TokenWriter, forLibSig = false)
{
var writeParms = (parms:ActionParameter[]) =>
{
var first = true;
parms.forEach((l:ActionParameter) => {
if (!first)
tw.op0(",").space();
if (!forLibSig)
l.writeIdOpt(tw);
l.local.writeWithType(this.parent, tw);
first = false;
});
}
tw.nl().keyword(this.isEvent() ? "event" : "action");
if (forLibSig) {
tw.op(!this.isAtomic ? "async" : "sync");
}
//if (forLibSig && this.isPage()) tw.op("page")
if (this.isActionTypeDef())
tw.op("type")
tw.id(this.getName()).op0("(");
var parms = this.getInParameters();
if (this.modelParameter) {
parms = parms.slice(0);
parms.unshift(this.modelParameter);
}
writeParms(parms);
tw.op0(")");
if (this.hasOutParameters()) {
tw.keyword("returns").op0("(");
writeParms(this.getOutParameters());
tw.op0(")");
}
}
public writeTo(tw:TokenWriter)
{
this.writeId(tw);
this.writeHeader(tw);
if (tw.skipActionBodies) {
tw.beginBlock();
tw.endBlock();
} else {
this.body.writeTo(tw);
}
tw.backspaceBlockEnd();
if (this.isPrivate)
tw.keyword("meta").keyword("private").op0(";").nl();
if (this.isPage())
tw.keyword("meta").keyword("page").op0(";").nl();
if (this.isAtomic && !this.isEvent())
tw.keyword("meta").keyword("sync").op0(";").nl();
if (this.isTest())
tw.keyword("meta").keyword("test").op0(";").nl();
if (this.isOffline)
tw.keyword("meta").keyword("offline").op0(";").nl();
if (this.isQuery)
tw.keyword("meta").keyword("query").op0(";").nl();
tw.endBlock();
}
public toString() { return this.getName(); }
public getStats() : StatsComputer
{
var s = new StatsComputer()
s.dispatch(this)
return s;
}
public freshlyCreated()
{
if (!this.isPage() || this.modelParameter) return;
var decl = <RecordDef> Parser.parseDecl("table __name__ { type = 'Object'; fields { } }");
decl.setName(Script.freshName(this.getName() + " page data"));
Script.addDecl(decl);
var l = AST.mkLocal(modelSymbol, decl.entryKind);
this.modelParameter = new ActionParameter(l);
}
private initParameterAnnotations()
{
if (this.parameterAnnotations) return;
this.parameterAnnotations = {};
var descr = this.getDescription();
descr.replace(/\{(hints|enum):([^:{}]*):([^{}]*)/g,(mtch, tp, arg, vals : string) => {
var annot = this.getParameterAnnotation(arg);
annot.hints = vals.split(',');
if (tp == "enum") {
annot.enumMap = {}
annot.hints = annot.hints.map(a => {
var m = /([^=]*)=(.+)$/i.exec(a)
if (m) {
annot.enumMap[m[1]] = m[2]
return m[1]
}
else return a
});
(<any>annot.hints).enumMap = annot.enumMap
}
return ""
})
descr.replace(/\{pichints:([^:{}]*):([^{}]*)/g,(mtch, arg, vals: string) => {
this.getParameterAnnotation(arg).pichints = Util.splitKeyValues(vals);
return ""
})
descr.replace(/\{language:([^:{}]*):([^{}]*)/g,(mtch, arg, vals: string) => {
this.getParameterAnnotation(arg).langugage = vals
return ""
})
}
private getParameterAnnotation(name:string): ParameterAnnotations
{
this.initParameterAnnotations()
if (!this.parameterAnnotations.hasOwnProperty(name))
this.parameterAnnotations[name] = {}
return this.parameterAnnotations[name]
}
private parameterAnnotations:StringMap<ParameterAnnotations>;
public mkPP(name:string, k:Kind) {
var r = super.mkPP(name, k)
var annot = this.getParameterAnnotation(name)
if (annot.hints) r.setDeflStrings(annot.hints)
if (annot.pichints) r.setDeflStringArtIds(annot.pichints);
if (annot.langugage) r.languageHint = annot.langugage
return r
}
}
export class ExtensionProperty
extends Property
{
constructor(public shortcutTo:Action)
{
super(shortcutTo.getInParameters()[0].getKind(), shortcutTo.getName(), shortcutTo.getDescription(), [], api.core.Nothing)
}
public getCategory() { return PropertyCategory.Action; }
public getParameters() { return this.shortcutTo.getParameters().slice(1); }
public getResult() { return this.shortcutTo.getResult() }
public getName() { return this.shortcutTo.getName() }
public getDescription() { return this.shortcutTo.getDescription() }
public getSignature() { return this.shortcutTo.getSignature() }
public getFlags() { return this.shortcutTo.getFlags() }
public canRename() { return false }
}
export class LocalDef
extends Decl
{
constructor() {
super()
}
public nodeType() { return "localDef"; }
public accept(v:NodeVisitor) { return v.visitLocalDef(this); }
public getDescription():string { return ": " + this.getKind().toString() + " -- a local variable"; }
public rename(nn:string) { this.setName(nn); }
public setKind(k:Kind) { this._kind = k }
public lastUsedAt = 0;
public lambdaNameStatus:number;
public isRegular:boolean;
public isSynthetic:boolean;
public isHiddenOut:boolean;
public isOut:boolean;
public _lastWriteLocation:Stmt;
public writeWithType(app:App, tw:TokenWriter)
{
tw.id(this.getName()).op0(':').kind(app, this.getKind());
}
public clone() { return mkLocal(this.getName(), this.getKind()); }
}
export interface AppEditorState
{
tutorialId?: string;
tutorialStep?: number;
tutorialFast?: boolean;
tutorialValidated?: boolean;
tutorialRedisplayed?: number;
tutorialMode?: string;
tutorialNumSteps?: number;
tutorialUpdateKey?: string;
tutorialAnonymousId?: string;
deployWebsite?: string;
parentScriptGuid?: string;
collabSessionId?: string;
groupId?: string;
buttonPlugins?: StringMap<any>;
libraryLocalBindings?: StringMap<string>; // library stable id -> installation guid
splitScreen?: boolean;
cordova?: Apps.CordovaOptions;
}
export interface HeaderWithState extends Cloud.Header
{
editorState: AppEditorState;
}
export enum LanguageFeature
{
None = 0x0000,
JS = 0x0001,
ContextCheck = 0x0002,
Refs = 0x0004,
LocalCloud = 0x0008,
UnicodeModel = 0x0010,
AllAsync = 0x0020,
UppercaseMultiplex = 0x0040,
Current = JS|Refs|ContextCheck|LocalCloud|UnicodeModel|AllAsync|UppercaseMultiplex
}
export enum AnnotationMode {
None, Coverage, Profiling, Crash
}
export class AppImports implements CompiledImports {
public npmModules: StringMap<string> = {};
public bowerModules: StringMap<string> = {};
public clientScripts: StringMap<string> = {};
public cordovaPlugins: StringMap<string> = {};
public pipPackages: StringMap<string> = {};
public touchDevelopPlugins: StringMap<string> = {};
public importTouchDevelop(tc : TypeChecker, t: Call, plugin: string, scriptId: string) {
if (scriptId) {
this.touchDevelopPlugins[scriptId] = "1";
}
}
public importPip(tc : TypeChecker, t: Call, pkg: string, v: string) {
if (pkg) {
var pkgs = this.pipPackages;
if (pkgs.hasOwnProperty(pkg)) {
if (pkgs[pkg] == "error") return;
var newOne = AppImports.combinePipVersions(pkgs[pkg], v)
if (newOne == "error")
tc.markError(t, lf("TD196: pip package '{0}' versions '{1}' and '{2}' conflict", pkg, pkgs[pkg], v))
pkgs[pkg] = newOne
} else
pkgs[pkg] = v;
}
}
public importCordova(tc : TypeChecker, t: Call, plugin: string, v: string) {
if (plugin) {
var plugins = this.cordovaPlugins
if (plugins.hasOwnProperty(plugin)) {
if (plugins[plugin] == "error") return;
var newOne = AppImports.combineNpmVersions(plugins[plugin], v)
if (newOne == "error")
tc.markError(t, lf("TD187: cordova plugin '{0}' versions '{1}' and '{2}' conflict", plugin, plugins[plugin], v))
plugins[plugin] = newOne
} else
plugins[plugin] = v;
}
}
static combineNpmModules(topApp: App): StringMap<string> {
var imports: StringMap<string> = {}
topApp.librariesAndThis().filter(l => !!l.resolved).forEach(l => {
Object.keys(l.resolved.imports.npmModules).forEach(k => {
var v = l.resolved.imports.npmModules[k]
if (imports.hasOwnProperty(k)) {
imports[k] = AppImports.combineNpmVersions(imports[k], v)
} else {
imports[k] = v
}
})
})
return imports
}
static combinePipPackages(topApp: App): StringMap<string> {
var imports: StringMap<string> = {}
topApp.librariesAndThis().filter(l => !!l.resolved).forEach(l => {
Object.keys(l.resolved.imports.pipPackages).forEach(k => {
var v = l.resolved.imports.pipPackages[k]
if (imports.hasOwnProperty(k)) {
imports[k] = AppImports.combinePipVersions(imports[k], v)
} else {
imports[k] = v
}
})
})
return imports
}
static combineCordovaPlugins(topApp: App): StringMap<string> {
var plugins: StringMap<string> = {
"org.apache.cordova.console": "*",
"com.msopentech.websql": "*",
"org.apache.cordova.inappbrowser": "*",
}
Object.keys(topApp.imports.cordovaPlugins)
.filter(k => topApp.imports.cordovaPlugins.hasOwnProperty(k))
.forEach(k => plugins[k] = AppImports.combineNpmVersions(plugins[k], topApp.imports.cordovaPlugins[k]));
topApp.librariesAndThis().filter(l => !!l.resolved).forEach(l => {
Object.keys(l.resolved.imports.cordovaPlugins).forEach(k => {
var v = l.resolved.imports.cordovaPlugins[k]
if (plugins.hasOwnProperty(k)) {
plugins[k] = AppImports.combineNpmVersions(plugins[k], v)
} else {
plugins[k] = v
}
})
})
return plugins
}
static combineTouchDevelopPlugins(topApp: App): StringMap<string> {
var plugins: StringMap<string> = {};
Object.keys(topApp.imports.touchDevelopPlugins)
.forEach(k => plugins[k] = "1");
topApp.librariesAndThis().filter(l => !!l.resolved).forEach(l => {
Object.keys(l.resolved.imports.touchDevelopPlugins).forEach(k => plugins[k] = "1");
})
return plugins
}
public importClientScript(tc : TypeChecker, t: Call, mod: string, v: string) {
if (mod && v != null) {
var imports = this.clientScripts;
imports[mod] = v;
}
}
public importNpm(tc : TypeChecker, t: Call, mod: string, v: string) {
if (mod && v != null) {
var imports = this.npmModules
if (imports.hasOwnProperty(mod)) {
if (imports[mod] == "error") return;
var newOne = AppImports.combineNpmVersions(imports[mod], v)
if (newOne == "error")
tc.markError(t, lf("TD180: npm module '{0}' versions '{1}' and '{2}' conflict", mod, imports[mod], v))
imports[mod] = newOne;
}
else
imports[mod] = v
}
}
public importBower(tc : TypeChecker, t: Call, mod: string, v: string) {
if (mod && v != null) {
var imports = this.bowerModules
if (imports.hasOwnProperty(mod)) {
if (imports[mod] == "error") return;
var newOne = AppImports.combineNpmVersions(imports[mod], v)
if (newOne == "error")
tc.markError(t, lf("TD197: bower module '{0}' versions '{1}' and '{2}' conflict", mod, imports[mod], v))
imports[mod] = newOne;
}
else
imports[mod] = v
}
}
static combinePipVersions(v0: string, v1: string) {
if (v0 === v1) return v0
if (v0 === undefined || (v1 && v0 === "*")) return v1
if (v1 === undefined || (v0 && v1 === "*")) return v0
// TODO
return "error"
}
static combineNpmVersions(v0: string, v1: string) {
if (v0 === v1) return v0
if (v0 === undefined || (v1 && v0 === "*")) return v1
if (v1 === undefined || (v0 && v1 === "*")) return v0
return "error"
}
}
export class App
extends Decl
{
static currentVersion = "v2.2,js,ctx,refs,localcloud,unicodemodel,allasync,upperplex";
constructor(p:Parser) {
// should be only contructed by the parser
super()
this.setStableName("app");
this.thisLibRef = LibraryRef.topScriptLibrary(this);
this.setName("no name");
this.version = App.currentVersion;
}
// split screen is used as a hit when loaded in the editor, serialized when publishing
static metaMapping = [ "showAd", "isLibrary", "allowExport", "isCloud", "hasIds", "splitScreen" ];
public nodeType() { return "app"; }
public things:Decl[] = [];
public getDescription() { return this.comment; }
public thisLibRef:LibraryRef;
public editorState:AppEditorState = {};
public parentIds:string[] = []; // used for merging
public rootId:string = uniqueAstId(24);
private languageFeatures = LanguageFeature.Current;
private usedIds:any = {};
public syntheticIds:StringMap<boolean> = {};
public diffRemovedThings: Decl[];
public imports = new AppImports();
public notifyVersionMarker:any = new Object();
public libNamespaceCache = new LibNamespaceCache(this);
public recompiler:Compiler;
public recompiledScript:CompiledScript;
public clearRecompiler() {
this.recompiler = null;
this.recompiledScript = null;
}
// visual annotation mode
public annotatedBy: AnnotationMode = AnnotationMode.None;
public htmlColor()
{
if (Cloud.isRestricted()) return "#0095ff"
if (!this.color) return ScriptIcons.stableColorFromName(this.getName());
else return "#" + this.color.replace("#", "").slice(-6);
}
public iconName() { return "emptycircle" } // this.icon || ScriptIcons.stableIconFromName(this.getName())
public iconPath() { return "svg:" + this.iconName() + ",white"; }
private version:string;
public icon:string;
public color:string;
public iconArtId: string;
public splashArtId: string;
public comment: string = "";
public hasLibraries(): boolean { return this.libraries().length > 0; }
public hasTests():boolean { return this.allActions().some((a) => a.isTest()); }
public isTestOnly():boolean { return !this.mainAction() && this.hasTests() }
public localGuid:string = Util.guidGen();
public isLibrary:boolean;
public isCloud: boolean;
public isTopLevel = false;
private seed:string;
public showAd:boolean;
public isDocsTopic() { return this.comment && /#docs/i.test(this.comment); }
public isTutorial() { return this.isDocsTopic() && this.allActions().some(a => /^#\d/.test(a.getName())) }
public allowExport:boolean;
public hasIds: boolean;
public splitScreen: boolean;
private stillParsing:boolean = true;
public accept(v:NodeVisitor) { return v.visitApp(this); }
public children() { return this.things; }
private _platform = PlatformCapability.Current;
public debuggingData: { critical: number; max: { critical: number }; };
public usesCloud()
{
return this.records().some(r => r.cloudEnabled) || this.variables().some(r => r.cloudEnabled);
}
public usesCloudLibs()
{
return this.librariesAndThis().some(l => l.isCloud())
}
private meta:any = {};
public notifyChange()
{
this.notifyVersionMarker = new Object();
super.notifyChange()
}
public getGlobalErrorDecl(ignoreLibs:boolean) { return this.things.filter((t) => t.hasErrors() && !(t instanceof Action) && (!ignoreLibs || !(t instanceof LibraryRef)))[0]; }
public getDefinedKinds()
{
var kindList:Kind[] = []
this.things.map(t => {
var tp = t.getDefinedKind()
if (tp) kindList.push(tp)
})
return kindList
}
public getKinds() : Kind[]
{
var kindList = api.getKinds().concat(this.getDefinedKinds())
this.libraries().forEach((l) => {
kindList.pushRange(l.getPublicKinds());
});
return kindList;
}
static platforms = [
{ cap: PlatformCapability.Current, id: "current", name: lf("Current device (overrides the rest)") },
{ cap: PlatformCapability.Accelerometer, id: "accelerometer", name: lf("Accelerometer") },
{ cap: PlatformCapability.Bluetooth, id: "bluetooth", name: lf("Bluetooth") },
{ cap: PlatformCapability.Calendar, id: "calendar", name: lf("Calendar") },
{ cap: PlatformCapability.Camera, id: "camera", name: lf("Camera") },
{ cap: PlatformCapability.CloudData, id: "clouddata", name: lf("Cloud Data") },
{ cap: PlatformCapability.CloudServices, id: "cloudservices", name: lf("OneDrive, OneNote") },
{ cap: PlatformCapability.Compass, id: "compass", name: lf("Compass") },
{ cap: PlatformCapability.Contacts, id: "contacts", name: lf("Contacts") },
{ cap: PlatformCapability.EditorOnly, id: "editoronly", name: lf("Editor only") },
{ cap: PlatformCapability.Gyroscope, id: "gyroscope", name: lf("Gyroscope") },
{ cap: PlatformCapability.Hawaii, id: "hawaii", name: lf("Hawaii") },
{ cap: PlatformCapability.Home, id: "home", name: lf("Home media devices") },
{ cap: PlatformCapability.Location, id: "location", name: lf("Location") },
{ cap: PlatformCapability.Maps, id: "maps", name: lf("Maps") },
{ cap: PlatformCapability.Media, id: "media", name: lf("Media libraries on device") },
{ cap: PlatformCapability.Microphone, id: "microphone", name: lf("Microphone") },
{ cap: PlatformCapability.Motion, id: "motion", name: lf("Motion") },
{ cap: PlatformCapability.MusicAndSounds, id: "musicandsounds", name: lf("Music and Sounds") },
{ cap: PlatformCapability.Network, id: "network", name: lf("Network") },
{ cap: PlatformCapability.Proximity, id: "proximity", name: lf("NFC") },
{ cap: PlatformCapability.Orientation, id: "orientation", name: lf("Orientation") },
{ cap: PlatformCapability.Phone, id: "phone", name: lf("Phone specific") },
{ cap: PlatformCapability.Radio, id: "radio", name: lf("Radio") },
{ cap: PlatformCapability.Search, id: "search", name: lf("Search") },
{ cap: PlatformCapability.Speech, id: "speech", name: lf("Speech") },
{ cap: PlatformCapability.Translation, id: "translation", name: lf("Translation") },
{ cap: PlatformCapability.Tiles, id: "tiles", name: lf("Tiles") },
{ cap: PlatformCapability.Npm, id: "npm", name: lf("Node Package Manager") },
{ cap: PlatformCapability.Cordova, id: "cordova", name: lf("Cordova Plugin Manager") },
{ cap: PlatformCapability.Shell, id: "shell", name: lf("TouchDevelop Local Shell") },
];
static capabilityName(p:PlatformCapability)
{
return App.platforms.filter((k) => !!(k.cap & p)).map((k) => k.name).join(", ")
}
static capabilityString(p:PlatformCapability)
{
return App.platforms.filter((k) => !!(k.cap & p)).map((k) => k.id).join(",")
}
static orderThings(things:Decl[], mixActions = false)
{
var score = (t:Decl) => {
if (t instanceof Action) {
var a = <Action>t;
if (mixActions) return 1
if (a.isPage()) return 3;
else if (a.isEvent()) return 2;
else if (a.isMainAction()) return 0.5;
else if (a.isPrivate) return 1.5;
else return 1;
} else if (t instanceof GlobalDef) {
var g = <GlobalDef>t;
if (g.isResource) return 5;
else return 4;
} else if (t instanceof RecordDef) {
return 6;
} else if (t instanceof LibraryRef) {
return 7;
} else {
return 0; // unknown
}
}
var cmp = (a:Decl, b:Decl) => {
var diff = score(a) - score(b);
if (diff == 0) {
var an = a.getName()
var bn = b.getName()
for (var i = 0; i < an.length; ++i)
if (an.charAt(i) != bn.charAt(i))
break;
if (/^[0-9]$/.test(an.charAt(i)) && /^[0-9]$/.test(bn.charAt(i))) {
var dot = false;
var c = ""
var beg = i - 1;
while (beg > 0 && /^[0-9\.]$/.test(c = an.charAt(beg))) {
if (c == ".") {
if (dot) break;
dot = true;
}
beg--;
}
beg++;
var am = /^(\d*(\.\d+)?)/.exec(an.slice(beg))
var bm = /^(\d*(\.\d+)?)/.exec(bn.slice(beg))
return parseFloat(am[1]) - parseFloat(bm[1])
}
if (a.getName() < b.getName()) return -1;
else if (a == b) return 0;
else return 1;
} else return diff;
}
things.sort(cmp);
}
public orderedThings(mixActions = false)
{
var th = this.things.slice(0);
App.orderThings(th, mixActions)
return th;
}
public canUseProperty(p:IProperty)
{
return (this.getPlatform() & p.getCapability()) == p.getCapability();
}
public canUseCapability(p:PlatformCapability)
{
return (this.getPlatform() & p) == p;
}
public supportsAllPlatforms(p:PlatformCapability)
{
return (this.getPlatform() & p) == this.getPlatform();
}
public getPlatform() : PlatformCapability
{
if (this._platform & PlatformCapability.Current)
return api.core.currentPlatform;
return this._platform;
}
public getPlatformRaw() : PlatformCapability
{
return this._platform;
}
public setPlatform(p:PlatformCapability)
{
this._platform = p;
}
private setPlatformString(p:string)
{
if (!p) this._platform = PlatformCapability.None;
var platform = App.fromCapabilityList(p.split(/,/));
if (platform == 0x3fffff) platform = PlatformCapability.Current;
if (platform) this._platform = platform;
}
static _platformMapping:any;
static capabilityByName(n:string)
{
if (!App._platformMapping) {
App._platformMapping = {}
App.platforms.forEach((k) => App._platformMapping[k.id] = k.cap);
}
n = n.toLowerCase();
if (App._platformMapping.hasOwnProperty(n)) return App._platformMapping[n];
else return PlatformCapability.None;
}
static fromCapabilityList(ps:string[])
{
var platform = PlatformCapability.None;
ps.forEach((p) => { platform |= App.capabilityByName(p) })
return platform;
}
public getCapabilityString()
{
return App.capabilityString(this._platform);
}
public getIconArtId() {
return this.iconArtId;
}
public getBoxInfo()
{
return lf("script properties");
}
private setVersion(v:string)
{
var f = LanguageFeature.None;
v.split(/,/).forEach((t) => {
switch (t) {
case 'js': f |= LanguageFeature.JS; break;
case 'ctx': f |= LanguageFeature.ContextCheck; break;
case 'refs': f |= LanguageFeature.Refs; break;
case 'localcloud': f |= LanguageFeature.LocalCloud; break;
case 'unicodemodel': f |= LanguageFeature.UnicodeModel; break;
case 'allasync': f |= LanguageFeature.AllAsync; break;
case 'upperplex': f |= LanguageFeature.UppercaseMultiplex; break;
default: break;
}
})
this.languageFeatures = f;
}
public addFeatures(f:LanguageFeature)
{
this.languageFeatures |= f;
}
public featureMissing(f:LanguageFeature)
{
return (this.languageFeatures & f) == 0;
}
public setMeta(k:string, v:string)
{
if (<any>v === true) v = "yes";
switch (k) {
case "icon": this.icon = v; break;
case "name": this.setName(v); break;
case "color": this.color = v; break;
case "seed": this.seed = v; break;
case "version": this.setVersion(v); break;
case "platform": this.setPlatformString(v); break;
case "parentIds": this.parentIds = v.split(/,\s*/).filter(x => !!x); break;
case "editorState":
if (v) try { this.editorState = JSON.parse(v); } catch (e) { Util.check(false, "editor state corrupted"); }
break;
case "rootId": this.rootId = v; break;
case "iconArtId": this.iconArtId = v; break;
case "splashArtId": this.splashArtId = v; break;
default:
if (App.metaMapping.indexOf(k) >= 0)
(<any>this)[k] = v == "yes";
this.meta[k] = v;
}
}
public mainAction()
{
var c0 = this.allActions().filter((a: Action) => a.isRunnable())
if (this.isTutorial()) {
var main0 = c0.filter((a: Action) => a.getName() == "#0 main")[0];
if (main0) return main0;
}
var c1 = c0.filter((a) => !a.isPage())
var c2 = c0.filter((a: Action) => a.getName() == "main");
if (c2.length == 0) c2 = c1;
if (c2.length == 0) c2 = c0;
/*
// the phone app doesn't sort
// we don't want locale-sensitive sort here
c1.sort((a, b) => {
var aa = a.getName().toLowerCase();
var bb = b.getName().toLowerCase();
if (aa < bb) return -1;
else if (aa > bb) return +1;
else return 0;
});
*/
if (c2.length > 0) return c2[0];
else return null;
}
public findActionByName(name: string) : Action {
for (var i = 0; i < this.things.length; i++) {
var t = this.things[i];
if (t instanceof Action && t.getName() == name) return <Action>t;
}
return null;
}
public allActions():Action[] { return <Action[]>this.things.filter((t) => t instanceof Action); }
public actions():Action[] { return <Action[]>this.things.filter((t) => t instanceof Action && !((<Action>t).isEvent()) && !((<Action>t).isActionTypeDef())); }
public events():Action[] { return <Action[]>this.things.filter((t) => t instanceof Action && ((<Action>t).isEvent())); }
public actionTypeDefs():Action[] { return <Action[]>this.things.filter((t) => t instanceof Action && ((<Action>t).isActionTypeDef())); }
public libraries():LibraryRef[] { return <LibraryRef[]>this.things.filter((t) => t instanceof LibraryRef); }
public librariesAndThis():LibraryRef[] { return [this.thisLibRef].concat(this.libraries()); }
public variables():GlobalDef[] { return <GlobalDef[]>this.things.filter((t) => t instanceof GlobalDef && !(<AST.GlobalDef> t).isResource); }
public resources():GlobalDef[] { return <GlobalDef[]>this.things.filter((t) => t instanceof GlobalDef && !!(<AST.GlobalDef> t).isResource); }
public variablesAndResources():GlobalDef[] { return <GlobalDef[]>this.things.filter((t) => t instanceof GlobalDef); }
public records():RecordDef[] { return <RecordDef[]>this.things.filter((t) => t instanceof RecordDef); }
public parsingFinished()
{
//this.stillParsing = true;
//this.usedIds = {};
//this.things.forEach(t => this.addToUsedIds(t))
this.stillParsing = false;
}
public setStableNames()
{
//TODO remove?
}
public findStableName(s:string) : Decl
{
if (!s) return null;
if (s == this.getStableName()) return this;
return this.things.filter((d) => d.getStableName() == s)[0];
}
public toMeta():any
{
var r = {
type: "app",
version: App.currentVersion,
name: this.getName(),
icon: this.iconName(),
color: this.htmlColor(),
comment: TokenWriter.normalizeComment(this.comment),
seed: this.seed,
localGuid: this.localGuid,
platform: this.getCapabilityString(),
rootId: this.rootId,
parentIds: this.parentIds.join(","),
};
if (this.iconArtId) r["iconArtId"] = this.iconArtId;
if (this.splashArtId) r["splashArtId"] = this.splashArtId;
App.metaMapping.forEach((k) => {
r[k] = (<any>this)[k] ? "yes" : "no";
})
return r;
}
public toJsonScript() : any
{
var r = {
kind: "script",
time: 0,
id: "",
userid: "me",
username: "Me",
name: this.getName(),
description: this.comment,
icon: this.icon,
iconbackground: this.color,
iconArtId: this.iconArtId,
splashArtId: this.splashArtId,
positivereviews: 0,
cumulativepositivereviews: 0,
comments: 0,
capabilities: [],
flows: [],
haserrors: this.hasErrors(),
rootid: "",
updateid: "",
ishidden: true,
islibrary: this.isLibrary,
installations: 0,
runs: 0,
};
return r;
}
public loadMeta(m:any)
{
Object.keys(m).forEach((k) => {
this.setMeta(k, m[k]);
});
this.comment = m.comment || "";
}
public serializeFinal()
{
var tw = TokenWriter.forStorage();
this.writeFinal(tw);
return tw.finalize();
}
public serializeMeta()
{
var tw = TokenWriter.forStorage();
this.writeMeta(tw);
return tw.finalize();
}
private writeMeta(tw:TokenWriter)
{
tw.meta("version", App.currentVersion);
tw.meta("name", this.getName());
tw.metaOpt("icon", this.icon);
var c = this.color
if (c) {
if (c.length == 7 && c[0] == '#') c = "#ff" + c.slice(1)
c = c.toLowerCase()
tw.metaOpt("color", c)
}
tw.meta("rootId", this.rootId);
tw.metaOpt("iconArtId", this.iconArtId);
tw.metaOpt("splashArtId", this.splashArtId);
App.metaMapping.forEach((k) => {
tw.metaOpt(k, (<any>this)[k] ? "yes" : "");
})
tw.meta("platform", this.getCapabilityString());
tw.meta("parentIds", this.parentIds.join(","));
if (!!this.comment)
tw.comment(this.comment);
}
private writeFinal(tw:TokenWriter)
{
}
public writeTo(tw:TokenWriter)
{
this.writeMeta(tw);
this.things.forEach((t) => t.writeTo(tw));
if (!tw.skipActionBodies)
this.writeFinal(tw);
}
public notifyChangeAll()
{
this.notifyChange();
for (var i = 0; i < this.things.length; ++i)
this.things[i].notifyChange();
}
static sanitizeScriptTextForCloud(s:string)
{
var r = "";
if (s) {
s = s.replace(/(\r?\n)+$/, "");
s.split(/\n/).forEach((ln) => {
if (/^meta (stableNames|editorState)/.test(ln)) { }
else {
r += ln + "\n";
}
});
}
return r;
}
private addToUsedIds(t:Decl)
{
var u = this.usedIds;
var process = (t:IStableNameEntry) => {
if (!t.getStableName() || /_/.test(t.getStableName())) {
var enc = ""
if (!this.stillParsing) {
enc = uniqueAstId(16)
} else {
// otherwise, we're dealing with legacy script that needs a deterministic stable name
enc = Util.toUTF8(t.getName().replace(/[ _]/g, ""))
enc = enc.replace(/[^a-zA-Z0-9]/g, (c) => c.charCodeAt(0).toString(16))
if (enc.length > 20)
enc = enc.slice(0, 20)
if (/^[0-9]/.test(enc)) enc = "x" + enc
if (u.hasOwnProperty(enc)) {
var add = 1
while (u.hasOwnProperty(enc + add)) add++;
enc = enc + add
}
}
this.syntheticIds[enc] = true
t.setStableName(enc)
}
u[t.getStableName()] = t;
}
var declExists = (d:Decl) => {
return d && (d == t || this.things.indexOf(d) >= 0);
}
if (t.getStableName() && declExists(u[t.getStableName()]))
t.setStableName(null);
process(t);
if (t instanceof RecordDef)
(<RecordDef>t).getFields().forEach(f => {
if (f.getStableName()) {
var q = u[f.getStableName()]
if (q &&
(declExists(q) ||
(q instanceof RecordField && declExists(q.def()) && q.def().getFields().indexOf(q) >= 0)))
f.setStableName(null);
}
process(f);
})
}
public recomputeStableName(t:AST.Decl)
{
this.addToUsedIds(t);
}
public resetStableName(t:AST.Decl)
{
t.setStableName(null);
this.recomputeStableName(t)
}
public addDecl(t:AST.Decl)
{
t.parent = this;
this.addToUsedIds(t);
this.things.push(t);
}
public deleteDecl(t:AST.Decl)
{
var i = this.things.indexOf(t);
t.deleted = true;
if (i >= 0)
this.things.splice(i, 1);
}
public hasDecl(t:AST.Decl) { return t == this || this.things.indexOf(t) >= 0; }
public namesMatch(a:string, b:string)
{
return a.replace(/\s*\d+$/, "") == b.replace(/\s*\d+$/,"");
}
public freshName(n:string)
{
return AstNode.freshNameCore(n,
(n:string) =>
api.getThing(n) != null ||
n == "this" ||
this.things.some((d:Decl) => d.getName() == n || d.getCoreName() == n));
}
public findAstNodeById(id:string, allowNonStmt = false)
{
if (!id) return null;
var s = new SearchForId(id)
s.dispatch(this)
if (s.foundNode && (allowNonStmt || s.foundNode instanceof Stmt))
return { node: s.foundNode, stmt: s.lastStmt, decl: s.lastDecl }
else
return null;
}
public findStmtByStableName(name: string): Stmt
{
var s = new SearchForStableName(name);
s.dispatch(this)
return <Stmt>s.found;
}
}
// -------------------------------------------------------------------------------------------------------
// Expressions
// -------------------------------------------------------------------------------------------------------
export class ExprHolder
extends AstNode
{
public tokens:Token[];
public parsed:Expr;
public locals:LocalDef[];
public definedLocals:LocalDef[];
public looksLikeVarDef: boolean;
public hasFix: boolean;
public profilingExprData: ProfilingAstNodeData;
public debuggingData: DebuggingAstNodeInfo = {};
public reachingDefs: ReachingDefsMgr;
public dominators: DominatorsMgr;
public usedSet: UsedSetMgr;
public aeSet: AvailableExpressionsMgr;
public cpSet: ConstantPropagationMgr;
public diffTokens:Token[];
public isAwait:boolean;
/** white + first 27 colors from http://www.vendian.org/mncharity/dir3/blackbody/ */
static heatmapColors: string[] =
["#ffffff", "#fff5f5", "#fff3ef", "#fff0e9", "#ffeee3", "#ffebdc", "#ffe8d5",
"#ffe4ce", "#ffe1c6", "#ffddbe", "#ffd9b6", "#ffd5ad", "#ffd1a3", "#ffcc99",
"#ffc78f", "#ffc184", "#ffbb78", "#ffb46b", "#ffad5e", "#ffa54f", "#ff9d3f",
"#ff932c", "#ff8912", "#ff7e00", "#ff7300", "#ff6500", "#ff5300", "#ff3800"];
static profilingDurationBucketSize: number; // each bucket gets its own color
constructor() {
super()
}
public nodeType() { return "exprHolder"; }
public hasValue() { return true; }
public isPlaceholder() { return this.tokens.length == 0 || (this.tokens.length == 1 && this.tokens[0].isPlaceholder()); }
private calcNode() { return this; }
public accept(v:NodeVisitor) { return v.visitExprHolder(this); }
public children() { return this.tokens; }
public assignmentInfo() { return !this.parsed ? null : this.parsed.assignmentInfo(); }
public hint = "";
public getError()
{
if (this._error != null)
return this._error;
if (this.debuggingData.errorMessage)
return this.debuggingData.errorMessage;
for (var i = 0; i < this.tokens.length; ++i)
if (this.tokens[i]._error != null)
return this.tokens[i]._error;
return null;
}
public topCall(e:Expr = null):Call
{
if (!e) e = this.parsed
if (!e) return null
var prop = e.getCalledProperty()
if (prop == api.core.AssignmentProp)
return this.topCall((<Call>e).args[1])
else if (prop == api.core.AsyncProp)
return this.topCall((<Call>e).args[1])
else if (e instanceof Call)
return <Call>e
return null
}
public getLiteral():any
{
if (this.tokens.length == 1) return this.tokens[0].getLiteral()
return undefined;
}
public clearError()
{
super.clearError();
this.hint = "";
this.hasFix = false
}
public writeTo(tw:TokenWriter)
{
if (this.isPlaceholder()) {
tw.op("...");
return;
}
var isDigit = (o:Token) => o instanceof Operator && /^[0-9\.]$/.test((<Operator>o).data);
var prev = null;
this.tokens.forEach((t:Token) => {
if (isDigit(t)) {
if (!isDigit(prev))
tw.sep();
tw.op0((<Operator>t).data);
} else {
tw.node(t);
}
prev = t;
});
}
public forSearch()
{
var r = "";
var wasDigit = false;
this.tokens.forEach((t:Token) => {
var isDigit = false;
var s = t.forSearch();
if (t instanceof Operator)
isDigit = /[0-9\.]/.test(s);
if (!!s) {
if (wasDigit && isDigit)
r += s;
else
r += " " + s;
wasDigit = isDigit;
}
});
return r;
}
}
// -------------------------------------------------------------------------------------------------------
// Tokens
// -------------------------------------------------------------------------------------------------------
export class Token
extends AstNode
{
public tokenFix:string;
constructor() {
super()
}
public getText() { return null; }
public toString() { return this.getText(); }
public hasValue() { return true; }
public renderedAs:HTMLElement;
public forSearch() { return ""; }
public matches(d:AstNode) { return false; }
public getCall():Call { return null }
public getOperator():string { return null; }
public getFunArgs():string[] { return null; }
public getProperty():IProperty { return null; }
public getCalledProperty():IProperty { return null; }
public getLiteral():any { return null; }
public getStringLiteral():string
{
if (typeof this.getLiteral() == "string")
return this.getLiteral()
return null
}
public getNumberLiteral():number
{
if (typeof this.getLiteral() == "number")
return this.getLiteral()
return null
}
public getThing():Decl { return null; }
public getLocalDef():LocalDef {
var r = this.getThing()
if (r instanceof LocalDef) return <LocalDef>r
else return null;
}
public isDigit() { return false }
public getForwardedDecl():Decl
{
var p = this.getProperty()
if (!p) return null
return p.forwardsTo()
}
public eq(other:Token):boolean
{
return this.nodeType() == other.nodeType() && this.getText() == other.getText();
}
}
export class Expr
extends Token
{
public loc:StackOp;
public languageHint:string;
public enumVal:number;
constructor() {
super()
}
public flatten(prop:IProperty) { return [this]; }
public calledAction() : Action { return null; }
public anyCalledAction():Action { return this.calledAction() || this.calledExtensionAction() }
public calledExtensionAction():Action { return null }
public referencedRecordField() : RecordField { return null; }
public referencedRecord() : RecordDef { return null; }
public referencedData() : GlobalDef { return null; }
public referencedLibrary() : LibraryRef { return null; }
public getLiftedSetter() : IProperty { return null; }
public calledProp() : IProperty { return null; }
public assignmentInfo() : AssignmentInfo { return null; }
public isRefValue() { return false }
public allowRefUse() { return false }
public isEscapeDef() { return false }
}
export class Literal
extends Expr
{
public data:any;
public stringForm:string; // set for number literals
constructor() {
super()
}
public nodeType() { return "literal"; }
public getText() { return this.data + ""; }
public accept(v:NodeVisitor) { return v.visitLiteral(this); }
public getLiteral() { return this.data; }
public writeTo(tw:TokenWriter)
{
switch (typeof this.data) {
case "string":
tw.string(this.data);
break;
case "number":
// TODO get rid of 'e' notation
// note that this is pretty much dead code most of the time, as numbers are represented as sequences of Operator nodes
tw.op(this.data.toString());
break;
case "boolean":
tw.id(this.data ? "true" : "false");
break;
default:
Util.oops("cannot writeTo " + this.data);
break;
}
}
public forSearch() : string
{
switch (typeof this.data) {
case "string":
return this.data.toLowerCase();
case "number":
return this.data.toString();
case "boolean":
return this.data ? "true" : "false";
default:
Util.oops("cannot writeTo " + this.data);
return "";
}
}
}
export class Operator
extends Token
{
public data:string;
public call:Call;
public funArgs:LocalDef[];
constructor() {
super()
}
public getText() { return this.data; }
public nodeType() { return "operator"; }
public accept(v:NodeVisitor) { return v.visitOperator(this); }
public getOperator() { return this.data; }
public isDigit() { return /^[0-9.\-]$/.test(this.data) }
public funSpan:number;
public writeTo(tw:TokenWriter)
{
if (this.data == "(" || this.data == ")")
tw.op0(this.data);
else if (this.data == ",")
tw.op0(this.data).space();
else if (/^fun:/.test(this.data))
tw.op("fun:" + this.getFunArgs().map(idUrlQuote).join(","))
else
tw.op(this.data);
}
public forSearch() { return this.data; }
public getFunArgs():string[]
{
if (!/^fun:/.test(this.data))
return
if (this.funArgs)
return this.funArgs.map(p => p.getName())
return this.data.slice(4).split(",").map(idUrlUnquote)
}
}
export class PropertyRef
extends Token
{
public data:string;
public prop:IProperty;
public call:Call;
public skipArrow:boolean;
public fromOp:Operator;
constructor() {
super()
}
public nodeType() { return "propertyRef"; }
public accept(v:NodeVisitor) { return v.visitPropertyRef(this); }
public getText():string { return !this.prop ? this.data : this.prop.getName(); }
public getProperty() { return this.prop }
public getOrMakeProp():IProperty
{
if (this.prop) return this.prop;
return new UnresolvedProperty(api.core.Unknown, this.data)
}
static mkProp(p:IProperty)
{
var r = new PropertyRef();
r.prop = p;
return r;
}
public writeTo(tw:TokenWriter)
{
var c = tw.lastChar;
if (!/^[A-Za-z0-9_\)]$/.test(c))
tw.space();
tw.op0("\u2192").id0(this.getText());
}
public forSearch() { return this.getText().toLowerCase(); }
public matches(d:AstNode) {
if (!this.prop) return false;
if (this.prop.forwardsTo() == d)
return true;
if (d instanceof RecordField && (<RecordField>d).asProperty() == this.prop)
return true;
return false;
}
public getCall() { return this.call }
}
export class ThingRef
extends Expr
{
static placeholderPrefix = "\u0001need ";
public data:string;
public def:Decl;
public namespaceLibrary:LibraryRef;
public _namespaceLibraryName:string;
public _lastTypechecker:any;
constructor() {
super()
}
public getText() { return !this.def ? this.data : this.def.getName(); }
public shortName()
{
return !this.def ? null :
this.def instanceof SingletonDef ? this.def.getKind().shortName() : null;
}
public nodeType() { return "thingRef"; }
public isPlaceholder() { return this.getText() == "$skip" || this.getText() == "..."; }
public accept(v:NodeVisitor) { return v.visitThingRef(this); }
public forceLocal = false;
public getThing():Decl { return this.def; }
public isEscapeDef()
{
return this.def instanceof PlaceholderDef && !!(<PlaceholderDef>this.def).escapeDef
}
public namespaceLibraryName()
{
if (this.namespaceLibrary)
return (this._namespaceLibraryName = this.namespaceLibrary.getName())
return this._namespaceLibraryName
}
public writeTo(tw:TokenWriter)
{
if (this.def instanceof LocalDef)
tw.sep().op0("$").id0(this.getText());
else if (this.def instanceof PlaceholderDef)
tw.id("\u0001" + this.def.getName())
else if (this.namespaceLibraryName())
tw.id(this.getText()).op0("[").id("lib").id(this.namespaceLibraryName()).op0("]")
else
tw.id(this.getText());
}
public forSearch() { return this.getText().toLowerCase() }
public matches(d:AstNode) { return this.def == d; }
}
export class AssignmentInfo
{
public missingArguments = 0;
public definedVars:LocalDef[] = [];
public targets:Expr[] = [];
public sources:LocalDef[] = [];
public isPagePush = false;
public fixContextError:boolean;
}
export class Call
extends Expr
{
public propRef:PropertyRef;
public args:Expr[];
public _assignmentInfo:AssignmentInfo;
public runAsAsync:boolean;
public autoGet:boolean;
public isSynthetic:boolean;
public isShim:boolean;
public savedFix:Call;
public funAction:InlineAction;
public optionalConstructor:InlineActions;
public compiledTypeArgs:any;
// this is for break, return, and show
public topRetLocal:LocalDef;
public topAffectedStmt:Stmt;
public topPostCall:Expr;
constructor() {
super()
}
public children() { return this.args; }
public assignmentInfo() { return this._assignmentInfo; }
public nodeType() { return "call"; }
public getCalledProperty():IProperty { return this.prop(); }
public getText()
{
var res = this.propRef.getText() + "(";
for (var i = 0; i < this.args.length; ++i) {
if (i > 0) res += ", ";
res += this.args[i].getText();
}
res += ")";
return res;
}
public referencedRecord()
{
var prop = this.prop()
if (prop instanceof AST.RecordDef)
return <AST.RecordDef>prop
return null
}
public referencedRecordField()
{
var prop = this.prop()
if (!prop) return null
var fwd = prop.forwardsToStmt()
if (fwd instanceof RecordField)
return <RecordField>fwd
return null
}
public referencedData():AST.GlobalDef
{
if (this.prop() && this.prop().getCategory() == PropertyCategory.Data)
return <GlobalDef>this.prop().forwardsTo();
else
return null;
}
public referencedLibrary() : LibraryRef
{
var prop = this.prop()
if (prop instanceof LibraryRef)
return <LibraryRef>prop
return null
}
public getCall() { return this }
public getLiftedSetter()
{
var prop = this.prop()
if (!prop) return null
if (prop.getFlags() & PropertyFlags.Async) return null
if (prop.getParameters().length != 1) return null
var tp = prop.getResult().getKind()
if (tp.equals(api.core.Nothing)) return null
var setter = prop.parentKind.getProperty("set " + prop.getName())
if (setter &&
!(setter.getFlags() & PropertyFlags.Async) &&
setter.getResult().getKind().equals(api.core.Nothing)) {
var parms = setter.getParameters()
if (parms.length == 2 && parms[1].getKind().equals(tp))
return setter
}
return null
}
public isRefValue()
{
var gd = this.referencedData()
if (gd) return true
var rcf = this.referencedRecordField()
if (rcf && !rcf.isKey) return true;
// if (this.getLiftedSetter()) return true;
return false
}
public allowAssignment()
{
return this.allowRefUse() || !!this.getLiftedSetter();
}
public allowRefUse()
{
if (!this.isRefValue()) return false
var gd = this.referencedData()
if (gd && gd.readonly) return false
return true
}
public prop() { return this.propRef.prop; }
public calledProp() { return this.prop(); }
public flatten(prop:IProperty) : Expr[]
{
if (this.propRef.prop == prop) {
return this.args.collect((e:Expr) => e.flatten(prop));
} else {
return [this];
}
}
public accept(v:NodeVisitor) {
var p = this.prop()
if (!p || p.parentKind != api.core.Unknown) // fast path
return v.visitCall(this)
if (p == api.core.AssignmentProp)
return v.visitAssignment(this)
else if (p == api.core.ReturnProp)
return v.visitReturn(this)
else if (p == api.core.BreakProp)
return v.visitBreak(this)
else if (p == api.core.ContinueProp)
return v.visitContinue(this)
else if (p == api.core.ShowProp)
return v.visitShow(this)
return v.visitCall(this);
}
public calledAction():AST.Action
{
if (this.prop() && this.prop().getCategory() == PropertyCategory.Action)
return <Action>this.prop().forwardsTo();
else
return null;
}
public calledExtensionAction():AST.Action
{
var prop = this.prop()
if (prop instanceof ExtensionProperty)
return (<ExtensionProperty>prop).shortcutTo
var act = this.calledAction()
if (act && act.isExtensionAction())
return act.extensionForward();
return null
}
public awaits()
{
return !this.runAsAsync && this.prop() && !!(this.prop().getFlags() & PropertyFlags.Async);
}
}
// -------------------------------------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------------------------------------
export function mkLocal(name:string, k:Kind)
{
var r = new LocalDef();
r.setName(name);
r._kind = k;
return r;
}
export function mkLit(v:any)
{
var r = new Literal();
r.data = v;
return r;
}
export function mkOp(v:string) : Operator
{
var o = new Operator();
o.data = v;
return o;
}
export function mkFunOp(args:string[]) : Operator
{
return mkOp("fun:" + args.map(idUrlQuote).join(","))
}
export function mkThing(v:string, forceLocal = false) : Expr
{
if (v == "true" || v == "false")
return mkLit(v == "true");
var t = new ThingRef();
t.data = v;
t.forceLocal = forceLocal;
return t;
}
export function mkLocalRef(l:LocalDef)
{
return mkThing(l.getName(), true)
}
export function mkPlaceholderByKind(k:Kind)
{
return mkPlaceholder(<any>{ getName: () => "this", getKind: () => k })
}
export function mkPlaceholder(p:PropertyParameter)
{
var name = p.getName();
if (name == "this" || p.parentProperty.getInfixPriority() > 0 || name == p.getKind().getStemName())
name = "";
var def = new PlaceholderDef();
def.label = name;
def._kind = p.getKind();
var t = new ThingRef();
t.def = def;
t.data = def.getName();
return t;
}
export function mkPropRef(v:string) : PropertyRef
{
var p = new PropertyRef();
p.data = v;
return p;
}
export function mkSingletonDef(n:string, k:Kind) : SingletonDef
{
var s = new SingletonDef();
s.setName(n);
s._kind = k;
if (k.isPrivate) s._isBrowsable = false;
return s;
}
export function mkTok(json:any) : Token
{
switch (json.type) {
case "literal": return mkLit(json.data);
case "operator": return mkOp(json.data);
case "propertyRef": return mkPropRef(json.data);
case "thingRef": return mkThing(json.data, json.forceLocal);
default: Util.die();
}
}
export function mkParam(name:string, k:Kind) { return mkLocal(name, k); }
export function mkPlaceholderThingRef() { return mkThing("$skip"); }
export function mkExprStmt(expr:ExprHolder, p:Parser = null)
{
var r = new ExprStmt();
if(p) {
r.setStableName(p.consumeLabel());
}
r.expr = expr;
return r;
}
export function exprToStmt(expr: Expr)
{
var r = new ExprHolder();
r.tokens = [];
r.locals = [];
r.parsed = expr;
return mkExprStmt(r);
}
export function mkWhere(expr:ExprHolder)
{
var r = new Where();
r.condition = expr;
return r;
}
export function mkFakeCall(prop:PropertyRef, args:Expr[] = [])
{
var t = new Call();
t.propRef = prop;
t.args = args;
t.isSynthetic = true;
return t;
}
export function mkCall(prop:PropertyRef, a:Expr[])
{
var t = new Call();
Util.assert(a.length > 0)
t.propRef = prop;
prop.call = t;
t.args = a;
return t;
}
export function idUrlQuote(s:string)
{
var sb = "";
for (var i = 0; i < s.length; ++i) {
var c = s.charAt(i);
if (/[A-Za-z0-9]/.test(c))
sb += c;
else if (c == " ")
sb += "_";
else
sb += "-" + (c.charCodeAt(0)|0x10000).toString(16).slice(-4);
}
return sb;
}
export function idUrlUnquote(s:string)
{
var r = ""
for (var i = 0; i < s.length; ++i) {
var c = s.charAt(i);
if (c == "_") {
r += " ";
} else if (c == "/" || c == "-") {
r += String.fromCharCode(parseInt(s.slice(i + 1, i + 5), 16))
i += 4
} else {
r += c;
}
}
return r;
}
export function isProperKind(k:Kind)
{
return k && k != api.core.Unknown && !(k instanceof MultiplexKind)
}
// -------------------------------------------------------------------------------------------------------
// Visitor
// -------------------------------------------------------------------------------------------------------
export class NodeVisitor
{
public visitAstNode(node:AstNode):any { return null; }
public visitToken(tok:Token) { return this.visitAstNode(tok); }
public visitOperator(n:Operator) { return this.visitToken(n); }
public visitPropertyRef(n:PropertyRef) { return this.visitToken(n); }
public visitExpr(tok:Expr) { return this.visitToken(tok); }
public visitLiteral(n:Literal) { return this.visitExpr(n); }
public visitThingRef(n:ThingRef) { return this.visitExpr(n); }
public visitCall(n:Call) { return this.visitExpr(n); }
public visitShow(n:Call) { return this.visitCall(n); }
public visitBreak(n:Call) { return this.visitCall(n); }
public visitContinue(n:Call) { return this.visitCall(n); }
public visitReturn(n:Call) { return this.visitCall(n); }
public visitAssignment(n:Call) { return this.visitCall(n); }
public visitExprHolder(n:ExprHolder) { return this.visitAstNode(n); }
public visitStmt(n:Stmt) { return this.visitAstNode(n); }
public visitComment(n:Comment) { return this.visitStmt(n); }
public visitBlock(n:Block) { return this.visitStmt(n); }
public visitCodeBlock(n:CodeBlock) { return this.visitBlock(n); }
public visitConditionBlock(n:ConditionBlock) { return this.visitBlock(n); }
public visitParameterBlock(n:ParameterBlock) { return this.visitBlock(n); }
public visitResolveBlock(n:ResolveBlock) { return this.visitBlock(n); }
public visitBindingBlock(n:BindingBlock) { return this.visitBlock(n); }
public visitFieldBlock(n:FieldBlock) { return this.visitBlock(n); }
public visitInlineActionBlock(n:InlineActionBlock) { return this.visitBlock(n); }
public visitFor(n:For) { return this.visitStmt(n); }
public visitForeach(n:Foreach) { return this.visitStmt(n); }
public visitWhile(n:While) { return this.visitStmt(n); }
public visitBox(n:Box) { return this.visitStmt(n); }
public visitAnyIf(n:If) { return this.visitStmt(n); }
public visitIf(n:If) { return this.visitAnyIf(n); }
public visitElseIf(n:If) { return this.visitAnyIf(n); }
public visitForeachClause(n:ForeachClause) { return this.visitStmt(n); }
public visitWhere(n:Where) { return this.visitForeachClause(n); }
public visitExprStmt(n:ExprStmt) { return this.visitStmt(n); }
public visitInlineActions(n:InlineActions) { return this.visitStmt(n); }
public visitInlineAction(n:InlineAction) { return this.visitStmt(n); }
public visitOptionalParameter(n:OptionalParameter) { return this.visitStmt(n); }
public visitActionParameter(n:ActionParameter) { return this.visitStmt(n); }
public visitActionHeader(n:ActionHeader) { return this.visitStmt(n); }
public visitDecl(n:Decl) { return this.visitStmt(n); }
public visitGlobalDef(n:GlobalDef) { return this.visitDecl(n); }
public visitLibraryRef(n:LibraryRef) { return this.visitDecl(n); }
public visitRecordDef(n:RecordDef) { return this.visitDecl(n); }
public visitSingletonDef(n:SingletonDef) { return this.visitDecl(n); }
public visitAction(n:Action) { return this.visitDecl(n); }
public visitLocalDef(n:LocalDef) { return this.visitDecl(n); }
public visitApp(n:App) { return this.visitDecl(n); }
public visitKindBinding(n:KindBinding) { return this.visitStmt(n); }
public visitActionBinding(n:ActionBinding) { return this.visitStmt(n); }
public visitResolveClause(n:ResolveClause) { return this.visitStmt(n); }
public visitRecordField(n:RecordField) { return this.visitStmt(n); }
public visitFieldName(n: AST.FieldName) { return this.visitLiteral(n); }
public visitRecordName(n: AST.RecordName) { return this.visitLiteral(n); }
public visitInlineStmt(n: AST.InlineStmt) { return this.visitStmt(n); }
public dispatch(n:AstNode) { return n.accept(this); }
public visitChildren(n:AstNode)
{
n.children().forEach((c) => c.accept(this));
}
}
class SearchForStableName
extends AST.NodeVisitor
{
public found: AST.AstNode;
constructor(public name: string)
{
super()
}
visitStmt(s:AST.Stmt)
{
if (s.getStableName() == this.name)
this.found = s;
this.visitChildren(s);
}
}
class SearchForId
extends AST.NodeVisitor
{
public foundNode:AST.AstNode;
public lastDecl:AST.Decl;
public lastStmt:AST.Stmt;
constructor(public id:string)
{
super()
}
check(n:AST.AstNode)
{
if (n.stableId == this.id)
this.foundNode = n;
}
visitDecl(d:AST.Decl)
{
this.check(d);
if (!this.foundNode) {
this.lastDecl = d;
this.visitChildren(d);
}
return null;
}
visitExprHolder(eh:AST.ExprHolder)
{
if (eh.parsed) this.dispatch(eh.parsed)
this.visitAstNode(eh)
}
visitAstNode(n:AST.AstNode)
{
this.check(n);
this.visitChildren(n)
}
visitStmt(s:AST.Stmt)
{
if (!this.foundNode) {
this.lastStmt = s;
this.check(s);
this.visitChildren(s);
}
return null;
}
}
export class StatsComputer
extends NodeVisitor
{
stmtCount = 0;
weight = 0;
action:Action;
constructor() { super() }
visitAction(n:Action)
{
this.action = n;
this.visitChildren(n);
if (n.isPage()) this.weight = 2;
else if (n.isEvent()) this.weight = 1;
else if (n.isPrivate) this.weight = 0;
else if (n.isMainAction()) this.weight = 4;
else this.weight = 3;
return null
}
visitBlock(b:Block)
{
this.visitChildren(b);
return null
}
visitStmt(s:Stmt)
{
if (!s.isPlaceholder())
this.stmtCount++;
this.visitChildren(s);
return null
}
}
export class StackOp
{
public type:string;
public infixProperty:IProperty;
public propertyRef:PropertyRef;
public tokens:Token[];
public op:string;
public expr:Expr = null;
public beg = 0;
public len = 0;
public prioOverride:number = 0;
public prio()
{
if (this.prioOverride != 0) return this.prioOverride;
if (!this.infixProperty) return 0;
return this.infixProperty.getInfixPriority();
}
public markError(msg:string)
{
var i = this.beg;
if (i < 0 || i >= this.tokens.length)
i = this.tokens.length - 1;
var end = i + this.len;
if (end <= i || end > this.tokens.length)
end = this.tokens.length;
while (i < end) {
this.tokens[i].setError(msg);
i++;
}
}
public copyFrom(other:StackOp)
{
this.tokens = other.tokens;
this.beg = other.beg;
this.len = other.len;
}
}
export class VariableFinder
extends NodeVisitor
{
private readLocals0:LocalDef[];
private writtenLocals0:LocalDef[];
readLocals:LocalDef[];
writtenLocals:LocalDef[];
lastStmt:Stmt;
saveWrites = false;
// globals read or written, including both global variables and records
readGlobals: Decl[];
writtenGlobals: Decl[];
visitedActions: Action[];
getGlobals: boolean = false;
add(s:Stmt, l:Decl, lst:LocalDef[])
{
if (l instanceof LocalDef && lst.indexOf(<LocalDef>l) < 0) {
if (this.saveWrites && s)
(<LocalDef>l)._lastWriteLocation = s
lst.push(<LocalDef>l);
}
}
private getLocal(e:Token):LocalDef
{
if (e instanceof ThingRef) {
var d = (<ThingRef>e).def;
if (d instanceof LocalDef) return <LocalDef>d;
}
return null;
}
static realName(g: Decl) {
return "$" + g.getStableName();
}
public writtenGlobalNames(): string[] {
return this.writtenGlobals.map(VariableFinder.realName);
}
public readGlobalNames(): string[] {
return this.readGlobals.map(VariableFinder.realName);
}
addGlobal(l: Decl, lst:Decl[])
{
if (lst.indexOf(l) < 0) lst.push(l);
}
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;
}
visitStmt(s:Stmt)
{
this.lastStmt = s
this.visitChildren(s)
return null;
}
visitAction(n: Action) {
if (this.visitedActions && this.visitedActions.indexOf(n) < 0) {
this.visitedActions.push(n);
super.visitAction(n);
}
}
visitThingRef(n:ThingRef)
{
this.add(null, n.def, this.readLocals0);
return null;
}
visitCall(n: Call) {
var prop = n.prop();
if (prop == api.core.AssignmentProp) {
n.args[0].flatten(api.core.TupleProp).forEach((e) => {
var l = this.getLocal(e);
if (l) this.add(null, l, this.writtenLocals0);
else if (this.getGlobals) {
var g = this.getGlobal(e);
if (g) this.addGlobal(g, this.writtenGlobals);
else this.dispatch(e);
} else this.dispatch(e);
});
this.dispatch(n.args[1]);
} else if (this.getGlobals) {
var act = n.calledAction();
if (act && this.visitedActions && this.visitedActions.indexOf(act) < 0) {
var readLocals0 = this.readLocals0;
var writtenLocals0 = this.writtenLocals0;
var readLocals = this.readLocals;
var writtenLocals = this.writtenLocals;
var readGlobals = this.readGlobals;
var writtenGlobals = this.writtenGlobals;
this.traverse(act, true, this.visitedActions);
readGlobals.forEach((g) => this.addGlobal(g, this.readGlobals));
writtenGlobals.forEach((g) => this.addGlobal(g, this.writtenGlobals));
this.readLocals0 = readLocals0;
this.writtenLocals0 = writtenLocals0;
this.readLocals = readLocals;
this.writtenLocals = writtenLocals;
}
if (prop instanceof GlobalDef) {
this.addGlobal(<GlobalDef>prop, this.readGlobals);
this.addGlobal(<GlobalDef>prop, this.writtenGlobals);
} else if (prop instanceof RecordDef) {
// consider record access as both read and write.
// Will differentiate them in the future
this.addGlobal(<RecordDef>prop, this.readGlobals);
this.addGlobal(<RecordDef>prop, this.writtenGlobals);
} else {
super.visitCall(n);
}
} else {
super.visitCall(n);
}
return null;
}
visitInlineAction(n:InlineAction)
{
n.inParameters.forEach(i => this.add(n, i, this.writtenLocals))
super.visitInlineAction(n)
}
visitExprHolder(n:ExprHolder)
{
var stmt = this.lastStmt
this.readLocals0 = [];
this.writtenLocals0 = [];
if (n.parsed)
this.dispatch(n.parsed);
// maybe there were syntax errors? also look at the raw tokens just in case
n.tokens.forEach((t) => {
var l = this.getLocal(t);
if (l && this.readLocals0.indexOf(l) < 0 && this.writtenLocals0.indexOf(l) < 0)
this.add(null, l, this.readLocals0);
});
this.readLocals0.forEach((l) => this.add(null, l, this.readLocals));
this.writtenLocals0.forEach((l) => this.add(stmt, l, this.writtenLocals));
return null;
}
visitFor(n:For)
{
this.add(n, n.boundLocal, this.writtenLocals);
super.visitFor(n);
return null;
}
visitForeach(n:Foreach)
{
this.add(n, n.boundLocal, this.writtenLocals);
super.visitForeach(n);
return null;
}
traverse(node: AstNode, getGlobals: boolean = false, visitedActions: Action[] = []) {
this.readLocals = [];
this.writtenLocals = [];
this.getGlobals = getGlobals;
if (getGlobals) {
this.readGlobals = [];
this.writtenGlobals = [];
this.visitedActions = visitedActions;
}
this.dispatch(node);
}
}
export class Extractor
extends VariableFinder
{
extractedAction:Action;
numAwait = 0;
failed:Stmt[] = [];
constructor(public extracted:CodeBlock, public action:Action, public callPlaceholder:ExprStmt, public name:string)
{
super();
}
visitCall(c:Call)
{
super.visitCall(c)
if (c.awaits())
this.numAwait++;
}
run()
{
AST.visitStmtsCtx({ inLoop: false, inHandler: false }, this.extracted, (ctx, s) => {
if (s.calcNode() && s.calcNode().parsed) {
var prop = s.calcNode().parsed.calledProp()
if (!ctx.inLoop && (prop == api.core.BreakProp || prop == api.core.ContinueProp))
this.failed.push(s)
if (!ctx.inHandler && (prop == api.core.ReturnProp))
this.failed.push(s)
}
ctx = Util.jsonClone(ctx)
if (s instanceof Loop)
ctx.inLoop = true
if (s instanceof InlineAction) {
ctx.inHandler = true
ctx.inLoop = true
}
return ctx
})
if (this.failed.length > 0) return
this.saveWrites = true
this.traverse(this.extracted);
var hasAwait = this.numAwait > 0;
var readSub = this.readLocals;
var writtenSub = this.writtenLocals;
this.saveWrites = false
this.traverse(this.action.body);
this.action.getInParameters().forEach((p) => this.add(null, p.local, this.writtenLocals));
if (this.action.modelParameter)
this.add(null, this.action.modelParameter.local, this.writtenLocals)
this.action.getOutParameters().forEach((p) => this.add(null, p.local, this.readLocals));
var outgoing = writtenSub.filter((l) => this.readLocals.indexOf(l) >= 0);
var incoming = readSub.filter((l) => this.writtenLocals.indexOf(l) >= 0);
var useReturn = outgoing.length == 1
if (outgoing.length > 1) {
this.failed = outgoing.map(l => l._lastWriteLocation)
return
}
var refLocal = (l:LocalDef) => mkThing(l.getName());
var copyNames:any = {}
var extractedStmts = this.extracted.stmts.slice(0);
var extractedAction = <Action>Parser.parseDecl("action go() { meta private; }");
extractedAction.setName(this.name);
extractedAction.body = this.extracted;
extractedAction.isAtomic = !hasAwait;
extractedAction.header.inParameters.pushRange(incoming.map((l) => new ActionParameter(l)));
outgoing.forEach((l) => {
if (useReturn || incoming.indexOf(l) >= 0) {
var copy = mkLocal(this.action.nameLocal(l.getName(), copyNames), l.getKind());
copyNames[copy.getName()] = true;
var stmt = Parser.emptyExprStmt();
if (useReturn)
stmt.expr.tokens = [AST.mkOp("return"), refLocal(l)];
else
stmt.expr.tokens = [refLocal(copy), mkOp(":="), refLocal(l)];
extractedStmts.push(stmt);
l = copy;
}
extractedAction.header.outParameters.push(new ActionParameter(l));
});
this.extracted.setChildren(extractedStmts);
var res = Parser.parseDecl(extractedAction.serialize());
this.extractedAction = <Action>res;
var toks = this.callPlaceholder.expr.tokens;
if (outgoing.length > 0) {
outgoing.forEach((l, i) => {
if (i > 0) toks.push(mkOp(","));
toks.push(refLocal(l));
});
toks.push(mkOp(":="));
}
toks.push(mkThing("code"));
toks.push(mkPropRef(this.name));
if (incoming.length > 0) {
toks.push(mkOp("("));
incoming.forEach((l, i) => {
if (i > 0) toks.push(mkOp(","));
toks.push(refLocal(l));
});
toks.push(mkOp(")"));
}
}
}
// Find out what methods are called (transitively) from the node
export class MethodFinder
extends NodeVisitor
{
called: Action[];
visitAstNode(n: AstNode) {
this.visitChildren(n);
return null;
}
visitCall(n: Call) {
var act = n.calledAction();
if (act && this.called.indexOf(act) < 0) {
this.called.push(act);
// scan the called action
this.traverse(act, this.called);
}
super.visitCall(n);
}
visitExprHolder(n: ExprHolder) {
if (n.parsed) this.dispatch(n.parsed);
}
traverse(node: AstNode, called: Action[] = []) {
this.called = called;
this.dispatch(node);
}
}
// remove comments, skip statments, useless meta
// used for loose script equality
export class ScriptCompacter
extends TDev.AST.NodeVisitor
{
constructor () {
super()
}
static compact(s: App) {
s.setMeta("stableNames", null);
s.accept(new ScriptCompacter());
return s;
}
visitDecl(d: AST.Decl) {
this.visitChildren(d);
return null;
}
visitAction(n: TDev.AST.Action) {
this.visitChildren(n);
return null
}
visitBlock(n: TDev.AST.Block) {
n.stmts = n.stmts.filter((s) => s.nodeType() != "comment" && !s.isPlaceholder());
this.visitChildren(n);
}
visitStmt(s:TDev.AST.Stmt)
{
this.visitChildren(s);
return null
}
}
export class DeepVisitor
extends TDev.AST.NodeVisitor
{
includeUnreachable = false;
localActions:Action[] = [];
libActions:LibraryRefAction[] = [];
useBuiltinProperty(p:IProperty)
{
}
useKind(k:Kind)
{
}
private useAction(a:Action)
{
if (!a) return;
if (a.visitorState) return;
a.visitorState = true;
if (a instanceof LibraryRefAction) {
this.libActions.push(<LibraryRefAction>a);
} else {
this.localActions.push(a);
}
}
static clearVisitorState(app:App)
{
app.libraries().forEach((l) => {
var pubs = l.getPublicActions();
if (pubs)
pubs.forEach((a) => a.visitorState = null);
if (l.resolved)
l.resolved.things.forEach((a) => a.visitorState = null)
})
app.things.forEach((a) => a.visitorState = null)
}
visitAstNode(n:AstNode)
{
super.visitChildren(n);
}
visitGlobalDef(g:GlobalDef)
{
this.useKind(g.getKind())
}
visitRecordField(rf:RecordField)
{
this.useKind(rf.dataKind)
}
runOnDecl(d:Decl)
{
if (d.visitorState) return;
if (d instanceof Action) this.useAction(<Action>d);
else {
d.visitorState = true;
this.dispatch(d);
}
}
visitRecordDef(r:RecordDef)
{
this.useKind(r.entryKind);
super.visitChildren(r);
}
private visitDeclOrProp(c:Call, p:IProperty)
{
var d:Decl = null
if (c)
d = c.calledExtensionAction()
if (!d)
d = p.forwardsTo();
if (d) {
this.runOnDecl(d);
} else {
this.useBuiltinProperty(p);
}
}
visitOperator(o:Operator)
{
var c = o.call
if (c) {
if (c._assignmentInfo && c._assignmentInfo.targets) {
c._assignmentInfo.targets.forEach(t => {
var setter = t.getLiftedSetter()
if (setter) this.visitDeclOrProp(mkFakeCall(PropertyRef.mkProp(setter), []), setter)
})
}
}
super.visitOperator(o)
}
visitPropertyRef(n:PropertyRef)
{
var p = n.prop
if (!p) return;
this.visitDeclOrProp(n.getCall(), p)
}
visitActionParameter(ap:ActionParameter)
{
this.useKind(ap.getKind());
super.visitChildren(ap);
}
visitAction(a:Action)
{
super.visitChildren(a);
}
private traverseActions()
{
while (this.localActions.length > 0) {
var a = this.localActions.pop();
this.dispatch(a);
}
}
private findActionMapping(app:App)
{
app.libraries().forEach((l) => {
if (!l.resolved) return;
var acts = this.libActions.filter((a) => a.parentLibrary() == l);
if (acts.length == 0) return;
var names = {}
acts.forEach((a) => names[a.getName()] = true)
l.resolved.allActions().forEach((a) => {
if (names.hasOwnProperty(a.getName()))
this.useAction(a);
});
});
}
secondaryRun(app: App)
{
}
run(app: App)
{
DeepVisitor.clearVisitorState(app);
app.libraries().forEach((l) => {
l.resolveClauses.forEach((r:ResolveClause) => {
r.actionBindings.forEach((b:Binding) => {
if (b instanceof ActionBinding) {
this.useAction((<ActionBinding>b).actual);
}
});
});
});
if (this.includeUnreachable)
app.things.forEach((d) => this.runOnDecl(d))
else
app.allActions().forEach((a:Action) => {
if (a.isEvent() || (!a.isPrivate || a.isTest()))
this.useAction(a);
})
this.secondaryRun(app);
this.traverseActions();
if (this.includeUnreachable) {
app.libraries().forEach((l) => {
if (!l.resolved) return;
l.resolved.things.forEach((d) => this.runOnDecl(d))
});
} else {
this.findActionMapping(app);
}
this.traverseActions();
}
}
export class PlatformDetector
extends DeepVisitor
{
propsByName:any = {};
featuresByName:any = {};
errors = "";
platform:PlatformCapability = PlatformCapability.None;
requiredPlatform:PlatformCapability = PlatformCapability.All;
compatMode = true;
useBuiltinProperty(p:IProperty)
{
var n = p.parentKind.toString() + "->" + p.getName();
if (this.propsByName[n]) return;
this.propsByName[n] = p;
var plat = this.compatMode ? p.getExplicitCapability() : p.getCapability();
if (p.parentKind.toString() == "Invalid")
plat = PlatformCapability.None;
this.useFeature(plat, n)
}
useKind(k:Kind)
{
// just using these types doesn't necessarily mean you're using caps
// for compat with C# parser
if (!this.compatMode)
this.useFeature(k.generalCapabilities, "type " + k.getName())
}
usePlatform(plat:PlatformCapability, name:string)
{
this.platform |= plat;
if ((plat & this.requiredPlatform) != plat) {
this.errors += name + " is not supported on this platform.\n";
}
}
useFeature(plat:PlatformCapability, name:string)
{
if (this.featuresByName[name]) return;
this.featuresByName[name] = true;
this.usePlatform(plat, name);
}
visitAction(a:Action)
{
super.visitAction(a);
if (a.isEvent())
this.useFeature(a.eventInfo.type.platform, a.getName())
}
visitRecordDef(r: RecordDef) {
if (r.cloudEnabled)
this.useFeature(PlatformCapability.CloudData, "cloud records")
}
visitApp(a:App)
{
super.visitApp(a)
}
}
export class DeclRefFinder
extends NodeVisitor
{
public found = false;
constructor(public decl:Decl)
{
super();
}
visitAstNode(n:AstNode)
{
if (this.found) return;
this.visitChildren(n);
}
visitToken(t:Token)
{
this.found = this.found || t.matches(this.decl);
}
}
export class ExprVisitor
extends NodeVisitor
{
visitAstNode(n:AstNode)
{
this.visitChildren(n);
}
visitExprHolder(eh:ExprHolder)
{
if (eh.parsed) this.dispatch(eh.parsed)
}
}
export class ShallowMethodFinder
extends ExprVisitor
{
called: Action[] = [];
visitCall(n: Call) {
var act = n.calledAction();
if (act && this.called.indexOf(act) < 0)
this.called.push(act);
super.visitCall(n);
}
}
class StmtVisitor<T>
extends NodeVisitor
{
t:T;
constructor(public f:(ctx:T, s:Stmt)=>T)
{
super()
}
visitStmt(s:Stmt)
{
var prev = this.t
this.t = this.f(prev, s)
this.visitChildren(s)
this.t = prev
}
}
export function visitStmts(s:Stmt, f:(s:Stmt)=>void)
{
var v = new StmtVisitor<number>((ctx, s) => { f(s); return ctx; })
v.dispatch(s)
}
export function visitStmtsCtx<T>(ctx:T, s:Stmt, f:(ctx:T, s:Stmt)=>T)
{
var v = new StmtVisitor(f)
v.t = ctx
v.dispatch(s)
}
export function visitExprHolders(s:Stmt, f:(stmt:Stmt, eh:ExprHolder)=>void)
{
visitStmts(s, stmt => {
if (stmt.calcNode()) f(stmt, stmt.calcNode())
})
}
class AllNodeVisitor
extends NodeVisitor
{
constructor(private f:(s:AstNode)=>void)
{
super()
}
visitAstNode(n:AstNode)
{
this.f(n)
this.visitChildren(n);
}
visitExprHolder(eh:ExprHolder)
{
this.f(eh)
if (eh.parsed) this.dispatch(eh.parsed)
this.visitChildren(eh)
}
}
export function visitNodes(s:Stmt, f:(s:AstNode)=>void)
{
var v = new AllNodeVisitor(f)
v.dispatch(s)
}
export interface LoopStmt {
body: CodeBlock;
}
export interface LoadScriptResult
{
numErrors:number;
status:string;
parseErrs: string[];
errLibs:App[];
prevScript:App;
numLibErrors:number;
}
export function loadScriptAsync(getText:(s:string)=>Promise, currentId = ""):Promise
{
var rp = new PromiseInv();
var res:LoadScriptResult = { numErrors: 0, status: "", parseErrs: [], errLibs: [], prevScript: Script, numLibErrors: 0 }
var problem = (msg) => {
res.numErrors++;
res.status += " " + msg + "\n";
}
getText(currentId).done((text) => {
if (!text) return;
var app = Parser.parseScript(text, res.parseErrs);
var byId:any = {}
app.libraries().forEach((lib) => {
var id = lib.getId();
if (id) byId[id] = getText(id);
})
Promise.join(byId).then((byId) => {
res.prevScript = Script;
setGlobalScript(app);
Script.isTopLevel = true;
var prevPlatform = Script.getPlatformRaw();
if (prevPlatform & PlatformCapability.Current)
Script.setPlatform(PlatformCapability.All);
Script.localGuid = Util.guidGen();
if (res.parseErrs.length > 0)
problem("Parse errors");
var resolved = {}
var errLibs = []
var hasEmptyLib = false;
Script.libraries().forEach((lib) => {
var id = lib.getId()
if (!id) {
lib.setError("TD131: unbound library")
problem("Library '" + lib.getName() + "' is unbound");
res.numLibErrors++;
return;
} else if (id == "sixvorgj") {
hasEmptyLib = true;
}
if (!resolved[id] && byId[id]) {
var libParseErrs = [];
var app = Parser.parseScript(byId[id], libParseErrs);
app.isTopLevel = true;
if (libParseErrs.length > 0)
problem("Parse errors in library " + id);
if (lib.guid) app.localGuid = lib.guid;
app.setPlatform(PlatformCapability.All);
var numErr = TypeChecker.tcScript(app, true);
app.things.forEach(t => { t.isExternal = true })
if (numErr > 0) {
problem("Library " + id + " has errors");
res.numLibErrors++;
res.errLibs.push(app);
}
resolved[id] = app;
}
if (resolved[id]) {
lib.resolved = resolved[id];
} else {
lib.setError("TD132: cannot bind library")
res.numLibErrors++;
problem("Library " + id + " not provided");
}
});
TypeChecker.tcScript(Script, false, false);
Script.setStableNames();
Script.things.forEach((th) => {
if (th.hasErrors())
problem("Declaration '" + th.getName() + "' has errors");
})
Script.setPlatform(prevPlatform);
if (res.numErrors && hasEmptyLib)
res.numLibErrors++;
rp.success(res);
}, rp.error).done(x => x, rp.error);
}, rp.error)
return rp;
}
class ErrorChecker
extends NodeVisitor
{
lastErrorNode:AstNode;
surplusErrors = 0;
mismatches = 0;
missingErrors = 0;
matches = 0;
resetError()
{
if (this.lastErrorNode)
this.surplusErrors++;
this.lastErrorNode = null;
}
expectError(node:Stmt, rx:string)
{
if (this.lastErrorNode) {
var err = this.lastErrorNode.getError();
if (new RegExp(rx).test(err)) {
this.lastErrorNode.errorIsOk = true;
this.matches++;
} else {
this.mismatches++;
node.setError("TD133: the error message doesn't match")
}
this.lastErrorNode = null;
} else {
this.missingErrors++;
node.setError("TD134: no error to match")
}
}
visitComment(n:Comment)
{
n.errorIsOk = false;
var m = /^E: (.*)/.exec(n.text)
if (m) {
this.expectError(n, m[1]);
} else {
super.visitComment(n);
}
}
visitBlock(n:Block)
{
// no resetError()
n.errorIsOk = false;
this.visitChildren(n);
}
visitStmt(n:Stmt)
{
n.errorIsOk = false;
if (n.parent instanceof CodeBlock || n.getError())
this.resetError();
if (n.getError())
this.lastErrorNode = n;
this.visitChildren(n);
}
visitDecl(d:Decl)
{
this.resetError();
if (d.getError())
this.lastErrorNode = d;
this.visitChildren(d);
this.resetError();
}
}
export function runErrorChecker(a:Action): boolean
{
var e = new ErrorChecker();
e.dispatch(a);
return e.surplusErrors + e.mismatches + e.missingErrors == 0;
}
export class FindErrorVisitor extends NodeVisitor
{
firstError:Stmt;
public visitStmt(s:Stmt)
{
if (this.firstError) return;
if (s.getError()) this.firstError = s
else this.visitChildren(s)
}
public visitDecl(d:Decl) { this.visitChildren(d) }
static run(n:AstNode)
{
var vis = new FindErrorVisitor()
vis.dispatch(n)
return vis.firstError
}
}
export class IntelliCollector extends NodeVisitor
{
props:any = {};
topicPath:string;
public visitAstNode(n:AstNode) {
this.visitChildren(n);
}
public visitAction(a:Action)
{
if (a._skipIntelliProfile)
return
if (a.isAtomic)
this.incr("scriptPropertiesPropertyAtomic");
a.getInParameters().forEach(ai => {
this.incr(ai.getKind().getName());
})
a.getOutParameters().forEach(ai => {
this.incr(ai.getKind().getName());
})
this.visitChildren(a);
}
private incrKind(k: Kind) {
if (k && k != api.core.Unknown)
this.incr(k.getName());
}
private incr(n:string)
{
if (!n) return;
if (!this.props.hasOwnProperty(n))
this.props[n] = 0;
this.props[n]++;
}
public visitExprHolder(eh:ExprHolder)
{
if (eh.parsed)
this.dispatch(eh.parsed)
}
public visitExpr(expr: Expr) {
this.incrKind(expr.getKind());
super.visitExpr(expr);
}
public visitCall(c:Call)
{
if (c.prop())
this.incr(c.prop().usageKey())
this.visitChildren(c);
}
public visitThingRef(t:ThingRef)
{
if (t.def)
this.incr(t.def.usageKey())
}
public visitGlobalDef(t: GlobalDef)
{
this.incr("addNewButton");
this.incrKind(t.getKind());
if (t.isResource) {
this.incr("artSection");
if (t.getKind() == api.core.Picture || t.getKind() == api.core.Sound) {
this.incr("calcSearchArt");
this.incr("searchArtRefactoring");
}
}
else {
this.incr("dataSection");
this.incr("promoteRefactoring");
}
super.visitGlobalDef(t);
}
public visitRecordDef(t: RecordDef)
{
this.incr("addNewButton");
this.incr("persistanceRadio");
this.incrKind(t.getKind());
switch (t.recordType) {
case RecordType.Decorator:
case RecordType.Object:
this.incr("recordsSection"); break;
case RecordType.Index:
case RecordType.Table:
this.incr("databaseSection"); break;
}
super.visitRecordDef(t);
}
public visitComment(c:Comment)
{
var dummy = c.text.replace(/#allow:(\w+)/, (match:string, feature:string) => {
this.incr(feature)
return ""
})
// usage of comments does not imply that they are allowed so no super call
var dummy2 = c.text.replace(/\{widgets:([\w,]*)\}/, (match:string, features:string) => {
this.incr("tutorialWidgets");
features.split(',').forEach(feature => this.incr(feature));
return ""
})
var dummy2 = c.text.replace(/\{flags:([\w,]*)\}/, (match:string, features:string) => {
this.incr("tutorialFlags");
features.split(',').forEach(feature => this.incr("flag:" + feature));
return ""
})
var dummy2 = c.text.replace(/\{topic:([\w-\/]*)\}/, (match:string, top:string) => {
this.topicPath = top;
return ""
})
}
public visitExprStmt(e:ExprStmt)
{
if (e.isVarDef()) this.incr("var");
super.visitExprStmt(e);
}
public visitStmt(s:Stmt)
{
if (!(s instanceof App))
this.incr(s.nodeType())
super.visitStmt(s)
}
}
export class IntelliProfile
{
public allowAllLibraries : boolean = true;
private properties:any; // p.helpTopic() => #occurences
public merge(other : IntelliProfile)
{
this.allowAllLibraries = this.allowAllLibraries && other.allowAllLibraries;
if (other.properties) {
if (!this.properties) this.properties = Util.clone(other.properties);
else Object.keys(other.properties).forEach(k => {
if (this.properties[k]) this.properties[k] += other.properties[k];
else this.properties[k] = other.properties[k];
});
}
}
static helpfulProperties = {
ColorsBlack: 1,
ColorsBlue: 1,
ColorsCyan: 1,
ColorsGreen: 1,
ColorsMagenta: 1,
ColorsOrange: 1,
ColorsPurple: 1,
ColorsRed: 1,
ColorsWhite: 1,
ColorsYellow: 1,
ColorsPink: 1,
//ColorsSepia: 1,
//ColorsBrown: 1,
}
public hasKey(k:string)
{
if (!k) return true;
return !this.properties || !!this.properties[k.toLowerCase()]
}
public hasTokenUsage(p:any)
{
if (!p) return true;
var tok:TokenUsage = p.getUsage ? p.getUsage() : null;
return tok && tok.localCount + tok.globalCount > 0;
}
public hasProperty(p:IProperty)
{
if (!this.properties) return true;
return this.hasTokenUsage(p) || this.hasKey(p.usageKey()) || (this.allowAllLibraries && p instanceof LibraryRefAction);
}
public hasFlag(flg:string)
{
if (!this.properties) return false;
return this.hasKey("flag:" + flg)
}
public hasKind(k: Kind): boolean {
if (!this.properties) return true;
return this.hasTokenUsage(k) || this.hasKey(k.getName());
}
public hasDecl(p:Decl)
{
if (!this.properties) return true;
if (p.getName() == AST.libSymbol)
// needs explicit #allow:libSingleton
return this.hasKey("libSingleton");
if (p instanceof SingletonDef && p.isExtension) return true;
return this.hasTokenUsage(p) || this.hasKey(p.usageKey());
}
public incr(k:string)
{
if (!this.properties) return
k = k.toLowerCase();
if (this.properties.hasOwnProperty(k)) this.properties[k]++
else this.properties[k] = 1
}
public loadFrom(node:AstNode, builtin : boolean)
{
var v = new IntelliCollector()
v.props = builtin ? Util.clone(IntelliProfile.helpfulProperties) : {};
v.dispatch(node)
this.properties = {}
Object.keys(v.props).forEach(k => {
this.properties[k.toLowerCase()] = v.props[k]
})
}
}
export class AtomicVisitor
extends ExprVisitor
{
private calls:StringMap<Set<Action>> = {}
private currentAction:Action;
private currentCalls:Set<Action>;
private numNonAtomic:number;
private nonAtomic = new Set<Action>();
visitCall(c:Call)
{
var act = c.calledAction() || c.calledExtensionAction()
if (act && !act.isInLibrary()) {
if (act.isPage()) this.numNonAtomic++;
else this.currentCalls.add(act)
} else {
if (c.prop().getFlags() & PropertyFlags.Async)
this.numNonAtomic++;
}
super.visitCall(c)
}
visitInlineAction(a:InlineAction)
{
// skip body
}
visitAction(a:Action)
{
if (a.isPage() || a.isEvent())
this.nonAtomic.add(a)
if (a.isActionTypeDef() || a.isAtomic)
return;
this.currentAction = a;
this.currentCalls = new Set<Action>();
this.calls[a.getName()] = this.currentCalls;
this.numNonAtomic = 0;
super.visitAction(a);
if (this.numNonAtomic > 0)
this.nonAtomic.add(a)
}
static run(app:App)
{
var v = new AtomicVisitor()
v.dispatch(app)
var nonAtomic = v.nonAtomic;
while (true) {
var prev = nonAtomic.length()
app.actions().forEach(a => {
if (nonAtomic.contains(a) || !v.calls.hasOwnProperty(a.getName())) return
if (v.calls[a.getName()].elts().some(x => nonAtomic.contains(x)))
nonAtomic.add(a)
})
if (prev == nonAtomic.length()) break;
}
return app.actions().filter(a => !nonAtomic.contains(a))
}
}
export class InitIdVisitor extends NodeVisitor {
private expectAllSet = false;
private unsetNodes:AstNode[];
private lastStmt:Stmt;
private usedIds:StringMap<AstNode> = {};
constructor(private refresh:boolean) {
super();
}
public expectSet(n:AstNode)
{
this.expectAllSet = true
this.unsetNodes = []
this.usedIds = {}
this.dispatch(n)
if (this.unsetNodes.length > 0) {
var dict = Util.toDictionary(this.unsetNodes, (n:AstNode) => n.nodeType())
Util.oops("id not set on " + Object.keys(dict).join(", "))
}
}
// these don't need IDs
public visitAstNode(node:AstNode):any { }
public visitToken(tok:Token) { }
public visitOperator(n:Operator) { }
public visitPropertyRef(n:PropertyRef) { }
public visitExpr(tok:Expr) { }
public visitLiteral(n:Literal) { }
public visitThingRef(n:ThingRef) { }
public visitCall(n:Call) { }
public visitExprHolder(eh:ExprHolder)
{
var n = this.lastStmt
var ai = eh.assignmentInfo()
if (ai)
ai.definedVars.forEach((x, i) => x.setStableName(n.getStableName() + "$l" + i));
}
// Stmt and subclasses need IDs
public visitStmt(n:Stmt) {
if (this.expectAllSet && !n.getStableName() && !n.isPlaceholder())
this.unsetNodes.push(n)
n.initStableName(this.refresh);
if (!this.refresh) {
var u = this.usedIds
var nn = n.getStableName()
if (u.hasOwnProperty(nn) && u[nn] != n) {
n.initStableName(true)
} else u[nn] = n
}
}
public visitBlock(n:Block) {
this.visitStmt(n);
/*var ch = n.children()
if (ch.length == 1 && ch[0].isPlaceholder())
// TODO XXX - this is a hack to give deterministic IDs to
// the auto-generated empty (skip) statements in new actions, etc.
this.deriveIdChildren(n)
else*/
this.visitChildren(n)
}
public visitChStmt(n:Stmt)
{
this.visitStmt(n)
this.deriveIdChildren(n)
}
private withBoundLocal(n:Stmt, loc:LocalDef)
{
this.visitChStmt(n)
if (loc)
loc.setStableName(n.getStableName() + "$l0")
}
public visitFor(n:For) { this.withBoundLocal(n, n.boundLocal); }
public visitForeach(n:Foreach) { this.withBoundLocal(n, n.boundLocal); }
public visitWhile(n:While) { this.visitChStmt(n); }
public visitBox(n:Box) { this.visitChStmt(n); }
public visitAnyIf(n:If) { this.visitChStmt(n); }
public visitInlineActions(n:InlineActions) { this.visitChStmt(n); }
public visitActionHeader(n:ActionHeader) { this.visitChStmt(n); }
public visitExprStmt(n:ExprStmt) { this.visitChStmt(n); }
public visitActionParameter(n:ActionParameter) { this.withBoundLocal(n, n.local) }
public visitInlineAction(n:InlineAction)
{
this.withBoundLocal(n, n.name);
n.inParameters.forEach((p, i) => p.setStableName(n.getStableName() + "$inlIn" + i))
n.outParameters.forEach((p, i) => p.setStableName(n.getStableName() + "$inlOut" + i))
}
public visitGlobalDef(n:GlobalDef) { this.visitChStmt(n); }
public visitRecordDef(n:RecordDef) { this.visitChStmt(n); }
public visitAction(n:Action) { this.visitChStmt(n); }
public visitLibraryRef(n:LibraryRef)
{
this.visitChStmt(n);
//n.getPublicActions().forEach(e => this.dispatch(e))
}
public visitLocalDef(n:LocalDef) { }
public visitApp(n:App) { this.visitDecl(n); this.visitChildren(n); }
public visitKindBinding(n:KindBinding) {
if (n.isExplicit)
this.visitStmt(n);
}
public visitActionBinding(n:ActionBinding) {
if (n.isExplicit)
this.visitStmt(n);
}
public visitResolveClause(n:ResolveClause) { this.visitChStmt(n); }
public visitRecordField(n:RecordField) { this.visitChStmt(n); }
public visitChildren(n:AstNode)
{
n.children().forEach(c => { if (c) c.accept(this); });
}
public deriveIdChildren(n:Stmt) {
this.lastStmt = n;
n.children().forEach((c, ix) => {
if(c) {
if(c instanceof Stmt) {
(<Stmt>c).deriveStableName(n, ix)
}
c.accept(this);
}
});
}
static ensureOK(app:App)
{
if (app.hasIds)
new InitIdVisitor(false).dispatch(app)
}
}
export function decompressStack(compressed:string):IStackFrame[]
{
var stmtById:StringMap<IStackFrame> = {}
var add = (rtid:string, rt:Stmt) => {
visitStmts(rt, s => {
stmtById[StackUtil.combineIds(s.getStableName(), rtid)] = <any> { pc: s.getStableName(), d: { libName: rtid } }
})
}
add("", Script)
Script.libraries().forEach(l => {
if (l.resolved) add(l.getStableName(), l.resolved)
})
var r = []
for (var i = 0; i < compressed.length; i += 8) {
var fr = compressed.slice(i, i + 8)
if (stmtById.hasOwnProperty(fr))
r.push(stmtById[fr])
}
return r
}
}