Added error correction so the parser always returns an AST Added span information to the expression parser Refactored the test to account for the difference in error reporting Added tests for error corretion Modified tests to validate the span information
394 lines
13 KiB
TypeScript
394 lines
13 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 {ListWrapper} from '../facade/collection';
|
|
import {isBlank} from '../facade/lang';
|
|
|
|
export class ParserError {
|
|
public message: string;
|
|
constructor(
|
|
message: string, public input: string, public errLocation: string, public ctxLocation?: any) {
|
|
this.message = `Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`;
|
|
}
|
|
}
|
|
|
|
export class ParseSpan {
|
|
constructor(public start: number, public end: number) {}
|
|
}
|
|
|
|
export class AST {
|
|
constructor(public span: ParseSpan) {}
|
|
visit(visitor: AstVisitor, context: any = null): any { return null; }
|
|
toString(): string { return 'AST'; }
|
|
}
|
|
|
|
/**
|
|
* Represents a quoted expression of the form:
|
|
*
|
|
* quote = prefix `:` uninterpretedExpression
|
|
* prefix = identifier
|
|
* uninterpretedExpression = arbitrary string
|
|
*
|
|
* A quoted expression is meant to be pre-processed by an AST transformer that
|
|
* converts it into another AST that no longer contains quoted expressions.
|
|
* It is meant to allow third-party developers to extend Angular template
|
|
* expression language. The `uninterpretedExpression` part of the quote is
|
|
* therefore not interpreted by the Angular's own expression parser.
|
|
*/
|
|
export class Quote extends AST {
|
|
constructor(
|
|
span: ParseSpan, public prefix: string, public uninterpretedExpression: string,
|
|
public location: any) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitQuote(this, context); }
|
|
toString(): string { return 'Quote'; }
|
|
}
|
|
|
|
export class EmptyExpr extends AST {
|
|
visit(visitor: AstVisitor, context: any = null) {
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
export class ImplicitReceiver extends AST {
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitImplicitReceiver(this, context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Multiple expressions separated by a semicolon.
|
|
*/
|
|
export class Chain extends AST {
|
|
constructor(span: ParseSpan, public expressions: any[]) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitChain(this, context); }
|
|
}
|
|
|
|
export class Conditional extends AST {
|
|
constructor(span: ParseSpan, public condition: AST, public trueExp: AST, public falseExp: AST) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitConditional(this, context);
|
|
}
|
|
}
|
|
|
|
export class PropertyRead extends AST {
|
|
constructor(span: ParseSpan, public receiver: AST, public name: string) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitPropertyRead(this, context);
|
|
}
|
|
}
|
|
|
|
export class PropertyWrite extends AST {
|
|
constructor(span: ParseSpan, public receiver: AST, public name: string, public value: AST) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitPropertyWrite(this, context);
|
|
}
|
|
}
|
|
|
|
export class SafePropertyRead extends AST {
|
|
constructor(span: ParseSpan, public receiver: AST, public name: string) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitSafePropertyRead(this, context);
|
|
}
|
|
}
|
|
|
|
export class KeyedRead extends AST {
|
|
constructor(span: ParseSpan, public obj: AST, public key: AST) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitKeyedRead(this, context);
|
|
}
|
|
}
|
|
|
|
export class KeyedWrite extends AST {
|
|
constructor(span: ParseSpan, public obj: AST, public key: AST, public value: AST) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitKeyedWrite(this, context);
|
|
}
|
|
}
|
|
|
|
export class BindingPipe extends AST {
|
|
constructor(span: ParseSpan, public exp: AST, public name: string, public args: any[]) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPipe(this, context); }
|
|
}
|
|
|
|
export class LiteralPrimitive extends AST {
|
|
constructor(span: ParseSpan, public value: any) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitLiteralPrimitive(this, context);
|
|
}
|
|
}
|
|
|
|
export class LiteralArray extends AST {
|
|
constructor(span: ParseSpan, public expressions: any[]) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitLiteralArray(this, context);
|
|
}
|
|
}
|
|
|
|
export class LiteralMap extends AST {
|
|
constructor(span: ParseSpan, public keys: any[], public values: any[]) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitLiteralMap(this, context);
|
|
}
|
|
}
|
|
|
|
export class Interpolation extends AST {
|
|
constructor(span: ParseSpan, public strings: any[], public expressions: any[]) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitInterpolation(this, context);
|
|
}
|
|
}
|
|
|
|
export class Binary extends AST {
|
|
constructor(span: ParseSpan, public operation: string, public left: AST, public right: AST) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitBinary(this, context);
|
|
}
|
|
}
|
|
|
|
export class PrefixNot extends AST {
|
|
constructor(span: ParseSpan, public expression: AST) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitPrefixNot(this, context);
|
|
}
|
|
}
|
|
|
|
export class MethodCall extends AST {
|
|
constructor(span: ParseSpan, public receiver: AST, public name: string, public args: any[]) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitMethodCall(this, context);
|
|
}
|
|
}
|
|
|
|
export class SafeMethodCall extends AST {
|
|
constructor(span: ParseSpan, public receiver: AST, public name: string, public args: any[]) {
|
|
super(span);
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitSafeMethodCall(this, context);
|
|
}
|
|
}
|
|
|
|
export class FunctionCall extends AST {
|
|
constructor(span: ParseSpan, public target: AST, public args: any[]) { super(span); }
|
|
visit(visitor: AstVisitor, context: any = null): any {
|
|
return visitor.visitFunctionCall(this, context);
|
|
}
|
|
}
|
|
|
|
export class ASTWithSource extends AST {
|
|
constructor(
|
|
public ast: AST, public source: string, public location: string,
|
|
public errors: ParserError[]) {
|
|
super(new ParseSpan(0, isBlank(source) ? 0 : source.length));
|
|
}
|
|
visit(visitor: AstVisitor, context: any = null): any { return this.ast.visit(visitor, context); }
|
|
toString(): string { return `${this.source} in ${this.location}`; }
|
|
}
|
|
|
|
export class TemplateBinding {
|
|
constructor(
|
|
public key: string, public keyIsVar: boolean, public name: string,
|
|
public expression: ASTWithSource) {}
|
|
}
|
|
|
|
export interface AstVisitor {
|
|
visitBinary(ast: Binary, context: any): any;
|
|
visitChain(ast: Chain, context: any): any;
|
|
visitConditional(ast: Conditional, context: any): any;
|
|
visitFunctionCall(ast: FunctionCall, context: any): any;
|
|
visitImplicitReceiver(ast: ImplicitReceiver, context: any): any;
|
|
visitInterpolation(ast: Interpolation, context: any): any;
|
|
visitKeyedRead(ast: KeyedRead, context: any): any;
|
|
visitKeyedWrite(ast: KeyedWrite, context: any): any;
|
|
visitLiteralArray(ast: LiteralArray, context: any): any;
|
|
visitLiteralMap(ast: LiteralMap, context: any): any;
|
|
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any;
|
|
visitMethodCall(ast: MethodCall, context: any): any;
|
|
visitPipe(ast: BindingPipe, context: any): any;
|
|
visitPrefixNot(ast: PrefixNot, context: any): any;
|
|
visitPropertyRead(ast: PropertyRead, context: any): any;
|
|
visitPropertyWrite(ast: PropertyWrite, context: any): any;
|
|
visitQuote(ast: Quote, context: any): any;
|
|
visitSafeMethodCall(ast: SafeMethodCall, context: any): any;
|
|
visitSafePropertyRead(ast: SafePropertyRead, context: any): any;
|
|
}
|
|
|
|
export class RecursiveAstVisitor implements AstVisitor {
|
|
visitBinary(ast: Binary, context: any): any {
|
|
ast.left.visit(this);
|
|
ast.right.visit(this);
|
|
return null;
|
|
}
|
|
visitChain(ast: Chain, context: any): any { return this.visitAll(ast.expressions, context); }
|
|
visitConditional(ast: Conditional, context: any): any {
|
|
ast.condition.visit(this);
|
|
ast.trueExp.visit(this);
|
|
ast.falseExp.visit(this);
|
|
return null;
|
|
}
|
|
visitPipe(ast: BindingPipe, context: any): any {
|
|
ast.exp.visit(this);
|
|
this.visitAll(ast.args, context);
|
|
return null;
|
|
}
|
|
visitFunctionCall(ast: FunctionCall, context: any): any {
|
|
ast.target.visit(this);
|
|
this.visitAll(ast.args, context);
|
|
return null;
|
|
}
|
|
visitImplicitReceiver(ast: ImplicitReceiver, context: any): any { return null; }
|
|
visitInterpolation(ast: Interpolation, context: any): any {
|
|
return this.visitAll(ast.expressions, context);
|
|
}
|
|
visitKeyedRead(ast: KeyedRead, context: any): any {
|
|
ast.obj.visit(this);
|
|
ast.key.visit(this);
|
|
return null;
|
|
}
|
|
visitKeyedWrite(ast: KeyedWrite, context: any): any {
|
|
ast.obj.visit(this);
|
|
ast.key.visit(this);
|
|
ast.value.visit(this);
|
|
return null;
|
|
}
|
|
visitLiteralArray(ast: LiteralArray, context: any): any {
|
|
return this.visitAll(ast.expressions, context);
|
|
}
|
|
visitLiteralMap(ast: LiteralMap, context: any): any { return this.visitAll(ast.values, context); }
|
|
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any { return null; }
|
|
visitMethodCall(ast: MethodCall, context: any): any {
|
|
ast.receiver.visit(this);
|
|
return this.visitAll(ast.args, context);
|
|
}
|
|
visitPrefixNot(ast: PrefixNot, context: any): any {
|
|
ast.expression.visit(this);
|
|
return null;
|
|
}
|
|
visitPropertyRead(ast: PropertyRead, context: any): any {
|
|
ast.receiver.visit(this);
|
|
return null;
|
|
}
|
|
visitPropertyWrite(ast: PropertyWrite, context: any): any {
|
|
ast.receiver.visit(this);
|
|
ast.value.visit(this);
|
|
return null;
|
|
}
|
|
visitSafePropertyRead(ast: SafePropertyRead, context: any): any {
|
|
ast.receiver.visit(this);
|
|
return null;
|
|
}
|
|
visitSafeMethodCall(ast: SafeMethodCall, context: any): any {
|
|
ast.receiver.visit(this);
|
|
return this.visitAll(ast.args, context);
|
|
}
|
|
visitAll(asts: AST[], context: any): any {
|
|
asts.forEach(ast => ast.visit(this, context));
|
|
return null;
|
|
}
|
|
visitQuote(ast: Quote, context: any): any { return null; }
|
|
}
|
|
|
|
export class AstTransformer implements AstVisitor {
|
|
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
|
|
|
|
visitInterpolation(ast: Interpolation, context: any): AST {
|
|
return new Interpolation(ast.span, ast.strings, this.visitAll(ast.expressions));
|
|
}
|
|
|
|
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST {
|
|
return new LiteralPrimitive(ast.span, ast.value);
|
|
}
|
|
|
|
visitPropertyRead(ast: PropertyRead, context: any): AST {
|
|
return new PropertyRead(ast.span, ast.receiver.visit(this), ast.name);
|
|
}
|
|
|
|
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
|
|
return new PropertyWrite(ast.span, ast.receiver.visit(this), ast.name, ast.value);
|
|
}
|
|
|
|
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
|
|
return new SafePropertyRead(ast.span, ast.receiver.visit(this), ast.name);
|
|
}
|
|
|
|
visitMethodCall(ast: MethodCall, context: any): AST {
|
|
return new MethodCall(ast.span, ast.receiver.visit(this), ast.name, this.visitAll(ast.args));
|
|
}
|
|
|
|
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
|
|
return new SafeMethodCall(
|
|
ast.span, ast.receiver.visit(this), ast.name, this.visitAll(ast.args));
|
|
}
|
|
|
|
visitFunctionCall(ast: FunctionCall, context: any): AST {
|
|
return new FunctionCall(ast.span, ast.target.visit(this), this.visitAll(ast.args));
|
|
}
|
|
|
|
visitLiteralArray(ast: LiteralArray, context: any): AST {
|
|
return new LiteralArray(ast.span, this.visitAll(ast.expressions));
|
|
}
|
|
|
|
visitLiteralMap(ast: LiteralMap, context: any): AST {
|
|
return new LiteralMap(ast.span, ast.keys, this.visitAll(ast.values));
|
|
}
|
|
|
|
visitBinary(ast: Binary, context: any): AST {
|
|
return new Binary(ast.span, ast.operation, ast.left.visit(this), ast.right.visit(this));
|
|
}
|
|
|
|
visitPrefixNot(ast: PrefixNot, context: any): AST {
|
|
return new PrefixNot(ast.span, ast.expression.visit(this));
|
|
}
|
|
|
|
visitConditional(ast: Conditional, context: any): AST {
|
|
return new Conditional(
|
|
ast.span, ast.condition.visit(this), ast.trueExp.visit(this), ast.falseExp.visit(this));
|
|
}
|
|
|
|
visitPipe(ast: BindingPipe, context: any): AST {
|
|
return new BindingPipe(ast.span, ast.exp.visit(this), ast.name, this.visitAll(ast.args));
|
|
}
|
|
|
|
visitKeyedRead(ast: KeyedRead, context: any): AST {
|
|
return new KeyedRead(ast.span, ast.obj.visit(this), ast.key.visit(this));
|
|
}
|
|
|
|
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
|
|
return new KeyedWrite(
|
|
ast.span, ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
|
|
}
|
|
|
|
visitAll(asts: any[]): any[] {
|
|
var res = ListWrapper.createFixedSize(asts.length);
|
|
for (var i = 0; i < asts.length; ++i) {
|
|
res[i] = asts[i].visit(this);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
visitChain(ast: Chain, context: any): AST {
|
|
return new Chain(ast.span, this.visitAll(ast.expressions));
|
|
}
|
|
|
|
visitQuote(ast: Quote, context: any): AST {
|
|
return new Quote(ast.span, ast.prefix, ast.uninterpretedExpression, ast.location);
|
|
}
|
|
}
|