mirror of
https://github.com/bendtherules/completion-viz.git
synced 2026-08-19 00:31:34 +00:00
1236 lines
40 KiB
JavaScript
1236 lines
40 KiB
JavaScript
import { TV, PropName } from '../static-semantics/all.mjs';
|
|
import {
|
|
Token, TokenPrecedence,
|
|
isPropertyOrCall,
|
|
isMember,
|
|
isKeyword,
|
|
isKeywordRaw,
|
|
isReservedWordStrict,
|
|
} from './tokens.mjs';
|
|
import { isLineTerminator } from './Lexer.mjs';
|
|
import { FunctionParser, FunctionKind } from './FunctionParser.mjs';
|
|
import { RegExpParser } from './RegExpParser.mjs';
|
|
|
|
export class ExpressionParser extends FunctionParser {
|
|
// Expression :
|
|
// AssignmentExpression
|
|
// Expression `,` AssignmentExpression
|
|
parseExpression() {
|
|
const node = this.startNode();
|
|
const AssignmentExpression = this.parseAssignmentExpression();
|
|
if (this.eat(Token.COMMA)) {
|
|
node.ExpressionList = [AssignmentExpression];
|
|
do {
|
|
node.ExpressionList.push(this.parseAssignmentExpression());
|
|
} while (this.eat(Token.COMMA));
|
|
return this.finishNode(node, 'CommaOperator');
|
|
}
|
|
return AssignmentExpression;
|
|
}
|
|
|
|
// AssignmentExpression :
|
|
// ConditionalExpression
|
|
// [+Yield] YieldExpression
|
|
// ArrowFunction
|
|
// AsyncArrowFunction
|
|
// LeftHandSideExpression `=` AssignmentExpression
|
|
// LeftHandSideExpression AssignmentOperator AssignmentExpression
|
|
// LeftHandSideExpression LogicalAssignmentOperator AssignmentExpression
|
|
//
|
|
// AssignmentOperator : one of
|
|
// *= /= %= += -= <<= >>= >>>= &= ^= |= **=
|
|
//
|
|
// LogicalAssignmentOperator : one of
|
|
// &&= ||= ??=
|
|
parseAssignmentExpression() {
|
|
if (this.test(Token.YIELD) && this.scope.hasYield()) {
|
|
return this.parseYieldExpression();
|
|
}
|
|
const node = this.startNode();
|
|
|
|
this.scope.pushAssignmentInfo('assign');
|
|
const left = this.parseConditionalExpression();
|
|
const assignmentInfo = this.scope.popAssignmentInfo();
|
|
|
|
if (left.type === 'IdentifierReference') {
|
|
// `async` [no LineTerminator here] IdentifierReference [no LineTerminator here] `=>`
|
|
if (left.name === 'async'
|
|
&& this.test(Token.IDENTIFIER)
|
|
&& !this.peek().hadLineTerminatorBefore
|
|
&& this.testAhead(Token.ARROW)
|
|
&& !this.peekAhead().hadLineTerminatorBefore) {
|
|
assignmentInfo.clear();
|
|
return this.parseArrowFunction(node, {
|
|
Arguments: [this.parseIdentifierReference()],
|
|
}, FunctionKind.ASYNC);
|
|
}
|
|
// IdentifierReference [no LineTerminator here] `=>`
|
|
if (this.test(Token.ARROW) && !this.peek().hadLineTerminatorBefore) {
|
|
assignmentInfo.clear();
|
|
return this.parseArrowFunction(node, { Arguments: [left] }, FunctionKind.NORMAL);
|
|
}
|
|
}
|
|
|
|
// `async` [no LineTerminator here] Arguments [no LineTerminator here] `=>`
|
|
if (left.type === 'CallExpression' && left.arrowInfo && this.test(Token.ARROW)
|
|
&& !this.peek().hadLineTerminatorBefore) {
|
|
const last = left.Arguments[left.Arguments.length - 1];
|
|
if (!left.arrowInfo.trailingComma || (last && last.type !== 'AssignmentRestElement')) {
|
|
assignmentInfo.clear();
|
|
return this.parseArrowFunction(node, left, FunctionKind.ASYNC);
|
|
}
|
|
}
|
|
|
|
if (left.type === 'CoverParenthesizedExpressionAndArrowParameterList') {
|
|
assignmentInfo.clear();
|
|
return this.parseArrowFunction(node, left, FunctionKind.NORMAL);
|
|
}
|
|
|
|
switch (this.peek().type) {
|
|
case Token.ASSIGN:
|
|
case Token.ASSIGN_MUL:
|
|
case Token.ASSIGN_DIV:
|
|
case Token.ASSIGN_MOD:
|
|
case Token.ASSIGN_ADD:
|
|
case Token.ASSIGN_SUB:
|
|
case Token.ASSIGN_SHL:
|
|
case Token.ASSIGN_SAR:
|
|
case Token.ASSIGN_SHR:
|
|
case Token.ASSIGN_BIT_AND:
|
|
case Token.ASSIGN_BIT_XOR:
|
|
case Token.ASSIGN_BIT_OR:
|
|
case Token.ASSIGN_EXP:
|
|
case Token.ASSIGN_AND:
|
|
case Token.ASSIGN_OR:
|
|
case Token.ASSIGN_NULLISH:
|
|
assignmentInfo.clear();
|
|
this.validateAssignmentTarget(left);
|
|
node.LeftHandSideExpression = left;
|
|
node.AssignmentOperator = this.next().value;
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
return this.finishNode(node, 'AssignmentExpression');
|
|
default:
|
|
return left;
|
|
}
|
|
}
|
|
|
|
validateAssignmentTarget(node) {
|
|
switch (node.type) {
|
|
case 'IdentifierReference':
|
|
if (this.isStrictMode() && (node.name === 'eval' || node.name === 'arguments')) {
|
|
break;
|
|
}
|
|
return;
|
|
case 'CoverInitializedName':
|
|
this.validateAssignmentTarget(node.IdentifierReference);
|
|
return;
|
|
case 'MemberExpression':
|
|
return;
|
|
case 'SuperProperty':
|
|
return;
|
|
case 'ParenthesizedExpression':
|
|
this.validateAssignmentTarget(node.Expression);
|
|
return;
|
|
case 'ArrayLiteral':
|
|
node.ElementList.forEach((p, i) => {
|
|
if (p.type === 'SpreadElement' && (i !== node.ElementList.length - 1 || node.hasTrailingComma)) {
|
|
this.raiseEarly('InvalidAssignmentTarget', p);
|
|
}
|
|
this.validateAssignmentTarget(p);
|
|
});
|
|
return;
|
|
case 'ObjectLiteral':
|
|
node.PropertyDefinitionList.forEach((p, i) => {
|
|
if (p.type === 'PropertyDefinition' && !p.PropertyName
|
|
&& i !== node.PropertyDefinitionList.length - 1) {
|
|
this.raiseEarly('InvalidAssignmentTarget', p);
|
|
}
|
|
this.validateAssignmentTarget(p);
|
|
});
|
|
return;
|
|
case 'PropertyDefinition':
|
|
this.validateAssignmentTarget(node.AssignmentExpression);
|
|
return;
|
|
case 'AssignmentExpression':
|
|
this.validateAssignmentTarget(node.LeftHandSideExpression);
|
|
return;
|
|
case 'Elision':
|
|
return;
|
|
case 'SpreadElement':
|
|
if (node.AssignmentExpression.type === 'AssignmentExpression') {
|
|
break;
|
|
}
|
|
this.validateAssignmentTarget(node.AssignmentExpression);
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
this.raiseEarly('InvalidAssignmentTarget', node);
|
|
}
|
|
|
|
// YieldExpression :
|
|
// `yield`
|
|
// `yield` [no LineTerminator here] AssignmentExpression
|
|
// `yield` [no LineTerminator here] `*` AssignmentExpression
|
|
parseYieldExpression() {
|
|
if (this.scope.inParameters()) {
|
|
this.raiseEarly('YieldInFormalParameters');
|
|
}
|
|
const node = this.startNode();
|
|
this.expect(Token.YIELD);
|
|
if (this.peek().hadLineTerminatorBefore) {
|
|
node.hasStar = false;
|
|
node.AssignmentExpression = null;
|
|
} else {
|
|
node.hasStar = this.eat(Token.MUL);
|
|
if (node.hasStar) {
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
} else {
|
|
switch (this.peek().type) {
|
|
case Token.EOS:
|
|
case Token.SEMICOLON:
|
|
case Token.RBRACE:
|
|
case Token.RBRACK:
|
|
case Token.RPAREN:
|
|
case Token.COLON:
|
|
case Token.COMMA:
|
|
case Token.IN:
|
|
node.AssignmentExpression = null;
|
|
break;
|
|
default:
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
}
|
|
}
|
|
}
|
|
if (this.scope.arrowInfoStack.length > 0) {
|
|
this.scope.arrowInfoStack[this.scope.arrowInfoStack.length - 1].yieldExpressions.push(node);
|
|
}
|
|
return this.finishNode(node, 'YieldExpression');
|
|
}
|
|
|
|
// ConditionalExpression :
|
|
// ShortCircuitExpression
|
|
// ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression
|
|
parseConditionalExpression() {
|
|
const node = this.startNode();
|
|
const ShortCircuitExpression = this.parseShortCircuitExpression();
|
|
if (this.eat(Token.CONDITIONAL)) {
|
|
node.ShortCircuitExpression = ShortCircuitExpression;
|
|
this.scope.with({ in: true }, () => {
|
|
node.AssignmentExpression_a = this.parseAssignmentExpression();
|
|
});
|
|
this.expect(Token.COLON);
|
|
node.AssignmentExpression_b = this.parseAssignmentExpression();
|
|
return this.finishNode(node, 'ConditionalExpression');
|
|
}
|
|
return ShortCircuitExpression;
|
|
}
|
|
|
|
// ShortCircuitExpression :
|
|
// LogicalORExpression
|
|
// CoalesceExpression
|
|
//
|
|
// CoalesceExpression :
|
|
// CoalesceExpressionHead `??` BitwiseORExpression
|
|
//
|
|
// CoalesceExpressionHead :
|
|
// CoalesceExpression
|
|
// BitwiseORExpression
|
|
parseShortCircuitExpression() {
|
|
// Start parse at BIT_OR, right above AND/OR/NULLISH
|
|
const expression = this.parseBinaryExpression(TokenPrecedence[Token.BIT_OR]);
|
|
switch (this.peek().type) {
|
|
case Token.AND:
|
|
case Token.OR:
|
|
// Drop into normal binary chain starting at OR
|
|
return this.parseBinaryExpression(TokenPrecedence[Token.OR], expression);
|
|
case Token.NULLISH: {
|
|
let x = expression;
|
|
while (this.eat(Token.NULLISH)) {
|
|
const node = this.startNode();
|
|
node.CoalesceExpressionHead = x;
|
|
node.BitwiseORExpression = this.parseBinaryExpression(TokenPrecedence[Token.BIT_OR]);
|
|
x = this.finishNode(node, 'CoalesceExpression');
|
|
}
|
|
return x;
|
|
}
|
|
default:
|
|
return expression;
|
|
}
|
|
}
|
|
|
|
parseBinaryExpression(precedence, x = this.parseUnaryExpression()) {
|
|
let p = TokenPrecedence[this.peek().type];
|
|
if (p >= precedence) {
|
|
do {
|
|
while (TokenPrecedence[this.peek().type] === p) {
|
|
const left = x;
|
|
const node = this.startNode(left);
|
|
if (this.peek().type === Token.IN && !this.scope.hasIn()) {
|
|
return left;
|
|
}
|
|
const op = this.next();
|
|
const nextP = op.type === Token.EXP ? p : p + 1;
|
|
const right = this.parseBinaryExpression(nextP);
|
|
let name;
|
|
switch (op.type) {
|
|
case Token.EXP:
|
|
name = 'ExponentiationExpression';
|
|
node.UpdateExpression = left;
|
|
node.ExponentiationExpression = right;
|
|
break;
|
|
case Token.MUL:
|
|
case Token.DIV:
|
|
case Token.MOD:
|
|
name = 'MultiplicativeExpression';
|
|
node.MultiplicativeExpression = left;
|
|
node.MultiplicativeOperator = op.value;
|
|
node.ExponentiationExpression = right;
|
|
break;
|
|
case Token.ADD:
|
|
case Token.SUB:
|
|
name = 'AdditiveExpression';
|
|
node.AdditiveExpression = left;
|
|
node.MultiplicativeExpression = right;
|
|
node.operator = op.value;
|
|
break;
|
|
case Token.SHL:
|
|
case Token.SAR:
|
|
case Token.SHR:
|
|
name = 'ShiftExpression';
|
|
node.ShiftExpression = left;
|
|
node.AdditiveExpression = right;
|
|
node.operator = op.value;
|
|
break;
|
|
case Token.LT:
|
|
case Token.GT:
|
|
case Token.LTE:
|
|
case Token.GTE:
|
|
case Token.INSTANCEOF:
|
|
case Token.IN:
|
|
name = 'RelationalExpression';
|
|
node.RelationalExpression = left;
|
|
node.ShiftExpression = right;
|
|
node.operator = op.value;
|
|
break;
|
|
case Token.EQ:
|
|
case Token.NE:
|
|
case Token.EQ_STRICT:
|
|
case Token.NE_STRICT:
|
|
name = 'EqualityExpression';
|
|
node.EqualityExpression = left;
|
|
node.RelationalExpression = right;
|
|
node.operator = op.value;
|
|
break;
|
|
case Token.BIT_AND:
|
|
name = 'BitwiseANDExpression';
|
|
node.A = left;
|
|
node.operator = op.value;
|
|
node.B = right;
|
|
break;
|
|
case Token.BIT_XOR:
|
|
name = 'BitwiseXORExpression';
|
|
node.A = left;
|
|
node.operator = op.value;
|
|
node.B = right;
|
|
break;
|
|
case Token.BIT_OR:
|
|
name = 'BitwiseORExpression';
|
|
node.A = left;
|
|
node.operator = op.value;
|
|
node.B = right;
|
|
break;
|
|
case Token.AND:
|
|
name = 'LogicalANDExpression';
|
|
node.LogicalANDExpression = left;
|
|
node.BitwiseORExpression = right;
|
|
break;
|
|
case Token.OR:
|
|
name = 'LogicalORExpression';
|
|
node.LogicalORExpression = left;
|
|
node.LogicalANDExpression = right;
|
|
break;
|
|
default:
|
|
this.unexpected(op);
|
|
}
|
|
x = this.finishNode(node, name);
|
|
}
|
|
p -= 1;
|
|
} while (p >= precedence);
|
|
}
|
|
return x;
|
|
}
|
|
|
|
// UnaryExpression :
|
|
// UpdateExpression
|
|
// `delete` UnaryExpression
|
|
// `void` UnaryExpression
|
|
// `typeof` UnaryExpression
|
|
// `+` UnaryExpression
|
|
// `-` UnaryExpression
|
|
// `~` UnaryExpression
|
|
// `!` UnaryExpression
|
|
// [+Await] AwaitExpression
|
|
parseUnaryExpression() {
|
|
return this.scope.with({ in: true }, () => {
|
|
if (this.test(Token.AWAIT) && this.scope.hasAwait()) {
|
|
return this.parseAwaitExpression();
|
|
}
|
|
const node = this.startNode();
|
|
switch (this.peek().type) {
|
|
case Token.DELETE:
|
|
case Token.VOID:
|
|
case Token.TYPEOF:
|
|
case Token.ADD:
|
|
case Token.SUB:
|
|
case Token.BIT_NOT:
|
|
case Token.NOT:
|
|
node.operator = this.next().value;
|
|
node.UnaryExpression = this.parseUnaryExpression();
|
|
if (this.isStrictMode()
|
|
&& node.operator === 'delete'
|
|
&& node.UnaryExpression.type === 'IdentifierReference') {
|
|
this.raiseEarly('DeleteIdentifier', node.UnaryExpression);
|
|
}
|
|
if (this.test(Token.EXP)) {
|
|
this.unexpected();
|
|
}
|
|
return this.finishNode(node, 'UnaryExpression');
|
|
default:
|
|
return this.parseUpdateExpression();
|
|
}
|
|
});
|
|
}
|
|
|
|
// AwaitExpression : `await` UnaryExpression
|
|
parseAwaitExpression() {
|
|
if (this.scope.inParameters()) {
|
|
this.raiseEarly('AwaitInFormalParameters');
|
|
}
|
|
const node = this.startNode();
|
|
this.expect(Token.AWAIT);
|
|
node.UnaryExpression = this.parseUnaryExpression();
|
|
if (this.scope.arrowInfoStack.length > 0) {
|
|
this.scope.arrowInfoStack[this.scope.arrowInfoStack.length - 1].awaitExpressions.push(node);
|
|
} else if (!this.scope.hasReturn()) {
|
|
this.state.hasTopLevelAwait = true;
|
|
}
|
|
return this.finishNode(node, 'AwaitExpression');
|
|
}
|
|
|
|
// UpdateExpression :
|
|
// LeftHandSideExpression
|
|
// LeftHandSideExpression [no LineTerminator here] `++`
|
|
// LeftHandSideExpression [no LineTerminator here] `--`
|
|
// `++` UnaryExpression
|
|
// `--` UnaryExpression
|
|
parseUpdateExpression() {
|
|
if (this.test(Token.INC) || this.test(Token.DEC)) {
|
|
const node = this.startNode();
|
|
node.operator = this.next().value;
|
|
node.LeftHandSideExpression = null;
|
|
node.UnaryExpression = this.parseUnaryExpression();
|
|
this.validateAssignmentTarget(node.UnaryExpression);
|
|
return this.finishNode(node, 'UpdateExpression');
|
|
}
|
|
const argument = this.parseLeftHandSideExpression();
|
|
if (!this.peek().hadLineTerminatorBefore) {
|
|
if (this.test(Token.INC) || this.test(Token.DEC)) {
|
|
this.validateAssignmentTarget(argument);
|
|
const node = this.startNode();
|
|
node.operator = this.next().value;
|
|
node.LeftHandSideExpression = argument;
|
|
node.UnaryExpression = null;
|
|
return this.finishNode(node, 'UpdateExpression');
|
|
}
|
|
}
|
|
return argument;
|
|
}
|
|
|
|
// LeftHandSideExpression
|
|
parseLeftHandSideExpression(allowCalls = true) {
|
|
let result;
|
|
switch (this.peek().type) {
|
|
case Token.NEW:
|
|
result = this.parseNewExpression();
|
|
break;
|
|
case Token.SUPER: {
|
|
const node = this.startNode();
|
|
this.next();
|
|
if (this.test(Token.LPAREN)) {
|
|
if (!this.scope.hasSuperCall()) {
|
|
this.raiseEarly('InvalidSuperCall');
|
|
}
|
|
node.Arguments = this.parseArguments().Arguments;
|
|
result = this.finishNode(node, 'SuperCall');
|
|
} else {
|
|
if (!this.scope.hasSuperProperty()) {
|
|
this.raiseEarly('InvalidSuperProperty');
|
|
}
|
|
if (this.eat(Token.LBRACK)) {
|
|
node.Expression = this.parseExpression();
|
|
this.expect(Token.RBRACK);
|
|
node.IdentifierName = null;
|
|
} else {
|
|
this.expect(Token.PERIOD);
|
|
node.Expression = null;
|
|
node.IdentifierName = this.parseIdentifierName();
|
|
}
|
|
result = this.finishNode(node, 'SuperProperty');
|
|
}
|
|
break;
|
|
}
|
|
case Token.IMPORT: {
|
|
const node = this.startNode();
|
|
this.next();
|
|
if (this.scope.hasImportMeta() && this.eat(Token.PERIOD)) {
|
|
this.expect('meta');
|
|
result = this.finishNode(node, 'ImportMeta');
|
|
} else {
|
|
if (!allowCalls) {
|
|
this.unexpected();
|
|
}
|
|
this.expect(Token.LPAREN);
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
this.expect(Token.RPAREN);
|
|
result = this.finishNode(node, 'ImportCall');
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
result = this.parsePrimaryExpression();
|
|
break;
|
|
}
|
|
|
|
const check = allowCalls ? isPropertyOrCall : isMember;
|
|
while (check(this.peek().type)) {
|
|
const node = this.startNode(result);
|
|
switch (this.peek().type) {
|
|
case Token.LBRACK: {
|
|
this.next();
|
|
node.MemberExpression = result;
|
|
node.IdentifierName = null;
|
|
node.Expression = this.parseExpression();
|
|
result = this.finishNode(node, 'MemberExpression');
|
|
this.expect(Token.RBRACK);
|
|
break;
|
|
}
|
|
case Token.PERIOD:
|
|
this.next();
|
|
node.MemberExpression = result;
|
|
node.IdentifierName = this.parseIdentifierName();
|
|
node.Expression = null;
|
|
result = this.finishNode(node, 'MemberExpression');
|
|
break;
|
|
case Token.LPAREN: {
|
|
// `async` [no LineTerminator here] `(`
|
|
const couldBeArrow = this.matches('async', this.currentToken)
|
|
&& result.type === 'IdentifierReference'
|
|
&& !this.peek().hadLineTerminatorBefore;
|
|
if (couldBeArrow) {
|
|
this.scope.pushArrowInfo(true);
|
|
}
|
|
const { Arguments, trailingComma } = this.parseArguments();
|
|
node.CallExpression = result;
|
|
node.Arguments = Arguments;
|
|
if (couldBeArrow) {
|
|
node.arrowInfo = this.scope.popArrowInfo();
|
|
node.arrowInfo.trailingComma = trailingComma;
|
|
}
|
|
result = this.finishNode(node, 'CallExpression');
|
|
break;
|
|
}
|
|
case Token.OPTIONAL:
|
|
node.MemberExpression = result;
|
|
node.OptionalChain = this.parseOptionalChain();
|
|
result = this.finishNode(node, 'OptionalExpression');
|
|
break;
|
|
case Token.TEMPLATE:
|
|
node.MemberExpression = result;
|
|
node.TemplateLiteral = this.parseTemplateLiteral(true);
|
|
result = this.finishNode(node, 'TaggedTemplateExpression');
|
|
break;
|
|
default:
|
|
this.unexpected();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// OptionalChain
|
|
parseOptionalChain() {
|
|
this.expect(Token.OPTIONAL);
|
|
let base = this.startNode();
|
|
base.OptionalChain = null;
|
|
if (this.test(Token.LPAREN)) {
|
|
base.Arguments = this.parseArguments().Arguments;
|
|
} else if (this.eat(Token.LBRACK)) {
|
|
base.Expression = this.parseExpression();
|
|
this.expect(Token.RBRACK);
|
|
} else if (this.test(Token.TEMPLATE)) {
|
|
this.raise('TemplateInOptionalChain');
|
|
} else {
|
|
base.IdentifierName = this.parseIdentifierName();
|
|
}
|
|
base = this.finishNode(base, 'OptionalChain');
|
|
|
|
while (true) {
|
|
const node = this.startNode();
|
|
if (this.test(Token.LPAREN)) {
|
|
node.OptionalChain = base;
|
|
node.Arguments = this.parseArguments().Arguments;
|
|
base = this.finishNode(node, 'OptionalChain');
|
|
} else if (this.eat(Token.LBRACK)) {
|
|
node.OptionalChain = base;
|
|
node.Expression = this.parseExpression();
|
|
this.expect(Token.RBRACK);
|
|
base = this.finishNode(node, 'OptionalChain');
|
|
} else if (this.test(Token.TEMPLATE)) {
|
|
this.raise('TemplateInOptionalChain');
|
|
} else if (this.eat(Token.PERIOD)) {
|
|
node.OptionalChain = base;
|
|
node.IdentifierName = this.parseIdentifierName();
|
|
base = this.finishNode(node, 'OptionalChain');
|
|
} else {
|
|
return base;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewExpression
|
|
parseNewExpression() {
|
|
const node = this.startNode();
|
|
this.expect(Token.NEW);
|
|
if (this.scope.hasNewTarget() && this.eat(Token.PERIOD)) {
|
|
this.expect('target');
|
|
return this.finishNode(node, 'NewTarget');
|
|
}
|
|
node.MemberExpression = this.parseLeftHandSideExpression(false);
|
|
if (this.test(Token.LPAREN)) {
|
|
node.Arguments = this.parseArguments().Arguments;
|
|
} else {
|
|
node.Arguments = null;
|
|
}
|
|
return this.finishNode(node, 'NewExpression');
|
|
}
|
|
|
|
// PrimaryExpression :
|
|
// ...
|
|
parsePrimaryExpression() {
|
|
switch (this.peek().type) {
|
|
case Token.IDENTIFIER:
|
|
case Token.ESCAPED_KEYWORD:
|
|
case Token.YIELD:
|
|
case Token.AWAIT:
|
|
// `async` [no LineTerminator here] `function`
|
|
if (this.test('async') && this.testAhead(Token.FUNCTION)
|
|
&& !this.peekAhead().hadLineTerminatorBefore) {
|
|
return this.parseFunctionExpression(FunctionKind.ASYNC);
|
|
}
|
|
return this.parseIdentifierReference();
|
|
case Token.THIS: {
|
|
const node = this.startNode();
|
|
this.next();
|
|
return this.finishNode(node, 'ThisExpression');
|
|
}
|
|
case Token.NUMBER:
|
|
case Token.BIGINT:
|
|
return this.parseNumericLiteral();
|
|
case Token.STRING:
|
|
return this.parseStringLiteral();
|
|
case Token.NULL: {
|
|
const node = this.startNode();
|
|
this.next();
|
|
return this.finishNode(node, 'NullLiteral');
|
|
}
|
|
case Token.TRUE:
|
|
case Token.FALSE:
|
|
return this.parseBooleanLiteral();
|
|
case Token.LBRACK:
|
|
return this.parseArrayLiteral();
|
|
case Token.LBRACE:
|
|
return this.parseObjectLiteral();
|
|
case Token.FUNCTION:
|
|
return this.parseFunctionExpression(FunctionKind.NORMAL);
|
|
case Token.CLASS:
|
|
return this.parseClassExpression();
|
|
case Token.TEMPLATE:
|
|
return this.parseTemplateLiteral();
|
|
case Token.DIV:
|
|
case Token.ASSIGN_DIV:
|
|
return this.parseRegularExpressionLiteral();
|
|
case Token.LPAREN:
|
|
return this.parseCoverParenthesizedExpressionAndArrowParameterList();
|
|
default:
|
|
return this.unexpected();
|
|
}
|
|
}
|
|
|
|
// NumericLiteral
|
|
parseNumericLiteral() {
|
|
const node = this.startNode();
|
|
if (!this.test(Token.NUMBER) && !this.test(Token.BIGINT)) {
|
|
this.unexpected();
|
|
}
|
|
node.value = this.next().value;
|
|
return this.finishNode(node, 'NumericLiteral');
|
|
}
|
|
|
|
// StringLiteral
|
|
parseStringLiteral() {
|
|
const node = this.startNode();
|
|
if (!this.test(Token.STRING)) {
|
|
this.unexpected();
|
|
}
|
|
node.value = this.next().value;
|
|
return this.finishNode(node, 'StringLiteral');
|
|
}
|
|
|
|
// BooleanLiteral :
|
|
// `true`
|
|
// `false`
|
|
parseBooleanLiteral() {
|
|
const node = this.startNode();
|
|
switch (this.peek().type) {
|
|
case Token.TRUE:
|
|
this.next();
|
|
node.value = true;
|
|
break;
|
|
case Token.FALSE:
|
|
this.next();
|
|
node.value = false;
|
|
break;
|
|
default:
|
|
this.unexpected();
|
|
}
|
|
return this.finishNode(node, 'BooleanLiteral');
|
|
}
|
|
|
|
// ArrayLiteral :
|
|
// `[` `]`
|
|
// `[` Elision `]`
|
|
// `[` ElementList `]`
|
|
// `[` ElementList `,` `]`
|
|
// `[` ElementList `,` Elision `]`
|
|
parseArrayLiteral() {
|
|
const node = this.startNode();
|
|
this.expect(Token.LBRACK);
|
|
node.ElementList = [];
|
|
node.hasTrailingComma = false;
|
|
while (true) {
|
|
while (this.test(Token.COMMA)) {
|
|
const elision = this.startNode();
|
|
this.next();
|
|
node.ElementList.push(this.finishNode(elision, 'Elision'));
|
|
}
|
|
if (this.eat(Token.RBRACK)) {
|
|
break;
|
|
}
|
|
if (this.test(Token.ELLIPSIS)) {
|
|
const spread = this.startNode();
|
|
this.next();
|
|
spread.AssignmentExpression = this.parseAssignmentExpression();
|
|
node.ElementList.push(this.finishNode(spread, 'SpreadElement'));
|
|
} else {
|
|
node.ElementList.push(this.parseAssignmentExpression());
|
|
}
|
|
if (this.eat(Token.RBRACK)) {
|
|
node.hasTrailingComma = false;
|
|
break;
|
|
}
|
|
node.hasTrailingComma = true;
|
|
this.expect(Token.COMMA);
|
|
}
|
|
return this.finishNode(node, 'ArrayLiteral');
|
|
}
|
|
|
|
// ObjectLiteral :
|
|
// `{` `}`
|
|
// `{` PropertyDefinitionList `}`
|
|
// `{` PropertyDefinitionList `,` `}`
|
|
parseObjectLiteral() {
|
|
const node = this.startNode();
|
|
this.expect(Token.LBRACE);
|
|
node.PropertyDefinitionList = [];
|
|
while (true) {
|
|
if (this.eat(Token.RBRACE)) {
|
|
break;
|
|
}
|
|
node.PropertyDefinitionList.push(this.parsePropertyDefinition());
|
|
if (this.eat(Token.RBRACE)) {
|
|
break;
|
|
}
|
|
this.expect(Token.COMMA);
|
|
}
|
|
return this.finishNode(node, 'ObjectLiteral');
|
|
}
|
|
|
|
parsePropertyDefinition() {
|
|
return this.parseBracketedDefinition('property');
|
|
}
|
|
|
|
parseFunctionExpression(kind) {
|
|
return this.parseFunction(true, kind);
|
|
}
|
|
|
|
parseArguments() {
|
|
this.expect(Token.LPAREN);
|
|
if (this.eat(Token.RPAREN)) {
|
|
return { Arguments: [], trailingComma: false };
|
|
}
|
|
const Arguments = [];
|
|
let trailingComma = false;
|
|
while (true) {
|
|
const node = this.startNode();
|
|
if (this.eat(Token.ELLIPSIS)) {
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
Arguments.push(this.finishNode(node, 'AssignmentRestElement'));
|
|
} else {
|
|
Arguments.push(this.parseAssignmentExpression());
|
|
}
|
|
if (this.eat(Token.RPAREN)) {
|
|
break;
|
|
}
|
|
this.expect(Token.COMMA);
|
|
if (this.eat(Token.RPAREN)) {
|
|
trailingComma = true;
|
|
break;
|
|
}
|
|
}
|
|
return { Arguments, trailingComma };
|
|
}
|
|
|
|
// #sec-class-definitions
|
|
// ClassDeclaration :
|
|
// `class` BindingIdentifier ClassTail
|
|
// [+Default] `class` ClassTail
|
|
//
|
|
// ClassExpression :
|
|
// `class` BindingIdentifier? ClassTail
|
|
parseClass(isExpression) {
|
|
const node = this.startNode();
|
|
|
|
this.expect(Token.CLASS);
|
|
|
|
this.scope.with({ strict: true }, () => {
|
|
if (!this.test(Token.LBRACE) && !this.test(Token.EXTENDS)) {
|
|
node.BindingIdentifier = this.parseBindingIdentifier();
|
|
if (!isExpression) {
|
|
this.scope.declare(node.BindingIdentifier, 'lexical');
|
|
}
|
|
} else if (isExpression === false && !this.scope.isDefault()) {
|
|
this.raise('ClassMissingBindingIdentifier');
|
|
} else {
|
|
node.BindingIdentifier = null;
|
|
}
|
|
node.ClassTail = this.scope.with({ default: false }, () => this.parseClassTail());
|
|
});
|
|
|
|
return this.finishNode(node, isExpression ? 'ClassExpression' : 'ClassDeclaration');
|
|
}
|
|
|
|
// ClassTail : ClassHeritage? `{` ClassBody? `}`
|
|
// ClassHeritage : `extends` LeftHandSideExpression
|
|
// ClassBody : ClassElementList
|
|
parseClassTail() {
|
|
const node = this.startNode();
|
|
|
|
if (this.eat(Token.EXTENDS)) {
|
|
node.ClassHeritage = this.parseLeftHandSideExpression();
|
|
} else {
|
|
node.ClassHeritage = null;
|
|
}
|
|
|
|
this.expect(Token.LBRACE);
|
|
if (this.eat(Token.RBRACE)) {
|
|
node.ClassBody = null;
|
|
} else {
|
|
this.scope.with({ superCall: !!node.ClassHeritage }, () => {
|
|
node.ClassBody = [];
|
|
let hasConstructor = false;
|
|
while (!this.eat(Token.RBRACE)) {
|
|
const m = this.parseClassElement();
|
|
node.ClassBody.push(m);
|
|
|
|
const name = PropName(m.MethodDefinition);
|
|
const isActualConstructor = !m.static
|
|
&& !!m.MethodDefinition.UniqueFormalParameters
|
|
&& m.MethodDefinition.type === 'MethodDefinition'
|
|
&& name === 'constructor';
|
|
if (isActualConstructor) {
|
|
if (hasConstructor) {
|
|
this.raiseEarly('DuplicateConstructor', m);
|
|
} else {
|
|
hasConstructor = true;
|
|
}
|
|
}
|
|
if ((m.static && name === 'prototype')
|
|
|| (!m.static && !isActualConstructor && name === 'constructor')) {
|
|
this.raiseEarly('InvalidMethodName', m, name);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return this.finishNode(node, 'ClassTail');
|
|
}
|
|
|
|
// ClassElement :
|
|
// `static` MethodDefinition
|
|
// MethodDefinition
|
|
parseClassElement() {
|
|
const node = this.startNode();
|
|
node.static = this.eat('static');
|
|
node.MethodDefinition = this.parseMethodDefinition(node.static);
|
|
while (this.eat(Token.SEMICOLON)) {
|
|
// nothing
|
|
}
|
|
return this.finishNode(node, 'ClassElement');
|
|
}
|
|
|
|
parseMethodDefinition(isStatic) {
|
|
return this.parseBracketedDefinition('method', isStatic);
|
|
}
|
|
|
|
parseClassExpression() {
|
|
return this.parseClass(true);
|
|
}
|
|
|
|
parseTemplateLiteral(tagged = false) {
|
|
const node = this.startNode();
|
|
node.TemplateSpanList = [];
|
|
node.ExpressionList = [];
|
|
let buffer = '';
|
|
while (true) {
|
|
if (this.position >= this.source.length) {
|
|
this.raise('UnterminatedTemplate', this.position);
|
|
}
|
|
const c = this.source[this.position];
|
|
switch (c) {
|
|
case '`':
|
|
this.position += 1;
|
|
node.TemplateSpanList.push(buffer);
|
|
this.next();
|
|
if (!tagged) {
|
|
node.TemplateSpanList.forEach((s) => {
|
|
if (TV(s) === undefined) {
|
|
this.raise('InvalidTemplateEscape');
|
|
}
|
|
});
|
|
}
|
|
return this.finishNode(node, 'TemplateLiteral');
|
|
case '$':
|
|
this.position += 1;
|
|
if (this.source[this.position] === '{') {
|
|
this.position += 1;
|
|
node.TemplateSpanList.push(buffer);
|
|
buffer = '';
|
|
this.next();
|
|
node.ExpressionList.push(this.parseExpression());
|
|
break;
|
|
}
|
|
buffer += c;
|
|
break;
|
|
default: {
|
|
if (c === '\\') {
|
|
buffer += c;
|
|
this.position += 1;
|
|
}
|
|
const l = this.source[this.position];
|
|
this.position += 1;
|
|
if (isLineTerminator(l)) {
|
|
if (l === '\r' && this.source[this.position] === '\n') {
|
|
this.position += 1;
|
|
}
|
|
if (l === '\u{2028}' || l === '\u{2029}') {
|
|
buffer += l;
|
|
} else {
|
|
buffer += '\n';
|
|
}
|
|
this.line += 1;
|
|
this.columnOffset = this.position;
|
|
} else {
|
|
buffer += l;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegularExpressionLiteral :
|
|
// `/` RegularExpressionBody `/` RegularExpressionFlags
|
|
parseRegularExpressionLiteral() {
|
|
const node = this.startNode();
|
|
this.scanRegularExpressionBody();
|
|
node.RegularExpressionBody = this.scannedValue;
|
|
this.scanRegularExpressionFlags();
|
|
node.RegularExpressionFlags = this.scannedValue;
|
|
try {
|
|
const parse = (flags) => {
|
|
const p = new RegExpParser(node.RegularExpressionBody);
|
|
return p.scope(flags, () => p.parsePattern());
|
|
};
|
|
if (node.RegularExpressionFlags.includes('u')) {
|
|
parse({ U: true, N: true });
|
|
} else {
|
|
const pattern = parse({ U: false, N: false });
|
|
if (pattern.groupSpecifiers.size > 0) {
|
|
parse({ U: false, N: true });
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (e instanceof SyntaxError) {
|
|
this.raise('Raw', node.location.startIndex + e.position + 1, e.message);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
const fakeToken = {
|
|
endIndex: this.position - 1,
|
|
line: this.line - 1,
|
|
column: this.position - this.columnOffset,
|
|
};
|
|
this.next();
|
|
this.currentToken = fakeToken;
|
|
return this.finishNode(node, 'RegularExpressionLiteral');
|
|
}
|
|
|
|
// CoverParenthesizedExpressionAndArrowParameterList :
|
|
// `(` Expression `)`
|
|
// `(` Expression `,` `)`
|
|
// `(` `)`
|
|
// `(` `...` BindingIdentifier `)`
|
|
// `(` `...` BindingPattern `)`
|
|
// `(` Expression `,` `...` BindingIdentifier `)`
|
|
// `(` Expression `.` `...` BindingPattern `)`
|
|
parseCoverParenthesizedExpressionAndArrowParameterList() {
|
|
const node = this.startNode();
|
|
const commaOp = this.startNode();
|
|
this.expect(Token.LPAREN);
|
|
if (this.test(Token.RPAREN)) {
|
|
if (!this.testAhead(Token.ARROW) || this.peekAhead().hadLineTerminatorBefore) {
|
|
this.unexpected();
|
|
}
|
|
this.next();
|
|
node.Arguments = [];
|
|
return this.finishNode(node, 'CoverParenthesizedExpressionAndArrowParameterList');
|
|
}
|
|
|
|
this.scope.pushArrowInfo();
|
|
this.scope.pushAssignmentInfo('arrow');
|
|
|
|
const expressions = [];
|
|
let rparenAfterComma;
|
|
while (true) {
|
|
if (this.test(Token.ELLIPSIS)) {
|
|
const inner = this.startNode();
|
|
this.next();
|
|
switch (this.peek().type) {
|
|
case Token.LBRACE:
|
|
case Token.LBRACK:
|
|
inner.BindingPattern = this.parseBindingPattern();
|
|
break;
|
|
default:
|
|
inner.BindingIdentifier = this.parseBindingIdentifier();
|
|
break;
|
|
}
|
|
expressions.push(this.finishNode(inner, 'BindingRestElement'));
|
|
this.expect(Token.RPAREN);
|
|
break;
|
|
}
|
|
expressions.push(this.parseAssignmentExpression());
|
|
if (this.eat(Token.COMMA)) {
|
|
if (this.eat(Token.RPAREN)) {
|
|
rparenAfterComma = this.currentToken;
|
|
break;
|
|
}
|
|
} else {
|
|
this.expect(Token.RPAREN);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const arrowInfo = this.scope.popArrowInfo();
|
|
const assignmentInfo = this.scope.popAssignmentInfo();
|
|
|
|
// ArrowParameters :
|
|
// CoverParenthesizedExpressionAndArrowParameterList
|
|
if (this.test(Token.ARROW) && !this.peek().hadLineTerminatorBefore) {
|
|
node.Arguments = expressions;
|
|
node.arrowInfo = arrowInfo;
|
|
assignmentInfo.clear();
|
|
return this.finishNode(node, 'CoverParenthesizedExpressionAndArrowParameterList');
|
|
}
|
|
|
|
// ParenthesizedExpression :
|
|
// `(` Expression `)`
|
|
if (expressions[expressions.length - 1].type === 'BindingRestElement') {
|
|
this.unexpected(expressions[expressions.length - 1]);
|
|
}
|
|
if (rparenAfterComma) {
|
|
this.unexpected(rparenAfterComma);
|
|
}
|
|
if (expressions.length === 1) {
|
|
node.Expression = expressions[0];
|
|
} else {
|
|
commaOp.ExpressionList = expressions;
|
|
node.Expression = this.finishNode(commaOp, 'CommaOperator');
|
|
}
|
|
return this.finishNode(node, 'ParenthesizedExpression');
|
|
}
|
|
|
|
// PropertyName :
|
|
// LiteralPropertyName
|
|
// ComputedPropertyName
|
|
// LiteralPropertyName :
|
|
// IdentifierName
|
|
// StringLiteral
|
|
// NumericLiteral
|
|
// ComputedPropertyName :
|
|
// `[` AssignmentExpression `]`
|
|
parsePropertyName() {
|
|
if (this.test(Token.LBRACK)) {
|
|
const node = this.startNode();
|
|
this.next();
|
|
node.ComputedPropertyName = this.parseAssignmentExpression();
|
|
this.expect(Token.RBRACK);
|
|
return this.finishNode(node, 'PropertyName');
|
|
}
|
|
if (this.test(Token.STRING)) {
|
|
return this.parseStringLiteral();
|
|
}
|
|
if (this.test(Token.NUMBER) || this.test(Token.BIGINT)) {
|
|
return this.parseNumericLiteral();
|
|
}
|
|
return this.parseIdentifierName();
|
|
}
|
|
|
|
// PropertyDefinition :
|
|
// IdentifierReference
|
|
// CoverInitializedName
|
|
// PropertyName `:` AssignmentExpression
|
|
// MethodDefinition
|
|
// `...` AssignmentExpression
|
|
// MethodDefinition :
|
|
// PropertyName `(` UniqueFormalParameters `)` `{` FunctionBody `}`
|
|
// GeneratorMethod
|
|
// AsyncMethod
|
|
// AsyncGeneratorMethod
|
|
// `get` PropertyName `(` `)` `{` FunctionBody `}`
|
|
// `set` PropertyName `(` PropertySetParameterList `)` `{` FunctionBody `}`
|
|
// GeneratorMethod :
|
|
// `*` PropertyName `(` UniqueFormalParameters `)` `{` GeneratorBody `}`
|
|
// AsyncMethod :
|
|
// `async` [no LineTerminator here] PropertyName `(` UniqueFormalParameters `)` `{` AsyncFunctionBody `}`
|
|
// AsyncGeneratorMethod :
|
|
// `async` [no LineTerminator here] `*` Propertyname `(` UniqueFormalParameters `)` `{` AsyncGeneratorBody `}`
|
|
parseBracketedDefinition(type, isStatic = false) {
|
|
const node = this.startNode();
|
|
|
|
if (type === 'property' && this.eat(Token.ELLIPSIS)) {
|
|
node.PropertyName = null;
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
return this.finishNode(node, 'PropertyDefinition');
|
|
}
|
|
|
|
let isGenerator = this.eat(Token.MUL);
|
|
let isGetter = false;
|
|
let isSetter = false;
|
|
let isAsync = false;
|
|
if (!isGenerator) {
|
|
if (this.test('get')) {
|
|
isGetter = true;
|
|
} else if (this.test('set')) {
|
|
isSetter = true;
|
|
} else if (this.test('async') && !this.peekAhead().hadLineTerminatorBefore) {
|
|
isAsync = true;
|
|
}
|
|
}
|
|
const firstName = this.parsePropertyName();
|
|
if (!isGenerator && !isGetter && !isSetter) {
|
|
isGenerator = this.eat(Token.MUL);
|
|
}
|
|
const isSpecialMethod = isGenerator || ((isSetter || isGetter || isAsync) && !this.test(Token.LPAREN));
|
|
|
|
if (!isGenerator && type === 'property') {
|
|
if (this.eat(Token.COLON)) {
|
|
node.PropertyName = firstName;
|
|
node.AssignmentExpression = this.parseAssignmentExpression();
|
|
return this.finishNode(node, 'PropertyDefinition');
|
|
}
|
|
if (this.scope.assignmentInfoStack.length > 0 && this.test(Token.ASSIGN)) {
|
|
node.IdentifierReference = firstName;
|
|
node.IdentifierReference.type = 'IdentifierReference';
|
|
node.Initializer = this.parseInitializerOpt();
|
|
this.finishNode(node, 'CoverInitializedName');
|
|
this.scope.registerCoverInitializedName(node);
|
|
return node;
|
|
}
|
|
|
|
if (!isSpecialMethod
|
|
&& firstName.type === 'IdentifierName'
|
|
&& !this.test(Token.LPAREN)
|
|
&& !isKeyword(firstName.name)) {
|
|
firstName.type = 'IdentifierReference';
|
|
if (firstName.name === 'await' && (this.isStrictMode() || this.scope.hasAwait())) {
|
|
this.raiseEarly('UnexpectedReservedWordStrict', firstName);
|
|
}
|
|
if (firstName.name === 'yield' && (this.isStrictMode() || this.scope.hasYield())) {
|
|
this.raiseEarly('UnexpectedReservedWordStrict', firstName);
|
|
}
|
|
if (firstName.name !== 'yield'
|
|
&& firstName.name !== 'await'
|
|
&& isKeywordRaw(firstName.name)) {
|
|
this.raiseEarly('UnexpectedToken', firstName);
|
|
}
|
|
if (this.isStrictMode() && isReservedWordStrict(firstName.name)) {
|
|
this.raiseEarly('UnexpectedReservedWordStrict', firstName);
|
|
}
|
|
return firstName;
|
|
}
|
|
}
|
|
|
|
node.PropertyName = (isSpecialMethod && (!isGenerator || isAsync)) ? this.parsePropertyName() : firstName;
|
|
|
|
this.scope.with({
|
|
lexical: true,
|
|
variable: true,
|
|
superProperty: true,
|
|
await: isAsync,
|
|
yield: isGenerator,
|
|
}, () => {
|
|
if (isSpecialMethod && isGetter) {
|
|
this.expect(Token.LPAREN);
|
|
this.expect(Token.RPAREN);
|
|
node.PropertySetParameterList = null;
|
|
node.UniqueFormalParameters = null;
|
|
} else if (isSpecialMethod && isSetter) {
|
|
this.expect(Token.LPAREN);
|
|
node.PropertySetParameterList = [this.parseFormalParameter()];
|
|
this.expect(Token.RPAREN);
|
|
node.UniqueFormalParameters = null;
|
|
} else {
|
|
node.PropertySetParameterList = null;
|
|
node.UniqueFormalParameters = this.parseUniqueFormalParameters();
|
|
}
|
|
|
|
this.scope.with({
|
|
superCall: !isSpecialMethod
|
|
&& !isStatic
|
|
&& (node.PropertyName.name === 'constructor' || node.PropertyName.value === 'constructor')
|
|
&& this.scope.hasSuperCall(),
|
|
}, () => {
|
|
const body = this.parseFunctionBody(isAsync, isGenerator, false);
|
|
node[`${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}Body`] = body;
|
|
if (node.UniqueFormalParameters || node.PropertySetParameterList) {
|
|
this.validateFormalParameters(node.UniqueFormalParameters || node.PropertySetParameterList, body, true);
|
|
}
|
|
});
|
|
});
|
|
|
|
const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : ''}Method${isAsync || isGenerator ? '' : 'Definition'}`;
|
|
return this.finishNode(node, name);
|
|
}
|
|
}
|