import * as cdAst from '../expression_parser/ast'; import * as o from '../output/output_ast'; import {Identifiers} from '../identifiers'; import {BaseException} from 'angular2/src/facade/exceptions'; import {isBlank, isPresent, isArray, CONST_EXPR} from 'angular2/src/facade/lang'; var IMPLICIT_RECEIVER = o.variable('#implicit'); export interface NameResolver { createPipe(name: string): o.Expression; getVariable(name: string): o.Expression; createLiteralArray(values: o.Expression[]): o.Expression; createLiteralMap(values: Array>): o.Expression; } export class ExpressionWithWrappedValueInfo { constructor(public expression: o.Expression, public needsValueUnwrapper: boolean) {} } export function convertCdExpressionToIr( nameResolver: NameResolver, implicitReceiver: o.Expression, expression: cdAst.AST, valueUnwrapper: o.ReadVarExpr): ExpressionWithWrappedValueInfo { var visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, valueUnwrapper); var irAst: o.Expression = expression.visit(visitor, _Mode.Expression); return new ExpressionWithWrappedValueInfo(irAst, visitor.needsValueUnwrapper); } export function convertCdStatementToIr(nameResolver: NameResolver, implicitReceiver: o.Expression, stmt: cdAst.AST): o.Statement[] { var visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, null); var statements = []; flattenStatements(stmt.visit(visitor, _Mode.Statement), statements); return statements; } enum _Mode { Statement, Expression } function ensureStatementMode(mode: _Mode, ast: cdAst.AST) { if (mode !== _Mode.Statement) { throw new BaseException(`Expected a statement, but saw ${ast}`); } } function ensureExpressionMode(mode: _Mode, ast: cdAst.AST) { if (mode !== _Mode.Expression) { throw new BaseException(`Expected an expression, but saw ${ast}`); } } function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expression | o.Statement { if (mode === _Mode.Statement) { return expr.toStmt(); } else { return expr; } } class _AstToIrVisitor implements cdAst.AstVisitor { public needsValueUnwrapper: boolean = false; constructor(private _nameResolver: NameResolver, private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr) {} visitBinary(ast: cdAst.Binary, mode: _Mode): any { var op; switch (ast.operation) { case '+': op = o.BinaryOperator.Plus; break; case '-': op = o.BinaryOperator.Minus; break; case '*': op = o.BinaryOperator.Multiply; break; case '/': op = o.BinaryOperator.Divide; break; case '%': op = o.BinaryOperator.Modulo; break; case '&&': op = o.BinaryOperator.And; break; case '||': op = o.BinaryOperator.Or; break; case '==': op = o.BinaryOperator.Equals; break; case '!=': op = o.BinaryOperator.NotEquals; break; case '===': op = o.BinaryOperator.Identical; break; case '!==': op = o.BinaryOperator.NotIdentical; break; case '<': op = o.BinaryOperator.Lower; break; case '>': op = o.BinaryOperator.Bigger; break; case '<=': op = o.BinaryOperator.LowerEquals; break; case '>=': op = o.BinaryOperator.BiggerEquals; break; default: throw new BaseException(`Unsupported operation ${ast.operation}`); } return convertToStatementIfNeeded( mode, new o.BinaryOperatorExpr(op, ast.left.visit(this, _Mode.Expression), ast.right.visit(this, _Mode.Expression))); } visitChain(ast: cdAst.Chain, mode: _Mode): any { ensureStatementMode(mode, ast); return this.visitAll(ast.expressions, mode); } visitConditional(ast: cdAst.Conditional, mode: _Mode): any { var value: o.Expression = ast.condition.visit(this, _Mode.Expression); return convertToStatementIfNeeded( mode, value.conditional(ast.trueExp.visit(this, _Mode.Expression), ast.falseExp.visit(this, _Mode.Expression))); } visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any { var pipeInstance = this._nameResolver.createPipe(ast.name); var input = ast.exp.visit(this, _Mode.Expression); var args = this.visitAll(ast.args, _Mode.Expression); this.needsValueUnwrapper = true; return convertToStatementIfNeeded( mode, this._valueUnwrapper.callMethod( 'unwrap', [pipeInstance.callMethod('transform', [input, o.literalArr(args)])])); } visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { return convertToStatementIfNeeded(mode, ast.target.visit(this, _Mode.Expression) .callFn(this.visitAll(ast.args, _Mode.Expression))); } visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any { ensureExpressionMode(mode, ast); return IMPLICIT_RECEIVER; } visitInterpolation(ast: cdAst.Interpolation, mode: _Mode): any { ensureExpressionMode(mode, ast); var args = [o.literal(ast.expressions.length)]; for (var i = 0; i < ast.strings.length - 1; i++) { args.push(o.literal(ast.strings[i])); args.push(ast.expressions[i].visit(this, _Mode.Expression)); } args.push(o.literal(ast.strings[ast.strings.length - 1])); return o.importExpr(Identifiers.interpolate).callFn(args); } visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any { return convertToStatementIfNeeded( mode, ast.obj.visit(this, _Mode.Expression).key(ast.key.visit(this, _Mode.Expression))); } visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any { var obj: o.Expression = ast.obj.visit(this, _Mode.Expression); var key: o.Expression = ast.key.visit(this, _Mode.Expression); var value: o.Expression = ast.value.visit(this, _Mode.Expression); return convertToStatementIfNeeded(mode, obj.key(key).set(value)); } visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { return convertToStatementIfNeeded( mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode))); } visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { var parts = []; for (var i = 0; i < ast.keys.length; i++) { parts.push([ast.keys[i], ast.values[i].visit(this, _Mode.Expression)]); } return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts)); } visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { return convertToStatementIfNeeded(mode, o.literal(ast.value)); } visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { var args = this.visitAll(ast.args, _Mode.Expression); var result = null; var receiver = ast.receiver.visit(this, _Mode.Expression); if (receiver === IMPLICIT_RECEIVER) { var varExpr = this._nameResolver.getVariable(ast.name); if (isPresent(varExpr)) { result = varExpr.callFn(args); } else { receiver = this._implicitReceiver; } } if (isBlank(result)) { result = receiver.callMethod(ast.name, args); } return convertToStatementIfNeeded(mode, result); } visitPrefixNot(ast: cdAst.PrefixNot, mode: _Mode): any { return convertToStatementIfNeeded(mode, o.not(ast.expression.visit(this, _Mode.Expression))); } visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any { var result = null; var receiver = ast.receiver.visit(this, _Mode.Expression); if (receiver === IMPLICIT_RECEIVER) { result = this._nameResolver.getVariable(ast.name); if (isBlank(result)) { receiver = this._implicitReceiver; } } if (isBlank(result)) { result = receiver.prop(ast.name); } return convertToStatementIfNeeded(mode, result); } visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any { var receiver: o.Expression = ast.receiver.visit(this, _Mode.Expression); if (receiver === IMPLICIT_RECEIVER) { var varExpr = this._nameResolver.getVariable(ast.name); if (isPresent(varExpr)) { throw new BaseException('Cannot reassign a variable binding'); } receiver = this._implicitReceiver; } return convertToStatementIfNeeded( mode, receiver.prop(ast.name).set(ast.value.visit(this, _Mode.Expression))); } visitSafePropertyRead(ast: cdAst.SafePropertyRead, mode: _Mode): any { var receiver = ast.receiver.visit(this, _Mode.Expression); return convertToStatementIfNeeded( mode, receiver.isBlank().conditional(o.NULL_EXPR, receiver.prop(ast.name))); } visitSafeMethodCall(ast: cdAst.SafeMethodCall, mode: _Mode): any { var receiver = ast.receiver.visit(this, _Mode.Expression); var args = this.visitAll(ast.args, _Mode.Expression); return convertToStatementIfNeeded( mode, receiver.isBlank().conditional(o.NULL_EXPR, receiver.callMethod(ast.name, args))); } visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => ast.visit(this, mode)); } visitQuote(ast: cdAst.Quote, mode: _Mode): any { throw new BaseException('Quotes are not supported for evaluation!'); } } function flattenStatements(arg: any, output: o.Statement[]) { if (isArray(arg)) { (arg).forEach((entry) => flattenStatements(entry, output)); } else { output.push(arg); } }