Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'

This commit is contained in:
2026-03-27 10:07:29 +05:30
433 changed files with 90478 additions and 0 deletions
+36
View File
@@ -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
+389
View File
@@ -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);
}
}
+144
View File
@@ -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');
}
}
+136
View File
@@ -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
+336
View File
@@ -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
+201
View File
@@ -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
+538
View File
@@ -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,
});
}
}
+959
View File
@@ -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');
}
}
+243
View File
@@ -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
}
+213
View File
@@ -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);
+20
View File
@@ -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;
}
+114
View File
@@ -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);
}