1385 lines
48 KiB
TypeScript
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);
|
|
}
|