mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
git-subtree-dir: engine262 git-subtree-split: ae71998cc5a8315700555135b1ac202a0d6d0b31
240 lines
7.1 KiB
TypeScript
240 lines
7.1 KiB
TypeScript
#!/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());
|
|
}
|