2018-01-23 13:33:48 -08:00

1385 lines
48 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ParseSourceSpan} from '../parse_util';
import {error} from '../util';
//// Types
export enum TypeModifier {
Const
}
export abstract class Type {
constructor(public modifiers: TypeModifier[]|null = null) {
if (!modifiers) {
this.modifiers = [];
}
}
abstract visitType(visitor: TypeVisitor, context: any): any;
hasModifier(modifier: TypeModifier): boolean { return this.modifiers !.indexOf(modifier) !== -1; }
}
export enum BuiltinTypeName {
Dynamic,
Bool,
String,
Int,
Number,
Function,
Inferred
}
export class BuiltinType extends Type {
constructor(public name: BuiltinTypeName, modifiers: TypeModifier[]|null = null) {
super(modifiers);
}
visitType(visitor: TypeVisitor, context: any): any {
return visitor.visitBuiltinType(this, context);
}
}
export class ExpressionType extends Type {
constructor(public value: Expression, modifiers: TypeModifier[]|null = null) { super(modifiers); }
visitType(visitor: TypeVisitor, context: any): any {
return visitor.visitExpressionType(this, context);
}
}
export class ArrayType extends Type {
constructor(public of : Type, modifiers: TypeModifier[]|null = null) { super(modifiers); }
visitType(visitor: TypeVisitor, context: any): any {
return visitor.visitArrayType(this, context);
}
}
export class MapType extends Type {
public valueType: Type|null;
constructor(valueType: Type|null|undefined, modifiers: TypeModifier[]|null = null) {
super(modifiers);
this.valueType = valueType || null;
}
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
}
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export const INFERRED_TYPE = new BuiltinType(BuiltinTypeName.Inferred);
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export interface TypeVisitor {
visitBuiltinType(type: BuiltinType, context: any): any;
visitExpressionType(type: ExpressionType, context: any): any;
visitArrayType(type: ArrayType, context: any): any;
visitMapType(type: MapType, context: any): any;
}
///// Expressions
export enum BinaryOperator {
Equals,
NotEquals,
Identical,
NotIdentical,
Minus,
Plus,
Divide,
Multiply,
Modulo,
And,
Or,
Lower,
LowerEquals,
Bigger,
BiggerEquals
}
export function nullSafeIsEquivalent<T extends{isEquivalent(other: T): boolean}>(
base: T | null, other: T | null) {
if (base == null || other == null) {
return base == other;
}
return base.isEquivalent(other);
}
export function areAllEquivalent<T extends{isEquivalent(other: T): boolean}>(
base: T[], other: T[]) {
const len = base.length;
if (len !== other.length) {
return false;
}
for (let i = 0; i < len; i++) {
if (!base[i].isEquivalent(other[i])) {
return false;
}
}
return true;
}
export abstract class Expression {
public type: Type|null;
public sourceSpan: ParseSourceSpan|null;
constructor(type: Type|null|undefined, sourceSpan?: ParseSourceSpan|null) {
this.type = type || null;
this.sourceSpan = sourceSpan || null;
}
abstract visitExpression(visitor: ExpressionVisitor, context: any): any;
/**
* Calculates whether this expression produces the same value as the given expression.
* Note: We don't check Types nor ParseSourceSpans nor function arguments.
*/
abstract isEquivalent(e: Expression): boolean;
prop(name: string, sourceSpan?: ParseSourceSpan|null): ReadPropExpr {
return new ReadPropExpr(this, name, null, sourceSpan);
}
key(index: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null): ReadKeyExpr {
return new ReadKeyExpr(this, index, type, sourceSpan);
}
callMethod(name: string|BuiltinMethod, params: Expression[], sourceSpan?: ParseSourceSpan|null):
InvokeMethodExpr {
return new InvokeMethodExpr(this, name, params, null, sourceSpan);
}
callFn(params: Expression[], sourceSpan?: ParseSourceSpan|null): InvokeFunctionExpr {
return new InvokeFunctionExpr(this, params, null, sourceSpan);
}
instantiate(params: Expression[], type?: Type|null, sourceSpan?: ParseSourceSpan|null):
InstantiateExpr {
return new InstantiateExpr(this, params, type, sourceSpan);
}
conditional(
trueCase: Expression, falseCase: Expression|null = null,
sourceSpan?: ParseSourceSpan|null): ConditionalExpr {
return new ConditionalExpr(this, trueCase, falseCase, null, sourceSpan);
}
equals(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Equals, this, rhs, null, sourceSpan);
}
notEquals(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.NotEquals, this, rhs, null, sourceSpan);
}
identical(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Identical, this, rhs, null, sourceSpan);
}
notIdentical(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.NotIdentical, this, rhs, null, sourceSpan);
}
minus(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Minus, this, rhs, null, sourceSpan);
}
plus(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Plus, this, rhs, null, sourceSpan);
}
divide(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Divide, this, rhs, null, sourceSpan);
}
multiply(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Multiply, this, rhs, null, sourceSpan);
}
modulo(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Modulo, this, rhs, null, sourceSpan);
}
and(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.And, this, rhs, null, sourceSpan);
}
or(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Or, this, rhs, null, sourceSpan);
}
lower(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Lower, this, rhs, null, sourceSpan);
}
lowerEquals(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.LowerEquals, this, rhs, null, sourceSpan);
}
bigger(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Bigger, this, rhs, null, sourceSpan);
}
biggerEquals(rhs: Expression, sourceSpan?: ParseSourceSpan|null): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.BiggerEquals, this, rhs, null, sourceSpan);
}
isBlank(sourceSpan?: ParseSourceSpan|null): Expression {
// Note: We use equals by purpose here to compare to null and undefined in JS.
// We use the typed null to allow strictNullChecks to narrow types.
return this.equals(TYPED_NULL_EXPR, sourceSpan);
}
cast(type: Type, sourceSpan?: ParseSourceSpan|null): Expression {
return new CastExpr(this, type, sourceSpan);
}
toStmt(): Statement { return new ExpressionStatement(this, null); }
}
export enum BuiltinVar {
This,
Super,
CatchError,
CatchStack
}
export class ReadVarExpr extends Expression {
public name: string|null;
public builtin: BuiltinVar|null;
constructor(name: string|BuiltinVar, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
if (typeof name === 'string') {
this.name = name;
this.builtin = null;
} else {
this.name = null;
this.builtin = <BuiltinVar>name;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadVarExpr && this.name === e.name && this.builtin === e.builtin;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadVarExpr(this, context);
}
set(value: Expression): WriteVarExpr {
if (!this.name) {
throw new Error(`Built in variable ${this.builtin} can not be assigned to.`);
}
return new WriteVarExpr(this.name, value, null, this.sourceSpan);
}
}
export class WriteVarExpr extends Expression {
public value: Expression;
constructor(
public name: string, value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteVarExpr(this, context);
}
toDeclStmt(type?: Type|null, modifiers?: StmtModifier[]|null): DeclareVarStmt {
return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan);
}
}
export class WriteKeyExpr extends Expression {
public value: Expression;
constructor(
public receiver: Expression, public index: Expression, value: Expression, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WriteKeyExpr && this.receiver.isEquivalent(e.receiver) &&
this.index.isEquivalent(e.index) && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteKeyExpr(this, context);
}
}
export class WritePropExpr extends Expression {
public value: Expression;
constructor(
public receiver: Expression, public name: string, value: Expression, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WritePropExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWritePropExpr(this, context);
}
}
export enum BuiltinMethod {
ConcatArray,
SubscribeObservable,
Bind
}
export class InvokeMethodExpr extends Expression {
public name: string|null;
public builtin: BuiltinMethod|null;
constructor(
public receiver: Expression, method: string|BuiltinMethod, public args: Expression[],
type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
if (typeof method === 'string') {
this.name = method;
this.builtin = null;
} else {
this.name = null;
this.builtin = <BuiltinMethod>method;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof InvokeMethodExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name && this.builtin === e.builtin && areAllEquivalent(this.args, e.args);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeMethodExpr(this, context);
}
}
export class InvokeFunctionExpr extends Expression {
constructor(
public fn: Expression, public args: Expression[], type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof InvokeFunctionExpr && this.fn.isEquivalent(e.fn) &&
areAllEquivalent(this.args, e.args);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeFunctionExpr(this, context);
}
}
export class InstantiateExpr extends Expression {
constructor(
public classExpr: Expression, public args: Expression[], type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof InstantiateExpr && this.classExpr.isEquivalent(e.classExpr) &&
areAllEquivalent(this.args, e.args);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInstantiateExpr(this, context);
}
}
export class LiteralExpr extends Expression {
constructor(
public value: number|string|boolean|null|undefined, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralExpr && this.value === e.value;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralExpr(this, context);
}
}
export class ExternalExpr extends Expression {
constructor(
public value: ExternalReference, type?: Type|null, public typeParams: Type[]|null = null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ExternalExpr && this.value.name === e.value.name &&
this.value.moduleName === e.value.moduleName && this.value.runtime === e.value.runtime;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitExternalExpr(this, context);
}
}
export class ExternalReference {
constructor(public moduleName: string|null, public name: string|null, public runtime?: any|null) {
}
// Note: no isEquivalent method here as we use this as an interface too.
}
export class ConditionalExpr extends Expression {
public trueCase: Expression;
constructor(
public condition: Expression, trueCase: Expression, public falseCase: Expression|null = null,
type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type || trueCase.type, sourceSpan);
this.trueCase = trueCase;
}
isEquivalent(e: Expression): boolean {
return e instanceof ConditionalExpr && this.condition.isEquivalent(e.condition) &&
this.trueCase.isEquivalent(e.trueCase) && nullSafeIsEquivalent(this.falseCase, e.falseCase);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitConditionalExpr(this, context);
}
}
export class NotExpr extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(BOOL_TYPE, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof NotExpr && this.condition.isEquivalent(e.condition);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitNotExpr(this, context);
}
}
export class AssertNotNull extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(condition.type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof AssertNotNull && this.condition.isEquivalent(e.condition);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitAssertNotNullExpr(this, context);
}
}
export class CastExpr extends Expression {
constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof CastExpr && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCastExpr(this, context);
}
}
export class FnParam {
constructor(public name: string, public type: Type|null = null) {}
isEquivalent(param: FnParam): boolean { return this.name === param.name; }
}
export class FunctionExpr extends Expression {
constructor(
public params: FnParam[], public statements: Statement[], type?: Type|null,
sourceSpan?: ParseSourceSpan|null, public name?: string|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof FunctionExpr && areAllEquivalent(this.params, e.params) &&
areAllEquivalent(this.statements, e.statements);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitFunctionExpr(this, context);
}
toDeclStmt(name: string, modifiers: StmtModifier[]|null = null): DeclareFunctionStmt {
return new DeclareFunctionStmt(
name, this.params, this.statements, this.type, modifiers, this.sourceSpan);
}
}
export class BinaryOperatorExpr extends Expression {
public lhs: Expression;
constructor(
public operator: BinaryOperator, lhs: Expression, public rhs: Expression, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type || lhs.type, sourceSpan);
this.lhs = lhs;
}
isEquivalent(e: Expression): boolean {
return e instanceof BinaryOperatorExpr && this.operator === e.operator &&
this.lhs.isEquivalent(e.lhs) && this.rhs.isEquivalent(e.rhs);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitBinaryOperatorExpr(this, context);
}
}
export class ReadPropExpr extends Expression {
constructor(
public receiver: Expression, public name: string, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadPropExpr(this, context);
}
set(value: Expression): WritePropExpr {
return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan);
}
}
export class ReadKeyExpr extends Expression {
constructor(
public receiver: Expression, public index: Expression, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) &&
this.index.isEquivalent(e.index);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadKeyExpr(this, context);
}
set(value: Expression): WriteKeyExpr {
return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan);
}
}
export class LiteralArrayExpr extends Expression {
public entries: Expression[];
constructor(entries: Expression[], type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
this.entries = entries;
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralArrayExpr(this, context);
}
}
export class LiteralMapEntry {
constructor(public key: string, public value: Expression, public quoted: boolean) {}
isEquivalent(e: LiteralMapEntry): boolean {
return this.key === e.key && this.value.isEquivalent(e.value);
}
}
export class LiteralMapExpr extends Expression {
public valueType: Type|null = null;
constructor(
public entries: LiteralMapEntry[], type?: MapType|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
if (type) {
this.valueType = type.valueType;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralMapExpr(this, context);
}
}
export class CommaExpr extends Expression {
constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan|null) {
super(parts[parts.length - 1].type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof CommaExpr && areAllEquivalent(this.parts, e.parts);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCommaExpr(this, context);
}
}
export interface ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any;
visitWriteVarExpr(expr: WriteVarExpr, context: any): any;
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any;
visitWritePropExpr(expr: WritePropExpr, context: any): any;
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any;
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any;
visitInstantiateExpr(ast: InstantiateExpr, context: any): any;
visitLiteralExpr(ast: LiteralExpr, context: any): any;
visitExternalExpr(ast: ExternalExpr, context: any): any;
visitConditionalExpr(ast: ConditionalExpr, context: any): any;
visitNotExpr(ast: NotExpr, context: any): any;
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any;
visitCastExpr(ast: CastExpr, context: any): any;
visitFunctionExpr(ast: FunctionExpr, context: any): any;
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any;
visitReadPropExpr(ast: ReadPropExpr, context: any): any;
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any;
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any;
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
visitCommaExpr(ast: CommaExpr, context: any): any;
}
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This, null, null);
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super, null, null);
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError, null, null);
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack, null, null);
export const NULL_EXPR = new LiteralExpr(null, null, null);
export const TYPED_NULL_EXPR = new LiteralExpr(null, INFERRED_TYPE, null);
//// Statements
export enum StmtModifier {
Final,
Private,
Exported,
Static,
}
export abstract class Statement {
public modifiers: StmtModifier[];
public sourceSpan: ParseSourceSpan|null;
constructor(modifiers?: StmtModifier[]|null, sourceSpan?: ParseSourceSpan|null) {
this.modifiers = modifiers || [];
this.sourceSpan = sourceSpan || null;
}
/**
* Calculates whether this statement produces the same value as the given statement.
* Note: We don't check Types nor ParseSourceSpans nor function arguments.
*/
abstract isEquivalent(stmt: Statement): boolean;
abstract visitStatement(visitor: StatementVisitor, context: any): any;
hasModifier(modifier: StmtModifier): boolean { return this.modifiers !.indexOf(modifier) !== -1; }
}
export class DeclareVarStmt extends Statement {
public type: Type|null;
constructor(
public name: string, public value: Expression, type?: Type|null,
modifiers: StmtModifier[]|null = null, sourceSpan?: ParseSourceSpan|null) {
super(modifiers, sourceSpan);
this.type = type || value.type;
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof DeclareVarStmt && this.name === stmt.name &&
this.value.isEquivalent(stmt.value);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareVarStmt(this, context);
}
}
export class DeclareFunctionStmt extends Statement {
public type: Type|null;
constructor(
public name: string, public params: FnParam[], public statements: Statement[],
type?: Type|null, modifiers: StmtModifier[]|null = null, sourceSpan?: ParseSourceSpan|null) {
super(modifiers, sourceSpan);
this.type = type || null;
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof DeclareFunctionStmt && areAllEquivalent(this.params, stmt.params) &&
areAllEquivalent(this.statements, stmt.statements);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareFunctionStmt(this, context);
}
}
export class ExpressionStatement extends Statement {
constructor(public expr: Expression, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof ExpressionStatement && this.expr.isEquivalent(stmt.expr);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitExpressionStmt(this, context);
}
}
export class ReturnStatement extends Statement {
constructor(public value: Expression, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof ReturnStatement && this.value.isEquivalent(stmt.value);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitReturnStmt(this, context);
}
}
export class AbstractClassPart {
public type: Type|null;
constructor(type: Type|null|undefined, public modifiers: StmtModifier[]|null) {
if (!modifiers) {
this.modifiers = [];
}
this.type = type || null;
}
hasModifier(modifier: StmtModifier): boolean { return this.modifiers !.indexOf(modifier) !== -1; }
}
export class ClassField extends AbstractClassPart {
constructor(
public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null,
public initializer?: Expression) {
super(type, modifiers);
}
isEquivalent(f: ClassField) { return this.name === f.name; }
}
export class ClassMethod extends AbstractClassPart {
constructor(
public name: string|null, public params: FnParam[], public body: Statement[],
type?: Type|null, modifiers: StmtModifier[]|null = null) {
super(type, modifiers);
}
isEquivalent(m: ClassMethod) {
return this.name === m.name && areAllEquivalent(this.body, m.body);
}
}
export class ClassGetter extends AbstractClassPart {
constructor(
public name: string, public body: Statement[], type?: Type|null,
modifiers: StmtModifier[]|null = null) {
super(type, modifiers);
}
isEquivalent(m: ClassGetter) {
return this.name === m.name && areAllEquivalent(this.body, m.body);
}
}
export class ClassStmt extends Statement {
constructor(
public name: string, public parent: Expression|null, public fields: ClassField[],
public getters: ClassGetter[], public constructorMethod: ClassMethod,
public methods: ClassMethod[], modifiers: StmtModifier[]|null = null,
sourceSpan?: ParseSourceSpan|null) {
super(modifiers, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof ClassStmt && this.name === stmt.name &&
nullSafeIsEquivalent(this.parent, stmt.parent) &&
areAllEquivalent(this.fields, stmt.fields) &&
areAllEquivalent(this.getters, stmt.getters) &&
this.constructorMethod.isEquivalent(stmt.constructorMethod) &&
areAllEquivalent(this.methods, stmt.methods);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareClassStmt(this, context);
}
}
export class IfStmt extends Statement {
constructor(
public condition: Expression, public trueCase: Statement[],
public falseCase: Statement[] = [], sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof IfStmt && this.condition.isEquivalent(stmt.condition) &&
areAllEquivalent(this.trueCase, stmt.trueCase) &&
areAllEquivalent(this.falseCase, stmt.falseCase);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitIfStmt(this, context);
}
}
export class CommentStmt extends Statement {
constructor(public comment: string, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean { return stmt instanceof CommentStmt; }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitCommentStmt(this, context);
}
}
export class TryCatchStmt extends Statement {
constructor(
public bodyStmts: Statement[], public catchStmts: Statement[],
sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof TryCatchStmt && areAllEquivalent(this.bodyStmts, stmt.bodyStmts) &&
areAllEquivalent(this.catchStmts, stmt.catchStmts);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitTryCatchStmt(this, context);
}
}
export class ThrowStmt extends Statement {
constructor(public error: Expression, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: ThrowStmt): boolean {
return stmt instanceof TryCatchStmt && this.error.isEquivalent(stmt.error);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitThrowStmt(this, context);
}
}
export interface StatementVisitor {
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any;
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any;
visitExpressionStmt(stmt: ExpressionStatement, context: any): any;
visitReturnStmt(stmt: ReturnStatement, context: any): any;
visitDeclareClassStmt(stmt: ClassStmt, context: any): any;
visitIfStmt(stmt: IfStmt, context: any): any;
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any;
visitThrowStmt(stmt: ThrowStmt, context: any): any;
visitCommentStmt(stmt: CommentStmt, context: any): any;
}
export class AstTransformer implements StatementVisitor, ExpressionVisitor {
transformExpr(expr: Expression, context: any): Expression { return expr; }
transformStmt(stmt: Statement, context: any): Statement { return stmt; }
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return this.transformExpr(ast, context); }
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
return this.transformExpr(
new WriteVarExpr(
expr.name, expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
context);
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
return this.transformExpr(
new WriteKeyExpr(
expr.receiver.visitExpression(this, context), expr.index.visitExpression(this, context),
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
context);
}
visitWritePropExpr(expr: WritePropExpr, context: any): any {
return this.transformExpr(
new WritePropExpr(
expr.receiver.visitExpression(this, context), expr.name,
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
context);
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
const method = ast.builtin || ast.name;
return this.transformExpr(
new InvokeMethodExpr(
ast.receiver.visitExpression(this, context), method !,
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan),
context);
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
return this.transformExpr(
new InvokeFunctionExpr(
ast.fn.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
ast.type, ast.sourceSpan),
context);
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
return this.transformExpr(
new InstantiateExpr(
ast.classExpr.visitExpression(this, context),
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan),
context);
}
visitLiteralExpr(ast: LiteralExpr, context: any): any { return this.transformExpr(ast, context); }
visitExternalExpr(ast: ExternalExpr, context: any): any {
return this.transformExpr(ast, context);
}
visitConditionalExpr(ast: ConditionalExpr, context: any): any {
return this.transformExpr(
new ConditionalExpr(
ast.condition.visitExpression(this, context),
ast.trueCase.visitExpression(this, context),
ast.falseCase !.visitExpression(this, context), ast.type, ast.sourceSpan),
context);
}
visitNotExpr(ast: NotExpr, context: any): any {
return this.transformExpr(
new NotExpr(ast.condition.visitExpression(this, context), ast.sourceSpan), context);
}
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any {
return this.transformExpr(
new AssertNotNull(ast.condition.visitExpression(this, context), ast.sourceSpan), context);
}
visitCastExpr(ast: CastExpr, context: any): any {
return this.transformExpr(
new CastExpr(ast.value.visitExpression(this, context), ast.type, ast.sourceSpan), context);
}
visitFunctionExpr(ast: FunctionExpr, context: any): any {
return this.transformExpr(
new FunctionExpr(
ast.params, this.visitAllStatements(ast.statements, context), ast.type, ast.sourceSpan),
context);
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
return this.transformExpr(
new BinaryOperatorExpr(
ast.operator, ast.lhs.visitExpression(this, context),
ast.rhs.visitExpression(this, context), ast.type, ast.sourceSpan),
context);
}
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
return this.transformExpr(
new ReadPropExpr(
ast.receiver.visitExpression(this, context), ast.name, ast.type, ast.sourceSpan),
context);
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
return this.transformExpr(
new ReadKeyExpr(
ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context),
ast.type, ast.sourceSpan),
context);
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
return this.transformExpr(
new LiteralArrayExpr(
this.visitAllExpressions(ast.entries, context), ast.type, ast.sourceSpan),
context);
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
const entries = ast.entries.map(
(entry): LiteralMapEntry => new LiteralMapEntry(
entry.key, entry.value.visitExpression(this, context), entry.quoted));
const mapType = new MapType(ast.valueType, null);
return this.transformExpr(new LiteralMapExpr(entries, mapType, ast.sourceSpan), context);
}
visitCommaExpr(ast: CommaExpr, context: any): any {
return this.transformExpr(
new CommaExpr(this.visitAllExpressions(ast.parts, context), ast.sourceSpan), context);
}
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
return exprs.map(expr => expr.visitExpression(this, context));
}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
return this.transformStmt(
new DeclareVarStmt(
stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers,
stmt.sourceSpan),
context);
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
return this.transformStmt(
new DeclareFunctionStmt(
stmt.name, stmt.params, this.visitAllStatements(stmt.statements, context), stmt.type,
stmt.modifiers, stmt.sourceSpan),
context);
}
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
return this.transformStmt(
new ExpressionStatement(stmt.expr.visitExpression(this, context), stmt.sourceSpan),
context);
}
visitReturnStmt(stmt: ReturnStatement, context: any): any {
return this.transformStmt(
new ReturnStatement(stmt.value.visitExpression(this, context), stmt.sourceSpan), context);
}
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
const parent = stmt.parent !.visitExpression(this, context);
const getters = stmt.getters.map(
getter => new ClassGetter(
getter.name, this.visitAllStatements(getter.body, context), getter.type,
getter.modifiers));
const ctorMethod = stmt.constructorMethod &&
new ClassMethod(stmt.constructorMethod.name, stmt.constructorMethod.params,
this.visitAllStatements(stmt.constructorMethod.body, context),
stmt.constructorMethod.type, stmt.constructorMethod.modifiers);
const methods = stmt.methods.map(
method => new ClassMethod(
method.name, method.params, this.visitAllStatements(method.body, context), method.type,
method.modifiers));
return this.transformStmt(
new ClassStmt(
stmt.name, parent, stmt.fields, getters, ctorMethod, methods, stmt.modifiers,
stmt.sourceSpan),
context);
}
visitIfStmt(stmt: IfStmt, context: any): any {
return this.transformStmt(
new IfStmt(
stmt.condition.visitExpression(this, context),
this.visitAllStatements(stmt.trueCase, context),
this.visitAllStatements(stmt.falseCase, context), stmt.sourceSpan),
context);
}
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
return this.transformStmt(
new TryCatchStmt(
this.visitAllStatements(stmt.bodyStmts, context),
this.visitAllStatements(stmt.catchStmts, context), stmt.sourceSpan),
context);
}
visitThrowStmt(stmt: ThrowStmt, context: any): any {
return this.transformStmt(
new ThrowStmt(stmt.error.visitExpression(this, context), stmt.sourceSpan), context);
}
visitCommentStmt(stmt: CommentStmt, context: any): any {
return this.transformStmt(stmt, context);
}
visitAllStatements(stmts: Statement[], context: any): Statement[] {
return stmts.map(stmt => stmt.visitStatement(this, context));
}
}
export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor {
visitType(ast: Type, context: any): any { return ast; }
visitExpression(ast: Expression, context: any): any {
if (ast.type) {
ast.type.visitType(this, context);
}
return ast;
}
visitBuiltinType(type: BuiltinType, context: any): any { return this.visitType(type, context); }
visitExpressionType(type: ExpressionType, context: any): any {
type.value.visitExpression(this, context);
return this.visitType(type, context);
}
visitArrayType(type: ArrayType, context: any): any { return this.visitType(type, context); }
visitMapType(type: MapType, context: any): any { return this.visitType(type, context); }
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
return this.visitExpression(ast, context);
}
visitWriteVarExpr(ast: WriteVarExpr, context: any): any {
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitWriteKeyExpr(ast: WriteKeyExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.index.visitExpression(this, context);
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitWritePropExpr(ast: WritePropExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
ast.receiver.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return this.visitExpression(ast, context);
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
ast.fn.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return this.visitExpression(ast, context);
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
ast.classExpr.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return this.visitExpression(ast, context);
}
visitLiteralExpr(ast: LiteralExpr, context: any): any {
return this.visitExpression(ast, context);
}
visitExternalExpr(ast: ExternalExpr, context: any): any {
if (ast.typeParams) {
ast.typeParams.forEach(type => type.visitType(this, context));
}
return this.visitExpression(ast, context);
}
visitConditionalExpr(ast: ConditionalExpr, context: any): any {
ast.condition.visitExpression(this, context);
ast.trueCase.visitExpression(this, context);
ast.falseCase !.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitNotExpr(ast: NotExpr, context: any): any {
ast.condition.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any {
ast.condition.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitCastExpr(ast: CastExpr, context: any): any {
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitFunctionExpr(ast: FunctionExpr, context: any): any {
this.visitAllStatements(ast.statements, context);
return this.visitExpression(ast, context);
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
ast.lhs.visitExpression(this, context);
ast.rhs.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
ast.receiver.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.index.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
this.visitAllExpressions(ast.entries, context);
return this.visitExpression(ast, context);
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
return this.visitExpression(ast, context);
}
visitCommaExpr(ast: CommaExpr, context: any): any {
this.visitAllExpressions(ast.parts, context);
return this.visitExpression(ast, context);
}
visitAllExpressions(exprs: Expression[], context: any): void {
exprs.forEach(expr => expr.visitExpression(this, context));
}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
stmt.value.visitExpression(this, context);
if (stmt.type) {
stmt.type.visitType(this, context);
}
return stmt;
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
this.visitAllStatements(stmt.statements, context);
if (stmt.type) {
stmt.type.visitType(this, context);
}
return stmt;
}
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
stmt.expr.visitExpression(this, context);
return stmt;
}
visitReturnStmt(stmt: ReturnStatement, context: any): any {
stmt.value.visitExpression(this, context);
return stmt;
}
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
stmt.parent !.visitExpression(this, context);
stmt.getters.forEach(getter => this.visitAllStatements(getter.body, context));
if (stmt.constructorMethod) {
this.visitAllStatements(stmt.constructorMethod.body, context);
}
stmt.methods.forEach(method => this.visitAllStatements(method.body, context));
return stmt;
}
visitIfStmt(stmt: IfStmt, context: any): any {
stmt.condition.visitExpression(this, context);
this.visitAllStatements(stmt.trueCase, context);
this.visitAllStatements(stmt.falseCase, context);
return stmt;
}
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
this.visitAllStatements(stmt.bodyStmts, context);
this.visitAllStatements(stmt.catchStmts, context);
return stmt;
}
visitThrowStmt(stmt: ThrowStmt, context: any): any {
stmt.error.visitExpression(this, context);
return stmt;
}
visitCommentStmt(stmt: CommentStmt, context: any): any { return stmt; }
visitAllStatements(stmts: Statement[], context: any): void {
stmts.forEach(stmt => stmt.visitStatement(this, context));
}
}
export function findReadVarNames(stmts: Statement[]): Set<string> {
const visitor = new _ReadVarVisitor();
visitor.visitAllStatements(stmts, null);
return visitor.varNames;
}
class _ReadVarVisitor extends RecursiveAstVisitor {
varNames = new Set<string>();
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
// Don't descend into nested functions
return stmt;
}
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
// Don't descend into nested classes
return stmt;
}
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
if (ast.name) {
this.varNames.add(ast.name);
}
return null;
}
}
export function collectExternalReferences(stmts: Statement[]): ExternalReference[] {
const visitor = new _FindExternalReferencesVisitor();
visitor.visitAllStatements(stmts, null);
return visitor.externalReferences;
}
class _FindExternalReferencesVisitor extends RecursiveAstVisitor {
externalReferences: ExternalReference[] = [];
visitExternalExpr(e: ExternalExpr, context: any) {
this.externalReferences.push(e.value);
return super.visitExternalExpr(e, context);
}
}
export function applySourceSpanToStatementIfNeeded(
stmt: Statement, sourceSpan: ParseSourceSpan | null): Statement {
if (!sourceSpan) {
return stmt;
}
const transformer = new _ApplySourceSpanTransformer(sourceSpan);
return stmt.visitStatement(transformer, null);
}
export function applySourceSpanToExpressionIfNeeded(
expr: Expression, sourceSpan: ParseSourceSpan | null): Expression {
if (!sourceSpan) {
return expr;
}
const transformer = new _ApplySourceSpanTransformer(sourceSpan);
return expr.visitExpression(transformer, null);
}
class _ApplySourceSpanTransformer extends AstTransformer {
constructor(private sourceSpan: ParseSourceSpan) { super(); }
private _clone(obj: any): any {
const clone = Object.create(obj.constructor.prototype);
for (let prop in obj) {
clone[prop] = obj[prop];
}
return clone;
}
transformExpr(expr: Expression, context: any): Expression {
if (!expr.sourceSpan) {
expr = this._clone(expr);
expr.sourceSpan = this.sourceSpan;
}
return expr;
}
transformStmt(stmt: Statement, context: any): Statement {
if (!stmt.sourceSpan) {
stmt = this._clone(stmt);
stmt.sourceSpan = this.sourceSpan;
}
return stmt;
}
}
export function variable(
name: string, type?: Type | null, sourceSpan?: ParseSourceSpan | null): ReadVarExpr {
return new ReadVarExpr(name, type, sourceSpan);
}
export function importExpr(
id: ExternalReference, typeParams: Type[] | null = null,
sourceSpan?: ParseSourceSpan | null): ExternalExpr {
return new ExternalExpr(id, null, typeParams, sourceSpan);
}
export function importType(
id: ExternalReference, typeParams: Type[] | null = null,
typeModifiers: TypeModifier[] | null = null): ExpressionType|null {
return id != null ? expressionType(importExpr(id, typeParams, null), typeModifiers) : null;
}
export function expressionType(
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType {
return new ExpressionType(expr, typeModifiers);
}
export function literalArr(
values: Expression[], type?: Type | null,
sourceSpan?: ParseSourceSpan | null): LiteralArrayExpr {
return new LiteralArrayExpr(values, type, sourceSpan);
}
export function literalMap(
values: {key: string, quoted: boolean, value: Expression}[],
type: MapType | null = null): LiteralMapExpr {
return new LiteralMapExpr(
values.map(e => new LiteralMapEntry(e.key, e.value, e.quoted)), type, null);
}
export function not(expr: Expression, sourceSpan?: ParseSourceSpan | null): NotExpr {
return new NotExpr(expr, sourceSpan);
}
export function assertNotNull(
expr: Expression, sourceSpan?: ParseSourceSpan | null): AssertNotNull {
return new AssertNotNull(expr, sourceSpan);
}
export function fn(
params: FnParam[], body: Statement[], type?: Type | null, sourceSpan?: ParseSourceSpan | null,
name?: string | null): FunctionExpr {
return new FunctionExpr(params, body, type, sourceSpan, name);
}
export function ifStmt(condition: Expression, thenClause: Statement[], elseClause?: Statement[]) {
return new IfStmt(condition, thenClause, elseClause);
}
export function literal(
value: any, type?: Type | null, sourceSpan?: ParseSourceSpan | null): LiteralExpr {
return new LiteralExpr(value, type, sourceSpan);
}