Compare commits
87 Commits
v0.4.1-alp
...
v0.7.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
d7f6010319
|
|||
|
87f8975275
|
|||
| 09eb6507dc | |||
|
2f58f6b245
|
|||
|
4bc363485f
|
|||
|
8e01db637b
|
|||
|
1c64912786
|
|||
|
28265a8f44
|
|||
|
e9e020512e
|
|||
|
4b0bbf5190
|
|||
|
031f78ebf1
|
|||
|
562f1f9425
|
|||
|
82a09e8865
|
|||
|
90da2db1fb
|
|||
|
3d275c52c0
|
|||
|
797585f539
|
|||
|
7a301eba8f
|
|||
|
1b2068a274
|
|||
|
078d7ed95f
|
|||
|
93973c12b1
|
|||
|
cad4f34b51
|
|||
|
d3571d6ee2
|
|||
|
caf9131cde
|
|||
| 8c6379eb24 | |||
|
380a5c720a
|
|||
|
76c3d377af
|
|||
|
42f443572a
|
|||
|
773bc72f3e
|
|||
|
0b5178e9fd
|
|||
|
8cffcff7db
|
|||
|
60bbaaccd4
|
|||
|
60b1762462
|
|||
|
34434965d2
|
|||
|
fe86123f0f
|
|||
|
d5e6edfa8b
|
|||
|
73345062ca
|
|||
|
124341969c
|
|||
|
8defd93855
|
|||
|
6c57e0e9f2
|
|||
|
f1932914ee
|
|||
|
977701e601
|
|||
|
e8a7ac8da8
|
|||
|
6b1cb852c2
|
|||
|
b59a003086
|
|||
|
1902e41f61
|
|||
|
27162ef8ac
|
|||
|
4f22e76d2b
|
|||
|
8924e7aadc
|
|||
| 1d0e31a423 | |||
|
ab56908171
|
|||
|
eb14bb080c
|
|||
|
f52f47cdb4
|
|||
|
567eaa8514
|
|||
|
2cfdd8e335
|
|||
|
4b1ec671e2
|
|||
|
b127aadcf6
|
|||
|
b6eaff65ca
|
|||
|
443dc0ffa1
|
|||
|
e107dd3507
|
|||
|
6af3c70fce
|
|||
|
8b743f49d1
|
|||
|
e1ce384815
|
|||
|
86d205e521
|
|||
|
f0ad936e5b
|
|||
|
4a1473c5be
|
|||
|
4111dbf5c4
|
|||
|
1666682dc2
|
|||
|
f2b33d0233
|
|||
|
f5a0b6eaf7
|
|||
|
829bea755d
|
|||
|
4b0dcffd13
|
|||
|
987f8b8f00
|
|||
|
55e3d46bc2
|
|||
|
3e25068219
|
|||
|
7ecb8bfabb
|
|||
|
488deea164
|
|||
|
ed08041335
|
|||
|
0a4149ba81
|
|||
|
30f5d619c3
|
|||
|
e7dbe91374
|
|||
|
455f5a613e
|
|||
|
1eeac3ae97
|
|||
|
1acd78e119
|
|||
|
df9932874d
|
|||
|
b47d1a7576
|
|||
|
fdfa8d7713
|
|||
|
f5d1287948
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* -text
|
||||
2
.github/workflows/tagged-release.yml
vendored
2
.github/workflows/tagged-release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
java-version: '11'
|
||||
- name: Clone repository
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||
with:
|
||||
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -1,11 +1,18 @@
|
||||
.vscode
|
||||
.gradle
|
||||
.ignore
|
||||
/out
|
||||
/build
|
||||
/bin
|
||||
/dst
|
||||
/*.js
|
||||
*
|
||||
|
||||
!/src
|
||||
!/src/**/*
|
||||
|
||||
/src/assets/js/ts.js
|
||||
|
||||
!/tests
|
||||
!/tests/**/*
|
||||
|
||||
!/.github
|
||||
!/.github/**/*
|
||||
|
||||
!/.gitignore
|
||||
!/.gitattributes
|
||||
!/build.js
|
||||
/dead-code
|
||||
/Metadata.java
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
|
||||
37
README.md
37
README.md
@@ -2,33 +2,28 @@
|
||||
|
||||
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
|
||||
|
||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
||||
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
|
||||
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||
|
||||
## Example
|
||||
|
||||
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
|
||||
The following is going to execute a simple javascript statement:
|
||||
|
||||
```java
|
||||
var engine = new PolyfillEngine(new File("."));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine.start();
|
||||
var engine = new Engine(false);
|
||||
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
|
||||
var env = Internals.apply(new Environment());
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
// Queue code to load internal libraries and start engine
|
||||
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
|
||||
// Run the engine on the same thread, until the event loop runs empty
|
||||
engine.run(true);
|
||||
|
||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
||||
Values.printValue(engine.context(), res);
|
||||
System.out.println();
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
}
|
||||
catch (InterruptedException _e) { return; }
|
||||
}
|
||||
catch (IOException | InterruptedException e) { return; }
|
||||
}
|
||||
// Get our result
|
||||
System.out.println(awaitable.await());
|
||||
```
|
||||
|
||||
## NOTE:
|
||||
|
||||
To setup the typescript bundle in your sources, run `node build.js init-ts`. This will download the latest version of typescript, minify it, and add it to your src/assets folder. If you are going to work with the `node build.js debug|release` command, this is not a necessary step.
|
||||
|
||||
129
build.js
129
build.js
@@ -1,17 +1,9 @@
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs/promises');
|
||||
const pt = require('path');
|
||||
const { argv } = require('process');
|
||||
const { argv, exit } = require('process');
|
||||
const { Readable } = require('stream');
|
||||
|
||||
const conf = {
|
||||
name: "java-jscript",
|
||||
author: "TopchetoEU",
|
||||
javahome: "",
|
||||
version: argv[3]
|
||||
};
|
||||
|
||||
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
|
||||
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
|
||||
|
||||
async function* find(src, dst, wildcard) {
|
||||
const stat = await fs.stat(src);
|
||||
@@ -36,9 +28,9 @@ async function copy(src, dst, wildcard) {
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
function run(cmd, ...args) {
|
||||
function run(suppressOutput, cmd, ...args) {
|
||||
return new Promise((res, rej) => {
|
||||
const proc = spawn(cmd, args, { stdio: 'inherit' });
|
||||
const proc = spawn(cmd, args, { stdio: suppressOutput ? 'ignore' : 'inherit' });
|
||||
proc.once('exit', code => {
|
||||
if (code === 0) res(code);
|
||||
else rej(new Error(`Process ${cmd} exited with code ${code}.`));
|
||||
@@ -46,7 +38,84 @@ function run(cmd, ...args) {
|
||||
})
|
||||
}
|
||||
|
||||
async function compileJava() {
|
||||
async function downloadTypescript(outFile) {
|
||||
try {
|
||||
// Import the required libraries, without the need of a package.json
|
||||
console.log('Importing modules...');
|
||||
await run(true, 'npm', 'i', 'tar', 'zlib', 'uglify-js');
|
||||
await fs.mkdir(pt.dirname(outFile), { recursive: true });
|
||||
await fs.mkdir('tmp', { recursive: true });
|
||||
|
||||
const tar = require('tar');
|
||||
const zlib = require('zlib');
|
||||
const { minify } = await import('uglify-js');
|
||||
|
||||
// Download the package.json file of typescript
|
||||
const packageDesc = await (await fetch('https://registry.npmjs.org/typescript/latest')).json();
|
||||
const url = packageDesc.dist.tarball;
|
||||
|
||||
console.log('Extracting typescript...');
|
||||
await new Promise(async (res, rej) => Readable.fromWeb((await fetch(url)).body)
|
||||
.pipe(zlib.createGunzip())
|
||||
.pipe(tar.x({ cwd: 'tmp', filter: v => v === 'package/lib/typescript.js' }))
|
||||
.on('end', res)
|
||||
.on('error', rej)
|
||||
);
|
||||
|
||||
console.log('Compiling typescript to ES5...');
|
||||
|
||||
const ts = require('./tmp/package/lib/typescript');
|
||||
const program = ts.createProgram([ 'tmp/package/lib/typescript.js' ], {
|
||||
outFile: "tmp/typescript-es5.js",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.None,
|
||||
downlevelIteration: true,
|
||||
allowJs: true,
|
||||
});
|
||||
program.emit();
|
||||
|
||||
console.log('Minifying typescript...');
|
||||
|
||||
const minified = minify((await fs.readFile('tmp/typescript-es5.js')).toString());
|
||||
if (minified.error) throw minified.error;
|
||||
|
||||
// Patch unsupported regex syntax
|
||||
minified.code = minified.code.replaceAll('[-/\\\\^$*+?.()|[\\]{}]', '[-/\\\\^$*+?.()|\\[\\]{}]');
|
||||
|
||||
const stream = await fs.open(outFile, 'w');
|
||||
|
||||
// Write typescript's license
|
||||
await stream.write(new TextEncoder().encode(`
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
|
||||
The following is a minified version of the unmodified Typescript 5.2
|
||||
***************************************************************************** */
|
||||
`));
|
||||
|
||||
await stream.write(minified.code);
|
||||
console.log('Typescript bundling done!');
|
||||
}
|
||||
finally {
|
||||
// Clean up all stuff left from typescript bundling
|
||||
await fs.rm('tmp', { recursive: true, force: true });
|
||||
await fs.rm('package.json');
|
||||
await fs.rm('package-lock.json');
|
||||
await fs.rm('node_modules', { recursive: true });
|
||||
}
|
||||
}
|
||||
async function compileJava(conf) {
|
||||
try {
|
||||
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
|
||||
.replace('${VERSION}', conf.version)
|
||||
@@ -57,8 +126,10 @@ async function compileJava() {
|
||||
if (argv[2] === 'debug') args.push('-g');
|
||||
args.push('-d', 'dst/classes', 'Metadata.java');
|
||||
|
||||
console.log('Compiling java project...');
|
||||
for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path);
|
||||
await run(conf.javahome + 'javac', ...args);
|
||||
await run(false, conf.javahome + 'javac', ...args);
|
||||
console.log('Compiled java project!');
|
||||
}
|
||||
finally {
|
||||
await fs.rm('Metadata.java');
|
||||
@@ -67,13 +138,35 @@ async function compileJava() {
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
if (argv[2] === 'init-ts') {
|
||||
await downloadTypescript('src/assets/js/ts.js');
|
||||
}
|
||||
else {
|
||||
const conf = {
|
||||
name: "java-jscript",
|
||||
author: "TopchetoEU",
|
||||
javahome: "",
|
||||
version: argv[3]
|
||||
};
|
||||
|
||||
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
|
||||
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
|
||||
|
||||
try { await fs.rm('dst', { recursive: true }); } catch {}
|
||||
await copy('src', 'dst/classes', v => !v.endsWith('.java'));
|
||||
await compileJava();
|
||||
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
|
||||
|
||||
await Promise.all([
|
||||
downloadTypescript('dst/classes/assets/js/ts.js'),
|
||||
copy('src', 'dst/classes', v => !v.endsWith('.java')),
|
||||
compileJava(conf),
|
||||
]);
|
||||
|
||||
await run(true, 'jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
|
||||
console.log('Done!');
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (argv[2] === 'debug') throw e;
|
||||
else console.log(e.toString());
|
||||
console.log(e.toString());
|
||||
exit(-1);
|
||||
}
|
||||
})();
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
1004
src/assets/debugger/protocol.json
Normal file
1004
src/assets/debugger/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
113
src/assets/js/bootstrap.js
vendored
Normal file
113
src/assets/js/bootstrap.js
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
(function (ts, env, libs) {
|
||||
var src = '', version = 0;
|
||||
var lib = libs.concat([
|
||||
'declare function exit(): never;',
|
||||
'declare function go(): any;',
|
||||
'declare function getTsDeclarations(): string[];'
|
||||
]).join('');
|
||||
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
|
||||
var environments = {};
|
||||
var declSnapshots = [];
|
||||
|
||||
var settings = {
|
||||
outDir: "/out",
|
||||
declarationDir: "/out",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [ ],
|
||||
module: ts.ModuleKind.None,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
downlevelIteration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
experimentalDecorators: true,
|
||||
strict: true,
|
||||
sourceMap: true,
|
||||
};
|
||||
|
||||
var reg = ts.createDocumentRegistry();
|
||||
var service = ts.createLanguageService({
|
||||
getCurrentDirectory: function() { return "/"; },
|
||||
getDefaultLibFileName: function() { return "/lib.d.ts"; },
|
||||
getScriptFileNames: function() {
|
||||
var res = [ "/src.ts", "/lib.d.ts" ];
|
||||
for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
|
||||
return res;
|
||||
},
|
||||
getCompilationSettings: function () { return settings; },
|
||||
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
|
||||
|
||||
getScriptSnapshot: function(filename) {
|
||||
if (filename === "/lib.d.ts") return libSnapshot;
|
||||
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||
|
||||
var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
|
||||
if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
|
||||
return declSnapshots[index - 1];
|
||||
}
|
||||
|
||||
throw new Error("File '" + filename + "' doesn't exist.");
|
||||
},
|
||||
getScriptVersion: function (filename) {
|
||||
if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0;
|
||||
else return version;
|
||||
},
|
||||
}, reg);
|
||||
|
||||
service.getEmitOutput("/lib.d.ts");
|
||||
log("Loaded libraries!");
|
||||
|
||||
var oldCompile = env.compile;
|
||||
|
||||
function compile(code, filename, env) {
|
||||
src = code;
|
||||
version++;
|
||||
|
||||
if (!environments[env.id]) environments[env.id] = []
|
||||
var decls = declSnapshots = environments[env.id];
|
||||
var emit = service.getEmitOutput("/src.ts");
|
||||
|
||||
var diagnostics = []
|
||||
.concat(service.getCompilerOptionsDiagnostics())
|
||||
.concat(service.getSyntacticDiagnostics("/src.ts"))
|
||||
.concat(service.getSemanticDiagnostics("/src.ts"))
|
||||
.map(function (diagnostic) {
|
||||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
if (diagnostic.file) {
|
||||
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||
var file = diagnostic.file.fileName.substring(1);
|
||||
if (file === "src.ts") file = filename;
|
||||
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
|
||||
}
|
||||
else return message;
|
||||
});
|
||||
|
||||
if (diagnostics.length > 0) {
|
||||
throw new SyntaxError(diagnostics.join("\n"));
|
||||
}
|
||||
|
||||
var map = JSON.parse(emit.outputFiles[0].text);
|
||||
var result = emit.outputFiles[1].text;
|
||||
var declaration = emit.outputFiles[2].text;
|
||||
|
||||
var compiled = oldCompile(result, filename, env);
|
||||
|
||||
return {
|
||||
function: function () {
|
||||
var val = compiled.function.apply(this, arguments);
|
||||
if (declaration !== '') decls.push(ts.ScriptSnapshot.fromString(declaration));
|
||||
return val;
|
||||
},
|
||||
breakpoints: compiled.breakpoints,
|
||||
mapChain: compiled.mapChain.concat(map.mappings),
|
||||
};
|
||||
}
|
||||
|
||||
function apply(env) {
|
||||
env.compile = compile;
|
||||
env.global.getTsDeclarations = function() {
|
||||
return environments[env.id];
|
||||
}
|
||||
}
|
||||
|
||||
apply(env);
|
||||
})(arguments[0], arguments[1], arguments[2]);
|
||||
@@ -19,11 +19,6 @@ type Extract<T, U> = T extends U ? T : never;
|
||||
type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT }
|
||||
type ReplaceFunc = (match: string, ...args: any[]) => string;
|
||||
|
||||
type PromiseFulfillFunc<T> = (val: T) => void;
|
||||
type PromiseThenFunc<T, NextT> = (val: T) => NextT;
|
||||
type PromiseRejectFunc = (err: unknown) => void;
|
||||
type PromiseFunc<T> = (resolve: PromiseFulfillFunc<T>, reject: PromiseRejectFunc) => void;
|
||||
|
||||
type PromiseResult<T> = { type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; }
|
||||
|
||||
// wippidy-wine, this code is now mine :D
|
||||
@@ -46,8 +41,7 @@ type IteratorReturnResult<TReturn> =
|
||||
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
|
||||
|
||||
interface Thenable<T> {
|
||||
then<NextT>(onFulfilled: PromiseThenFunc<T, NextT>, onRejected?: PromiseRejectFunc): Promise<Awaited<NextT>>;
|
||||
then(onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise<T>;
|
||||
then<NextT = void>(onFulfilled?: (val: T) => NextT, onRejected?: (err: any) => NextT): Promise<Awaited<NextT>>;
|
||||
}
|
||||
|
||||
interface RegExpResultIndices extends Array<[number, number]> {
|
||||
@@ -111,7 +105,7 @@ interface AsyncIterableIterator<T> extends AsyncIterator<T> {
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
|
||||
}
|
||||
|
||||
interface Generator<T = unknown, TReturn = unknown, TNext = unknown> extends Iterator<T, TReturn, TNext> {
|
||||
interface Generator<T = unknown, TReturn = void, TNext = unknown> extends Iterator<T, TReturn, TNext> {
|
||||
[Symbol.iterator](): Generator<T, TReturn, TNext>;
|
||||
return(value: TReturn): IteratorResult<T, TReturn>;
|
||||
throw(e: any): IteratorResult<T, TReturn>;
|
||||
@@ -124,7 +118,7 @@ interface GeneratorFunction {
|
||||
readonly prototype: Generator;
|
||||
}
|
||||
|
||||
interface AsyncGenerator<T = unknown, TReturn = unknown, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
|
||||
interface AsyncGenerator<T = unknown, TReturn = void, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
|
||||
return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
|
||||
throw(e: any): Promise<IteratorResult<T, TReturn>>;
|
||||
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
|
||||
@@ -464,14 +458,19 @@ interface SymbolConstructor {
|
||||
readonly asyncIterator: unique symbol;
|
||||
}
|
||||
|
||||
|
||||
interface Promise<T> extends Thenable<T> {
|
||||
catch(func: PromiseRejectFunc): Promise<T>;
|
||||
catch<ResT = void>(func: (err: unknown) => ResT): Promise<ResT>;
|
||||
finally(func: () => void): Promise<T>;
|
||||
constructor: PromiseConstructor;
|
||||
}
|
||||
interface PromiseConstructor {
|
||||
interface PromiseConstructorLike {
|
||||
new <T>(func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Thenable<Awaited<T>>;
|
||||
}
|
||||
interface PromiseConstructor extends PromiseConstructorLike {
|
||||
prototype: Promise<any>;
|
||||
|
||||
new <T>(func: PromiseFunc<T>): Promise<Awaited<T>>;
|
||||
new <T>(func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Promise<Awaited<T>>;
|
||||
resolve<T>(val: T): Promise<Awaited<T>>;
|
||||
reject(val: any): Promise<never>;
|
||||
|
||||
@@ -482,6 +481,39 @@ interface PromiseConstructor {
|
||||
allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>;
|
||||
}
|
||||
|
||||
interface FileStat {
|
||||
type: 'file' | 'folder';
|
||||
mode: 'r' | 'rw';
|
||||
}
|
||||
interface File {
|
||||
readonly pointer: Promise<number>;
|
||||
readonly length: Promise<number>;
|
||||
|
||||
read(n: number): Promise<number[]>;
|
||||
write(buff: number[]): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
seek(offset: number, whence: number): Promise<void>;
|
||||
}
|
||||
interface Filesystem {
|
||||
readonly SEEK_SET: 0;
|
||||
readonly SEEK_CUR: 1;
|
||||
readonly SEEK_END: 2;
|
||||
|
||||
open(path: string, mode: 'r' | 'rw'): Promise<File>;
|
||||
ls(path: string): AsyncIterableIterator<string>;
|
||||
mkdir(path: string): Promise<void>;
|
||||
mkfile(path: string): Promise<void>;
|
||||
rm(path: string, recursive?: boolean): Promise<void>;
|
||||
stat(path: string): Promise<FileStat>;
|
||||
exists(path: string): Promise<boolean>;
|
||||
normalize(...paths: string[]): string;
|
||||
}
|
||||
|
||||
interface Encoding {
|
||||
encode(val: string): number[];
|
||||
decode(val: number[]): string;
|
||||
}
|
||||
|
||||
declare var String: StringConstructor;
|
||||
//@ts-ignore
|
||||
declare const arguments: IArguments;
|
||||
@@ -498,6 +530,7 @@ declare var parseInt: typeof Number.parseInt;
|
||||
declare var parseFloat: typeof Number.parseFloat;
|
||||
|
||||
declare function log(...vals: any[]): void;
|
||||
declare function require(name: string): any;
|
||||
|
||||
declare var Array: ArrayConstructor;
|
||||
declare var Boolean: BooleanConstructor;
|
||||
@@ -508,11 +541,14 @@ declare var Object: ObjectConstructor;
|
||||
declare var Symbol: SymbolConstructor;
|
||||
declare var Promise: PromiseConstructor;
|
||||
declare var Math: MathObject;
|
||||
declare var Encoding: Encoding;
|
||||
declare var Filesystem: Filesystem;
|
||||
|
||||
declare var Error: ErrorConstructor;
|
||||
declare var RangeError: RangeErrorConstructor;
|
||||
declare var TypeError: TypeErrorConstructor;
|
||||
declare var SyntaxError: SyntaxErrorConstructor;
|
||||
declare var self: typeof globalThis;
|
||||
|
||||
declare class Map<KeyT, ValueT> {
|
||||
public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>;
|
||||
File diff suppressed because it is too large
Load Diff
53
src/me/topchetoeu/jscript/Buffer.java
Normal file
53
src/me/topchetoeu/jscript/Buffer.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public class Buffer {
|
||||
private byte[] data;
|
||||
private int length;
|
||||
|
||||
public void write(int i, byte[] val) {
|
||||
if (i + val.length > data.length) {
|
||||
var newCap = i + val.length + 1;
|
||||
if (newCap < data.length * 2) newCap = data.length * 2;
|
||||
|
||||
var tmp = new byte[newCap];
|
||||
System.arraycopy(this.data, 0, tmp, 0, length);
|
||||
this.data = tmp;
|
||||
}
|
||||
|
||||
System.arraycopy(val, 0, data, i, val.length);
|
||||
if (i + val.length > length) length = i + val.length;
|
||||
}
|
||||
public int read(int i, byte[] buff) {
|
||||
int n = buff.length;
|
||||
if (i + n > length) n = length - i;
|
||||
System.arraycopy(data, i, buff, 0, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
public void append(byte b) {
|
||||
write(length, new byte[] { b });
|
||||
}
|
||||
|
||||
public byte[] data() {
|
||||
var res = new byte[length];
|
||||
System.arraycopy(this.data, 0, res, 0, length);
|
||||
return res;
|
||||
}
|
||||
public int length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public Buffer(byte[] data) {
|
||||
this.data = new byte[data.length];
|
||||
this.length = data.length;
|
||||
System.arraycopy(data, 0, this.data, 0, data.length);
|
||||
}
|
||||
public Buffer(int capacity) {
|
||||
this.data = new byte[capacity];
|
||||
this.length = 0;
|
||||
}
|
||||
public Buffer() {
|
||||
this.data = new byte[128];
|
||||
this.length = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class Filename {
|
||||
public final String protocol;
|
||||
@@ -40,9 +41,7 @@ public class Filename {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Filename fromFile(File file) {
|
||||
return new Filename("file", file.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Filename(String protocol, String path) {
|
||||
@@ -51,4 +50,16 @@ public class Filename {
|
||||
this.protocol = protocol;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static Filename parse(String val) {
|
||||
var i = val.indexOf("://");
|
||||
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
|
||||
else return new Filename("file", val.trim());
|
||||
}
|
||||
public static Path normalize(String path) {
|
||||
return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
|
||||
}
|
||||
public static Filename fromFile(File file) {
|
||||
return new Filename("file", file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,23 @@ public class Location implements Comparable<Location> {
|
||||
this.start = start;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public static Location parse(String raw) {
|
||||
int i0 = -1, i1 = -1;
|
||||
for (var i = raw.length() - 1; i >= 0; i--) {
|
||||
if (raw.charAt(i) == ':') {
|
||||
if (i1 == -1) i1 = i;
|
||||
else if (i0 == -1) {
|
||||
i0 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Location(
|
||||
Integer.parseInt(raw.substring(i0 + 1, i1)),
|
||||
Integer.parseInt(raw.substring(i1 + 1)),
|
||||
Filename.parse(raw.substring(0, i0))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.nio.file.Path;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugServer;
|
||||
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
@@ -19,15 +18,14 @@ import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
|
||||
import me.topchetoeu.jscript.filesystem.Mode;
|
||||
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
|
||||
import me.topchetoeu.jscript.lib.Internals;
|
||||
import me.topchetoeu.jscript.modules.ModuleRepo;
|
||||
|
||||
public class Main {
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine;
|
||||
static Environment env;
|
||||
static int j = 0;
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public static class Printer implements Observer<Object> {
|
||||
public void next(Object data) {
|
||||
Values.printValue(null, data);
|
||||
System.out.println();
|
||||
@@ -37,32 +35,80 @@ public class Main {
|
||||
Values.printError(err, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
engineTask.interrupt();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
|
||||
engine = new Engine(true);
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine = new Engine(true);
|
||||
static DebugServer debugServer = new DebugServer();
|
||||
static Environment environment = new Environment(null, null, null);
|
||||
|
||||
env = new Environment(null, null, null);
|
||||
var exited = new boolean[1];
|
||||
var server = new DebugServer();
|
||||
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
|
||||
static int j = 0;
|
||||
static boolean exited = false;
|
||||
static String[] args;
|
||||
|
||||
engineTask = engine.start();
|
||||
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
private static void reader() {
|
||||
try {
|
||||
for (var arg : args) {
|
||||
try {
|
||||
if (arg.equals("--ts")) initTypescript();
|
||||
else {
|
||||
var file = Path.of(arg);
|
||||
var raw = Files.readString(file);
|
||||
var res = engine.pushMsg(
|
||||
false, environment,
|
||||
Filename.fromFile(file.toFile()),
|
||||
raw, null
|
||||
).await();
|
||||
Values.printValue(null, res);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, null); }
|
||||
}
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
var raw = Reading.read();
|
||||
|
||||
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
|
||||
new Internals().apply(env);
|
||||
if (raw == null) break;
|
||||
var res = engine.pushMsg(
|
||||
false, environment,
|
||||
new Filename("jscript", "repl/" + i + ".js"),
|
||||
raw, null
|
||||
).await();
|
||||
Values.printValue(null, res);
|
||||
System.out.println();
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, null); }
|
||||
catch (SyntaxException e) { Values.printError(e, null); }
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.out.println(e.toString());
|
||||
exited = true;
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (!exited) {
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (exited) {
|
||||
debugTask.interrupt();
|
||||
engineTask.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
env.global.define("exit", _ctx -> {
|
||||
exited[0] = true;
|
||||
private static void initEnv() {
|
||||
environment = Internals.apply(environment);
|
||||
|
||||
environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
|
||||
exited = true;
|
||||
throw new InterruptException();
|
||||
});
|
||||
env.global.define("go", _ctx -> {
|
||||
}));
|
||||
environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> {
|
||||
try {
|
||||
var f = Path.of("do.js");
|
||||
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||
@@ -71,81 +117,52 @@ public class Main {
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
// TODO: make better API
|
||||
env.global.define(true, new NativeFunction("include", (_ctx, th, __args) -> {
|
||||
try {
|
||||
var currFilename = StackData.peekFrame(_ctx).function.loc().filename();
|
||||
var loc = Path.of("").toAbsolutePath();
|
||||
if (currFilename.protocol.equals("file")) loc = Path.of(currFilename.path).getParent();
|
||||
var path = loc.resolve(Path.of(__args.length >= 1 ? Values.toString(_ctx, __args[0]) : ""));
|
||||
var src = Files.readString(path);
|
||||
var func = _ctx.compile(Filename.fromFile(path.toFile()), src);
|
||||
var callArgs = new ArrayValue();
|
||||
if (__args.length >= 2 && __args[1] instanceof ArrayValue) callArgs = (ArrayValue)__args[1];
|
||||
return func.call(_ctx, null, callArgs);
|
||||
}
|
||||
catch (IOException e) { throw EngineException.ofError("IOError", "Couldn't open file."); }
|
||||
}));
|
||||
|
||||
return null;
|
||||
}), null).await();
|
||||
|
||||
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
|
||||
environment.filesystem.protocols.put("file", new PhysicalFilesystem("."));
|
||||
environment.modules.repos.put("file", ModuleRepo.ofFilesystem(environment.filesystem));
|
||||
}
|
||||
private static void initEngine() {
|
||||
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
|
||||
engineTask = engine.start();
|
||||
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
}
|
||||
private static void initTypescript() {
|
||||
try {
|
||||
var tsEnv = env.child();
|
||||
var tsEnv = Internals.apply(new Environment(null, null, null));
|
||||
tsEnv.stackVisible = false;
|
||||
tsEnv.global.define(null, "module", false, new ObjectValue());
|
||||
var bsEnv = Internals.apply(new Environment(null, null, null));
|
||||
bsEnv.stackVisible = false;
|
||||
|
||||
engine.pushMsg(
|
||||
false, new Context(engine).pushEnv(tsEnv),
|
||||
false, tsEnv,
|
||||
new Filename("jscript", "ts.js"),
|
||||
Reading.resourceToString("js/ts.js"), null
|
||||
).await();
|
||||
System.out.println("Loaded typescript!");
|
||||
|
||||
var ctx = new Context(engine).pushEnv(env.child());
|
||||
|
||||
engine.pushMsg(
|
||||
false, ctx,
|
||||
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
|
||||
tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
|
||||
false, bsEnv,
|
||||
new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
|
||||
tsEnv.global.get(new Context(engine, bsEnv), "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
|
||||
).await();
|
||||
}
|
||||
catch (EngineException e) {
|
||||
Values.printError(e, "(while initializing TS)");
|
||||
}
|
||||
}
|
||||
|
||||
var reader = new Thread(() -> {
|
||||
try {
|
||||
for (var arg : args) {
|
||||
try {
|
||||
var file = Path.of(arg);
|
||||
var raw = Files.readString(file);
|
||||
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await());
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, ""); }
|
||||
}
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
var raw = Reading.read();
|
||||
public static void main(String args[]) {
|
||||
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
|
||||
|
||||
Main.args = args;
|
||||
var reader = new Thread(Main::reader);
|
||||
|
||||
initEnv();
|
||||
initEngine();
|
||||
|
||||
if (raw == null) break;
|
||||
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, ""); }
|
||||
}
|
||||
}
|
||||
catch (IOException e) { exited[0] = true; }
|
||||
catch (SyntaxException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (!exited[0]) {
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (exited[0]) debugTask.interrupt();
|
||||
});
|
||||
reader.setDaemon(true);
|
||||
reader.setName("STD Reader");
|
||||
reader.start();
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public class Metadata {
|
||||
public static final String VERSION = "${VERSION}";
|
||||
public static final String AUTHOR = "${AUTHOR}";
|
||||
public static final String NAME = "${NAME}";
|
||||
private static final String VERSION = "${VERSION}";
|
||||
private static final String AUTHOR = "${AUTHOR}";
|
||||
private static final String NAME = "${NAME}";
|
||||
|
||||
public static String version() {
|
||||
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
|
||||
else return VERSION;
|
||||
}
|
||||
public static String author() {
|
||||
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
|
||||
else return AUTHOR;
|
||||
}
|
||||
public static String name() {
|
||||
if (NAME.equals("$" + "{NAME}")) return "some-product";
|
||||
else return NAME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,22 +15,13 @@ public class Reading {
|
||||
}
|
||||
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
StringBuilder out = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
for(var line = br.readLine(); line != null; line = br.readLine()) {
|
||||
out.append(line).append('\n');
|
||||
}
|
||||
|
||||
br.close();
|
||||
return out.toString();
|
||||
}
|
||||
try { return new String(in.readAllBytes()); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
public static InputStream resourceToStream(String name) {
|
||||
return Reading.class.getResourceAsStream("/assets/" + name);
|
||||
}
|
||||
public static String resourceToString(String name) {
|
||||
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
|
||||
if (str == null) return null;
|
||||
return streamToString(str);
|
||||
return streamToString(resourceToStream(name));
|
||||
}
|
||||
}
|
||||
|
||||
21
src/me/topchetoeu/jscript/compilation/CalculateResult.java
Normal file
21
src/me/topchetoeu/jscript/compilation/CalculateResult.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public final class CalculateResult {
|
||||
public final boolean exists;
|
||||
public final Object value;
|
||||
|
||||
public final boolean isTruthy() {
|
||||
return exists && Values.toBoolean(value);
|
||||
}
|
||||
|
||||
public CalculateResult(Object value) {
|
||||
this.exists = true;
|
||||
this.value = value;
|
||||
}
|
||||
public CalculateResult() {
|
||||
this.exists = false;
|
||||
this.value = null;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
|
||||
public class CompileTarget {
|
||||
public final Vector<Instruction> target = new Vector<>();
|
||||
public final Map<Long, FunctionBody> functions;
|
||||
public final TreeSet<Location> breakpoints;
|
||||
private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
|
||||
|
||||
public Instruction add(Instruction instr) {
|
||||
target.add(instr);
|
||||
@@ -18,19 +23,42 @@ public class CompileTarget {
|
||||
public Instruction set(int i, Instruction instr) {
|
||||
return target.set(i, instr);
|
||||
}
|
||||
public void setDebug(int i) {
|
||||
breakpoints.add(target.get(i).location);
|
||||
public void setDebug(int i, BreakpointType type) {
|
||||
var instr = target.get(i);
|
||||
instr.breakpoint = type;
|
||||
|
||||
if (type == BreakpointType.NONE) {
|
||||
breakpoints.remove(target.get(i).location);
|
||||
bpToInstr.remove(instr.location, instr);
|
||||
}
|
||||
public void setDebug() {
|
||||
setDebug(target.size() - 1);
|
||||
else {
|
||||
breakpoints.add(target.get(i).location);
|
||||
|
||||
var old = bpToInstr.put(instr.location, instr);
|
||||
if (old != null) old.breakpoint = BreakpointType.NONE;
|
||||
}
|
||||
}
|
||||
public void setDebug(BreakpointType type) {
|
||||
setDebug(target.size() - 1, type);
|
||||
}
|
||||
public Instruction get(int i) {
|
||||
return target.get(i);
|
||||
}
|
||||
public int size() { return target.size(); }
|
||||
public Location lastLoc(Location fallback) {
|
||||
if (target.size() == 0) return fallback;
|
||||
else return target.get(target.size() - 1).location;
|
||||
}
|
||||
|
||||
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
||||
|
||||
public FunctionBody body() {
|
||||
return functions.get(0l);
|
||||
}
|
||||
public CodeFunction func(Environment env) {
|
||||
return new CodeFunction(env, "", body());
|
||||
}
|
||||
|
||||
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
|
||||
this.functions = functions;
|
||||
this.breakpoints = breakpoints;
|
||||
|
||||
@@ -1,68 +1,54 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.control.ContinueStatement;
|
||||
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
|
||||
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CompoundStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
public final boolean separateFuncs;
|
||||
public Location end;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var stm : statements) {
|
||||
if (!stm.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var stm : statements) {
|
||||
stm.declare(varsScope);
|
||||
}
|
||||
for (var stm : statements) stm.declare(varsScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) {
|
||||
int start = target.size();
|
||||
((FunctionStatement)stm).compile(target, scope, null, true);
|
||||
target.setDebug(start);
|
||||
target.add(Instruction.discard());
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||
List<Statement> statements = new Vector<Statement>();
|
||||
if (separateFuncs) for (var stm : this.statements) {
|
||||
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
|
||||
stm.compile(target, scope, false);
|
||||
}
|
||||
else statements.add(stm);
|
||||
}
|
||||
else statements = List.of(this.statements);
|
||||
|
||||
var polluted = false;
|
||||
|
||||
for (var i = 0; i < statements.size(); i++) {
|
||||
var stm = statements.get(i);
|
||||
|
||||
if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
|
||||
}
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i];
|
||||
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
|
||||
else stm.compileWithDebug(target, scope, pollute);
|
||||
if (!polluted && pollute) {
|
||||
target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
if (end != null) {
|
||||
target.add(Instruction.nop().locate(end));
|
||||
target.setDebug();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var res = new Vector<Statement>(statements.length);
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i].optimize();
|
||||
if (i < statements.length - 1 && stm.pure()) continue;
|
||||
res.add(stm);
|
||||
if (
|
||||
stm instanceof ContinueStatement ||
|
||||
stm instanceof ReturnStatement ||
|
||||
stm instanceof ThrowStatement ||
|
||||
stm instanceof ContinueStatement
|
||||
) break;
|
||||
}
|
||||
|
||||
if (res.size() == 1) return res.get(0);
|
||||
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
|
||||
}
|
||||
|
||||
public CompoundStatement setEnd(Location loc) {
|
||||
@@ -70,8 +56,9 @@ public class CompoundStatement extends Statement {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompoundStatement(Location loc, Statement ...statements) {
|
||||
public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
|
||||
super(loc);
|
||||
this.separateFuncs = separateFuncs;
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,25 @@ package me.topchetoeu.jscript.compilation;
|
||||
public class FunctionBody {
|
||||
public final Instruction[] instructions;
|
||||
public final String[] captureNames, localNames;
|
||||
public final int localsN, argsN;
|
||||
|
||||
public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) {
|
||||
public FunctionBody(int localsN, int argsN, Instruction[] instructions, String[] captureNames, String[] localNames) {
|
||||
this.argsN = argsN;
|
||||
this.localsN = localsN;
|
||||
this.instructions = instructions;
|
||||
this.captureNames = captureNames;
|
||||
this.localNames = localNames;
|
||||
}
|
||||
public FunctionBody(Instruction[] instructions) {
|
||||
public FunctionBody(int localsN, int argsN, Instruction[] instructions) {
|
||||
this.argsN = argsN;
|
||||
this.localsN = localsN;
|
||||
this.instructions = instructions;
|
||||
this.captureNames = new String[0];
|
||||
this.localNames = new String[0];
|
||||
}
|
||||
public FunctionBody(Instruction... instructions) {
|
||||
this.argsN = 0;
|
||||
this.localsN = 2;
|
||||
this.instructions = instructions;
|
||||
this.captureNames = new String[0];
|
||||
this.localNames = new String[0];
|
||||
|
||||
@@ -10,7 +10,8 @@ public class Instruction {
|
||||
THROW,
|
||||
THROW_SYNTAX,
|
||||
DELETE,
|
||||
TRY,
|
||||
TRY_START,
|
||||
TRY_END,
|
||||
NOP,
|
||||
|
||||
CALL,
|
||||
@@ -33,7 +34,6 @@ public class Instruction {
|
||||
LOAD_REGEX,
|
||||
|
||||
DUP,
|
||||
MOVE,
|
||||
|
||||
STORE_VAR,
|
||||
STORE_MEMBER,
|
||||
@@ -45,51 +45,30 @@ public class Instruction {
|
||||
|
||||
TYPEOF,
|
||||
OPERATION;
|
||||
// TYPEOF,
|
||||
// INSTANCEOF(true),
|
||||
// IN(true),
|
||||
}
|
||||
public static enum BreakpointType {
|
||||
NONE,
|
||||
STEP_OVER,
|
||||
STEP_IN;
|
||||
|
||||
// MULTIPLY(true),
|
||||
// DIVIDE(true),
|
||||
// MODULO(true),
|
||||
// ADD(true),
|
||||
// SUBTRACT(true),
|
||||
|
||||
// USHIFT_RIGHT(true),
|
||||
// SHIFT_RIGHT(true),
|
||||
// SHIFT_LEFT(true),
|
||||
|
||||
// GREATER(true),
|
||||
// LESS(true),
|
||||
// GREATER_EQUALS(true),
|
||||
// LESS_EQUALS(true),
|
||||
// LOOSE_EQUALS(true),
|
||||
// LOOSE_NOT_EQUALS(true),
|
||||
// EQUALS(true),
|
||||
// NOT_EQUALS(true),
|
||||
|
||||
// AND(true),
|
||||
// OR(true),
|
||||
// XOR(true),
|
||||
|
||||
// NEG(true),
|
||||
// POS(true),
|
||||
// NOT(true),
|
||||
// INVERSE(true);
|
||||
|
||||
// final boolean isOperation;
|
||||
|
||||
// private Type(boolean isOperation) {
|
||||
// this.isOperation = isOperation;
|
||||
// }
|
||||
// private Type() {
|
||||
// this(false);
|
||||
// }
|
||||
public boolean shouldStepIn() {
|
||||
return this != NONE;
|
||||
}
|
||||
public boolean shouldStepOver() {
|
||||
return this == STEP_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
public final Object[] params;
|
||||
public Location location;
|
||||
public BreakpointType breakpoint = BreakpointType.NONE;
|
||||
|
||||
public Instruction setDbgData(Instruction other) {
|
||||
this.location = other.location;
|
||||
this.breakpoint = other.breakpoint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Instruction locate(Location loc) {
|
||||
this.location = loc;
|
||||
@@ -129,26 +108,32 @@ public class Instruction {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public static Instruction tryInstr(int n, int catchN, int finallyN) {
|
||||
return new Instruction(null, Type.TRY, n, catchN, finallyN);
|
||||
public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
|
||||
return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
|
||||
}
|
||||
public static Instruction throwInstr() {
|
||||
return new Instruction(null, Type.THROW);
|
||||
public static Instruction tryEnd(Location loc) {
|
||||
return new Instruction(loc, Type.TRY_END);
|
||||
}
|
||||
public static Instruction throwSyntax(SyntaxException err) {
|
||||
return new Instruction(null, Type.THROW_SYNTAX, err.getMessage());
|
||||
public static Instruction throwInstr(Location loc) {
|
||||
return new Instruction(loc, Type.THROW);
|
||||
}
|
||||
public static Instruction delete() {
|
||||
return new Instruction(null, Type.DELETE);
|
||||
public static Instruction throwSyntax(Location loc, SyntaxException err) {
|
||||
return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage());
|
||||
}
|
||||
public static Instruction ret() {
|
||||
return new Instruction(null, Type.RETURN);
|
||||
public static Instruction throwSyntax(Location loc, String err) {
|
||||
return new Instruction(loc, Type.THROW_SYNTAX, err);
|
||||
}
|
||||
public static Instruction debug() {
|
||||
return new Instruction(null, Type.NOP, "debug");
|
||||
public static Instruction delete(Location loc) {
|
||||
return new Instruction(loc, Type.DELETE);
|
||||
}
|
||||
public static Instruction ret(Location loc) {
|
||||
return new Instruction(loc, Type.RETURN);
|
||||
}
|
||||
public static Instruction debug(Location loc) {
|
||||
return new Instruction(loc, Type.NOP, "debug");
|
||||
}
|
||||
|
||||
public static Instruction nop(Object ...params) {
|
||||
public static Instruction nop(Location loc, Object ...params) {
|
||||
for (var param : params) {
|
||||
if (param instanceof String) continue;
|
||||
if (param instanceof Boolean) continue;
|
||||
@@ -158,109 +143,104 @@ public class Instruction {
|
||||
|
||||
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
|
||||
}
|
||||
return new Instruction(null, Type.NOP, params);
|
||||
return new Instruction(loc, Type.NOP, params);
|
||||
}
|
||||
|
||||
public static Instruction call(int argn) {
|
||||
return new Instruction(null, Type.CALL, argn);
|
||||
public static Instruction call(Location loc, int argn) {
|
||||
return new Instruction(loc, Type.CALL, argn);
|
||||
}
|
||||
public static Instruction callNew(int argn) {
|
||||
return new Instruction(null, Type.CALL_NEW, argn);
|
||||
public static Instruction callNew(Location loc, int argn) {
|
||||
return new Instruction(loc, Type.CALL_NEW, argn);
|
||||
}
|
||||
public static Instruction jmp(int offset) {
|
||||
return new Instruction(null, Type.JMP, offset);
|
||||
public static Instruction jmp(Location loc, int offset) {
|
||||
return new Instruction(loc, Type.JMP, offset);
|
||||
}
|
||||
public static Instruction jmpIf(int offset) {
|
||||
return new Instruction(null, Type.JMP_IF, offset);
|
||||
public static Instruction jmpIf(Location loc, int offset) {
|
||||
return new Instruction(loc, Type.JMP_IF, offset);
|
||||
}
|
||||
public static Instruction jmpIfNot(int offset) {
|
||||
return new Instruction(null, Type.JMP_IFN, offset);
|
||||
public static Instruction jmpIfNot(Location loc, int offset) {
|
||||
return new Instruction(loc, Type.JMP_IFN, offset);
|
||||
}
|
||||
|
||||
public static Instruction loadValue(Object val) {
|
||||
return new Instruction(null, Type.LOAD_VALUE, val);
|
||||
public static Instruction loadValue(Location loc, Object val) {
|
||||
return new Instruction(loc, Type.LOAD_VALUE, val);
|
||||
}
|
||||
|
||||
public static Instruction makeVar(String name) {
|
||||
return new Instruction(null, Type.MAKE_VAR, name);
|
||||
public static Instruction makeVar(Location loc, String name) {
|
||||
return new Instruction(loc, Type.MAKE_VAR, name);
|
||||
}
|
||||
public static Instruction loadVar(Object i) {
|
||||
return new Instruction(null, Type.LOAD_VAR, i);
|
||||
public static Instruction loadVar(Location loc, Object i) {
|
||||
return new Instruction(loc, Type.LOAD_VAR, i);
|
||||
}
|
||||
public static Instruction loadGlob() {
|
||||
return new Instruction(null, Type.LOAD_GLOB);
|
||||
public static Instruction loadGlob(Location loc) {
|
||||
return new Instruction(loc, Type.LOAD_GLOB);
|
||||
}
|
||||
public static Instruction loadMember() {
|
||||
return new Instruction(null, Type.LOAD_MEMBER);
|
||||
public static Instruction loadMember(Location loc) {
|
||||
return new Instruction(loc, Type.LOAD_MEMBER);
|
||||
}
|
||||
public static Instruction loadMember(Object key) {
|
||||
public static Instruction loadMember(Location loc, Object key) {
|
||||
if (key instanceof Number) key = ((Number)key).doubleValue();
|
||||
return new Instruction(null, Type.LOAD_VAL_MEMBER, key);
|
||||
return new Instruction(loc, Type.LOAD_VAL_MEMBER, key);
|
||||
}
|
||||
|
||||
public static Instruction loadRegex(String pattern, String flags) {
|
||||
return new Instruction(null, Type.LOAD_REGEX, pattern, flags);
|
||||
public static Instruction loadRegex(Location loc, String pattern, String flags) {
|
||||
return new Instruction(loc, Type.LOAD_REGEX, pattern, flags);
|
||||
}
|
||||
public static Instruction loadFunc(long id, int varN, int len, int[] captures) {
|
||||
var args = new Object[3 + captures.length];
|
||||
public static Instruction loadFunc(Location loc, long id, int[] captures) {
|
||||
var args = new Object[1 + captures.length];
|
||||
args[0] = id;
|
||||
args[1] = varN;
|
||||
args[2] = len;
|
||||
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
|
||||
return new Instruction(null, Type.LOAD_FUNC, args);
|
||||
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
|
||||
return new Instruction(loc, Type.LOAD_FUNC, args);
|
||||
}
|
||||
public static Instruction loadObj() {
|
||||
return new Instruction(null, Type.LOAD_OBJ);
|
||||
public static Instruction loadObj(Location loc) {
|
||||
return new Instruction(loc, Type.LOAD_OBJ);
|
||||
}
|
||||
public static Instruction loadArr(int count) {
|
||||
return new Instruction(null, Type.LOAD_ARR, count);
|
||||
public static Instruction loadArr(Location loc, int count) {
|
||||
return new Instruction(loc, Type.LOAD_ARR, count);
|
||||
}
|
||||
public static Instruction dup() {
|
||||
return new Instruction(null, Type.DUP, 0, 1);
|
||||
public static Instruction dup(Location loc) {
|
||||
return new Instruction(loc, Type.DUP, 1);
|
||||
}
|
||||
public static Instruction dup(int count, int offset) {
|
||||
return new Instruction(null, Type.DUP, offset, count);
|
||||
}
|
||||
public static Instruction move(int count, int offset) {
|
||||
return new Instruction(null, Type.MOVE, offset, count);
|
||||
public static Instruction dup(Location loc, int count) {
|
||||
return new Instruction(loc, Type.DUP, count);
|
||||
}
|
||||
|
||||
public static Instruction storeSelfFunc(int i) {
|
||||
return new Instruction(null, Type.STORE_SELF_FUNC, i);
|
||||
public static Instruction storeSelfFunc(Location loc, int i) {
|
||||
return new Instruction(loc, Type.STORE_SELF_FUNC, i);
|
||||
}
|
||||
public static Instruction storeVar(Object i) {
|
||||
return new Instruction(null, Type.STORE_VAR, i, false);
|
||||
public static Instruction storeVar(Location loc, Object i) {
|
||||
return new Instruction(loc, Type.STORE_VAR, i, false);
|
||||
}
|
||||
public static Instruction storeVar(Object i, boolean keep) {
|
||||
return new Instruction(null, Type.STORE_VAR, i, keep);
|
||||
public static Instruction storeVar(Location loc, Object i, boolean keep) {
|
||||
return new Instruction(loc, Type.STORE_VAR, i, keep);
|
||||
}
|
||||
public static Instruction storeMember() {
|
||||
return new Instruction(null, Type.STORE_MEMBER, false);
|
||||
public static Instruction storeMember(Location loc) {
|
||||
return new Instruction(loc, Type.STORE_MEMBER, false);
|
||||
}
|
||||
public static Instruction storeMember(boolean keep) {
|
||||
return new Instruction(null, Type.STORE_MEMBER, keep);
|
||||
public static Instruction storeMember(Location loc, boolean keep) {
|
||||
return new Instruction(loc, Type.STORE_MEMBER, keep);
|
||||
}
|
||||
public static Instruction discard() {
|
||||
return new Instruction(null, Type.DISCARD);
|
||||
public static Instruction discard(Location loc) {
|
||||
return new Instruction(loc, Type.DISCARD);
|
||||
}
|
||||
|
||||
public static Instruction typeof() {
|
||||
return new Instruction(null, Type.TYPEOF);
|
||||
public static Instruction typeof(Location loc) {
|
||||
return new Instruction(loc, Type.TYPEOF);
|
||||
}
|
||||
public static Instruction typeof(Object varName) {
|
||||
return new Instruction(null, Type.TYPEOF, varName);
|
||||
public static Instruction typeof(Location loc, Object varName) {
|
||||
return new Instruction(loc, Type.TYPEOF, varName);
|
||||
}
|
||||
|
||||
public static Instruction keys(boolean forInFormat) {
|
||||
return new Instruction(null, Type.KEYS, forInFormat);
|
||||
public static Instruction keys(Location loc, boolean forInFormat) {
|
||||
return new Instruction(loc, Type.KEYS, forInFormat);
|
||||
}
|
||||
|
||||
public static Instruction defProp() {
|
||||
return new Instruction(null, Type.DEF_PROP);
|
||||
public static Instruction defProp(Location loc) {
|
||||
return new Instruction(loc, Type.DEF_PROP);
|
||||
}
|
||||
|
||||
public static Instruction operation(Operation op) {
|
||||
return new Instruction(null, Type.OPERATION, op);
|
||||
public static Instruction operation(Location loc, Operation op) {
|
||||
return new Instruction(loc, Type.OPERATION, op);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class Statement {
|
||||
private Location _loc;
|
||||
|
||||
public boolean pure() { return false; }
|
||||
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute);
|
||||
public void declare(ScopeRecord varsScope) { }
|
||||
public Statement optimize() { return this; }
|
||||
|
||||
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||
int start = target.size();
|
||||
compile(target, scope, pollute);
|
||||
if (target.size() != start) target.setDebug(start);
|
||||
|
||||
if (target.size() != start) {
|
||||
target.get(start).locate(loc());
|
||||
target.setDebug(start, type);
|
||||
}
|
||||
}
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, pollute, BreakpointType.NONE);
|
||||
}
|
||||
|
||||
public Location loc() { return _loc; }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class ThrowSyntaxStatement extends Statement {
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.throwSyntax(loc(), name));
|
||||
}
|
||||
|
||||
public ThrowSyntaxStatement(SyntaxException e) {
|
||||
super(e.loc);
|
||||
this.name = e.msg;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
@@ -32,23 +33,16 @@ public class VariableDeclareStatement extends Statement {
|
||||
for (var entry : values) {
|
||||
if (entry.name == null) continue;
|
||||
var key = scope.getKey(entry.name);
|
||||
int start = target.size();
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location));
|
||||
if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
|
||||
|
||||
if (entry.value instanceof FunctionStatement) {
|
||||
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
|
||||
target.add(Instruction.storeVar(key).locate(entry.location));
|
||||
if (entry.value != null) {
|
||||
FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.storeVar(entry.location, key));
|
||||
}
|
||||
else if (entry.value != null) {
|
||||
entry.value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(key).locate(entry.location));
|
||||
}
|
||||
|
||||
if (target.size() != start) target.setDebug(start);
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||
|
||||
@@ -11,8 +11,8 @@ public class BreakStatement extends Statement {
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop("break", label).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
target.add(Instruction.nop(loc(), "break", label));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public BreakStatement(Location loc, String label) {
|
||||
|
||||
@@ -11,8 +11,8 @@ public class ContinueStatement extends Statement {
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop("cont", label).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
target.add(Instruction.nop(loc(), "cont", label));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public ContinueStatement(Location loc, String label) {
|
||||
|
||||
@@ -9,8 +9,8 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class DebugStatement extends Statement {
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.debug().locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
target.add(Instruction.debug(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public DebugStatement(Location loc) {
|
||||
|
||||
@@ -15,8 +15,8 @@ public class DeleteStatement extends Statement {
|
||||
value.compile(target, scope, true);
|
||||
key.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.delete().locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
target.add(Instruction.delete(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), true));
|
||||
}
|
||||
|
||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||
|
||||
@@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class DoWhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
@@ -20,54 +18,14 @@ public class DoWhileStatement extends Statement {
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
int start = target.size();
|
||||
body.compile(target, scope, false);
|
||||
int end = target.size();
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
|
||||
}
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
return;
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
body.compileWithDebug(target, scope, false);
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
int mid = target.size();
|
||||
condition.compile(target, scope, true);
|
||||
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
target.add(Instruction.jmpIf(start - end).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
|
||||
if (b instanceof CompoundStatement) {
|
||||
var comp = (CompoundStatement)b;
|
||||
if (comp.statements.length > 0) {
|
||||
var last = comp.statements[comp.statements.length - 1];
|
||||
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
|
||||
if (last instanceof BreakStatement) {
|
||||
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
|
||||
return new CompoundStatement(loc());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (b instanceof ContinueStatement) {
|
||||
b = new CompoundStatement(loc());
|
||||
}
|
||||
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
|
||||
|
||||
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
|
||||
else return new DoWhileStatement(loc(), label, cond, b);
|
||||
target.add(Instruction.jmpIf(loc(), start - end));
|
||||
}
|
||||
|
||||
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
@@ -25,39 +26,38 @@ public class ForInStatement extends Statement {
|
||||
var key = scope.getKey(varName);
|
||||
|
||||
int first = target.size();
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
varValue.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
||||
target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compileWithDebug(target, scope, true);
|
||||
target.add(Instruction.keys(true));
|
||||
object.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.keys(loc(), true));
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadValue(null));
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), null));
|
||||
target.add(Instruction.operation(loc(), Operation.EQUALS));
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.nop(loc()));
|
||||
|
||||
target.add(Instruction.loadMember("value").locate(varLocation));
|
||||
target.setDebug();
|
||||
target.add(Instruction.storeVar(key));
|
||||
target.add(Instruction.loadMember(varLocation, "value"));
|
||||
target.add(Instruction.storeVar(object.loc(), key));
|
||||
target.setDebug(BreakpointType.STEP_OVER);
|
||||
|
||||
body.compileWithDebug(target, scope, false);
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end));
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(null));
|
||||
target.add(Instruction.jmp(loc(), start - end));
|
||||
target.add(Instruction.discard(loc()));
|
||||
target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
target.get(first).locate(loc());
|
||||
target.setDebug(first);
|
||||
}
|
||||
|
||||
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
|
||||
@@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class ForStatement extends Statement {
|
||||
public final Statement declaration, assignment, condition, body;
|
||||
@@ -20,58 +18,22 @@ public class ForStatement extends Statement {
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
declaration.compile(target, scope, false);
|
||||
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compile(target, scope, false);
|
||||
int mid = target.size();
|
||||
assignment.compileWithDebug(target, scope, false);
|
||||
int end = target.size();
|
||||
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int start = target.size();
|
||||
condition.compile(target, scope, true);
|
||||
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compile(target, scope, false);
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
int beforeAssign = target.size();
|
||||
assignment.compileWithDebug(target, scope, false);
|
||||
assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var decl = declaration.optimize();
|
||||
var asgn = assignment.optimize();
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
|
||||
if (asgn.pure()) {
|
||||
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
|
||||
else return new CompoundStatement(loc(),
|
||||
decl, new WhileStatement(loc(), label, cond, b)
|
||||
).optimize();
|
||||
}
|
||||
|
||||
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
|
||||
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
|
||||
);
|
||||
else if (b instanceof BreakStatement) return decl;
|
||||
|
||||
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
|
||||
else return new ForStatement(loc(), label, decl, cond, asgn, b);
|
||||
target.add(Instruction.jmp(loc(), start - end));
|
||||
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
|
||||
@@ -82,14 +44,4 @@ public class ForStatement extends Statement {
|
||||
this.assignment = assignment;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
|
||||
return new CompoundStatement(loc,
|
||||
declaration,
|
||||
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
|
||||
body,
|
||||
increment
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class IfStatement extends Statement {
|
||||
public final Statement condition, body, elseBody;
|
||||
@@ -19,53 +16,31 @@ public class IfStatement extends Statement {
|
||||
if (elseBody != null) elseBody.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)condition).value)) {
|
||||
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
else {
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compile(target, scope, true);
|
||||
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
|
||||
condition.compile(target, scope, true, breakpoint);
|
||||
|
||||
if (elseBody == null) {
|
||||
int i = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, pollute, breakpoint);
|
||||
int endI = target.size();
|
||||
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
|
||||
target.set(i, Instruction.jmpIfNot(loc(), endI - i));
|
||||
}
|
||||
else {
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, pollute, breakpoint);
|
||||
target.add(Instruction.nop(null));
|
||||
int mid = target.size();
|
||||
elseBody.compileWithDebug(target, scope, pollute);
|
||||
elseBody.compile(target, scope, pollute, breakpoint);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
|
||||
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc()));
|
||||
target.set(start, Instruction.jmpIfNot(loc(), mid - start));
|
||||
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
var e = elseBody == null ? null : elseBody.optimize();
|
||||
|
||||
if (b.pure()) b = new CompoundStatement(null);
|
||||
if (e != null && e.pure()) e = null;
|
||||
|
||||
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
|
||||
else return new IfStatement(loc(), cond, b, e);
|
||||
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, pollute, BreakpointType.STEP_IN);
|
||||
}
|
||||
|
||||
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
|
||||
|
||||
@@ -11,9 +11,9 @@ public class ReturnStatement extends Statement {
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
if (value == null) target.add(Instruction.loadValue(loc(), null));
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.ret().locate(loc()));
|
||||
target.add(Instruction.ret(loc()));
|
||||
}
|
||||
|
||||
public ReturnStatement(Location loc, Statement value) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -33,46 +34,47 @@ public class SwitchStatement extends Statement {
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var caseMap = new HashMap<Integer, Integer>();
|
||||
var stmIndexMap = new HashMap<Integer, Integer>();
|
||||
var caseToStatement = new HashMap<Integer, Integer>();
|
||||
var statementToIndex = new HashMap<Integer, Integer>();
|
||||
|
||||
value.compile(target, scope, true);
|
||||
value.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
|
||||
for (var ccase : cases) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.dup(loc()));
|
||||
ccase.value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
|
||||
caseMap.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop().locate(ccase.value.loc()));
|
||||
target.add(Instruction.operation(loc(), Operation.EQUALS));
|
||||
caseToStatement.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop(null));
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.nop(null));
|
||||
|
||||
for (var stm : body) {
|
||||
stmIndexMap.put(stmIndexMap.size(), target.size());
|
||||
stm.compileWithDebug(target, scope, false);
|
||||
statementToIndex.put(statementToIndex.size(), target.size());
|
||||
stm.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
}
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
|
||||
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc());
|
||||
int end = target.size();
|
||||
target.add(Instruction.discard(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
|
||||
else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start));
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
|
||||
target.set(i, Instruction.jmp(target.size() - i).locate(instr.location));
|
||||
target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location));
|
||||
}
|
||||
}
|
||||
for (var el : caseMap.entrySet()) {
|
||||
var loc = target.get(el.getKey()).location;
|
||||
var i = stmIndexMap.get(el.getValue());
|
||||
if (i == null) i = target.size();
|
||||
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc));
|
||||
target.setDebug(el.getKey());
|
||||
for (var el : caseToStatement.entrySet()) {
|
||||
var i = statementToIndex.get(el.getValue());
|
||||
if (i == null) i = end;
|
||||
target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
|
||||
}
|
||||
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
|
||||
|
||||
@@ -12,7 +12,7 @@ public class ThrowStatement extends Statement {
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.throwInstr().locate(loc()));
|
||||
target.add(Instruction.throwInstr(loc()));
|
||||
}
|
||||
|
||||
public ThrowStatement(Location loc, Statement value) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -22,31 +23,32 @@ public class TryStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop());
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
|
||||
target.add(Instruction.nop(null));
|
||||
|
||||
int start = target.size(), tryN, catchN = -1, finN = -1;
|
||||
int start = target.size(), catchStart = -1, finallyStart = -1;
|
||||
|
||||
tryBody.compile(target, scope, false);
|
||||
tryN = target.size() - start;
|
||||
target.add(Instruction.tryEnd(loc()));
|
||||
|
||||
if (catchBody != null) {
|
||||
int tmp = target.size();
|
||||
catchStart = target.size() - start;
|
||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||
local.define(name, true);
|
||||
catchBody.compile(target, scope, false);
|
||||
local.undefine();
|
||||
catchN = target.size() - tmp;
|
||||
target.add(Instruction.tryEnd(loc()));
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
int tmp = target.size();
|
||||
finallyStart = target.size() - start;
|
||||
finallyBody.compile(target, scope, false);
|
||||
finN = target.size() - tmp;
|
||||
target.add(Instruction.tryEnd(loc()));
|
||||
}
|
||||
|
||||
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
|
||||
target.setDebug(start - 1, BreakpointType.STEP_OVER);
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
|
||||
@@ -3,13 +3,10 @@ package me.topchetoeu.jscript.compilation.control;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class WhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
@@ -21,43 +18,19 @@ public class WhileStatement extends Statement {
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compile(target, scope, false);
|
||||
int end = target.size();
|
||||
replaceBreaks(target, label, start, end, start, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compile(target, scope, true);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compile(target, scope, false);
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
|
||||
if (b instanceof ContinueStatement) {
|
||||
b = new CompoundStatement(loc());
|
||||
}
|
||||
else if (b instanceof BreakStatement) return new DiscardStatement(loc(), cond).optimize();
|
||||
|
||||
if (b.pure()) return new WhileStatement(loc(), label, cond, new CompoundStatement(null));
|
||||
else return new WhileStatement(loc(), label, cond, b);
|
||||
target.add(Instruction.jmp(loc(), start - end));
|
||||
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||
@@ -71,23 +44,11 @@ public class WhileStatement extends Statement {
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.jmp(continuePoint - i));
|
||||
target.get(i).location = instr.location;
|
||||
target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
|
||||
}
|
||||
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.jmp(breakPoint - i));
|
||||
target.get(i).location = instr.location;
|
||||
target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
|
||||
// return new CompoundStatement(loc,
|
||||
// declaration,
|
||||
// new WhileStatement(loc, label, condition, new CompoundStatement(loc,
|
||||
// body,
|
||||
// increment
|
||||
// ))
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
@@ -9,23 +9,29 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
@Override public boolean pure() {
|
||||
for (var stm : statements) {
|
||||
if (!stm.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadArr(statements.length).locate(loc()));
|
||||
var i = 0;
|
||||
for (var el : statements) {
|
||||
target.add(Instruction.loadArr(loc(), statements.length));
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var el = statements[i];
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue(i).locate(loc()));
|
||||
target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), i));
|
||||
el.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
target.add(Instruction.storeMember(loc()));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
@@ -4,36 +4,47 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CallStatement extends Statement {
|
||||
public final Statement func;
|
||||
public final Statement[] args;
|
||||
public final boolean isNew;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (func instanceof IndexStatement) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||
if (isNew) func.compile(target, scope, true);
|
||||
else if (func instanceof IndexStatement) {
|
||||
((IndexStatement)func).compile(target, scope, true, true);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
target.add(Instruction.loadValue(loc(), null));
|
||||
func.compile(target, scope, true);
|
||||
}
|
||||
|
||||
for (var arg : args) arg.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.call(args.length).locate(loc()));
|
||||
target.setDebug();
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
if (isNew) target.add(Instruction.callNew(loc(), args.length));
|
||||
else target.add(Instruction.call(loc(), args.length));
|
||||
target.setDebug(type);
|
||||
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, pollute, BreakpointType.STEP_IN);
|
||||
}
|
||||
|
||||
public CallStatement(Location loc, Statement func, Statement ...args) {
|
||||
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
|
||||
super(loc);
|
||||
this.isNew = isNew;
|
||||
this.func = func;
|
||||
this.args = args;
|
||||
}
|
||||
public CallStatement(Location loc, Statement obj, Object key, Statement ...args) {
|
||||
public CallStatement(Location loc, boolean isNew, Statement obj, Object key, Statement ...args) {
|
||||
super(loc);
|
||||
this.isNew = isNew;
|
||||
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ public class ChangeStatement extends Statement {
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
else if (postfix) {
|
||||
target.add(Instruction.loadValue(addAmount));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
target.add(Instruction.loadValue(loc(), addAmount));
|
||||
target.add(Instruction.operation(loc(), Operation.SUBTRACT));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CommaStatement extends Statement {
|
||||
public final Statement[] values;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
values[i].compile(target, scope, i == values.length - 1 && pollute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var res = new Vector<Statement>(values.length);
|
||||
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var stm = values[i].optimize();
|
||||
if (i < values.length - 1 && stm.pure()) continue;
|
||||
res.add(stm);
|
||||
}
|
||||
|
||||
if (res.size() == 1) return res.get(0);
|
||||
else return new CommaStatement(loc(), res.toArray(Statement[]::new));
|
||||
}
|
||||
|
||||
public CommaStatement(Location loc, Statement ...args) {
|
||||
super(loc);
|
||||
this.values = args;
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class ConstantStatement extends Statement {
|
||||
public final Object value;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadValue(value).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), value));
|
||||
}
|
||||
|
||||
public ConstantStatement(Location loc, Object val) {
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class DiscardStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override public boolean pure() { return value.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, false);
|
||||
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
if (value == null) return this;
|
||||
var val = value.optimize();
|
||||
if (val.pure()) return new ConstantStatement(loc(), null);
|
||||
else return new DiscardStatement(loc(), val);
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public DiscardStatement(Location loc, Statement val) {
|
||||
@@ -8,23 +8,25 @@ import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class FunctionStatement extends Statement {
|
||||
public final CompoundStatement body;
|
||||
public final String name;
|
||||
public final String varName;
|
||||
public final String[] args;
|
||||
public final boolean statement;
|
||||
public final Location end;
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
@Override
|
||||
public boolean pure() { return name == null; }
|
||||
@Override public boolean pure() { return varName == null && statement; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord scope) {
|
||||
if (name != null) scope.define(name);
|
||||
if (varName != null && statement) scope.define(varName);
|
||||
}
|
||||
|
||||
public static void checkBreakAndCont(CompileTarget target, int start) {
|
||||
@@ -40,73 +42,98 @@ public class FunctionStatement extends Statement {
|
||||
}
|
||||
}
|
||||
|
||||
public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) {
|
||||
private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var j = 0; j < i; j++) {
|
||||
if (args[i].equals(args[j])) {
|
||||
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.")));
|
||||
return;
|
||||
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
var subscope = scope.child();
|
||||
|
||||
int start = target.size();
|
||||
var funcTarget = new CompileTarget(target.functions, target.breakpoints);
|
||||
var id = rand.nextLong();
|
||||
var subscope = scope.child();
|
||||
var subtarget = new CompileTarget(target.functions, target.breakpoints);
|
||||
|
||||
subscope.define("this");
|
||||
var argsVar = subscope.define("arguments");
|
||||
|
||||
if (args.length > 0) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
funcTarget.add(Instruction.loadVar(argsVar).locate(loc()));
|
||||
funcTarget.add(Instruction.loadMember(i).locate(loc()));
|
||||
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
||||
subtarget.add(Instruction.loadVar(loc(), argsVar));
|
||||
subtarget.add(Instruction.loadMember(loc(), i));
|
||||
subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStatement && this.name != null) {
|
||||
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
||||
if (!statement && this.varName != null) {
|
||||
subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
|
||||
subtarget.setDebug(bp);
|
||||
}
|
||||
|
||||
body.declare(subscope);
|
||||
body.compile(funcTarget, subscope, false);
|
||||
funcTarget.add(Instruction.ret().locate(loc()));
|
||||
checkBreakAndCont(funcTarget, start);
|
||||
body.compile(subtarget, subscope, false);
|
||||
subtarget.add(Instruction.ret(end));
|
||||
checkBreakAndCont(subtarget, 0);
|
||||
|
||||
var id = rand.nextLong();
|
||||
if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures()));
|
||||
target.functions.put(id, new FunctionBody(
|
||||
subscope.localsCount(), args.length,
|
||||
subtarget.array(), subscope.captures(), subscope.locals()
|
||||
));
|
||||
|
||||
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
||||
target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
|
||||
|
||||
if (name == null) name = this.name;
|
||||
|
||||
if (name != null) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue("name").locate(loc()));
|
||||
target.add(Instruction.loadValue(name).locate(loc()));
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
return id;
|
||||
}
|
||||
|
||||
if (this.name != null && isStatement) {
|
||||
var key = scope.getKey(this.name);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
|
||||
if (this.varName != null) name = this.varName;
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
|
||||
var hasVar = this.varName != null && statement;
|
||||
var hasName = name != null;
|
||||
|
||||
compileBody(target, scope, pollute || hasVar || hasName, bp);
|
||||
|
||||
if (hasName) {
|
||||
if (pollute || hasVar) target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), "name"));
|
||||
target.add(Instruction.loadValue(loc(), name));
|
||||
target.add(Instruction.storeMember(loc()));
|
||||
}
|
||||
|
||||
if (hasVar) {
|
||||
var key = scope.getKey(this.varName);
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
|
||||
target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false));
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, null, false);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
|
||||
compile(target, scope, pollute, name, BreakpointType.NONE);
|
||||
}
|
||||
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) {
|
||||
compile(target, scope, pollute, (String)null, bp);
|
||||
}
|
||||
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, pollute, (String)null, BreakpointType.NONE);
|
||||
}
|
||||
|
||||
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
|
||||
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
|
||||
super(loc);
|
||||
this.name = name;
|
||||
|
||||
this.end = end;
|
||||
this.varName = varName;
|
||||
this.statement = statement;
|
||||
|
||||
this.args = args;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
|
||||
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
|
||||
else stm.compile(target, scope, pollute);
|
||||
}
|
||||
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
|
||||
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
|
||||
else stm.compile(target, scope, pollute, bp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class GlobalThisStatement extends Statement {
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadGlob().locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadGlob(loc()));
|
||||
}
|
||||
|
||||
public GlobalThisStatement(Location loc) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
@@ -18,22 +19,22 @@ public class IndexAssignStatement extends Statement {
|
||||
if (operation != null) {
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.dup(2, 0).locate(loc()));
|
||||
target.add(Instruction.dup(loc(), 2));
|
||||
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
target.add(Instruction.loadMember(loc()));
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
target.add(Instruction.operation(loc(), operation));
|
||||
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()));
|
||||
target.setDebug();
|
||||
target.add(Instruction.storeMember(loc(), pollute));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
}
|
||||
else {
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()));
|
||||
target.setDebug();
|
||||
target.add(Instruction.storeMember(loc(), pollute));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
@@ -12,26 +13,23 @@ public class IndexStatement extends AssignableStatement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||
}
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||
object.compile(target, scope, true);
|
||||
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
||||
if (dupObj) target.add(Instruction.dup(loc()));
|
||||
if (index instanceof ConstantStatement) {
|
||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
||||
target.setDebug();
|
||||
target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
return;
|
||||
}
|
||||
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
target.setDebug();
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
target.add(Instruction.loadMember(loc()));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
|
||||
@@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class LazyAndStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
@@ -26,12 +23,12 @@ public class LazyAndStatement extends Statement {
|
||||
}
|
||||
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
if (pollute) target.add(Instruction.dup(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
if (pollute) target.add(Instruction.discard().locate(loc()));
|
||||
target.add(Instruction.nop(null));
|
||||
if (pollute) target.add(Instruction.discard(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
||||
target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
|
||||
}
|
||||
|
||||
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
||||
|
||||
@@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class LazyOrStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
@@ -26,12 +23,12 @@ public class LazyOrStatement extends Statement {
|
||||
}
|
||||
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
if (pollute) target.add(Instruction.dup(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
if (pollute) target.add(Instruction.discard().locate(loc()));
|
||||
target.add(Instruction.nop(null));
|
||||
if (pollute) target.add(Instruction.discard(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
||||
target.set(start, Instruction.jmpIf(loc(), target.size() - start));
|
||||
}
|
||||
|
||||
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class NewStatement extends Statement {
|
||||
public final Statement func;
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
func.compile(target, scope, true);
|
||||
|
||||
for (var arg : args) arg.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.callNew(args.length).locate(loc()));
|
||||
target.setDebug();
|
||||
}
|
||||
|
||||
public NewStatement(Location loc, Statement func, Statement ...args) {
|
||||
super(loc);
|
||||
this.func = func;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,24 @@ public class ObjectStatement extends Statement {
|
||||
public final Map<Object, FunctionStatement> getters;
|
||||
public final Map<Object, FunctionStatement> setters;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var el : map.values()) {
|
||||
if (!el.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadObj().locate(loc()));
|
||||
target.add(Instruction.loadObj(loc()));
|
||||
|
||||
for (var el : map.entrySet()) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
|
||||
target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), el.getKey()));
|
||||
var val = el.getValue();
|
||||
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
|
||||
else val.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
|
||||
target.add(Instruction.storeMember(loc()));
|
||||
}
|
||||
|
||||
var keys = new ArrayList<Object>();
|
||||
@@ -32,19 +39,19 @@ public class ObjectStatement extends Statement {
|
||||
keys.addAll(setters.keySet());
|
||||
|
||||
for (var key : keys) {
|
||||
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
|
||||
else target.add(Instruction.loadValue((Double)key).locate(loc()));
|
||||
if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
|
||||
else target.add(Instruction.loadValue(loc(), (Double)key));
|
||||
|
||||
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
else target.add(Instruction.loadValue(loc(), null));
|
||||
|
||||
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
else target.add(Instruction.loadValue(loc(), null));
|
||||
|
||||
target.add(Instruction.defProp().locate(loc()));
|
||||
target.add(Instruction.defProp(loc()));
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
||||
|
||||
@@ -4,57 +4,29 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class OperationStatement extends Statement {
|
||||
public final Statement[] args;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var el : args) {
|
||||
if (!el.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var arg : args) {
|
||||
arg.compile(target, scope, true);
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.operation(operation).locate(loc()));
|
||||
else target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pure() {
|
||||
for (var arg : args) {
|
||||
if (!arg.pure()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var args = new Statement[this.args.length];
|
||||
var allConst = true;
|
||||
|
||||
for (var i = 0; i < this.args.length; i++) {
|
||||
args[i] = this.args[i].optimize();
|
||||
if (!(args[i] instanceof ConstantStatement)) allConst = false;
|
||||
}
|
||||
|
||||
if (allConst) {
|
||||
var vals = new Object[this.args.length];
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
vals[i] = ((ConstantStatement)args[i]).value;
|
||||
}
|
||||
|
||||
try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
|
||||
catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
|
||||
}
|
||||
|
||||
return new OperationStatement(loc(), operation, args);
|
||||
|
||||
if (pollute) target.add(Instruction.operation(loc(), operation));
|
||||
else target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public OperationStatement(Location loc, Operation operation, Statement ...args) {
|
||||
|
||||
@@ -9,13 +9,13 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class RegexStatement extends Statement {
|
||||
public final String pattern, flags;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
// Not really pure, since a function is called, but can be ignored.
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadRegex(pattern, flags).locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
target.add(Instruction.loadRegex(loc(), pattern, flags));
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public RegexStatement(Location loc, String pattern, String flags) {
|
||||
|
||||
@@ -4,44 +4,26 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.control.ArrayStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class TypeofStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
// Not really pure, since a variable from the global scope could be accessed,
|
||||
// which could lead to code execution, that would get omitted
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value instanceof VariableStatement) {
|
||||
var i = scope.getKey(((VariableStatement)value).name);
|
||||
if (i instanceof String) {
|
||||
target.add(Instruction.typeof((String)i).locate(loc()));
|
||||
target.add(Instruction.typeof(loc(), (String)i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
value.compile(target, scope, pollute);
|
||||
target.add(Instruction.typeof().locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var val = value.optimize();
|
||||
|
||||
if (val instanceof ConstantStatement) {
|
||||
return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value));
|
||||
}
|
||||
else if (
|
||||
val instanceof ObjectStatement ||
|
||||
val instanceof ArrayStatement ||
|
||||
val instanceof GlobalThisStatement
|
||||
) return new ConstantStatement(loc(), "object");
|
||||
else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function");
|
||||
|
||||
return new TypeofStatement(loc(), val);
|
||||
target.add(Instruction.typeof(loc()));
|
||||
}
|
||||
|
||||
public TypeofStatement(Location loc, Statement value) {
|
||||
|
||||
@@ -12,20 +12,20 @@ public class VariableAssignStatement extends Statement {
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public boolean pure() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
if (operation != null) {
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, pollute).locate(loc()));
|
||||
target.add(Instruction.loadVar(loc(), i));
|
||||
FunctionStatement.compileWithName(value, target, scope, true, name);
|
||||
target.add(Instruction.operation(loc(), operation));
|
||||
target.add(Instruction.storeVar(loc(), i, pollute));
|
||||
}
|
||||
else {
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(i, pollute).locate(loc()));
|
||||
FunctionStatement.compileWithName(value, target, scope, true, name);
|
||||
target.add(Instruction.storeVar(loc(), i, pollute));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class VariableIndexStatement extends Statement {
|
||||
public final int index;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadVar(index).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadVar(loc(), index));
|
||||
}
|
||||
|
||||
public VariableIndexStatement(Location loc, int i) {
|
||||
|
||||
@@ -11,8 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class VariableStatement extends AssignableStatement {
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
@Override public boolean pure() { return false; }
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
@@ -22,8 +21,8 @@ public class VariableStatement extends AssignableStatement {
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
target.add(Instruction.loadVar(loc(), i));
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public VariableStatement(Location loc, String name) {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
|
||||
public class VoidStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value != null) value.compile(target, scope, false);
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
if (value == null) return this;
|
||||
var val = value.optimize();
|
||||
if (val.pure()) return new ConstantStatement(loc(), null);
|
||||
else return new VoidStatement(loc(), val);
|
||||
}
|
||||
|
||||
public VoidStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,124 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
|
||||
public class Context {
|
||||
private final Stack<Environment> env = new Stack<>();
|
||||
public final Data data;
|
||||
private final ArrayList<CodeFrame> frames = new ArrayList<>();
|
||||
public final Engine engine;
|
||||
|
||||
public Environment environment() {
|
||||
return env.empty() ? null : env.peek();
|
||||
}
|
||||
|
||||
public Context pushEnv(Environment env) {
|
||||
private Context pushEnv(Environment env) {
|
||||
this.env.push(env);
|
||||
return this;
|
||||
}
|
||||
public void popEnv() {
|
||||
private void popEnv() {
|
||||
if (!env.empty()) this.env.pop();
|
||||
}
|
||||
|
||||
public FunctionValue compile(Filename filename, String raw) {
|
||||
var transpiled = environment().compile.call(this, null, raw, filename.toString());
|
||||
String source = null;
|
||||
FunctionValue runner = null;
|
||||
var env = environment();
|
||||
var result = env.compile.call(this, null, raw, filename.toString(), env);
|
||||
|
||||
if (transpiled instanceof ObjectValue) {
|
||||
source = Values.toString(this, Values.getMember(this, transpiled, "source"));
|
||||
var _runner = Values.getMember(this, transpiled, "runner");
|
||||
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner;
|
||||
var function = (FunctionValue)Values.getMember(this, result, "function");
|
||||
if (!engine.debugging) return function;
|
||||
|
||||
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
|
||||
var breakpoints = new TreeSet<>(
|
||||
Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
|
||||
.map(v -> Location.parse(Values.toString(this, v)))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
var maps = new SourceMap[rawMapChain.length];
|
||||
|
||||
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
|
||||
|
||||
var map = SourceMap.chain(maps);
|
||||
|
||||
if (map != null) {
|
||||
var newBreakpoints = new TreeSet<Location>();
|
||||
for (var bp : breakpoints) {
|
||||
bp = map.toCompiled(bp);
|
||||
if (bp != null) newBreakpoints.add(bp);
|
||||
}
|
||||
breakpoints = newBreakpoints;
|
||||
}
|
||||
else source = Values.toString(this, transpiled);
|
||||
|
||||
var breakpoints = new TreeSet<Location>();
|
||||
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
|
||||
engine.onSource(filename, source, breakpoints);
|
||||
engine.onSource(filename, raw, breakpoints, map);
|
||||
|
||||
if (runner != null) res = (FunctionValue)runner.call(this, null, res);
|
||||
return function;
|
||||
}
|
||||
|
||||
|
||||
public void pushFrame(CodeFrame frame) {
|
||||
frames.add(frame);
|
||||
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
|
||||
pushEnv(frame.function.environment);
|
||||
engine.onFramePush(this, frame);
|
||||
}
|
||||
public boolean popFrame(CodeFrame frame) {
|
||||
if (frames.size() == 0) return false;
|
||||
if (frames.get(frames.size() - 1) != frame) return false;
|
||||
frames.remove(frames.size() - 1);
|
||||
popEnv();
|
||||
engine.onFramePop(this, frame);
|
||||
return true;
|
||||
}
|
||||
public CodeFrame peekFrame() {
|
||||
if (frames.size() == 0) return null;
|
||||
return frames.get(frames.size() - 1);
|
||||
}
|
||||
|
||||
public List<CodeFrame> frames() {
|
||||
return Collections.unmodifiableList(frames);
|
||||
}
|
||||
public List<String> stackTrace() {
|
||||
var res = new ArrayList<String>();
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
var el = frames.get(i);
|
||||
var name = el.function.name;
|
||||
Location loc = null;
|
||||
|
||||
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
|
||||
if (loc == null) loc = el.function.loc();
|
||||
|
||||
var trace = "";
|
||||
|
||||
if (loc != null) trace += "at " + loc.toString() + " ";
|
||||
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
||||
|
||||
trace = trace.trim();
|
||||
|
||||
if (!trace.equals("")) res.add(trace);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context(Engine engine, Data data) {
|
||||
this.data = new Data(engine.data);
|
||||
if (data != null) this.data.addAll(data);
|
||||
public Context(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
public Context(Engine engine) {
|
||||
this(engine, null);
|
||||
public Context(Engine engine, Environment env) {
|
||||
this(engine);
|
||||
if (env != null) this.pushEnv(env);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Data {
|
||||
public final Data parent;
|
||||
private HashMap<DataKey<Object>, Object> data = new HashMap<>();
|
||||
|
||||
public Data copy() {
|
||||
@@ -33,19 +32,12 @@ public class Data {
|
||||
return this;
|
||||
}
|
||||
public <T> T get(DataKey<T> key, T val) {
|
||||
for (var it = this; it != null; it = it.parent) {
|
||||
if (it.data.containsKey(key)) {
|
||||
return (T)it.data.get((DataKey<Object>)key);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
|
||||
set(key, val);
|
||||
return val;
|
||||
}
|
||||
public <T> T get(DataKey<T> key) {
|
||||
for (var it = this; it != null; it = it.parent) {
|
||||
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
|
||||
}
|
||||
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
|
||||
return null;
|
||||
}
|
||||
public boolean has(DataKey<?> key) { return data.containsKey(key); }
|
||||
@@ -61,11 +53,4 @@ public class Data {
|
||||
public int increase(DataKey<Integer> key) {
|
||||
return increase(key, 1, 0);
|
||||
}
|
||||
|
||||
public Data() {
|
||||
this.parent = null;
|
||||
}
|
||||
public Data(Data parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
@@ -15,6 +15,7 @@ import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
|
||||
public class Engine implements DebugController {
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
@@ -35,65 +36,72 @@ public class Engine implements DebugController {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task {
|
||||
private static class Task implements Comparable<Task> {
|
||||
public final FunctionValue func;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final Context ctx;
|
||||
public final boolean micro;
|
||||
|
||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) {
|
||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
|
||||
this.ctx = ctx;
|
||||
this.func = func;
|
||||
this.thisArg = thisArg;
|
||||
this.args = args;
|
||||
this.micro = micro;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Task other) {
|
||||
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static int nextId = 0;
|
||||
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||
|
||||
private Thread thread;
|
||||
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
|
||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
||||
|
||||
public final int id = ++nextId;
|
||||
public final Data data = new Data().set(StackData.MAX_FRAMES, 200);
|
||||
public final boolean debugging;
|
||||
public int maxStackFrames = 10000;
|
||||
|
||||
private final HashMap<Filename, String> sources = new HashMap<>();
|
||||
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
|
||||
private DebugController debugger;
|
||||
private final HashMap<Filename, SourceMap> maps = new HashMap<>();
|
||||
|
||||
public boolean attachDebugger(DebugController debugger) {
|
||||
public Location mapToCompiled(Location location) {
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toCompiled(location);
|
||||
}
|
||||
public Location mapToOriginal(Location location) {
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toOriginal(location);
|
||||
}
|
||||
|
||||
private DebugController debugger;
|
||||
private Thread thread;
|
||||
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
|
||||
|
||||
public synchronized boolean attachDebugger(DebugController debugger) {
|
||||
if (!debugging || this.debugger != null) return false;
|
||||
|
||||
for (var source : sources.entrySet()) {
|
||||
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey()));
|
||||
}
|
||||
for (var source : sources.entrySet()) debugger.onSource(
|
||||
source.getKey(), source.getValue(),
|
||||
bpts.get(source.getKey()),
|
||||
maps.get(source.getKey())
|
||||
);
|
||||
|
||||
this.debugger = debugger;
|
||||
return true;
|
||||
}
|
||||
public boolean detachDebugger() {
|
||||
public synchronized boolean detachDebugger() {
|
||||
if (!debugging || this.debugger == null) return false;
|
||||
this.debugger = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||
else return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
|
||||
if (!debugging) return;
|
||||
if (debugger != null) debugger.onSource(filename, source, breakpoints);
|
||||
sources.put(filename, source);
|
||||
bpts.put(filename, breakpoints);
|
||||
}
|
||||
|
||||
private void runTask(Task task) {
|
||||
try {
|
||||
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
|
||||
@@ -104,18 +112,12 @@ public class Engine implements DebugController {
|
||||
}
|
||||
}
|
||||
public void run(boolean untilEmpty) {
|
||||
while (!untilEmpty || !macroTasks.isEmpty()) {
|
||||
while (!untilEmpty || !tasks.isEmpty()) {
|
||||
try {
|
||||
runTask(macroTasks.take());
|
||||
|
||||
while (!microTasks.isEmpty()) {
|
||||
runTask(microTasks.take());
|
||||
}
|
||||
runTask(tasks.take());
|
||||
}
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : macroTasks) {
|
||||
msg.notifier.error(new InterruptException(e));
|
||||
}
|
||||
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -135,18 +137,36 @@ public class Engine implements DebugController {
|
||||
public boolean inExecThread() {
|
||||
return Thread.currentThread() == thread;
|
||||
}
|
||||
public boolean isRunning() {
|
||||
public synchronized boolean isRunning() {
|
||||
return this.thread != null;
|
||||
}
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args);
|
||||
if (micro) microTasks.addLast(msg);
|
||||
else macroTasks.addLast(msg);
|
||||
public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(new Context(this, env), func, thisArg, args, micro);
|
||||
tasks.add(msg);
|
||||
return msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
|
||||
public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFramePush(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePush(ctx, frame);
|
||||
}
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||
else return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
|
||||
if (!debugging) return;
|
||||
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
|
||||
sources.put(filename, source);
|
||||
bpts.put(filename, breakpoints);
|
||||
maps.put(filename, map);
|
||||
}
|
||||
|
||||
public Engine(boolean debugging) {
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.filesystem.RootFilesystem;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
|
||||
import me.topchetoeu.jscript.modules.RootModuleRepo;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.permissions.Permission;
|
||||
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||
|
||||
public class Environment {
|
||||
// TODO: Remove hardcoded extensions form environment
|
||||
public class Environment implements PermissionsProvider {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
|
||||
public final Data data = new Data();
|
||||
@@ -22,16 +33,41 @@ public class Environment {
|
||||
public GlobalScope global;
|
||||
public WrappersProvider wrappers;
|
||||
|
||||
@Native public FunctionValue compile;
|
||||
public PermissionsProvider permissions = null;
|
||||
public final RootFilesystem filesystem = new RootFilesystem(this);
|
||||
public final RootModuleRepo modules = new RootModuleRepo();
|
||||
public String moduleCwd = "/";
|
||||
|
||||
private static int nextId = 0;
|
||||
|
||||
@Native public boolean stackVisible = true;
|
||||
@Native public int id = ++nextId;
|
||||
|
||||
@Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
|
||||
var source = Values.toString(ctx, args[0]);
|
||||
var filename = Values.toString(ctx, args[1]);
|
||||
var isDebug = Values.toBoolean(args[2]);
|
||||
|
||||
var env = Values.wrapper(args[2], Environment.class);
|
||||
var res = new ObjectValue();
|
||||
|
||||
var target = Parsing.compile(env, Filename.parse(filename), source);
|
||||
Engine.functions.putAll(target.functions);
|
||||
Engine.functions.remove(0l);
|
||||
|
||||
res.defineProperty(ctx, "function", target.func(env));
|
||||
res.defineProperty(ctx, "mapChain", new ArrayValue());
|
||||
|
||||
if (isDebug) {
|
||||
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
||||
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
|
||||
});
|
||||
|
||||
public Environment addData(Data data) {
|
||||
this.data.addAll(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Native public ObjectValue proto(String name) {
|
||||
return prototypes.get(name);
|
||||
}
|
||||
@@ -40,12 +76,7 @@ public class Environment {
|
||||
}
|
||||
|
||||
@Native public Symbol symbol(String name) {
|
||||
if (symbols.containsKey(name)) return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
return res;
|
||||
}
|
||||
return getSymbol(name);
|
||||
}
|
||||
|
||||
@NativeGetter("global") public ObjectValue getGlobal() {
|
||||
@@ -65,23 +96,41 @@ public class Environment {
|
||||
@Native public Environment child() {
|
||||
var res = fork();
|
||||
res.global = res.global.globalChild();
|
||||
res.permissions = this.permissions;
|
||||
res.filesystem.protocols.putAll(this.filesystem.protocols);
|
||||
res.modules.repos.putAll(this.modules.repos);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Engine engine, Data data) {
|
||||
return new Context(engine, data).pushEnv(this);
|
||||
@Override public boolean hasPermission(Permission perm, char delim) {
|
||||
return permissions == null || permissions.hasPermission(perm, delim);
|
||||
}
|
||||
@Override public boolean hasPermission(Permission perm) {
|
||||
return permissions == null || permissions.hasPermission(perm);
|
||||
}
|
||||
|
||||
public Context context(Engine engine) {
|
||||
return new Context(engine).pushEnv(this);
|
||||
return new Context(engine, this);
|
||||
}
|
||||
|
||||
public static Symbol getSymbol(String name) {
|
||||
if (symbols.containsKey(name)) return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
|
||||
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]);
|
||||
if (compile != null) this.compile = compile;
|
||||
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
|
||||
if (global == null) global = new GlobalScope();
|
||||
|
||||
this.wrappers = nativeConverter;
|
||||
this.compile = compile;
|
||||
this.global = global;
|
||||
}
|
||||
public Environment() {
|
||||
this(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.debug.Debugger;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class StackData {
|
||||
public static final DataKey<ArrayList<CodeFrame>> FRAMES = new DataKey<>();
|
||||
public static final DataKey<Integer> MAX_FRAMES = new DataKey<>();
|
||||
public static final DataKey<Debugger> DEBUGGER = new DataKey<>();
|
||||
|
||||
public static void pushFrame(Context ctx, CodeFrame frame) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
frames.add(frame);
|
||||
if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) throw EngineException.ofRange("Stack overflow!");
|
||||
ctx.pushEnv(frame.function.environment);
|
||||
}
|
||||
public static boolean popFrame(Context ctx, CodeFrame frame) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
if (frames.size() == 0) return false;
|
||||
if (frames.get(frames.size() - 1) != frame) return false;
|
||||
frames.remove(frames.size() - 1);
|
||||
ctx.popEnv();
|
||||
ctx.engine.onFramePop(ctx, frame);
|
||||
return true;
|
||||
}
|
||||
public static CodeFrame peekFrame(Context ctx) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
if (frames.size() == 0) return null;
|
||||
return frames.get(frames.size() - 1);
|
||||
}
|
||||
|
||||
public static List<CodeFrame> frames(Context ctx) {
|
||||
return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>()));
|
||||
}
|
||||
public static List<String> stackTrace(Context ctx) {
|
||||
var res = new ArrayList<String>();
|
||||
var frames = frames(ctx);
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
var el = frames.get(i);
|
||||
var name = el.function.name;
|
||||
Location loc = null;
|
||||
|
||||
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
|
||||
if (loc == null) loc = el.function.loc();
|
||||
|
||||
var trace = "";
|
||||
|
||||
if (loc != null) trace += "at " + loc.toString() + " ";
|
||||
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
||||
|
||||
trace = trace.trim();
|
||||
|
||||
if (!trace.equals("")) res.add(trace);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,17 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
|
||||
public interface DebugController {
|
||||
/**
|
||||
* Called when a script has been loaded
|
||||
* @param breakpoints
|
||||
* @param filename The name of the source
|
||||
* @param source The name of the source
|
||||
* @param breakpoints A set of all the breakpointable locations in this source
|
||||
* @param map The source map associated with this file. null if this source map isn't mapped
|
||||
*/
|
||||
void onSource(Filename filename, String source, TreeSet<Location> breakpoints);
|
||||
void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
|
||||
|
||||
/**
|
||||
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
|
||||
@@ -30,6 +34,13 @@ public interface DebugController {
|
||||
*/
|
||||
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
|
||||
|
||||
/**
|
||||
* Called immediatly before a frame has been pushed on the frame stack.
|
||||
* This function might pause in order to await debugging commands.
|
||||
* @param ctx The context of execution
|
||||
* @param frame The code frame which was pushed
|
||||
*/
|
||||
void onFramePush(Context ctx, CodeFrame frame);
|
||||
/**
|
||||
* Called immediatly after a frame has been popped out of the frame stack.
|
||||
* This function might pause in order to await debugging commands.
|
||||
|
||||
@@ -4,7 +4,6 @@ public interface DebugHandler {
|
||||
void enable(V8Message msg);
|
||||
void disable(V8Message msg);
|
||||
|
||||
void setBreakpoint(V8Message msg);
|
||||
void setBreakpointByUrl(V8Message msg);
|
||||
void removeBreakpoint(V8Message msg);
|
||||
void continueToLocation(V8Message msg);
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.Metadata;
|
||||
import me.topchetoeu.jscript.Reading;
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
import me.topchetoeu.jscript.events.Notifier;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
@@ -19,7 +20,7 @@ import me.topchetoeu.jscript.json.JSONList;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class DebugServer {
|
||||
public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION;
|
||||
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
|
||||
|
||||
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
|
||||
|
||||
@@ -74,7 +75,6 @@ public class DebugServer {
|
||||
debugger.enable(msg); continue;
|
||||
case "Debugger.disable": debugger.disable(msg); continue;
|
||||
|
||||
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
|
||||
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
|
||||
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
|
||||
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
|
||||
@@ -232,13 +232,12 @@ public class DebugServer {
|
||||
|
||||
public DebugServer() {
|
||||
try {
|
||||
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes();
|
||||
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes();
|
||||
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes());
|
||||
this.index = index
|
||||
.replace("${NAME}", Metadata.NAME)
|
||||
.replace("${VERSION}", Metadata.VERSION)
|
||||
.replace("${AUTHOR}", Metadata.AUTHOR)
|
||||
this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
|
||||
this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
|
||||
this.index = Reading.resourceToString("debugger/index.html")
|
||||
.replace("${NAME}", Metadata.name())
|
||||
.replace("${VERSION}", Metadata.version())
|
||||
.replace("${AUTHOR}", Metadata.author())
|
||||
.getBytes();
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -15,7 +15,6 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
@@ -31,15 +30,24 @@ import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONList;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
// very simple indeed
|
||||
public class SimpleDebugger implements Debugger {
|
||||
public static final Set<String> VSCODE_EMPTY = Set.of(
|
||||
"function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}",
|
||||
"function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}",
|
||||
"function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}",
|
||||
"function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}"
|
||||
);
|
||||
public static final Set<String> VSCODE_SELF = Set.of(
|
||||
"function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}"
|
||||
);
|
||||
|
||||
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
|
||||
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}";
|
||||
public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}";
|
||||
public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}";
|
||||
public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}";
|
||||
public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}";
|
||||
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}";
|
||||
public static final String VSCODE_AUTOCOMPLETE = "function(t,e,r){let n=r?\"variable\":\"property\",i=(l,p,f)=>{if(p!==\"function\")return n;if(l===\"constructor\")return\"class\";let m=String(f);return m.startsWith(\"class \")||m.includes(\"[native code]\")&&/^[A-Z]/.test(l)?\"class\":r?\"function\":\"method\"\n},o=l=>{switch(typeof l){case\"number\":case\"boolean\":return`${l}`;case\"object\":return l===null?\"null\":l.constructor.name||\"object\";case\"function\":return`fn(${new Array(l.length).fill(\"?\").join(\", \")})`;default:return typeof l}},s=[],a=new Set,u=\"~\",c=t===void 0?this:t;for(;c!=null;c=c.__proto__){u+=\"~\";let l=Object.getOwnPropertyNames(c).filter(p=>p.startsWith(e)&&!p.match(/^\\d+$/));for(let p of l){if(a.has(p))continue;a.add(p);let f=Object.getOwnPropertyDescriptor(c,p),m=n,h;try{let H=c[p];m=i(p,typeof f?.value,H),h=o(H)}catch{}s.push({label:p,sortText:u+p.replace(/^_+/,H=>\"{\".repeat(H.length)),type:m,detail:h})}r=!1}return{result:s,isArray:this instanceof Array}}";
|
||||
|
||||
private static enum State {
|
||||
RESUMED,
|
||||
@@ -104,7 +112,7 @@ public class SimpleDebugger implements Debugger {
|
||||
public boolean debugData = false;
|
||||
|
||||
public void updateLoc(Location loc) {
|
||||
serialized.set("location", serializeLocation(loc));
|
||||
if (loc == null) return;
|
||||
this.location = loc;
|
||||
}
|
||||
|
||||
@@ -112,8 +120,6 @@ public class SimpleDebugger implements Debugger {
|
||||
this.frame = frame;
|
||||
this.func = frame.function;
|
||||
this.id = id;
|
||||
this.local = new ObjectValue();
|
||||
this.location = frame.function.loc();
|
||||
|
||||
this.global = frame.function.environment.global.obj;
|
||||
this.local = frame.getLocalScope(ctx, true);
|
||||
@@ -121,13 +127,11 @@ public class SimpleDebugger implements Debugger {
|
||||
this.local.setPrototype(ctx, capture);
|
||||
this.capture.setPrototype(ctx, global);
|
||||
this.valstack = frame.getValStackScope(ctx);
|
||||
|
||||
if (location != null) {
|
||||
debugData = true;
|
||||
|
||||
this.serialized = new JSONMap()
|
||||
.set("callFrameId", id + "")
|
||||
.set("functionName", func.name)
|
||||
.set("location", serializeLocation(location))
|
||||
.set("scopeChain", new JSONList()
|
||||
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
|
||||
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
|
||||
@@ -136,9 +140,23 @@ public class SimpleDebugger implements Debugger {
|
||||
);
|
||||
}
|
||||
}
|
||||
private class ObjRef {
|
||||
public final ObjectValue obj;
|
||||
public final Context ctx;
|
||||
public final HashSet<String> heldGroups = new HashSet<>();
|
||||
public boolean held = true;
|
||||
|
||||
public boolean shouldRelease() {
|
||||
return !held && heldGroups.size() == 0;
|
||||
}
|
||||
|
||||
private class RunResult {
|
||||
public ObjRef(Context ctx, ObjectValue obj) {
|
||||
this.ctx = ctx;
|
||||
this.obj = obj;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RunResult {
|
||||
public final Context ctx;
|
||||
public final Object result;
|
||||
public final EngineException error;
|
||||
@@ -151,7 +169,7 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
|
||||
public boolean enabled = true;
|
||||
public CatchType execptionType = CatchType.ALL;
|
||||
public CatchType execptionType = CatchType.NONE;
|
||||
public State state = State.RESUMED;
|
||||
|
||||
public final WebSocket ws;
|
||||
@@ -172,23 +190,50 @@ public class SimpleDebugger implements Debugger {
|
||||
private HashMap<Integer, Frame> idToFrame = new HashMap<>();
|
||||
private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>();
|
||||
|
||||
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>();
|
||||
private HashMap<Integer, ObjRef> idToObject = new HashMap<>();
|
||||
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
|
||||
private HashMap<ObjectValue, Context> objectToCtx = new HashMap<>();
|
||||
private HashMap<String, ArrayList<ObjectValue>> objectGroups = new HashMap<>();
|
||||
private HashMap<String, ArrayList<ObjRef>> objectGroups = new HashMap<>();
|
||||
|
||||
private Notifier updateNotifier = new Notifier();
|
||||
private boolean pendingPause = false;
|
||||
|
||||
private int nextId = new Random().nextInt() & 0x7FFFFFFF;
|
||||
private Location prevLocation = null;
|
||||
private int nextId = 0;
|
||||
private Frame stepOutFrame = null, currFrame = null;
|
||||
private int stepOutPtr = 0;
|
||||
|
||||
private int nextId() {
|
||||
return nextId++ ^ 1630022591 /* big prime */;
|
||||
private boolean compare(String src, String target) {
|
||||
if (src.length() != target.length()) return false;
|
||||
var diff = 0;
|
||||
var all = 0;
|
||||
|
||||
for (var i = 0; i < src.length(); i++) {
|
||||
var a = src.charAt(i);
|
||||
var b = target.charAt(i);
|
||||
var letter = Parsing.isLetter(a) && Parsing.isLetter(b);
|
||||
|
||||
if (a != b) {
|
||||
if (letter) diff++;
|
||||
else return false;
|
||||
}
|
||||
|
||||
private void updateFrames(Context ctx) {
|
||||
var frame = StackData.peekFrame(ctx);
|
||||
if (letter) all++;
|
||||
}
|
||||
|
||||
return diff / (float)all < .5f;
|
||||
}
|
||||
private boolean compare(String src, Set<String> target) {
|
||||
for (var el : target) {
|
||||
if (compare(src, el)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int nextId() {
|
||||
return nextId++;
|
||||
}
|
||||
|
||||
private synchronized void updateFrames(Context ctx) {
|
||||
var frame = ctx.peekFrame();
|
||||
if (frame == null) return;
|
||||
|
||||
if (!codeFrameToFrame.containsKey(frame)) {
|
||||
@@ -203,10 +248,13 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
private JSONList serializeFrames(Context ctx) {
|
||||
var res = new JSONList();
|
||||
var frames = StackData.frames(ctx);
|
||||
var frames = ctx.frames();
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
res.add(codeFrameToFrame.get(frames.get(i)).serialized);
|
||||
var frame = codeFrameToFrame.get(frames.get(i));
|
||||
if (frame.location == null) continue;
|
||||
frame.serialized.set("location", serializeLocation(frame.location));
|
||||
if (frame.location != null) res.add(frame.serialized);
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -246,13 +294,13 @@ public class SimpleDebugger implements Debugger {
|
||||
if (objectToId.containsKey(obj)) return objectToId.get(obj);
|
||||
else {
|
||||
int id = nextId();
|
||||
var ref = new ObjRef(ctx, obj);
|
||||
objectToId.put(obj, id);
|
||||
if (ctx != null) objectToCtx.put(obj, ctx);
|
||||
idToObject.put(id, obj);
|
||||
idToObject.put(id, ref);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
private JSONMap serializeObj(Context ctx, Object val) {
|
||||
private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
|
||||
val = Values.normalize(null, val);
|
||||
|
||||
if (val == Values.NULL) {
|
||||
@@ -273,7 +321,7 @@ public class SimpleDebugger implements Debugger {
|
||||
if (obj instanceof FunctionValue) type = "function";
|
||||
if (obj instanceof ArrayValue) subtype = "array";
|
||||
|
||||
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
|
||||
try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); }
|
||||
catch (Exception e) { }
|
||||
|
||||
var res = new JSONMap()
|
||||
@@ -283,9 +331,28 @@ public class SimpleDebugger implements Debugger {
|
||||
if (subtype != null) res.set("subtype", subtype);
|
||||
if (className != null) {
|
||||
res.set("className", className);
|
||||
res.set("description", "{ " + className + " }");
|
||||
}
|
||||
|
||||
if (obj instanceof ArrayValue) res.set("description", "Array(" + ((ArrayValue)obj).size() + ")");
|
||||
else if (obj instanceof FunctionValue) res.set("description", obj.toString());
|
||||
else {
|
||||
var defaultToString = false;
|
||||
|
||||
try {
|
||||
defaultToString =
|
||||
Values.getMember(ctx, obj, "toString") ==
|
||||
Values.getMember(ctx, ctx.environment().proto("object"), "toString");
|
||||
}
|
||||
catch (Exception e) { }
|
||||
|
||||
try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(ctx, obj) + " }")); }
|
||||
catch (EngineException e) { res.set("description", className); }
|
||||
}
|
||||
|
||||
|
||||
if (byValue) try { res.put("value", JSON.fromJs(ctx, obj)); }
|
||||
catch (Exception e) { }
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -309,33 +376,44 @@ public class SimpleDebugger implements Debugger {
|
||||
|
||||
throw new IllegalArgumentException("Unexpected JS object.");
|
||||
}
|
||||
private void setObjectGroup(String name, Object val) {
|
||||
private JSONMap serializeObj(Context ctx, Object val) {
|
||||
return serializeObj(ctx, val, false);
|
||||
}
|
||||
private void addObjectGroup(String name, Object val) {
|
||||
if (val instanceof ObjectValue) {
|
||||
var obj = (ObjectValue)val;
|
||||
var id = objectToId.getOrDefault(obj, -1);
|
||||
if (id < 0) return;
|
||||
|
||||
if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj);
|
||||
else objectGroups.put(name, new ArrayList<>(List.of(obj)));
|
||||
var ref = idToObject.get(id);
|
||||
|
||||
if (objectGroups.containsKey(name)) objectGroups.get(name).add(ref);
|
||||
else objectGroups.put(name, new ArrayList<>(List.of(ref)));
|
||||
|
||||
ref.heldGroups.add(name);
|
||||
}
|
||||
}
|
||||
private void releaseGroup(String name) {
|
||||
var objs = objectGroups.remove(name);
|
||||
|
||||
if (objs != null) {
|
||||
for (var obj : objs) {
|
||||
var id = objectToId.remove(obj);
|
||||
if (objs != null) for (var obj : objs) {
|
||||
if (obj.heldGroups.remove(name) && obj.shouldRelease()) {
|
||||
var id = objectToId.remove(obj.obj);
|
||||
if (id != null) idToObject.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Object deserializeArgument(JSONMap val) {
|
||||
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId")));
|
||||
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))).obj;
|
||||
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
|
||||
case "NaN": return Double.NaN;
|
||||
case "-Infinity": return Double.NEGATIVE_INFINITY;
|
||||
case "Infinity": return Double.POSITIVE_INFINITY;
|
||||
case "-0": return -0.;
|
||||
}
|
||||
return JSON.toJs(val.get("value"));
|
||||
var res = val.get("value");
|
||||
if (res == null) return null;
|
||||
else return JSON.toJs(res);
|
||||
}
|
||||
|
||||
private JSONMap serializeException(Context ctx, EngineException err) {
|
||||
@@ -351,7 +429,6 @@ public class SimpleDebugger implements Debugger {
|
||||
var res = new JSONMap()
|
||||
.set("exceptionId", nextId())
|
||||
.set("exception", serializeObj(ctx, err.value))
|
||||
.set("exception", serializeObj(ctx, err.value))
|
||||
.set("text", text);
|
||||
|
||||
return res;
|
||||
@@ -399,19 +476,14 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
|
||||
private RunResult run(Frame codeFrame, String code) {
|
||||
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
|
||||
var engine = new Engine(false);
|
||||
var env = codeFrame.func.environment.fork();
|
||||
|
||||
ObjectValue global = env.global.obj,
|
||||
capture = codeFrame.frame.getCaptureScope(null, enabled),
|
||||
local = codeFrame.frame.getLocalScope(null, enabled);
|
||||
env.global = new GlobalScope(codeFrame.local);
|
||||
|
||||
capture.setPrototype(null, global);
|
||||
local.setPrototype(null, capture);
|
||||
env.global = new GlobalScope(local);
|
||||
|
||||
var ctx = new Context(engine).pushEnv(env);
|
||||
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
var ctx = new Context(engine, env);
|
||||
var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
|
||||
engine.run(true);
|
||||
|
||||
@@ -419,7 +491,80 @@ public class SimpleDebugger implements Debugger {
|
||||
catch (EngineException e) { return new RunResult(ctx, null, e); }
|
||||
}
|
||||
|
||||
@Override public void enable(V8Message msg) {
|
||||
private ObjectValue vscodeAutoSuggest(Context ctx, Object target, String query, boolean variable) {
|
||||
var res = new ArrayValue();
|
||||
var passed = new HashSet<String>();
|
||||
var tildas = "~";
|
||||
if (target == null) target = ctx.environment().getGlobal();
|
||||
|
||||
for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) {
|
||||
for (var el : Values.getMembers(ctx, proto, true, true)) {
|
||||
var strKey = Values.toString(ctx, el);
|
||||
if (passed.contains(strKey)) continue;
|
||||
passed.add(strKey);
|
||||
|
||||
var val = Values.getMember(ctx, Values.getMemberDescriptor(ctx, proto, el), "value");
|
||||
var desc = new ObjectValue();
|
||||
var sortText = "";
|
||||
if (strKey.startsWith(query)) sortText += "0@";
|
||||
else if (strKey.toLowerCase().startsWith(query.toLowerCase())) sortText += "1@";
|
||||
else if (strKey.contains(query)) sortText += "2@";
|
||||
else if (strKey.toLowerCase().contains(query.toLowerCase())) sortText += "3@";
|
||||
else sortText += "4@";
|
||||
sortText += tildas + strKey;
|
||||
|
||||
desc.defineProperty(ctx, "label", strKey);
|
||||
desc.defineProperty(ctx, "sortText", sortText);
|
||||
|
||||
if (val instanceof FunctionValue) {
|
||||
if (strKey.equals("constructor")) desc.defineProperty(ctx, "type", "name");
|
||||
else desc.defineProperty(ctx, "type", variable ? "function" : "method");
|
||||
}
|
||||
else desc.defineProperty(ctx, "type", variable ? "variable" : "property");
|
||||
|
||||
switch (Values.type(val)) {
|
||||
case "number":
|
||||
case "boolean":
|
||||
desc.defineProperty(ctx, "detail", Values.toString(ctx, val));
|
||||
break;
|
||||
case "object":
|
||||
if (val == Values.NULL) desc.defineProperty(ctx, "detail", "null");
|
||||
else try {
|
||||
desc.defineProperty(ctx, "detail", Values.getMemberPath(ctx, target, "constructor", "name"));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
desc.defineProperty(ctx, "detail", "object");
|
||||
}
|
||||
break;
|
||||
case "function": {
|
||||
var type = "fn(";
|
||||
for (var i = 0; i < ((FunctionValue)val).length; i++) {
|
||||
if (i != 0) type += ",";
|
||||
type += "?";
|
||||
}
|
||||
type += ")";
|
||||
desc.defineProperty(ctx, "detail", type);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
desc.defineProperty(ctx, "type", Values.type(val));
|
||||
break;
|
||||
}
|
||||
|
||||
res.set(ctx, res.size(), desc);
|
||||
}
|
||||
|
||||
tildas += "~";
|
||||
variable = true;
|
||||
}
|
||||
|
||||
var resObj = new ObjectValue();
|
||||
resObj.defineProperty(ctx, "result", res);
|
||||
resObj.defineProperty(ctx, "isArray", target instanceof ArrayValue);
|
||||
return resObj;
|
||||
}
|
||||
|
||||
@Override public synchronized void enable(V8Message msg) {
|
||||
enabled = true;
|
||||
ws.send(msg.respond());
|
||||
|
||||
@@ -428,17 +573,17 @@ public class SimpleDebugger implements Debugger {
|
||||
|
||||
updateNotifier.next();
|
||||
}
|
||||
@Override public void disable(V8Message msg) {
|
||||
@Override public synchronized void disable(V8Message msg) {
|
||||
enabled = false;
|
||||
ws.send(msg.respond());
|
||||
updateNotifier.next();
|
||||
}
|
||||
|
||||
@Override public void getScriptSource(V8Message msg) {
|
||||
@Override public synchronized void getScriptSource(V8Message msg) {
|
||||
int id = Integer.parseInt(msg.params.string("scriptId"));
|
||||
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
|
||||
}
|
||||
@Override public void getPossibleBreakpoints(V8Message msg) {
|
||||
@Override public synchronized void getPossibleBreakpoints(V8Message msg) {
|
||||
var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId")));
|
||||
var start = deserializeLocation(msg.params.get("start"), false);
|
||||
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
|
||||
@@ -453,30 +598,21 @@ public class SimpleDebugger implements Debugger {
|
||||
ws.send(msg.respond(new JSONMap().set("locations", res)));
|
||||
}
|
||||
|
||||
@Override public void pause(V8Message msg) {
|
||||
@Override public synchronized void pause(V8Message msg) {
|
||||
pendingPause = true;
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
|
||||
@Override public void resume(V8Message msg) {
|
||||
@Override public synchronized void resume(V8Message msg) {
|
||||
resume(State.RESUMED);
|
||||
ws.send(msg.respond(new JSONMap()));
|
||||
}
|
||||
|
||||
@Override public void setBreakpoint(V8Message msg) {
|
||||
// int id = nextId();
|
||||
// var loc = deserializeLocation(msg.params.get("location"), true);
|
||||
// var bpt = new Breakpoint(id, loc, null);
|
||||
// breakpoints.put(loc, bpt);
|
||||
// idToBrpt.put(id, bpt);
|
||||
// ws.send(msg.respond(new JSONMap()
|
||||
// .set("breakpointId", id)
|
||||
// .set("actualLocation", serializeLocation(loc))
|
||||
// ));
|
||||
}
|
||||
@Override public void setBreakpointByUrl(V8Message msg) {
|
||||
@Override public synchronized void setBreakpointByUrl(V8Message msg) {
|
||||
var line = (int)msg.params.number("lineNumber") + 1;
|
||||
var col = (int)msg.params.number("columnNumber", 0) + 1;
|
||||
var cond = msg.params.string("condition", null);
|
||||
var cond = msg.params.string("condition", "").trim();
|
||||
|
||||
if (cond.equals("")) cond = null;
|
||||
if (cond != null) cond = "(" + cond + ")";
|
||||
|
||||
Pattern regex;
|
||||
@@ -506,7 +642,7 @@ public class SimpleDebugger implements Debugger {
|
||||
.set("locations", locs)
|
||||
));
|
||||
}
|
||||
@Override public void removeBreakpoint(V8Message msg) {
|
||||
@Override public synchronized void removeBreakpoint(V8Message msg) {
|
||||
var id = Integer.parseInt(msg.params.string("breakpointId"));
|
||||
|
||||
if (idToBptCand.containsKey(id)) {
|
||||
@@ -523,7 +659,7 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
@Override public void continueToLocation(V8Message msg) {
|
||||
@Override public synchronized void continueToLocation(V8Message msg) {
|
||||
var loc = deserializeLocation(msg.params.get("location"), true);
|
||||
|
||||
tmpBreakpts.add(loc);
|
||||
@@ -532,39 +668,48 @@ public class SimpleDebugger implements Debugger {
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
|
||||
@Override public void setPauseOnExceptions(V8Message msg) {
|
||||
ws.send(new V8Error("i dont wanna to"));
|
||||
@Override public synchronized void setPauseOnExceptions(V8Message msg) {
|
||||
switch (msg.params.string("state")) {
|
||||
case "none": execptionType = CatchType.NONE; break;
|
||||
case "all": execptionType = CatchType.ALL; break;
|
||||
case "uncaught": execptionType = CatchType.UNCAUGHT; break;
|
||||
default:
|
||||
ws.send(new V8Error("Invalid exception pause type."));
|
||||
return;
|
||||
}
|
||||
|
||||
@Override public void stepInto(V8Message msg) {
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
|
||||
@Override public synchronized void stepInto(V8Message msg) {
|
||||
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
|
||||
else {
|
||||
prevLocation = currFrame.location;
|
||||
stepOutFrame = currFrame;
|
||||
stepOutPtr = currFrame.frame.codePtr;
|
||||
resume(State.STEPPING_IN);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
}
|
||||
@Override public void stepOut(V8Message msg) {
|
||||
@Override public synchronized void stepOut(V8Message msg) {
|
||||
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
|
||||
else {
|
||||
prevLocation = currFrame.location;
|
||||
stepOutFrame = currFrame;
|
||||
stepOutPtr = currFrame.frame.codePtr;
|
||||
resume(State.STEPPING_OUT);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
}
|
||||
@Override public void stepOver(V8Message msg) {
|
||||
@Override public synchronized void stepOver(V8Message msg) {
|
||||
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
|
||||
else {
|
||||
prevLocation = currFrame.location;
|
||||
stepOutFrame = currFrame;
|
||||
stepOutPtr = currFrame.frame.codePtr;
|
||||
resume(State.STEPPING_OVER);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void evaluateOnCallFrame(V8Message msg) {
|
||||
@Override public synchronized void evaluateOnCallFrame(V8Message msg) {
|
||||
var cfId = Integer.parseInt(msg.params.string("callFrameId"));
|
||||
var expr = msg.params.string("expression");
|
||||
var group = msg.params.string("objectGroup", null);
|
||||
@@ -572,32 +717,37 @@ public class SimpleDebugger implements Debugger {
|
||||
var cf = idToFrame.get(cfId);
|
||||
var res = run(cf, expr);
|
||||
|
||||
if (group != null) setObjectGroup(group, res.result);
|
||||
if (group != null) addObjectGroup(group, res.result);
|
||||
|
||||
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result))));
|
||||
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
|
||||
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(res.ctx, res.error))));
|
||||
else ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
|
||||
}
|
||||
|
||||
@Override public void releaseObjectGroup(V8Message msg) {
|
||||
@Override public synchronized void releaseObjectGroup(V8Message msg) {
|
||||
var group = msg.params.string("objectGroup");
|
||||
releaseGroup(group);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
@Override
|
||||
public void releaseObject(V8Message msg) {
|
||||
@Override public synchronized void releaseObject(V8Message msg) {
|
||||
var id = Integer.parseInt(msg.params.string("objectId"));
|
||||
var obj = idToObject.remove(id);
|
||||
if (obj != null) objectToId.remove(obj);
|
||||
var ref = idToObject.get(id);
|
||||
ref.held = false;
|
||||
|
||||
if (ref.shouldRelease()) {
|
||||
objectToId.remove(ref.obj);
|
||||
idToObject.remove(id);
|
||||
}
|
||||
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
@Override public void getProperties(V8Message msg) {
|
||||
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
|
||||
@Override public synchronized void getProperties(V8Message msg) {
|
||||
var ref = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
|
||||
var obj = ref.obj;
|
||||
|
||||
var res = new JSONList();
|
||||
var ctx = objectToCtx.get(obj);
|
||||
var ctx = ref.ctx;
|
||||
|
||||
if (obj != emptyObject) {
|
||||
if (obj != emptyObject && obj != null) {
|
||||
for (var key : obj.keys(true)) {
|
||||
var propDesc = new JSONMap();
|
||||
|
||||
@@ -637,7 +787,7 @@ public class SimpleDebugger implements Debugger {
|
||||
|
||||
ws.send(msg.respond(new JSONMap().set("result", res)));
|
||||
}
|
||||
@Override public void callFunctionOn(V8Message msg) {
|
||||
@Override public synchronized void callFunctionOn(V8Message msg) {
|
||||
var src = msg.params.string("functionDeclaration");
|
||||
var args = msg.params
|
||||
.list("arguments", new JSONList())
|
||||
@@ -645,9 +795,11 @@ public class SimpleDebugger implements Debugger {
|
||||
.map(v -> v.map())
|
||||
.map(this::deserializeArgument)
|
||||
.collect(Collectors.toList());
|
||||
var byValue = msg.params.bool("returnByValue", false);
|
||||
|
||||
var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
|
||||
var ctx = objectToCtx.get(thisArg);
|
||||
var thisArgRef = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
|
||||
var thisArg = thisArgRef.obj;
|
||||
var ctx = thisArgRef.ctx;
|
||||
|
||||
while (true) {
|
||||
var start = src.lastIndexOf("//# sourceURL=");
|
||||
@@ -658,47 +810,36 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
|
||||
try {
|
||||
switch (src) {
|
||||
case CHROME_GET_PROP_FUNC: {
|
||||
var path = JSON.parse(null, (String)args.get(0)).list();
|
||||
Object res = thisArg;
|
||||
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el));
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
|
||||
return;
|
||||
Object res = null;
|
||||
if (compare(src, VSCODE_EMPTY)) res = emptyObject;
|
||||
else if (compare(src, VSCODE_SELF)) res = thisArg;
|
||||
else if (compare(src, CHROME_GET_PROP_FUNC)) {
|
||||
res = thisArg;
|
||||
for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(ctx, res, JSON.toJs(el));
|
||||
}
|
||||
case VSCODE_STRINGIFY_VAL:
|
||||
case VSCODE_STRINGIFY_PROPS:
|
||||
case VSCODE_SHALLOW_COPY:
|
||||
case VSCODE_SYMBOL_REQUEST:
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
|
||||
break;
|
||||
case VSCODE_FLATTEN_ARRAY:
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
|
||||
break;
|
||||
case VSCODE_CALL: {
|
||||
else if (compare(src, VSCODE_CALL)) {
|
||||
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
else if (compare(src, VSCODE_AUTOCOMPLETE)) {
|
||||
var target = args.get(0);
|
||||
if (target == null) target = thisArg;
|
||||
res = vscodeAutoSuggest(ctx, target, Values.toString(ctx, args.get(1)), Values.toBoolean(args.get(2)));
|
||||
}
|
||||
else {
|
||||
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
|
||||
break;
|
||||
// default: {
|
||||
// var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx);
|
||||
// if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args);
|
||||
// ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
|
||||
// }
|
||||
return;
|
||||
}
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res, byValue))));
|
||||
}
|
||||
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runtimeEnable(V8Message msg) {
|
||||
@Override public synchronized void runtimeEnable(V8Message msg) {
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) {
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations, SourceMap map) {
|
||||
int id = nextId();
|
||||
var src = new Source(id, filename, source, locations);
|
||||
|
||||
@@ -720,87 +861,103 @@ public class SimpleDebugger implements Debugger {
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (!enabled) return false;
|
||||
|
||||
updateFrames(ctx);
|
||||
var frame = codeFrameToFrame.get(cf);
|
||||
boolean isBreakpointable;
|
||||
Location loc;
|
||||
Frame frame;
|
||||
|
||||
synchronized (this) {
|
||||
frame = codeFrameToFrame.get(cf);
|
||||
|
||||
if (!frame.debugData) return false;
|
||||
|
||||
if (instruction.location != null) frame.updateLoc(instruction.location);
|
||||
var loc = frame.location;
|
||||
var isBreakpointable = loc != null && (
|
||||
idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) ||
|
||||
returnVal != Runners.NO_RETURN
|
||||
);
|
||||
|
||||
// TODO: FIXXXX
|
||||
// if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null;
|
||||
if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location));
|
||||
loc = frame.location;
|
||||
isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn());
|
||||
|
||||
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
|
||||
pauseException(ctx);
|
||||
}
|
||||
else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Runners.NO_RETURN && stepOutFrame == frame) {
|
||||
pauseDebug(ctx, null);
|
||||
}
|
||||
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
|
||||
var bp = locToBreakpoint.get(loc);
|
||||
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
|
||||
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
|
||||
}
|
||||
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
|
||||
else if (isBreakpointable && pendingPause) {
|
||||
pauseDebug(ctx, null);
|
||||
pendingPause = false;
|
||||
}
|
||||
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
|
||||
}
|
||||
|
||||
|
||||
while (enabled) {
|
||||
synchronized (this) {
|
||||
switch (state) {
|
||||
case PAUSED_EXCEPTION:
|
||||
case PAUSED_NORMAL: break;
|
||||
|
||||
case STEPPING_OUT:
|
||||
case RESUMED: return false;
|
||||
|
||||
case STEPPING_IN:
|
||||
if (!prevLocation.equals(loc)) {
|
||||
if (isBreakpointable) pauseDebug(ctx, null);
|
||||
else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null);
|
||||
else return false;
|
||||
}
|
||||
else return false;
|
||||
break;
|
||||
case STEPPING_OVER:
|
||||
if (stepOutFrame.frame == frame.frame) {
|
||||
if (isBreakpointable && (
|
||||
!loc.filename().equals(prevLocation.filename()) ||
|
||||
loc.line() != prevLocation.line()
|
||||
)) pauseDebug(ctx, null);
|
||||
else return false;
|
||||
if (returnVal != Runners.NO_RETURN || error != null) {
|
||||
state = State.STEPPING_OUT;
|
||||
continue;
|
||||
}
|
||||
else return false;
|
||||
else if (stepOutPtr != frame.frame.codePtr) {
|
||||
if (state == State.STEPPING_IN && instruction.breakpoint.shouldStepIn()) {
|
||||
pauseDebug(ctx, null);
|
||||
break;
|
||||
}
|
||||
else if (state == State.STEPPING_OVER && instruction.breakpoint.shouldStepOver()) {
|
||||
pauseDebug(ctx, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
updateNotifier.await();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@Override public void onFramePush(Context ctx, CodeFrame frame) {
|
||||
var prevFrame = currFrame;
|
||||
updateFrames(ctx);
|
||||
|
||||
if (stepOutFrame != null && stepOutFrame.frame == prevFrame.frame && state == State.STEPPING_IN) {
|
||||
stepOutFrame = currFrame;
|
||||
}
|
||||
}
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
updateFrames(ctx);
|
||||
|
||||
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
|
||||
catch (NullPointerException e) { }
|
||||
|
||||
if (StackData.frames(ctx).size() == 0) resume(State.RESUMED);
|
||||
else if (stepOutFrame != null && stepOutFrame.frame == frame &&
|
||||
(state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER)
|
||||
) {
|
||||
// if (state == State.STEPPING_OVER) state = State.STEPPING_IN;
|
||||
// else {
|
||||
pauseDebug(ctx, null);
|
||||
updateNotifier.await();
|
||||
// }
|
||||
if (ctx.frames().size() == 0) {
|
||||
if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED);
|
||||
}
|
||||
else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) {
|
||||
state = State.STEPPING_IN;
|
||||
stepOutFrame = currFrame;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void connect() {
|
||||
@Override public synchronized void connect() {
|
||||
if (!target.attachDebugger(this)) {
|
||||
ws.send(new V8Error("A debugger is already attached to this engine."));
|
||||
}
|
||||
}
|
||||
@Override public void disconnect() {
|
||||
@Override public synchronized void disconnect() {
|
||||
target.detachDebugger();
|
||||
enabled = false;
|
||||
updateNotifier.next();
|
||||
|
||||
@@ -118,8 +118,6 @@ public class WebSocket implements AutoCloseable {
|
||||
else send(msg.textData());
|
||||
}
|
||||
public void send(Object data) {
|
||||
// TODO: Remove
|
||||
// System.out.println("SEND: " + data);
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(1, data.toString().getBytes());
|
||||
}
|
||||
@@ -201,10 +199,6 @@ public class WebSocket implements AutoCloseable {
|
||||
if (!fin) continue;
|
||||
var raw = data.toByteArray();
|
||||
|
||||
// TODO: Remove
|
||||
// System.out.println("RECEIVED: " + new String(raw));
|
||||
|
||||
|
||||
if (type == 1) return new WebSocketMessage(new String(raw));
|
||||
else return new WebSocketMessage(raw);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
@@ -16,32 +17,74 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class CodeFrame {
|
||||
public static enum TryState {
|
||||
TRY,
|
||||
CATCH,
|
||||
FINALLY,
|
||||
}
|
||||
|
||||
public static class TryCtx {
|
||||
public static final int STATE_TRY = 0;
|
||||
public static final int STATE_CATCH = 1;
|
||||
public static final int STATE_FINALLY_THREW = 2;
|
||||
public static final int STATE_FINALLY_RETURNED = 3;
|
||||
public static final int STATE_FINALLY_JUMPED = 4;
|
||||
public final int start, end, catchStart, finallyStart;
|
||||
public final int restoreStackPtr;
|
||||
public final TryState state;
|
||||
public final EngineException error;
|
||||
public final PendingResult result;
|
||||
|
||||
public final boolean hasCatch, hasFinally;
|
||||
public final int tryStart, catchStart, finallyStart, end;
|
||||
public int state;
|
||||
public Object retVal;
|
||||
public EngineException err;
|
||||
public int jumpPtr;
|
||||
public boolean hasCatch() { return catchStart >= 0; }
|
||||
public boolean hasFinally() { return finallyStart >= 0; }
|
||||
|
||||
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) {
|
||||
hasCatch = catchN >= 0;
|
||||
hasFinally = finallyN >= 0;
|
||||
public boolean inBounds(int ptr) {
|
||||
return ptr >= start && ptr < end;
|
||||
}
|
||||
|
||||
if (catchN < 0) catchN = 0;
|
||||
if (finallyN < 0) finallyN = 0;
|
||||
public TryCtx _catch(EngineException e) {
|
||||
if (error != null) e.setCause(error);
|
||||
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
|
||||
}
|
||||
public TryCtx _finally(PendingResult res) {
|
||||
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
|
||||
}
|
||||
|
||||
this.tryStart = tryStart;
|
||||
this.catchStart = tryStart + tryN;
|
||||
this.finallyStart = catchStart + catchN;
|
||||
this.end = finallyStart + finallyN;
|
||||
this.jumpPtr = end;
|
||||
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
|
||||
this.catchStart = catchStart;
|
||||
this.finallyStart = finallyStart;
|
||||
this.restoreStackPtr = stackPtr;
|
||||
this.result = res == null ? PendingResult.ofNone() : res;
|
||||
this.state = state;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.error = err;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PendingResult {
|
||||
public final boolean isReturn, isJump, isThrow;
|
||||
public final Object value;
|
||||
public final EngineException error;
|
||||
public final int ptr;
|
||||
public final Instruction instruction;
|
||||
|
||||
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
|
||||
this.instruction = instr;
|
||||
this.isReturn = isReturn;
|
||||
this.isJump = isJump;
|
||||
this.isThrow = isThrow;
|
||||
this.value = value;
|
||||
this.error = error;
|
||||
this.ptr = ptr;
|
||||
}
|
||||
|
||||
public static PendingResult ofNone() {
|
||||
return new PendingResult(null, false, false, false, null, null, 0);
|
||||
}
|
||||
public static PendingResult ofReturn(Object value, Instruction instr) {
|
||||
return new PendingResult(instr, true, false, false, value, null, 0);
|
||||
}
|
||||
public static PendingResult ofThrow(EngineException error, Instruction instr) {
|
||||
return new PendingResult(instr, false, false, true, null, error, 0);
|
||||
}
|
||||
public static PendingResult ofJump(int codePtr, Instruction instr) {
|
||||
return new PendingResult(instr, false, true, false, null, null, codePtr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +93,10 @@ public class CodeFrame {
|
||||
public final Object[] args;
|
||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||
public final CodeFunction function;
|
||||
|
||||
public Object[] stack = new Object[32];
|
||||
public int stackPtr = 0;
|
||||
public int codePtr = 0;
|
||||
public boolean jumpFlag = false;
|
||||
public boolean jumpFlag = false, popTryFlag = false;
|
||||
private Location prevLoc = null;
|
||||
|
||||
public ObjectValue getLocalScope(Context ctx, boolean props) {
|
||||
@@ -104,9 +146,9 @@ public class CodeFrame {
|
||||
};
|
||||
}
|
||||
|
||||
public void addTry(int n, int catchN, int finallyN) {
|
||||
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
||||
if (!tryStack.empty()) res.err = tryStack.peek().err;
|
||||
public void addTry(int start, int end, int catchStart, int finallyStart) {
|
||||
var err = tryStack.empty() ? null : tryStack.peek().error;
|
||||
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
|
||||
|
||||
tryStack.add(res);
|
||||
}
|
||||
@@ -144,30 +186,29 @@ public class CodeFrame {
|
||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
private void setCause(Context ctx, EngineException err, EngineException cause) {
|
||||
err.setCause(cause);
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
|
||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||
|
||||
Instruction instr = null;
|
||||
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
|
||||
|
||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||
try {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||
|
||||
var instr = function.body[codePtr];
|
||||
if (instr == null) returnValue = null;
|
||||
else {
|
||||
// System.out.println(instr + "@" + instr.location);
|
||||
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
|
||||
|
||||
if (codePtr < 0 || codePtr >= function.body.length) returnValue = null;
|
||||
else {
|
||||
if (instr.location != null) prevLoc = instr.location;
|
||||
|
||||
try {
|
||||
this.jumpFlag = false;
|
||||
this.jumpFlag = this.popTryFlag = false;
|
||||
returnValue = Runners.exec(ctx, instr, this);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
|
||||
error = e.add(ctx, function.name, prevLoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,113 +217,85 @@ public class CodeFrame {
|
||||
|
||||
while (!tryStack.empty()) {
|
||||
var tryCtx = tryStack.peek();
|
||||
var newState = -1;
|
||||
TryCtx newCtx = null;
|
||||
|
||||
switch (tryCtx.state) {
|
||||
case TryCtx.STATE_TRY:
|
||||
if (error != null) {
|
||||
if (tryCtx.hasCatch) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_CATCH;
|
||||
}
|
||||
else if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
break;
|
||||
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
||||
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.retVal = returnValue;
|
||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
|
||||
}
|
||||
break;
|
||||
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
|
||||
}
|
||||
else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN;
|
||||
else if (!this.popTryFlag) newCtx = tryCtx;
|
||||
|
||||
if (tryCtx.hasFinally) {
|
||||
if (jumpFlag) tryCtx.jumpPtr = codePtr;
|
||||
else tryCtx.jumpPtr = tryCtx.end;
|
||||
newState = TryCtx.STATE_FINALLY_JUMPED;
|
||||
}
|
||||
else codePtr = tryCtx.end;
|
||||
break;
|
||||
case TryCtx.STATE_CATCH:
|
||||
if (error != null) {
|
||||
setCause(ctx, error, tryCtx.err);
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.retVal = returnValue;
|
||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (codePtr >= tryCtx.catchStart && codePtr < tryCtx.finallyStart) return Runners.NO_RETURN;
|
||||
|
||||
if (tryCtx.hasFinally) {
|
||||
if (jumpFlag) tryCtx.jumpPtr = codePtr;
|
||||
else tryCtx.jumpPtr = tryCtx.end;
|
||||
newState = TryCtx.STATE_FINALLY_JUMPED;
|
||||
}
|
||||
else codePtr = tryCtx.end;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_THREW:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
|
||||
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_RETURNED:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
if (returnValue == Runners.NO_RETURN) {
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
|
||||
else return Runners.NO_RETURN;
|
||||
}
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_JUMPED:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
||||
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
|
||||
else codePtr = tryCtx.end;
|
||||
}
|
||||
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
|
||||
if (newCtx != null) {
|
||||
if (newCtx != tryCtx) {
|
||||
switch (newCtx.state) {
|
||||
case CATCH:
|
||||
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
break;
|
||||
case FINALLY:
|
||||
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
codePtr = tryCtx.finallyStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
default:
|
||||
}
|
||||
|
||||
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (newState == -1) {
|
||||
var err = tryCtx.err;
|
||||
tryStack.pop();
|
||||
if (!tryStack.isEmpty()) tryStack.peek().err = err;
|
||||
tryStack.push(newCtx);
|
||||
}
|
||||
error = null;
|
||||
returnValue = Runners.NO_RETURN;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
popTryFlag = false;
|
||||
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
|
||||
codePtr = tryCtx.finallyStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
tryStack.pop();
|
||||
tryStack.push(tryCtx._finally(null));
|
||||
break;
|
||||
}
|
||||
else {
|
||||
tryStack.pop();
|
||||
codePtr = tryCtx.end;
|
||||
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
|
||||
if (tryCtx.result.isJump) {
|
||||
codePtr = tryCtx.result.ptr;
|
||||
jumpFlag = true;
|
||||
}
|
||||
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
||||
if (tryCtx.result.isThrow) {
|
||||
error = tryCtx.result.error;
|
||||
}
|
||||
if (error != null) error.setCause(tryCtx.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
tryCtx.state = newState;
|
||||
switch (newState) {
|
||||
case TryCtx.STATE_CATCH:
|
||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true);
|
||||
break;
|
||||
default:
|
||||
codePtr = tryCtx.finallyStart;
|
||||
}
|
||||
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false);
|
||||
var caught = false;
|
||||
|
||||
for (var frame : ctx.frames()) {
|
||||
for (var tryCtx : frame.tryStack) {
|
||||
if (tryCtx.state == TryState.TRY) caught = true;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.engine.onInstruction(ctx, this, instr, null, error, caught);
|
||||
throw error;
|
||||
}
|
||||
if (returnValue != Runners.NO_RETURN) {
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
|
||||
ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,50 +97,37 @@ public class Runners {
|
||||
obj.defineProperty(ctx, "value", el);
|
||||
frame.push(ctx, obj);
|
||||
}
|
||||
// var arr = new ObjectValue();
|
||||
|
||||
// var members = Values.getMembers(ctx, val, false, false);
|
||||
// Collections.reverse(members);
|
||||
// for (var el : members) {
|
||||
// if (el instanceof Symbol) continue;
|
||||
// arr.defineProperty(ctx, i++, el);
|
||||
// }
|
||||
|
||||
// arr.defineProperty(ctx, "length", i);
|
||||
|
||||
// frame.push(ctx, arr);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.addTry(instr.get(0), instr.get(1), instr.get(2));
|
||||
public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int start = frame.codePtr + 1;
|
||||
int catchStart = (int)instr.get(0);
|
||||
int finallyStart = (int)instr.get(1);
|
||||
if (finallyStart >= 0) finallyStart += start;
|
||||
if (catchStart >= 0) catchStart += start;
|
||||
int end = (int)instr.get(2) + start;
|
||||
frame.addTry(start, end, catchStart, finallyStart);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.popTryFlag = true;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int offset = instr.get(0), count = instr.get(1);
|
||||
int count = instr.get(0);
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
frame.push(ctx, frame.peek(offset + count - 1));
|
||||
frame.push(ctx, frame.peek(count - 1));
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execMove(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int offset = instr.get(0), count = instr.get(1);
|
||||
|
||||
var tmp = frame.take(offset);
|
||||
var res = frame.take(count);
|
||||
|
||||
for (var i = 0; i < offset; i++) frame.push(ctx, tmp[i]);
|
||||
for (var i = 0; i < count; i++) frame.push(ctx, res[i]);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, null);
|
||||
frame.codePtr++;
|
||||
@@ -179,16 +166,13 @@ public class Runners {
|
||||
}
|
||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
long id = (Long)instr.get(0);
|
||||
int localsN = (Integer)instr.get(1);
|
||||
int len = (Integer)instr.get(2);
|
||||
var captures = new ValueVariable[instr.params.length - 3];
|
||||
var captures = new ValueVariable[instr.params.length - 1];
|
||||
|
||||
for (var i = 3; i < instr.params.length; i++) {
|
||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
||||
for (var i = 1; i < instr.params.length; i++) {
|
||||
captures[i - 1] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var body = Engine.functions.get(id);
|
||||
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
|
||||
var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
|
||||
|
||||
frame.push(ctx, func);
|
||||
|
||||
@@ -306,7 +290,6 @@ public class Runners {
|
||||
var val = frame.pop();
|
||||
|
||||
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
||||
frame.push(ctx, true);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -330,10 +313,10 @@ public class Runners {
|
||||
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
||||
case CALL: return execCall(ctx, instr, frame);
|
||||
case CALL_NEW: return execCallNew(ctx, instr, frame);
|
||||
case TRY: return execTry(ctx, instr, frame);
|
||||
case TRY_START: return execTryStart(ctx, instr, frame);
|
||||
case TRY_END: return execTryEnd(ctx, instr, frame);
|
||||
|
||||
case DUP: return execDup(ctx, instr, frame);
|
||||
case MOVE: return execMove(ctx, instr, frame);
|
||||
case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
|
||||
case LOAD_VAR: return execLoadVar(ctx, instr, frame);
|
||||
case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
|
||||
|
||||
@@ -12,9 +12,6 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
public class GlobalScope implements ScopeRecord {
|
||||
public final ObjectValue obj;
|
||||
|
||||
@Override
|
||||
public GlobalScope parent() { return null; }
|
||||
|
||||
public boolean has(Context ctx, String name) {
|
||||
return obj.hasMember(ctx, name, false);
|
||||
}
|
||||
@@ -28,7 +25,7 @@ public class GlobalScope implements ScopeRecord {
|
||||
return new GlobalScope(obj);
|
||||
}
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this);
|
||||
return new LocalScopeRecord();
|
||||
}
|
||||
|
||||
public Object define(String name) {
|
||||
|
||||
@@ -2,11 +2,8 @@ package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
public final GlobalScope global;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
@@ -18,11 +15,8 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalScopeRecord parent() { return parent; }
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this, global);
|
||||
return new LocalScopeRecord(this);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
@@ -62,12 +56,6 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
|
||||
return name;
|
||||
}
|
||||
public boolean has(Context ctx, String name) {
|
||||
return
|
||||
global.has(ctx, name) ||
|
||||
locals.contains(name) ||
|
||||
parent != null && parent.has(ctx, name);
|
||||
}
|
||||
public Object define(String name, boolean force) {
|
||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
@@ -80,12 +68,10 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord(GlobalScope global) {
|
||||
public LocalScopeRecord() {
|
||||
this.parent = null;
|
||||
this.global = global;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
|
||||
public LocalScopeRecord(LocalScopeRecord parent) {
|
||||
this.parent = parent;
|
||||
this.global = global;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,5 @@ package me.topchetoeu.jscript.engine.scope;
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public ScopeRecord parent();
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
public final int localsN;
|
||||
public final int length;
|
||||
public final Instruction[] body;
|
||||
public final String[] captureNames, localNames;
|
||||
public final ValueVariable[] captures;
|
||||
@@ -35,7 +33,7 @@ public class CodeFunction extends FunctionValue {
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var frame = new CodeFrame(ctx, thisArg, args, this);
|
||||
try {
|
||||
StackData.pushFrame(ctx, frame);
|
||||
ctx.pushFrame(frame);
|
||||
|
||||
while (true) {
|
||||
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
@@ -43,18 +41,17 @@ public class CodeFunction extends FunctionValue {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
StackData.popFrame(ctx, frame);
|
||||
ctx.popFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) {
|
||||
super(name, length);
|
||||
public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
|
||||
super(name, body.argsN);
|
||||
this.captures = captures;
|
||||
this.captureNames = body.captureNames;
|
||||
this.localNames = body.localNames;
|
||||
this.environment = environment;
|
||||
this.localsN = localsN;
|
||||
this.length = length;
|
||||
this.localsN = body.localsN;
|
||||
this.body = body.instructions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "function(...) { ...}";
|
||||
return String.format("function %s(...)", name);
|
||||
}
|
||||
|
||||
public abstract Object call(Context ctx, Object thisArg, Object ...args);
|
||||
@@ -21,21 +21,21 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (key.equals("name")) return name;
|
||||
if (key.equals("length")) return length;
|
||||
if ("name".equals(key)) return name;
|
||||
if ("length".equals(key)) return length;
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (key.equals("name")) name = Values.toString(ctx, val);
|
||||
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
|
||||
if ("name".equals(key)) name = Values.toString(ctx, val);
|
||||
else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
|
||||
else return super.setField(ctx, key, val);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (key.equals("name")) return true;
|
||||
if (key.equals("length")) return true;
|
||||
if ("name".equals(key)) return true;
|
||||
if ("length".equals(key)) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,19 @@ public class NativeWrapper extends ObjectValue {
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return wrapped.equals(obj);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return wrapped.hashCode();
|
||||
}
|
||||
|
||||
public NativeWrapper(Object wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
prototype = NATIVE_PROTO;
|
||||
|
||||
@@ -262,7 +262,7 @@ public class ObjectValue {
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
else if (key.equals("__proto__")) return setPrototype(ctx, val);
|
||||
else if ("__proto__".equals(key)) return setPrototype(ctx, val);
|
||||
else if (nonWritableSet.contains(key)) return false;
|
||||
else return setField(ctx, key, val);
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public class ObjectValue {
|
||||
public final boolean hasMember(Context ctx, Object key, boolean own) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (key != null && key.equals("__proto__")) return true;
|
||||
if (key != null && "__proto__".equals(key)) return true;
|
||||
if (hasField(ctx, key)) return true;
|
||||
if (properties.containsKey(key)) return true;
|
||||
if (own) return false;
|
||||
|
||||
@@ -22,6 +22,9 @@ public class ScopeValue extends ObjectValue {
|
||||
return true;
|
||||
}
|
||||
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null && proto.hasField(ctx, key) && proto.setField(ctx, key, val)) return true;
|
||||
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
|
||||
@@ -17,8 +17,27 @@ import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
import me.topchetoeu.jscript.lib.PromiseLib;
|
||||
|
||||
public class Values {
|
||||
public static enum CompareResult {
|
||||
NOT_EQUAL,
|
||||
EQUAL,
|
||||
LESS,
|
||||
GREATER;
|
||||
|
||||
public boolean less() { return this == LESS; }
|
||||
public boolean greater() { return this == GREATER; }
|
||||
public boolean lessOrEqual() { return this == LESS || this == EQUAL; }
|
||||
public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; }
|
||||
|
||||
public static CompareResult from(int cmp) {
|
||||
if (cmp < 0) return LESS;
|
||||
if (cmp > 0) return GREATER;
|
||||
return EQUAL;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Object NULL = new Object();
|
||||
|
||||
public static boolean isObject(Object val) { return val instanceof ObjectValue; }
|
||||
@@ -51,8 +70,7 @@ public class Values {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T wrapper(Object val, Class<T> clazz) {
|
||||
if (!isWrapper(val)) return null;
|
||||
|
||||
if (!isWrapper(val)) val = new NativeWrapper(val);
|
||||
var res = (NativeWrapper)val;
|
||||
if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped;
|
||||
else return null;
|
||||
@@ -105,7 +123,7 @@ public class Values {
|
||||
}
|
||||
public static boolean toBoolean(Object obj) {
|
||||
if (obj == NULL || obj == null) return false;
|
||||
if (obj instanceof Number && number(obj) == 0) return false;
|
||||
if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false;
|
||||
if (obj instanceof String && ((String)obj).equals("")) return false;
|
||||
if (obj instanceof Boolean) return (Boolean)obj;
|
||||
return true;
|
||||
@@ -137,7 +155,7 @@ public class Values {
|
||||
}
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return ((Symbol)val).toString();
|
||||
if (val instanceof Symbol) return val.toString();
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
@@ -191,12 +209,18 @@ public class Values {
|
||||
return _a >>> _b;
|
||||
}
|
||||
|
||||
public static int compare(Context ctx, Object a, Object b) {
|
||||
public static CompareResult compare(Context ctx, Object a, Object b) {
|
||||
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
|
||||
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
|
||||
|
||||
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b);
|
||||
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
|
||||
if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
|
||||
|
||||
var _a = toNumber(ctx, a);
|
||||
var _b = toNumber(ctx, b);
|
||||
|
||||
if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL;
|
||||
|
||||
return CompareResult.from(Double.compare(_a, _b));
|
||||
}
|
||||
|
||||
public static boolean not(Object obj) {
|
||||
@@ -232,10 +256,10 @@ public class Values {
|
||||
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
|
||||
case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]);
|
||||
|
||||
case GREATER: return compare(ctx, args[0], args[1]) > 0;
|
||||
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0;
|
||||
case LESS: return compare(ctx, args[0], args[1]) < 0;
|
||||
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0;
|
||||
case GREATER: return compare(ctx, args[0], args[1]).greater();
|
||||
case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual();
|
||||
case LESS: return compare(ctx, args[0], args[1]).less();
|
||||
case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
|
||||
|
||||
case INVERSE: return bitwiseNot(ctx, args[0]);
|
||||
case NOT: return not(args[0]);
|
||||
@@ -272,15 +296,20 @@ public class Values {
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
|
||||
if (proto == null) return key.equals("__proto__") ? NULL : null;
|
||||
else if (key != null && key.equals("__proto__")) return proto;
|
||||
if (proto == null) return "__proto__".equals(key) ? NULL : null;
|
||||
else if (key != null && "__proto__".equals(key)) return proto;
|
||||
else return proto.getMember(ctx, key, obj);
|
||||
}
|
||||
public static Object getMemberPath(Context ctx, Object obj, Object ...path) {
|
||||
var res = obj;
|
||||
for (var key : path) res = getMember(ctx, res, key);
|
||||
return res;
|
||||
}
|
||||
public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
|
||||
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
|
||||
if (key.equals("__proto__")) return setPrototype(ctx, obj, val);
|
||||
if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
|
||||
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
@@ -290,7 +319,7 @@ public class Values {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
if (key.equals("__proto__")) return true;
|
||||
if ("__proto__".equals(key)) return true;
|
||||
if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
@@ -518,7 +547,7 @@ public class Values {
|
||||
throw new ConvertException(type(obj), clazz.getSimpleName());
|
||||
}
|
||||
|
||||
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
|
||||
public static Iterable<Object> fromJSIterator(Context ctx, Object obj) {
|
||||
return () -> {
|
||||
try {
|
||||
var symbol = ctx.environment().symbol("Symbol.iterator");
|
||||
@@ -571,7 +600,7 @@ public class Values {
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) {
|
||||
public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
@@ -592,10 +621,43 @@ public class Values {
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) {
|
||||
return fromJavaIterator(ctx, it.iterator());
|
||||
public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
|
||||
return toJSIterator(ctx, it.iterator());
|
||||
}
|
||||
|
||||
public static ObjectValue toJSAsyncIterator(Context ctx, Iterator<?> it) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||
else {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(_ctx, "value", it.next());
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static boolean isEmptyFunc(ObjectValue val) {
|
||||
if (!(val instanceof FunctionValue)) return false;
|
||||
if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false;
|
||||
var proto = val.values.get("prototype");
|
||||
if (!(proto instanceof ObjectValue)) return false;
|
||||
var protoObj = (ObjectValue)proto;
|
||||
if (protoObj.values.get("constructor") != val) return false;
|
||||
if (protoObj.values.size() + protoObj.properties.size() != 1) return false;
|
||||
return true;
|
||||
}
|
||||
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) {
|
||||
if (tab == 0 && val instanceof String) {
|
||||
System.out.print(val);
|
||||
@@ -610,10 +672,7 @@ public class Values {
|
||||
var printed = true;
|
||||
|
||||
if (val instanceof FunctionValue) {
|
||||
System.out.print("function ");
|
||||
var name = Values.getMember(ctx, val, "name");
|
||||
if (name != null) System.out.print(Values.toString(ctx, name));
|
||||
System.out.print("(...)");
|
||||
System.out.print(val.toString());
|
||||
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
|
||||
|
||||
if (loc != null) System.out.print(" @ " + loc);
|
||||
@@ -643,7 +702,7 @@ public class Values {
|
||||
passed.add(val);
|
||||
|
||||
var obj = (ObjectValue)val;
|
||||
if (obj.values.size() + obj.properties.size() == 0) {
|
||||
if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) {
|
||||
if (!printed) System.out.println("{}");
|
||||
}
|
||||
else {
|
||||
@@ -665,8 +724,9 @@ public class Values {
|
||||
for (int i = 0; i < tab; i++) System.out.print(" ");
|
||||
System.out.print("}");
|
||||
|
||||
passed.remove(val);
|
||||
}
|
||||
|
||||
passed.remove(val);
|
||||
}
|
||||
else if (val == null) System.out.print("undefined");
|
||||
else if (val == Values.NULL) System.out.print("null");
|
||||
@@ -681,7 +741,7 @@ public class Values {
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
var ee = ((EngineException)err);
|
||||
System.out.println(prefix + " " + ee.toString(new Context(ee.engine).pushEnv(ee.env)));
|
||||
System.out.println(prefix + " " + ee.toString(new Context(ee.engine, ee.env)));
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
|
||||
|
||||
@@ -12,19 +12,48 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
|
||||
public class EngineException extends RuntimeException {
|
||||
public static class StackElement {
|
||||
public final Location location;
|
||||
public final String function;
|
||||
public final Context ctx;
|
||||
|
||||
public boolean visible() {
|
||||
return ctx == null || ctx.environment() == null || ctx.environment().stackVisible;
|
||||
}
|
||||
public String toString() {
|
||||
var res = "";
|
||||
var loc = location;
|
||||
|
||||
if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc);
|
||||
|
||||
if (loc != null) res += "at " + loc.toString() + " ";
|
||||
if (function != null && !function.equals("")) res += "in " + function + " ";
|
||||
|
||||
return res.trim();
|
||||
}
|
||||
|
||||
public StackElement(Context ctx, Location location, String function) {
|
||||
if (function != null) function = function.trim();
|
||||
if (function.equals("")) function = null;
|
||||
|
||||
if (ctx == null) this.ctx = null;
|
||||
else this.ctx = new Context(ctx.engine, ctx.environment());
|
||||
this.location = location;
|
||||
this.function = function;
|
||||
}
|
||||
}
|
||||
|
||||
public final Object value;
|
||||
public EngineException cause;
|
||||
public Environment env = null;
|
||||
public Engine engine = null;
|
||||
public final List<String> stackTrace = new ArrayList<>();
|
||||
public final List<StackElement> stackTrace = new ArrayList<>();
|
||||
|
||||
public EngineException add(String name, Location location) {
|
||||
var res = "";
|
||||
|
||||
if (location != null) res += "at " + location.toString() + " ";
|
||||
if (name != null && !name.equals("")) res += "in " + name + " ";
|
||||
|
||||
this.stackTrace.add(res.trim());
|
||||
public EngineException add(Context ctx, String name, Location location) {
|
||||
var el = new StackElement(ctx, location, name);
|
||||
if (el.function == null && el.location == null) return this;
|
||||
setCtx(ctx.environment(), ctx.engine);
|
||||
stackTrace.add(el);
|
||||
return this;
|
||||
}
|
||||
public EngineException setCause(EngineException cause) {
|
||||
@@ -46,7 +75,7 @@ public class EngineException extends RuntimeException {
|
||||
ss.append("[Error while stringifying]\n");
|
||||
}
|
||||
for (var line : stackTrace) {
|
||||
ss.append(" ").append(line).append('\n');
|
||||
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
|
||||
}
|
||||
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
ss.deleteCharAt(ss.length() - 1);
|
||||
@@ -74,7 +103,7 @@ public class EngineException extends RuntimeException {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
|
||||
}
|
||||
public static EngineException ofSyntax(SyntaxException e) {
|
||||
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc);
|
||||
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc);
|
||||
}
|
||||
public static EngineException ofSyntax(String msg) {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
public enum EntryType {
|
||||
NONE,
|
||||
FILE,
|
||||
FOLDER,
|
||||
NONE("none"),
|
||||
FILE("file"),
|
||||
FOLDER("folder");
|
||||
|
||||
public final String name;
|
||||
|
||||
private EntryType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,37 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import me.topchetoeu.jscript.Buffer;
|
||||
|
||||
public interface File {
|
||||
int read() throws IOException, InterruptedException;
|
||||
boolean write(byte val) throws IOException, InterruptedException;
|
||||
long tell() throws IOException, InterruptedException;
|
||||
void seek(long offset, int pos) throws IOException, InterruptedException;
|
||||
void close() throws IOException, InterruptedException;
|
||||
Permissions perms();
|
||||
int read(byte[] buff);
|
||||
void write(byte[] buff);
|
||||
long seek(long offset, int pos);
|
||||
void close();
|
||||
|
||||
default String readToString() throws IOException, InterruptedException {
|
||||
seek(0, 2);
|
||||
long len = tell();
|
||||
default String readToString() {
|
||||
long len = seek(0, 2);
|
||||
if (len < 0) return null;
|
||||
|
||||
seek(0, 0);
|
||||
byte[] res = new byte[(int)len];
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
res[i] = (byte)read();
|
||||
}
|
||||
byte[] res = new byte[(int)len];
|
||||
len = read(res);
|
||||
|
||||
return new String(res);
|
||||
}
|
||||
default String readLine() {
|
||||
var res = new Buffer();
|
||||
var buff = new byte[1];
|
||||
|
||||
while (true) {
|
||||
if (read(buff) == 0) {
|
||||
if (res.length() == 0) return null;
|
||||
else break;
|
||||
}
|
||||
|
||||
if (buff[0] == '\n') break;
|
||||
|
||||
res.write(res.length(), buff);
|
||||
}
|
||||
return new String(res.data());
|
||||
}
|
||||
}
|
||||
11
src/me/topchetoeu/jscript/filesystem/FileStat.java
Normal file
11
src/me/topchetoeu/jscript/filesystem/FileStat.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
public class FileStat {
|
||||
public final Mode mode;
|
||||
public final EntryType type;
|
||||
|
||||
public FileStat(Mode mode, EntryType type) {
|
||||
this.mode = mode;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface Filesystem {
|
||||
File open(String path) throws IOException, InterruptedException;
|
||||
boolean mkdir(String path) throws IOException, InterruptedException;
|
||||
EntryType type(String path) throws IOException, InterruptedException;
|
||||
boolean rm(String path) throws IOException, InterruptedException;
|
||||
String normalize(String... path);
|
||||
File open(String path, Mode mode) throws FilesystemException;
|
||||
void create(String path, EntryType type) throws FilesystemException;
|
||||
FileStat stat(String path) throws FilesystemException;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class FilesystemException extends RuntimeException {
|
||||
public static enum FSCode {
|
||||
DOESNT_EXIST(0x1),
|
||||
NOT_FILE(0x2),
|
||||
NOT_FOLDER(0x3),
|
||||
NO_PERMISSIONS_R(0x4),
|
||||
NO_PERMISSIONS_RW(0x5),
|
||||
FOLDER_NOT_EMPTY(0x6),
|
||||
ALREADY_EXISTS(0x7),
|
||||
FOLDER_EXISTS(0x8),
|
||||
UNSUPPORTED_OPERATION(0x9);
|
||||
|
||||
public final int code;
|
||||
|
||||
private FSCode(int code) { this.code = code; }
|
||||
}
|
||||
|
||||
public static final String[] MESSAGES = {
|
||||
"How did we get here?",
|
||||
"The file or folder '%s' doesn't exist or is inaccessible.",
|
||||
"'%s' is not a file",
|
||||
"'%s' is not a folder",
|
||||
"No permissions to read '%s'",
|
||||
"No permissions to write '%s'",
|
||||
"Can't delete '%s', since it is a full folder.",
|
||||
"'%s' already exists.",
|
||||
"An unsupported operation was performed on the file '%s'."
|
||||
};
|
||||
|
||||
public final String message, filename;
|
||||
public final FSCode code;
|
||||
|
||||
public FilesystemException(String message, String filename, FSCode code) {
|
||||
super(code + ": " + String.format(message, filename));
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
this.filename = filename;
|
||||
}
|
||||
public FilesystemException(String filename, FSCode code) {
|
||||
super(code + ": " + String.format(MESSAGES[code.code], filename));
|
||||
this.message = MESSAGES[code.code];
|
||||
this.code = code;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public EngineException toEngineException() {
|
||||
var res = EngineException.ofError("IOError", getMessage());
|
||||
Values.setMember(null, res.value, "code", code);
|
||||
Values.setMember(null, res.value, "filename", filename.toString());
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InaccessibleFile implements File {
|
||||
public static final InaccessibleFile INSTANCE = new InaccessibleFile();
|
||||
|
||||
@Override
|
||||
public int read() throws IOException, InterruptedException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(byte val) throws IOException, InterruptedException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException, InterruptedException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException, InterruptedException { }
|
||||
|
||||
@Override
|
||||
public void close() throws IOException, InterruptedException { }
|
||||
|
||||
@Override
|
||||
public Permissions perms() {
|
||||
return Permissions.NONE;
|
||||
}
|
||||
|
||||
private InaccessibleFile() { }
|
||||
}
|
||||
73
src/me/topchetoeu/jscript/filesystem/ListFile.java
Normal file
73
src/me/topchetoeu/jscript/filesystem/ListFile.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class ListFile implements File {
|
||||
private Iterator<String> it;
|
||||
private String filename;
|
||||
private byte[] currFile;
|
||||
private long ptr = 0, start = 0, end = 0;
|
||||
|
||||
private void next() {
|
||||
if (it != null && it.hasNext()) {
|
||||
start = end;
|
||||
currFile = (it.next() + "\n").getBytes();
|
||||
end = start + currFile.length;
|
||||
}
|
||||
else {
|
||||
it = null;
|
||||
currFile = null;
|
||||
end = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
it = null;
|
||||
currFile = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buff) {
|
||||
if (ptr < start) return 0;
|
||||
if (it == null) return 0;
|
||||
|
||||
var i = 0;
|
||||
|
||||
while (i < buff.length) {
|
||||
while (i + ptr >= end) {
|
||||
next();
|
||||
if (it == null) return 0;
|
||||
}
|
||||
|
||||
int cpyN = Math.min(currFile.length, buff.length - i);
|
||||
System.arraycopy(currFile, (int)(ptr + i - start), buff, i, cpyN);
|
||||
|
||||
i += cpyN;
|
||||
}
|
||||
|
||||
ptr += i;
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long seek(long offset, int pos) {
|
||||
if (pos == 2) throw new FilesystemException(filename, FSCode.UNSUPPORTED_OPERATION);
|
||||
if (pos == 1) offset += ptr;
|
||||
return ptr = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buff) {
|
||||
throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
||||
}
|
||||
|
||||
public ListFile(String filename, Stream<String> stream) throws IOException {
|
||||
this.it = stream.iterator();
|
||||
this.filename = filename;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,62 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import me.topchetoeu.jscript.Buffer;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class MemoryFile implements File {
|
||||
private int ptr;
|
||||
private Permissions mode;
|
||||
public final byte[] data;
|
||||
private Mode mode;
|
||||
private Buffer data;
|
||||
private String filename;
|
||||
|
||||
public Buffer data() { return data; }
|
||||
|
||||
@Override
|
||||
public int read() throws IOException, InterruptedException {
|
||||
if (data == null || !mode.readable || ptr >= data.length) return -1;
|
||||
return data[ptr++];
|
||||
public int read(byte[] buff) {
|
||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
var res = data.read(ptr, buff);
|
||||
ptr += res;
|
||||
return res;
|
||||
}
|
||||
@Override
|
||||
public void write(byte[] buff) {
|
||||
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
||||
|
||||
data.write(ptr, buff);
|
||||
ptr += buff.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(byte val) throws IOException, InterruptedException {
|
||||
if (data == null || !mode.writable || ptr >= data.length) return false;
|
||||
data[ptr++] = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException, InterruptedException {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException, InterruptedException {
|
||||
if (data == null) return;
|
||||
public long seek(long offset, int pos) {
|
||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
|
||||
if (pos == 0) ptr = (int)offset;
|
||||
else if (pos == 1) ptr += (int)offset;
|
||||
else if (pos == 2) ptr = data.length - (int)offset;
|
||||
else if (pos == 2) ptr = data.length() - (int)offset;
|
||||
|
||||
if (ptr < 0) ptr = 0;
|
||||
if (ptr > data.length()) ptr = data.length();
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException, InterruptedException {
|
||||
mode = null;
|
||||
public void close() {
|
||||
mode = Mode.NONE;
|
||||
ptr = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permissions perms() {
|
||||
if (data == null) return Permissions.NONE;
|
||||
return mode;
|
||||
}
|
||||
|
||||
public MemoryFile(byte[] buff, Permissions mode) {
|
||||
public MemoryFile(String filename, Buffer buff, Mode mode) {
|
||||
this.filename = filename;
|
||||
this.data = buff;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
|
||||
var res = new StringBuilder();
|
||||
|
||||
for (var el : list) res.append(el.getName()).append('\n');
|
||||
|
||||
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
|
||||
}
|
||||
}
|
||||
|
||||
96
src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java
Normal file
96
src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import me.topchetoeu.jscript.Buffer;
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class MemoryFilesystem implements Filesystem {
|
||||
public final Mode mode;
|
||||
private HashMap<Path, Buffer> files = new HashMap<>();
|
||||
private HashSet<Path> folders = new HashSet<>();
|
||||
|
||||
private Path realPath(String path) {
|
||||
return Filename.normalize(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String normalize(String... path) {
|
||||
return Paths.normalize(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(String _path, EntryType type) {
|
||||
var path = realPath(_path);
|
||||
|
||||
switch (type) {
|
||||
case FILE:
|
||||
if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
|
||||
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
||||
if (folders.contains(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
||||
files.put(path, new Buffer());
|
||||
break;
|
||||
case FOLDER:
|
||||
if (!folders.contains(path.getParent())) throw new FilesystemException(_path, FSCode.DOESNT_EXIST);
|
||||
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
||||
folders.add(path);
|
||||
break;
|
||||
default:
|
||||
case NONE:
|
||||
if (!folders.remove(path) && files.remove(path) == null) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File open(String _path, Mode perms) {
|
||||
var path = realPath(_path);
|
||||
var pcount = path.getNameCount();
|
||||
|
||||
if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms);
|
||||
else if (folders.contains(path)) {
|
||||
var res = new StringBuilder();
|
||||
for (var folder : folders) {
|
||||
if (pcount + 1 != folder.getNameCount()) continue;
|
||||
if (!folder.startsWith(path)) continue;
|
||||
res.append(folder.toFile().getName()).append('\n');
|
||||
}
|
||||
for (var file : files.keySet()) {
|
||||
if (pcount + 1 != file.getNameCount()) continue;
|
||||
if (!file.startsWith(path)) continue;
|
||||
res.append(file.toFile().getName()).append('\n');
|
||||
}
|
||||
return new MemoryFile(path.toString(), new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
|
||||
}
|
||||
else throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileStat stat(String _path) {
|
||||
var path = realPath(_path);
|
||||
|
||||
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
|
||||
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
|
||||
else return new FileStat(Mode.NONE, EntryType.NONE);
|
||||
}
|
||||
|
||||
public MemoryFilesystem put(String path, byte[] data) {
|
||||
var _path = realPath(path);
|
||||
var _curr = "/";
|
||||
|
||||
for (var seg : _path) {
|
||||
create(_curr, EntryType.FOLDER);
|
||||
_curr += seg + "/";
|
||||
}
|
||||
|
||||
files.put(_path, new Buffer(data));
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemoryFilesystem(Mode mode) {
|
||||
this.mode = mode;
|
||||
folders.add(Path.of("/"));
|
||||
}
|
||||
}
|
||||
31
src/me/topchetoeu/jscript/filesystem/Mode.java
Normal file
31
src/me/topchetoeu/jscript/filesystem/Mode.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
public enum Mode {
|
||||
NONE("", false, false),
|
||||
READ("r", true, false),
|
||||
READ_WRITE("rw", true, true);
|
||||
|
||||
public final String name;
|
||||
public final boolean readable;
|
||||
public final boolean writable;
|
||||
|
||||
public Mode intersect(Mode other) {
|
||||
if (this == NONE || other == NONE) return NONE;
|
||||
if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE;
|
||||
return READ;
|
||||
}
|
||||
|
||||
private Mode(String mode, boolean r, boolean w) {
|
||||
this.name = mode;
|
||||
this.readable = r;
|
||||
this.writable = w;
|
||||
}
|
||||
|
||||
public static Mode parse(String mode) {
|
||||
switch (mode) {
|
||||
case "r": return READ;
|
||||
case "rw": return READ_WRITE;
|
||||
default: return NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/me/topchetoeu/jscript/filesystem/Paths.java
Normal file
52
src/me/topchetoeu/jscript/filesystem/Paths.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Paths {
|
||||
public static String normalize(String... path) {
|
||||
var parts = String.join("/", path).split("[\\\\/]");
|
||||
var res = new ArrayList<String>();
|
||||
|
||||
for (var part : parts) {
|
||||
if (part.equals("...")) res.clear();
|
||||
else if (part.equals("..")) {
|
||||
if (res.size() > 0) res.remove(res.size() - 1);
|
||||
}
|
||||
else if (!part.equals(".") && !part.isEmpty()) res.add(part);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (var el : res) sb.append("/").append(el);
|
||||
|
||||
if (sb.length() == 0) return "/";
|
||||
else return sb.toString();
|
||||
}
|
||||
|
||||
public static String chroot(String root, String path) {
|
||||
return normalize(root) + normalize(path);
|
||||
}
|
||||
|
||||
public static String cwd(String cwd, String path) {
|
||||
return normalize(cwd + "/" + path);
|
||||
}
|
||||
|
||||
public static String filename(String path) {
|
||||
var i = path.lastIndexOf('/');
|
||||
if (i < 0) i = path.lastIndexOf('\\');
|
||||
|
||||
if (i < 0) return path;
|
||||
else return path.substring(i + 1);
|
||||
}
|
||||
|
||||
public static String extension(String path) {
|
||||
var i = path.lastIndexOf('.');
|
||||
|
||||
if (i < 0) return "";
|
||||
else return path.substring(i + 1);
|
||||
}
|
||||
|
||||
public static String dir(String path) {
|
||||
return normalize(path + "/..");
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
public enum Permissions {
|
||||
NONE("", false, false),
|
||||
READ("r", true, false),
|
||||
READ_WRITE("rw", true, true);
|
||||
|
||||
public final String readMode;
|
||||
public final boolean readable;
|
||||
public final boolean writable;
|
||||
|
||||
private Permissions(String mode, boolean r, boolean w) {
|
||||
this.readMode = mode;
|
||||
this.readable = r;
|
||||
this.writable = w;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,57 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class PhysicalFile implements File {
|
||||
private String filename;
|
||||
private RandomAccessFile file;
|
||||
private Permissions perms;
|
||||
private Mode mode;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException, InterruptedException {
|
||||
if (file == null || !perms.readable) return -1;
|
||||
else return file.read();
|
||||
public int read(byte[] buff) {
|
||||
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
else try { return file.read(buff); }
|
||||
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
||||
}
|
||||
@Override
|
||||
public void write(byte[] buff) {
|
||||
if (file == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
||||
else try { file.write(buff); }
|
||||
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(byte val) throws IOException, InterruptedException {
|
||||
if (file == null || !perms.writable) return false;
|
||||
file.write(val);
|
||||
return true;
|
||||
public long seek(long offset, int pos) {
|
||||
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
|
||||
try {
|
||||
if (pos == 1) offset += file.getFilePointer();
|
||||
else if (pos == 2) offset += file.length();
|
||||
file.seek(offset);
|
||||
return offset;
|
||||
}
|
||||
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException, InterruptedException {
|
||||
if (file == null) return 0;
|
||||
return file.getFilePointer();
|
||||
}
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException, InterruptedException {
|
||||
public void close() {
|
||||
if (file == null) return;
|
||||
if (pos == 0) file.seek(pos);
|
||||
else if (pos == 1) file.seek(file.getFilePointer() + pos);
|
||||
else if (pos == 2) file.seek(file.length() + pos);
|
||||
try { file.close(); }
|
||||
catch (IOException e) {} // SHUT
|
||||
file = null;
|
||||
mode = Mode.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException, InterruptedException {
|
||||
if (file == null) return;
|
||||
file.close();
|
||||
}
|
||||
public PhysicalFile(String name, String path, Mode mode) throws FileNotFoundException {
|
||||
this.filename = name;
|
||||
this.mode = mode;
|
||||
|
||||
@Override
|
||||
public Permissions perms() { return perms; }
|
||||
|
||||
public PhysicalFile(String path, Permissions mode) throws IOException {
|
||||
if (mode == Permissions.NONE) file = null;
|
||||
else file = new RandomAccessFile(path, mode.readMode);
|
||||
|
||||
perms = mode;
|
||||
if (mode == Mode.NONE) file = null;
|
||||
else try { file = new RandomAccessFile(path, mode.name); }
|
||||
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,88 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class PhysicalFilesystem implements Filesystem {
|
||||
public final Path root;
|
||||
public final String root;
|
||||
|
||||
private Permissions getPerms(Path path) {
|
||||
var file = path.toFile();
|
||||
if (!path.startsWith(root)) return Permissions.NONE;
|
||||
if (file.canRead() && file.canWrite()) return Permissions.READ_WRITE;
|
||||
if (file.canRead()) return Permissions.READ;
|
||||
private void checkMode(Path path, Mode mode) {
|
||||
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
|
||||
|
||||
return Permissions.NONE;
|
||||
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
|
||||
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
|
||||
}
|
||||
private Path getPath(String name) {
|
||||
return root.resolve(name);
|
||||
|
||||
private Path realPath(String path) {
|
||||
return Path.of(Paths.chroot(root, path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File open(String path) throws IOException, InterruptedException {
|
||||
var _path = root.resolve(path);
|
||||
|
||||
var perms = getPerms(_path);
|
||||
if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE;
|
||||
|
||||
var f = _path.toFile();
|
||||
|
||||
if (f.isDirectory()) {
|
||||
var res = new StringBuilder();
|
||||
|
||||
for (var child : f.listFiles()) res.append(child.toString()).append('\n');
|
||||
|
||||
return new MemoryFile(res.toString().getBytes(), Permissions.READ);
|
||||
}
|
||||
else return new PhysicalFile(path, perms);
|
||||
public String normalize(String... paths) {
|
||||
return Paths.normalize(paths);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String path) throws IOException, InterruptedException {
|
||||
var _path = getPath(path);
|
||||
var perms = getPerms(_path);
|
||||
var f = _path.toFile();
|
||||
public File open(String _path, Mode perms) {
|
||||
_path = normalize(_path);
|
||||
var path = realPath(_path);
|
||||
|
||||
if (!perms.writable) return false;
|
||||
else return f.mkdir();
|
||||
checkMode(path, perms);
|
||||
|
||||
try {
|
||||
if (Files.isDirectory(path)) return new ListFile(_path, Files.list(path).map((v -> v.getFileName().toString())));
|
||||
else return new PhysicalFile(_path, path.toString(), perms);
|
||||
}
|
||||
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntryType type(String path) throws IOException, InterruptedException {
|
||||
var _path = getPath(path);
|
||||
var perms = getPerms(_path);
|
||||
var f = _path.toFile();
|
||||
public void create(String _path, EntryType type) {
|
||||
var path = realPath(_path);
|
||||
|
||||
if (perms == Permissions.NONE) return EntryType.NONE;
|
||||
else if (f.isFile()) return EntryType.FILE;
|
||||
else return EntryType.FOLDER;
|
||||
if (type == EntryType.NONE != Files.exists(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case FILE:
|
||||
Files.createFile(path);
|
||||
break;
|
||||
case FOLDER:
|
||||
Files.createDirectories(path);
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
Files.delete(path);
|
||||
}
|
||||
}
|
||||
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rm(String path) throws IOException, InterruptedException {
|
||||
var _path = getPath(path);
|
||||
var perms = getPerms(_path);
|
||||
var f = _path.toFile();
|
||||
public FileStat stat(String _path) {
|
||||
var path = realPath(_path);
|
||||
|
||||
if (!perms.writable) return false;
|
||||
else return f.delete();
|
||||
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
|
||||
|
||||
var perms = Mode.NONE;
|
||||
|
||||
if (Files.isReadable(path)) {
|
||||
if (Files.isWritable(path)) perms = Mode.READ_WRITE;
|
||||
else perms = Mode.READ;
|
||||
}
|
||||
|
||||
public PhysicalFilesystem(Path root) {
|
||||
this.root = root;
|
||||
if (perms == Mode.NONE) return new FileStat(Mode.NONE, EntryType.NONE);
|
||||
|
||||
return new FileStat(
|
||||
perms,
|
||||
Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
|
||||
);
|
||||
}
|
||||
|
||||
public PhysicalFilesystem(String root) {
|
||||
this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
68
src/me/topchetoeu/jscript/filesystem/RootFilesystem.java
Normal file
68
src/me/topchetoeu/jscript/filesystem/RootFilesystem.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||
|
||||
public class RootFilesystem implements Filesystem {
|
||||
public final Map<String, Filesystem> protocols = new HashMap<>();
|
||||
public final PermissionsProvider perms;
|
||||
|
||||
private boolean canRead(String _path) {
|
||||
return perms.hasPermission("jscript.file.read:" + _path, '/');
|
||||
}
|
||||
private boolean canWrite(String _path) {
|
||||
return perms.hasPermission("jscript.file.write:" + _path, '/');
|
||||
}
|
||||
|
||||
private void modeAllowed(String _path, Mode mode) throws FilesystemException {
|
||||
if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R);
|
||||
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
|
||||
}
|
||||
|
||||
@Override public String normalize(String... paths) {
|
||||
if (paths.length == 0) return "file://";
|
||||
else {
|
||||
var filename = Filename.parse(paths[0]);
|
||||
var protocol = protocols.get(filename.protocol);
|
||||
paths[0] = filename.path;
|
||||
|
||||
|
||||
if (protocol == null) return Paths.normalize(paths);
|
||||
else return filename.protocol + "://" + protocol.normalize(paths);
|
||||
}
|
||||
}
|
||||
@Override public File open(String path, Mode perms) throws FilesystemException {
|
||||
var filename = Filename.parse(path);
|
||||
var protocol = protocols.get(filename.protocol);
|
||||
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
||||
modeAllowed(filename.toString(), perms);
|
||||
|
||||
try { return protocol.open(filename.path, perms); }
|
||||
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
||||
}
|
||||
@Override public void create(String path, EntryType type) throws FilesystemException {
|
||||
var filename = Filename.parse(path);
|
||||
var protocol = protocols.get(filename.protocol);
|
||||
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
||||
modeAllowed(filename.toString(), Mode.READ_WRITE);
|
||||
|
||||
try { protocol.create(filename.path, type); }
|
||||
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
||||
}
|
||||
@Override public FileStat stat(String path) throws FilesystemException {
|
||||
var filename = Filename.parse(path);
|
||||
var protocol = protocols.get(filename.protocol);
|
||||
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
||||
|
||||
try { return protocol.stat(filename.path); }
|
||||
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
||||
}
|
||||
|
||||
public RootFilesystem(PermissionsProvider perms) {
|
||||
this.perms = perms;
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,12 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getName(Class<?> clazz) {
|
||||
var classNat = clazz.getAnnotation(Native.class);
|
||||
if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim();
|
||||
else return clazz.getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a prototype for the given class.
|
||||
* The returned object will have appropriate wrappers for all instance members.
|
||||
@@ -117,6 +123,8 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
|
||||
|
||||
for (var overload : clazz.getDeclaredMethods()) {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
||||
@@ -137,11 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
* @param clazz The class for which a constructor should be generated
|
||||
*/
|
||||
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
||||
var name = clazz.getName();
|
||||
var classNat = clazz.getAnnotation(Native.class);
|
||||
if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim();
|
||||
|
||||
FunctionValue func = new OverloadFunction(name);
|
||||
FunctionValue func = new OverloadFunction(getName(clazz));
|
||||
|
||||
for (var overload : clazz.getDeclaredConstructors()) {
|
||||
var nat = overload.getAnnotation(Native.class);
|
||||
@@ -161,7 +165,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
}
|
||||
|
||||
if (((OverloadFunction)func).overloads.size() == 0) {
|
||||
func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
|
||||
func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
|
||||
}
|
||||
|
||||
applyMethods(ctx, false, func, clazz);
|
||||
@@ -256,7 +260,25 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
constructors.put(clazz, value);
|
||||
}
|
||||
|
||||
private void initError() {
|
||||
var proto = new ObjectValue();
|
||||
proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> {
|
||||
if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage();
|
||||
else return null;
|
||||
}));
|
||||
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
|
||||
proto.defineProperty(null, "toString", new NativeFunction("toString", (ctx, thisArg, args) -> thisArg.toString()));
|
||||
|
||||
var constr = makeConstructor(null, Throwable.class);
|
||||
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||
constr.defineProperty(null, "prototype", proto, true, false, false);
|
||||
|
||||
setProto(Throwable.class, proto);
|
||||
setConstr(Throwable.class, constr);
|
||||
}
|
||||
|
||||
public NativeWrapperProvider(Environment env) {
|
||||
this.env = env;
|
||||
initError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
@@ -82,21 +83,28 @@ public class OverloadFunction extends FunctionValue {
|
||||
catch (InvocationTargetException e) {
|
||||
var loc = Location.INTERNAL;
|
||||
if (e.getTargetException() instanceof EngineException) {
|
||||
throw ((EngineException)e.getTargetException()).add(name, loc);
|
||||
throw ((EngineException)e.getTargetException()).add(ctx, name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof NullPointerException) {
|
||||
e.printStackTrace();
|
||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
|
||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||
throw new InterruptException();
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc);
|
||||
var target = e.getTargetException();
|
||||
var targetClass = target.getClass();
|
||||
var err = new NativeWrapper(e.getTargetException());
|
||||
|
||||
err.defineProperty(ctx, "message", target.getMessage());
|
||||
err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass));
|
||||
|
||||
throw new EngineException(err).add(ctx, name, loc);
|
||||
}
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL);
|
||||
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
87
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
87
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
@@ -1,87 +0,0 @@
|
||||
(function (_arguments) {
|
||||
var ts = _arguments[0];
|
||||
var src = '', lib = _arguments[2].concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0;
|
||||
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
|
||||
|
||||
var settings = {
|
||||
outDir: "/out",
|
||||
declarationDir: "/out",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [ ],
|
||||
module: ts.ModuleKind.None,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
downlevelIteration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
experimentalDecorators: true,
|
||||
strict: true,
|
||||
};
|
||||
|
||||
var reg = ts.createDocumentRegistry();
|
||||
var service = ts.createLanguageService({
|
||||
getCurrentDirectory: function() { return "/"; },
|
||||
getDefaultLibFileName: function() { return "/lib_.d.ts"; },
|
||||
getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
|
||||
getCompilationSettings: function () { return settings; },
|
||||
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
|
||||
|
||||
getScriptSnapshot: function(filename) {
|
||||
if (filename === "/lib.d.ts") return libSnapshot;
|
||||
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
|
||||
throw new Error("File '" + filename + "' doesn't exist.");
|
||||
},
|
||||
getScriptVersion: function (filename) {
|
||||
if (filename === "/lib.d.ts") return 0;
|
||||
else return version;
|
||||
},
|
||||
}, reg);
|
||||
|
||||
service.getEmitOutput('/lib.d.ts');
|
||||
log('Loaded libraries!');
|
||||
|
||||
function compile(code, filename) {
|
||||
src = code, version++;
|
||||
|
||||
var emit = service.getEmitOutput("/src.ts");
|
||||
|
||||
var diagnostics = []
|
||||
.concat(service.getCompilerOptionsDiagnostics())
|
||||
.concat(service.getSyntacticDiagnostics("/src.ts"))
|
||||
.concat(service.getSemanticDiagnostics("/src.ts"))
|
||||
.map(function (diagnostic) {
|
||||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
if (diagnostic.file) {
|
||||
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||
var file = diagnostic.file.fileName.substring(1);
|
||||
if (file === "src.ts") file = filename;
|
||||
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
|
||||
}
|
||||
else return "Error: " + message;
|
||||
});
|
||||
|
||||
if (diagnostics.length > 0) {
|
||||
throw new SyntaxError(diagnostics.join('\n'));
|
||||
}
|
||||
|
||||
return {
|
||||
result: emit.outputFiles[0].text,
|
||||
declaration: emit.outputFiles[1].text
|
||||
};
|
||||
}
|
||||
|
||||
_arguments[1].compile = function (filename, code) {
|
||||
var res = compile(filename, code);
|
||||
|
||||
return {
|
||||
source: res.result,
|
||||
runner: function(func) {
|
||||
return function() {
|
||||
var val = func.apply(this, arguments);
|
||||
decls += res.declaration;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})(arguments);
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user