mirror of
https://github.com/bendtherules/completion-viz.git
synced 2026-08-19 00:31:34 +00:00
Clone engine262 in /engine262
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import { Lexer } from './Lexer.mjs';
|
||||
|
||||
export class BaseParser extends Lexer {}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,322 @@
|
||||
import { IsSimpleParameterList } from '../static-semantics/all.mjs';
|
||||
import { getDeclarations } from './Scope.mjs';
|
||||
import { Token } from './tokens.mjs';
|
||||
import { IdentifierParser } from './IdentifierParser.mjs';
|
||||
|
||||
export const FunctionKind = {
|
||||
NORMAL: 0,
|
||||
ASYNC: 1,
|
||||
};
|
||||
|
||||
export class FunctionParser extends IdentifierParser {
|
||||
// FunctionDeclaration :
|
||||
// `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}`
|
||||
// [+Default] `function` `(` FormalParameters `)` `{` FunctionBody `}`
|
||||
// FunctionExpression :
|
||||
// `function` BindingIdentifier? `(` FormalParameters `)` `{` FunctionBody `}`
|
||||
// GeneratorDeclaration :
|
||||
// `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}`
|
||||
// [+Default] `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}`
|
||||
// GeneratorExpression :
|
||||
// `function` BindingIdentifier? `(` FormalParameters `)` `{` GeneratorBody `}`
|
||||
// AsyncGeneratorDeclaration :
|
||||
// `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}`
|
||||
// [+Default] `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}`
|
||||
// AsyncGeneratorExpression :
|
||||
// `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncGeneratorBody `}`
|
||||
// AsyncFunctionDeclaration :
|
||||
// `async` `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}`
|
||||
// [+Default] `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}`
|
||||
// Async`FunctionExpression :
|
||||
// `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncFunctionBody `}`
|
||||
parseFunction(isExpression, kind) {
|
||||
const isAsync = kind === FunctionKind.ASYNC;
|
||||
const node = this.startNode();
|
||||
if (isAsync) {
|
||||
this.expect('async');
|
||||
}
|
||||
this.expect(Token.FUNCTION);
|
||||
const isGenerator = this.eat(Token.MUL);
|
||||
if (!this.test(Token.LPAREN)) {
|
||||
this.scope.with({
|
||||
await: isExpression ? false : undefined,
|
||||
yield: isExpression ? false : undefined,
|
||||
}, () => {
|
||||
node.BindingIdentifier = this.parseBindingIdentifier();
|
||||
});
|
||||
if (!isExpression) {
|
||||
this.scope.declare(node.BindingIdentifier, 'function');
|
||||
}
|
||||
} else if (isExpression === false && !this.scope.isDefault()) {
|
||||
this.unexpected();
|
||||
} else {
|
||||
node.BindingIdentifier = null;
|
||||
}
|
||||
|
||||
this.scope.with({
|
||||
default: false,
|
||||
await: isAsync,
|
||||
yield: isGenerator,
|
||||
lexical: true,
|
||||
variable: true,
|
||||
variableFunctions: true,
|
||||
}, () => {
|
||||
node.FormalParameters = this.parseFormalParameters();
|
||||
|
||||
const body = this.parseFunctionBody(isAsync, isGenerator, false);
|
||||
node[body.type] = body;
|
||||
|
||||
if (node.BindingIdentifier) {
|
||||
if (body.strict && (node.BindingIdentifier.name === 'eval' || node.BindingIdentifier.name === 'arguments')) {
|
||||
this.raiseEarly('UnexpectedToken', node.BindingIdentifier);
|
||||
}
|
||||
if (isExpression) {
|
||||
if (this.scope.hasYield() && node.BindingIdentifier.name === 'yield') {
|
||||
this.raiseEarly('UnexpectedToken', node.BindingIdentifier);
|
||||
}
|
||||
if (this.scope.hasAwait() && node.BindingIdentifier.name === 'await') {
|
||||
this.raiseEarly('UnexpectedToken', node.BindingIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.validateFormalParameters(node.FormalParameters, body);
|
||||
});
|
||||
|
||||
const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}${isExpression ? 'Expression' : 'Declaration'}`;
|
||||
return this.finishNode(node, name);
|
||||
}
|
||||
|
||||
validateFormalParameters(parameters, body, wantsUnique = false) {
|
||||
const isStrict = body.strict;
|
||||
const hasStrictDirective = body.directives && body.directives.includes('use strict');
|
||||
if (wantsUnique === false && !IsSimpleParameterList(parameters)) {
|
||||
wantsUnique = true;
|
||||
}
|
||||
|
||||
if (hasStrictDirective) {
|
||||
parameters.forEach((p) => {
|
||||
if (p.type !== 'SingleNameBinding' || p.Initializer) {
|
||||
this.raiseEarly('UseStrictNonSimpleParameter', p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const names = new Set();
|
||||
getDeclarations(parameters)
|
||||
.forEach((d) => {
|
||||
if (isStrict) {
|
||||
if (d.name === 'arguments' || d.name === 'eval') {
|
||||
this.raiseEarly('UnexpectedToken', d.node);
|
||||
}
|
||||
}
|
||||
if (isStrict || wantsUnique) {
|
||||
if (names.has(d.name)) {
|
||||
this.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
} else {
|
||||
names.add(d.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
convertArrowParameter(node) {
|
||||
switch (node.type) {
|
||||
case 'IdentifierReference': {
|
||||
node.type = 'BindingIdentifier';
|
||||
const container = this.startNode();
|
||||
container.BindingIdentifier = node;
|
||||
container.Initializer = null;
|
||||
this.scope.declare(node, 'parameter');
|
||||
return this.finishNode(container, 'SingleNameBinding');
|
||||
}
|
||||
case 'BindingRestElement':
|
||||
this.scope.declare(node, 'parameter');
|
||||
return node;
|
||||
case 'Elision':
|
||||
return node;
|
||||
case 'ArrayLiteral': {
|
||||
const wrap = this.startNode();
|
||||
node.BindingElementList = [];
|
||||
node.ElementList.forEach((p, i) => {
|
||||
const c = this.convertArrowParameter(p);
|
||||
if (c.type === 'BindingRestElement') {
|
||||
if (i !== node.ElementList.length - 1) {
|
||||
this.raiseEarly('UnexpectedToken', c);
|
||||
}
|
||||
node.BindingRestElement = c;
|
||||
} else {
|
||||
node.BindingElementList.push(c);
|
||||
}
|
||||
});
|
||||
delete node.ElementList;
|
||||
node.type = 'ArrayBindingPattern';
|
||||
wrap.BindingPattern = node;
|
||||
wrap.Initializer = null;
|
||||
return this.finishNode(wrap, 'BindingElement');
|
||||
}
|
||||
case 'ObjectLiteral': {
|
||||
const wrap = this.startNode();
|
||||
node.BindingPropertyList = [];
|
||||
node.PropertyDefinitionList.forEach((p) => {
|
||||
const c = this.convertArrowParameter(p);
|
||||
if (c.type === 'BindingRestProperty') {
|
||||
node.BindingRestProperty = c;
|
||||
} else {
|
||||
node.BindingPropertyList.push(c);
|
||||
}
|
||||
});
|
||||
delete node.PropertyDefinitionList;
|
||||
node.type = 'ObjectBindingPattern';
|
||||
wrap.BindingPattern = node;
|
||||
wrap.Initializer = null;
|
||||
return this.finishNode(wrap, 'BindingElement');
|
||||
}
|
||||
case 'AssignmentExpression': {
|
||||
const result = this.convertArrowParameter(node.LeftHandSideExpression);
|
||||
result.Initializer = node.AssignmentExpression;
|
||||
return result;
|
||||
}
|
||||
case 'CoverInitializedName':
|
||||
node.type = 'SingleNameBinding';
|
||||
node.BindingIdentifier = node.IdentifierReference;
|
||||
node.BindingIdentifier.type = 'BindingIdentifier';
|
||||
delete node.IdentifierReference;
|
||||
this.scope.declare(node, 'parameter');
|
||||
return node;
|
||||
case 'PropertyDefinition':
|
||||
if (node.PropertyName === null) {
|
||||
node.type = 'BindingRestProperty';
|
||||
node.BindingIdentifier = node.AssignmentExpression;
|
||||
node.BindingIdentifier.type = 'BindingIdentifier';
|
||||
} else {
|
||||
node.type = 'BindingProperty';
|
||||
node.BindingElement = this.convertArrowParameter(node.AssignmentExpression);
|
||||
}
|
||||
this.scope.declare(node, 'parameter');
|
||||
delete node.AssignmentExpression;
|
||||
return node;
|
||||
case 'SpreadElement':
|
||||
case 'AssignmentRestElement':
|
||||
node.type = 'BindingRestElement';
|
||||
if (node.AssignmentExpression.type === 'AssignmentExpression') {
|
||||
this.raiseEarly('UnexpectedToken', node);
|
||||
} else if (node.AssignmentExpression.type === 'IdentifierReference') {
|
||||
node.BindingIdentifier = node.AssignmentExpression;
|
||||
node.BindingIdentifier.type = 'BindingIdentifier';
|
||||
} else {
|
||||
node.BindingPattern = this.convertArrowParameter(node.AssignmentExpression).BindingPattern;
|
||||
}
|
||||
this.scope.declare(node, 'parameter');
|
||||
delete node.AssignmentExpression;
|
||||
return node;
|
||||
default:
|
||||
this.raiseEarly('UnexpectedToken', node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
parseArrowFunction(node, { arrowInfo, Arguments }, kind) {
|
||||
const isAsync = kind === FunctionKind.ASYNC;
|
||||
this.expect(Token.ARROW);
|
||||
if (arrowInfo) {
|
||||
arrowInfo.awaitExpressions.forEach((e) => {
|
||||
this.raiseEarly('AwaitInFormalParameters', e);
|
||||
});
|
||||
arrowInfo.yieldExpressions.forEach((e) => {
|
||||
this.raiseEarly('YieldInFormalParameters', e);
|
||||
});
|
||||
if (isAsync) {
|
||||
arrowInfo.awaitIdentifiers.forEach((e) => {
|
||||
this.raiseEarly('AwaitInFormalParameters', e);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.scope.with({
|
||||
default: false,
|
||||
lexical: true,
|
||||
variable: true,
|
||||
}, () => {
|
||||
this.scope.with({
|
||||
parameters: true,
|
||||
}, () => {
|
||||
node.ArrowParameters = Arguments.map((p) => this.convertArrowParameter(p));
|
||||
});
|
||||
const body = this.parseConciseBody(isAsync);
|
||||
this.validateFormalParameters(node.ArrowParameters, body, true);
|
||||
node[`${isAsync ? 'Async' : ''}ConciseBody`] = body;
|
||||
});
|
||||
return this.finishNode(node, `${isAsync ? 'Async' : ''}ArrowFunction`);
|
||||
}
|
||||
|
||||
parseConciseBody(isAsync) {
|
||||
if (this.test(Token.LBRACE)) {
|
||||
return this.parseFunctionBody(isAsync, false, true);
|
||||
}
|
||||
const asyncBody = this.startNode();
|
||||
const exprBody = this.startNode();
|
||||
this.scope.with({ await: isAsync }, () => {
|
||||
exprBody.AssignmentExpression = this.parseAssignmentExpression();
|
||||
});
|
||||
asyncBody.ExpressionBody = this.finishNode(exprBody, 'ExpressionBody');
|
||||
return this.finishNode(asyncBody, `${isAsync ? 'Async' : ''}ConciseBody`);
|
||||
}
|
||||
|
||||
// FormalParameter : BindingElement
|
||||
parseFormalParameter() {
|
||||
return this.parseBindingElement();
|
||||
}
|
||||
|
||||
parseFormalParameters() {
|
||||
this.expect(Token.LPAREN);
|
||||
if (this.eat(Token.RPAREN)) {
|
||||
return [];
|
||||
}
|
||||
const params = [];
|
||||
this.scope.with({ parameters: true }, () => {
|
||||
while (true) {
|
||||
if (this.test(Token.ELLIPSIS)) {
|
||||
const element = this.parseBindingRestElement();
|
||||
this.scope.declare(element, 'parameter');
|
||||
params.push(element);
|
||||
this.expect(Token.RPAREN);
|
||||
break;
|
||||
} else {
|
||||
const formal = this.parseFormalParameter();
|
||||
this.scope.declare(formal, 'parameter');
|
||||
params.push(formal);
|
||||
}
|
||||
if (this.eat(Token.RPAREN)) {
|
||||
break;
|
||||
}
|
||||
this.expect(Token.COMMA);
|
||||
if (this.eat(Token.RPAREN)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
parseUniqueFormalParameters() {
|
||||
return this.parseFormalParameters();
|
||||
}
|
||||
|
||||
parseFunctionBody(isAsync, isGenerator, isArrow) {
|
||||
const node = this.startNode();
|
||||
this.expect(Token.LBRACE);
|
||||
this.scope.with({
|
||||
newTarget: isArrow ? undefined : true,
|
||||
return: true,
|
||||
await: isAsync,
|
||||
yield: isGenerator,
|
||||
label: 'boundary',
|
||||
}, () => {
|
||||
node.directives = [];
|
||||
node.FunctionStatementList = this.parseStatementList(Token.RBRACE, node.directives);
|
||||
node.strict = node.strict || node.directives.includes('use strict');
|
||||
});
|
||||
const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}Body`;
|
||||
return this.finishNode(node, name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
Token,
|
||||
isKeyword,
|
||||
isReservedWordStrict,
|
||||
isKeywordRaw,
|
||||
} from './tokens.mjs';
|
||||
import { BaseParser } from './BaseParser.mjs';
|
||||
|
||||
export class IdentifierParser extends BaseParser {
|
||||
// IdentifierName
|
||||
parseIdentifierName() {
|
||||
const node = this.startNode();
|
||||
const p = this.peek();
|
||||
if (p.type === Token.IDENTIFIER
|
||||
|| p.type === Token.ESCAPED_KEYWORD
|
||||
|| isKeyword(p.type)) {
|
||||
node.name = this.next().value;
|
||||
} else {
|
||||
this.unexpected();
|
||||
}
|
||||
return this.finishNode(node, 'IdentifierName');
|
||||
}
|
||||
|
||||
// BindingIdentifier :
|
||||
// Identifier
|
||||
// `yield`
|
||||
// `await`
|
||||
parseBindingIdentifier() {
|
||||
const node = this.startNode();
|
||||
const token = this.next();
|
||||
switch (token.type) {
|
||||
case Token.IDENTIFIER:
|
||||
node.name = token.value;
|
||||
break;
|
||||
case Token.ESCAPED_KEYWORD:
|
||||
node.name = token.value;
|
||||
break;
|
||||
case Token.YIELD:
|
||||
node.name = 'yield';
|
||||
break;
|
||||
case Token.AWAIT:
|
||||
node.name = 'await';
|
||||
for (let i = 0; i < this.scope.arrowInfoStack.length; i += 1) {
|
||||
const arrowInfo = this.scope.arrowInfoStack[i];
|
||||
if (arrowInfo.isAsync) {
|
||||
arrowInfo.awaitIdentifiers.push(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.unexpected(token);
|
||||
}
|
||||
if (node.name === 'yield' && (this.scope.hasYield() || this.scope.isModule())) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (node.name === 'await' && (this.scope.hasAwait() || this.scope.isModule())) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (this.isStrictMode()) {
|
||||
if (isReservedWordStrict(node.name)) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (node.name === 'eval' || node.name === 'arguments') {
|
||||
this.raiseEarly('UnexpectedEvalOrArguments', token);
|
||||
}
|
||||
}
|
||||
if (node.name !== 'yield'
|
||||
&& node.name !== 'await'
|
||||
&& isKeywordRaw(node.name)) {
|
||||
this.raiseEarly('UnexpectedToken', token);
|
||||
}
|
||||
return this.finishNode(node, 'BindingIdentifier');
|
||||
}
|
||||
|
||||
// IdentifierReference :
|
||||
// Identifier
|
||||
// [~Yield] `yield`
|
||||
// [~Await] `await`
|
||||
parseIdentifierReference() {
|
||||
const node = this.startNode();
|
||||
const token = this.next();
|
||||
switch (token.type) {
|
||||
case Token.IDENTIFIER:
|
||||
node.name = token.value;
|
||||
break;
|
||||
case Token.ESCAPED_KEYWORD:
|
||||
node.name = token.value;
|
||||
break;
|
||||
case Token.YIELD:
|
||||
if (this.scope.hasYield()) {
|
||||
this.unexpected(token);
|
||||
}
|
||||
node.name = 'yield';
|
||||
break;
|
||||
case Token.AWAIT:
|
||||
if (this.scope.hasAwait()) {
|
||||
this.unexpected(token);
|
||||
}
|
||||
for (let i = 0; i < this.scope.arrowInfoStack.length; i += 1) {
|
||||
const arrowInfo = this.scope.arrowInfoStack[i];
|
||||
if (arrowInfo.isAsync) {
|
||||
arrowInfo.awaitIdentifiers.push(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
node.name = 'await';
|
||||
break;
|
||||
default:
|
||||
this.unexpected(token);
|
||||
}
|
||||
if (node.name === 'yield' && (this.scope.hasYield() || this.scope.isModule())) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (node.name === 'await' && (this.scope.hasAwait() || this.scope.isModule())) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (this.isStrictMode() && isReservedWordStrict(node.name)) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (node.name !== 'yield'
|
||||
&& node.name !== 'await'
|
||||
&& isKeywordRaw(node.name)) {
|
||||
this.raiseEarly('UnexpectedToken', token);
|
||||
}
|
||||
return this.finishNode(node, 'IdentifierReference');
|
||||
}
|
||||
|
||||
// LabelIdentifier :
|
||||
// Identifier
|
||||
// [~Yield] `yield`
|
||||
// [~Await] `await`
|
||||
parseLabelIdentifier() {
|
||||
const node = this.parseIdentifierReference();
|
||||
node.type = 'LabelIdentifier';
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { StatementParser } from './StatementParser.mjs';
|
||||
import { Token } from './tokens.mjs';
|
||||
|
||||
export class LanguageParser extends StatementParser {
|
||||
// Script : ScriptBody?
|
||||
parseScript() {
|
||||
if (this.feature('hashbang')) {
|
||||
this.skipHashbangComment();
|
||||
}
|
||||
const node = this.startNode();
|
||||
if (this.eat(Token.EOS)) {
|
||||
node.ScriptBody = null;
|
||||
} else {
|
||||
node.ScriptBody = this.parseScriptBody();
|
||||
}
|
||||
return this.finishNode(node, 'Script');
|
||||
}
|
||||
|
||||
// ScriptBody : StatementList
|
||||
parseScriptBody() {
|
||||
const node = this.startNode();
|
||||
this.scope.with({
|
||||
in: true,
|
||||
lexical: true,
|
||||
variable: true,
|
||||
variableFunctions: true,
|
||||
}, () => {
|
||||
const directives = [];
|
||||
node.StatementList = this.parseStatementList(Token.EOS, directives);
|
||||
node.strict = directives.includes('use strict');
|
||||
});
|
||||
return this.finishNode(node, 'ScriptBody');
|
||||
}
|
||||
|
||||
// Module : ModuleBody?
|
||||
parseModule() {
|
||||
if (this.feature('hashbang')) {
|
||||
this.skipHashbangComment();
|
||||
}
|
||||
return this.scope.with({
|
||||
module: true,
|
||||
strict: true,
|
||||
in: true,
|
||||
importMeta: true,
|
||||
await: this.feature('top-level-await'),
|
||||
lexical: true,
|
||||
variable: true,
|
||||
}, () => {
|
||||
const node = this.startNode();
|
||||
if (this.eat(Token.EOS)) {
|
||||
node.ModuleBody = null;
|
||||
} else {
|
||||
node.ModuleBody = this.parseModuleBody();
|
||||
}
|
||||
this.scope.undefinedExports.forEach((importNode, name) => {
|
||||
this.raiseEarly('ModuleUndefinedExport', importNode, name);
|
||||
});
|
||||
node.hasTopLevelAwait = this.state.hasTopLevelAwait;
|
||||
return this.finishNode(node, 'Module');
|
||||
});
|
||||
}
|
||||
|
||||
// ModuleBody :
|
||||
// ModuleItemList
|
||||
parseModuleBody() {
|
||||
const node = this.startNode();
|
||||
node.ModuleItemList = this.parseModuleItemList();
|
||||
return this.finishNode(node, 'ModuleBody');
|
||||
}
|
||||
|
||||
// ModuleItemList :
|
||||
// ModuleItem
|
||||
// ModuleItemList ModuleItem
|
||||
//
|
||||
// ModuleItem :
|
||||
// ImportDeclaration
|
||||
// ExportDeclaration
|
||||
// StatementListItem
|
||||
parseModuleItemList() {
|
||||
const moduleItemList = [];
|
||||
while (!this.eat(Token.EOS)) {
|
||||
switch (this.peek().type) {
|
||||
case Token.IMPORT:
|
||||
moduleItemList.push(this.parseImportDeclaration());
|
||||
break;
|
||||
case Token.EXPORT:
|
||||
moduleItemList.push(this.parseExportDeclaration());
|
||||
break;
|
||||
default:
|
||||
moduleItemList.push(this.parseStatementListItem());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return moduleItemList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
import isUnicodeIDStartRegex from 'unicode-13.0.0/Binary_Property/ID_Start/regex';
|
||||
import isUnicodeIDContinueRegex from 'unicode-13.0.0/Binary_Property/ID_Continue/regex';
|
||||
import isSpaceSeparatorRegex from 'unicode-13.0.0/General_Category/Space_Separator/regex';
|
||||
import { surroundingAgent } from '../engine.mjs';
|
||||
import { UTF16SurrogatePairToCodePoint } from '../static-semantics/all.mjs';
|
||||
import {
|
||||
RawTokens,
|
||||
Token,
|
||||
TokenNames,
|
||||
KeywordLookup,
|
||||
isKeywordRaw,
|
||||
} from './tokens.mjs';
|
||||
|
||||
const isUnicodeIDStart = (c) => c && isUnicodeIDStartRegex.test(c);
|
||||
const isUnicodeIDContinue = (c) => c && isUnicodeIDContinueRegex.test(c);
|
||||
export const isDecimalDigit = (c) => c && /\d/u.test(c);
|
||||
export const isHexDigit = (c) => c && /[\da-f]/ui.test(c);
|
||||
const isOctalDigit = (c) => c && /[0-7]/u.test(c);
|
||||
const isBinaryDigit = (c) => (c === '0' || c === '1');
|
||||
export const isWhitespace = (c) => c && (/[\u0009\u000B\u000C\u0020\u00A0\uFEFF]/u.test(c) || isSpaceSeparatorRegex.test(c)); // eslint-disable-line no-control-regex
|
||||
export const isLineTerminator = (c) => c && /[\r\n\u2028\u2029]/u.test(c);
|
||||
const isRegularExpressionFlagPart = (c) => c && (isUnicodeIDContinue(c) || c === '$');
|
||||
export const isIdentifierStart = (c) => SingleCharTokens[c] === Token.IDENTIFIER || isUnicodeIDStart(c);
|
||||
export const isIdentifierPart = (c) => SingleCharTokens[c] === Token.IDENTIFIER || c === '\u{200C}' || c === '\u{200D}' || isUnicodeIDContinue(c);
|
||||
export const isLeadingSurrogate = (cp) => cp >= 0xD800 && cp <= 0xDBFF;
|
||||
export const isTrailingSurrogate = (cp) => cp >= 0xDC00 && cp <= 0xDFFF;
|
||||
|
||||
const SingleCharTokens = {
|
||||
'__proto__': null,
|
||||
'0': Token.NUMBER,
|
||||
'1': Token.NUMBER,
|
||||
'2': Token.NUMBER,
|
||||
'3': Token.NUMBER,
|
||||
'4': Token.NUMBER,
|
||||
'5': Token.NUMBER,
|
||||
'6': Token.NUMBER,
|
||||
'7': Token.NUMBER,
|
||||
'8': Token.NUMBER,
|
||||
'9': Token.NUMBER,
|
||||
'a': Token.IDENTIFIER,
|
||||
'b': Token.IDENTIFIER,
|
||||
'c': Token.IDENTIFIER,
|
||||
'd': Token.IDENTIFIER,
|
||||
'e': Token.IDENTIFIER,
|
||||
'f': Token.IDENTIFIER,
|
||||
'g': Token.IDENTIFIER,
|
||||
'h': Token.IDENTIFIER,
|
||||
'i': Token.IDENTIFIER,
|
||||
'j': Token.IDENTIFIER,
|
||||
'k': Token.IDENTIFIER,
|
||||
'l': Token.IDENTIFIER,
|
||||
'm': Token.IDENTIFIER,
|
||||
'n': Token.IDENTIFIER,
|
||||
'o': Token.IDENTIFIER,
|
||||
'p': Token.IDENTIFIER,
|
||||
'q': Token.IDENTIFIER,
|
||||
'r': Token.IDENTIFIER,
|
||||
's': Token.IDENTIFIER,
|
||||
't': Token.IDENTIFIER,
|
||||
'u': Token.IDENTIFIER,
|
||||
'v': Token.IDENTIFIER,
|
||||
'w': Token.IDENTIFIER,
|
||||
'x': Token.IDENTIFIER,
|
||||
'y': Token.IDENTIFIER,
|
||||
'z': Token.IDENTIFIER,
|
||||
'A': Token.IDENTIFIER,
|
||||
'B': Token.IDENTIFIER,
|
||||
'C': Token.IDENTIFIER,
|
||||
'D': Token.IDENTIFIER,
|
||||
'E': Token.IDENTIFIER,
|
||||
'F': Token.IDENTIFIER,
|
||||
'G': Token.IDENTIFIER,
|
||||
'H': Token.IDENTIFIER,
|
||||
'I': Token.IDENTIFIER,
|
||||
'J': Token.IDENTIFIER,
|
||||
'K': Token.IDENTIFIER,
|
||||
'L': Token.IDENTIFIER,
|
||||
'M': Token.IDENTIFIER,
|
||||
'N': Token.IDENTIFIER,
|
||||
'O': Token.IDENTIFIER,
|
||||
'P': Token.IDENTIFIER,
|
||||
'Q': Token.IDENTIFIER,
|
||||
'R': Token.IDENTIFIER,
|
||||
'S': Token.IDENTIFIER,
|
||||
'T': Token.IDENTIFIER,
|
||||
'U': Token.IDENTIFIER,
|
||||
'V': Token.IDENTIFIER,
|
||||
'W': Token.IDENTIFIER,
|
||||
'X': Token.IDENTIFIER,
|
||||
'Y': Token.IDENTIFIER,
|
||||
'Z': Token.IDENTIFIER,
|
||||
'$': Token.IDENTIFIER,
|
||||
'_': Token.IDENTIFIER,
|
||||
'\\': Token.IDENTIFIER,
|
||||
'.': Token.PERIOD,
|
||||
',': Token.COMMA,
|
||||
':': Token.COLON,
|
||||
';': Token.SEMICOLON,
|
||||
'%': Token.MOD,
|
||||
'~': Token.BIT_NOT,
|
||||
'!': Token.NOT,
|
||||
'+': Token.ADD,
|
||||
'-': Token.SUB,
|
||||
'*': Token.MUL,
|
||||
'<': Token.LT,
|
||||
'>': Token.GT,
|
||||
'=': Token.ASSIGN,
|
||||
'?': Token.CONDITIONAL,
|
||||
'[': Token.LBRACK,
|
||||
']': Token.RBRACK,
|
||||
'(': Token.LPAREN,
|
||||
')': Token.RPAREN,
|
||||
'/': Token.DIV,
|
||||
'^': Token.BIT_XOR,
|
||||
'`': Token.TEMPLATE,
|
||||
'{': Token.LBRACE,
|
||||
'}': Token.RBRACE,
|
||||
'&': Token.BIT_AND,
|
||||
'|': Token.BIT_OR,
|
||||
'"': Token.STRING,
|
||||
'\'': Token.STRING,
|
||||
};
|
||||
|
||||
export class Lexer {
|
||||
constructor() {
|
||||
this.currentToken = undefined;
|
||||
this.peekToken = undefined;
|
||||
this.peekAheadToken = undefined;
|
||||
|
||||
this.position = 0;
|
||||
this.line = 1;
|
||||
this.columnOffset = 0;
|
||||
this.scannedValue = undefined;
|
||||
this.lineTerminatorBeforeNextToken = false;
|
||||
this.positionForNextToken = 0;
|
||||
this.lineForNextToken = 0;
|
||||
this.columnForNextToken = 0;
|
||||
}
|
||||
|
||||
advance() {
|
||||
this.lineTerminatorBeforeNextToken = false;
|
||||
const type = this.nextToken();
|
||||
return {
|
||||
type,
|
||||
startIndex: this.positionForNextToken,
|
||||
endIndex: this.position,
|
||||
line: this.lineForNextToken,
|
||||
column: this.columnForNextToken,
|
||||
hadLineTerminatorBefore: this.lineTerminatorBeforeNextToken,
|
||||
name: TokenNames[type],
|
||||
value: (
|
||||
type === Token.IDENTIFIER
|
||||
|| type === Token.NUMBER
|
||||
|| type === Token.BIGINT
|
||||
|| type === Token.STRING
|
||||
|| type === Token.ESCAPED_KEYWORD
|
||||
) ? this.scannedValue : RawTokens[type][1],
|
||||
};
|
||||
}
|
||||
|
||||
next() {
|
||||
this.currentToken = this.peekToken;
|
||||
if (this.peekAheadToken !== undefined) {
|
||||
this.peekToken = this.peekAheadToken;
|
||||
this.peekAheadToken = undefined;
|
||||
} else {
|
||||
this.peekToken = this.advance();
|
||||
}
|
||||
return this.currentToken;
|
||||
}
|
||||
|
||||
peek() {
|
||||
if (this.peekToken === undefined) {
|
||||
this.next();
|
||||
}
|
||||
return this.peekToken;
|
||||
}
|
||||
|
||||
peekAhead() {
|
||||
if (this.peekAheadToken === undefined) {
|
||||
this.peek();
|
||||
this.peekAheadToken = this.advance();
|
||||
}
|
||||
return this.peekAheadToken;
|
||||
}
|
||||
|
||||
matches(token, peek) {
|
||||
if (typeof token === 'string') {
|
||||
if (peek.type === Token.IDENTIFIER && peek.value === token) {
|
||||
const escapeIndex = this.source.slice(peek.startIndex, peek.endIndex).indexOf('\\');
|
||||
if (escapeIndex !== -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return peek.type === token;
|
||||
}
|
||||
|
||||
test(token) {
|
||||
return this.matches(token, this.peek());
|
||||
}
|
||||
|
||||
testAhead(token) {
|
||||
return this.matches(token, this.peekAhead());
|
||||
}
|
||||
|
||||
eat(token) {
|
||||
if (this.test(token)) {
|
||||
this.next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
expect(token) {
|
||||
if (this.test(token)) {
|
||||
return this.next();
|
||||
}
|
||||
return this.unexpected();
|
||||
}
|
||||
|
||||
skipSpace() {
|
||||
loop: // eslint-disable-line no-labels
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
this.position += 1;
|
||||
break;
|
||||
case '/':
|
||||
switch (this.source[this.position + 1]) {
|
||||
case '/':
|
||||
this.skipLineComment();
|
||||
break;
|
||||
case '*':
|
||||
this.skipBlockComment();
|
||||
break;
|
||||
default:
|
||||
break loop; // eslint-disable-line no-labels
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isWhitespace(c)) {
|
||||
this.position += 1;
|
||||
} else if (isLineTerminator(c)) {
|
||||
this.position += 1;
|
||||
if (c === '\r' && this.source[this.position] === '\n') {
|
||||
this.position += 1;
|
||||
}
|
||||
this.line += 1;
|
||||
this.columnOffset = this.position;
|
||||
this.lineTerminatorBeforeNextToken = true;
|
||||
break;
|
||||
} else {
|
||||
break loop; // eslint-disable-line no-labels
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipHashbangComment() {
|
||||
if (this.position === 0
|
||||
&& this.source[0] === '#'
|
||||
&& this.source[1] === '!') {
|
||||
this.skipLineComment();
|
||||
}
|
||||
}
|
||||
|
||||
skipLineComment() {
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
this.position += 1;
|
||||
if (isLineTerminator(c)) {
|
||||
if (c === '\r' && this.source[this.position] === '\n') {
|
||||
this.position += 1;
|
||||
}
|
||||
this.line += 1;
|
||||
this.columnOffset = this.position;
|
||||
this.lineTerminatorBeforeNextToken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipBlockComment() {
|
||||
const end = this.source.indexOf('*/', this.position + 2);
|
||||
if (end === -1) {
|
||||
this.raise('UnterminatedComment', this.position);
|
||||
}
|
||||
this.position += 2;
|
||||
for (const match of this.source.slice(this.position, end).matchAll(/\r\n?|[\n\u2028\u2029]/ug)) {
|
||||
this.position = match.index;
|
||||
this.line += 1;
|
||||
this.columnOffset = this.position;
|
||||
this.lineTerminatorBeforeNextToken = true;
|
||||
}
|
||||
this.position = end + 2;
|
||||
}
|
||||
|
||||
nextToken() {
|
||||
this.skipSpace();
|
||||
|
||||
// set token location info after skipping space
|
||||
this.positionForNextToken = this.position;
|
||||
this.lineForNextToken = this.line;
|
||||
this.columnForNextToken = this.position - this.columnOffset + 1;
|
||||
|
||||
if (this.position >= this.source.length) {
|
||||
return Token.EOS;
|
||||
}
|
||||
const c = this.source[this.position];
|
||||
this.position += 1;
|
||||
const c1 = this.source[this.position];
|
||||
if (c.charCodeAt(0) <= 127) {
|
||||
const single = SingleCharTokens[c];
|
||||
switch (single) {
|
||||
case Token.LPAREN:
|
||||
case Token.RPAREN:
|
||||
case Token.LBRACE:
|
||||
case Token.RBRACE:
|
||||
case Token.LBRACK:
|
||||
case Token.RBRACK:
|
||||
case Token.COLON:
|
||||
case Token.SEMICOLON:
|
||||
case Token.COMMA:
|
||||
case Token.BIT_NOT:
|
||||
case Token.TEMPLATE:
|
||||
return single;
|
||||
|
||||
case Token.CONDITIONAL:
|
||||
// ? ?. ?? ??=
|
||||
if (c1 === '.' && !isDecimalDigit(this.source[this.position + 1])) {
|
||||
this.position += 1;
|
||||
return Token.OPTIONAL;
|
||||
}
|
||||
if (c1 === '?') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_NULLISH;
|
||||
}
|
||||
return Token.NULLISH;
|
||||
}
|
||||
return Token.CONDITIONAL;
|
||||
|
||||
case Token.LT:
|
||||
// < <= << <<=
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.LTE;
|
||||
}
|
||||
if (c1 === '<') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_SHL;
|
||||
}
|
||||
return Token.SHL;
|
||||
}
|
||||
return Token.LT;
|
||||
|
||||
case Token.GT:
|
||||
// > >= >> >>= >>> >>>=
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.GTE;
|
||||
}
|
||||
if (c1 === '>') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '>') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_SHR;
|
||||
}
|
||||
return Token.SHR;
|
||||
}
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_SAR;
|
||||
}
|
||||
return Token.SAR;
|
||||
}
|
||||
return Token.GT;
|
||||
|
||||
case Token.ASSIGN:
|
||||
// = == === =>
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.EQ_STRICT;
|
||||
}
|
||||
return Token.EQ;
|
||||
}
|
||||
if (c1 === '>') {
|
||||
this.position += 1;
|
||||
return Token.ARROW;
|
||||
}
|
||||
return Token.ASSIGN;
|
||||
|
||||
case Token.NOT:
|
||||
// ! != !==
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.NE_STRICT;
|
||||
}
|
||||
return Token.NE;
|
||||
}
|
||||
return Token.NOT;
|
||||
|
||||
case Token.ADD:
|
||||
// + ++ +=
|
||||
if (c1 === '+') {
|
||||
this.position += 1;
|
||||
return Token.INC;
|
||||
}
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_ADD;
|
||||
}
|
||||
return Token.ADD;
|
||||
|
||||
case Token.SUB:
|
||||
// - -- -=
|
||||
if (c1 === '-') {
|
||||
this.position += 1;
|
||||
return Token.DEC;
|
||||
}
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_SUB;
|
||||
}
|
||||
return Token.SUB;
|
||||
|
||||
case Token.MUL:
|
||||
// * *= ** **=
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_MUL;
|
||||
}
|
||||
if (c1 === '*') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_EXP;
|
||||
}
|
||||
return Token.EXP;
|
||||
}
|
||||
return Token.MUL;
|
||||
|
||||
case Token.MOD:
|
||||
// % %=
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_MOD;
|
||||
}
|
||||
return Token.MOD;
|
||||
|
||||
case Token.DIV:
|
||||
// / /=
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_DIV;
|
||||
}
|
||||
return Token.DIV;
|
||||
|
||||
case Token.BIT_AND:
|
||||
// & && &= &&=
|
||||
if (c1 === '&') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_AND;
|
||||
}
|
||||
return Token.AND;
|
||||
}
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_BIT_AND;
|
||||
}
|
||||
return Token.BIT_AND;
|
||||
|
||||
case Token.BIT_OR:
|
||||
// | || |=
|
||||
if (c1 === '|') {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_OR;
|
||||
}
|
||||
return Token.OR;
|
||||
}
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_BIT_OR;
|
||||
}
|
||||
return Token.BIT_OR;
|
||||
|
||||
case Token.BIT_XOR:
|
||||
// ^ ^=
|
||||
if (c1 === '=') {
|
||||
this.position += 1;
|
||||
return Token.ASSIGN_BIT_XOR;
|
||||
}
|
||||
return Token.BIT_XOR;
|
||||
|
||||
case Token.PERIOD:
|
||||
// . ... NUMBER
|
||||
if (isDecimalDigit(c1)) {
|
||||
this.position -= 1;
|
||||
return this.scanNumber();
|
||||
}
|
||||
if (c1 === '.') {
|
||||
if (this.source[this.position + 1] === '.') {
|
||||
this.position += 2;
|
||||
return Token.ELLIPSIS;
|
||||
}
|
||||
}
|
||||
return Token.PERIOD;
|
||||
|
||||
case Token.STRING:
|
||||
return this.scanString(c);
|
||||
|
||||
case Token.NUMBER:
|
||||
this.position -= 1;
|
||||
return this.scanNumber();
|
||||
|
||||
case Token.IDENTIFIER:
|
||||
this.position -= 1;
|
||||
return this.scanIdentifierOrKeyword();
|
||||
|
||||
default:
|
||||
this.unexpected(single);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.position -= 1;
|
||||
|
||||
if (isLeadingSurrogate(c.charCodeAt(0)) || isIdentifierStart(c)) {
|
||||
return this.scanIdentifierOrKeyword();
|
||||
}
|
||||
|
||||
return this.unexpected(this.position);
|
||||
}
|
||||
|
||||
scanNumber() {
|
||||
const separators = surroundingAgent.feature('numeric-separators');
|
||||
const start = this.position;
|
||||
let base = 10;
|
||||
let check = isDecimalDigit;
|
||||
if (this.source[this.position] === '0') {
|
||||
this.scannedValue = 0;
|
||||
this.position += 1;
|
||||
switch (this.source[this.position]) {
|
||||
case 'x':
|
||||
case 'X':
|
||||
base = 16;
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
base = 8;
|
||||
break;
|
||||
case 'b':
|
||||
case 'B':
|
||||
base = 2;
|
||||
break;
|
||||
case '.':
|
||||
case 'e':
|
||||
case 'E':
|
||||
break;
|
||||
case 'n':
|
||||
this.position += 1;
|
||||
this.scannedValue = 0n;
|
||||
return Token.BIGINT;
|
||||
default:
|
||||
return Token.NUMBER;
|
||||
}
|
||||
check = {
|
||||
16: isHexDigit,
|
||||
10: isDecimalDigit,
|
||||
8: isOctalDigit,
|
||||
2: isBinaryDigit,
|
||||
}[base];
|
||||
if (base !== 10) {
|
||||
if (!check(this.source[this.position + 1])) {
|
||||
return Token.NUMBER;
|
||||
}
|
||||
this.position += 1;
|
||||
}
|
||||
}
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
if (check(c) || (separators && c === '_')) {
|
||||
this.position += 1;
|
||||
if (separators && c === '_') {
|
||||
if (!check(this.source[this.position])) {
|
||||
this.unexpected(this.position);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.source[this.position] === 'n') {
|
||||
const buffer = this.source.slice(start, this.position).replace(/_/g, '');
|
||||
this.position += 1;
|
||||
this.scannedValue = BigInt(buffer);
|
||||
return Token.BIGINT;
|
||||
}
|
||||
if (base === 10 && this.source[this.position] === '.') {
|
||||
this.position += 1;
|
||||
if (separators && this.source[this.position] === '_') {
|
||||
this.unexpected(this.position);
|
||||
}
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
if (isDecimalDigit(c) || (separators && c === '_')) {
|
||||
this.position += 1;
|
||||
if (separators && c === '_') {
|
||||
if (!isDecimalDigit(this.source[this.position])) {
|
||||
this.unexpected(this.position);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (base === 10 && (this.source[this.position] === 'E' || this.source[this.position] === 'e')) {
|
||||
this.position += 1;
|
||||
if (this.source[this.position] === '-' || this.source[this.position] === '+') {
|
||||
this.position += 1;
|
||||
}
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
if (isDecimalDigit(c) || (separators && c === '_')) {
|
||||
this.position += 1;
|
||||
if (separators && c === '_') {
|
||||
if (!isDecimalDigit(this.source[this.position])) {
|
||||
this.unexpected(this.position);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isIdentifierStart(this.source[this.position])) {
|
||||
this.unexpected(this.position);
|
||||
}
|
||||
const buffer = this.source
|
||||
.slice(base === 10 ? start : start + 2, this.position)
|
||||
.replace(/_/g, '');
|
||||
this.scannedValue = base === 10
|
||||
? Number.parseFloat(buffer, base)
|
||||
: Number.parseInt(buffer, base);
|
||||
return Token.NUMBER;
|
||||
}
|
||||
|
||||
scanString(char) {
|
||||
let buffer = '';
|
||||
while (true) {
|
||||
if (this.position >= this.source.length) {
|
||||
this.raise('UnterminatedString', this.position);
|
||||
}
|
||||
const c = this.source[this.position];
|
||||
if (c === char) {
|
||||
this.position += 1;
|
||||
break;
|
||||
}
|
||||
if (c === '\r' || c === '\n') {
|
||||
this.raise('UnterminatedString', this.position);
|
||||
}
|
||||
this.position += 1;
|
||||
if (c === '\\') {
|
||||
const l = this.source[this.position];
|
||||
if (isLineTerminator(l)) {
|
||||
this.position += 1;
|
||||
if (l === '\r' && this.source[this.position] === '\n') {
|
||||
this.position += 1;
|
||||
}
|
||||
this.line += 1;
|
||||
this.columnOffset = this.position;
|
||||
this.lineTerminatorBeforeNextToken = true;
|
||||
} else {
|
||||
buffer += this.scanEscapeSequence();
|
||||
}
|
||||
} else {
|
||||
buffer += c;
|
||||
}
|
||||
}
|
||||
this.scannedValue = buffer;
|
||||
return Token.STRING;
|
||||
}
|
||||
|
||||
scanEscapeSequence() {
|
||||
const c = this.source[this.position];
|
||||
switch (c) {
|
||||
case 'b':
|
||||
this.position += 1;
|
||||
return '\b';
|
||||
case 't':
|
||||
this.position += 1;
|
||||
return '\t';
|
||||
case 'n':
|
||||
this.position += 1;
|
||||
return '\n';
|
||||
case 'v':
|
||||
this.position += 1;
|
||||
return '\v';
|
||||
case 'f':
|
||||
this.position += 1;
|
||||
return '\f';
|
||||
case 'r':
|
||||
this.position += 1;
|
||||
return '\r';
|
||||
case 'x':
|
||||
this.position += 1;
|
||||
return String.fromCodePoint(this.scanHex(2));
|
||||
case 'u':
|
||||
this.position += 1;
|
||||
return String.fromCodePoint(this.scanCodePoint());
|
||||
default:
|
||||
if (c === '0' && !isDecimalDigit(this.source[this.position + 1])) {
|
||||
this.position += 1;
|
||||
return '\u{0000}';
|
||||
} else if (this.isStrictMode() && isDecimalDigit(c)) {
|
||||
this.raise('IllegalOctalEscape', this.position);
|
||||
}
|
||||
this.position += 1;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
scanCodePoint() {
|
||||
if (this.source[this.position] === '{') {
|
||||
const end = this.source.indexOf('}', this.position);
|
||||
this.position += 1;
|
||||
const code = this.scanHex(end - this.position);
|
||||
this.position += 1;
|
||||
if (code > 0x10FFFF) {
|
||||
this.raise('InvalidCodePoint', this.position);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
return this.scanHex(4);
|
||||
}
|
||||
|
||||
scanHex(length) {
|
||||
if (length === 0) {
|
||||
this.raise('InvalidCodePoint', this.position);
|
||||
}
|
||||
let n = 0;
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
const c = this.source[this.position];
|
||||
if (isHexDigit(c)) {
|
||||
this.position += 1;
|
||||
n = (n << 4) | Number.parseInt(c, 16);
|
||||
} else {
|
||||
this.unexpected(this.position);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
scanIdentifierOrKeyword() {
|
||||
let buffer = '';
|
||||
let escapeIndex = -1;
|
||||
let check = isIdentifierStart;
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
const code = c.charCodeAt(0);
|
||||
if (c === '\\') {
|
||||
if (escapeIndex === -1) {
|
||||
escapeIndex = this.position;
|
||||
}
|
||||
this.position += 1;
|
||||
if (this.source[this.position] !== 'u') {
|
||||
this.raise('InvalidUnicodeEscape', this.position);
|
||||
}
|
||||
this.position += 1;
|
||||
const raw = String.fromCodePoint(this.scanCodePoint());
|
||||
if (!check(raw)) {
|
||||
this.raise('InvalidUnicodeEscape', this.position);
|
||||
}
|
||||
buffer += raw;
|
||||
} else if (isLeadingSurrogate(code)) {
|
||||
const lowSurrogate = this.source.charCodeAt(this.position + 1);
|
||||
if (!isTrailingSurrogate(lowSurrogate)) {
|
||||
this.raise('InvalidUnicodeEscape', this.position);
|
||||
}
|
||||
const codePoint = UTF16SurrogatePairToCodePoint(code, lowSurrogate);
|
||||
const raw = String.fromCodePoint(codePoint);
|
||||
if (!check(raw)) {
|
||||
this.raise('InvalidUnicodeEscape', this.position);
|
||||
}
|
||||
this.position += 2;
|
||||
buffer += raw;
|
||||
} else if (check(c)) {
|
||||
buffer += c;
|
||||
this.position += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
check = isIdentifierPart;
|
||||
}
|
||||
if (isKeywordRaw(buffer)) {
|
||||
if (escapeIndex !== -1) {
|
||||
this.scannedValue = buffer;
|
||||
return Token.ESCAPED_KEYWORD;
|
||||
}
|
||||
return KeywordLookup[buffer];
|
||||
} else {
|
||||
this.scannedValue = buffer;
|
||||
return Token.IDENTIFIER;
|
||||
}
|
||||
}
|
||||
|
||||
scanRegularExpressionBody() {
|
||||
let inClass = false;
|
||||
let buffer = this.peek().type === Token.ASSIGN_DIV ? '=' : '';
|
||||
while (true) {
|
||||
if (this.position >= this.source.length) {
|
||||
this.raise('UnterminatedRegExp', this.position);
|
||||
}
|
||||
const c = this.source[this.position];
|
||||
switch (c) {
|
||||
case '[':
|
||||
inClass = true;
|
||||
this.position += 1;
|
||||
buffer += c;
|
||||
break;
|
||||
case ']':
|
||||
if (inClass) {
|
||||
inClass = false;
|
||||
}
|
||||
buffer += c;
|
||||
this.position += 1;
|
||||
break;
|
||||
case '/':
|
||||
this.position += 1;
|
||||
if (!inClass) {
|
||||
this.scannedValue = buffer;
|
||||
return;
|
||||
}
|
||||
buffer += c;
|
||||
break;
|
||||
case '\\':
|
||||
buffer += c;
|
||||
this.position += 1;
|
||||
if (isLineTerminator(this.source[this.position])) {
|
||||
this.raise('UnterminatedRegExp', this.position);
|
||||
}
|
||||
buffer += this.source[this.position];
|
||||
this.position += 1;
|
||||
break;
|
||||
default:
|
||||
if (isLineTerminator(c)) {
|
||||
this.raise('UnterminatedRegExp', this.position);
|
||||
}
|
||||
this.position += 1;
|
||||
buffer += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanRegularExpressionFlags() {
|
||||
let buffer = '';
|
||||
while (true) {
|
||||
if (this.position >= this.source.length) {
|
||||
this.scannedValue = buffer;
|
||||
return;
|
||||
}
|
||||
const c = this.source[this.position];
|
||||
if (isRegularExpressionFlagPart(c)
|
||||
&& 'gimsuy'.includes(c)
|
||||
&& !buffer.includes(c)) {
|
||||
this.position += 1;
|
||||
buffer += c;
|
||||
} else {
|
||||
this.scannedValue = buffer;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { surroundingAgent } from '../engine.mjs';
|
||||
import * as messages from '../messages.mjs';
|
||||
import { LanguageParser } from './LanguageParser.mjs';
|
||||
import { Token } from './tokens.mjs';
|
||||
import { Scope } from './Scope.mjs';
|
||||
import { isLineTerminator } from './Lexer.mjs';
|
||||
|
||||
export class Parser extends LanguageParser {
|
||||
constructor({ source, specifier }) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.specifier = specifier;
|
||||
this.earlyErrors = new Set();
|
||||
this.state = {
|
||||
hasTopLevelAwait: false,
|
||||
strict: false,
|
||||
};
|
||||
this.scope = new Scope(this);
|
||||
}
|
||||
|
||||
isStrictMode() {
|
||||
return this.state.strict;
|
||||
}
|
||||
|
||||
feature(name) {
|
||||
// eslint-disable-next-line engine262/valid-feature
|
||||
return surroundingAgent.feature(name);
|
||||
}
|
||||
|
||||
startNode(inheritStart = undefined) {
|
||||
this.peek();
|
||||
const node = {
|
||||
type: undefined,
|
||||
location: {
|
||||
startIndex: inheritStart ? inheritStart.location.startIndex : this.peekToken.startIndex,
|
||||
endIndex: -1,
|
||||
start: inheritStart ? { ...inheritStart.location.start } : {
|
||||
line: this.peekToken.line,
|
||||
column: this.peekToken.column,
|
||||
},
|
||||
end: {
|
||||
line: -1,
|
||||
column: -1,
|
||||
},
|
||||
},
|
||||
strict: this.state.strict,
|
||||
sourceText: () => this.source.slice(node.location.startIndex, node.location.endIndex),
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
finishNode(node, type) {
|
||||
node.type = type;
|
||||
node.location.endIndex = this.currentToken.endIndex;
|
||||
node.location.end.line = this.currentToken.line;
|
||||
node.location.end.column = this.currentToken.column;
|
||||
return node;
|
||||
}
|
||||
|
||||
createSyntaxError(context = this.peek(), template, templateArgs) {
|
||||
if (template === 'UnexpectedToken' && context.type === Token.EOS) {
|
||||
template = 'UnexpectedEOS';
|
||||
}
|
||||
|
||||
let startIndex;
|
||||
let endIndex;
|
||||
let line;
|
||||
let column;
|
||||
if (typeof context === 'number') {
|
||||
line = this.line;
|
||||
if (context === this.source.length) {
|
||||
while (isLineTerminator(this.source[context - 1])) {
|
||||
line -= 1;
|
||||
context -= 1;
|
||||
}
|
||||
}
|
||||
startIndex = context;
|
||||
endIndex = context + 1;
|
||||
} else if (context.type === Token.EOS) {
|
||||
line = this.line;
|
||||
startIndex = context.startIndex;
|
||||
while (isLineTerminator(this.source[startIndex - 1])) {
|
||||
line -= 1;
|
||||
startIndex -= 1;
|
||||
}
|
||||
endIndex = startIndex + 1;
|
||||
} else {
|
||||
if (context.location) {
|
||||
context = context.location;
|
||||
}
|
||||
({
|
||||
startIndex,
|
||||
endIndex,
|
||||
start: {
|
||||
line,
|
||||
column,
|
||||
} = context,
|
||||
} = context);
|
||||
}
|
||||
|
||||
/*
|
||||
* Source looks like:
|
||||
*
|
||||
* const a = 1;
|
||||
* const b 'string string string'; // a string
|
||||
* const c = 3; | |
|
||||
* | | | |
|
||||
* | | startIndex | endIndex |
|
||||
* | lineStart | lineEnd
|
||||
*
|
||||
* Exception looks like:
|
||||
*
|
||||
* const b 'string string string'; // a string
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^
|
||||
* SyntaxError: unexpected token
|
||||
*/
|
||||
|
||||
let lineStart = startIndex;
|
||||
while (!isLineTerminator(this.source[lineStart - 1]) && this.source[lineStart - 1] !== undefined) {
|
||||
lineStart -= 1;
|
||||
}
|
||||
|
||||
let lineEnd = startIndex;
|
||||
while (!isLineTerminator(this.source[lineEnd]) && this.source[lineEnd] !== undefined) {
|
||||
lineEnd += 1;
|
||||
}
|
||||
|
||||
if (column === undefined) {
|
||||
column = startIndex - lineStart + 1;
|
||||
}
|
||||
|
||||
const e = new SyntaxError(messages[template](...templateArgs));
|
||||
e.decoration = `\
|
||||
${this.specifier ? `${this.specifier}:${line}:${column}\n` : ''}${this.source.slice(lineStart, lineEnd)}
|
||||
${' '.repeat(startIndex - lineStart)}${'^'.repeat(Math.max(endIndex - startIndex, 1))}`;
|
||||
return e;
|
||||
}
|
||||
|
||||
raiseEarly(template, context, ...templateArgs) {
|
||||
const e = this.createSyntaxError(context, template, templateArgs);
|
||||
this.earlyErrors.add(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
raise(template, context, ...templateArgs) {
|
||||
const e = this.createSyntaxError(context, template, templateArgs);
|
||||
throw e;
|
||||
}
|
||||
|
||||
unexpected(...args) {
|
||||
return this.raise('UnexpectedToken', ...args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,911 @@
|
||||
import {
|
||||
BinaryUnicodeProperties,
|
||||
NonbinaryUnicodeProperties,
|
||||
UnicodeGeneralCategoryValues,
|
||||
UnicodeScriptValues,
|
||||
} from '../runtime-semantics/all.mjs';
|
||||
import {
|
||||
CharacterValue,
|
||||
UTF16SurrogatePairToCodePoint,
|
||||
} from '../static-semantics/all.mjs';
|
||||
import {
|
||||
isIdentifierStart,
|
||||
isIdentifierPart,
|
||||
isLeadingSurrogate,
|
||||
isTrailingSurrogate,
|
||||
isHexDigit,
|
||||
} from './Lexer.mjs';
|
||||
|
||||
const isSyntaxCharacter = (c) => '^$\\.*+?()[]{}|'.includes(c);
|
||||
const isClosingSyntaxCharacter = (c) => ')]}|'.includes(c);
|
||||
const isDecimalDigit = (c) => /[0123456789]/u.test(c);
|
||||
const isControlLetter = (c) => /[a-zA-Z]/u.test(c);
|
||||
|
||||
const PLUS_U = 1 << 0;
|
||||
const PLUS_N = 1 << 1;
|
||||
|
||||
export class RegExpParser {
|
||||
constructor(source) {
|
||||
this.source = source;
|
||||
this.position = 0;
|
||||
this.capturingGroups = [];
|
||||
this.groupSpecifiers = new Map();
|
||||
this.decimalEscapes = [];
|
||||
this.groupNameRefs = [];
|
||||
this.state = 0;
|
||||
}
|
||||
|
||||
scope(flags, f) {
|
||||
const oldState = this.state;
|
||||
|
||||
if (flags.U === true) {
|
||||
this.state |= PLUS_U;
|
||||
} else if (flags.U === false) {
|
||||
this.state &= ~PLUS_U;
|
||||
}
|
||||
|
||||
if (flags.N === true) {
|
||||
this.state |= PLUS_N;
|
||||
} else if (flags.N === false) {
|
||||
this.state &= ~PLUS_N;
|
||||
}
|
||||
|
||||
const r = f();
|
||||
|
||||
this.state = oldState;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
get plusU() {
|
||||
return (this.state & PLUS_U) > 0;
|
||||
}
|
||||
|
||||
get plusN() {
|
||||
return (this.state & PLUS_N) > 0;
|
||||
}
|
||||
|
||||
raise(message, position = this.position) {
|
||||
const e = new SyntaxError(message);
|
||||
e.position = position;
|
||||
throw e;
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.source[this.position];
|
||||
}
|
||||
|
||||
test(c) {
|
||||
return this.source[this.position] === c;
|
||||
}
|
||||
|
||||
eat(c) {
|
||||
if (this.test(c)) {
|
||||
this.next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
next() {
|
||||
const c = this.source[this.position];
|
||||
this.position += 1;
|
||||
return c;
|
||||
}
|
||||
|
||||
expect(c) {
|
||||
if (!this.eat(c)) {
|
||||
this.raise(`Expected ${c} but got ${this.peek()}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern ::
|
||||
// Disjunction
|
||||
parsePattern() {
|
||||
const node = {
|
||||
type: 'Pattern',
|
||||
groupSpecifiers: this.groupSpecifiers,
|
||||
capturingGroups: this.capturingGroups,
|
||||
Disjunction: undefined,
|
||||
};
|
||||
node.Disjunction = this.parseDisjunction();
|
||||
if (this.position < this.source.length) {
|
||||
this.raise('Unexpected token');
|
||||
}
|
||||
this.decimalEscapes.forEach((d) => {
|
||||
if (d.value > node.capturingGroups.length) {
|
||||
this.raise('Invalid decimal escape', d.position);
|
||||
}
|
||||
});
|
||||
this.groupNameRefs.forEach((g) => {
|
||||
if (!node.groupSpecifiers.has(g.GroupName)) {
|
||||
this.raise('Invalid group name', g.position);
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
// Disjunction ::
|
||||
// Alternative
|
||||
// Alternative `|` Disjunction
|
||||
parseDisjunction() {
|
||||
const node = {
|
||||
type: 'Disjunction',
|
||||
Alternative: undefined,
|
||||
Disjunction: undefined,
|
||||
};
|
||||
node.Alternative = this.parseAlternative();
|
||||
if (this.eat('|')) {
|
||||
node.Disjunction = this.parseDisjunction();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
// Alternative ::
|
||||
// [empty]
|
||||
// Term Alternative
|
||||
parseAlternative() {
|
||||
let node = {
|
||||
type: 'Alternative',
|
||||
Term: undefined,
|
||||
Alternative: undefined,
|
||||
};
|
||||
while (this.position < this.source.length
|
||||
&& !isClosingSyntaxCharacter(this.peek())) {
|
||||
node = {
|
||||
type: 'Alternative',
|
||||
Term: this.parseTerm(),
|
||||
Alternative: node,
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Term ::
|
||||
// Assertion
|
||||
// Atom
|
||||
// Atom Quantifier
|
||||
parseTerm() {
|
||||
const assertion = this.maybeParseAssertion();
|
||||
if (assertion) {
|
||||
return assertion;
|
||||
}
|
||||
return {
|
||||
type: 'Term',
|
||||
capturingParenthesesBefore: this.capturingGroups.length,
|
||||
Atom: this.parseAtom(),
|
||||
Quantifier: this.maybeParseQuantifier(),
|
||||
};
|
||||
}
|
||||
|
||||
// Assertion ::
|
||||
// `^`
|
||||
// `$`
|
||||
// `\` `b`
|
||||
// `\` `B`
|
||||
// `(` `?` `=` Disjunction `)`
|
||||
// `(` `?` `!` Disjunction `)`
|
||||
// `(` `?` `<=` Disjunction `)`
|
||||
// `(` `?` `<!` Disjunction `)`
|
||||
maybeParseAssertion() {
|
||||
if (this.eat('^')) {
|
||||
return { type: 'Assertion', subtype: '^' };
|
||||
}
|
||||
if (this.eat('$')) {
|
||||
return { type: 'Assertion', subtype: '$' };
|
||||
}
|
||||
|
||||
const look2 = this.source.slice(this.position, this.position + 2);
|
||||
if (look2 === '\\b') {
|
||||
this.position += 2;
|
||||
return { type: 'Assertion', subtype: 'b' };
|
||||
}
|
||||
if (look2 === '\\B') {
|
||||
this.position += 2;
|
||||
return { type: 'Assertion', subtype: 'B' };
|
||||
}
|
||||
|
||||
const look3 = this.source.slice(this.position, this.position + 3);
|
||||
if (look3 === '(?=') {
|
||||
this.position += 3;
|
||||
const d = this.parseDisjunction();
|
||||
this.expect(')');
|
||||
return {
|
||||
type: 'Assertion',
|
||||
subtype: '?=',
|
||||
Disjunction: d,
|
||||
};
|
||||
}
|
||||
if (look3 === '(?!') {
|
||||
this.position += 3;
|
||||
const d = this.parseDisjunction();
|
||||
this.expect(')');
|
||||
return {
|
||||
type: 'Assertion',
|
||||
subtype: '?!',
|
||||
Disjunction: d,
|
||||
};
|
||||
}
|
||||
|
||||
const look4 = this.source.slice(this.position, this.position + 4);
|
||||
if (look4 === '(?<=') {
|
||||
this.position += 4;
|
||||
const d = this.parseDisjunction();
|
||||
this.expect(')');
|
||||
return {
|
||||
type: 'Assertion',
|
||||
subtype: '?<=',
|
||||
Disjunction: d,
|
||||
};
|
||||
}
|
||||
if (look4 === '(?<!') {
|
||||
this.position += 4;
|
||||
const d = this.parseDisjunction();
|
||||
this.expect(')');
|
||||
return {
|
||||
type: 'Assertion',
|
||||
subtype: '?<!',
|
||||
Disjunction: d,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Quantifier ::
|
||||
// QuantifierPrefix
|
||||
// QuantifierPrefix `?`
|
||||
// QuantifierPrefix ::
|
||||
// `*`
|
||||
// `+`
|
||||
// `?`
|
||||
// `{` DecimalDigits `}`
|
||||
// `{` DecimalDigits `,` `}`
|
||||
// `{` DecimalDigits `,` DecimalDigits `}`
|
||||
maybeParseQuantifier() {
|
||||
let QuantifierPrefix;
|
||||
|
||||
if (this.eat('*')) {
|
||||
QuantifierPrefix = '*';
|
||||
} else if (this.eat('+')) {
|
||||
QuantifierPrefix = '+';
|
||||
} else if (this.eat('?')) {
|
||||
QuantifierPrefix = '?';
|
||||
} else if (this.eat('{')) {
|
||||
QuantifierPrefix = {
|
||||
DecimalDigits_a: undefined,
|
||||
DecimalDigits_b: undefined,
|
||||
};
|
||||
QuantifierPrefix.DecimalDigits_a = Number.parseInt(this.parseDecimalDigits(), 10);
|
||||
if (this.eat(',')) {
|
||||
if (this.test('}')) {
|
||||
QuantifierPrefix.DecimalDigits_b = Infinity;
|
||||
} else {
|
||||
QuantifierPrefix.DecimalDigits_b = Number.parseInt(this.parseDecimalDigits(), 10);
|
||||
}
|
||||
if (QuantifierPrefix.DecimalDigits_a > QuantifierPrefix.DecimalDigits_b) {
|
||||
this.raise('Numbers out of order in quantifier');
|
||||
}
|
||||
}
|
||||
this.expect('}');
|
||||
}
|
||||
|
||||
if (QuantifierPrefix) {
|
||||
return {
|
||||
type: 'Quantifier',
|
||||
QuantifierPrefix,
|
||||
greedy: !this.eat('?'),
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Atom ::
|
||||
// PatternCharacter
|
||||
// `.`
|
||||
// `\` AtomEscape
|
||||
// CharacterClass
|
||||
// `(` GroupSpecifier Disjunction `)`
|
||||
// `(` `?` `:` Disjunction `)`
|
||||
parseAtom() {
|
||||
if (this.eat('.')) {
|
||||
return { type: 'Atom', subtype: '.', enclosedCapturingParentheses: 0 };
|
||||
}
|
||||
if (this.eat('\\')) {
|
||||
return this.parseAtomEscape();
|
||||
}
|
||||
if (this.eat('(')) {
|
||||
const node = {
|
||||
type: 'Atom',
|
||||
capturingParenthesesBefore: this.capturingGroups.length,
|
||||
enclosedCapturingParentheses: 0,
|
||||
capturing: true,
|
||||
GroupSpecifier: undefined,
|
||||
Disjunction: undefined,
|
||||
};
|
||||
if (this.eat('?')) {
|
||||
if (this.eat(':')) {
|
||||
node.capturing = false;
|
||||
} else {
|
||||
node.GroupSpecifier = this.parseGroupName();
|
||||
}
|
||||
}
|
||||
if (node.capturing) {
|
||||
this.capturingGroups.push(node);
|
||||
}
|
||||
if (node.GroupSpecifier) {
|
||||
if (this.groupSpecifiers.has(node.GroupSpecifier)) {
|
||||
this.raise(`Duplicate group specifier '${node.GroupSpecifier}'`);
|
||||
}
|
||||
this.groupSpecifiers.set(node.GroupSpecifier, node.capturingParenthesesBefore);
|
||||
}
|
||||
node.Disjunction = this.parseDisjunction();
|
||||
this.expect(')');
|
||||
node.enclosedCapturingParentheses = this.capturingGroups.length - node.capturingParenthesesBefore - 1;
|
||||
return node;
|
||||
}
|
||||
if (this.test('[')) {
|
||||
return {
|
||||
type: 'Atom',
|
||||
CharacterClass: this.parseCharacterClass(),
|
||||
};
|
||||
}
|
||||
if (isSyntaxCharacter(this.peek())) {
|
||||
this.raise(`Expected a PatternCharacter but got ${this.peek()}`);
|
||||
}
|
||||
return {
|
||||
type: 'Atom',
|
||||
PatternCharacter: this.parseSourceCharacter(),
|
||||
};
|
||||
}
|
||||
|
||||
// AtomEscape ::
|
||||
// DecimalEscape
|
||||
// CharacterClassEscape
|
||||
// CharacterEscape
|
||||
// [+N] `k` GroupName
|
||||
parseAtomEscape() {
|
||||
if (this.plusN && this.eat('k')) {
|
||||
const node = {
|
||||
type: 'AtomEscape',
|
||||
position: this.position,
|
||||
GroupName: this.parseGroupName(),
|
||||
};
|
||||
this.groupNameRefs.push(node);
|
||||
return node;
|
||||
}
|
||||
const CharacterClassEscape = this.maybeParseCharacterClassEscape();
|
||||
if (CharacterClassEscape) {
|
||||
return {
|
||||
type: 'AtomEscape',
|
||||
CharacterClassEscape,
|
||||
};
|
||||
}
|
||||
const DecimalEscape = this.maybeParseDecimalEscape();
|
||||
if (DecimalEscape) {
|
||||
return {
|
||||
type: 'AtomEscape',
|
||||
DecimalEscape,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'AtomEscape',
|
||||
CharacterEscape: this.parseCharacterEscape(),
|
||||
};
|
||||
}
|
||||
|
||||
// CharacterEscape ::
|
||||
// ControlEscape
|
||||
// `c` ControlLetter
|
||||
// `0` [lookahead ∉ DecimalDigit]
|
||||
// HexEscapeSequence
|
||||
// RegExpUnicodeEscapeSequence
|
||||
// IdentityEscape
|
||||
//
|
||||
// IdentityEscape ::
|
||||
// [+U] SyntaxCharacter
|
||||
// [+U] `/`
|
||||
// [~U] SourceCharacter but not UnicodeIDContinue
|
||||
parseCharacterEscape() {
|
||||
switch (this.peek()) {
|
||||
case 'f':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
case 'v':
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
ControlEscape: this.next(),
|
||||
};
|
||||
case 'c': {
|
||||
this.next();
|
||||
const c = this.next();
|
||||
if (c === undefined) {
|
||||
if (this.plusU) {
|
||||
this.raise('Invalid identity escape');
|
||||
}
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
IdentityEscape: 'c',
|
||||
};
|
||||
}
|
||||
const p = c.codePointAt(0);
|
||||
if ((p >= 65 && p <= 90) || (p >= 97 && p <= 122)) {
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
ControlLetter: c,
|
||||
};
|
||||
}
|
||||
if (this.plusU) {
|
||||
this.raise('Invalid identity escape');
|
||||
}
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
IdentityEscape: c,
|
||||
};
|
||||
}
|
||||
case 'x':
|
||||
if (isHexDigit(this.source[this.position + 1]) && isHexDigit(this.source[this.position + 2])) {
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
HexEscapeSequence: this.parseHexEscapeSequence(),
|
||||
};
|
||||
}
|
||||
if (this.plusU) {
|
||||
this.raise('Invalid identity escape');
|
||||
}
|
||||
this.next();
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
IdentityEscape: 'x',
|
||||
};
|
||||
case 'u': {
|
||||
const RegExpUnicodeEscapeSequence = this.maybeParseRegExpUnicodeEscapeSequence();
|
||||
if (RegExpUnicodeEscapeSequence) {
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
RegExpUnicodeEscapeSequence,
|
||||
};
|
||||
}
|
||||
if (this.plusU) {
|
||||
this.raise('Invalid identity escape');
|
||||
}
|
||||
this.next();
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
IdentityEscape: 'u',
|
||||
};
|
||||
}
|
||||
default: {
|
||||
const c = this.next();
|
||||
if (c === undefined) {
|
||||
this.raise('Unexpected escape');
|
||||
}
|
||||
if (c === '0' && !isDecimalDigit(this.peek())) {
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
subtype: '0',
|
||||
};
|
||||
}
|
||||
if (this.plusU && !isSyntaxCharacter(c) && c !== '/') {
|
||||
this.raise('Invalid identity escape');
|
||||
}
|
||||
return {
|
||||
type: 'CharacterEscape',
|
||||
IdentityEscape: c,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DecimalEscape ::
|
||||
// NonZeroDigit DecimalDigits? [lookahead != DecimalDigit]
|
||||
maybeParseDecimalEscape() {
|
||||
if (isDecimalDigit(this.source[this.position]) && this.source[this.position] !== '0') {
|
||||
const start = this.position;
|
||||
let buffer = this.source[this.position];
|
||||
this.position += 1;
|
||||
while (isDecimalDigit(this.source[this.position])) {
|
||||
buffer += this.source[this.position];
|
||||
this.position += 1;
|
||||
}
|
||||
const node = {
|
||||
type: 'DecimalEscape',
|
||||
position: start,
|
||||
value: Number.parseInt(buffer, 10),
|
||||
};
|
||||
this.decimalEscapes.push(node);
|
||||
return node;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// CharacterClassEscape ::
|
||||
// `d`
|
||||
// `D`
|
||||
// `s`
|
||||
// `S`
|
||||
// `w`
|
||||
// `W`
|
||||
// [+U] `p{` UnicodePropertyValueExpression `}`
|
||||
// [+U] `P{` UnicodePropertyValueExpression `}`
|
||||
maybeParseCharacterClassEscape() {
|
||||
switch (this.peek()) {
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 's':
|
||||
case 'S':
|
||||
case 'w':
|
||||
case 'W':
|
||||
return {
|
||||
type: 'CharacterClassEscape',
|
||||
value: this.next(),
|
||||
};
|
||||
case 'p':
|
||||
case 'P': {
|
||||
if (!this.plusU) {
|
||||
return undefined;
|
||||
}
|
||||
const value = this.next();
|
||||
this.expect('{');
|
||||
let sawDigit;
|
||||
let LoneUnicodePropertyNameOrValue = '';
|
||||
while (true) {
|
||||
if (this.position >= this.source.length) {
|
||||
this.raise('Invalid unicode property name or value');
|
||||
}
|
||||
const c = this.source[this.position];
|
||||
if (isDecimalDigit(c)) {
|
||||
sawDigit = true;
|
||||
this.position += 1;
|
||||
LoneUnicodePropertyNameOrValue += c;
|
||||
continue;
|
||||
}
|
||||
if (c === '_') {
|
||||
this.position += 1;
|
||||
LoneUnicodePropertyNameOrValue += c;
|
||||
continue;
|
||||
}
|
||||
if (!isControlLetter(c)) {
|
||||
break;
|
||||
}
|
||||
this.position += 1;
|
||||
LoneUnicodePropertyNameOrValue += c;
|
||||
}
|
||||
if (LoneUnicodePropertyNameOrValue.length === 0) {
|
||||
this.raise('Invalid unicode property name or value');
|
||||
}
|
||||
if (sawDigit && this.eat('}')) {
|
||||
if (!(LoneUnicodePropertyNameOrValue in UnicodeGeneralCategoryValues
|
||||
|| LoneUnicodePropertyNameOrValue in BinaryUnicodeProperties)) {
|
||||
this.raise('Invalid unicode property name or value');
|
||||
}
|
||||
return {
|
||||
type: 'CharacterClassEscape',
|
||||
value,
|
||||
UnicodePropertyValueExpression: {
|
||||
type: 'UnicodePropertyValueExpression',
|
||||
LoneUnicodePropertyNameOrValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
let UnicodePropertyValue;
|
||||
if (this.source[this.position] === '=') {
|
||||
this.position += 1;
|
||||
UnicodePropertyValue = '';
|
||||
while (true) {
|
||||
if (this.position >= this.source.length) {
|
||||
this.raise('Invalid unicode property value');
|
||||
}
|
||||
const c = this.source[this.position];
|
||||
if (!isControlLetter(c) && !isDecimalDigit(c) && c !== '_') {
|
||||
break;
|
||||
}
|
||||
this.position += 1;
|
||||
UnicodePropertyValue += c;
|
||||
}
|
||||
if (UnicodePropertyValue.length === 0) {
|
||||
this.raise('Invalid unicode property value');
|
||||
}
|
||||
}
|
||||
this.expect('}');
|
||||
if (UnicodePropertyValue) {
|
||||
if (!(LoneUnicodePropertyNameOrValue in NonbinaryUnicodeProperties)) {
|
||||
this.raise('Invalid unicode property name');
|
||||
}
|
||||
if (!(UnicodePropertyValue in UnicodeGeneralCategoryValues || UnicodePropertyValue in UnicodeScriptValues)) {
|
||||
this.raise('Invalid unicode property value');
|
||||
}
|
||||
return {
|
||||
type: 'CharacterClassEscape',
|
||||
value,
|
||||
UnicodePropertyValueExpression: {
|
||||
type: 'UnicodePropertyValueExpression',
|
||||
UnicodePropertyName: LoneUnicodePropertyNameOrValue,
|
||||
UnicodePropertyValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!(LoneUnicodePropertyNameOrValue in UnicodeGeneralCategoryValues
|
||||
|| LoneUnicodePropertyNameOrValue in BinaryUnicodeProperties)) {
|
||||
this.raise('Invalid unicode property name or value');
|
||||
}
|
||||
return {
|
||||
type: 'CharacterClassEscape',
|
||||
value,
|
||||
UnicodePropertyValueExpression: {
|
||||
type: 'UnicodePropertyValueExpression',
|
||||
LoneUnicodePropertyNameOrValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// CharacterClass ::
|
||||
// `[` ClassRanges `]`
|
||||
// `[` `^` ClassRanges `]`
|
||||
parseCharacterClass() {
|
||||
this.expect('[');
|
||||
const node = {
|
||||
type: 'CharacterClass',
|
||||
invert: false,
|
||||
ClassRanges: undefined,
|
||||
};
|
||||
node.invert = this.eat('^');
|
||||
node.ClassRanges = this.parseClassRanges();
|
||||
this.expect(']');
|
||||
return node;
|
||||
}
|
||||
|
||||
// ClassRanges ::
|
||||
// [empty]
|
||||
// NonemptyClassRanges
|
||||
parseClassRanges() {
|
||||
const ranges = [];
|
||||
while (!this.test(']')) {
|
||||
if (this.position >= this.source.length) {
|
||||
this.raise('Unexpected end of CharacterClass');
|
||||
}
|
||||
const atom = this.parseClassAtom();
|
||||
if (this.eat('-')) {
|
||||
if (atom.type === 'CharacterClassEscape') {
|
||||
this.raise('Invalid class range');
|
||||
}
|
||||
if (this.test(']')) {
|
||||
ranges.push(atom);
|
||||
ranges.push({ type: 'ClassAtom', value: '-' });
|
||||
} else {
|
||||
const atom2 = this.parseClassAtom();
|
||||
if (atom2.type === 'CharacterClassEscape') {
|
||||
this.raise('Invalid class range');
|
||||
}
|
||||
if (CharacterValue(atom) > CharacterValue(atom2)) {
|
||||
this.raise('Invalid class range');
|
||||
}
|
||||
ranges.push([atom, atom2]);
|
||||
}
|
||||
} else {
|
||||
ranges.push(atom);
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// ClassAtom ::
|
||||
// `-`
|
||||
// ClassAtomNoDash
|
||||
// ClassAtomNoDash ::
|
||||
// SourceCharacter but not one of `\` or `]` or `-`
|
||||
// `\` ClassEscape
|
||||
// ClassEscape :
|
||||
// `b`
|
||||
// [+U] `-`
|
||||
// CharacterClassEscape
|
||||
// CharacterEscape
|
||||
parseClassAtom() {
|
||||
if (this.eat('\\')) {
|
||||
if (this.eat('b')) {
|
||||
return {
|
||||
type: 'ClassEscape',
|
||||
value: 'b',
|
||||
};
|
||||
}
|
||||
if (this.plusU && this.eat('-')) {
|
||||
return {
|
||||
type: 'ClassEscape',
|
||||
value: '-',
|
||||
};
|
||||
}
|
||||
const CharacterClassEscape = this.maybeParseCharacterClassEscape();
|
||||
if (CharacterClassEscape) {
|
||||
return CharacterClassEscape;
|
||||
}
|
||||
return {
|
||||
type: 'ClassEscape',
|
||||
CharacterEscape: this.parseCharacterEscape(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'ClassAtom',
|
||||
SourceCharacter: this.parseSourceCharacter(),
|
||||
};
|
||||
}
|
||||
|
||||
parseSourceCharacter() {
|
||||
const lead = this.source.charCodeAt(this.position);
|
||||
const trail = this.source.charCodeAt(this.position + 1);
|
||||
if (trail && isLeadingSurrogate(lead) && isTrailingSurrogate(trail)) {
|
||||
return this.next() + this.next();
|
||||
}
|
||||
return this.next();
|
||||
}
|
||||
|
||||
parseGroupName() {
|
||||
this.expect('<');
|
||||
const RegExpIdentifierName = this.parseRegExpIdentifierName();
|
||||
this.expect('>');
|
||||
return RegExpIdentifierName;
|
||||
}
|
||||
|
||||
// RegExpIdentifierName ::
|
||||
// RegExpIdentifierStart
|
||||
// RegExpIdentifierName RegExpIdentifierPart
|
||||
parseRegExpIdentifierName() {
|
||||
let buffer = '';
|
||||
let check = isIdentifierStart;
|
||||
while (this.position < this.source.length) {
|
||||
const c = this.source[this.position];
|
||||
const code = c.charCodeAt(0);
|
||||
if (c === '\\') {
|
||||
this.position += 1;
|
||||
const RegExpUnicodeEscapeSequence = this.scope({ U: true }, () => this.maybeParseRegExpUnicodeEscapeSequence());
|
||||
if (!RegExpUnicodeEscapeSequence) {
|
||||
this.raise('Invalid unicode escape');
|
||||
}
|
||||
const raw = String.fromCodePoint(CharacterValue(RegExpUnicodeEscapeSequence));
|
||||
if (!check(raw)) {
|
||||
this.raise('Invalid identifier escape');
|
||||
}
|
||||
buffer += raw;
|
||||
} else if (isLeadingSurrogate(code)) {
|
||||
const lowSurrogate = this.source.charCodeAt(this.position + 1);
|
||||
if (!isTrailingSurrogate(lowSurrogate)) {
|
||||
this.raise('Invalid trailing surrogate');
|
||||
}
|
||||
const codePoint = UTF16SurrogatePairToCodePoint(code, lowSurrogate);
|
||||
const raw = String.fromCodePoint(codePoint);
|
||||
if (!check(raw)) {
|
||||
this.raise('Invalid surrogate pair');
|
||||
}
|
||||
this.position += 2;
|
||||
buffer += raw;
|
||||
} else if (check(c)) {
|
||||
buffer += c;
|
||||
this.position += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
check = isIdentifierPart;
|
||||
}
|
||||
if (buffer.length === 0) {
|
||||
this.raise('Invalid empty identifier');
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// DecimalDigits ::
|
||||
// DecimalDigit
|
||||
// DecimalDigits DecimalDigit
|
||||
parseDecimalDigits() {
|
||||
let n = '';
|
||||
if (!isDecimalDigit(this.peek())) {
|
||||
this.raise('Invalid decimal digits');
|
||||
}
|
||||
while (isDecimalDigit(this.peek())) {
|
||||
n += this.next();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// HexEscapeSequence ::
|
||||
// `x` HexDigit HexDigit
|
||||
parseHexEscapeSequence() {
|
||||
this.expect('x');
|
||||
const HexDigit_a = this.next();
|
||||
if (!isHexDigit(HexDigit_a)) {
|
||||
this.raise('Not a hex digit');
|
||||
}
|
||||
const HexDigit_b = this.next();
|
||||
if (!isHexDigit(HexDigit_b)) {
|
||||
this.raise('Not a hex digit');
|
||||
}
|
||||
return {
|
||||
type: 'HexEscapeSequence',
|
||||
HexDigit_a,
|
||||
HexDigit_b,
|
||||
};
|
||||
}
|
||||
|
||||
scanHex(length) {
|
||||
if (length === 0) {
|
||||
this.raise('Invalid code point');
|
||||
}
|
||||
let n = 0;
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
const c = this.source[this.position];
|
||||
if (isHexDigit(c)) {
|
||||
this.position += 1;
|
||||
n = (n << 4) | Number.parseInt(c, 16);
|
||||
} else {
|
||||
this.raise('Invalid hex digit');
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// RegExpUnicodeEscapeSequence ::
|
||||
// [+U] `u` HexLeadSurrogate `\u` HexTrailSurrogate
|
||||
// [+U] `u` HexLeadSurrogate
|
||||
// [+U] `u` HexTrailSurrogate
|
||||
// [+U] `u` HexNonSurrogate
|
||||
// [~U] `u` Hex4Digits
|
||||
// [+U] `u{` CodePoint `}`
|
||||
maybeParseRegExpUnicodeEscapeSequence() {
|
||||
const start = this.position;
|
||||
if (!this.eat('u')) {
|
||||
this.position = start;
|
||||
return undefined;
|
||||
}
|
||||
if (this.plusU && this.eat('{')) {
|
||||
const end = this.source.indexOf('}', this.position);
|
||||
if (end === -1) {
|
||||
this.raise('Invalid code point');
|
||||
}
|
||||
const code = this.scanHex(end - this.position);
|
||||
if (code > 0x10FFFF) {
|
||||
this.raise('Invalid code point');
|
||||
}
|
||||
this.position += 1;
|
||||
return {
|
||||
type: 'RegExpUnicodeEscapeSequence',
|
||||
CodePoint: code,
|
||||
};
|
||||
}
|
||||
let lead;
|
||||
try {
|
||||
lead = this.scanHex(4);
|
||||
} catch {
|
||||
this.position = start;
|
||||
return undefined;
|
||||
}
|
||||
if (this.plusU && isLeadingSurrogate(lead)) {
|
||||
const back = this.position;
|
||||
if (this.eat('\\') && this.eat('u')) {
|
||||
let trail;
|
||||
try {
|
||||
trail = this.scanHex(4);
|
||||
} catch {
|
||||
this.position = back;
|
||||
}
|
||||
return {
|
||||
type: 'RegExpUnicodeEscapeSequence',
|
||||
HexLeadSurrogate: lead,
|
||||
HexTrailSurrogate: trail,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'RegExpUnicodeEscapeSequence',
|
||||
HexLeadSurrogate: lead,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'RegExpUnicodeEscapeSequence',
|
||||
Hex4Digits: lead,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
import { OutOfRange } from '../helpers.mjs';
|
||||
|
||||
export const Flag = {
|
||||
__proto__: null,
|
||||
};
|
||||
[
|
||||
'return',
|
||||
'await',
|
||||
'yield',
|
||||
'parameters',
|
||||
'newTarget',
|
||||
'importMeta',
|
||||
'superCall',
|
||||
'superProperty',
|
||||
'in',
|
||||
'default',
|
||||
'module',
|
||||
].forEach((name, i) => {
|
||||
/* istanbul ignore next */
|
||||
if (i > 31) {
|
||||
throw new RangeError(name);
|
||||
}
|
||||
Flag[name] = 1 << i;
|
||||
});
|
||||
|
||||
export function getDeclarations(node) {
|
||||
if (Array.isArray(node)) {
|
||||
return node.flatMap((n) => getDeclarations(n));
|
||||
}
|
||||
switch (node.type) {
|
||||
case 'LexicalBinding':
|
||||
case 'VariableDeclaration':
|
||||
case 'BindingRestElement':
|
||||
case 'BindingRestProperty':
|
||||
case 'ForBinding':
|
||||
if (node.BindingIdentifier) {
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
}
|
||||
if (node.BindingPattern) {
|
||||
return getDeclarations(node.BindingPattern);
|
||||
}
|
||||
return [];
|
||||
case 'SingleNameBinding':
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
case 'ImportClause': {
|
||||
const d = [];
|
||||
if (node.ImportedDefaultBinding) {
|
||||
d.push(...getDeclarations(node.ImportedDefaultBinding));
|
||||
}
|
||||
if (node.NameSpaceImport) {
|
||||
d.push(...getDeclarations(node.NameSpaceImport));
|
||||
}
|
||||
if (node.NamedImports) {
|
||||
d.push(...getDeclarations(node.NamedImports));
|
||||
}
|
||||
return d;
|
||||
}
|
||||
case 'ImportSpecifier':
|
||||
return getDeclarations(node.ImportedBinding);
|
||||
case 'ImportedDefaultBinding':
|
||||
case 'NameSpaceImport':
|
||||
return getDeclarations(node.ImportedBinding);
|
||||
case 'NamedImports':
|
||||
return getDeclarations(node.ImportsList);
|
||||
case 'ObjectBindingPattern': {
|
||||
const declarations = getDeclarations(node.BindingPropertyList);
|
||||
if (node.BindingRestProperty) {
|
||||
declarations.push(...getDeclarations(node.BindingRestProperty));
|
||||
}
|
||||
return declarations;
|
||||
}
|
||||
case 'ArrayBindingPattern': {
|
||||
const declarations = getDeclarations(node.BindingElementList);
|
||||
if (node.BindingRestElement) {
|
||||
declarations.push(...getDeclarations(node.BindingRestElement));
|
||||
}
|
||||
return declarations;
|
||||
}
|
||||
case 'BindingElement':
|
||||
return getDeclarations(node.BindingPattern);
|
||||
case 'BindingProperty':
|
||||
return getDeclarations(node.BindingElement);
|
||||
case 'BindingIdentifier':
|
||||
case 'IdentifierName':
|
||||
case 'LabelIdentifier':
|
||||
return [{ name: node.name, node }];
|
||||
case 'StringLiteral':
|
||||
return [{ name: node.value, node }];
|
||||
case 'Elision':
|
||||
return [];
|
||||
case 'ForDeclaration':
|
||||
return getDeclarations(node.ForBinding);
|
||||
case 'ExportSpecifier':
|
||||
if (node.IdentifierName) {
|
||||
return getDeclarations(node.IdentifierName);
|
||||
}
|
||||
return getDeclarations(node.IdentifierName_b);
|
||||
case 'FunctionDeclaration':
|
||||
case 'GeneratorDeclaration':
|
||||
case 'AsyncFunctionDeclaration':
|
||||
case 'AsyncGeneratorDeclaration':
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
case 'LexicalDeclaration':
|
||||
return getDeclarations(node.BindingList);
|
||||
case 'VariableStatement':
|
||||
return getDeclarations(node.VariableDeclarationList);
|
||||
case 'ClassDeclaration':
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
default:
|
||||
throw new OutOfRange('getDeclarations', node);
|
||||
}
|
||||
}
|
||||
|
||||
export class Scope {
|
||||
constructor(parser) {
|
||||
this.parser = parser;
|
||||
this.scopeStack = [];
|
||||
this.labels = [];
|
||||
this.arrowInfoStack = [];
|
||||
this.assignmentInfoStack = [];
|
||||
this.exports = new Set();
|
||||
this.undefinedExports = new Map();
|
||||
this.flags = 0;
|
||||
}
|
||||
|
||||
hasReturn() {
|
||||
return (this.flags & Flag.return) !== 0;
|
||||
}
|
||||
|
||||
hasAwait() {
|
||||
return (this.flags & Flag.await) !== 0;
|
||||
}
|
||||
|
||||
hasYield() {
|
||||
return (this.flags & Flag.yield) !== 0;
|
||||
}
|
||||
|
||||
hasNewTarget() {
|
||||
return (this.flags & Flag.newTarget) !== 0;
|
||||
}
|
||||
|
||||
hasSuperCall() {
|
||||
return (this.flags & Flag.superCall) !== 0;
|
||||
}
|
||||
|
||||
hasSuperProperty() {
|
||||
return (this.flags & Flag.superProperty) !== 0;
|
||||
}
|
||||
|
||||
hasImportMeta() {
|
||||
return (this.flags & Flag.importMeta) !== 0;
|
||||
}
|
||||
|
||||
hasIn() {
|
||||
return (this.flags & Flag.in) !== 0;
|
||||
}
|
||||
|
||||
inParameters() {
|
||||
return (this.flags & Flag.parameters) !== 0;
|
||||
}
|
||||
|
||||
isDefault() {
|
||||
return (this.flags & Flag.default) !== 0;
|
||||
}
|
||||
|
||||
isModule() {
|
||||
return (this.flags & Flag.module) !== 0;
|
||||
}
|
||||
|
||||
with(flags, f) {
|
||||
const oldFlags = this.flags;
|
||||
|
||||
Object.entries(flags)
|
||||
.forEach(([k, v]) => {
|
||||
if (k in Flag) {
|
||||
if (v === true) {
|
||||
this.flags |= Flag[k];
|
||||
} else if (v === false) {
|
||||
this.flags &= ~Flag[k];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (flags.lexical || flags.variable) {
|
||||
this.scopeStack.push({
|
||||
flags,
|
||||
lexicals: new Set(),
|
||||
variables: new Set(),
|
||||
functions: new Set(),
|
||||
parameters: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
const oldLabels = this.labels;
|
||||
if (flags.label === 'boundary') {
|
||||
this.labels = [];
|
||||
} else if (flags.label) {
|
||||
this.labels.push({ type: flags.label });
|
||||
}
|
||||
|
||||
const oldStrict = this.parser.state.strict;
|
||||
if (flags.strict === true) {
|
||||
this.parser.state.strict = true;
|
||||
} else if (flags.strict === false) {
|
||||
this.parser.state.strict = false;
|
||||
}
|
||||
|
||||
const r = f();
|
||||
|
||||
if (flags.label === 'boundary') {
|
||||
this.labels = oldLabels;
|
||||
} else if (flags.label) {
|
||||
this.labels.pop();
|
||||
}
|
||||
|
||||
if (flags.lexical || flags.variable) {
|
||||
this.scopeStack.pop();
|
||||
}
|
||||
|
||||
this.parser.state.strict = oldStrict;
|
||||
this.flags = oldFlags;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
pushArrowInfo(isAsync = false) {
|
||||
this.arrowInfoStack.push({
|
||||
isAsync,
|
||||
hasTrailingComma: false,
|
||||
yieldExpressions: [],
|
||||
awaitExpressions: [],
|
||||
awaitIdentifiers: [],
|
||||
});
|
||||
}
|
||||
|
||||
popArrowInfo() {
|
||||
return this.arrowInfoStack.pop();
|
||||
}
|
||||
|
||||
pushAssignmentInfo(type) {
|
||||
const parser = this.parser;
|
||||
this.assignmentInfoStack.push({
|
||||
type,
|
||||
coverInitializedNameErrors: [],
|
||||
clear() {
|
||||
this.coverInitializedNameErrors.forEach((e) => {
|
||||
parser.earlyErrors.delete(e);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
popAssignmentInfo() {
|
||||
return this.assignmentInfoStack.pop();
|
||||
}
|
||||
|
||||
registerCoverInitializedName(CoverInitializedName) {
|
||||
const error = this.parser.raiseEarly('UnexpectedToken', CoverInitializedName);
|
||||
for (let i = this.assignmentInfoStack.length - 1; i >= 0; i -= 1) {
|
||||
const info = this.assignmentInfoStack[i];
|
||||
info.coverInitializedNameErrors.push(error);
|
||||
if (info.type !== 'assign') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lexicalScope() {
|
||||
for (let i = this.scopeStack.length - 1; i >= 0; i -= 1) {
|
||||
const scope = this.scopeStack[i];
|
||||
if (scope.flags.lexical) {
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
throw new RangeError();
|
||||
}
|
||||
|
||||
variableScope() {
|
||||
for (let i = this.scopeStack.length - 1; i >= 0; i -= 1) {
|
||||
const scope = this.scopeStack[i];
|
||||
if (scope.flags.variable) {
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
throw new RangeError();
|
||||
}
|
||||
|
||||
declare(node, type) {
|
||||
const declarations = getDeclarations(node);
|
||||
declarations.forEach((d) => {
|
||||
switch (type) {
|
||||
case 'lexical':
|
||||
case 'import': {
|
||||
if (type === 'lexical' && d.name === 'let') {
|
||||
this.parser.raiseEarly('LetInLexicalBinding', d.node);
|
||||
}
|
||||
const scope = this.lexicalScope();
|
||||
if (scope.lexicals.has(d.name)
|
||||
|| scope.variables.has(d.name)
|
||||
|| scope.functions.has(d.name)
|
||||
|| scope.parameters.has(d.name)) {
|
||||
this.parser.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
}
|
||||
scope.lexicals.add(d.name);
|
||||
if (scope === this.scopeStack[0] && this.undefinedExports.has(d.name)) {
|
||||
this.undefinedExports.delete(d.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'function': {
|
||||
const scope = this.lexicalScope();
|
||||
if (scope.lexicals.has(d.name)) {
|
||||
this.parser.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
}
|
||||
if (scope.flags.variableFunctions) {
|
||||
scope.functions.add(d.name);
|
||||
} else {
|
||||
if (scope.variables.has(d.name)) {
|
||||
this.parser.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
}
|
||||
scope.lexicals.add(d.name);
|
||||
}
|
||||
if (scope === this.scopeStack[0] && this.undefinedExports.has(d.name)) {
|
||||
this.undefinedExports.delete(d.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'parameter':
|
||||
this.variableScope().parameters.add(d.name);
|
||||
break;
|
||||
case 'variable':
|
||||
for (let i = this.scopeStack.length - 1; i >= 0; i -= 1) {
|
||||
const scope = this.scopeStack[i];
|
||||
scope.variables.add(d.name);
|
||||
if (scope.lexicals.has(d.name) || (!scope.flags.variableFunctions && scope.functions.has(d.name))) {
|
||||
this.parser.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
}
|
||||
if (i === 0 && this.undefinedExports.has(d.name)) {
|
||||
this.undefinedExports.delete(d.name);
|
||||
}
|
||||
if (scope.flags.variable) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'export':
|
||||
if (this.exports.has(d.name)) {
|
||||
this.parser.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
} else {
|
||||
this.exports.add(d.name);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* istanbul ignore next */
|
||||
throw new RangeError(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkUndefinedExports(NamedExports) {
|
||||
const scope = this.variableScope();
|
||||
NamedExports.ExportsList.forEach((n) => {
|
||||
const targetNode = n.IdentifierName || n.IdentifierName_a;
|
||||
if (!scope.lexicals.has(targetNode.name) && !scope.variables.has(targetNode.name)) {
|
||||
this.undefinedExports.set(targetNode.name, targetNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
const MaybeAssignTokens = [
|
||||
// Logical
|
||||
['NULLISH', '??', 3],
|
||||
['OR', '||', 4],
|
||||
['AND', '&&', 5],
|
||||
|
||||
// Binop
|
||||
['BIT_OR', '|', 6],
|
||||
['BIT_XOR', '^', 7],
|
||||
['BIT_AND', '&', 8],
|
||||
['SHL', '<<', 11],
|
||||
['SAR', '>>', 11],
|
||||
['SHR', '>>>', 11],
|
||||
['MUL', '*', 13],
|
||||
['DIV', '/', 13],
|
||||
['MOD', '%', 13],
|
||||
['EXP', '**', 14],
|
||||
|
||||
// Unop
|
||||
['ADD', '+', 12],
|
||||
['SUB', '-', 12],
|
||||
];
|
||||
|
||||
export const RawTokens = [
|
||||
// BEGIN PropertyOrCall
|
||||
// BEGIN Member
|
||||
// BEGIN Template
|
||||
['TEMPLATE', '`'],
|
||||
// END Template
|
||||
|
||||
// BEGIN Property
|
||||
['PERIOD', '.'],
|
||||
['LBRACK', '['],
|
||||
// END Property
|
||||
// END Member
|
||||
['OPTIONAL', '?.'],
|
||||
['LPAREN', '('],
|
||||
// END PropertyOrCall
|
||||
['RPAREN', ')'],
|
||||
['RBRACK', ']'],
|
||||
['LBRACE', '{'],
|
||||
['COLON', ':'],
|
||||
['ELLIPSIS', '...'],
|
||||
['CONDITIONAL', '?'],
|
||||
// BEGIN AutoSemicolon
|
||||
['SEMICOLON', ';'],
|
||||
['RBRACE', '}'],
|
||||
|
||||
['EOS', 'EOS'],
|
||||
// END AutoSemicolon
|
||||
|
||||
// BEGIN ArrowOrAssign
|
||||
['ARROW', '=>'],
|
||||
// BEGIN Assign
|
||||
['ASSIGN', '=', 2],
|
||||
...MaybeAssignTokens.map((t) => [`ASSIGN_${t[0]}`, `${t[1]}=`, 2]),
|
||||
// END Assign
|
||||
// END ArrowOrAssign
|
||||
|
||||
// Binary operators by precidence
|
||||
['COMMA', ',', 1],
|
||||
|
||||
...MaybeAssignTokens,
|
||||
|
||||
['NOT', '!'],
|
||||
['BIT_NOT', '~'],
|
||||
['DELETE', 'delete'],
|
||||
['TYPEOF', 'typeof'],
|
||||
['VOID', 'void'],
|
||||
|
||||
// BEGIN IsCountOp
|
||||
['INC', '++'],
|
||||
['DEC', '--'],
|
||||
// END IsCountOp
|
||||
// END IsUnaryOrCountOp
|
||||
|
||||
['EQ', '==', 9],
|
||||
['EQ_STRICT', '===', 9],
|
||||
['NE', '!=', 9],
|
||||
['NE_STRICT', '!==', 9],
|
||||
['LT', '<', 10],
|
||||
['GT', '>', 10],
|
||||
['LTE', '<=', 10],
|
||||
['GTE', '>=', 10],
|
||||
['INSTANCEOF', 'instanceof', 10],
|
||||
['IN', 'in', 10],
|
||||
|
||||
['BREAK', 'break'],
|
||||
['CASE', 'case'],
|
||||
['CATCH', 'catch'],
|
||||
['CONTINUE', 'continue'],
|
||||
['DEBUGGER', 'debugger'],
|
||||
['DEFAULT', 'default'],
|
||||
// DELETE
|
||||
['DO', 'do'],
|
||||
['ELSE', 'else'],
|
||||
['FINALLY', 'finally'],
|
||||
['FOR', 'for'],
|
||||
['FUNCTION', 'function'],
|
||||
['IF', 'if'],
|
||||
// IN
|
||||
// INSTANCEOF
|
||||
['NEW', 'new'],
|
||||
['RETURN', 'return'],
|
||||
['SWITCH', 'switch'],
|
||||
['THROW', 'throw'],
|
||||
['TRY', 'try'],
|
||||
// TYPEOF
|
||||
['VAR', 'var'],
|
||||
// VOID
|
||||
['WHILE', 'while'],
|
||||
['WITH', 'with'],
|
||||
['THIS', 'this'],
|
||||
|
||||
['NULL', 'null'],
|
||||
['TRUE', 'true'],
|
||||
['FALSE', 'false'],
|
||||
['NUMBER', null],
|
||||
['STRING', null],
|
||||
|
||||
// BEGIN Callable
|
||||
['SUPER', 'super'],
|
||||
// BEGIN AnyIdentifier
|
||||
['IDENTIFIER', null],
|
||||
['AWAIT', 'await'],
|
||||
['YIELD', 'yield'],
|
||||
// END AnyIdentifier
|
||||
// END Callable
|
||||
['CLASS', 'class'],
|
||||
['CONST', 'const'],
|
||||
['EXPORT', 'export'],
|
||||
['EXTENDS', 'extends'],
|
||||
['IMPORT', 'import'],
|
||||
|
||||
['ENUM', 'enum'],
|
||||
|
||||
['ESCAPED_KEYWORD', null],
|
||||
];
|
||||
|
||||
export const Token = RawTokens
|
||||
.reduce((obj, [name], i) => {
|
||||
obj[name] = i;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
export const TokenNames = RawTokens.map((r) => r[0]);
|
||||
|
||||
export const TokenPrecedence = RawTokens.map((r) => (r[2] || 0));
|
||||
|
||||
const Keywords = RawTokens
|
||||
.filter(([name, raw]) => name.toLowerCase() === raw)
|
||||
.map(([, raw]) => raw);
|
||||
|
||||
export const KeywordLookup = Keywords
|
||||
.reduce((obj, kw) => {
|
||||
obj[kw] = Token[kw.toUpperCase()];
|
||||
return obj;
|
||||
}, Object.create(null));
|
||||
|
||||
const KeywordTokens = new Set(Object.values(KeywordLookup));
|
||||
|
||||
const isInRange = (t, l, h) => t >= l && t <= h;
|
||||
export const isAutomaticSemicolon = (t) => isInRange(t, Token.SEMICOLON, Token.EOS);
|
||||
export const isMember = (t) => isInRange(t, Token.TEMPLATE, Token.LBRACK);
|
||||
export const isPropertyOrCall = (t) => isInRange(t, Token.TEMPLATE, Token.LPAREN);
|
||||
export const isKeyword = (t) => KeywordTokens.has(t);
|
||||
export const isKeywordRaw = (s) => Keywords.includes(s);
|
||||
|
||||
const ReservedWordsStrict = [
|
||||
'implements', 'interface', 'let',
|
||||
'package', 'private', 'protected',
|
||||
'public', 'static', 'yield',
|
||||
];
|
||||
|
||||
export const isReservedWordStrict = (s) => ReservedWordsStrict.includes(s);
|
||||
Reference in New Issue
Block a user