@angular/compiler
Version:
Angular - the compiler library
1,025 lines (1,024 loc) • 190 kB
JavaScript
/**
* @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
*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define("@angular/compiler/src/expression_parser/parser", ["require", "exports", "tslib", "@angular/compiler/src/chars", "@angular/compiler/src/ml_parser/interpolation_config", "@angular/compiler/src/expression_parser/ast", "@angular/compiler/src/expression_parser/lexer"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._ParseAST = exports.IvyParser = exports.Parser = exports.TemplateBindingParseResult = exports.SplitInterpolation = void 0;
var tslib_1 = require("tslib");
var chars = require("@angular/compiler/src/chars");
var interpolation_config_1 = require("@angular/compiler/src/ml_parser/interpolation_config");
var ast_1 = require("@angular/compiler/src/expression_parser/ast");
var lexer_1 = require("@angular/compiler/src/expression_parser/lexer");
var SplitInterpolation = /** @class */ (function () {
function SplitInterpolation(strings, expressions, offsets) {
this.strings = strings;
this.expressions = expressions;
this.offsets = offsets;
}
return SplitInterpolation;
}());
exports.SplitInterpolation = SplitInterpolation;
var TemplateBindingParseResult = /** @class */ (function () {
function TemplateBindingParseResult(templateBindings, warnings, errors) {
this.templateBindings = templateBindings;
this.warnings = warnings;
this.errors = errors;
}
return TemplateBindingParseResult;
}());
exports.TemplateBindingParseResult = TemplateBindingParseResult;
var Parser = /** @class */ (function () {
function Parser(_lexer) {
this._lexer = _lexer;
this.errors = [];
this.simpleExpressionChecker = SimpleExpressionChecker;
}
Parser.prototype.parseAction = function (input, location, absoluteOffset, interpolationConfig) {
if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; }
this._checkNoInterpolation(input, location, interpolationConfig);
var sourceToLex = this._stripComments(input);
var tokens = this._lexer.tokenize(this._stripComments(input));
var ast = new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, true, this.errors, input.length - sourceToLex.length)
.parseChain();
return new ast_1.ASTWithSource(ast, input, location, absoluteOffset, this.errors);
};
Parser.prototype.parseBinding = function (input, location, absoluteOffset, interpolationConfig) {
if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; }
var ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
return new ast_1.ASTWithSource(ast, input, location, absoluteOffset, this.errors);
};
Parser.prototype.checkSimpleExpression = function (ast) {
var checker = new this.simpleExpressionChecker();
ast.visit(checker);
return checker.errors;
};
Parser.prototype.parseSimpleBinding = function (input, location, absoluteOffset, interpolationConfig) {
if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; }
var ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
var errors = this.checkSimpleExpression(ast);
if (errors.length > 0) {
this._reportError("Host binding expression cannot contain " + errors.join(' '), input, location);
}
return new ast_1.ASTWithSource(ast, input, location, absoluteOffset, this.errors);
};
Parser.prototype._reportError = function (message, input, errLocation, ctxLocation) {
this.errors.push(new ast_1.ParserError(message, input, errLocation, ctxLocation));
};
Parser.prototype._parseBindingAst = function (input, location, absoluteOffset, interpolationConfig) {
// Quotes expressions use 3rd-party expression language. We don't want to use
// our lexer or parser for that, so we check for that ahead of time.
var quote = this._parseQuote(input, location, absoluteOffset);
if (quote != null) {
return quote;
}
this._checkNoInterpolation(input, location, interpolationConfig);
var sourceToLex = this._stripComments(input);
var tokens = this._lexer.tokenize(sourceToLex);
return new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, input.length - sourceToLex.length)
.parseChain();
};
Parser.prototype._parseQuote = function (input, location, absoluteOffset) {
if (input == null)
return null;
var prefixSeparatorIndex = input.indexOf(':');
if (prefixSeparatorIndex == -1)
return null;
var prefix = input.substring(0, prefixSeparatorIndex).trim();
if (!lexer_1.isIdentifier(prefix))
return null;
var uninterpretedExpression = input.substring(prefixSeparatorIndex + 1);
var span = new ast_1.ParseSpan(0, input.length);
return new ast_1.Quote(span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location);
};
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
* <div *ngFor="let item of items">
* ^ ^ absoluteValueOffset for `templateValue`
* absoluteKeyOffset for `templateKey`
* ```
* contains three bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
*
* This is apparent from the de-sugared template:
* ```
* <ng-template ngFor let-item [ngForOf]="items">
* ```
*
* @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
* @param templateValue RHS of the microsyntax attribute
* @param templateUrl template filename if it's external, component filename if it's inline
* @param absoluteKeyOffset start of the `templateKey`
* @param absoluteValueOffset start of the `templateValue`
*/
Parser.prototype.parseTemplateBindings = function (templateKey, templateValue, templateUrl, absoluteKeyOffset, absoluteValueOffset) {
var tokens = this._lexer.tokenize(templateValue);
var parser = new _ParseAST(templateValue, templateUrl, absoluteValueOffset, tokens, templateValue.length, false /* parseAction */, this.errors, 0 /* relative offset */);
return parser.parseTemplateBindings({
source: templateKey,
span: new ast_1.AbsoluteSourceSpan(absoluteKeyOffset, absoluteKeyOffset + templateKey.length),
});
};
Parser.prototype.parseInterpolation = function (input, location, absoluteOffset, interpolationConfig) {
if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; }
var _a = this.splitInterpolation(input, location, interpolationConfig), strings = _a.strings, expressions = _a.expressions, offsets = _a.offsets;
if (expressions.length === 0)
return null;
var expressionNodes = [];
for (var i = 0; i < expressions.length; ++i) {
var expressionText = expressions[i].text;
var sourceToLex = this._stripComments(expressionText);
var tokens = this._lexer.tokenize(sourceToLex);
var ast = new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, offsets[i] + (expressionText.length - sourceToLex.length))
.parseChain();
expressionNodes.push(ast);
}
return this.createInterpolationAst(strings.map(function (s) { return s.text; }), expressionNodes, input, location, absoluteOffset);
};
/**
* Similar to `parseInterpolation`, but treats the provided string as a single expression
* element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
* This is used for parsing the switch expression in ICUs.
*/
Parser.prototype.parseInterpolationExpression = function (expression, location, absoluteOffset) {
var sourceToLex = this._stripComments(expression);
var tokens = this._lexer.tokenize(sourceToLex);
var ast = new _ParseAST(expression, location, absoluteOffset, tokens, sourceToLex.length,
/* parseAction */ false, this.errors, 0)
.parseChain();
var strings = ['', '']; // The prefix and suffix strings are both empty
return this.createInterpolationAst(strings, [ast], expression, location, absoluteOffset);
};
Parser.prototype.createInterpolationAst = function (strings, expressions, input, location, absoluteOffset) {
var span = new ast_1.ParseSpan(0, input.length);
var interpolation = new ast_1.Interpolation(span, span.toAbsolute(absoluteOffset), strings, expressions);
return new ast_1.ASTWithSource(interpolation, input, location, absoluteOffset, this.errors);
};
/**
* Splits a string of text into "raw" text segments and expressions present in interpolations in
* the string.
* Returns `null` if there are no interpolations, otherwise a
* `SplitInterpolation` with splits that look like
* <raw text> <expression> <raw text> ... <raw text> <expression> <raw text>
*/
Parser.prototype.splitInterpolation = function (input, location, interpolationConfig) {
if (interpolationConfig === void 0) { interpolationConfig = interpolation_config_1.DEFAULT_INTERPOLATION_CONFIG; }
var strings = [];
var expressions = [];
var offsets = [];
var i = 0;
var atInterpolation = false;
var extendLastString = false;
var interpStart = interpolationConfig.start, interpEnd = interpolationConfig.end;
while (i < input.length) {
if (!atInterpolation) {
// parse until starting {{
var start = i;
i = input.indexOf(interpStart, i);
if (i === -1) {
i = input.length;
}
var text = input.substring(start, i);
strings.push({ text: text, start: start, end: i });
atInterpolation = true;
}
else {
// parse from starting {{ to ending }} while ignoring content inside quotes.
var fullStart = i;
var exprStart = fullStart + interpStart.length;
var exprEnd = this._getInterpolationEndIndex(input, interpEnd, exprStart);
if (exprEnd === -1) {
// Could not find the end of the interpolation; do not parse an expression.
// Instead we should extend the content on the last raw string.
atInterpolation = false;
extendLastString = true;
break;
}
var fullEnd = exprEnd + interpEnd.length;
var text = input.substring(exprStart, exprEnd);
if (text.trim().length === 0) {
this._reportError('Blank expressions are not allowed in interpolated strings', input, "at column " + i + " in", location);
}
expressions.push({ text: text, start: fullStart, end: fullEnd });
offsets.push(exprStart);
i = fullEnd;
atInterpolation = false;
}
}
if (!atInterpolation) {
// If we are now at a text section, add the remaining content as a raw string.
if (extendLastString) {
var piece = strings[strings.length - 1];
piece.text += input.substring(i);
piece.end = input.length;
}
else {
strings.push({ text: input.substring(i), start: i, end: input.length });
}
}
return new SplitInterpolation(strings, expressions, offsets);
};
Parser.prototype.wrapLiteralPrimitive = function (input, location, absoluteOffset) {
var span = new ast_1.ParseSpan(0, input == null ? 0 : input.length);
return new ast_1.ASTWithSource(new ast_1.LiteralPrimitive(span, span.toAbsolute(absoluteOffset), input), input, location, absoluteOffset, this.errors);
};
Parser.prototype._stripComments = function (input) {
var i = this._commentStart(input);
return i != null ? input.substring(0, i).trim() : input;
};
Parser.prototype._commentStart = function (input) {
var outerQuote = null;
for (var i = 0; i < input.length - 1; i++) {
var char = input.charCodeAt(i);
var nextChar = input.charCodeAt(i + 1);
if (char === chars.$SLASH && nextChar == chars.$SLASH && outerQuote == null)
return i;
if (outerQuote === char) {
outerQuote = null;
}
else if (outerQuote == null && lexer_1.isQuote(char)) {
outerQuote = char;
}
}
return null;
};
Parser.prototype._checkNoInterpolation = function (input, location, _a) {
var e_1, _b;
var start = _a.start, end = _a.end;
var startIndex = -1;
var endIndex = -1;
try {
for (var _c = tslib_1.__values(this._forEachUnquotedChar(input, 0)), _d = _c.next(); !_d.done; _d = _c.next()) {
var charIndex = _d.value;
if (startIndex === -1) {
if (input.startsWith(start)) {
startIndex = charIndex;
}
}
else {
endIndex = this._getInterpolationEndIndex(input, end, charIndex);
if (endIndex > -1) {
break;
}
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
}
finally { if (e_1) throw e_1.error; }
}
if (startIndex > -1 && endIndex > -1) {
this._reportError("Got interpolation (" + start + end + ") where expression was expected", input, "at column " + startIndex + " in", location);
}
};
/**
* Finds the index of the end of an interpolation expression
* while ignoring comments and quoted content.
*/
Parser.prototype._getInterpolationEndIndex = function (input, expressionEnd, start) {
var e_2, _a;
try {
for (var _b = tslib_1.__values(this._forEachUnquotedChar(input, start)), _c = _b.next(); !_c.done; _c = _b.next()) {
var charIndex = _c.value;
if (input.startsWith(expressionEnd, charIndex)) {
return charIndex;
}
// Nothing else in the expression matters after we've
// hit a comment so look directly for the end token.
if (input.startsWith('//', charIndex)) {
return input.indexOf(expressionEnd, charIndex);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_2) throw e_2.error; }
}
return -1;
};
/**
* Generator used to iterate over the character indexes of a string that are outside of quotes.
* @param input String to loop through.
* @param start Index within the string at which to start.
*/
Parser.prototype._forEachUnquotedChar = function (input, start) {
var currentQuote, escapeCount, i, char;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
currentQuote = null;
escapeCount = 0;
i = start;
_a.label = 1;
case 1:
if (!(i < input.length)) return [3 /*break*/, 6];
char = input[i];
if (!(lexer_1.isQuote(input.charCodeAt(i)) && (currentQuote === null || currentQuote === char) &&
escapeCount % 2 === 0)) return [3 /*break*/, 2];
currentQuote = currentQuote === null ? char : null;
return [3 /*break*/, 4];
case 2:
if (!(currentQuote === null)) return [3 /*break*/, 4];
return [4 /*yield*/, i];
case 3:
_a.sent();
_a.label = 4;
case 4:
escapeCount = char === '\\' ? escapeCount + 1 : 0;
_a.label = 5;
case 5:
i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/];
}
});
};
return Parser;
}());
exports.Parser = Parser;
var IvyParser = /** @class */ (function (_super) {
tslib_1.__extends(IvyParser, _super);
function IvyParser() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.simpleExpressionChecker = IvySimpleExpressionChecker;
return _this;
}
return IvyParser;
}(Parser));
exports.IvyParser = IvyParser;
/** Describes a stateful context an expression parser is in. */
var ParseContextFlags;
(function (ParseContextFlags) {
ParseContextFlags[ParseContextFlags["None"] = 0] = "None";
/**
* A Writable context is one in which a value may be written to an lvalue.
* For example, after we see a property access, we may expect a write to the
* property via the "=" operator.
* prop
* ^ possible "=" after
*/
ParseContextFlags[ParseContextFlags["Writable"] = 1] = "Writable";
})(ParseContextFlags || (ParseContextFlags = {}));
var _ParseAST = /** @class */ (function () {
function _ParseAST(input, location, absoluteOffset, tokens, inputLength, parseAction, errors, offset) {
this.input = input;
this.location = location;
this.absoluteOffset = absoluteOffset;
this.tokens = tokens;
this.inputLength = inputLength;
this.parseAction = parseAction;
this.errors = errors;
this.offset = offset;
this.rparensExpected = 0;
this.rbracketsExpected = 0;
this.rbracesExpected = 0;
this.context = ParseContextFlags.None;
// Cache of expression start and input indeces to the absolute source span they map to, used to
// prevent creating superfluous source spans in `sourceSpan`.
// A serial of the expression start and input index is used for mapping because both are stateful
// and may change for subsequent expressions visited by the parser.
this.sourceSpanCache = new Map();
this.index = 0;
}
_ParseAST.prototype.peek = function (offset) {
var i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : lexer_1.EOF;
};
Object.defineProperty(_ParseAST.prototype, "next", {
get: function () {
return this.peek(0);
},
enumerable: false,
configurable: true
});
Object.defineProperty(_ParseAST.prototype, "atEOF", {
/** Whether all the parser input has been processed. */
get: function () {
return this.index >= this.tokens.length;
},
enumerable: false,
configurable: true
});
Object.defineProperty(_ParseAST.prototype, "inputIndex", {
/**
* Index of the next token to be processed, or the end of the last token if all have been
* processed.
*/
get: function () {
return this.atEOF ? this.currentEndIndex : this.next.index + this.offset;
},
enumerable: false,
configurable: true
});
Object.defineProperty(_ParseAST.prototype, "currentEndIndex", {
/**
* End index of the last processed token, or the start of the first token if none have been
* processed.
*/
get: function () {
if (this.index > 0) {
var curToken = this.peek(-1);
return curToken.end + this.offset;
}
// No tokens have been processed yet; return the next token's start or the length of the input
// if there is no token.
if (this.tokens.length === 0) {
return this.inputLength + this.offset;
}
return this.next.index + this.offset;
},
enumerable: false,
configurable: true
});
Object.defineProperty(_ParseAST.prototype, "currentAbsoluteOffset", {
/**
* Returns the absolute offset of the start of the current token.
*/
get: function () {
return this.absoluteOffset + this.inputIndex;
},
enumerable: false,
configurable: true
});
/**
* Retrieve a `ParseSpan` from `start` to the current position (or to `artificialEndIndex` if
* provided).
*
* @param start Position from which the `ParseSpan` will start.
* @param artificialEndIndex Optional ending index to be used if provided (and if greater than the
* natural ending index)
*/
_ParseAST.prototype.span = function (start, artificialEndIndex) {
var endIndex = this.currentEndIndex;
if (artificialEndIndex !== undefined && artificialEndIndex > this.currentEndIndex) {
endIndex = artificialEndIndex;
}
// In some unusual parsing scenarios (like when certain tokens are missing and an `EmptyExpr` is
// being created), the current token may already be advanced beyond the `currentEndIndex`. This
// appears to be a deep-seated parser bug.
//
// As a workaround for now, swap the start and end indices to ensure a valid `ParseSpan`.
// TODO(alxhub): fix the bug upstream in the parser state, and remove this workaround.
if (start > endIndex) {
var tmp = endIndex;
endIndex = start;
start = tmp;
}
return new ast_1.ParseSpan(start, endIndex);
};
_ParseAST.prototype.sourceSpan = function (start, artificialEndIndex) {
var serial = start + "@" + this.inputIndex + ":" + artificialEndIndex;
if (!this.sourceSpanCache.has(serial)) {
this.sourceSpanCache.set(serial, this.span(start, artificialEndIndex).toAbsolute(this.absoluteOffset));
}
return this.sourceSpanCache.get(serial);
};
_ParseAST.prototype.advance = function () {
this.index++;
};
/**
* Executes a callback in the provided context.
*/
_ParseAST.prototype.withContext = function (context, cb) {
this.context |= context;
var ret = cb();
this.context ^= context;
return ret;
};
_ParseAST.prototype.consumeOptionalCharacter = function (code) {
if (this.next.isCharacter(code)) {
this.advance();
return true;
}
else {
return false;
}
};
_ParseAST.prototype.peekKeywordLet = function () {
return this.next.isKeywordLet();
};
_ParseAST.prototype.peekKeywordAs = function () {
return this.next.isKeywordAs();
};
/**
* Consumes an expected character, otherwise emits an error about the missing expected character
* and skips over the token stream until reaching a recoverable point.
*
* See `this.error` and `this.skip` for more details.
*/
_ParseAST.prototype.expectCharacter = function (code) {
if (this.consumeOptionalCharacter(code))
return;
this.error("Missing expected " + String.fromCharCode(code));
};
_ParseAST.prototype.consumeOptionalOperator = function (op) {
if (this.next.isOperator(op)) {
this.advance();
return true;
}
else {
return false;
}
};
_ParseAST.prototype.expectOperator = function (operator) {
if (this.consumeOptionalOperator(operator))
return;
this.error("Missing expected operator " + operator);
};
_ParseAST.prototype.prettyPrintToken = function (tok) {
return tok === lexer_1.EOF ? 'end of input' : "token " + tok;
};
_ParseAST.prototype.expectIdentifierOrKeyword = function () {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) {
if (n.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(n, 'expected identifier or keyword');
}
else {
this.error("Unexpected " + this.prettyPrintToken(n) + ", expected identifier or keyword");
}
return null;
}
this.advance();
return n.toString();
};
_ParseAST.prototype.expectIdentifierOrKeywordOrString = function () {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
if (n.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(n, 'expected identifier, keyword or string');
}
else {
this.error("Unexpected " + this.prettyPrintToken(n) + ", expected identifier, keyword, or string");
}
return '';
}
this.advance();
return n.toString();
};
_ParseAST.prototype.parseChain = function () {
var exprs = [];
var start = this.inputIndex;
while (this.index < this.tokens.length) {
var expr = this.parsePipe();
exprs.push(expr);
if (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
if (!this.parseAction) {
this.error('Binding expression cannot contain chained expression');
}
while (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
} // read all semicolons
}
else if (this.index < this.tokens.length) {
this.error("Unexpected token '" + this.next + "'");
}
}
if (exprs.length == 0) {
// We have no expressions so create an empty expression that spans the entire input length
var artificialStart = this.offset;
var artificialEnd = this.offset + this.inputLength;
return new ast_1.EmptyExpr(this.span(artificialStart, artificialEnd), this.sourceSpan(artificialStart, artificialEnd));
}
if (exprs.length == 1)
return exprs[0];
return new ast_1.Chain(this.span(start), this.sourceSpan(start), exprs);
};
_ParseAST.prototype.parsePipe = function () {
var start = this.inputIndex;
var result = this.parseExpression();
if (this.consumeOptionalOperator('|')) {
if (this.parseAction) {
this.error('Cannot have a pipe in an action expression');
}
do {
var nameStart = this.inputIndex;
var nameId = this.expectIdentifierOrKeyword();
var nameSpan = void 0;
var fullSpanEnd = undefined;
if (nameId !== null) {
nameSpan = this.sourceSpan(nameStart);
}
else {
// No valid identifier was found, so we'll assume an empty pipe name ('').
nameId = '';
// However, there may have been whitespace present between the pipe character and the next
// token in the sequence (or the end of input). We want to track this whitespace so that
// the `BindingPipe` we produce covers not just the pipe character, but any trailing
// whitespace beyond it. Another way of thinking about this is that the zero-length name
// is assumed to be at the end of any whitespace beyond the pipe character.
//
// Therefore, we push the end of the `ParseSpan` for this pipe all the way up to the
// beginning of the next token, or until the end of input if the next token is EOF.
fullSpanEnd = this.next.index !== -1 ? this.next.index : this.inputLength + this.offset;
// The `nameSpan` for an empty pipe name is zero-length at the end of any whitespace
// beyond the pipe character.
nameSpan = new ast_1.ParseSpan(fullSpanEnd, fullSpanEnd).toAbsolute(this.absoluteOffset);
}
var args = [];
while (this.consumeOptionalCharacter(chars.$COLON)) {
args.push(this.parseExpression());
// If there are additional expressions beyond the name, then the artificial end for the
// name is no longer relevant.
}
result = new ast_1.BindingPipe(this.span(start), this.sourceSpan(start, fullSpanEnd), result, nameId, args, nameSpan);
} while (this.consumeOptionalOperator('|'));
}
return result;
};
_ParseAST.prototype.parseExpression = function () {
return this.parseConditional();
};
_ParseAST.prototype.parseConditional = function () {
var start = this.inputIndex;
var result = this.parseLogicalOr();
if (this.consumeOptionalOperator('?')) {
var yes = this.parsePipe();
var no = void 0;
if (!this.consumeOptionalCharacter(chars.$COLON)) {
var end = this.inputIndex;
var expression = this.input.substring(start, end);
this.error("Conditional expression " + expression + " requires all 3 expressions");
no = new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start));
}
else {
no = this.parsePipe();
}
return new ast_1.Conditional(this.span(start), this.sourceSpan(start), result, yes, no);
}
else {
return result;
}
};
_ParseAST.prototype.parseLogicalOr = function () {
// '||'
var start = this.inputIndex;
var result = this.parseLogicalAnd();
while (this.consumeOptionalOperator('||')) {
var right = this.parseLogicalAnd();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), '||', result, right);
}
return result;
};
_ParseAST.prototype.parseLogicalAnd = function () {
// '&&'
var start = this.inputIndex;
var result = this.parseNullishCoalescing();
while (this.consumeOptionalOperator('&&')) {
var right = this.parseNullishCoalescing();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
}
return result;
};
_ParseAST.prototype.parseNullishCoalescing = function () {
// '??'
var start = this.inputIndex;
var result = this.parseEquality();
while (this.consumeOptionalOperator('??')) {
var right = this.parseEquality();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), '??', result, right);
}
return result;
};
_ParseAST.prototype.parseEquality = function () {
// '==','!=','===','!=='
var start = this.inputIndex;
var result = this.parseRelational();
while (this.next.type == lexer_1.TokenType.Operator) {
var operator = this.next.strValue;
switch (operator) {
case '==':
case '===':
case '!=':
case '!==':
this.advance();
var right = this.parseRelational();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
};
_ParseAST.prototype.parseRelational = function () {
// '<', '>', '<=', '>='
var start = this.inputIndex;
var result = this.parseAdditive();
while (this.next.type == lexer_1.TokenType.Operator) {
var operator = this.next.strValue;
switch (operator) {
case '<':
case '>':
case '<=':
case '>=':
this.advance();
var right = this.parseAdditive();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
};
_ParseAST.prototype.parseAdditive = function () {
// '+', '-'
var start = this.inputIndex;
var result = this.parseMultiplicative();
while (this.next.type == lexer_1.TokenType.Operator) {
var operator = this.next.strValue;
switch (operator) {
case '+':
case '-':
this.advance();
var right = this.parseMultiplicative();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
};
_ParseAST.prototype.parseMultiplicative = function () {
// '*', '%', '/'
var start = this.inputIndex;
var result = this.parsePrefix();
while (this.next.type == lexer_1.TokenType.Operator) {
var operator = this.next.strValue;
switch (operator) {
case '*':
case '%':
case '/':
this.advance();
var right = this.parsePrefix();
result = new ast_1.Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
};
_ParseAST.prototype.parsePrefix = function () {
if (this.next.type == lexer_1.TokenType.Operator) {
var start = this.inputIndex;
var operator = this.next.strValue;
var result = void 0;
switch (operator) {
case '+':
this.advance();
result = this.parsePrefix();
return ast_1.Unary.createPlus(this.span(start), this.sourceSpan(start), result);
case '-':
this.advance();
result = this.parsePrefix();
return ast_1.Unary.createMinus(this.span(start), this.sourceSpan(start), result);
case '!':
this.advance();
result = this.parsePrefix();
return new ast_1.PrefixNot(this.span(start), this.sourceSpan(start), result);
}
}
return this.parseCallChain();
};
_ParseAST.prototype.parseCallChain = function () {
var start = this.inputIndex;
var result = this.parsePrimary();
while (true) {
if (this.consumeOptionalCharacter(chars.$PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, start, false);
}
else if (this.consumeOptionalOperator('?.')) {
result = this.consumeOptionalCharacter(chars.$LBRACKET) ?
this.parseKeyedReadOrWrite(result, start, true) :
this.parseAccessMemberOrMethodCall(result, start, true);
}
else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
result = this.parseKeyedReadOrWrite(result, start, false);
}
else if (this.consumeOptionalCharacter(chars.$LPAREN)) {
this.rparensExpected++;
var args = this.parseCallArguments();
this.rparensExpected--;
this.expectCharacter(chars.$RPAREN);
result = new ast_1.FunctionCall(this.span(start), this.sourceSpan(start), result, args);
}
else if (this.consumeOptionalOperator('!')) {
result = new ast_1.NonNullAssert(this.span(start), this.sourceSpan(start), result);
}
else {
return result;
}
}
};
_ParseAST.prototype.parsePrimary = function () {
var start = this.inputIndex;
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
this.rparensExpected++;
var result = this.parsePipe();
this.rparensExpected--;
this.expectCharacter(chars.$RPAREN);
return result;
}
else if (this.next.isKeywordNull()) {
this.advance();
return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
}
else if (this.next.isKeywordUndefined()) {
this.advance();
return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0);
}
else if (this.next.isKeywordTrue()) {
this.advance();
return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
}
else if (this.next.isKeywordFalse()) {
this.advance();
return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
}
else if (this.next.isKeywordThis()) {
this.advance();
return new ast_1.ThisReceiver(this.span(start), this.sourceSpan(start));
}
else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
this.rbracketsExpected++;
var elements = this.parseExpressionList(chars.$RBRACKET);
this.rbracketsExpected--;
this.expectCharacter(chars.$RBRACKET);
return new ast_1.LiteralArray(this.span(start), this.sourceSpan(start), elements);
}
else if (this.next.isCharacter(chars.$LBRACE)) {
return this.parseLiteralMap();
}
else if (this.next.isIdentifier()) {
return this.parseAccessMemberOrMethodCall(new ast_1.ImplicitReceiver(this.span(start), this.sourceSpan(start)), start, false);
}
else if (this.next.isNumber()) {
var value = this.next.toNumber();
this.advance();
return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), value);
}
else if (this.next.isString()) {
var literalValue = this.next.toString();
this.advance();
return new ast_1.LiteralPrimitive(this.span(start), this.sourceSpan(start), literalValue);
}
else if (this.next.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(this.next, null);
return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start));
}
else if (this.index >= this.tokens.length) {
this.error("Unexpected end of expression: " + this.input);
return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start));
}
else {
this.error("Unexpected token " + this.next);
return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start));
}
};
_ParseAST.prototype.parseExpressionList = function (terminator) {
var result = [];
do {
if (!this.next.isCharacter(terminator)) {
result.push(this.parsePipe());
}
else {
break;
}
} while (this.consumeOptionalCharacter(chars.$COMMA));
return result;
};
_ParseAST.prototype.parseLiteralMap = function () {
var keys = [];
var values = [];
var start = this.inputIndex;
this.expectCharacter(chars.$LBRACE);
if (!this.consumeOptionalCharacter(chars.$RBRACE)) {
this.rbracesExpected++;
do {
var keyStart = this.inputIndex;
var quoted = this.next.isString();
var key = this.expectIdentifierOrKeywordOrString();
keys.push({ key: key, quoted: quoted });
// Properties with quoted keys can't use the shorthand syntax.
if (quoted) {
this.expectCharacter(chars.$COLON);
values.push(this.parsePipe());
}
else if (this.consumeOptionalCharacter(chars.$COLON)) {
values.push(this.parsePipe());
}
else {
var span = this.span(keyStart);
var sourceSpan = this.sourceSpan(keyStart);
values.push(new ast_1.PropertyRead(span, sourceSpan, sourceSpan, new ast_1.ImplicitReceiver(span, sourceSpan), key));
}
} while (this.consumeOptionalCharacter(chars.$COMMA));
this.rbracesExpected--;
this.expectCharacter(chars.$RBRACE);
}
return new ast_1.LiteralMap(this.span(start), this.sourceSpan(start), keys, values);
};
_ParseAST.prototype.parseAccessMemberOrMethodCall = function (receiver, start, isSafe) {
var _this = this;
var nameStart = this.inputIndex;
var id = this.withContext(ParseContextFlags.Writable, function () {
var _a;
var id = (_a = _this.expectIdentifierOrKeyword()) !== null && _a !== void 0 ? _a : '';
if (id.length === 0) {
_this.error("Expected identifier for property access", receiver.span.end);
}
return id;
});
var nameSpan = this.sourceSpan(nameStart);
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
var argumentStart = this.inputIndex;
this.rparensExpected++;
var args = this.parseCallArguments();
var argumentSpan = this.span(argumentStart, this.inputIndex).toAbsolute(this.absoluteOffset);
this.expectCharacter(chars.$RPAREN);
this.rparensExpected--;
var span = this.span(start);
var sourceSpan = this.sourceSpan(start);
return isSafe ?
new ast_1.SafeMethodCall(span, sourceSpan, nameSpan, receiver, id, args, argumentSpan) :
new ast_1.MethodCall(span, sourceSpan, nameSpan, receiver, id, args, argumentSpan);
}
else {
if (isSafe) {
if (this.consumeOptionalOperator('=')) {
this.error('The \'?.\' operator cannot be used in the assignment');
return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start));
}
else {
return new ast_1.SafePropertyRead(this.span(start), this.sourceSpan(start), nameSpan, receiver, id);
}
}
else {
if (this.consumeOptionalOperator('=')) {
if (!this.parseAction) {
this.error('Bindings cannot contain assignments');
return new ast_1.EmptyExpr(this.span(start), this.sourceSpan(start));
}
var value = this.parseConditional();
return new ast_1.PropertyWrite(this.span(start), this.sourceSpan(start), nameSpan, receiver, id, value);
}
else {
return new ast_1.PropertyRead(this.span(start), this.sourceSpan(start), nameSpan, receiver, id);
}
}
}
};
_ParseAST.prototype.parseCallArguments = function () {
if (this.next.isCharacter(chars.$RPAREN))
return [];
var positionals = [];
do {
positionals.push(this.parsePipe());
} while (this.consumeOptionalCharacter(chars.$COMMA));
return positionals;
};
/**
* Parses an identifier, a keyword, a string with an optional `-` in between,
* and returns the string along with its absolute source span.
*/
_ParseAST.prototype.expectTemplateBindingKey = function () {
var result = '';
var operatorFound = false;
var start = this.currentAbsoluteOffset;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.consumeOptionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return {
source: result,
span: new ast_1.AbsoluteSourceSpan(start, start + result.length),
};
};
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.