mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-19 00:31:06 +00:00
Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { Lexer } from './Lexer.mts';
|
||||
import type { ParseNode, ParseNodesByType } from './ParseNode.mts';
|
||||
import type { Scope } from './Scope.mts';
|
||||
|
||||
export abstract class BaseParser extends Lexer {
|
||||
protected abstract scope: Scope;
|
||||
|
||||
abstract startNode<T extends ParseNode>(inheritStart?: ParseNode): ParseNode.Unfinished<T>;
|
||||
|
||||
abstract finishNode<T extends ParseNode.Unfinished, K extends T['type'] & ParseNode['type']>(node: T, type: K): ParseNodesByType[K];
|
||||
|
||||
/**
|
||||
* Repurpose a {@link ParseNode} of one type as a {@link ParseNode} of another type.
|
||||
* @param node The node to repurpose.
|
||||
* @param type The name of the new node type.
|
||||
* @param update an optional callback that can be used to mutate {@link node} to match the new node type.
|
||||
*/
|
||||
protected repurpose<T extends ParseNode, K extends ParseNode['type']>(
|
||||
node: T,
|
||||
type: K,
|
||||
update?: (
|
||||
/** The same value as {@link node}, but cast to an unfinished node of the provided type */
|
||||
asNewNode: ParseNode.Unfinished<ParseNodesByType[K]>,
|
||||
/** The same value as {@link node} */
|
||||
asOldNode: T,
|
||||
/** The same value as {@link node}, but cast to a partial, mutable type so that excess properties can be removed. */
|
||||
asPartialNode: { -readonly [P in keyof T]?: T[P] },
|
||||
) => void,
|
||||
): ParseNodesByType[K] {
|
||||
// NOTE: must down-cast to `ParseNode` before up-casting to `Unfinished<T>` due to the incompatbile `type` discriminant.
|
||||
const unfinished = node as ParseNode.Unfinished<ParseNodesByType[K]>;
|
||||
unfinished.type = type;
|
||||
update?.(unfinished, node, node);
|
||||
return unfinished as ParseNode as ParseNodesByType[K];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,389 @@
|
||||
import { IsSimpleParameterList } from '../static-semantics/all.mts';
|
||||
import { type Mutable } from '../helpers.mts';
|
||||
import { getDeclarations, type ArrowInfo } from './Scope.mts';
|
||||
import { Token } from './tokens.mts';
|
||||
import { IdentifierParser } from './IdentifierParser.mts';
|
||||
import type { ParseNode, ParseNodesByType } from './ParseNode.mts';
|
||||
|
||||
export enum FunctionKind {
|
||||
NORMAL = 0,
|
||||
ASYNC = 1,
|
||||
}
|
||||
|
||||
interface ArrowParameterConversions {
|
||||
'IdentifierReference': ParseNode.SingleNameBinding;
|
||||
'BindingRestElement': ParseNode.BindingRestElement;
|
||||
'Elision': ParseNode.Elision;
|
||||
'ArrayLiteral': ParseNode.BindingElement;
|
||||
'ObjectLiteral': ParseNode.BindingElement;
|
||||
'AssignmentExpression': ParseNode.SingleNameBinding | ParseNode.BindingElement;
|
||||
'CoverInitializedName': ParseNode.SingleNameBinding;
|
||||
'PropertyDefinition': ParseNode.BindingRestProperty | ParseNode.BindingProperty;
|
||||
'SpreadElement': ParseNode.BindingRestElement;
|
||||
'AssignmentRestElement': ParseNode.BindingRestElement;
|
||||
}
|
||||
|
||||
type ConvertArrowParameterResult<T> =
|
||||
T extends keyof ArrowParameterConversions ? ArrowParameterConversions[T] : never;
|
||||
|
||||
interface ConciseBodyInfo {
|
||||
'ConciseBody': ParseNode.ConciseBodyLike;
|
||||
'AsyncConciseBody': ParseNode.AsyncConciseBodyLike;
|
||||
}
|
||||
|
||||
export abstract class FunctionParser extends IdentifierParser {
|
||||
abstract parseStatementList(token: string | Token, directives?: readonly string[]): ParseNode.StatementList;
|
||||
|
||||
abstract parseAssignmentExpression(): ParseNode.AssignmentExpressionOrHigher;
|
||||
|
||||
abstract parseBindingElement(): ParseNode.BindingElementLike;
|
||||
|
||||
abstract parseBindingRestElement(): ParseNode.BindingRestElement;
|
||||
|
||||
// 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 `)` `{` AsyncBody `}`
|
||||
// Async`FunctionExpression :
|
||||
// `async` `function` BindingIdentifier? `(` FormalParameters `)` `{` AsyncBody `}`
|
||||
parseFunction(isExpression: boolean, kind: FunctionKind) {
|
||||
const isAsync = kind === FunctionKind.ASYNC;
|
||||
const node = this.startNode<ParseNode.FunctionLike>();
|
||||
if (isAsync) {
|
||||
this.expect('async');
|
||||
}
|
||||
this.expect(Token.FUNCTION);
|
||||
const isGenerator = this.eat(Token.MUL);
|
||||
if (!this.test(Token.LPAREN)) {
|
||||
node.BindingIdentifier = this.scope.with({
|
||||
await: isExpression ? false : undefined,
|
||||
yield: isExpression ? false : undefined,
|
||||
}, () => 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,
|
||||
parameters: false,
|
||||
classStaticBlock: false,
|
||||
}, () => {
|
||||
this.scope.arrowInfoStack.push(null);
|
||||
|
||||
node.FormalParameters = this.parseFormalParameters();
|
||||
|
||||
const body = this.parseFunctionBody(isAsync, isGenerator, false);
|
||||
this.setFunctionBodyGeneric(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);
|
||||
|
||||
this.scope.arrowInfoStack.pop();
|
||||
});
|
||||
|
||||
const name = `${isAsync ? 'Async' : ''}${isGenerator ? 'Generator' : 'Function'}${isExpression ? 'Expression' : 'Declaration'}` as const;
|
||||
return this.finishNode(node, name);
|
||||
}
|
||||
|
||||
private setFunctionBodyGeneric<T extends ParseNode.FunctionBodyLike['type']>(node: { [P in T]?: ParseNodesByType[T] }, type: T, body: ParseNodesByType[T]) {
|
||||
node[type] = body;
|
||||
}
|
||||
|
||||
validateFormalParameters(parameters: ParseNode.FormalParameters, body: ParseNode.FunctionBodyLike | ParseNode.ConciseBody | ParseNode.AsyncConciseBody, 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<T extends ParseNode>(node: T): ConvertArrowParameterResult<T['type']>;
|
||||
|
||||
convertArrowParameter(node: ParseNode) {
|
||||
switch (node.type) {
|
||||
case 'IdentifierReference': {
|
||||
const BindingIdentifier = this.repurpose(node, 'BindingIdentifier');
|
||||
const SingleNameBinding = this.startNode<ParseNode.SingleNameBinding>(node);
|
||||
SingleNameBinding.BindingIdentifier = BindingIdentifier;
|
||||
SingleNameBinding.Initializer = null;
|
||||
this.scope.declare(node, 'parameter');
|
||||
return this.finishNode(SingleNameBinding, 'SingleNameBinding');
|
||||
}
|
||||
case 'BindingRestElement':
|
||||
this.scope.declare(node, 'parameter');
|
||||
return node;
|
||||
case 'Elision':
|
||||
return node;
|
||||
case 'ArrayLiteral': {
|
||||
const BindingPattern = this.repurpose(node, 'ArrayBindingPattern', (asNew, asOld, asPartial) => {
|
||||
const BindingElementList: Mutable<ParseNode.BindingElementList> = [];
|
||||
asNew.BindingElementList = BindingElementList;
|
||||
for (const [i, p] of asOld.ElementList.entries()) {
|
||||
const c = this.convertArrowParameter(p);
|
||||
if (c.type === 'BindingRestElement') {
|
||||
if (i !== asOld.ElementList.length - 1) {
|
||||
this.raiseEarly('UnexpectedToken', c);
|
||||
}
|
||||
asNew.BindingRestElement = c;
|
||||
} else {
|
||||
BindingElementList.push(c);
|
||||
}
|
||||
}
|
||||
delete asPartial.ElementList;
|
||||
});
|
||||
const BindingElement = this.startNode<ParseNode.BindingElement>(node);
|
||||
BindingElement.BindingPattern = BindingPattern;
|
||||
BindingElement.Initializer = null;
|
||||
return this.finishNode(BindingElement, 'BindingElement');
|
||||
}
|
||||
case 'ObjectLiteral': {
|
||||
const BindingPattern = this.repurpose(node, 'ObjectBindingPattern', (asNew, asOld, asPartial) => {
|
||||
const BindingPropertyList: Mutable<ParseNode.BindingPropertyList> = [];
|
||||
asNew.BindingPropertyList = BindingPropertyList;
|
||||
for (const p of asOld.PropertyDefinitionList) {
|
||||
const c = this.convertArrowParameter(p);
|
||||
if (c.type === 'BindingRestProperty') {
|
||||
asNew.BindingRestProperty = c;
|
||||
} else {
|
||||
BindingPropertyList.push(c);
|
||||
}
|
||||
}
|
||||
delete asPartial.PropertyDefinitionList;
|
||||
});
|
||||
const BindingElement = this.startNode<ParseNode.BindingElement>(node);
|
||||
BindingElement.BindingPattern = BindingPattern;
|
||||
BindingElement.Initializer = null;
|
||||
return this.finishNode(BindingElement, 'BindingElement');
|
||||
}
|
||||
case 'AssignmentExpression': {
|
||||
const result = this.convertArrowParameter(node.LeftHandSideExpression) as ParseNode.Unfinished<ParseNode.SingleNameBinding | ParseNode.BindingElement>;
|
||||
result.Initializer = node.AssignmentExpression;
|
||||
return result as ParseNode.SingleNameBinding | ParseNode.BindingElement;
|
||||
}
|
||||
case 'CoverInitializedName': {
|
||||
const SingleNameBinding = this.repurpose(node, 'SingleNameBinding', (asNew, asOld, asPartial) => {
|
||||
asNew.BindingIdentifier = this.repurpose(asOld.IdentifierReference, 'BindingIdentifier');
|
||||
delete asPartial.IdentifierReference;
|
||||
});
|
||||
this.scope.declare(SingleNameBinding, 'parameter');
|
||||
return SingleNameBinding;
|
||||
}
|
||||
case 'PropertyDefinition': {
|
||||
let BindingProperty: ParseNode.BindingProperty | ParseNode.BindingRestProperty;
|
||||
if (node.PropertyName === null) {
|
||||
BindingProperty = this.repurpose(node, 'BindingRestProperty', (asNew, asOld, asPartial) => {
|
||||
asNew.BindingIdentifier = this.repurpose(asOld.AssignmentExpression, 'BindingIdentifier');
|
||||
delete asPartial.AssignmentExpression;
|
||||
});
|
||||
} else {
|
||||
BindingProperty = this.repurpose(node, 'BindingProperty', (asNew, asOld, asPartial) => {
|
||||
asNew.BindingElement = this.convertArrowParameter(asOld.AssignmentExpression);
|
||||
delete asPartial.AssignmentExpression;
|
||||
});
|
||||
}
|
||||
this.scope.declare(node, 'parameter');
|
||||
return BindingProperty;
|
||||
}
|
||||
case 'SpreadElement':
|
||||
case 'AssignmentRestElement': {
|
||||
const BindingRestElement = this.repurpose(node, 'BindingRestElement', (asNew, asOld, asPartial) => {
|
||||
const { AssignmentExpression } = asOld;
|
||||
if (AssignmentExpression.type === 'AssignmentExpression') {
|
||||
this.raiseEarly('UnexpectedToken', node);
|
||||
} else if (AssignmentExpression.type === 'IdentifierReference') {
|
||||
asNew.BindingIdentifier = this.repurpose(AssignmentExpression, 'BindingIdentifier');
|
||||
} else {
|
||||
asNew.BindingPattern = this.convertArrowParameter(AssignmentExpression).BindingPattern;
|
||||
}
|
||||
delete asPartial.AssignmentExpression;
|
||||
});
|
||||
this.scope.declare(BindingRestElement, 'parameter');
|
||||
return BindingRestElement;
|
||||
}
|
||||
default:
|
||||
this.raiseEarly('UnexpectedToken', node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
parseArrowFunction(node: ParseNode.Unfinished<ParseNode.ArrowFunction | ParseNode.AsyncArrowFunction>, { arrowInfo, Arguments }: { arrowInfo?: ArrowInfo, Arguments: ParseNode.CoverParenthesizedExpressionAndArrowParameterList['Arguments'] }, kind: FunctionKind): ParseNode.ArrowFunction | ParseNode.AsyncArrowFunction {
|
||||
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,
|
||||
}, () => {
|
||||
node.ArrowParameters = this.scope.with({
|
||||
parameters: true,
|
||||
}, () => Arguments.map((p) => this.convertArrowParameter(p)));
|
||||
const body = this.parseConciseBody(isAsync);
|
||||
this.validateFormalParameters(node.ArrowParameters, body, true);
|
||||
let bodyType: 'ConciseBody' | 'AsyncConciseBody';
|
||||
if (body.type === 'FunctionBody') {
|
||||
bodyType = 'ConciseBody';
|
||||
} else if (body.type === 'AsyncBody') {
|
||||
bodyType = 'AsyncConciseBody';
|
||||
} else {
|
||||
bodyType = body.type;
|
||||
}
|
||||
this.setConciseBodyGeneric(node, bodyType, body);
|
||||
});
|
||||
return this.finishNode(node, `${isAsync ? 'Async' : ''}ArrowFunction`);
|
||||
}
|
||||
|
||||
private setConciseBodyGeneric<T extends 'ConciseBody' | 'AsyncConciseBody'>(node: { [P in T]?: ConciseBodyInfo[T] }, type: T, body: ConciseBodyInfo[T]) {
|
||||
node[type] = body;
|
||||
}
|
||||
|
||||
parseConciseBody(isAsync: boolean): ParseNode.ConciseBody | ParseNode.FunctionBody | ParseNode.AsyncConciseBody | ParseNode.AsyncBody {
|
||||
if (this.test(Token.LBRACE)) {
|
||||
return this.parseFunctionBody(isAsync, false, true) as ParseNode.FunctionBody | ParseNode.AsyncBody;
|
||||
}
|
||||
const asyncBody = this.startNode<ParseNode.ConciseBody | ParseNode.AsyncConciseBody>();
|
||||
const exprBody = this.startNode<ParseNode.ExpressionBody>();
|
||||
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(): ParseNode.FormalParameter {
|
||||
return this.parseBindingElement();
|
||||
}
|
||||
|
||||
parseFormalParameters(): ParseNode.FormalParameters {
|
||||
this.expect(Token.LPAREN);
|
||||
if (this.eat(Token.RPAREN)) {
|
||||
return [];
|
||||
}
|
||||
const params: Mutable<ParseNode.FormalParameters> = [];
|
||||
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(): ParseNode.UniqueFormalParameters {
|
||||
return this.parseFormalParameters();
|
||||
}
|
||||
|
||||
parseFunctionBody(isAsync: boolean, isGenerator: boolean, isArrow: boolean): ParseNode.FunctionBodyLike {
|
||||
const node = this.startNode<ParseNode.FunctionBodyLike>();
|
||||
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');
|
||||
});
|
||||
let name: ParseNode.FunctionBodyLike['type'];
|
||||
if (isAsync) {
|
||||
name = isGenerator ? 'AsyncGeneratorBody' : 'AsyncBody';
|
||||
} else {
|
||||
name = isGenerator ? 'GeneratorBody' : 'FunctionBody';
|
||||
}
|
||||
return this.finishNode(node, name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
Token,
|
||||
isKeyword,
|
||||
isReservedWordStrict,
|
||||
isKeywordRaw,
|
||||
} from './tokens.mts';
|
||||
import { BaseParser } from './BaseParser.mts';
|
||||
import type { ParseNode } from './ParseNode.mts';
|
||||
import { type Locatable } from './Lexer.mts';
|
||||
|
||||
export abstract class IdentifierParser extends BaseParser {
|
||||
// IdentifierName
|
||||
parseIdentifierName() {
|
||||
const node = this.startNode<ParseNode.IdentifierName>();
|
||||
const p = this.peek();
|
||||
if (p.type === Token.IDENTIFIER
|
||||
|| p.type === Token.ESCAPED_KEYWORD
|
||||
|| isKeyword(p.type)) {
|
||||
node.name = this.next().valueAsString();
|
||||
} else {
|
||||
this.unexpected();
|
||||
}
|
||||
return this.finishNode(node, 'IdentifierName');
|
||||
}
|
||||
|
||||
// BindingIdentifier :
|
||||
// Identifier
|
||||
// `yield`
|
||||
// `await`
|
||||
parseBindingIdentifier() {
|
||||
const node = this.startNode<ParseNode.BindingIdentifier>();
|
||||
const token = this.next();
|
||||
switch (token.type) {
|
||||
case Token.IDENTIFIER:
|
||||
node.name = token.valueAsString();
|
||||
break;
|
||||
case Token.ESCAPED_KEYWORD:
|
||||
node.name = token.valueAsString();
|
||||
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) {
|
||||
break;
|
||||
}
|
||||
if (arrowInfo.isAsync) {
|
||||
arrowInfo.awaitIdentifiers.push(node as ParseNode.BindingIdentifier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.unexpected(token);
|
||||
}
|
||||
if (this.isStrictMode() && (node.name === 'eval' || node.name === 'arguments')) {
|
||||
this.raiseEarly('UnexpectedEvalOrArguments', token);
|
||||
}
|
||||
this.validateIdentifierReference(node.name, token);
|
||||
return this.finishNode(node, 'BindingIdentifier');
|
||||
}
|
||||
|
||||
// IdentifierReference :
|
||||
// Identifier
|
||||
// [~Yield] `yield`
|
||||
// [~Await] `await`
|
||||
parseIdentifierReference() {
|
||||
const node = this.startNode<ParseNode.IdentifierReference>();
|
||||
const token = this.next();
|
||||
node.escaped = token.escaped;
|
||||
switch (token.type) {
|
||||
case Token.IDENTIFIER:
|
||||
node.name = token.valueAsString();
|
||||
break;
|
||||
case Token.ESCAPED_KEYWORD:
|
||||
node.name = token.valueAsString();
|
||||
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) {
|
||||
break;
|
||||
}
|
||||
if (arrowInfo.isAsync) {
|
||||
arrowInfo.awaitIdentifiers.push(node as ParseNode.IdentifierReference);
|
||||
break;
|
||||
}
|
||||
}
|
||||
node.name = 'await';
|
||||
break;
|
||||
default:
|
||||
this.unexpected(token);
|
||||
}
|
||||
this.validateIdentifierReference(node.name, token);
|
||||
return this.finishNode(node, 'IdentifierReference');
|
||||
}
|
||||
|
||||
validateIdentifierReference(name: string, token: Locatable) {
|
||||
if (name === 'yield' && (this.scope.hasYield() || this.scope.isModule())) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (name === 'await' && (this.scope.hasAwait() || this.scope.isModule())) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (this.isStrictMode() && isReservedWordStrict(name)) {
|
||||
this.raiseEarly('UnexpectedReservedWordStrict', token);
|
||||
}
|
||||
if (this.scope.inClassStaticBlock() && name === 'arguments') {
|
||||
this.raiseEarly('UnexpectedEvalOrArguments', token);
|
||||
}
|
||||
if (name !== 'yield' && name !== 'await' && isKeywordRaw(name)) {
|
||||
this.raiseEarly('UnexpectedToken', token);
|
||||
}
|
||||
}
|
||||
|
||||
// LabelIdentifier :
|
||||
// Identifier
|
||||
// [~Yield] `yield`
|
||||
// [~Await] `await`
|
||||
parseLabelIdentifier() {
|
||||
const node = this.parseIdentifierReference();
|
||||
return this.repurpose(node, 'LabelIdentifier');
|
||||
}
|
||||
|
||||
// PrivateIdentifier ::
|
||||
// `#` IdentifierName
|
||||
parsePrivateIdentifier() {
|
||||
const node = this.startNode<ParseNode.PrivateIdentifier>();
|
||||
node.name = this.expect(Token.PRIVATE_IDENTIFIER).valueAsString();
|
||||
return this.finishNode(node, 'PrivateIdentifier');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { ModuleParser } from './ModuleParser.mts';
|
||||
import type { ParseNode } from './ParseNode.mts';
|
||||
import { Token } from './tokens.mts';
|
||||
import { Throw } from '#self';
|
||||
|
||||
export abstract class LanguageParser extends ModuleParser {
|
||||
// Script : ScriptBody?
|
||||
parseScript(): ParseNode.Script {
|
||||
this.skipHashbangComment();
|
||||
const node = this.startNode<ParseNode.Script>();
|
||||
if (this.eat(Token.EOS)) {
|
||||
node.ScriptBody = null;
|
||||
} else {
|
||||
node.ScriptBody = this.parseScriptBody();
|
||||
}
|
||||
Object.defineProperty(node, 'sourceText', {
|
||||
configurable: true,
|
||||
get: () => this.source,
|
||||
});
|
||||
return this.finishNode(node, 'Script');
|
||||
}
|
||||
|
||||
// ScriptBody : StatementList
|
||||
parseScriptBody(): ParseNode.ScriptBody {
|
||||
const node = this.startNode<ParseNode.ScriptBody>();
|
||||
this.scope.with({
|
||||
in: true,
|
||||
lexical: true,
|
||||
variable: true,
|
||||
variableFunctions: true,
|
||||
}, () => {
|
||||
const directives: string[] = [];
|
||||
node.StatementList = this.parseStatementList(Token.EOS, directives);
|
||||
node.strict = directives.includes('use strict');
|
||||
});
|
||||
Object.defineProperty(node, 'sourceText', {
|
||||
configurable: true,
|
||||
get: () => this.source,
|
||||
});
|
||||
return this.finishNode(node, 'ScriptBody');
|
||||
}
|
||||
|
||||
// Module : ModuleBody?
|
||||
parseModule(): ParseNode.Module {
|
||||
this.skipHashbangComment();
|
||||
return this.scope.with({
|
||||
module: true,
|
||||
strict: true,
|
||||
in: true,
|
||||
importMeta: true,
|
||||
await: true,
|
||||
lexical: true,
|
||||
variable: true,
|
||||
}, () => {
|
||||
const node = this.startNode<ParseNode.Module>();
|
||||
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;
|
||||
Object.defineProperty(node, 'sourceText', {
|
||||
configurable: true,
|
||||
get: () => this.source,
|
||||
});
|
||||
return this.finishNode(node, 'Module');
|
||||
});
|
||||
}
|
||||
|
||||
// ModuleBody :
|
||||
// ModuleItemList
|
||||
parseModuleBody(): ParseNode.ModuleBody {
|
||||
const node = this.startNode<ParseNode.ModuleBody>();
|
||||
node.ModuleItemList = this.parseModuleItemList();
|
||||
Object.defineProperty(node, 'sourceText', {
|
||||
configurable: true,
|
||||
get: () => this.source,
|
||||
});
|
||||
return this.finishNode(node, 'ModuleBody');
|
||||
}
|
||||
|
||||
// ModuleItemList :
|
||||
// ModuleItem
|
||||
// ModuleItemList ModuleItem
|
||||
//
|
||||
// ModuleItem :
|
||||
// ImportDeclaration
|
||||
// ExportDeclaration
|
||||
// StatementListItem
|
||||
parseModuleItemList(): ParseNode.ModuleItemList {
|
||||
const moduleItemList: Mutable<ParseNode.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(null));
|
||||
break;
|
||||
case Token.AT: {
|
||||
const decorators = this.parseDecorators();
|
||||
if (this.peek().type === Token.EXPORT) {
|
||||
// ModuleItem: DecoratorList `export` Declaration
|
||||
const exports = this.parseExportDeclaration(decorators);
|
||||
// TODO(decorator):
|
||||
// ExportDeclaration : DecoratorList? `export` Declaration
|
||||
// It is a Syntax Error if DecoratorList is present and Declaration is not ClassDeclaration.
|
||||
if (!exports.ClassDeclaration) {
|
||||
this.addEarlyError(Throw.SyntaxError('Decorators can only be used to decorate classes'), exports.AssignmentExpression || exports.Declaration || exports.ExportFromClause || exports.FromClause || exports.HoistableDeclaration || exports.VariableStatement || exports.WithClause || exports);
|
||||
}
|
||||
// It is a Syntax Error if DecoratorList is present, Declaration is a ClassDeclaration, and the DecoratorList of that ClassDeclaration is present.
|
||||
// ExportDeclaration : DecoratorList? export default ClassDeclaration
|
||||
// It is a Syntax Error if DecoratorList is present and the DecoratorList of ClassDeclaration is present.
|
||||
if (exports.ClassDeclaration && exports.ClassDeclaration.Decorators?.length) {
|
||||
this.addEarlyError(Throw.SyntaxError('Decorators cannot appear on both sides of the export keyword'), exports.ClassDeclaration.Decorators[0]);
|
||||
}
|
||||
moduleItemList.push(exports);
|
||||
} else {
|
||||
// ModuleItem : DecoratorList ClassDeclaration
|
||||
const classDecl = this.parseClassDeclaration(decorators);
|
||||
moduleItemList.push(classDecl);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
moduleItemList.push(this.parseStatementListItem());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return moduleItemList;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,336 @@
|
||||
import { IsStringWellFormedUnicode, StringValue } from '../static-semantics/all.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { Token, isKeywordRaw } from './tokens.mts';
|
||||
import { StatementParser } from './StatementParser.mts';
|
||||
import { FunctionKind } from './FunctionParser.mts';
|
||||
import type { ParseNode } from './ParseNode.mts';
|
||||
|
||||
export abstract class ModuleParser extends StatementParser {
|
||||
// ImportDeclaration :
|
||||
// `import` ImportClause FromClause WithClause? `;`
|
||||
// `import` ModuleSpecifier WithClause? `;`
|
||||
parseImportDeclaration(): ParseNode.ImportDeclaration | ParseNode.ExpressionStatement | ParseNode.LabelledStatement {
|
||||
if (this.testAhead(Token.PERIOD) || this.testAhead(Token.LPAREN)) {
|
||||
// `import` `(`
|
||||
// `import` `.`
|
||||
return this.parseExpressionStatement();
|
||||
}
|
||||
const node = this.startNode<ParseNode.ImportDeclaration>();
|
||||
this.next();
|
||||
if (this.test(Token.STRING)) {
|
||||
node.ModuleSpecifier = this.parsePrimaryExpression();
|
||||
} else {
|
||||
if (this.test('defer') && this.testAhead(Token.MUL)) {
|
||||
this.next(); // defer
|
||||
node.Phase = 'defer';
|
||||
const importClause = this.startNode<ParseNode.ImportClause>();
|
||||
importClause.NameSpaceImport = this.parseNameSpaceImport();
|
||||
node.ImportClause = this.finishNode(importClause, 'ImportClause');
|
||||
} else {
|
||||
node.Phase = 'evaluation';
|
||||
node.ImportClause = this.parseImportClause();
|
||||
}
|
||||
this.scope.declare(node.ImportClause, 'import');
|
||||
node.FromClause = this.parseFromClause();
|
||||
}
|
||||
if (this.test(Token.WITH)) {
|
||||
node.WithClause = this.parseWithClause();
|
||||
}
|
||||
this.semicolon();
|
||||
return this.finishNode(node, 'ImportDeclaration');
|
||||
}
|
||||
|
||||
// ImportClause :
|
||||
// ImportedDefaultBinding
|
||||
// NameSpaceImport
|
||||
// NamedImports
|
||||
// ImportedDefaultBinding `,` NameSpaceImport
|
||||
// ImportedDefaultBinding `,` NamedImports
|
||||
//
|
||||
// ImportedBinding :
|
||||
// BindingIdentifier
|
||||
parseImportClause(): ParseNode.ImportClause {
|
||||
const node = this.startNode<ParseNode.ImportClause>();
|
||||
if (this.test(Token.IDENTIFIER)) {
|
||||
node.ImportedDefaultBinding = this.parseImportedDefaultBinding();
|
||||
if (!this.eat(Token.COMMA)) {
|
||||
return this.finishNode(node, 'ImportClause');
|
||||
}
|
||||
}
|
||||
if (this.test(Token.MUL)) {
|
||||
node.NameSpaceImport = this.parseNameSpaceImport();
|
||||
} else if (this.eat(Token.LBRACE)) {
|
||||
node.NamedImports = this.parseNamedImports();
|
||||
} else {
|
||||
this.unexpected();
|
||||
}
|
||||
return this.finishNode(node, 'ImportClause');
|
||||
}
|
||||
|
||||
// ImportedDefaultBinding :
|
||||
// ImportedBinding
|
||||
parseImportedDefaultBinding(): ParseNode.ImportedDefaultBinding {
|
||||
const node = this.startNode<ParseNode.ImportedDefaultBinding>();
|
||||
node.ImportedBinding = this.parseBindingIdentifier();
|
||||
return this.finishNode(node, 'ImportedDefaultBinding');
|
||||
}
|
||||
|
||||
// NameSpaceImport :
|
||||
// `*` `as` ImportedBinding
|
||||
parseNameSpaceImport(): ParseNode.NameSpaceImport {
|
||||
const node = this.startNode<ParseNode.NameSpaceImport>();
|
||||
this.expect(Token.MUL);
|
||||
this.expect('as');
|
||||
node.ImportedBinding = this.parseBindingIdentifier();
|
||||
return this.finishNode(node, 'NameSpaceImport');
|
||||
}
|
||||
|
||||
// NamedImports :
|
||||
// `{` `}`
|
||||
// `{` ImportsList `}`
|
||||
// `{` ImportsList `,` `}`
|
||||
parseNamedImports(): ParseNode.NamedImports {
|
||||
const node = this.startNode<ParseNode.NamedImports>();
|
||||
const ImportsList: Mutable<ParseNode.ImportsList> = [];
|
||||
node.ImportsList = ImportsList;
|
||||
while (!this.eat(Token.RBRACE)) {
|
||||
ImportsList.push(this.parseImportSpecifier());
|
||||
if (this.eat(Token.RBRACE)) {
|
||||
break;
|
||||
}
|
||||
this.expect(Token.COMMA);
|
||||
}
|
||||
return this.finishNode(node, 'NamedImports');
|
||||
}
|
||||
|
||||
// ImportSpecifier :
|
||||
// ImportedBinding
|
||||
// ModuleExportName `as` ImportedBinding
|
||||
parseImportSpecifier(): ParseNode.ImportSpecifier {
|
||||
const node = this.startNode<ParseNode.ImportSpecifier>();
|
||||
const name = this.parseModuleExportName();
|
||||
if (name.type === 'StringLiteral' || this.test('as')) {
|
||||
this.expect('as');
|
||||
node.ModuleExportName = name;
|
||||
node.ImportedBinding = this.parseBindingIdentifier();
|
||||
} else {
|
||||
node.ImportedBinding = this.repurpose(name, 'BindingIdentifier');
|
||||
if (isKeywordRaw(node.ImportedBinding.name)) {
|
||||
this.raiseEarly('UnexpectedToken', node.ImportedBinding);
|
||||
}
|
||||
if (node.ImportedBinding.name === 'eval' || node.ImportedBinding.name === 'arguments') {
|
||||
this.raiseEarly('UnexpectedToken', node.ImportedBinding);
|
||||
}
|
||||
}
|
||||
return this.finishNode(node, 'ImportSpecifier');
|
||||
}
|
||||
|
||||
// ExportDeclaration :
|
||||
// `export` ExportFromClause FromClause `;`
|
||||
// `export` NamedExports `;`
|
||||
// `export` VariableStatement
|
||||
// `export` Declaration
|
||||
// DecoratorList? `export` Declaration
|
||||
// `export` `default` HoistableDeclaration
|
||||
// DecoratorList? `export` `default` ClassDeclaration
|
||||
// `export` `default` AssignmentExpression `;`
|
||||
//
|
||||
// ExportFromClause :
|
||||
// `*`
|
||||
// `*` as ModuleExportName
|
||||
// NamedExports
|
||||
parseExportDeclaration(decoratorsBeforeExportKeyword: null | readonly ParseNode.Decorator[]): ParseNode.ExportDeclaration {
|
||||
const node = this.startNode<ParseNode.ExportDeclaration>();
|
||||
node.Decorators = decoratorsBeforeExportKeyword;
|
||||
this.expect(Token.EXPORT);
|
||||
node.default = this.eat(Token.DEFAULT);
|
||||
if (node.default) {
|
||||
switch (this.peek().type) {
|
||||
case Token.FUNCTION:
|
||||
node.HoistableDeclaration = this.scope.with({ default: true }, () => this.parseFunctionDeclaration(FunctionKind.NORMAL));
|
||||
break;
|
||||
case Token.AT: {
|
||||
const decorators = this.parseDecorators();
|
||||
node.ClassDeclaration = this.scope.with({ default: true }, () => this.parseClassDeclaration(decorators));
|
||||
break;
|
||||
}
|
||||
case Token.CLASS:
|
||||
node.ClassDeclaration = this.scope.with({ default: true }, () => this.parseClassDeclaration(null));
|
||||
break;
|
||||
default:
|
||||
if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) {
|
||||
node.HoistableDeclaration = this.scope.with({ default: true }, () => this.parseFunctionDeclaration(FunctionKind.ASYNC));
|
||||
} else {
|
||||
node.AssignmentExpression = this.parseAssignmentExpression();
|
||||
this.semicolon();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this.scope.exports.has('default')) {
|
||||
this.raiseEarly('AlreadyDeclared', node, 'default');
|
||||
} else {
|
||||
this.scope.exports.add('default');
|
||||
}
|
||||
} else {
|
||||
switch (this.peek().type) {
|
||||
case Token.CONST:
|
||||
node.Declaration = this.parseLexicalDeclaration();
|
||||
this.scope.declare(node.Declaration, 'export');
|
||||
break;
|
||||
case Token.AT:
|
||||
case Token.CLASS:
|
||||
node.Declaration = this.parseClassDeclaration(null);
|
||||
this.scope.declare(node.Declaration, 'export');
|
||||
break;
|
||||
case Token.FUNCTION:
|
||||
node.Declaration = this.parseHoistableDeclaration();
|
||||
this.scope.declare(node.Declaration, 'export');
|
||||
break;
|
||||
case Token.VAR:
|
||||
node.VariableStatement = this.parseVariableStatement();
|
||||
this.scope.declare(node.VariableStatement, 'export');
|
||||
break;
|
||||
case Token.LBRACE: {
|
||||
const NamedExports = this.parseNamedExports();
|
||||
if (this.test('from')) {
|
||||
node.ExportFromClause = NamedExports;
|
||||
node.FromClause = this.parseFromClause();
|
||||
if (this.test(Token.WITH)) {
|
||||
node.WithClause = this.parseWithClause();
|
||||
}
|
||||
} else {
|
||||
NamedExports.ExportsList.forEach((n) => {
|
||||
if (n.localName.type === 'StringLiteral') {
|
||||
this.raiseEarly('UnexpectedToken', n.localName);
|
||||
}
|
||||
});
|
||||
node.NamedExports = NamedExports;
|
||||
this.scope.checkUndefinedExports(node.NamedExports);
|
||||
}
|
||||
this.semicolon();
|
||||
break;
|
||||
}
|
||||
case Token.MUL: {
|
||||
const inner = this.startNode<ParseNode.ExportFromClause>();
|
||||
this.next();
|
||||
if (this.eat('as')) {
|
||||
inner.ModuleExportName = this.parseModuleExportName();
|
||||
this.scope.declare(inner.ModuleExportName, 'export');
|
||||
}
|
||||
node.ExportFromClause = this.finishNode(inner, 'ExportFromClause');
|
||||
node.FromClause = this.parseFromClause();
|
||||
if (this.test(Token.WITH)) {
|
||||
node.WithClause = this.parseWithClause();
|
||||
}
|
||||
this.semicolon();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (this.test('let')) {
|
||||
node.Declaration = this.parseLexicalDeclaration();
|
||||
this.scope.declare(node.Declaration, 'export');
|
||||
} else if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) {
|
||||
node.Declaration = this.parseHoistableDeclaration();
|
||||
this.scope.declare(node.Declaration, 'export');
|
||||
} else {
|
||||
this.unexpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.finishNode(node, 'ExportDeclaration');
|
||||
}
|
||||
|
||||
// NamedExports :
|
||||
// `{` `}`
|
||||
// `{` ExportsList `}`
|
||||
// `{` ExportsList `,` `}`
|
||||
parseNamedExports(): ParseNode.NamedExports {
|
||||
const node = this.startNode<ParseNode.NamedExports>();
|
||||
this.expect(Token.LBRACE);
|
||||
const ExportsList: Mutable<ParseNode.ExportsList> = [];
|
||||
node.ExportsList = ExportsList;
|
||||
while (!this.eat(Token.RBRACE)) {
|
||||
ExportsList.push(this.parseExportSpecifier());
|
||||
if (this.eat(Token.RBRACE)) {
|
||||
break;
|
||||
}
|
||||
this.expect(Token.COMMA);
|
||||
}
|
||||
return this.finishNode(node, 'NamedExports');
|
||||
}
|
||||
|
||||
// ExportSpecifier :
|
||||
// ModuleExportName
|
||||
// ModuleExportName `as` ModuleExportName
|
||||
parseExportSpecifier(): ParseNode.ExportSpecifier {
|
||||
const node = this.startNode<ParseNode.ExportSpecifier>();
|
||||
node.localName = this.parseModuleExportName();
|
||||
if (this.eat('as')) {
|
||||
node.exportName = this.parseModuleExportName();
|
||||
} else {
|
||||
node.exportName = node.localName;
|
||||
}
|
||||
this.scope.declare(node.exportName, 'export');
|
||||
return this.finishNode(node, 'ExportSpecifier');
|
||||
}
|
||||
|
||||
// ModuleExportName :
|
||||
// IdentifierName
|
||||
// StringLiteral
|
||||
parseModuleExportName(): ParseNode.ModuleExportName {
|
||||
if (this.test(Token.STRING)) {
|
||||
const literal = this.parseStringLiteral();
|
||||
if (!IsStringWellFormedUnicode(StringValue(literal))) {
|
||||
this.raiseEarly('ModuleExportNameInvalidUnicode', literal);
|
||||
}
|
||||
return literal;
|
||||
}
|
||||
return this.parseIdentifierName();
|
||||
}
|
||||
|
||||
// FromClause :
|
||||
// `from` ModuleSpecifier
|
||||
parseFromClause(): ParseNode.FromClause {
|
||||
this.expect('from');
|
||||
return this.parseStringLiteral();
|
||||
}
|
||||
|
||||
// WithClause :
|
||||
// `with` `{` `}`
|
||||
// `with` `{` WithEntries `,`? `}`
|
||||
parseWithClause(): ParseNode.WithClause {
|
||||
const node = this.startNode<ParseNode.WithClause>();
|
||||
this.expect(Token.WITH);
|
||||
this.expect(Token.LBRACE);
|
||||
|
||||
const seenKeys = new Set<string>();
|
||||
|
||||
const WithEntries = [];
|
||||
while (!this.eat(Token.RBRACE)) {
|
||||
const entry = this.parseWithEntry();
|
||||
|
||||
const key = StringValue(entry.AttributeKey).value;
|
||||
if (seenKeys.has(key)) {
|
||||
this.raiseEarly('DuplicateImportAttribute', entry, key);
|
||||
}
|
||||
seenKeys.add(key);
|
||||
|
||||
WithEntries.push(entry);
|
||||
if (this.eat(Token.RBRACE)) {
|
||||
break;
|
||||
}
|
||||
this.expect(Token.COMMA);
|
||||
}
|
||||
node.WithEntries = WithEntries;
|
||||
|
||||
return this.finishNode(node, 'WithClause');
|
||||
}
|
||||
|
||||
parseWithEntry(): ParseNode.WithEntry {
|
||||
const node = this.startNode<ParseNode.WithEntry>();
|
||||
node.AttributeKey = this.test(Token.STRING) ? this.parseStringLiteral() : this.parseIdentifierName();
|
||||
this.expect(Token.COLON);
|
||||
node.AttributeValue = this.parseStringLiteral();
|
||||
return this.finishNode(node, 'WithEntry');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,201 @@
|
||||
import { surroundingAgent, type Feature } from '../host-defined/engine.mts';
|
||||
import * as messages from '../messages.mts';
|
||||
import { LanguageParser } from './LanguageParser.mts';
|
||||
import { isLineTerminator, type Locatable } from './Lexer.mts';
|
||||
import type {
|
||||
Location,
|
||||
ParseNode,
|
||||
ParseNodesByType,
|
||||
Position,
|
||||
} from './ParseNode.mts';
|
||||
import { Scope } from './Scope.mts';
|
||||
import { Token } from './tokens.mts';
|
||||
|
||||
export interface ParserOptions {
|
||||
readonly source: string;
|
||||
readonly specifier?: string;
|
||||
readonly json?: boolean;
|
||||
readonly allowAllPrivateNames?: boolean;
|
||||
}
|
||||
|
||||
export class Parser extends LanguageParser {
|
||||
protected readonly source: string;
|
||||
|
||||
protected readonly specifier?: string;
|
||||
|
||||
/** @deprecated migrating to earlyErrors2... */
|
||||
readonly earlyErrors: Set<SyntaxError>;
|
||||
|
||||
readonly state: {
|
||||
hasTopLevelAwait: boolean;
|
||||
strict: boolean;
|
||||
json: boolean;
|
||||
allowAllPrivateNames: boolean;
|
||||
};
|
||||
|
||||
readonly scope = new Scope(this);
|
||||
|
||||
constructor({
|
||||
source, specifier, json = false, allowAllPrivateNames = false,
|
||||
}: ParserOptions) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.specifier = specifier;
|
||||
this.earlyErrors = new Set();
|
||||
this.state = {
|
||||
hasTopLevelAwait: false,
|
||||
strict: false,
|
||||
json,
|
||||
allowAllPrivateNames,
|
||||
};
|
||||
}
|
||||
|
||||
isStrictMode() {
|
||||
return this.state.strict;
|
||||
}
|
||||
|
||||
feature(name: Feature) {
|
||||
return surroundingAgent.feature(name);
|
||||
}
|
||||
|
||||
startNode<T extends ParseNode>(inheritStart?: ParseNode.BaseParseNode): ParseNode.Unfinished<T>;
|
||||
|
||||
startNode(inheritStart?: ParseNode.BaseParseNode): ParseNode.Unfinished {
|
||||
this.peek();
|
||||
const s = this.source;
|
||||
const node: ParseNode.BaseParseNode = {
|
||||
type: undefined!,
|
||||
parent: 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,
|
||||
get sourceText() {
|
||||
return s.slice(node.location.startIndex, node.location.endIndex);
|
||||
},
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
markNodeStart(node: ParseNode.Unfinished) {
|
||||
node.location.startIndex = this.peekToken.startIndex;
|
||||
node.location.start = {
|
||||
line: this.peekToken.line,
|
||||
column: this.peekToken.column,
|
||||
};
|
||||
}
|
||||
|
||||
finishNode<T extends ParseNode.Unfinished, K extends T['type'] & ParseNode['type']>(node: T, type: K): ParseNodesByType[K];
|
||||
|
||||
finishNode(node: ParseNode.Unfinished, type: ParseNode['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<K extends keyof typeof messages>(context: number | Locatable = this.peek(), template: K, templateArgs: Parameters<typeof messages[K]>): SyntaxError {
|
||||
if (template === 'UnexpectedToken' && typeof context !== 'number' && 'type' in context && context.type === Token.EOS) {
|
||||
return this.createSyntaxError(context, '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 ('type' in context && 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 ('location' in context && context.location) {
|
||||
context = context.location;
|
||||
}
|
||||
({
|
||||
startIndex,
|
||||
endIndex,
|
||||
start: {
|
||||
line,
|
||||
column,
|
||||
} = context as Position, // NOTE: unsound cast
|
||||
} = context as Location); // NOTE: unsound cast
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 message = messages[template] as (...args: Parameters<typeof messages[K]>) => string;
|
||||
const e = new SyntaxError(message(...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<K extends keyof typeof messages>(template: K, context?: number | Locatable, ...templateArgs: Parameters<typeof messages[K]>) {
|
||||
const e = this.createSyntaxError(context, template, templateArgs);
|
||||
this.earlyErrors.add(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
raise<K extends keyof typeof messages>(template: K, context?: number | Locatable, ...templateArgs: Parameters<typeof messages[K]>): never {
|
||||
const e = this.createSyntaxError(context, template, templateArgs);
|
||||
throw e;
|
||||
}
|
||||
|
||||
unexpected(...args: [(number | Locatable)?, ...Parameters<typeof messages['UnexpectedToken']>]) {
|
||||
return this.raise('UnexpectedToken', ...args);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,538 @@
|
||||
import { Assert, Parser } from '../index.mts';
|
||||
import { isArray, OutOfRange } from '../helpers.mts';
|
||||
import type { TokenData } from './Lexer.mts';
|
||||
import type { ParseNode } from './ParseNode.mts';
|
||||
|
||||
export enum Flag {
|
||||
return = 1 << 0,
|
||||
await = 1 << 1,
|
||||
yield = 1 << 2,
|
||||
parameters = 1 << 3,
|
||||
newTarget = 1 << 4,
|
||||
importMeta = 1 << 5,
|
||||
superCall = 1 << 6,
|
||||
superProperty = 1 << 7,
|
||||
in = 1 << 8,
|
||||
default = 1 << 9,
|
||||
module = 1 << 10,
|
||||
classStaticBlock = 1 << 11,
|
||||
}
|
||||
|
||||
export interface DeclarationInfo {
|
||||
readonly name: string;
|
||||
readonly node: ParseNode;
|
||||
}
|
||||
|
||||
export function getDeclarations(node: ParseNode | readonly ParseNode[]): DeclarationInfo[] {
|
||||
if (isArray(node)) {
|
||||
return node.flatMap((n) => getDeclarations(n));
|
||||
}
|
||||
switch (node.type) {
|
||||
case 'LexicalBinding':
|
||||
case 'VariableDeclaration':
|
||||
case 'BindingRestElement':
|
||||
case 'ForBinding':
|
||||
if (node.BindingIdentifier) {
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
}
|
||||
if (node.BindingPattern) {
|
||||
return getDeclarations(node.BindingPattern);
|
||||
}
|
||||
return [];
|
||||
case 'BindingRestProperty':
|
||||
if (node.BindingIdentifier) {
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
}
|
||||
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 'PrivateIdentifier':
|
||||
return [{ name: `#${node.name}`, node }];
|
||||
case 'StringLiteral':
|
||||
return [{ name: node.value, node }];
|
||||
case 'Elision':
|
||||
return [];
|
||||
case 'ForDeclaration':
|
||||
return getDeclarations(node.ForBinding);
|
||||
case 'ExportSpecifier':
|
||||
return getDeclarations(node.exportName);
|
||||
case 'FunctionDeclaration':
|
||||
case 'GeneratorDeclaration':
|
||||
case 'AsyncFunctionDeclaration':
|
||||
case 'AsyncGeneratorDeclaration':
|
||||
Assert(!!node.BindingIdentifier);
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
case 'LexicalDeclaration':
|
||||
return getDeclarations(node.BindingList);
|
||||
case 'VariableStatement':
|
||||
return getDeclarations(node.VariableDeclarationList);
|
||||
case 'ClassDeclaration':
|
||||
Assert(!!node.BindingIdentifier);
|
||||
return getDeclarations(node.BindingIdentifier);
|
||||
default:
|
||||
throw new OutOfRange('getDeclarations', node);
|
||||
}
|
||||
}
|
||||
|
||||
export type ScopeFlagSetters =
|
||||
& { readonly [P in (keyof typeof Flag) & string]?: boolean; }
|
||||
& {
|
||||
readonly lexical?: boolean;
|
||||
readonly variable?: boolean;
|
||||
readonly variableFunctions?: boolean;
|
||||
readonly private?: boolean;
|
||||
readonly label?: LabelType | 'boundary';
|
||||
readonly strict?: boolean;
|
||||
};
|
||||
|
||||
export interface ScopeInfo {
|
||||
readonly flags: ScopeFlagSetters;
|
||||
readonly lexicals: Set<string>;
|
||||
readonly variables: Set<string>;
|
||||
readonly functions: Set<string>;
|
||||
readonly parameters: Set<string>;
|
||||
}
|
||||
|
||||
export interface PrivateScopeInfo {
|
||||
readonly outer: PrivateScopeInfo | undefined;
|
||||
readonly names: Map<string, Set<'field' | 'method' | 'get' | 'set'>>;
|
||||
}
|
||||
|
||||
export interface UndefinedPrivateAccessInfo {
|
||||
readonly node: ParseNode;
|
||||
readonly name: string;
|
||||
readonly scope: PrivateScopeInfo | undefined;
|
||||
}
|
||||
|
||||
export interface ArrowInfo {
|
||||
readonly isAsync: boolean;
|
||||
hasTrailingComma: boolean;
|
||||
readonly yieldExpressions: ParseNode[];
|
||||
readonly awaitExpressions: ParseNode[];
|
||||
readonly awaitIdentifiers: ParseNode[];
|
||||
merge(other: ArrowInfo): void;
|
||||
}
|
||||
|
||||
export interface AssignmentInfo {
|
||||
readonly type: 'assign' | 'arrow' | 'for';
|
||||
readonly earlyErrors: SyntaxError[];
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
export type LabelType = 'switch' | 'loop';
|
||||
|
||||
export interface Label {
|
||||
type: LabelType | null;
|
||||
readonly name?: string;
|
||||
readonly nextToken?: TokenData | null;
|
||||
}
|
||||
|
||||
export class Scope {
|
||||
private readonly parser: Parser;
|
||||
|
||||
private readonly scopeStack: ScopeInfo[] = [];
|
||||
|
||||
labels: Label[] = [];
|
||||
|
||||
readonly arrowInfoStack: (ArrowInfo | null)[] = [];
|
||||
|
||||
readonly assignmentInfoStack: AssignmentInfo[] = [];
|
||||
|
||||
readonly exports = new Set<string>();
|
||||
|
||||
readonly undefinedExports = new Map<string, ParseNode.ModuleExportName>();
|
||||
|
||||
privateScope: PrivateScopeInfo | undefined;
|
||||
|
||||
private readonly undefinedPrivateAccesses: UndefinedPrivateAccessInfo[] = [];
|
||||
|
||||
private flags: Flag = 0 as Flag;
|
||||
|
||||
constructor(parser: Parser) {
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
inClassStaticBlock() {
|
||||
return (this.flags & Flag.classStaticBlock) !== 0;
|
||||
}
|
||||
|
||||
isDefault() {
|
||||
return (this.flags & Flag.default) !== 0;
|
||||
}
|
||||
|
||||
isModule() {
|
||||
return (this.flags & Flag.module) !== 0;
|
||||
}
|
||||
|
||||
with<R>(flags: ScopeFlagSetters, f: () => R) {
|
||||
const oldFlags = this.flags;
|
||||
|
||||
Object.entries(flags)
|
||||
.forEach(([k, v]) => {
|
||||
if (k in Flag && typeof Flag[k as keyof typeof Flag] === 'number') {
|
||||
if (v === true) {
|
||||
this.flags |= Flag[k as keyof typeof Flag];
|
||||
} else if (v === false) {
|
||||
this.flags &= ~Flag[k as keyof typeof Flag];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (flags.lexical || flags.variable) {
|
||||
this.scopeStack.push({
|
||||
flags,
|
||||
lexicals: new Set(),
|
||||
variables: new Set(),
|
||||
functions: new Set(),
|
||||
parameters: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
if (flags.private) {
|
||||
this.privateScope = {
|
||||
outer: this.privateScope,
|
||||
names: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
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.private) {
|
||||
this.privateScope = this.privateScope!.outer;
|
||||
|
||||
if (this.privateScope === undefined) {
|
||||
this.undefinedPrivateAccesses.forEach(({ node, name, scope }) => {
|
||||
while (scope) {
|
||||
if (scope.names.has(name)) {
|
||||
return;
|
||||
}
|
||||
scope = scope.outer;
|
||||
}
|
||||
this.parser.raiseEarly('NotDefined', node, name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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: [],
|
||||
merge(other) {
|
||||
this.yieldExpressions.push(...other.yieldExpressions);
|
||||
this.awaitExpressions.push(...other.awaitExpressions);
|
||||
this.awaitIdentifiers.push(...other.awaitIdentifiers);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
popArrowInfo() {
|
||||
const arrowInfo = this.arrowInfoStack.pop();
|
||||
Assert(!!arrowInfo);
|
||||
return arrowInfo;
|
||||
}
|
||||
|
||||
get arrowInfo() {
|
||||
if (this.arrowInfoStack.length > 0) {
|
||||
return this.arrowInfoStack[this.arrowInfoStack.length - 1];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
pushAssignmentInfo(type: 'assign' | 'arrow' | 'for') {
|
||||
const parser = this.parser;
|
||||
this.assignmentInfoStack.push({
|
||||
type,
|
||||
earlyErrors: [],
|
||||
clear() {
|
||||
this.earlyErrors.forEach((e) => {
|
||||
parser.earlyErrors.delete(e);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
popAssignmentInfo() {
|
||||
const assignmentInfo = this.assignmentInfoStack.pop();
|
||||
Assert(!!assignmentInfo);
|
||||
return assignmentInfo;
|
||||
}
|
||||
|
||||
registerObjectLiteralEarlyError(error: SyntaxError) {
|
||||
for (let i = this.assignmentInfoStack.length - 1; i >= 0; i -= 1) {
|
||||
const info = this.assignmentInfoStack[i];
|
||||
info.earlyErrors.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;
|
||||
}
|
||||
}
|
||||
/* node:coverage 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;
|
||||
}
|
||||
}
|
||||
/* node:coverage ignore next */
|
||||
throw new RangeError();
|
||||
}
|
||||
|
||||
declare(node: ParseNode | readonly ParseNode[], type: 'private', extraType?: 'field' | 'method' | 'get' | 'set'): void;
|
||||
|
||||
declare(node: ParseNode | readonly ParseNode[], type: 'lexical' | 'import' | 'function' | 'parameter' | 'variable' | 'export'): void;
|
||||
|
||||
declare(node: ParseNode | readonly ParseNode[], type: 'lexical' | 'import' | 'function' | 'parameter' | 'variable' | 'export' | 'private', extraType?: 'field' | 'method' | 'get' | 'set') {
|
||||
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;
|
||||
case 'private': {
|
||||
const types = this.privateScope!.names.get(d.name);
|
||||
if (types) {
|
||||
let duplicate = true;
|
||||
switch (extraType) {
|
||||
case 'field':
|
||||
case 'method':
|
||||
break;
|
||||
case 'set':
|
||||
case 'get':
|
||||
duplicate = types.has(extraType) || types.has('field') || types.has('method');
|
||||
types.add(extraType);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (duplicate) {
|
||||
this.parser.raiseEarly('AlreadyDeclared', d.node, d.name);
|
||||
}
|
||||
} else if (extraType) {
|
||||
this.privateScope!.names.set(d.name, new Set([extraType]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* node:coverage ignore next 2 */
|
||||
default:
|
||||
throw new RangeError(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkUndefinedExports(NamedExports: ParseNode.NamedExports) {
|
||||
const scope = this.variableScope();
|
||||
NamedExports.ExportsList.forEach((n) => {
|
||||
const name = n.localName.type === 'IdentifierName' ? n.localName.name : n.localName.value;
|
||||
if (!scope.lexicals.has(name) && !scope.variables.has(name)) {
|
||||
this.undefinedExports.set(name, n.localName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkUndefinedPrivate(PrivateIdentifier: ParseNode.PrivateIdentifier) {
|
||||
if (this.parser.state.allowAllPrivateNames) {
|
||||
return;
|
||||
}
|
||||
const [{ node, name }] = getDeclarations(PrivateIdentifier);
|
||||
|
||||
if (!this.privateScope) {
|
||||
this.parser.raiseEarly('NotDefined', node, name);
|
||||
return;
|
||||
}
|
||||
|
||||
let scope: PrivateScopeInfo | undefined = this.privateScope;
|
||||
while (scope) {
|
||||
if (scope.names.has(name)) {
|
||||
return;
|
||||
}
|
||||
scope = scope.outer;
|
||||
}
|
||||
|
||||
this.undefinedPrivateAccesses.push({
|
||||
node,
|
||||
name,
|
||||
scope: this.privateScope,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,959 @@
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { Token, isAutomaticSemicolon } from './tokens.mts';
|
||||
import { ExpressionParser } from './ExpressionParser.mts';
|
||||
import { FunctionKind } from './FunctionParser.mts';
|
||||
import { getDeclarations, type LabelType } from './Scope.mts';
|
||||
import type { ParseNode } from './ParseNode.mts';
|
||||
|
||||
export abstract class StatementParser extends ExpressionParser {
|
||||
eatSemicolonWithASI() {
|
||||
if (this.eat(Token.SEMICOLON)) {
|
||||
return true;
|
||||
}
|
||||
if (this.peek().hadLineTerminatorBefore || isAutomaticSemicolon(this.peek().type)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
semicolon() {
|
||||
if (!this.eatSemicolonWithASI()) {
|
||||
this.unexpected();
|
||||
}
|
||||
}
|
||||
|
||||
// StatementList :
|
||||
// StatementListItem
|
||||
// StatementList StatementListItem
|
||||
/**
|
||||
* @param endToken endToken
|
||||
* @param directives directives, this array will be mutated.
|
||||
*/
|
||||
parseStatementList(endToken: string | Token, directives?: string[]): ParseNode.StatementList {
|
||||
const statementList: Mutable<ParseNode.StatementList> = [];
|
||||
const oldStrict = this.state.strict;
|
||||
const directiveData = [];
|
||||
while (!this.eat(endToken)) {
|
||||
if (directives !== undefined && this.test(Token.STRING)) {
|
||||
const token = this.peek();
|
||||
const directive = this.source.slice(token.startIndex + 1, token.endIndex - 1);
|
||||
if (directive === 'use strict') {
|
||||
this.state.strict = true;
|
||||
directiveData.forEach((d) => {
|
||||
if (/\\([1-9]|0\d)/.test(d.directive)) {
|
||||
this.raiseEarly('IllegalOctalEscape', d.token);
|
||||
}
|
||||
});
|
||||
}
|
||||
directives.push(directive);
|
||||
directiveData.push({ directive, token });
|
||||
} else {
|
||||
directives = undefined;
|
||||
}
|
||||
|
||||
const stmt = this.parseStatementListItem();
|
||||
statementList.push(stmt);
|
||||
}
|
||||
|
||||
this.state.strict = oldStrict;
|
||||
|
||||
return statementList;
|
||||
}
|
||||
|
||||
// StatementListItem :
|
||||
// Statement
|
||||
// Declaration
|
||||
//
|
||||
// Declaration :
|
||||
// HoistableDeclaration
|
||||
// ClassDeclaration
|
||||
// LexicalDeclaration
|
||||
parseStatementListItem(): ParseNode.StatementListItem {
|
||||
switch (this.peek().type) {
|
||||
case Token.FUNCTION:
|
||||
return this.parseHoistableDeclaration();
|
||||
case Token.AT:
|
||||
case Token.CLASS:
|
||||
return this.parseClassDeclaration(null);
|
||||
case Token.CONST:
|
||||
return this.parseLexicalDeclaration();
|
||||
default:
|
||||
if (this.test('let')) {
|
||||
switch (this.peekAhead().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.LBRACK:
|
||||
case Token.IDENTIFIER:
|
||||
case Token.YIELD:
|
||||
case Token.AWAIT:
|
||||
return this.parseLexicalDeclaration();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) {
|
||||
return this.parseHoistableDeclaration();
|
||||
}
|
||||
return this.parseStatement();
|
||||
}
|
||||
}
|
||||
|
||||
// HoistableDeclaration :
|
||||
// FunctionDeclaration
|
||||
// GeneratorDeclaration
|
||||
// AsyncFunctionDeclaration
|
||||
// AsyncGeneratorDeclaration
|
||||
parseHoistableDeclaration(): ParseNode.HoistableDeclaration {
|
||||
switch (this.peek().type) {
|
||||
case Token.FUNCTION:
|
||||
return this.parseFunctionDeclaration(FunctionKind.NORMAL);
|
||||
default:
|
||||
if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) {
|
||||
return this.parseFunctionDeclaration(FunctionKind.ASYNC);
|
||||
}
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
}
|
||||
|
||||
// ClassDeclaration :
|
||||
// `class` BindingIdentifier ClassTail
|
||||
// [+Default] `class` ClassTail
|
||||
parseClassDeclaration(decoratorsAttachedToClassDeclaration: null | readonly ParseNode.Decorator[]): ParseNode.ClassDeclaration {
|
||||
return this.parseClass(decoratorsAttachedToClassDeclaration, false) as ParseNode.ClassDeclaration;
|
||||
}
|
||||
|
||||
// LexicalDeclaration : LetOrConst BindingList `;`
|
||||
parseLexicalDeclaration(): ParseNode.LexicalDeclarationLike {
|
||||
const node = this.startNode<ParseNode.LexicalDeclaration>();
|
||||
const letOrConst = this.eat('let') ? 'let' : this.expect(Token.CONST) && 'const';
|
||||
node.LetOrConst = letOrConst;
|
||||
node.BindingList = this.parseBindingList();
|
||||
this.semicolon();
|
||||
|
||||
this.scope.declare(node.BindingList, 'lexical');
|
||||
node.BindingList.forEach((b) => {
|
||||
if (node.LetOrConst === 'const' && !b.Initializer) {
|
||||
this.raiseEarly('ConstDeclarationMissingInitializer', b);
|
||||
}
|
||||
});
|
||||
|
||||
return this.finishNode(node, 'LexicalDeclaration');
|
||||
}
|
||||
|
||||
// BindingList :
|
||||
// LexicalBinding
|
||||
// BindingList `,` LexicalBinding
|
||||
//
|
||||
// LexicalBinding :
|
||||
// BindingIdentifier Initializer?
|
||||
// BindingPattern Initializer
|
||||
parseBindingList(): ParseNode.BindingList {
|
||||
const bindingList: Mutable<ParseNode.BindingList> = [];
|
||||
do {
|
||||
const node = this.parseBindingElement();
|
||||
bindingList.push(this.repurpose(node, 'LexicalBinding'));
|
||||
} while (this.eat(Token.COMMA));
|
||||
return bindingList;
|
||||
}
|
||||
|
||||
// BindingElement :
|
||||
// SingleNameBinding
|
||||
// BindingPattern Initializer?
|
||||
// SingleNameBinding :
|
||||
// BindingIdentifier Initializer?
|
||||
parseBindingElement(): ParseNode.BindingElementLike {
|
||||
const node = this.startNode<ParseNode.BindingElementLike>();
|
||||
if (this.test(Token.LBRACE) || this.test(Token.LBRACK)) {
|
||||
node.BindingPattern = this.parseBindingPattern();
|
||||
} else {
|
||||
node.BindingIdentifier = this.parseBindingIdentifier();
|
||||
}
|
||||
node.Initializer = this.parseInitializerOpt();
|
||||
return this.finishNode(node, node.BindingPattern ? 'BindingElement' : 'SingleNameBinding');
|
||||
}
|
||||
|
||||
// BindingPattern:
|
||||
// ObjectBindingPattern
|
||||
// ArrayBindingPattern
|
||||
parseBindingPattern(): ParseNode.BindingPattern {
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
return this.parseObjectBindingPattern();
|
||||
case Token.LBRACK:
|
||||
return this.parseArrayBindingPattern();
|
||||
default:
|
||||
return this.unexpected();
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectBindingPattern :
|
||||
// `{` `}`
|
||||
// `{` BindingRestProperty `}`
|
||||
// `{` BindingPropertyList `}`
|
||||
// `{` BindingPropertyList `,` BindingRestProperty? `}`
|
||||
parseObjectBindingPattern(): ParseNode.ObjectBindingPattern {
|
||||
const node = this.startNode<ParseNode.ObjectBindingPattern>();
|
||||
this.expect(Token.LBRACE);
|
||||
const BindingPropertyList: Mutable<ParseNode.BindingPropertyList> = [];
|
||||
node.BindingPropertyList = BindingPropertyList;
|
||||
while (!this.eat(Token.RBRACE)) {
|
||||
if (this.test(Token.ELLIPSIS)) {
|
||||
node.BindingRestProperty = this.parseBindingRestProperty();
|
||||
this.expect(Token.RBRACE);
|
||||
break;
|
||||
} else {
|
||||
BindingPropertyList.push(this.parseBindingProperty());
|
||||
if (!this.eat(Token.COMMA)) {
|
||||
this.expect(Token.RBRACE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.finishNode(node, 'ObjectBindingPattern');
|
||||
}
|
||||
|
||||
// BindingProperty :
|
||||
// SingleNameBinding
|
||||
// PropertyName : BindingElement
|
||||
parseBindingProperty(): ParseNode.BindingPropertyLike {
|
||||
const node = this.startNode<ParseNode.BindingProperty | ParseNode.SingleNameBinding>();
|
||||
const name = this.parsePropertyName();
|
||||
if (this.eat(Token.COLON)) {
|
||||
node.PropertyName = name;
|
||||
node.BindingElement = this.parseBindingElement();
|
||||
return this.finishNode(node, 'BindingProperty');
|
||||
} else {
|
||||
if (name.type !== 'IdentifierName') {
|
||||
this.unexpected(name);
|
||||
}
|
||||
this.validateIdentifierReference(name.name, node);
|
||||
}
|
||||
node.BindingIdentifier = this.repurpose(name, 'BindingIdentifier');
|
||||
node.Initializer = this.parseInitializerOpt();
|
||||
return this.finishNode(node, 'SingleNameBinding');
|
||||
}
|
||||
|
||||
// BindingRestProperty :
|
||||
// `...` BindingIdentifier
|
||||
parseBindingRestProperty(): ParseNode.BindingRestProperty {
|
||||
const node = this.startNode<ParseNode.BindingRestProperty>();
|
||||
this.expect(Token.ELLIPSIS);
|
||||
node.BindingIdentifier = this.parseBindingIdentifier();
|
||||
return this.finishNode(node, 'BindingRestProperty');
|
||||
}
|
||||
|
||||
// ArrayBindingPattern :
|
||||
// `[` Elision? BindingRestElement `]`
|
||||
// `[` BindingElementList `]`
|
||||
// `[` BindingElementList `,` Elision? BindingRestElement `]`
|
||||
parseArrayBindingPattern(): ParseNode.ArrayBindingPattern {
|
||||
const node = this.startNode<ParseNode.ArrayBindingPattern>();
|
||||
this.expect(Token.LBRACK);
|
||||
const BindingElementList: Mutable<ParseNode.BindingElementList> = [];
|
||||
node.BindingElementList = BindingElementList;
|
||||
while (true) {
|
||||
while (this.test(Token.COMMA)) {
|
||||
const elision = this.startNode<ParseNode.Elision>();
|
||||
this.next();
|
||||
BindingElementList.push(this.finishNode(elision, 'Elision'));
|
||||
}
|
||||
if (this.eat(Token.RBRACK)) {
|
||||
break;
|
||||
}
|
||||
if (this.test(Token.ELLIPSIS)) {
|
||||
node.BindingRestElement = this.parseBindingRestElement();
|
||||
this.expect(Token.RBRACK);
|
||||
break;
|
||||
} else {
|
||||
BindingElementList.push(this.parseBindingElement());
|
||||
}
|
||||
if (this.eat(Token.RBRACK)) {
|
||||
break;
|
||||
}
|
||||
this.expect(Token.COMMA);
|
||||
}
|
||||
return this.finishNode(node, 'ArrayBindingPattern');
|
||||
}
|
||||
|
||||
// BindingRestElement :
|
||||
// `...` BindingIdentifier
|
||||
// `...` BindingPattern
|
||||
parseBindingRestElement(): ParseNode.BindingRestElement {
|
||||
const node = this.startNode<ParseNode.BindingRestElement>();
|
||||
this.expect(Token.ELLIPSIS);
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.LBRACK:
|
||||
node.BindingPattern = this.parseBindingPattern();
|
||||
break;
|
||||
default:
|
||||
node.BindingIdentifier = this.parseBindingIdentifier();
|
||||
break;
|
||||
}
|
||||
return this.finishNode(node, 'BindingRestElement');
|
||||
}
|
||||
|
||||
// Initializer : `=` AssignmentExpression
|
||||
parseInitializerOpt(): ParseNode.Initializer | null {
|
||||
if (this.eat(Token.ASSIGN)) {
|
||||
return this.parseAssignmentExpression();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// FunctionDeclaration
|
||||
parseFunctionDeclaration(kind: FunctionKind): ParseNode.FunctionDeclarationLike {
|
||||
return this.parseFunction(false, kind) as ParseNode.FunctionDeclarationLike;
|
||||
}
|
||||
|
||||
// Statement :
|
||||
// ...
|
||||
parseStatement(): ParseNode.Statement {
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
return this.parseBlockStatement();
|
||||
case Token.VAR:
|
||||
return this.parseVariableStatement();
|
||||
case Token.SEMICOLON: {
|
||||
const node = this.startNode<ParseNode.EmptyStatement>();
|
||||
this.next();
|
||||
return this.finishNode(node, 'EmptyStatement');
|
||||
}
|
||||
case Token.IF:
|
||||
return this.parseIfStatement();
|
||||
case Token.DO:
|
||||
return this.parseDoWhileStatement();
|
||||
case Token.WHILE:
|
||||
return this.parseWhileStatement();
|
||||
case Token.FOR:
|
||||
return this.parseForStatement();
|
||||
case Token.SWITCH:
|
||||
return this.parseSwitchStatement();
|
||||
case Token.CONTINUE:
|
||||
case Token.BREAK:
|
||||
return this.parseBreakContinueStatement();
|
||||
case Token.RETURN:
|
||||
return this.parseReturnStatement();
|
||||
case Token.WITH:
|
||||
return this.parseWithStatement();
|
||||
case Token.THROW:
|
||||
return this.parseThrowStatement();
|
||||
case Token.TRY:
|
||||
return this.parseTryStatement();
|
||||
case Token.DEBUGGER:
|
||||
return this.parseDebuggerStatement();
|
||||
default:
|
||||
return this.parseExpressionStatement();
|
||||
}
|
||||
}
|
||||
|
||||
// BlockStatement : Block
|
||||
parseBlockStatement(): ParseNode.BlockStatement {
|
||||
return this.parseBlock();
|
||||
}
|
||||
|
||||
// Block : `{` StatementList `}`
|
||||
parseBlock(lexical = true): ParseNode.Block {
|
||||
const node = this.startNode<ParseNode.Block>();
|
||||
this.expect(Token.LBRACE);
|
||||
node.StatementList = this.scope.with({ lexical }, () => this.parseStatementList(Token.RBRACE));
|
||||
return this.finishNode(node, 'Block');
|
||||
}
|
||||
|
||||
// VariableStatement : `var` VariableDeclarationList `;`
|
||||
parseVariableStatement(): ParseNode.VariableStatement {
|
||||
const node = this.startNode<ParseNode.VariableStatement>();
|
||||
this.expect(Token.VAR);
|
||||
node.VariableDeclarationList = this.parseVariableDeclarationList();
|
||||
this.semicolon();
|
||||
this.scope.declare(node.VariableDeclarationList, 'variable');
|
||||
return this.finishNode(node, 'VariableStatement');
|
||||
}
|
||||
|
||||
// VariableDeclarationList :
|
||||
// VariableDeclaration
|
||||
// VariableDeclarationList `,` VariableDeclaration
|
||||
parseVariableDeclarationList(firstDeclarationRequiresInit = true): ParseNode.VariableDeclarationList {
|
||||
const declarationList: Mutable<ParseNode.VariableDeclarationList> = [];
|
||||
do {
|
||||
const node = this.parseVariableDeclaration(firstDeclarationRequiresInit);
|
||||
declarationList.push(node);
|
||||
} while (this.eat(Token.COMMA));
|
||||
return declarationList;
|
||||
}
|
||||
|
||||
// VariableDeclaration :
|
||||
// BindingIdentifier Initializer?
|
||||
// BindingPattern Initializer
|
||||
parseVariableDeclaration(firstDeclarationRequiresInit: boolean): ParseNode.VariableDeclaration {
|
||||
const node = this.startNode<ParseNode.VariableDeclaration>();
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.LBRACK:
|
||||
node.BindingPattern = this.parseBindingPattern();
|
||||
if (firstDeclarationRequiresInit) {
|
||||
this.expect(Token.ASSIGN);
|
||||
node.Initializer = this.parseAssignmentExpression();
|
||||
} else {
|
||||
node.Initializer = this.parseInitializerOpt();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
node.BindingIdentifier = this.parseBindingIdentifier();
|
||||
node.Initializer = this.parseInitializerOpt();
|
||||
break;
|
||||
}
|
||||
return this.finishNode(node, 'VariableDeclaration');
|
||||
}
|
||||
|
||||
// IfStatement :
|
||||
// `if` `(` Expression `)` Statement `else` Statement
|
||||
// `if` `(` Expression `)` Statement [lookahead != `else`]
|
||||
parseIfStatement(): ParseNode.IfStatement {
|
||||
const node = this.startNode<ParseNode.IfStatement>();
|
||||
this.expect(Token.IF);
|
||||
this.expect(Token.LPAREN);
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement_a = this.parseStatement();
|
||||
if (this.eat(Token.ELSE)) {
|
||||
node.Statement_b = this.parseStatement();
|
||||
}
|
||||
return this.finishNode(node, 'IfStatement');
|
||||
}
|
||||
|
||||
// `while` `(` Expression `)` Statement
|
||||
parseWhileStatement(): ParseNode.WhileStatement {
|
||||
const node = this.startNode<ParseNode.WhileStatement>();
|
||||
this.expect(Token.WHILE);
|
||||
this.expect(Token.LPAREN);
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
this.scope.with({ label: 'loop' }, () => {
|
||||
node.Statement = this.parseStatement();
|
||||
});
|
||||
return this.finishNode(node, 'WhileStatement');
|
||||
}
|
||||
|
||||
// `do` Statement `while` `(` Expression `)` `;`
|
||||
parseDoWhileStatement(): ParseNode.DoWhileStatement {
|
||||
const node = this.startNode<ParseNode.DoWhileStatement>();
|
||||
this.expect(Token.DO);
|
||||
node.Statement = this.scope.with({ label: 'loop' }, () => this.parseStatement());
|
||||
this.expect(Token.WHILE);
|
||||
this.expect(Token.LPAREN);
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
// Semicolons are completely optional after a do-while, even without a newline
|
||||
this.eat(Token.SEMICOLON);
|
||||
return this.finishNode(node, 'DoWhileStatement');
|
||||
}
|
||||
|
||||
// `for` `(` [lookahead != `let` `[`] Expression? `;` Expression? `;` Expression? `)` Statement
|
||||
// `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement
|
||||
// `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement
|
||||
// `for` `(` [lookahead != `let` `[`] LeftHandSideExpression `in` Expression `)` Statement
|
||||
// `for` `(` `var` ForBinding `in` Expression `)` Statement
|
||||
// `for` `(` ForDeclaration `in` Expression `)` Statement
|
||||
// `for` `(` [lookahead != { `let`, `async` `of` }] LeftHandSideExpression `of` AssignmentExpression `)` Statement
|
||||
// `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement
|
||||
// `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement
|
||||
// `for` `await` `(` [lookahead != `let`] LeftHandSideExpression `of` AssignmentExpression `)` Statement
|
||||
// `for` `await` `(` `var` ForBinding `of` AssignmentExpression `)` Statement
|
||||
// `for` `await` `(` ForDeclaration `of` AssignmentExpression `)` Statement
|
||||
//
|
||||
// ForDeclaration : LetOrConst ForBinding
|
||||
parseForStatement(): ParseNode.ForStatement | ParseNode.ForInOfStatement {
|
||||
return this.scope.with({
|
||||
lexical: true,
|
||||
label: 'loop',
|
||||
}, () => {
|
||||
const node = this.startNode<ParseNode.ForStatement | ParseNode.ForInOfStatement>();
|
||||
this.expect(Token.FOR);
|
||||
const isAwait = this.scope.hasAwait() && this.eat(Token.AWAIT);
|
||||
if (isAwait && !this.scope.hasReturn()) {
|
||||
this.state.hasTopLevelAwait = true;
|
||||
}
|
||||
this.expect(Token.LPAREN);
|
||||
if (isAwait && this.test(Token.SEMICOLON)) {
|
||||
this.unexpected();
|
||||
}
|
||||
if (this.eat(Token.SEMICOLON)) {
|
||||
if (!this.test(Token.SEMICOLON)) {
|
||||
node.Expression_b = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.SEMICOLON);
|
||||
if (!this.test(Token.RPAREN)) {
|
||||
node.Expression_c = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForStatement');
|
||||
}
|
||||
const isLexicalStart = () => {
|
||||
switch (this.peekAhead().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.LBRACK:
|
||||
case Token.IDENTIFIER:
|
||||
case Token.YIELD:
|
||||
case Token.AWAIT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if ((this.test('let') || this.test(Token.CONST)) && isLexicalStart()) {
|
||||
const inner = this.startNode<ParseNode.LexicalDeclaration | ParseNode.ForDeclaration>();
|
||||
if (this.eat('let')) {
|
||||
inner.LetOrConst = 'let';
|
||||
} else {
|
||||
this.expect(Token.CONST);
|
||||
inner.LetOrConst = 'const';
|
||||
}
|
||||
const list = this.parseBindingList();
|
||||
this.scope.declare(list, 'lexical');
|
||||
if (list.length > 1 || this.test(Token.SEMICOLON)) {
|
||||
inner.BindingList = list;
|
||||
node.LexicalDeclaration = this.finishNode(inner, 'LexicalDeclaration');
|
||||
this.expect(Token.SEMICOLON);
|
||||
if (!this.test(Token.SEMICOLON)) {
|
||||
node.Expression_a = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.SEMICOLON);
|
||||
if (!this.test(Token.RPAREN)) {
|
||||
node.Expression_b = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForStatement');
|
||||
}
|
||||
inner.ForBinding = this.repurpose(list[0], 'ForBinding', (_, oldNode) => {
|
||||
if (oldNode.Initializer) {
|
||||
this.unexpected(oldNode.Initializer);
|
||||
}
|
||||
});
|
||||
node.ForDeclaration = this.finishNode(inner, 'ForDeclaration');
|
||||
getDeclarations(node.ForDeclaration)
|
||||
.forEach((d) => {
|
||||
if (d.name === 'let') {
|
||||
this.raiseEarly('UnexpectedToken', d.node);
|
||||
}
|
||||
});
|
||||
if (!isAwait && this.eat(Token.IN)) {
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForInStatement');
|
||||
}
|
||||
this.expect('of');
|
||||
node.AssignmentExpression = this.parseAssignmentExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, isAwait ? 'ForAwaitStatement' : 'ForOfStatement');
|
||||
}
|
||||
if (this.eat(Token.VAR)) {
|
||||
if (isAwait) {
|
||||
node.ForBinding = this.parseForBinding();
|
||||
this.expect('of');
|
||||
node.AssignmentExpression = this.parseAssignmentExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForAwaitStatement');
|
||||
}
|
||||
const list = this.parseVariableDeclarationList(false);
|
||||
if (list.length > 1 || this.test(Token.SEMICOLON)) {
|
||||
node.VariableDeclarationList = list;
|
||||
this.expect(Token.SEMICOLON);
|
||||
if (!this.test(Token.SEMICOLON)) {
|
||||
node.Expression_a = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.SEMICOLON);
|
||||
if (!this.test(Token.RPAREN)) {
|
||||
node.Expression_b = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForStatement');
|
||||
}
|
||||
node.ForBinding = this.repurpose(list[0], 'ForBinding', (_, oldNode) => {
|
||||
if (oldNode.Initializer) {
|
||||
this.unexpected(oldNode.Initializer);
|
||||
}
|
||||
});
|
||||
if (this.eat('of')) {
|
||||
node.AssignmentExpression = this.parseAssignmentExpression();
|
||||
} else {
|
||||
this.expect(Token.IN);
|
||||
node.Expression = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, node.AssignmentExpression ? 'ForOfStatement' : 'ForInStatement');
|
||||
}
|
||||
|
||||
this.scope.pushAssignmentInfo('for');
|
||||
const expression = this.scope.with({ in: false }, () => this.parseExpression());
|
||||
const validateLHS = (n: ParseNode) => {
|
||||
if (n.type === 'AssignmentExpression') {
|
||||
this.raiseEarly('UnexpectedToken', n);
|
||||
} else {
|
||||
this.validateAssignmentTarget(n);
|
||||
}
|
||||
};
|
||||
const assignmentInfo = this.scope.popAssignmentInfo();
|
||||
if (!isAwait && this.eat(Token.IN)) {
|
||||
assignmentInfo.clear();
|
||||
validateLHS(expression);
|
||||
node.LeftHandSideExpression = expression as ParseNode.LeftHandSideExpression; // NOTE: unsound cast
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForInStatement');
|
||||
}
|
||||
const isExactlyAsync = expression.type === 'IdentifierReference'
|
||||
&& !expression.escaped
|
||||
&& expression.name === 'async';
|
||||
if ((!isExactlyAsync || isAwait) && this.eat('of')) {
|
||||
assignmentInfo.clear();
|
||||
validateLHS(expression);
|
||||
node.LeftHandSideExpression = expression as ParseNode.LeftHandSideExpression; // NOTE: unsound cast
|
||||
node.AssignmentExpression = this.parseAssignmentExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, isAwait ? 'ForAwaitStatement' : 'ForOfStatement');
|
||||
}
|
||||
|
||||
node.Expression_a = expression;
|
||||
this.expect(Token.SEMICOLON);
|
||||
|
||||
if (!this.test(Token.SEMICOLON)) {
|
||||
node.Expression_b = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.SEMICOLON);
|
||||
|
||||
if (!this.test(Token.RPAREN)) {
|
||||
node.Expression_c = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.RPAREN);
|
||||
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'ForStatement');
|
||||
});
|
||||
}
|
||||
|
||||
// ForBinding :
|
||||
// BindingIdentifier
|
||||
// BindingPattern
|
||||
parseForBinding(): ParseNode.ForBinding {
|
||||
const node = this.startNode<ParseNode.ForBinding>();
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.LBRACK:
|
||||
node.BindingPattern = this.parseBindingPattern();
|
||||
break;
|
||||
default:
|
||||
node.BindingIdentifier = this.parseBindingIdentifier();
|
||||
break;
|
||||
}
|
||||
return this.finishNode(node, 'ForBinding');
|
||||
}
|
||||
|
||||
|
||||
// SwitchStatement :
|
||||
// `switch` `(` Expression `)` CaseBlock
|
||||
parseSwitchStatement(): ParseNode.SwitchStatement {
|
||||
const node = this.startNode<ParseNode.SwitchStatement>();
|
||||
this.expect(Token.SWITCH);
|
||||
this.expect(Token.LPAREN);
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
this.scope.with({
|
||||
lexical: true,
|
||||
label: 'switch',
|
||||
}, () => {
|
||||
node.CaseBlock = this.parseCaseBlock();
|
||||
});
|
||||
return this.finishNode(node, 'SwitchStatement');
|
||||
}
|
||||
|
||||
// CaseBlock :
|
||||
// `{` CaseClauses? `}`
|
||||
// `{` CaseClauses? DefaultClause CaseClauses? `}`
|
||||
// CaseClauses :
|
||||
// CaseClause
|
||||
// CaseClauses CauseClause
|
||||
// CaseClause :
|
||||
// `case` Expression `:` StatementList?
|
||||
// DefaultClause :
|
||||
// `default` `:` StatementList?
|
||||
parseCaseBlock(): ParseNode.CaseBlock {
|
||||
const node = this.startNode<ParseNode.CaseBlock>();
|
||||
let CaseClauses_a: Mutable<ParseNode.CaseClauses> | undefined;
|
||||
let CaseClauses_b: Mutable<ParseNode.CaseClauses> | undefined;
|
||||
this.expect(Token.LBRACE);
|
||||
while (!this.eat(Token.RBRACE)) {
|
||||
switch (this.peek().type) {
|
||||
case Token.CASE:
|
||||
case Token.DEFAULT: {
|
||||
const inner = this.startNode<ParseNode.CaseClause | ParseNode.DefaultClause>();
|
||||
const t = this.next().type;
|
||||
if (t === Token.DEFAULT && node.DefaultClause) {
|
||||
this.unexpected();
|
||||
}
|
||||
if (t === Token.CASE) {
|
||||
inner.Expression = this.parseExpression();
|
||||
}
|
||||
this.expect(Token.COLON);
|
||||
let StatementList: Mutable<ParseNode.StatementList> | undefined;
|
||||
while (!(this.test(Token.CASE) || this.test(Token.DEFAULT) || this.test(Token.RBRACE))) {
|
||||
if (!StatementList) {
|
||||
StatementList = [];
|
||||
inner.StatementList = StatementList;
|
||||
}
|
||||
StatementList.push(this.parseStatementListItem());
|
||||
}
|
||||
if (t === Token.DEFAULT) {
|
||||
node.DefaultClause = this.finishNode(inner, 'DefaultClause');
|
||||
} else {
|
||||
if (node.DefaultClause) {
|
||||
if (!CaseClauses_b) {
|
||||
CaseClauses_b = [];
|
||||
node.CaseClauses_b = CaseClauses_b;
|
||||
}
|
||||
CaseClauses_b.push(this.finishNode(inner, 'CaseClause'));
|
||||
} else {
|
||||
if (!CaseClauses_a) {
|
||||
CaseClauses_a = [];
|
||||
node.CaseClauses_a = CaseClauses_a;
|
||||
}
|
||||
CaseClauses_a.push(this.finishNode(inner, 'CaseClause'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.unexpected();
|
||||
}
|
||||
}
|
||||
return this.finishNode(node, 'CaseBlock');
|
||||
}
|
||||
|
||||
// BreakStatement :
|
||||
// `break` `;`
|
||||
// `break` [no LineTerminator here] LabelIdentifier `;`
|
||||
//
|
||||
// ContinueStatement :
|
||||
// `continue` `;`
|
||||
// `continue` [no LineTerminator here] LabelIdentifier `;`
|
||||
parseBreakContinueStatement(): ParseNode.BreakStatement | ParseNode.ContinueStatement {
|
||||
const node = this.startNode<ParseNode.BreakStatement | ParseNode.ContinueStatement>();
|
||||
const isBreak = this.eat(Token.BREAK);
|
||||
if (!isBreak) {
|
||||
this.expect(Token.CONTINUE);
|
||||
}
|
||||
if (this.eat(Token.SEMICOLON)) {
|
||||
node.LabelIdentifier = null;
|
||||
} else if (this.peek().hadLineTerminatorBefore) {
|
||||
node.LabelIdentifier = null;
|
||||
this.semicolon();
|
||||
} else {
|
||||
if (this.test(Token.IDENTIFIER)) {
|
||||
node.LabelIdentifier = this.parseLabelIdentifier();
|
||||
} else {
|
||||
node.LabelIdentifier = null;
|
||||
}
|
||||
this.semicolon();
|
||||
}
|
||||
this.verifyBreakContinue(node, isBreak);
|
||||
return this.finishNode(node, isBreak ? 'BreakStatement' : 'ContinueStatement');
|
||||
}
|
||||
|
||||
verifyBreakContinue(node: ParseNode.Unfinished<ParseNode.BreakStatement | ParseNode.ContinueStatement>, isBreak: boolean) {
|
||||
let i = 0;
|
||||
for (; i < this.scope.labels.length; i += 1) {
|
||||
const label = this.scope.labels[i];
|
||||
if (!node.LabelIdentifier || node.LabelIdentifier.name === label.name) {
|
||||
if (label.type && (isBreak || label.type === 'loop')) {
|
||||
break;
|
||||
}
|
||||
if (node.LabelIdentifier && isBreak) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i === this.scope.labels.length) {
|
||||
this.raiseEarly('IllegalBreakContinue', node, isBreak);
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnStatement :
|
||||
// `return` `;`
|
||||
// `return` [no LineTerminator here] Expression `;`
|
||||
parseReturnStatement(): ParseNode.ReturnStatement {
|
||||
if (!this.scope.hasReturn()) {
|
||||
this.unexpected();
|
||||
}
|
||||
const node = this.startNode<ParseNode.ReturnStatement>();
|
||||
this.expect(Token.RETURN);
|
||||
if (this.eatSemicolonWithASI()) {
|
||||
node.Expression = null;
|
||||
} else {
|
||||
node.Expression = this.parseExpression();
|
||||
this.semicolon();
|
||||
}
|
||||
return this.finishNode(node, 'ReturnStatement');
|
||||
}
|
||||
|
||||
// WithStatement :
|
||||
// `with` `(` Expression `)` Statement
|
||||
parseWithStatement(): ParseNode.WithStatement {
|
||||
if (this.isStrictMode()) {
|
||||
this.raiseEarly('UnexpectedToken');
|
||||
}
|
||||
const node = this.startNode<ParseNode.WithStatement>();
|
||||
this.expect(Token.WITH);
|
||||
this.expect(Token.LPAREN);
|
||||
node.Expression = this.parseExpression();
|
||||
this.expect(Token.RPAREN);
|
||||
node.Statement = this.parseStatement();
|
||||
return this.finishNode(node, 'WithStatement');
|
||||
}
|
||||
|
||||
// ThrowStatement :
|
||||
// `throw` [no LineTerminator here] Expression `;`
|
||||
parseThrowStatement(): ParseNode.ThrowStatement {
|
||||
const node = this.startNode<ParseNode.ThrowStatement>();
|
||||
this.expect(Token.THROW);
|
||||
if (this.peek().hadLineTerminatorBefore) {
|
||||
this.raise('NewlineAfterThrow', node);
|
||||
}
|
||||
node.Expression = this.parseExpression();
|
||||
this.semicolon();
|
||||
return this.finishNode(node, 'ThrowStatement');
|
||||
}
|
||||
|
||||
// TryStatement :
|
||||
// `try` Block Catch
|
||||
// `try` Block Finally
|
||||
// `try` Block Catch Finally
|
||||
//
|
||||
// Catch :
|
||||
// `catch` `(` CatchParameter `)` Block
|
||||
// `catch` Block
|
||||
//
|
||||
// Finally :
|
||||
// `finally` Block
|
||||
//
|
||||
// CatchParameter :
|
||||
// BindingIdentifier
|
||||
// BindingPattern
|
||||
parseTryStatement(): ParseNode.TryStatement {
|
||||
const node = this.startNode<ParseNode.TryStatement>();
|
||||
this.expect(Token.TRY);
|
||||
node.Block = this.parseBlock();
|
||||
if (this.eat(Token.CATCH)) {
|
||||
this.scope.with({ lexical: true }, () => {
|
||||
const clause = this.startNode<ParseNode.Catch>();
|
||||
if (this.eat(Token.LPAREN)) {
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.LBRACK:
|
||||
clause.CatchParameter = this.parseBindingPattern();
|
||||
break;
|
||||
default:
|
||||
clause.CatchParameter = this.parseBindingIdentifier();
|
||||
break;
|
||||
}
|
||||
this.scope.declare(clause.CatchParameter, 'lexical');
|
||||
this.expect(Token.RPAREN);
|
||||
} else {
|
||||
clause.CatchParameter = null;
|
||||
}
|
||||
clause.Block = this.parseBlock(false);
|
||||
node.Catch = this.finishNode(clause, 'Catch');
|
||||
});
|
||||
} else {
|
||||
node.Catch = null;
|
||||
}
|
||||
if (this.eat(Token.FINALLY)) {
|
||||
node.Finally = this.parseBlock();
|
||||
} else {
|
||||
node.Finally = null;
|
||||
}
|
||||
if (!node.Catch && !node.Finally) {
|
||||
this.raise('TryMissingCatchOrFinally');
|
||||
}
|
||||
return this.finishNode(node, 'TryStatement');
|
||||
}
|
||||
|
||||
// DebuggerStatement : `debugger` `;`
|
||||
parseDebuggerStatement(): ParseNode.DebuggerStatement {
|
||||
const node = this.startNode<ParseNode.DebuggerStatement>();
|
||||
this.expect(Token.DEBUGGER);
|
||||
this.semicolon();
|
||||
return this.finishNode(node, 'DebuggerStatement');
|
||||
}
|
||||
|
||||
// ExpressionStatement :
|
||||
// [lookahead != `{`, `function`, `async` [no LineTerminator here] `function`, `class`, `let` `[` ] Expression `;`
|
||||
parseExpressionStatement(): ParseNode.ExpressionStatement | ParseNode.LabelledStatement {
|
||||
switch (this.peek().type) {
|
||||
case Token.LBRACE:
|
||||
case Token.FUNCTION:
|
||||
case Token.CLASS:
|
||||
this.unexpected();
|
||||
break;
|
||||
default:
|
||||
if (this.test('async') && this.testAhead(Token.FUNCTION) && !this.peekAhead().hadLineTerminatorBefore) {
|
||||
this.unexpected();
|
||||
}
|
||||
if (this.test('let') && this.testAhead(Token.LBRACK)) {
|
||||
this.unexpected();
|
||||
}
|
||||
break;
|
||||
}
|
||||
const startToken = this.peek();
|
||||
const node = this.startNode<ParseNode.ExpressionStatement | ParseNode.LabelledStatement>();
|
||||
const expression = this.parseExpression();
|
||||
if (expression.type === 'IdentifierReference' && this.eat(Token.COLON)) {
|
||||
const LabelIdentifier = this.repurpose(expression, 'LabelIdentifier');
|
||||
node.LabelIdentifier = LabelIdentifier;
|
||||
|
||||
if (this.scope.labels.find((l) => l.name === LabelIdentifier.name)) {
|
||||
this.raiseEarly('AlreadyDeclared', node.LabelIdentifier, node.LabelIdentifier.name);
|
||||
}
|
||||
let type: LabelType | null = null;
|
||||
switch (this.peek().type) {
|
||||
case Token.SWITCH:
|
||||
type = 'switch';
|
||||
break;
|
||||
case Token.DO:
|
||||
case Token.WHILE:
|
||||
case Token.FOR:
|
||||
type = 'loop';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (type !== null && this.scope.labels.length > 0) {
|
||||
const last = this.scope.labels[this.scope.labels.length - 1];
|
||||
if (last.nextToken === startToken) {
|
||||
last.type = type;
|
||||
}
|
||||
}
|
||||
this.scope.labels.push({
|
||||
name: node.LabelIdentifier.name,
|
||||
type,
|
||||
nextToken: type === null ? this.peek() : null,
|
||||
});
|
||||
|
||||
node.LabelledItem = this.parseStatement();
|
||||
|
||||
this.scope.labels.pop();
|
||||
|
||||
return this.finishNode(node, 'LabelledStatement');
|
||||
}
|
||||
node.Expression = expression;
|
||||
this.semicolon();
|
||||
return this.finishNode(node, 'ExpressionStatement');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar
|
||||
|
||||
import type { TemporalDurationObject } from '../intrinsics/Temporal/Duration.mts';
|
||||
import { temporal_todo } from '../abstract-ops/temporal/not-implemented.mts';
|
||||
import {
|
||||
Assert,
|
||||
JSStringValue,
|
||||
Q,
|
||||
surroundingAgent,
|
||||
ToPrimitive,
|
||||
Value,
|
||||
type PlainCompletion, type PlainEvaluator, type TimeRecord, type ValueCompletion,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-iso-string-time-zone-parse-records */
|
||||
export interface ISOStringTimeZoneParseRecord {
|
||||
readonly Z: boolean;
|
||||
readonly OffsetString: string | undefined;
|
||||
readonly TimeZoneAnnotation: string | undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-iso-date-time-parse-records */
|
||||
export interface ISODateTimeParseRecord {
|
||||
readonly Year: number | undefined;
|
||||
readonly Month: number;
|
||||
readonly Day: number;
|
||||
readonly Time: TimeRecord | 'start-of-day';
|
||||
readonly TimeZone: ISOStringTimeZoneParseRecord;
|
||||
readonly Calendar: string | undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime */
|
||||
export function ParseISODateTime(_isoString: string, _allowedFormats: Array<'TemporalInstantString' | 'TemporalDateTimeString[~Zoned]' | 'TemporalTimeString' | 'TemporalMonthDayString' | 'TemporalYearMonthString' | 'TemporalDateTimeString[+Zoned]'>): PlainCompletion<ISODateTimeParseRecord> {
|
||||
temporal_todo();
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring */
|
||||
export function ParseTemporalCalendarString(_isoString: string): PlainCompletion<string> {
|
||||
temporal_todo();
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring */
|
||||
export function ParseTemporalDurationString(_isoString: string): ValueCompletion<TemporalDurationObject> {
|
||||
temporal_todo();
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring */
|
||||
export function ParseTemporalTimeZoneString(_timeZoneString: string): PlainCompletion<TimeZoneIdentifierParseRecord> {
|
||||
temporal_todo();
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-time-zone-identifier-parse-records */
|
||||
export interface TimeZoneIdentifierParseRecord {
|
||||
Name: string | undefined;
|
||||
OffsetMinutes: number | undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode */
|
||||
export function* ParseMonthCode(argument: Value | string): PlainEvaluator<{ MonthNumber: number; IsLeapMonth: boolean }> {
|
||||
const monthCode = typeof argument === 'string' ? Value(argument) : Q(yield* ToPrimitive(argument, 'string'));
|
||||
if (!(monthCode instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', typeof argument === 'string' ? Value(argument) : argument);
|
||||
}
|
||||
|
||||
// If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
|
||||
|
||||
// MonthCode :::
|
||||
// M00L
|
||||
// M0 NonZeroDigit L?
|
||||
// M NonZeroDigit DecimalDigit L?
|
||||
|
||||
if (!monthCode.stringValue().match(/^(M00L|M0[1-9]L?|M[1-9][0-9]L?)$/)) {
|
||||
return surroundingAgent.Throw('RangeError', 'InvalidMonth');
|
||||
}
|
||||
|
||||
let isLeapMonth = false;
|
||||
if (monthCode.stringValue().length === 4) {
|
||||
// Assert: The fourth code unit of monthCode is 0x004C (LATIN CAPITAL LETTER L).
|
||||
Assert(monthCode.stringValue().charCodeAt(4) === 0x004C);
|
||||
isLeapMonth = true;
|
||||
}
|
||||
const monthCodeDigits = monthCode.stringValue().substring(1, 3);
|
||||
const monthNumber = parseInt(monthCodeDigits, 10);
|
||||
if (monthNumber === 0 && !isLeapMonth) {
|
||||
return surroundingAgent.Throw('RangeError', 'InvalidMonth');
|
||||
}
|
||||
return { MonthNumber: monthNumber, IsLeapMonth: isLeapMonth };
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset */
|
||||
export function ParseDateTimeUTCOffset(_offsetString: string): PlainCompletion<number> {
|
||||
temporal_todo();
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-parsetimezoneidentifier
|
||||
export function ParseTimeZoneIdentifier(_identifier: string): PlainCompletion<TimeZoneIdentifierParseRecord> {
|
||||
temporal_todo();
|
||||
}
|
||||
|
||||
export class DateParser {
|
||||
public input: string;
|
||||
|
||||
public pos = 0;
|
||||
|
||||
constructor(input: string) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.input[this.pos];
|
||||
}
|
||||
|
||||
expect(char: string, message?: string) {
|
||||
if (this.input[this.pos] !== char) {
|
||||
throw new Error(message || `Expected '${char}' at position ${this.pos}`);
|
||||
}
|
||||
this.pos += 1;
|
||||
}
|
||||
|
||||
tryParse<T>(f: () => T) {
|
||||
const startPos = this.pos;
|
||||
try {
|
||||
return f();
|
||||
} catch {
|
||||
this.pos = startPos;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// #region Top Goals (used as a parameter of ParseText)
|
||||
// AmbiguousTemporalTimeString :::
|
||||
// DateSpecMonthDay TimeZoneAnnotation? Annotations?
|
||||
// DateSpecYearMonth TimeZoneAnnotation? Annotations?
|
||||
parseAmbiguousTemporalTimeString() {
|
||||
const DateSpecMonthDay = this.tryParse(() => this.parseDateSpecMonthDay());
|
||||
const DateSpecYearMonth = DateSpecMonthDay ? undefined : this.parseDateSpecYearMonth();
|
||||
const TimeZoneAnnotation = this.tryParse(() => this.parseTimeZoneAnnotation());
|
||||
const Annotations = this.peek() && this.parseAnnotations();
|
||||
return {
|
||||
DateSpecMonthDay, DateSpecYearMonth, TimeZoneAnnotation, Annotations,
|
||||
};
|
||||
}
|
||||
|
||||
// AnnotationValue
|
||||
parseAnnotationValue() { }
|
||||
|
||||
// TemporalDurationString
|
||||
parseTemporalDurationString() { }
|
||||
|
||||
// TemporalDateTimeString
|
||||
parseTemporalDateTimeString() { }
|
||||
|
||||
// TemporalInstantString
|
||||
parseTemporalInstantString() { }
|
||||
|
||||
// TemporalYearMonthString
|
||||
parseTemporalYearMonthString() { }
|
||||
|
||||
// TemporalMonthDayString
|
||||
parseTemporalMonthDayString() { }
|
||||
|
||||
// TemporalTimeString :::
|
||||
// AnnotatedTime
|
||||
// AnnotatedDateTime[~Zoned, +TimeRequired]
|
||||
parseTemporalTimeString() { }
|
||||
|
||||
// TimeZoneIdentifier :::
|
||||
// UTCOffset[~SubMinutePrecision]
|
||||
// TimeZoneIANAName
|
||||
parseTimeZoneIdentifier() {
|
||||
const next = this.peek();
|
||||
if (next === '+' || next === '-') {
|
||||
return { UTCOffset: this.parseUTCOffset(false), TimeZoneIANAName: undefined };
|
||||
}
|
||||
return { UTCOffset: undefined, TimeZoneIANAName: this.parseTimeZoneIANAName() };
|
||||
}
|
||||
|
||||
// UTCOffset[SubMinutePrecision] :::
|
||||
// ASCIISign Hour
|
||||
// ASCIISign Hour TimeSeparator[+Extended] MinuteSecond
|
||||
// ASCIISign Hour TimeSeparator[~Extended] MinuteSecond
|
||||
// [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction?
|
||||
// [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction?
|
||||
parseUTCOffset(SubMinutePrecision: boolean) {
|
||||
const sign = this.parseAsciiSign();
|
||||
const hour = this.parseHour();
|
||||
const timeSeparator = this.tryParseTimeSeparator_ExtendedOrNot();
|
||||
const minuteSecond = this.parseMinuteSecond();
|
||||
if (SubMinutePrecision && this.peek()) {
|
||||
return this.tryParse(() => {
|
||||
const timeSeparator2 = this.tryParseTimeSeparator_ExtendedOrNot();
|
||||
const minuteSecond2 = this.parseMinuteSecond();
|
||||
const fraction = this.tryParseTemporalDecimalFraction();
|
||||
return {
|
||||
sign, hour, timeSeparator, minuteSecond, timeSeparator2, minuteSecond2, fraction,
|
||||
};
|
||||
});
|
||||
}
|
||||
return {
|
||||
sign, hour, timeSeparator, minuteSecond,
|
||||
};
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Sub goals
|
||||
// ASCIISign ::: one of + -
|
||||
parseAsciiSign(): '+' | '-' {
|
||||
const next = this.peek();
|
||||
if (next === '+' || next === '-') {
|
||||
this.pos += 1;
|
||||
return next;
|
||||
}
|
||||
throw new Error(`Expected '+' or '-' at position ${this.pos}`);
|
||||
}
|
||||
|
||||
// TimeSeparator[Extended] :::
|
||||
// [+Extended] :
|
||||
// [~Extended] [empty]
|
||||
tryParseTimeSeparator_ExtendedOrNot(): ':' | undefined {
|
||||
if (this.peek() === ':') {
|
||||
this.pos += 1;
|
||||
return ':';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
parseTimeZoneIANAName() { }
|
||||
|
||||
parseHour() { }
|
||||
|
||||
parseMinuteSecond() { }
|
||||
|
||||
tryParseTemporalDecimalFraction() { }
|
||||
|
||||
parseDateSpecMonthDay() { }
|
||||
|
||||
parseDateSpecYearMonth() { }
|
||||
|
||||
parseTimeZoneAnnotation() { }
|
||||
|
||||
parseAnnotations() {}
|
||||
// #endregion
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/** Coerces a property key into a numeric index */
|
||||
type ToIndex<T extends PropertyKey> =
|
||||
T extends number ? ToIndex<`${T}`> :
|
||||
T extends `${bigint}` ? T extends `${infer I extends number}` ? I : never :
|
||||
never;
|
||||
|
||||
type ReplaceType<T, U, V> = T extends U ? V : T;
|
||||
|
||||
type TokenDefinition = readonly [name: string, value: string | null, precedence?: number];
|
||||
|
||||
type TokenArrayToAssignTokenArray<A extends readonly TokenDefinition[]> = {
|
||||
readonly [P in keyof A]: readonly [`ASSIGN_${A[P][0]}`, `${A[P][1]}`, A[P][2]];
|
||||
};
|
||||
|
||||
type TokenArrayToEnumLike<A extends readonly TokenDefinition[]> = {
|
||||
readonly [I in ToIndex<keyof A> as A[I][0]]: I;
|
||||
};
|
||||
|
||||
type TokenArrayToElementArray<A extends readonly TokenDefinition[], I extends 0 | 1 | 2, V = undefined> = {
|
||||
readonly [P in keyof A]: ReplaceType<A[P][I], undefined, V>;
|
||||
};
|
||||
|
||||
type TokenArrayToKeywordsArray<A extends readonly TokenDefinition[]> = readonly {
|
||||
readonly [I in ToIndex<keyof A>]: A[I][1] extends Lowercase<A[I][0]> ? A[I][1] : never;
|
||||
}[ToIndex<keyof A>][];
|
||||
|
||||
type KeywordsArrayToEnumLike<A extends readonly string[]> = {
|
||||
readonly [P in A[number]]: typeof Token[Uppercase<P> & keyof typeof Token];
|
||||
};
|
||||
|
||||
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],
|
||||
] as const satisfies readonly TokenDefinition[];
|
||||
|
||||
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]) as readonly TokenDefinition[] as TokenArrayToAssignTokenArray<typeof MaybeAssignTokens>,
|
||||
// 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],
|
||||
['BIGINT', 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'],
|
||||
['PRIVATE_IDENTIFIER', null],
|
||||
['AT', '@'],
|
||||
|
||||
['ENUM', 'enum'],
|
||||
|
||||
['ESCAPED_KEYWORD', null],
|
||||
] as const satisfies readonly TokenDefinition[];
|
||||
|
||||
export const Token = RawTokens
|
||||
.reduce((obj, [name], i) => {
|
||||
obj[name] = i;
|
||||
return obj;
|
||||
}, Object.create(null)) as TokenArrayToEnumLike<typeof RawTokens>;
|
||||
|
||||
export type Token = typeof Token[keyof typeof Token];
|
||||
|
||||
export const TokenNames = RawTokens.map((r) => r[0]) as readonly string[] as TokenArrayToElementArray<typeof RawTokens, 0>;
|
||||
|
||||
export const TokenValues = RawTokens.map((r) => r[1]) as readonly (string | null)[] as TokenArrayToElementArray<typeof RawTokens, 1>;
|
||||
|
||||
export const TokenPrecedence = RawTokens.map((r) => (r[2] || 0)) as readonly number[] as TokenArrayToElementArray<typeof RawTokens, 2, 0>;
|
||||
|
||||
const Keywords = RawTokens
|
||||
.filter(([name, raw]) => name.toLowerCase() === raw)
|
||||
.map(([, raw]) => raw!) as TokenArrayToKeywordsArray<typeof RawTokens>;
|
||||
|
||||
export const KeywordLookup = Keywords
|
||||
.reduce((obj, kw) => {
|
||||
obj[kw] = Token[kw.toUpperCase() as Uppercase<typeof kw>];
|
||||
return obj;
|
||||
}, Object.create(null)) as KeywordsArrayToEnumLike<typeof Keywords>;
|
||||
|
||||
const KeywordRaw: ReadonlySet<string> = new Set(Object.keys(KeywordLookup));
|
||||
const KeywordTokens: ReadonlySet<number> = new Set(Object.values(KeywordLookup));
|
||||
|
||||
const isInRange = (t: number, l: number, h: number) => t >= l && t <= h;
|
||||
export const isAutomaticSemicolon = (t: number) => isInRange(t, Token.SEMICOLON, Token.EOS);
|
||||
export const isMember = (t: number) => isInRange(t, Token.TEMPLATE, Token.LBRACK);
|
||||
export const isPropertyOrCall = (t: number) => isInRange(t, Token.TEMPLATE, Token.LPAREN);
|
||||
export const isKeyword = (t: number): t is typeof KeywordLookup[keyof typeof KeywordLookup] => KeywordTokens.has(t);
|
||||
export const isKeywordRaw = (s: string): s is keyof typeof KeywordLookup => KeywordRaw.has(s);
|
||||
|
||||
const ReservedWordsStrict: ReadonlySet<string> = new Set([
|
||||
'implements', 'interface', 'let',
|
||||
'package', 'private', 'protected',
|
||||
'public', 'static', 'yield',
|
||||
]);
|
||||
|
||||
export const isReservedWordStrict = (s: string) => ReservedWordsStrict.has(s);
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
declare module '@unicode/unicode-16.0.0/Binary_Property/ID_Start/regex.js' {
|
||||
let regex: RegExp;
|
||||
export default regex;
|
||||
}
|
||||
declare module '@unicode/unicode-16.0.0/Binary_Property/ID_Continue/regex.js' {
|
||||
let regex: RegExp;
|
||||
export default regex;
|
||||
}
|
||||
declare module '@unicode/unicode-16.0.0/General_Category/Space_Separator/regex.js' {
|
||||
let regex: RegExp;
|
||||
export default regex;
|
||||
}
|
||||
declare module '@unicode/unicode-16.0.0/Case_Folding/C/symbols.js' {
|
||||
let data: Map<string, string>;
|
||||
export default data;
|
||||
}
|
||||
declare module '@unicode/unicode-16.0.0/Case_Folding/S/symbols.js' {
|
||||
let data: Map<string, string>;
|
||||
export default data;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import type { ParseNode } from '#self';
|
||||
|
||||
export type TargetSymbol = ParseNode['type'] | 'super' | 'this';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-static-semantics-contains */
|
||||
export function Contains(node: ParseNode, symbol: TargetSymbol): boolean {
|
||||
switch (node.type) {
|
||||
case 'FunctionDeclaration':
|
||||
case 'FunctionExpression':
|
||||
case 'GeneratorDeclaration':
|
||||
case 'GeneratorExpression':
|
||||
case 'AsyncGeneratorDeclaration':
|
||||
case 'AsyncGeneratorExpression':
|
||||
case 'AsyncFunctionDeclaration':
|
||||
case 'AsyncFunctionExpression':
|
||||
return false;
|
||||
case 'ClassTail': {
|
||||
// We don't have ClassBody?
|
||||
throw new Error('TODO');
|
||||
}
|
||||
case 'ClassStaticBlock':
|
||||
return false;
|
||||
case 'ArrowFunction':
|
||||
case 'AsyncArrowFunction':
|
||||
throw new Error('TODO');
|
||||
case 'PropertyDefinition': {
|
||||
// Note && TODO: PropertyDefinition in spec refers to MethodDefinition here,
|
||||
// but our PropertyDefinition is parital one.
|
||||
// We should check this at all use site of PropertyDefinitionList.
|
||||
break;
|
||||
}
|
||||
// LiteralPropertyName : IdentifierName
|
||||
// throw new Error('TODO');
|
||||
case 'MemberExpression': {
|
||||
// MemberExpression : MemberExpression . IdentifierName
|
||||
if (node.IdentifierName) {
|
||||
return Contains(node.MemberExpression, symbol);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'SuperProperty': {
|
||||
if (node.IdentifierName) {
|
||||
return symbol === 'super';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CallExpression': {
|
||||
throw new Error('TODO');
|
||||
}
|
||||
case 'OptionalChain': {
|
||||
if (node.IdentifierName) {
|
||||
// OptionalChain : OptionalChain . IdentifierName
|
||||
if (node.OptionalChain) {
|
||||
return Contains(node.OptionalChain, symbol);
|
||||
}
|
||||
// OptionalChain : ?. IdentifierName
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
// 1. For each child node child of this Parse Node
|
||||
for (const child of avoid_using_children(node)) {
|
||||
// a. If child is an instance of symbol, return true.
|
||||
if (child.type === symbol) {
|
||||
return true;
|
||||
}
|
||||
// b. If child is an instance of a nonterminal, then
|
||||
const contained = Contains(child, symbol);
|
||||
// i. If contained is true, return true.
|
||||
if (contained) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-static-semantics-arrayliteralcontentnodes */
|
||||
export function ArrayLiteralContentNodes(node: ParseNode.ArrayLiteral) {
|
||||
return node.ElementList;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-static-semantics-propertydefinitionnodes */
|
||||
export function PropertyDefinitionNodes(node: ParseNode.ObjectLiteral) {
|
||||
return node.PropertyDefinitionList;
|
||||
}
|
||||
|
||||
// Note: this is not a correct forEachChild implementation, but it is not worth the effort to implement it fully.
|
||||
// defer it to the future if needed.
|
||||
export function* avoid_using_children(node: ParseNode): Generator<ParseNode> {
|
||||
for (const key of Reflect.ownKeys(node)) {
|
||||
if (typeof key === 'string' && key !== 'parent' && key !== 'type' && key !== 'location' && key !== 'sourceText') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const child = (node as any)[key];
|
||||
if (typeof child === 'object' && child) {
|
||||
if (Array.isArray(child)) {
|
||||
for (const element of child) {
|
||||
if (isParseNode(element)) {
|
||||
yield element;
|
||||
}
|
||||
}
|
||||
} else if ('type' in child) {
|
||||
yield child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isParseNode(value: unknown): value is ParseNode {
|
||||
return !!(value && typeof value === 'object' && 'type' in value && 'location' in value);
|
||||
}
|
||||
Reference in New Issue
Block a user