mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 21:31:46 +00:00
Squashed 'engine262/' content from commit ae71998
git-subtree-dir: engine262 git-subtree-split: ae71998cc5a8315700555135b1ac202a0d6d0b31
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { start } from 'node:repl';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { format as _format, inspect as _inspect, parseArgs } from 'node:util';
|
||||
// let's try if the following message on old node causes test failure on test262.fyi
|
||||
// "ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time"
|
||||
// import packageJson from '../../package.json' with { type: 'json' };
|
||||
import { createRequire } from 'node:module';
|
||||
import { createConsole } from '../inspector/utils.mts';
|
||||
import type { NodeWebsocketInspector } from './inspector.mts';
|
||||
import { loadImportedModule } from './module.mts';
|
||||
import {
|
||||
setSurroundingAgent, FEATURES, inspect, Value, Completion, AbruptCompletion,
|
||||
type Arguments,
|
||||
evalQ,
|
||||
Agent,
|
||||
ManagedRealm,
|
||||
skipDebugger,
|
||||
type ValueCompletion,
|
||||
createTest262Intrinsics,
|
||||
surroundingAgent,
|
||||
ThrowCompletion,
|
||||
ValueOfNormalCompletion,
|
||||
ScriptEvaluation,
|
||||
type PlainEvaluator,
|
||||
} from '#self';
|
||||
|
||||
const packageJson = createRequire(import.meta.url)('../../package.json');
|
||||
const help = `
|
||||
engine262 v${packageJson.version}
|
||||
|
||||
Usage:
|
||||
|
||||
engine262 [options]
|
||||
engine262 [options] [input file]
|
||||
engine262 [input file]
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help Show help (this screen)
|
||||
-m, --module Evaluate contents of input-file as a module.
|
||||
-e, --eval Evaluate the given string.
|
||||
--features=... A comma separated list of features.
|
||||
--features=all Enable all features.
|
||||
--list-features List available features.
|
||||
--no-test262 Do not expose $ and $262 for test262.
|
||||
--no-inspector Do not attach an inspector.
|
||||
--no-preview Do not enable preview in the inspector.
|
||||
`;
|
||||
|
||||
const argv = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
allowPositionals: true,
|
||||
allowNegative: true,
|
||||
strict: true,
|
||||
options: {
|
||||
'help': { type: 'boolean', short: 'h' },
|
||||
'eval': { type: 'string', short: 'e' },
|
||||
'module': { type: 'boolean', short: 'm' },
|
||||
'features': { type: 'string' },
|
||||
'list-features': { type: 'boolean' },
|
||||
'inspector': { type: 'boolean' },
|
||||
'test262': { type: 'boolean', default: true },
|
||||
// hidden options
|
||||
'preview-debug': { type: 'boolean' },
|
||||
},
|
||||
});
|
||||
|
||||
if (argv.values.help) {
|
||||
process.stdout.write(help);
|
||||
process.exit(0);
|
||||
} else if (argv.values['list-features']) {
|
||||
let nameLength = 0;
|
||||
let flagLength = 0;
|
||||
FEATURES.forEach((f) => {
|
||||
if (f.name.length > nameLength) {
|
||||
nameLength = f.name.length;
|
||||
}
|
||||
if (f.flag.length > flagLength) {
|
||||
flagLength = f.flag.length;
|
||||
}
|
||||
});
|
||||
const log = (f: string, n: string, u: string) => {
|
||||
process.stdout.write(`${f.padEnd(flagLength, ' ')} ${n.padEnd(nameLength, ' ')} ${u}\n`);
|
||||
};
|
||||
log('flag', 'name', 'url');
|
||||
log('----', '----', '---');
|
||||
FEATURES.forEach((f) => {
|
||||
log(f.flag, f.name, f.url);
|
||||
});
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let features: string[];
|
||||
if (argv.values.features === 'all') {
|
||||
features = FEATURES.map((f) => f.flag);
|
||||
} else if (argv.values.features) {
|
||||
features = argv.values.features.split(',');
|
||||
} else {
|
||||
features = [];
|
||||
}
|
||||
|
||||
const agent = new Agent({
|
||||
features,
|
||||
supportedImportAttributes: ['type'],
|
||||
loadImportedModule,
|
||||
});
|
||||
setSurroundingAgent(agent);
|
||||
|
||||
const realm = new ManagedRealm({ resolverCache: new Map(), name: 'repl', specifier: process.cwd() });
|
||||
// Define console.log
|
||||
{
|
||||
const format = (function* format(args: Arguments): PlainEvaluator<string> {
|
||||
const str = [];
|
||||
for (const arg of args.values()) {
|
||||
// TODO: inspect should return a PlainEvaluator so debugger can hook in.
|
||||
str.push(inspect(arg));
|
||||
}
|
||||
return str.join(' ');
|
||||
});
|
||||
createConsole(realm, {
|
||||
* log(args) {
|
||||
process.stdout.write(`${yield* format(args)}\n`);
|
||||
},
|
||||
* error(args) {
|
||||
process.stderr.write(`${yield* format(args)}\n`);
|
||||
},
|
||||
* debug(args) {
|
||||
process.stderr.write(`${yield* format(args)}\n`);
|
||||
},
|
||||
});
|
||||
}
|
||||
if (argv.values.test262) {
|
||||
createTest262Intrinsics(realm, argv.values.test262);
|
||||
}
|
||||
|
||||
let inspector: NodeWebsocketInspector | undefined;
|
||||
if (argv.values.inspector !== false) {
|
||||
let has_ws = false;
|
||||
try {
|
||||
await import('ws');
|
||||
has_ws = true;
|
||||
} catch {
|
||||
if (argv.values.inspector === true) {
|
||||
process.stderr.write('--inspector requires the "ws" package to be installed.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (has_ws) {
|
||||
const { NodeWebsocketInspector } = await import('./inspector.mts');
|
||||
inspector = await NodeWebsocketInspector.new();
|
||||
inspector.attachAgent(surroundingAgent, [realm]);
|
||||
inspector.preference.previewDebug = argv.values['preview-debug'] || false;
|
||||
}
|
||||
}
|
||||
|
||||
function oneShotEval(source: string, filename: string) {
|
||||
realm.scope(() => {
|
||||
const completion = evalQ((Q) => {
|
||||
if (argv.values.module || filename.endsWith('.mjs')) {
|
||||
const module = Q(realm.compileModule(source, { specifier: filename }));
|
||||
realm.HostDefined.resolverCache?.set(filename, module);
|
||||
const load = Q(module.LoadRequestedModules());
|
||||
if (load.PromiseState === 'rejected') {
|
||||
Q(ThrowCompletion(load.PromiseResult!));
|
||||
} else if (load.PromiseState === 'pending') {
|
||||
throw new Error('Internal error: .LoadRequestedModules() returned a pending promise');
|
||||
}
|
||||
Q(module.Link());
|
||||
const evaluate = Q(skipDebugger(module.Evaluate()));
|
||||
if (evaluate.PromiseState === 'rejected') {
|
||||
Q(ThrowCompletion(evaluate.PromiseResult!));
|
||||
}
|
||||
} else {
|
||||
Q(realm.evaluateScript(source, { specifier: filename }));
|
||||
}
|
||||
});
|
||||
if (completion instanceof AbruptCompletion) {
|
||||
const inspected = inspect(completion);
|
||||
process.stderr.write(`${inspected}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
inspector?.stop();
|
||||
}
|
||||
|
||||
if (argv.positionals[0]) {
|
||||
const source = readFileSync(argv.positionals[0], 'utf8');
|
||||
oneShotEval(source, resolve(argv.positionals[0]));
|
||||
} else if (!process.stdin.isTTY) {
|
||||
process.stdin.setEncoding('utf8');
|
||||
let source = '';
|
||||
process.stdin.on('data', (data) => {
|
||||
source += data;
|
||||
});
|
||||
process.stdin.once('end', () => {
|
||||
oneShotEval(source, process.cwd());
|
||||
});
|
||||
} else if (argv.values.eval) {
|
||||
oneShotEval(argv.values.eval, process.cwd());
|
||||
} else {
|
||||
process.stdout.write(`${packageJson.name} v${String(packageJson.version).replace('0.0.1-', '')}
|
||||
Type ".help" for more information. Please report bugs to ${packageJson.bugs.url}
|
||||
`);
|
||||
const server = start({
|
||||
prompt: '> ',
|
||||
eval: (cmd, _context, _filename, callback) => {
|
||||
try {
|
||||
const script = realm.compileScript(cmd, {});
|
||||
if (script instanceof ThrowCompletion) {
|
||||
callback(null, script);
|
||||
return;
|
||||
}
|
||||
let c;
|
||||
surroundingAgent.evaluate(ScriptEvaluation(ValueOfNormalCompletion(script)), (completion) => {
|
||||
c = completion;
|
||||
callback(null, completion);
|
||||
});
|
||||
if (!c) {
|
||||
surroundingAgent.resumeEvaluate();
|
||||
}
|
||||
} catch (e) {
|
||||
callback(e as Error, null);
|
||||
}
|
||||
},
|
||||
preview: false,
|
||||
writer: (o) => realm.scope(() => {
|
||||
if (o instanceof Value || o instanceof Completion) {
|
||||
return inspect(o as Value | ValueCompletion);
|
||||
}
|
||||
return _inspect(o);
|
||||
}),
|
||||
});
|
||||
|
||||
server.on('exit', () => inspector?.stop());
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* eslint-disable no-console */
|
||||
import {
|
||||
Agent, inspect, ManagedRealm, NormalCompletion, setSurroundingAgent, ThrowCompletion, type Arguments, type PlainEvaluator,
|
||||
} from '#self';
|
||||
import { createConsole } from '#self/inspector';
|
||||
|
||||
// Agent is the running environment.
|
||||
const agent = new Agent({
|
||||
});
|
||||
// Only one agent can be active at a time.
|
||||
setSurroundingAgent(agent);
|
||||
|
||||
// A Realm is a separate global environment.
|
||||
// In Web browsers, each iframe has its own Realm and they may interact with each other.
|
||||
const realm = new ManagedRealm({ resolverCache: new Map(), name: 'My Realm', specifier: process.cwd() });
|
||||
|
||||
// Define console.log
|
||||
{
|
||||
const format = (function* format(args: Arguments): PlainEvaluator<string> {
|
||||
const str = [];
|
||||
for (const arg of args.values()) {
|
||||
str.push(inspect(arg));
|
||||
}
|
||||
return str.join(' ');
|
||||
});
|
||||
createConsole(realm, {
|
||||
* log(args) {
|
||||
process.stdout.write(`${yield* format(args)}\n`);
|
||||
},
|
||||
* error(args) {
|
||||
process.stderr.write(`${yield* format(args)}\n`);
|
||||
},
|
||||
* debug(args) {
|
||||
process.stderr.write(`${yield* format(args)}\n`);
|
||||
},
|
||||
* default(method, args) {
|
||||
process.stdout.write(`[console.${method}] ${yield* format(args)}\n`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Do not forget to use realm.scope when running code.
|
||||
realm.scope(() => {
|
||||
// Run ECMAScript code in the Realm.
|
||||
realm.evaluateScript(`
|
||||
console.log('Hello from engine262!');
|
||||
console.log('2 + 2 =', 2 + 2);
|
||||
`, { specifier: 'example.mts' });
|
||||
|
||||
const result = realm.evaluateScript(`
|
||||
throw new Error('This is an example error');
|
||||
`, { specifier: 'example.mts' });
|
||||
if (result instanceof NormalCompletion) {
|
||||
console.log('No Error');
|
||||
} else if (result instanceof ThrowCompletion) {
|
||||
console.error('Caught error from evaluated script:', inspect(result.Value));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import packageJson from '../../package.json' with { type: 'json' };
|
||||
// Note: typescript will not copy json files, so it will not appear in the lib directory
|
||||
// eslint-disable-next-line import/no-useless-path-segments
|
||||
import protocol from '../../lib-src/inspector/js_protocol.json' with { type: 'json' };
|
||||
import { Inspector } from '../inspector/index.mts';
|
||||
|
||||
const ANSI = {
|
||||
reset: '\u001b[0m',
|
||||
red: '\u001b[31m',
|
||||
green: '\u001b[32m',
|
||||
yellow: '\u001b[33m',
|
||||
blue: '\u001b[34m',
|
||||
};
|
||||
|
||||
export class NodeWebsocketInspector extends Inspector {
|
||||
_server: http.Server | https.Server;
|
||||
|
||||
_ws: WebSocketServer;
|
||||
|
||||
isDebug = false;
|
||||
|
||||
protected override send(data: object): void {
|
||||
const s = JSON.stringify(data);
|
||||
this._ws.clients.forEach((ws) => {
|
||||
ws.send(s);
|
||||
});
|
||||
}
|
||||
|
||||
protected constructor(server: http.Server | https.Server, isDebug: boolean) {
|
||||
super();
|
||||
this._server = server;
|
||||
const ws = new WebSocketServer({ server });
|
||||
this._ws = ws;
|
||||
ws.on('connection', (ws) => {
|
||||
const send = (obj: unknown) => {
|
||||
const s = JSON.stringify(obj);
|
||||
ws.send(s);
|
||||
};
|
||||
|
||||
const sendEvent = Object.create(new Proxy({}, {
|
||||
get: (_, key: string) => {
|
||||
const f = (params: Record<string, unknown>) => {
|
||||
send({ method: key, params });
|
||||
};
|
||||
Object.defineProperty(sendEvent, key, { value: key });
|
||||
return f;
|
||||
},
|
||||
}));
|
||||
|
||||
ws.on('message', (data: string) => {
|
||||
const { id, method, params } = JSON.parse(data);
|
||||
if (isDebug) {
|
||||
process.stdout.write(`${ANSI.green}${method}${ANSI.reset}: ${JSON.stringify(params)}\n`);
|
||||
}
|
||||
this.onMessage(id, method, params);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static inspectorHTTPServer(req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) {
|
||||
if (req.method !== 'GET') {
|
||||
res.writeHead(405);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const json = (obj: unknown) => {
|
||||
const s = JSON.stringify(obj);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(s),
|
||||
});
|
||||
res.end(s);
|
||||
};
|
||||
|
||||
switch (req.url) {
|
||||
case '/json':
|
||||
case '/json/list':
|
||||
json([{
|
||||
description: `${packageJson.name} instance`,
|
||||
devtoolsFrontendUrl: 'chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9229/',
|
||||
devtoolsFrontendUrlCompat: 'chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9229/',
|
||||
faviconUrl: 'https://avatars0.githubusercontent.com/u/51185628',
|
||||
id: 'inspector.0',
|
||||
title: 'engine262',
|
||||
type: 'node',
|
||||
url: `file://${process.cwd()}`,
|
||||
webSocketDebuggerUrl: 'ws://localhost:9229/',
|
||||
}]);
|
||||
break;
|
||||
case '/json/version':
|
||||
json({
|
||||
'Browser': `${packageJson.name}/v${packageJson.version}`,
|
||||
'Protocol-Version': `${protocol.version.major}.${protocol.version.minor}`,
|
||||
});
|
||||
break;
|
||||
case '/json/protocol':
|
||||
json(protocol);
|
||||
break;
|
||||
default:
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static new(port = 9229, host = '127.0.0.1', isDebug = !!process.env.DEBUG) {
|
||||
const server = http.createServer(NodeWebsocketInspector.inspectorHTTPServer);
|
||||
const inspector = new NodeWebsocketInspector(server, isDebug);
|
||||
return new Promise<NodeWebsocketInspector>((resolve) => {
|
||||
server.listen(port, host, () => {
|
||||
resolve(inspector);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._server.close();
|
||||
this._ws.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { readFile, readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
evalQ, ManagedRealm, Realm, Throw, ThrowCompletion, type AgentHostDefined,
|
||||
} from '#self';
|
||||
|
||||
export function createLoadImportedModule(getCache = (realm: ManagedRealm) => realm.HostDefined.resolverCache) {
|
||||
const validateType = (attributes: Map<string, string>, finish: (completion: ThrowCompletion) => void) => {
|
||||
const type = attributes.get('type');
|
||||
if (type && type !== 'json') {
|
||||
finish(Throw('TypeError', 'UnsupportedModuleType', type));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const parseModule = (realm: ManagedRealm, resolved: string, attributes: Map<string, string>, source: string) => (attributes.get('type') === 'json' || resolved.endsWith('.json')
|
||||
? realm.createJSONModule(resolved, source)
|
||||
: realm.compileModule(source, { specifier: resolved }));
|
||||
|
||||
const loadImportedModuleSyncOrAsync = (
|
||||
readFile: (path: string, callback: (err: NodeJS.ErrnoException | null, data: string) => void) => void,
|
||||
...[referrer, specifier, attributes, _hostDefined, finish]: Parameters<NonNullable<AgentHostDefined['loadImportedModule']>>
|
||||
) => {
|
||||
const realm = (referrer instanceof Realm ? referrer : referrer.Realm) as ManagedRealm;
|
||||
const cache = getCache(realm);
|
||||
|
||||
if (!referrer.HostDefined.specifier) {
|
||||
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateType(attributes, finish)) {
|
||||
return;
|
||||
}
|
||||
|
||||
evalQ(async (Q) => {
|
||||
const base = path.dirname(referrer.HostDefined.specifier!);
|
||||
const resolved = path.resolve(base, specifier);
|
||||
if (cache?.has(resolved)) {
|
||||
finish(cache.get(resolved)!);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
readFile(resolved, (err, data) => {
|
||||
if (err) {
|
||||
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
|
||||
return;
|
||||
}
|
||||
const m = Q(parseModule(realm, resolved, attributes, data));
|
||||
cache?.set(resolved, m);
|
||||
finish(m);
|
||||
});
|
||||
} catch (error) {
|
||||
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const loadImportedModule: NonNullable<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
|
||||
readFile(path, 'utf8', callback);
|
||||
});
|
||||
const loadImportedModuleSync: NonNullable<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
|
||||
try {
|
||||
const data = readFileSync(path, 'utf8');
|
||||
callback(null, data);
|
||||
} catch (error) {
|
||||
callback(error as NodeJS.ErrnoException, '');
|
||||
}
|
||||
});
|
||||
return { loadImportedModule, loadImportedModuleSync };
|
||||
}
|
||||
|
||||
export const { loadImportedModule, loadImportedModuleSync } = createLoadImportedModule();
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"references": [{ "path": "../inspector" }, { "path": "../../src" }],
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"declarationDir": "../../lib/node",
|
||||
"tsBuildInfoFile": "../../lib/node/.tsbuildinfo",
|
||||
"erasableSyntaxOnly": true,
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"rootDir": "./",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "../../lib/node/"
|
||||
},
|
||||
"include": [
|
||||
"./example.mts",
|
||||
"./bin.mts",
|
||||
"./inspector.mts",
|
||||
"./module.mts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user