@auscope/angular2parse 20.0.2 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -0
- package/LICENSE +21 -0
- package/README.md +66 -49
- package/angular.json +56 -0
- package/package.json +41 -33
- package/projects/angular2parse/README.md +49 -0
- package/projects/angular2parse/ng-package.json +7 -0
- package/projects/angular2parse/package.json +19 -0
- package/projects/angular2parse/src/lib/angular/compiler/assertions.d.ts +10 -0
- package/projects/angular2parse/src/lib/angular/compiler/assertions.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/assertions.js +45 -0
- package/projects/angular2parse/src/lib/angular/compiler/assertions.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/assertions.ts +49 -0
- package/projects/angular2parse/src/lib/angular/compiler/ast.d.ts +243 -0
- package/projects/angular2parse/src/lib/angular/compiler/ast.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/ast.js +393 -0
- package/projects/angular2parse/src/lib/angular/compiler/ast.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/ast.ts +393 -0
- package/projects/angular2parse/src/lib/angular/compiler/chars.d.ts +70 -0
- package/projects/angular2parse/src/lib/angular/compiler/chars.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/chars.js +78 -0
- package/projects/angular2parse/src/lib/angular/compiler/chars.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/chars.ts +89 -0
- package/projects/angular2parse/src/lib/angular/compiler/interpolation-config.d.ts +15 -0
- package/projects/angular2parse/src/lib/angular/compiler/interpolation-config.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/interpolation-config.js +24 -0
- package/projects/angular2parse/src/lib/angular/compiler/interpolation-config.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/interpolation-config.ts +25 -0
- package/projects/angular2parse/src/lib/angular/compiler/lexer.d.ts +45 -0
- package/projects/angular2parse/src/lib/angular/compiler/lexer.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/lexer.js +344 -0
- package/projects/angular2parse/src/lib/angular/compiler/lexer.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/lexer.ts +383 -0
- package/projects/angular2parse/src/lib/angular/compiler/parser.d.ts +93 -0
- package/projects/angular2parse/src/lib/angular/compiler/parser.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/parser.js +735 -0
- package/projects/angular2parse/src/lib/angular/compiler/parser.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler/parser.ts +825 -0
- package/projects/angular2parse/src/lib/angular/compiler.d.ts +7 -0
- package/projects/angular2parse/src/lib/angular/compiler.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler.js +7 -0
- package/projects/angular2parse/src/lib/angular/compiler.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/compiler.ts +6 -0
- package/projects/angular2parse/src/lib/angular/facade/lang.d.ts +45 -0
- package/projects/angular2parse/src/lib/angular/facade/lang.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/facade/lang.js +85 -0
- package/projects/angular2parse/src/lib/angular/facade/lang.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/facade/lang.ts +124 -0
- package/projects/angular2parse/src/lib/angular/facade.d.ts +2 -0
- package/projects/angular2parse/src/lib/angular/facade.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular/facade.js +2 -0
- package/projects/angular2parse/src/lib/angular/facade.js.map +1 -0
- package/projects/angular2parse/src/lib/angular/facade.ts +1 -0
- package/projects/angular2parse/src/lib/angular.d.ts +3 -0
- package/projects/angular2parse/src/lib/angular.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/angular.js +3 -0
- package/projects/angular2parse/src/lib/angular.js.map +1 -0
- package/projects/angular2parse/src/lib/angular.ts +2 -0
- package/projects/angular2parse/src/lib/module.d.ts +6 -0
- package/projects/angular2parse/src/lib/module.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/module.js +17 -0
- package/projects/angular2parse/src/lib/module.js.map +1 -0
- package/projects/angular2parse/src/lib/module.ts +12 -0
- package/projects/angular2parse/src/lib/parse.d.ts +19 -0
- package/projects/angular2parse/src/lib/parse.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/parse.js +61 -0
- package/projects/angular2parse/src/lib/parse.js.map +1 -0
- package/projects/angular2parse/src/lib/parse.ts +78 -0
- package/projects/angular2parse/src/lib/util/binary-operations.d.ts +2 -0
- package/projects/angular2parse/src/lib/util/binary-operations.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/util/binary-operations.js +18 -0
- package/projects/angular2parse/src/lib/util/binary-operations.js.map +1 -0
- package/projects/angular2parse/src/lib/util/binary-operations.ts +17 -0
- package/projects/angular2parse/src/lib/util/lang.d.ts +5 -0
- package/projects/angular2parse/src/lib/util/lang.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/util/lang.js +13 -0
- package/projects/angular2parse/src/lib/util/lang.js.map +1 -0
- package/projects/angular2parse/src/lib/util/lang.ts +15 -0
- package/projects/angular2parse/src/lib/util.d.ts +3 -0
- package/projects/angular2parse/src/lib/util.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/util.js +3 -0
- package/projects/angular2parse/src/lib/util.js.map +1 -0
- package/projects/angular2parse/src/lib/util.ts +2 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-compiler.d.ts +24 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-compiler.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-compiler.js +96 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-compiler.js.map +1 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-compiler.ts +149 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-resolver.d.ts +26 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-resolver.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-resolver.js +141 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-resolver.js.map +1 -0
- package/projects/angular2parse/src/lib/visitors/parse-visitor-resolver.ts +208 -0
- package/projects/angular2parse/src/lib/visitors.d.ts +3 -0
- package/projects/angular2parse/src/lib/visitors.d.ts.map +1 -0
- package/projects/angular2parse/src/lib/visitors.js +3 -0
- package/projects/angular2parse/src/lib/visitors.js.map +1 -0
- package/projects/angular2parse/src/lib/visitors.ts +2 -0
- package/projects/angular2parse/src/public-api.d.ts +3 -0
- package/projects/angular2parse/src/public-api.d.ts.map +1 -0
- package/projects/angular2parse/src/public-api.js +6 -0
- package/projects/angular2parse/src/public-api.js.map +1 -0
- package/projects/angular2parse/src/public-api.ts +5 -0
- package/projects/angular2parse/tsconfig.lib.json +22 -0
- package/projects/angular2parse/tsconfig.lib.prod.json +15 -0
- package/tsconfig.json +21 -0
- package/fesm2022/auscope-angular2parse.mjs +0 -2033
- package/fesm2022/auscope-angular2parse.mjs.map +0 -1
- package/index.d.ts +0 -32
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google Inc. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.io/license
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as chars from './chars';
|
|
10
|
+
import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
|
|
11
|
+
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation-config';
|
|
12
|
+
|
|
13
|
+
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
|
|
14
|
+
import {EOF, Lexer, Token, TokenType, isIdentifier, isQuote} from './lexer';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class SplitInterpolation {
|
|
18
|
+
constructor(public strings: string[], public expressions: string[], public offsets: number[]) {}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class TemplateBindingParseResult {
|
|
22
|
+
constructor(
|
|
23
|
+
public templateBindings: TemplateBinding[], public warnings: string[],
|
|
24
|
+
public errors: ParserError[]) {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
|
|
28
|
+
const pattern = escapeRegExp(config.start) + '([\\s\\S]*?)' + escapeRegExp(config.end);
|
|
29
|
+
return new RegExp(pattern, 'g');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class Parser {
|
|
33
|
+
private errors: ParserError[] = [];
|
|
34
|
+
|
|
35
|
+
constructor(private _lexer: Lexer) {}
|
|
36
|
+
|
|
37
|
+
parseAction(
|
|
38
|
+
input: string, location: any,
|
|
39
|
+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
|
40
|
+
this._checkNoInterpolation(input, location, interpolationConfig);
|
|
41
|
+
const sourceToLex = this._stripComments(input);
|
|
42
|
+
const tokens = this._lexer.tokenize(this._stripComments(input));
|
|
43
|
+
const ast = new _ParseAST(
|
|
44
|
+
input, location, tokens, sourceToLex.length, true, this.errors,
|
|
45
|
+
input.length - sourceToLex.length)
|
|
46
|
+
.parseChain();
|
|
47
|
+
return new ASTWithSource(ast, input, location, this.errors);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
parseBinding(
|
|
51
|
+
input: string, location: any,
|
|
52
|
+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
|
53
|
+
const ast = this._parseBindingAst(input, location, interpolationConfig);
|
|
54
|
+
return new ASTWithSource(ast, input, location, this.errors);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
parseSimpleBinding(
|
|
58
|
+
input: string, location: string,
|
|
59
|
+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
|
60
|
+
const ast = this._parseBindingAst(input, location, interpolationConfig);
|
|
61
|
+
const errors = SimpleExpressionChecker.check(ast);
|
|
62
|
+
if (errors.length > 0) {
|
|
63
|
+
this._reportError(
|
|
64
|
+
`Host binding expression cannot contain ${errors.join(' ')}`, input, location);
|
|
65
|
+
}
|
|
66
|
+
return new ASTWithSource(ast, input, location, this.errors);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private _reportError(message: string, input: string, errLocation: string, ctxLocation?: any) {
|
|
70
|
+
this.errors.push(new ParserError(message, input, errLocation, ctxLocation));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _parseBindingAst(
|
|
74
|
+
input: string, location: string, interpolationConfig: InterpolationConfig): AST {
|
|
75
|
+
// Quotes expressions use 3rd-party expression language. We don't want to use
|
|
76
|
+
// our lexer or parser for that, so we check for that ahead of time.
|
|
77
|
+
const quote = this._parseQuote(input, location);
|
|
78
|
+
|
|
79
|
+
if (isPresent(quote)) {
|
|
80
|
+
return quote;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._checkNoInterpolation(input, location, interpolationConfig);
|
|
84
|
+
const sourceToLex = this._stripComments(input);
|
|
85
|
+
const tokens = this._lexer.tokenize(sourceToLex);
|
|
86
|
+
return new _ParseAST(
|
|
87
|
+
input, location, tokens, sourceToLex.length, false, this.errors,
|
|
88
|
+
input.length - sourceToLex.length)
|
|
89
|
+
.parseChain();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private _parseQuote(input: string, location: any): AST {
|
|
93
|
+
if (isBlank(input)) throw new Error('Quote expression cannot be blank');
|
|
94
|
+
const prefixSeparatorIndex = input.indexOf(':');
|
|
95
|
+
if (prefixSeparatorIndex == -1) throw new Error('Quote expression requires a prefix');
|
|
96
|
+
const prefix = input.substring(0, prefixSeparatorIndex).trim();
|
|
97
|
+
if (!isIdentifier(prefix)) throw new Error('Invalid quote prefix');
|
|
98
|
+
const uninterpretedExpression = input.substring(prefixSeparatorIndex + 1);
|
|
99
|
+
return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
parseTemplateBindings(prefixToken: string, input: string, location: any):
|
|
103
|
+
TemplateBindingParseResult {
|
|
104
|
+
const tokens = this._lexer.tokenize(input);
|
|
105
|
+
if (prefixToken) {
|
|
106
|
+
// Prefix the tokens with the tokens from prefixToken but have them take no space (0 index).
|
|
107
|
+
const prefixTokens = this._lexer.tokenize(prefixToken).map(t => {
|
|
108
|
+
t.index = 0;
|
|
109
|
+
return t;
|
|
110
|
+
});
|
|
111
|
+
tokens.unshift(...prefixTokens);
|
|
112
|
+
}
|
|
113
|
+
return new _ParseAST(input, location, tokens, input.length, false, this.errors, 0)
|
|
114
|
+
.parseTemplateBindings();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
parseInterpolation(
|
|
118
|
+
input: string, location: any,
|
|
119
|
+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
|
120
|
+
const split = this.splitInterpolation(input, location, interpolationConfig);
|
|
121
|
+
if (split == null) throw new Error(`No interpolation found in ${input} at ${location}`);
|
|
122
|
+
|
|
123
|
+
const expressions: AST[] = [];
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < split.expressions.length; ++i) {
|
|
126
|
+
const expressionText = split.expressions[i];
|
|
127
|
+
const sourceToLex = this._stripComments(expressionText);
|
|
128
|
+
const tokens = this._lexer.tokenize(this._stripComments(split.expressions[i]));
|
|
129
|
+
const ast = new _ParseAST(
|
|
130
|
+
input, location, tokens, sourceToLex.length, false, this.errors,
|
|
131
|
+
split.offsets[i] + (expressionText.length - sourceToLex.length))
|
|
132
|
+
.parseChain();
|
|
133
|
+
expressions.push(ast);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return new ASTWithSource(
|
|
137
|
+
new Interpolation(
|
|
138
|
+
new ParseSpan(0, isBlank(input) ? 0 : input.length), split.strings, expressions),
|
|
139
|
+
input, location, this.errors);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
splitInterpolation(
|
|
143
|
+
input: string, location: string,
|
|
144
|
+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): SplitInterpolation {
|
|
145
|
+
const regexp = _createInterpolateRegExp(interpolationConfig);
|
|
146
|
+
const parts = input.split(regexp);
|
|
147
|
+
if (parts.length <= 1) {
|
|
148
|
+
throw new Error(`Expected interpolation (${interpolationConfig.start}...${interpolationConfig.end}) in ${input} at ${location}`);
|
|
149
|
+
}
|
|
150
|
+
const strings: string[] = [];
|
|
151
|
+
const expressions: string[] = [];
|
|
152
|
+
const offsets: number[] = [];
|
|
153
|
+
let offset = 0;
|
|
154
|
+
for (let i = 0; i < parts.length; i++) {
|
|
155
|
+
const part: string = parts[i];
|
|
156
|
+
if (i % 2 === 0) {
|
|
157
|
+
// fixed string
|
|
158
|
+
strings.push(part);
|
|
159
|
+
offset += part.length;
|
|
160
|
+
} else if (part.trim().length > 0) {
|
|
161
|
+
offset += interpolationConfig.start.length;
|
|
162
|
+
expressions.push(part);
|
|
163
|
+
offsets.push(offset);
|
|
164
|
+
offset += part.length + interpolationConfig.end.length;
|
|
165
|
+
} else {
|
|
166
|
+
this._reportError(
|
|
167
|
+
'Blank expressions are not allowed in interpolated strings', input,
|
|
168
|
+
`at column ${this._findInterpolationErrorColumn(parts, i, interpolationConfig)} in`,
|
|
169
|
+
location);
|
|
170
|
+
expressions.push('$implict');
|
|
171
|
+
offsets.push(offset);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return new SplitInterpolation(strings, expressions, offsets);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
|
|
178
|
+
return new ASTWithSource(
|
|
179
|
+
new LiteralPrimitive(new ParseSpan(0, isBlank(input) ? 0 : input.length), input), input,
|
|
180
|
+
location, this.errors);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private _stripComments(input: string): string {
|
|
184
|
+
const i = this._commentStart(input);
|
|
185
|
+
return isPresent(i) ? input.substring(0, i).trim() : input;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private _commentStart(input: string): number | null {
|
|
189
|
+
let outerQuote: number | null = null;
|
|
190
|
+
for (let i = 0; i < input.length - 1; i++) {
|
|
191
|
+
const char = input.charCodeAt(i);
|
|
192
|
+
const nextChar = input.charCodeAt(i + 1);
|
|
193
|
+
|
|
194
|
+
if (char === chars.$SLASH && nextChar == chars.$SLASH && isBlank(outerQuote)) return i;
|
|
195
|
+
|
|
196
|
+
if (outerQuote === char) {
|
|
197
|
+
outerQuote = null;
|
|
198
|
+
} else if (isBlank(outerQuote) && isQuote(char)) {
|
|
199
|
+
outerQuote = char;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private _checkNoInterpolation(
|
|
206
|
+
input: string, location: any, interpolationConfig: InterpolationConfig): void {
|
|
207
|
+
const regexp = _createInterpolateRegExp(interpolationConfig);
|
|
208
|
+
const parts = input.split(regexp);
|
|
209
|
+
if (parts.length > 1) {
|
|
210
|
+
this._reportError(
|
|
211
|
+
`Got interpolation (${interpolationConfig.start}${interpolationConfig.end}) where expression was expected`,
|
|
212
|
+
input,
|
|
213
|
+
`at column ${this._findInterpolationErrorColumn(parts, 1, interpolationConfig)} in`,
|
|
214
|
+
location);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private _findInterpolationErrorColumn(
|
|
219
|
+
parts: string[], partInErrIdx: number, interpolationConfig: InterpolationConfig): number {
|
|
220
|
+
let errLocation = '';
|
|
221
|
+
for (let j = 0; j < partInErrIdx; j++) {
|
|
222
|
+
errLocation += j % 2 === 0 ?
|
|
223
|
+
parts[j] :
|
|
224
|
+
`${interpolationConfig.start}${parts[j]}${interpolationConfig.end}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return errLocation.length;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export class _ParseAST {
|
|
232
|
+
private rparensExpected = 0;
|
|
233
|
+
private rbracketsExpected = 0;
|
|
234
|
+
private rbracesExpected = 0;
|
|
235
|
+
|
|
236
|
+
index: number = 0;
|
|
237
|
+
|
|
238
|
+
constructor(
|
|
239
|
+
public input: string, public location: any, public tokens: Token[],
|
|
240
|
+
public inputLength: number, public parseAction: boolean, private errors: ParserError[],
|
|
241
|
+
private offset: number) {}
|
|
242
|
+
|
|
243
|
+
peek(offset: number): Token {
|
|
244
|
+
const i = this.index + offset;
|
|
245
|
+
return i < this.tokens.length ? this.tokens[i] : EOF;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
get next(): Token { return this.peek(0); }
|
|
249
|
+
|
|
250
|
+
get inputIndex(): number {
|
|
251
|
+
return (this.index < this.tokens.length) ? this.next.index + this.offset :
|
|
252
|
+
this.inputLength + this.offset;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
span(start: number) { return new ParseSpan(start, this.inputIndex); }
|
|
256
|
+
|
|
257
|
+
advance() { this.index++; }
|
|
258
|
+
|
|
259
|
+
optionalCharacter(code: number): boolean {
|
|
260
|
+
if (this.next.isCharacter(code)) {
|
|
261
|
+
this.advance();
|
|
262
|
+
return true;
|
|
263
|
+
} else {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
peekKeywordLet(): boolean { return this.next.isKeywordLet(); }
|
|
269
|
+
|
|
270
|
+
expectCharacter(code: number) {
|
|
271
|
+
if (this.optionalCharacter(code)) return;
|
|
272
|
+
this.error(`Missing expected ${String.fromCharCode(code)}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
optionalOperator(op: string): boolean {
|
|
276
|
+
if (this.next.isOperator(op)) {
|
|
277
|
+
this.advance();
|
|
278
|
+
return true;
|
|
279
|
+
} else {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
expectOperator(operator: string) {
|
|
285
|
+
if (this.optionalOperator(operator)) return;
|
|
286
|
+
this.error(`Missing expected operator ${operator}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
expectIdentifierOrKeyword(): string {
|
|
290
|
+
const n = this.next;
|
|
291
|
+
if (!n.isIdentifier() && !n.isKeyword()) {
|
|
292
|
+
this.error(`Unexpected token ${n}, expected identifier or keyword`);
|
|
293
|
+
return '';
|
|
294
|
+
}
|
|
295
|
+
this.advance();
|
|
296
|
+
const nStr = n.toString();
|
|
297
|
+
if (nStr === null) {
|
|
298
|
+
throw new Error('Unexpected null token');
|
|
299
|
+
}
|
|
300
|
+
return nStr;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
expectIdentifierOrKeywordOrString(): string {
|
|
304
|
+
const n = this.next;
|
|
305
|
+
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
|
|
306
|
+
this.error(`Unexpected token ${n}, expected identifier, keyword, or string`);
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
this.advance();
|
|
310
|
+
const nStr = n.toString();
|
|
311
|
+
if (nStr === null) {
|
|
312
|
+
throw new Error('Unexpected null token');
|
|
313
|
+
}
|
|
314
|
+
return nStr;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
parseChain(): AST {
|
|
318
|
+
const exprs: AST[] = [];
|
|
319
|
+
const start = this.inputIndex;
|
|
320
|
+
while (this.index < this.tokens.length) {
|
|
321
|
+
const expr = this.parsePipe();
|
|
322
|
+
exprs.push(expr);
|
|
323
|
+
|
|
324
|
+
if (this.optionalCharacter(chars.$SEMICOLON)) {
|
|
325
|
+
if (!this.parseAction) {
|
|
326
|
+
this.error('Binding expression cannot contain chained expression');
|
|
327
|
+
}
|
|
328
|
+
while (this.optionalCharacter(chars.$SEMICOLON)) {
|
|
329
|
+
} // read all semicolons
|
|
330
|
+
} else if (this.index < this.tokens.length) {
|
|
331
|
+
this.error(`Unexpected token '${this.next}'`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (exprs.length == 0) return new EmptyExpr(this.span(start));
|
|
335
|
+
if (exprs.length == 1) return exprs[0];
|
|
336
|
+
return new Chain(this.span(start), exprs);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
parsePipe(): AST {
|
|
340
|
+
let result = this.parseExpression();
|
|
341
|
+
if (this.optionalOperator('|')) {
|
|
342
|
+
if (this.parseAction) {
|
|
343
|
+
this.error('Cannot have a pipe in an action expression');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
do {
|
|
347
|
+
const name = this.expectIdentifierOrKeyword();
|
|
348
|
+
const args: AST[] = [];
|
|
349
|
+
while (this.optionalCharacter(chars.$COLON)) {
|
|
350
|
+
args.push(this.parseExpression());
|
|
351
|
+
}
|
|
352
|
+
result = new BindingPipe(this.span(result.span.start), result, name, args);
|
|
353
|
+
} while (this.optionalOperator('|'));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
parseExpression(): AST { return this.parseConditional(); }
|
|
360
|
+
|
|
361
|
+
parseConditional(): AST {
|
|
362
|
+
const start = this.inputIndex;
|
|
363
|
+
const result = this.parseLogicalOr();
|
|
364
|
+
|
|
365
|
+
if (this.optionalOperator('?')) {
|
|
366
|
+
const yes = this.parsePipe();
|
|
367
|
+
let no: AST;
|
|
368
|
+
if (!this.optionalCharacter(chars.$COLON)) {
|
|
369
|
+
const end = this.inputIndex;
|
|
370
|
+
const expression = this.input.substring(start, end);
|
|
371
|
+
this.error(`Conditional expression ${expression} requires all 3 expressions`);
|
|
372
|
+
no = new EmptyExpr(this.span(start));
|
|
373
|
+
} else {
|
|
374
|
+
no = this.parsePipe();
|
|
375
|
+
}
|
|
376
|
+
return new Conditional(this.span(start), result, yes, no);
|
|
377
|
+
} else {
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
parseLogicalOr(): AST {
|
|
383
|
+
// '||'
|
|
384
|
+
let result = this.parseLogicalAnd();
|
|
385
|
+
while (this.optionalOperator('||')) {
|
|
386
|
+
const right = this.parseLogicalAnd();
|
|
387
|
+
result = new Binary(this.span(result.span.start), '||', result, right);
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
parseLogicalAnd(): AST {
|
|
393
|
+
// '&&'
|
|
394
|
+
let result = this.parseEquality();
|
|
395
|
+
while (this.optionalOperator('&&')) {
|
|
396
|
+
const right = this.parseEquality();
|
|
397
|
+
result = new Binary(this.span(result.span.start), '&&', result, right);
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
parseEquality(): AST {
|
|
403
|
+
// '==','!=','===','!=='
|
|
404
|
+
let result = this.parseRelational();
|
|
405
|
+
while (this.next.type == TokenType.Operator) {
|
|
406
|
+
const operator = this.next.strValue;
|
|
407
|
+
switch (operator) {
|
|
408
|
+
case '==':
|
|
409
|
+
case '===':
|
|
410
|
+
case '!=':
|
|
411
|
+
case '!==':
|
|
412
|
+
this.advance();
|
|
413
|
+
const right = this.parseRelational();
|
|
414
|
+
result = new Binary(this.span(result.span.start), operator, result, right);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
parseRelational(): AST {
|
|
423
|
+
// '<', '>', '<=', '>='
|
|
424
|
+
let result = this.parseAdditive();
|
|
425
|
+
while (this.next.type == TokenType.Operator) {
|
|
426
|
+
const operator = this.next.strValue;
|
|
427
|
+
switch (operator) {
|
|
428
|
+
case '<':
|
|
429
|
+
case '>':
|
|
430
|
+
case '<=':
|
|
431
|
+
case '>=':
|
|
432
|
+
this.advance();
|
|
433
|
+
const right = this.parseAdditive();
|
|
434
|
+
result = new Binary(this.span(result.span.start), operator, result, right);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
parseAdditive(): AST {
|
|
443
|
+
// '+', '-'
|
|
444
|
+
let result = this.parseMultiplicative();
|
|
445
|
+
while (this.next.type == TokenType.Operator) {
|
|
446
|
+
const operator = this.next.strValue;
|
|
447
|
+
switch (operator) {
|
|
448
|
+
case '+':
|
|
449
|
+
case '-':
|
|
450
|
+
this.advance();
|
|
451
|
+
let right = this.parseMultiplicative();
|
|
452
|
+
result = new Binary(this.span(result.span.start), operator, result, right);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
parseMultiplicative(): AST {
|
|
461
|
+
// '*', '%', '/'
|
|
462
|
+
let result = this.parsePrefix();
|
|
463
|
+
while (this.next.type == TokenType.Operator) {
|
|
464
|
+
const operator = this.next.strValue;
|
|
465
|
+
switch (operator) {
|
|
466
|
+
case '*':
|
|
467
|
+
case '%':
|
|
468
|
+
case '/':
|
|
469
|
+
this.advance();
|
|
470
|
+
let right = this.parsePrefix();
|
|
471
|
+
result = new Binary(this.span(result.span.start), operator, result, right);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
parsePrefix(): AST {
|
|
480
|
+
if (this.next.type == TokenType.Operator) {
|
|
481
|
+
const start = this.inputIndex;
|
|
482
|
+
const operator = this.next.strValue;
|
|
483
|
+
let result: AST;
|
|
484
|
+
switch (operator) {
|
|
485
|
+
case '+':
|
|
486
|
+
this.advance();
|
|
487
|
+
return this.parsePrefix();
|
|
488
|
+
case '-':
|
|
489
|
+
this.advance();
|
|
490
|
+
result = this.parsePrefix();
|
|
491
|
+
return new Binary(
|
|
492
|
+
this.span(start), operator, new LiteralPrimitive(new ParseSpan(start, start), 0),
|
|
493
|
+
result);
|
|
494
|
+
case '!':
|
|
495
|
+
this.advance();
|
|
496
|
+
result = this.parsePrefix();
|
|
497
|
+
return new PrefixNot(this.span(start), result);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return this.parseCallChain();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
parseCallChain(): AST {
|
|
504
|
+
let result = this.parsePrimary();
|
|
505
|
+
while (true) {
|
|
506
|
+
if (this.optionalCharacter(chars.$PERIOD)) {
|
|
507
|
+
result = this.parseAccessMemberOrMethodCall(result, false);
|
|
508
|
+
|
|
509
|
+
} else if (this.optionalOperator('?.')) {
|
|
510
|
+
result = this.parseAccessMemberOrMethodCall(result, true);
|
|
511
|
+
|
|
512
|
+
} else if (this.optionalCharacter(chars.$LBRACKET)) {
|
|
513
|
+
this.rbracketsExpected++;
|
|
514
|
+
const key = this.parsePipe();
|
|
515
|
+
this.rbracketsExpected--;
|
|
516
|
+
this.expectCharacter(chars.$RBRACKET);
|
|
517
|
+
if (this.optionalOperator('=')) {
|
|
518
|
+
const value = this.parseConditional();
|
|
519
|
+
result = new KeyedWrite(this.span(result.span.start), result, key, value);
|
|
520
|
+
} else {
|
|
521
|
+
result = new KeyedRead(this.span(result.span.start), result, key);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
} else if (this.optionalCharacter(chars.$LPAREN)) {
|
|
525
|
+
this.rparensExpected++;
|
|
526
|
+
const args = this.parseCallArguments();
|
|
527
|
+
this.rparensExpected--;
|
|
528
|
+
this.expectCharacter(chars.$RPAREN);
|
|
529
|
+
result = new FunctionCall(this.span(result.span.start), result, args);
|
|
530
|
+
|
|
531
|
+
} else {
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
parsePrimary(): AST {
|
|
538
|
+
const start = this.inputIndex;
|
|
539
|
+
if (this.optionalCharacter(chars.$LPAREN)) {
|
|
540
|
+
this.rparensExpected++;
|
|
541
|
+
const result = this.parsePipe();
|
|
542
|
+
this.rparensExpected--;
|
|
543
|
+
this.expectCharacter(chars.$RPAREN);
|
|
544
|
+
return result;
|
|
545
|
+
|
|
546
|
+
} else if (this.next.isKeywordNull()) {
|
|
547
|
+
this.advance();
|
|
548
|
+
return new LiteralPrimitive(this.span(start), null);
|
|
549
|
+
|
|
550
|
+
} else if (this.next.isKeywordUndefined()) {
|
|
551
|
+
this.advance();
|
|
552
|
+
return new LiteralPrimitive(this.span(start), void 0);
|
|
553
|
+
|
|
554
|
+
} else if (this.next.isKeywordTrue()) {
|
|
555
|
+
this.advance();
|
|
556
|
+
return new LiteralPrimitive(this.span(start), true);
|
|
557
|
+
|
|
558
|
+
} else if (this.next.isKeywordFalse()) {
|
|
559
|
+
this.advance();
|
|
560
|
+
return new LiteralPrimitive(this.span(start), false);
|
|
561
|
+
|
|
562
|
+
} else if (this.next.isKeywordThis()) {
|
|
563
|
+
this.advance();
|
|
564
|
+
return new ImplicitReceiver(this.span(start));
|
|
565
|
+
|
|
566
|
+
} else if (this.optionalCharacter(chars.$LBRACKET)) {
|
|
567
|
+
this.rbracketsExpected++;
|
|
568
|
+
const elements = this.parseExpressionList(chars.$RBRACKET);
|
|
569
|
+
this.rbracketsExpected--;
|
|
570
|
+
this.expectCharacter(chars.$RBRACKET);
|
|
571
|
+
return new LiteralArray(this.span(start), elements);
|
|
572
|
+
|
|
573
|
+
} else if (this.next.isCharacter(chars.$LBRACE)) {
|
|
574
|
+
return this.parseLiteralMap();
|
|
575
|
+
|
|
576
|
+
} else if (this.next.isIdentifier()) {
|
|
577
|
+
return this.parseAccessMemberOrMethodCall(new ImplicitReceiver(this.span(start)), false);
|
|
578
|
+
|
|
579
|
+
} else if (this.next.isNumber()) {
|
|
580
|
+
const value = this.next.toNumber();
|
|
581
|
+
this.advance();
|
|
582
|
+
return new LiteralPrimitive(this.span(start), value);
|
|
583
|
+
|
|
584
|
+
} else if (this.next.isString()) {
|
|
585
|
+
const literalValue = this.next.toString();
|
|
586
|
+
this.advance();
|
|
587
|
+
return new LiteralPrimitive(this.span(start), literalValue);
|
|
588
|
+
|
|
589
|
+
} else if (this.index >= this.tokens.length) {
|
|
590
|
+
this.error(`Unexpected end of expression: ${this.input}`);
|
|
591
|
+
return new EmptyExpr(this.span(start));
|
|
592
|
+
} else {
|
|
593
|
+
this.error(`Unexpected token ${this.next}`);
|
|
594
|
+
return new EmptyExpr(this.span(start));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
parseExpressionList(terminator: number): AST[] {
|
|
599
|
+
const result: AST[] = [];
|
|
600
|
+
if (!this.next.isCharacter(terminator)) {
|
|
601
|
+
do {
|
|
602
|
+
result.push(this.parsePipe());
|
|
603
|
+
} while (this.optionalCharacter(chars.$COMMA));
|
|
604
|
+
}
|
|
605
|
+
return result;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
parseLiteralMap(): LiteralMap {
|
|
609
|
+
const keys: string[] = [];
|
|
610
|
+
const values: AST[] = [];
|
|
611
|
+
const start = this.inputIndex;
|
|
612
|
+
this.expectCharacter(chars.$LBRACE);
|
|
613
|
+
if (!this.optionalCharacter(chars.$RBRACE)) {
|
|
614
|
+
this.rbracesExpected++;
|
|
615
|
+
do {
|
|
616
|
+
const key = this.expectIdentifierOrKeywordOrString();
|
|
617
|
+
keys.push(key);
|
|
618
|
+
this.expectCharacter(chars.$COLON);
|
|
619
|
+
values.push(this.parsePipe());
|
|
620
|
+
} while (this.optionalCharacter(chars.$COMMA));
|
|
621
|
+
this.rbracesExpected--;
|
|
622
|
+
this.expectCharacter(chars.$RBRACE);
|
|
623
|
+
}
|
|
624
|
+
return new LiteralMap(this.span(start), keys, values);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
|
|
628
|
+
const start = receiver.span.start;
|
|
629
|
+
const id = this.expectIdentifierOrKeyword();
|
|
630
|
+
|
|
631
|
+
if (this.optionalCharacter(chars.$LPAREN)) {
|
|
632
|
+
this.rparensExpected++;
|
|
633
|
+
const args = this.parseCallArguments();
|
|
634
|
+
this.expectCharacter(chars.$RPAREN);
|
|
635
|
+
this.rparensExpected--;
|
|
636
|
+
const span = this.span(start);
|
|
637
|
+
return isSafe ? new SafeMethodCall(span, receiver, id, args) :
|
|
638
|
+
new MethodCall(span, receiver, id, args);
|
|
639
|
+
|
|
640
|
+
} else {
|
|
641
|
+
if (isSafe) {
|
|
642
|
+
if (this.optionalOperator('=')) {
|
|
643
|
+
this.error('The \'?.\' operator cannot be used in the assignment');
|
|
644
|
+
return new EmptyExpr(this.span(start));
|
|
645
|
+
} else {
|
|
646
|
+
return new SafePropertyRead(this.span(start), receiver, id);
|
|
647
|
+
}
|
|
648
|
+
} else {
|
|
649
|
+
if (this.optionalOperator('=')) {
|
|
650
|
+
if (!this.parseAction) {
|
|
651
|
+
this.error('Bindings cannot contain assignments');
|
|
652
|
+
return new EmptyExpr(this.span(start));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const value = this.parseConditional();
|
|
656
|
+
return new PropertyWrite(this.span(start), receiver, id, value);
|
|
657
|
+
} else {
|
|
658
|
+
return new PropertyRead(this.span(start), receiver, id);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
parseCallArguments(): BindingPipe[] {
|
|
665
|
+
if (this.next.isCharacter(chars.$RPAREN)) return [];
|
|
666
|
+
const positionals: AST[] = [];
|
|
667
|
+
do {
|
|
668
|
+
positionals.push(this.parsePipe());
|
|
669
|
+
} while (this.optionalCharacter(chars.$COMMA));
|
|
670
|
+
return positionals as BindingPipe[];
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* An identifier, a keyword, a string with an optional `-` inbetween.
|
|
675
|
+
*/
|
|
676
|
+
expectTemplateBindingKey(): string {
|
|
677
|
+
let result = '';
|
|
678
|
+
let operatorFound = false;
|
|
679
|
+
do {
|
|
680
|
+
result += this.expectIdentifierOrKeywordOrString();
|
|
681
|
+
operatorFound = this.optionalOperator('-');
|
|
682
|
+
if (operatorFound) {
|
|
683
|
+
result += '-';
|
|
684
|
+
}
|
|
685
|
+
} while (operatorFound);
|
|
686
|
+
|
|
687
|
+
return result.toString();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
parseTemplateBindings(): TemplateBindingParseResult {
|
|
691
|
+
const bindings: TemplateBinding[] = [];
|
|
692
|
+
let prefix!: string;
|
|
693
|
+
const warnings: string[] = [];
|
|
694
|
+
while (this.index < this.tokens.length) {
|
|
695
|
+
const start = this.inputIndex;
|
|
696
|
+
const keyIsVar: boolean = this.peekKeywordLet();
|
|
697
|
+
if (keyIsVar) {
|
|
698
|
+
this.advance();
|
|
699
|
+
}
|
|
700
|
+
let key = this.expectTemplateBindingKey();
|
|
701
|
+
if (!keyIsVar) {
|
|
702
|
+
if (prefix == null) {
|
|
703
|
+
prefix = key;
|
|
704
|
+
} else {
|
|
705
|
+
key = prefix + key[0].toUpperCase() + key.substring(1);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
this.optionalCharacter(chars.$COLON);
|
|
709
|
+
let name: string | null = null;
|
|
710
|
+
let expression: ASTWithSource | null = null;
|
|
711
|
+
if (keyIsVar) {
|
|
712
|
+
if (this.optionalOperator('=')) {
|
|
713
|
+
name = this.expectTemplateBindingKey();
|
|
714
|
+
} else {
|
|
715
|
+
name = '\$implicit';
|
|
716
|
+
}
|
|
717
|
+
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
|
718
|
+
const start = this.inputIndex;
|
|
719
|
+
const ast = this.parsePipe();
|
|
720
|
+
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
|
721
|
+
expression = new ASTWithSource(ast, source, this.location, this.errors);
|
|
722
|
+
}
|
|
723
|
+
if (name === null) {
|
|
724
|
+
throw new Error('Internal error: name is not defined after parsing template bindings');
|
|
725
|
+
}
|
|
726
|
+
if (expression === null) {
|
|
727
|
+
throw new Error('Internal error: expression is not defined after parsing template bindings');
|
|
728
|
+
}
|
|
729
|
+
bindings.push(new TemplateBinding(this.span(start), key, keyIsVar, name, expression));
|
|
730
|
+
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
|
731
|
+
this.optionalCharacter(chars.$COMMA);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
error(message: string, index?: number) {
|
|
738
|
+
this.errors.push(new ParserError(message, this.input, this.locationText(index), this.location));
|
|
739
|
+
this.skip();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private locationText(index?: number | null) {
|
|
743
|
+
if (isBlank(index)) index = this.index;
|
|
744
|
+
return (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
|
|
745
|
+
`at the end of the expression`;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Error recovery should skip tokens until it encounters a recovery point. skip() treats
|
|
749
|
+
// the end of input and a ';' as unconditionally a recovery point. It also treats ')',
|
|
750
|
+
// '}' and ']' as conditional recovery points if one of calling productions is expecting
|
|
751
|
+
// one of these symbols. This allows skip() to recover from errors such as '(a.) + 1' allowing
|
|
752
|
+
// more of the AST to be retained (it doesn't skip any tokens as the ')' is retained because
|
|
753
|
+
// of the '(' begins an '(' <expr> ')' production). The recovery points of grouping symbols
|
|
754
|
+
// must be conditional as they must be skipped if none of the calling productions are not
|
|
755
|
+
// expecting the closing token else we will never make progress in the case of an
|
|
756
|
+
// extraneous group closing symbol (such as a stray ')'). This is not the case for ';' because
|
|
757
|
+
// parseChain() is always the root production and it expects a ';'.
|
|
758
|
+
|
|
759
|
+
// If a production expects one of these token it increments the corresponding nesting count,
|
|
760
|
+
// and then decrements it just prior to checking if the token is in the input.
|
|
761
|
+
private skip() {
|
|
762
|
+
let n = this.next;
|
|
763
|
+
while (this.index < this.tokens.length && !n.isCharacter(chars.$SEMICOLON) &&
|
|
764
|
+
(this.rparensExpected <= 0 || !n.isCharacter(chars.$RPAREN)) &&
|
|
765
|
+
(this.rbracesExpected <= 0 || !n.isCharacter(chars.$RBRACE)) &&
|
|
766
|
+
(this.rbracketsExpected <= 0 || !n.isCharacter(chars.$RBRACKET))) {
|
|
767
|
+
if (this.next.isError()) {
|
|
768
|
+
this.errors.push(
|
|
769
|
+
new ParserError(this.next.toString()!, this.input, this.locationText(), this.location));
|
|
770
|
+
}
|
|
771
|
+
this.advance();
|
|
772
|
+
n = this.next;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
class SimpleExpressionChecker implements AstVisitor {
|
|
778
|
+
static check(ast: AST): string[] {
|
|
779
|
+
const s = new SimpleExpressionChecker();
|
|
780
|
+
ast.visit(s);
|
|
781
|
+
return s.errors;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
errors: string[] = [];
|
|
785
|
+
|
|
786
|
+
visitImplicitReceiver(ast: ImplicitReceiver, context: any) {}
|
|
787
|
+
|
|
788
|
+
visitInterpolation(ast: Interpolation, context: any) {}
|
|
789
|
+
|
|
790
|
+
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {}
|
|
791
|
+
|
|
792
|
+
visitPropertyRead(ast: PropertyRead, context: any) {}
|
|
793
|
+
|
|
794
|
+
visitPropertyWrite(ast: PropertyWrite, context: any) {}
|
|
795
|
+
|
|
796
|
+
visitSafePropertyRead(ast: SafePropertyRead, context: any) {}
|
|
797
|
+
|
|
798
|
+
visitMethodCall(ast: MethodCall, context: any) {}
|
|
799
|
+
|
|
800
|
+
visitSafeMethodCall(ast: SafeMethodCall, context: any) {}
|
|
801
|
+
|
|
802
|
+
visitFunctionCall(ast: FunctionCall, context: any) {}
|
|
803
|
+
|
|
804
|
+
visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); }
|
|
805
|
+
|
|
806
|
+
visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); }
|
|
807
|
+
|
|
808
|
+
visitBinary(ast: Binary, context: any) {}
|
|
809
|
+
|
|
810
|
+
visitPrefixNot(ast: PrefixNot, context: any) {}
|
|
811
|
+
|
|
812
|
+
visitConditional(ast: Conditional, context: any) {}
|
|
813
|
+
|
|
814
|
+
visitPipe(ast: BindingPipe, context: any) { this.errors.push('pipes'); }
|
|
815
|
+
|
|
816
|
+
visitKeyedRead(ast: KeyedRead, context: any) {}
|
|
817
|
+
|
|
818
|
+
visitKeyedWrite(ast: KeyedWrite, context: any) {}
|
|
819
|
+
|
|
820
|
+
visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); }
|
|
821
|
+
|
|
822
|
+
visitChain(ast: Chain, context: any) {}
|
|
823
|
+
|
|
824
|
+
visitQuote(ast: Quote, context: any) {}
|
|
825
|
+
}
|