/** * @license * Copyright Google LLC 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 {CompileReflector} from '../compile_reflector'; import * as o from './output_ast'; import {debugOutputAstAsTypeScript} from './ts_emitter'; export function interpretStatements( statements: o.Statement[], reflector: CompileReflector): {[key: string]: any} { const ctx = new _ExecutionContext(null, null, null, new Map()); const visitor = new StatementInterpreter(reflector); visitor.visitAllStatements(statements, ctx); const result: {[key: string]: any} = {}; ctx.exports.forEach((exportName) => { result[exportName] = ctx.vars.get(exportName); }); return result; } function _executeFunctionStatements( varNames: string[], varValues: any[], statements: o.Statement[], ctx: _ExecutionContext, visitor: StatementInterpreter): any { const childCtx = ctx.createChildWihtLocalVars(); for (let i = 0; i < varNames.length; i++) { childCtx.vars.set(varNames[i], varValues[i]); } const result = visitor.visitAllStatements(statements, childCtx); return result ? result.value : null; } class _ExecutionContext { exports: string[] = []; constructor( public parent: _ExecutionContext|null, public instance: Object|null, public className: string|null, public vars: Map) {} createChildWihtLocalVars(): _ExecutionContext { return new _ExecutionContext(this, this.instance, this.className, new Map()); } } class ReturnValue { constructor(public value: any) {} } function createDynamicClass( _classStmt: o.ClassStmt, _ctx: _ExecutionContext, _visitor: StatementInterpreter): Function { const propertyDescriptors: {[key: string]: any} = {}; _classStmt.getters.forEach((getter: o.ClassGetter) => { // Note: use `function` instead of arrow function to capture `this` propertyDescriptors[getter.name] = { configurable: false, get: function() { const instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); return _executeFunctionStatements([], [], getter.body, instanceCtx, _visitor); } }; }); _classStmt.methods.forEach(function(method: o.ClassMethod) { const paramNames = method.params.map(param => param.name); // Note: use `function` instead of arrow function to capture `this` propertyDescriptors[method.name!] = { writable: false, configurable: false, value: function(...args: any[]) { const instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); return _executeFunctionStatements(paramNames, args, method.body, instanceCtx, _visitor); } }; }); const ctorParamNames = _classStmt.constructorMethod.params.map(param => param.name); // Note: use `function` instead of arrow function to capture `this` const ctor = function(this: Object, ...args: any[]) { const instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); _classStmt.fields.forEach((field) => { (this as any)[field.name] = undefined; }); _executeFunctionStatements( ctorParamNames, args, _classStmt.constructorMethod.body, instanceCtx, _visitor); }; const superClass = _classStmt.parent ? _classStmt.parent.visitExpression(_visitor, _ctx) : Object; ctor.prototype = Object.create(superClass.prototype, propertyDescriptors); return ctor; } class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { constructor(private reflector: CompileReflector) {} debugAst(ast: o.Expression|o.Statement|o.Type): string { return debugOutputAstAsTypeScript(ast); } visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: _ExecutionContext): any { const initialValue = stmt.value ? stmt.value.visitExpression(this, ctx) : undefined; ctx.vars.set(stmt.name, initialValue); if (stmt.hasModifier(o.StmtModifier.Exported)) { ctx.exports.push(stmt.name); } return null; } visitWriteVarExpr(expr: o.WriteVarExpr, ctx: _ExecutionContext): any { const value = expr.value.visitExpression(this, ctx); let currCtx = ctx; while (currCtx != null) { if (currCtx.vars.has(expr.name)) { currCtx.vars.set(expr.name, value); return value; } currCtx = currCtx.parent!; } throw new Error(`Not declared variable ${expr.name}`); } visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: _ExecutionContext): never { throw new Error('Cannot interpret a WrappedNodeExpr.'); } visitTypeofExpr(ast: o.TypeofExpr, ctx: _ExecutionContext): never { throw new Error('Cannot interpret a TypeofExpr'); } visitReadVarExpr(ast: o.ReadVarExpr, ctx: _ExecutionContext): any { let varName = ast.name!; if (ast.builtin != null) { switch (ast.builtin) { case o.BuiltinVar.Super: return Object.getPrototypeOf(ctx.instance); case o.BuiltinVar.This: return ctx.instance; case o.BuiltinVar.CatchError: varName = CATCH_ERROR_VAR; break; case o.BuiltinVar.CatchStack: varName = CATCH_STACK_VAR; break; default: throw new Error(`Unknown builtin variable ${ast.builtin}`); } } let currCtx = ctx; while (currCtx != null) { if (currCtx.vars.has(varName)) { return currCtx.vars.get(varName); } currCtx = currCtx.parent!; } throw new Error(`Not declared variable ${varName}`); } visitWriteKeyExpr(expr: o.WriteKeyExpr, ctx: _ExecutionContext): any { const receiver = expr.receiver.visitExpression(this, ctx); const index = expr.index.visitExpression(this, ctx); const value = expr.value.visitExpression(this, ctx); receiver[index] = value; return value; } visitWritePropExpr(expr: o.WritePropExpr, ctx: _ExecutionContext): any { const receiver = expr.receiver.visitExpression(this, ctx); const value = expr.value.visitExpression(this, ctx); receiver[expr.name] = value; return value; } visitInvokeMethodExpr(expr: o.InvokeMethodExpr, ctx: _ExecutionContext): any { const receiver = expr.receiver.visitExpression(this, ctx); const args = this.visitAllExpressions(expr.args, ctx); let result: any; if (expr.builtin != null) { switch (expr.builtin) { case o.BuiltinMethod.ConcatArray: result = receiver.concat(...args); break; case o.BuiltinMethod.SubscribeObservable: result = receiver.subscribe({next: args[0]}); break; case o.BuiltinMethod.Bind: result = receiver.bind(...args); break; default: throw new Error(`Unknown builtin method ${expr.builtin}`); } } else { result = receiver[expr.name!].apply(receiver, args); } return result; } visitInvokeFunctionExpr(stmt: o.InvokeFunctionExpr, ctx: _ExecutionContext): any { const args = this.visitAllExpressions(stmt.args, ctx); const fnExpr = stmt.fn; if (fnExpr instanceof o.ReadVarExpr && fnExpr.builtin === o.BuiltinVar.Super) { ctx.instance!.constructor.prototype.constructor.apply(ctx.instance, args); return null; } else { const fn = stmt.fn.visitExpression(this, ctx); return fn.apply(null, args); } } visitReturnStmt(stmt: o.ReturnStatement, ctx: _ExecutionContext): any { return new ReturnValue(stmt.value.visitExpression(this, ctx)); } visitDeclareClassStmt(stmt: o.ClassStmt, ctx: _ExecutionContext): any { const clazz = createDynamicClass(stmt, ctx, this); ctx.vars.set(stmt.name, clazz); if (stmt.hasModifier(o.StmtModifier.Exported)) { ctx.exports.push(stmt.name); } return null; } visitExpressionStmt(stmt: o.ExpressionStatement, ctx: _ExecutionContext): any { return stmt.expr.visitExpression(this, ctx); } visitIfStmt(stmt: o.IfStmt, ctx: _ExecutionContext): any { const condition = stmt.condition.visitExpression(this, ctx); if (condition) { return this.visitAllStatements(stmt.trueCase, ctx); } else if (stmt.falseCase != null) { return this.visitAllStatements(stmt.falseCase, ctx); } return null; } visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: _ExecutionContext): any { try { return this.visitAllStatements(stmt.bodyStmts, ctx); } catch (e) { const childCtx = ctx.createChildWihtLocalVars(); childCtx.vars.set(CATCH_ERROR_VAR, e); childCtx.vars.set(CATCH_STACK_VAR, e.stack); return this.visitAllStatements(stmt.catchStmts, childCtx); } } visitThrowStmt(stmt: o.ThrowStmt, ctx: _ExecutionContext): any { throw stmt.error.visitExpression(this, ctx); } visitCommentStmt(stmt: o.CommentStmt, context?: any): any { return null; } visitJSDocCommentStmt(stmt: o.JSDocCommentStmt, context?: any): any { return null; } visitInstantiateExpr(ast: o.InstantiateExpr, ctx: _ExecutionContext): any { const args = this.visitAllExpressions(ast.args, ctx); const clazz = ast.classExpr.visitExpression(this, ctx); return new clazz(...args); } visitLiteralExpr(ast: o.LiteralExpr, ctx: _ExecutionContext): any { return ast.value; } visitLocalizedString(ast: o.LocalizedString, context: any): any { return null; } visitExternalExpr(ast: o.ExternalExpr, ctx: _ExecutionContext): any { return this.reflector.resolveExternalReference(ast.value); } visitConditionalExpr(ast: o.ConditionalExpr, ctx: _ExecutionContext): any { if (ast.condition.visitExpression(this, ctx)) { return ast.trueCase.visitExpression(this, ctx); } else if (ast.falseCase != null) { return ast.falseCase.visitExpression(this, ctx); } return null; } visitNotExpr(ast: o.NotExpr, ctx: _ExecutionContext): any { return !ast.condition.visitExpression(this, ctx); } visitAssertNotNullExpr(ast: o.AssertNotNull, ctx: _ExecutionContext): any { return ast.condition.visitExpression(this, ctx); } visitCastExpr(ast: o.CastExpr, ctx: _ExecutionContext): any { return ast.value.visitExpression(this, ctx); } visitFunctionExpr(ast: o.FunctionExpr, ctx: _ExecutionContext): any { const paramNames = ast.params.map((param) => param.name); return _declareFn(paramNames, ast.statements, ctx, this); } visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: _ExecutionContext): any { const paramNames = stmt.params.map((param) => param.name); ctx.vars.set(stmt.name, _declareFn(paramNames, stmt.statements, ctx, this)); if (stmt.hasModifier(o.StmtModifier.Exported)) { ctx.exports.push(stmt.name); } return null; } visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: _ExecutionContext): any { const lhs = () => ast.lhs.visitExpression(this, ctx); const rhs = () => ast.rhs.visitExpression(this, ctx); switch (ast.operator) { case o.BinaryOperator.Equals: return lhs() == rhs(); case o.BinaryOperator.Identical: return lhs() === rhs(); case o.BinaryOperator.NotEquals: return lhs() != rhs(); case o.BinaryOperator.NotIdentical: return lhs() !== rhs(); case o.BinaryOperator.And: return lhs() && rhs(); case o.BinaryOperator.Or: return lhs() || rhs(); case o.BinaryOperator.Plus: return lhs() + rhs(); case o.BinaryOperator.Minus: return lhs() - rhs(); case o.BinaryOperator.Divide: return lhs() / rhs(); case o.BinaryOperator.Multiply: return lhs() * rhs(); case o.BinaryOperator.Modulo: return lhs() % rhs(); case o.BinaryOperator.Lower: return lhs() < rhs(); case o.BinaryOperator.LowerEquals: return lhs() <= rhs(); case o.BinaryOperator.Bigger: return lhs() > rhs(); case o.BinaryOperator.BiggerEquals: return lhs() >= rhs(); default: throw new Error(`Unknown operator ${ast.operator}`); } } visitReadPropExpr(ast: o.ReadPropExpr, ctx: _ExecutionContext): any { let result: any; const receiver = ast.receiver.visitExpression(this, ctx); result = receiver[ast.name]; return result; } visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: _ExecutionContext): any { const receiver = ast.receiver.visitExpression(this, ctx); const prop = ast.index.visitExpression(this, ctx); return receiver[prop]; } visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: _ExecutionContext): any { return this.visitAllExpressions(ast.entries, ctx); } visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: _ExecutionContext): any { const result: {[k: string]: any} = {}; ast.entries.forEach(entry => result[entry.key] = entry.value.visitExpression(this, ctx)); return result; } visitCommaExpr(ast: o.CommaExpr, context: any): any { const values = this.visitAllExpressions(ast.parts, context); return values[values.length - 1]; } visitAllExpressions(expressions: o.Expression[], ctx: _ExecutionContext): any { return expressions.map((expr) => expr.visitExpression(this, ctx)); } visitAllStatements(statements: o.Statement[], ctx: _ExecutionContext): ReturnValue|null { for (let i = 0; i < statements.length; i++) { const stmt = statements[i]; const val = stmt.visitStatement(this, ctx); if (val instanceof ReturnValue) { return val; } } return null; } } function _declareFn( varNames: string[], statements: o.Statement[], ctx: _ExecutionContext, visitor: StatementInterpreter): Function { return (...args: any[]) => _executeFunctionStatements(varNames, args, statements, ctx, visitor); } const CATCH_ERROR_VAR = 'error'; const CATCH_STACK_VAR = 'stack';