mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-19 00:31:06 +00:00
267 lines
11 KiB
TypeScript
267 lines
11 KiB
TypeScript
import { Parser, type ParserOptions } from './parser/Parser.mts';
|
|
import { RegExpParser, type RegExpParserContext } from './parser/RegExpParser.mts';
|
|
import { surroundingAgent, type GCMarker } from './host-defined/engine.mts';
|
|
import {
|
|
SourceTextModuleRecord, SyntheticModuleRecord, type LoadedModuleRequestRecord, type ModuleRecordHostDefined,
|
|
} from './modules.mts';
|
|
import { JSStringValue, ObjectValue, Value } from './value.mts';
|
|
import { Q, X, type PlainCompletion } from './completion.mts';
|
|
import {
|
|
ModuleRequests,
|
|
ImportEntries,
|
|
ExportEntries,
|
|
ImportedLocalNames,
|
|
} from './static-semantics/all.mts';
|
|
import {
|
|
JSStringSet, kInternal, skipDebugger, type Mutable,
|
|
} from './helpers.mts';
|
|
import type { ParseNode } from './parser/ParseNode.mts';
|
|
import { ParseJSON } from './intrinsics/JSON.mts';
|
|
import { avoid_using_children } from './parser/utils.mts';
|
|
import {
|
|
Get,
|
|
Set,
|
|
CreateDefaultExportSyntheticModule,
|
|
ToString,
|
|
Throw,
|
|
} from '#self';
|
|
import type { Realm } from '#self';
|
|
|
|
export { Parser, RegExpParser };
|
|
|
|
function handleError(e: unknown) {
|
|
if (e instanceof SyntaxError) {
|
|
const v = surroundingAgent.Throw('SyntaxError', 'Raw', e.message).Value as ObjectValue;
|
|
if (e.decoration) {
|
|
const stackString = Value('stack');
|
|
const stack = X(Get(v, stackString));
|
|
// Note: in many cases the output will be padded by space or text like "Uncaught",
|
|
// insert a new line allow decoration lines get the same padding.
|
|
const newStackString = `\n${e.decoration}\n${stack instanceof JSStringValue ? stack.stringValue() : ''}`;
|
|
X(Set(v, stackString, Value(newStackString), Value.true));
|
|
}
|
|
return v;
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export function wrappedParse<T>(init: ParserOptions, f: (parser: Parser) => T) {
|
|
const p = new Parser(init);
|
|
|
|
try {
|
|
const r = f(p);
|
|
const errors = [];
|
|
for (const error of p.earlyErrors) {
|
|
errors.push(handleError(error));
|
|
}
|
|
for (const error of p.earlyErrors2) {
|
|
errors.push(error);
|
|
}
|
|
if (errors.length > 0) {
|
|
return errors;
|
|
}
|
|
return r;
|
|
} catch (e) {
|
|
return [handleError(e)];
|
|
}
|
|
}
|
|
|
|
export class ScriptRecord {
|
|
readonly Realm: Realm;
|
|
|
|
readonly ECMAScriptCode: ParseNode.Script;
|
|
|
|
readonly LoadedModules: LoadedModuleRequestRecord[];
|
|
|
|
readonly HostDefined: ParseScriptHostDefined;
|
|
|
|
mark(m: GCMarker) {
|
|
m(this.Realm);
|
|
}
|
|
|
|
constructor(record: Omit<ScriptRecord, 'mark'>) {
|
|
this.ECMAScriptCode = record.ECMAScriptCode;
|
|
this.Realm = record.Realm;
|
|
this.LoadedModules = record.LoadedModules;
|
|
this.HostDefined = record.HostDefined;
|
|
}
|
|
}
|
|
export interface ParseScriptHostDefined {
|
|
readonly specifier?: string | undefined;
|
|
readonly [kInternal]?: {
|
|
json?: boolean;
|
|
/** only used in inspector.compileScript */ allowAllPrivateNames?: boolean;
|
|
};
|
|
scriptId?: string;
|
|
readonly doNotTrackScriptId?: boolean;
|
|
}
|
|
export function ParseScript(sourceText: string, realm: Realm, hostDefined: ParseScriptHostDefined = {}): ScriptRecord | ObjectValue[] {
|
|
// 1. Assert: sourceText is an ECMAScript source text (see clause 10).
|
|
// 2. Parse sourceText using Script as the goal symbol and analyse the parse result for
|
|
// any Early Error conditions. If the parse was successful and no early errors were found,
|
|
// let body be the resulting parse tree. Otherwise, let body be a List of one or more
|
|
// SyntaxError objects representing the parsing errors and/or early errors. Parsing and
|
|
// early error detection may be interweaved in an implementation-dependent manner. If more
|
|
// than one parsing error or early error is present, the number and ordering of error
|
|
// objects in the list is implementation-dependent, but at least one must be present.
|
|
const body = wrappedParse({
|
|
source: sourceText,
|
|
specifier: hostDefined.specifier,
|
|
json: hostDefined[kInternal]?.json,
|
|
allowAllPrivateNames: hostDefined[kInternal]?.allowAllPrivateNames,
|
|
}, (p) => p.parseScript());
|
|
// 3. If body is a List of errors, return body.
|
|
if (Array.isArray(body)) {
|
|
const scriptId = hostDefined.doNotTrackScriptId ? undefined : surroundingAgent.addDynamicParsedSource(realm, sourceText);
|
|
body.forEach((error) => Parser.decorateSyntaxErrorWithScriptId(error, scriptId));
|
|
return body;
|
|
}
|
|
setNodeParent(body, undefined);
|
|
// 4. Return Script Record { [[Realm]]: realm, [[ECMAScriptCode]]: body, [[HostDefined]]: hostDefined }.
|
|
const script = new ScriptRecord({
|
|
Realm: realm,
|
|
ECMAScriptCode: body,
|
|
LoadedModules: [],
|
|
HostDefined: hostDefined,
|
|
});
|
|
if (!hostDefined.doNotTrackScriptId) {
|
|
surroundingAgent.addParsedSource(script);
|
|
}
|
|
return script;
|
|
}
|
|
|
|
export function ParseModule(sourceText: string, realm: Realm, hostDefined: ModuleRecordHostDefined = {}) {
|
|
// 1. Assert: sourceText is an ECMAScript source text (see clause 10).
|
|
// 2. Parse sourceText using Module as the goal symbol and analyse the parse result for
|
|
// any Early Error conditions. If the parse was successful and no early errors were found,
|
|
// let body be the resulting parse tree. Otherwise, let body be a List of one or more
|
|
// SyntaxError objects representing the parsing errors and/or early errors. Parsing and
|
|
// early error detection may be interweaved in an implementation-dependent manner. If more
|
|
// than one parsing error or early error is present, the number and ordering of error
|
|
// objects in the list is implementation-dependent, but at least one must be present.
|
|
const body = wrappedParse<ParseNode.Module>({ source: sourceText, specifier: hostDefined.specifier }, (p) => p.parseModule());
|
|
// 3. If body is a List of errors, return body.
|
|
if (Array.isArray(body)) {
|
|
const scriptId = hostDefined.doNotTrackScriptId ? undefined : surroundingAgent.addDynamicParsedSource(realm, sourceText);
|
|
body.forEach((error) => Parser.decorateSyntaxErrorWithScriptId(error, scriptId));
|
|
return body;
|
|
}
|
|
setNodeParent(body, undefined);
|
|
// 4. Let requestedModules be the ModuleRequests of body.
|
|
const requestedModules = ModuleRequests(body);
|
|
// 5. Let importEntries be ImportEntries of body.
|
|
const importEntries = ImportEntries(body);
|
|
// 6. Let importedBoundNames be ImportedLocalNames(importEntries).
|
|
const importedBoundNames = new JSStringSet(ImportedLocalNames(importEntries));
|
|
// 7. Let indirectExportEntries be a new empty List.
|
|
const indirectExportEntries = [];
|
|
// 8. Let localExportEntries be a new empty List.
|
|
const localExportEntries = [];
|
|
// 9. Let starExportEntries be a new empty List.
|
|
const starExportEntries = [];
|
|
// 10. Let exportEntries be ExportEntries of body.
|
|
const exportEntries = ExportEntries(body);
|
|
// 11. For each ExportEntry Record ee in exportEntries, do
|
|
for (const ee of exportEntries) {
|
|
// a. If ee.[[ModuleRequest]] is null, then
|
|
if (ee.ModuleRequest === Value.null) {
|
|
// i. If ee.[[LocalName]] is not an element of importedBoundNames, then
|
|
if (!importedBoundNames.has(ee.LocalName)) {
|
|
// 1. Append ee to localExportEntries.
|
|
localExportEntries.push(ee);
|
|
} else { // ii. Else,
|
|
// 1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]].
|
|
const ie = importEntries.find((e) => e.LocalName.stringValue() === (ee.LocalName as JSStringValue).stringValue());
|
|
// 2. If ie.[[ImportName]] is ~namespace-object~, then
|
|
if (ie!.ImportName === 'namespace-object') {
|
|
// a. NOTE: This is a re-export of an imported module namespace object.
|
|
// b. Append ee to localExportEntries.
|
|
localExportEntries.push(ee);
|
|
} else { // 3. Else,
|
|
// a. NOTE: This is a re-export of a single name.
|
|
// b. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries.
|
|
indirectExportEntries.push({
|
|
ModuleRequest: ie!.ModuleRequest,
|
|
ImportName: ie!.ImportName,
|
|
LocalName: Value.null,
|
|
ExportName: ee.ExportName,
|
|
});
|
|
}
|
|
}
|
|
} else if (ee.ImportName && ee.ImportName === 'all-but-default' && ee.ExportName === Value.null) { // b. Else if ee.[[ImportName]] is ~all-but-default~ and ee.[[ExportName]] is null, then
|
|
// i. Append ee to starExportEntries.
|
|
starExportEntries.push(ee);
|
|
} else { // c. Else,
|
|
// i. Append ee to indirectExportEntries.
|
|
indirectExportEntries.push(ee);
|
|
}
|
|
}
|
|
// 12. Return Source Text Module Record { [[Realm]]: realm, [[Environment]]: undefined, [[Namespace]]: undefined, [[Status]]: unlinked, [[EvaluationError]]: undefined, [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[Context]]: empty, [[ImportMeta]]: empty, [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries, [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSAncestorIndex]]: undefined }.
|
|
const module = new (hostDefined.SourceTextModuleRecord || SourceTextModuleRecord)({
|
|
Realm: realm,
|
|
Environment: undefined,
|
|
Namespace: undefined,
|
|
Status: 'new',
|
|
EvaluationError: undefined,
|
|
HostDefined: hostDefined,
|
|
ECMAScriptCode: body,
|
|
Context: undefined,
|
|
ImportMeta: undefined,
|
|
RequestedModules: requestedModules,
|
|
LoadedModules: [],
|
|
ImportEntries: importEntries,
|
|
LocalExportEntries: localExportEntries,
|
|
IndirectExportEntries: indirectExportEntries,
|
|
StarExportEntries: starExportEntries,
|
|
CycleRoot: undefined,
|
|
HasTLA: body.hasTopLevelAwait ? Value.true : Value.false,
|
|
AsyncEvaluationOrder: 'unset',
|
|
TopLevelCapability: undefined,
|
|
AsyncParentModules: [],
|
|
DFSAncestorIndex: undefined,
|
|
PendingAsyncDependencies: undefined,
|
|
});
|
|
if (!hostDefined.doNotTrackScriptId) {
|
|
surroundingAgent.addParsedSource(module);
|
|
}
|
|
return module;
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-parsejsonmodule */
|
|
export function ParseJSONModule(sourceText: Value, realm: Realm, hostDefined: ModuleRecordHostDefined): PlainCompletion<SyntheticModuleRecord> {
|
|
const string = Q(skipDebugger(ToString(sourceText)));
|
|
const result = Q(ParseJSON(string.stringValue()));
|
|
return CreateDefaultExportSyntheticModule(result.Value, realm, hostDefined);
|
|
}
|
|
|
|
function setNodeParent(node: ParseNode, parent: ParseNode | undefined) {
|
|
(node as Mutable<ParseNode.BaseParseNode>).parent = parent;
|
|
for (const child of avoid_using_children(node)) {
|
|
if (!child.parent) {
|
|
setNodeParent(child, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-parsepattern */
|
|
export function ParsePattern(patternText: string, u: boolean, v: boolean) {
|
|
const parse = (flags: RegExpParserContext) => {
|
|
try {
|
|
const p = new RegExpParser(patternText);
|
|
return p.scope(flags, () => p.parsePattern());
|
|
} catch (e) {
|
|
return [handleError(e)];
|
|
}
|
|
};
|
|
if (v && u) {
|
|
return [Throw.SyntaxError('RegExp flags "v" and "u" cannot be used together').Value];
|
|
} else if (v) {
|
|
return parse({ UnicodeMode: true, UnicodeSetsMode: true, NamedCaptureGroups: true });
|
|
} else if (u) {
|
|
return parse({ UnicodeMode: true, NamedCaptureGroups: true });
|
|
} else {
|
|
return parse({ NamedCaptureGroups: true });
|
|
}
|
|
}
|