Compare commits

...

120 Commits

Author SHA1 Message Date
d7f6010319 fix: make code java 11 compatible 2023-12-26 14:22:35 +02:00
87f8975275 build: minify typescript code 2023-12-26 14:22:25 +02:00
09eb6507dc Merge pull request #11 from TopchetoEU/TopchetoEU/modules
Module support
2023-12-26 14:20:55 +02:00
2f58f6b245 feat: implement basic module system 2023-12-26 14:12:41 +02:00
4bc363485f fix: losts of FS API improvements 2023-12-26 14:02:32 +02:00
8e01db637b fix: improve path resolutions and FS API 2023-12-26 11:27:40 +02:00
1c64912786 feat: create memory-light directory list file 2023-12-26 11:27:09 +02:00
28265a8f44 fix: some bug fixes and improvements with File interface 2023-12-26 11:26:08 +02:00
e9e020512e fix: environment pushed when it shouldn't be 2023-12-24 15:17:38 +02:00
4b0bbf5190 fix: correctly convert virtual to real path 2023-12-24 14:40:27 +02:00
031f78ebf1 fix: don't include ts's code in repo anymore 2023-12-24 14:32:37 +02:00
562f1f9425 fix: remove major mistakes in README 2023-12-24 14:31:50 +02:00
82a09e8865 fix: some config files cleanup 2023-12-24 14:31:33 +02:00
90da2db1fb fix: error now is not null-prototyped 2023-12-24 14:30:48 +02:00
3d275c52c0 refactor: fix code to not result in compile warning 2023-12-24 14:30:31 +02:00
797585f539 feat: implement some stdlib functions 2023-12-24 14:28:12 +02:00
7a301eba8f fix: some stupid mistakes in FS 2023-12-24 14:27:27 +02:00
1b2068a274 fix: never-ending try-catch issues 2023-12-24 14:27:00 +02:00
078d7ed95f refactor: improve Engine API 2023-12-24 14:26:42 +02:00
93973c12b1 fix: change default return type to void for generators 2023-12-24 14:10:03 +02:00
cad4f34b51 fix: use correct env declarations in bootstrap.js 2023-12-24 14:08:20 +02:00
d3571d6ee2 fix: make .gitignore more restrictive 2023-12-22 10:46:30 +02:00
caf9131cde refactor: replace all CRLF with LF 2023-12-22 10:45:04 +02:00
8c6379eb24 Merge pull request #10 from TopchetoEU/TopchetoEU/mapping
Add support for source mappings
2023-12-18 22:42:38 +02:00
380a5c720a feat: make environment hidable from stack trace 2023-12-18 22:38:26 +02:00
76c3d377af feat: use mappings in stack traces 2023-12-18 22:30:14 +02:00
42f443572a fix: how the hell did i fix this (it was the cache all along) 2023-12-18 22:11:08 +02:00
773bc72f3e refactor: rename compileWithDebug to compile 2023-12-14 21:07:16 +02:00
0b5178e9fd fix: return minified typescript 2023-12-14 21:06:23 +02:00
8cffcff7db refactor: remove unused instructions 2023-12-14 17:02:24 +02:00
60bbaaccd4 fix: some issues with try-catch 2023-12-14 16:49:51 +02:00
60b1762462 refactor: remove printf 2023-12-14 16:49:35 +02:00
34434965d2 cant be fucked to split this one up 2023-12-14 12:39:01 +02:00
fe86123f0f refactor: improve function statement 2023-11-29 13:04:41 +02:00
d5e6edfa8b refactor: replace .locate with argument 2023-11-29 13:03:53 +02:00
73345062ca feat: implement new API with source maps 2023-11-28 21:40:37 +02:00
124341969c fix: simplify source map API 2023-11-28 21:39:25 +02:00
8defd93855 fix: use proper name for native constructors 2023-11-28 21:37:46 +02:00
6c57e0e9f2 fix: properly handle wrapper function 2023-11-28 21:37:13 +02:00
f1932914ee fix: move debugger assets to correct location 2023-11-27 20:28:02 +02:00
977701e601 feat: implement source maps 2023-11-26 16:50:07 +02:00
e8a7ac8da8 refactor: reorganize assets 2023-11-26 16:49:47 +02:00
6b1cb852c2 fix: arrays wrongly stringified in JSONLib 2023-11-26 13:51:56 +02:00
b59a003086 feat: implement VLQ parsing 2023-11-26 13:51:43 +02:00
1902e41f61 feat: make typescript output mappings 2023-11-26 11:52:47 +02:00
27162ef8ac feat: improve Bufffer API 2023-11-26 11:51:32 +02:00
4f22e76d2b fix: make code java 11 compatible 2023-11-25 20:22:16 +02:00
8924e7aadc build: fix build script to exit with code when error occurs 2023-11-25 20:16:06 +02:00
1d0e31a423 Merge pull request #9 from TopchetoEU/TopchetoEU/perms-and-fs
Permissions and filesystems
2023-11-25 20:10:59 +02:00
ab56908171 fix: faulty insufficient permissions error 2023-11-25 20:09:59 +02:00
eb14bb080c fix: debugger breakpoints not registered 2023-11-25 20:09:47 +02:00
f52f47cdb4 feat: properly implement filesystems 2023-11-25 19:36:18 +02:00
567eaa8514 fix: incorrect declarations 2023-11-25 19:13:20 +02:00
2cfdd8e335 feat: add utf8 encoding and decoding 2023-11-25 19:12:56 +02:00
4b1ec671e2 fix: micro tasks not handled properly 2023-11-25 18:58:35 +02:00
b127aadcf6 fix: promise was sending macro tasks instead of micro 2023-11-25 18:58:24 +02:00
b6eaff65ca style: remove unnececeary import 2023-11-25 18:48:46 +02:00
443dc0ffa1 feat: add await function to promise 2023-11-25 18:47:51 +02:00
e107dd3507 fix: promise doesn't use microtasks in some cases 2023-11-25 18:47:36 +02:00
6af3c70fce feat: add filesystem libs 2023-11-25 18:47:01 +02:00
8b743f49d1 feat: add toString, equals and hashCode overrides to wrappers and objects 2023-11-25 18:46:13 +02:00
e1ce384815 feat: add parse function to Filename 2023-11-25 18:44:27 +02:00
86d205e521 fix: parsing files won't modify last instruction to RET, instead will just append it 2023-11-25 18:44:11 +02:00
f0ad936e5b refactor: use new iterable names 2023-11-25 18:43:43 +02:00
4a1473c5be fix: sorting with no comparator now works 2023-11-25 18:42:54 +02:00
4111dbf5c4 fix: functions now print their name when stringified 2023-11-25 18:41:47 +02:00
1666682dc2 refactor: get rid of data parent hierarchy 2023-11-25 18:41:18 +02:00
f2b33d0233 feat: add support for async iterators
fix: comparasions had small behavioural issue
2023-11-25 18:40:49 +02:00
f5a0b6eaf7 fix: some improvements of compiled bytecode 2023-11-25 18:38:43 +02:00
829bea755d fix: CodeFrame didnt't handle out of bounds jumps well 2023-11-25 18:38:16 +02:00
4b0dcffd13 feat: add fs declarations in lib.d.ts 2023-11-25 18:37:40 +02:00
987f8b8f00 fix: handle native errors properly-ish 2023-11-25 14:18:46 +02:00
55e3d46bc2 feat: implement memory and root fs 2023-11-25 14:18:04 +02:00
3e25068219 feat: implement bulk of fs 2023-11-25 14:17:37 +02:00
7ecb8bfabb feat: implement permissions 2023-11-25 14:16:02 +02:00
488deea164 fix: improve performance of typescript by caching separate declarations 2023-11-14 09:24:39 +02:00
ed08041335 fix: internal error when trying to use key of "undefined" 2023-11-14 09:24:00 +02:00
0a4149ba81 fix: remove double space in "Uncaught ..." 2023-11-14 09:23:15 +02:00
30f5d619c3 fix: errors now have the right prototype and name 2023-11-14 09:22:56 +02:00
e7dbe91374 refactor: clean up protocol.json 2023-11-13 20:06:07 +02:00
455f5a613e feat: implement simple permission matching 2023-11-13 19:09:33 +02:00
1eeac3ae97 fix: replace templates in Metadata class with placeholder data 2023-11-13 19:08:56 +02:00
1acd78e119 refactor: clean up Main class 2023-11-13 18:56:59 +02:00
df9932874d feat: remove unnececeary @NativeInit directives 2023-11-06 14:03:15 +02:00
b47d1a7576 refactor: remove StackData and Data usage 2023-11-06 13:53:36 +02:00
fdfa8d7713 refactor: some minor fixes, rewrite README example 2023-11-06 10:55:38 +02:00
f5d1287948 fix: some annoying bugs, as well as splice 2023-11-06 00:55:30 +02:00
15f4278cb1 fix: build with java 17 2023-11-05 21:00:39 +02:00
df8465cb49 Merge pull request #8 from TopchetoEU/TopchetoEU/tests
Integrate typescript
2023-11-05 20:32:42 +02:00
e3104c223c fix: remove some unnececeary logs 2023-11-05 20:29:21 +02:00
1d0bae3de8 feat: include typescript code in source code 2023-11-05 20:27:23 +02:00
b66acd3089 fix: several more fixes 2023-11-05 19:44:44 +02:00
e326847287 feat: send value stack to debug client 2023-11-05 19:44:35 +02:00
26591d6631 fix: lazy operators incorrectly pop values from stack 2023-11-05 19:44:08 +02:00
af31b1ab79 fix: several small fixes 2023-11-05 19:43:53 +02:00
f885d4349f refactor: fix some bad code >:( 2023-11-05 19:43:28 +02:00
d57044acb7 fix: several bug fixes to help with typescript support 2023-11-05 12:44:29 +02:00
7df4e3b03f fix: oops 2023-11-04 11:43:57 +02:00
ed1009ab69 refactor: some code restructuring in the debugging 2023-11-04 11:42:06 +02:00
f856cdf37e fix: messages larger than 64KB are now fragmented properly 2023-11-04 11:41:31 +02:00
4f82574b8c fix: various small behavioural issues
fix: pesky try-catch logic
2023-11-04 11:40:50 +02:00
0ae24148d8 feat: write some tests 2023-11-04 11:38:48 +02:00
ac128d17f4 feat: implement Array.reduce
fix: native functions are now named
2023-11-04 11:38:29 +02:00
6508f15bb0 refactor: remove typescript source code from repo (for now) 2023-11-04 11:37:13 +02:00
69f93b4f87 refactor: make filenames more consistent 2023-11-04 11:36:36 +02:00
b675411925 fix: a lot of minor bugs 2023-10-29 23:47:48 +02:00
d1e93c2088 Merge branch 'master' of https://github.com/TopchetoEU/java-jscript 2023-10-29 12:33:18 +02:00
942db54546 feat: improve vscode debugging compatibility 2023-10-29 12:30:43 +02:00
d20df66982 feat: improve vscode debugging compatibility 2023-10-29 12:25:33 +02:00
16a9e5d761 Merge pull request #7 from TopchetoEU/TopcehtoEU/debugging
Debugging support
2023-10-28 17:11:55 +03:00
dc9d84a370 chore: nothing of use 2023-10-28 17:10:27 +03:00
edb71daef4 feat: fully implement local and capture scope object wrappers
feat: implement object sending and receiving
2023-10-28 16:58:33 +03:00
4b84309df6 feat: complete steptrough and breakpoints in debugger 2023-10-27 15:12:14 +03:00
d2d9fa9738 fix: exit now works 2023-10-07 14:08:47 +03:00
a4e5f7f471 refactor: clean up useless catch blocks 2023-10-07 13:55:44 +03:00
cc044374ba refactor: replace InterruptedException with unchecked alternative 2023-10-07 13:42:55 +03:00
517e3e6657 refactor: clean up context and stack data 2023-10-07 13:07:07 +03:00
926b9c17d8 feat: readd JSON to lib 2023-10-06 18:42:35 +03:00
fc705e7383 feat: readd date internals 2023-10-06 12:03:33 +03:00
a17ec737b7 refactor: rename polyfills to libs 2023-10-06 11:57:29 +03:00
193 changed files with 13645 additions and 8872 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* -text

View File

@@ -11,6 +11,11 @@ jobs:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '11'
- name: Clone repository - name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main uses: GuillaumeFalourd/clone-github-repo-action@main
with: with:

27
.gitignore vendored
View File

@@ -1,11 +1,18 @@
.vscode *
.gradle
.ignore !/src
/out !/src/**/*
/build
/bin /src/assets/js/ts.js
/dst
/*.js !/tests
!/tests/**/*
!/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/build.js !/build.js
/dead-code !/LICENSE
/Metadata.java !/README.md

View File

@@ -2,33 +2,28 @@
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript** **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 ## 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 ```java
var engine = new PolyfillEngine(new File(".")); var engine = new Engine(false);
var in = new BufferedReader(new InputStreamReader(System.in)); // Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
engine.start(); var env = Internals.apply(new Environment());
while (true) { // Queue code to load internal libraries and start engine
try { var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
var raw = in.readLine(); // 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(); // Get our result
Values.printValue(engine.context(), res); System.out.println(awaitable.await());
System.out.println();
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(engine.context()));
}
catch (InterruptedException _e) { return; }
}
catch (IOException | InterruptedException e) { return; }
}
``` ```
## 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.

131
build.js
View File

@@ -1,17 +1,9 @@
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const fs = require('fs/promises'); const fs = require('fs/promises');
const pt = require('path'); 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) { async function* find(src, dst, wildcard) {
const stat = await fs.stat(src); const stat = await fs.stat(src);
@@ -36,9 +28,9 @@ async function copy(src, dst, wildcard) {
await Promise.all(promises); await Promise.all(promises);
} }
function run(cmd, ...args) { function run(suppressOutput, cmd, ...args) {
return new Promise((res, rej) => { 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 => { proc.once('exit', code => {
if (code === 0) res(code); if (code === 0) res(code);
else rej(new Error(`Process ${cmd} exited with code ${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 { try {
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString() await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
.replace('${VERSION}', conf.version) .replace('${VERSION}', conf.version)
@@ -57,8 +126,10 @@ async function compileJava() {
if (argv[2] === 'debug') args.push('-g'); if (argv[2] === 'debug') args.push('-g');
args.push('-d', 'dst/classes', 'Metadata.java'); 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); 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 { finally {
await fs.rm('Metadata.java'); await fs.rm('Metadata.java');
@@ -67,13 +138,35 @@ async function compileJava() {
(async () => { (async () => {
try { try {
try { await fs.rm('dst', { recursive: true }); } catch {} if (argv[2] === 'init-ts') {
await copy('src', 'dst/classes', v => !v.endsWith('.java')); await downloadTypescript('src/assets/js/ts.js');
await compileJava(); }
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.'); 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 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) { catch (e) {
if (argv[2] === 'debug') throw e; if (argv[2] === 'debug') throw e;
else console.log(e.toString()); console.log(e.toString());
exit(-1);
} }
})(); })();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JScript Debugger</title>
</head>
<body>
<p>
This is the debugger of JScript. It implement the <a href="https://chromedevtools.github.io/devtools-protocol/1-2/">V8 Debugging protocol</a>,
so you can use the devtools in chrome. <br>
The debugger is still in early development, so please report any issues to
<a href="https://github.com/TopchetoEU/java-jscript/issues">the github repo</a>.
</p>
<p>
Here are the available entrypoints:
<ul>
<li><a href="json/version">/json/version</a> - version and other stuff about the JScript engine</li>
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
<li>/(any target) - websocket entrypoints for debugging</li>
</ul>
</p>
<p>
Running ${NAME} v${VERSION} by ${AUTHOR}
</p>
</body>
</html>

File diff suppressed because it is too large Load Diff

113
src/assets/js/bootstrap.js vendored Normal file
View 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]);

View File

@@ -19,12 +19,7 @@ type Extract<T, U> = T extends U ? T : never;
type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT } type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT }
type ReplaceFunc = (match: string, ...args: any[]) => string; type ReplaceFunc = (match: string, ...args: any[]) => string;
type PromiseFulfillFunc<T> = (val: T) => void; type PromiseResult<T> = { type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; }
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 // wippidy-wine, this code is now mine :D
type Awaited<T> = type Awaited<T> =
@@ -46,8 +41,7 @@ type IteratorReturnResult<TReturn> =
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>; type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Thenable<T> { interface Thenable<T> {
then<NextT>(onFulfilled: PromiseThenFunc<T, NextT>, onRejected?: PromiseRejectFunc): Promise<Awaited<NextT>>; then<NextT = void>(onFulfilled?: (val: T) => NextT, onRejected?: (err: any) => NextT): Promise<Awaited<NextT>>;
then(onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise<T>;
} }
interface RegExpResultIndices extends Array<[number, number]> { interface RegExpResultIndices extends Array<[number, number]> {
@@ -111,7 +105,7 @@ interface AsyncIterableIterator<T> extends AsyncIterator<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<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>; [Symbol.iterator](): Generator<T, TReturn, TNext>;
return(value: TReturn): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>;
@@ -124,7 +118,7 @@ interface GeneratorFunction {
readonly prototype: Generator; 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>>; return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw(e: any): Promise<IteratorResult<T, TReturn>>; throw(e: any): Promise<IteratorResult<T, TReturn>>;
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>; [Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
@@ -464,14 +458,19 @@ interface SymbolConstructor {
readonly asyncIterator: unique symbol; readonly asyncIterator: unique symbol;
} }
interface Promise<T> extends Thenable<T> { 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>; 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>; 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>>; resolve<T>(val: T): Promise<Awaited<T>>;
reject(val: any): Promise<never>; 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]>>}]>; 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; declare var String: StringConstructor;
//@ts-ignore //@ts-ignore
declare const arguments: IArguments; declare const arguments: IArguments;
@@ -498,6 +530,7 @@ declare var parseInt: typeof Number.parseInt;
declare var parseFloat: typeof Number.parseFloat; declare var parseFloat: typeof Number.parseFloat;
declare function log(...vals: any[]): void; declare function log(...vals: any[]): void;
declare function require(name: string): any;
declare var Array: ArrayConstructor; declare var Array: ArrayConstructor;
declare var Boolean: BooleanConstructor; declare var Boolean: BooleanConstructor;
@@ -508,11 +541,14 @@ declare var Object: ObjectConstructor;
declare var Symbol: SymbolConstructor; declare var Symbol: SymbolConstructor;
declare var Promise: PromiseConstructor; declare var Promise: PromiseConstructor;
declare var Math: MathObject; declare var Math: MathObject;
declare var Encoding: Encoding;
declare var Filesystem: Filesystem;
declare var Error: ErrorConstructor; declare var Error: ErrorConstructor;
declare var RangeError: RangeErrorConstructor; declare var RangeError: RangeErrorConstructor;
declare var TypeError: TypeErrorConstructor; declare var TypeError: TypeErrorConstructor;
declare var SyntaxError: SyntaxErrorConstructor; declare var SyntaxError: SyntaxErrorConstructor;
declare var self: typeof globalThis;
declare class Map<KeyT, ValueT> { declare class Map<KeyT, ValueT> {
public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>; public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>;

View 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;
}
}

View File

@@ -0,0 +1,65 @@
package me.topchetoeu.jscript;
import java.io.File;
import java.nio.file.Path;
public class Filename {
public final String protocol;
public final String path;
public String toString() {
return protocol + "://" + path;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
var other = (Filename) obj;
if (protocol == null) {
if (other.protocol != null) return false;
}
else if (!protocol.equals(other.protocol)) return false;
if (path == null) {
if (other.path != null) return false;
}
else if (!path.equals(other.path)) return false;
return true;
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
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());
}
}

View File

@@ -1,18 +1,18 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
public class Location { public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, "<internal>"); public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
private int line; private int line;
private int start; private int start;
private String filename; private Filename filename;
public int line() { return line; } public int line() { return line; }
public int start() { return start; } public int start() { return start; }
public String filename() { return filename; } public Filename filename() { return filename; }
@Override @Override
public String toString() { public String toString() {
return filename + ":" + line + ":" + start; return filename.toString() + ":" + line + ":" + start;
} }
public Location add(int n, boolean clone) { public Location add(int n, boolean clone) {
@@ -55,9 +55,39 @@ public class Location {
return true; return true;
} }
public Location(int line, int start, String filename) { @Override
public int compareTo(Location other) {
int a = filename.toString().compareTo(other.filename.toString());
int b = Integer.compare(line, other.line);
int c = Integer.compare(start, other.start);
if (a != 0) return a;
if (b != 0) return b;
return c;
}
public Location(int line, int start, Filename filename) {
this.line = line; this.line = line;
this.start = start; this.start = start;
this.filename = filename; 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))
);
}
} }

View File

@@ -1,127 +1,168 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.net.InetSocketAddress;
import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.polyfills.Internals; 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 { public class Main {
static Thread task; public static class Printer implements Observer<Object> {
static Engine engine;
static Environment env;
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();
}
catch (IOException e) {
return null;
}
}
public static String resourceToString(String name) {
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
if (str == null) return null;
return streamToString(str);
}
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) { public void next(Object data) {
try { Values.printValue(null, data); } Values.printValue(null, data);
catch (InterruptedException e) { }
System.out.println(); System.out.println();
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
try { Values.printError(err, null); } Values.printError(err, null);
catch (InterruptedException ex) { return; }
} }
};
public static void main(String args[]) { public void finish() {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); engineTask.interrupt();
var in = new BufferedReader(new InputStreamReader(System.in)); }
engine = new Engine(); }
env = new Environment(null, null, null); static Thread engineTask, debugTask;
var exited = new boolean[1]; static Engine engine = new Engine(true);
static DebugServer debugServer = new DebugServer();
static Environment environment = new Environment(null, null, null);
engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> { static int j = 0;
new Internals().apply(env); static boolean exited = false;
static String[] args;
env.global.define("exit", _ctx -> { private static void reader() {
exited[0] = true; try {
task.interrupt(); for (var arg : args) {
throw new InterruptedException();
});
env.global.define("go", _ctx -> {
try { try {
var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js")))); if (arg.equals("--ts")) initTypescript();
return func.call(_ctx); else {
} var file = Path.of(arg);
catch (IOException e) { var raw = Files.readString(file);
throw new EngineException("Couldn't open do.js"); var res = engine.pushMsg(
} false, environment,
}); Filename.fromFile(file.toFile()),
raw, null
return null; ).await();
}), null); Values.printValue(null, res);
System.out.println();
task = engine.start();
var reader = new Thread(() -> {
try {
while (true) {
try {
var raw = in.readLine();
if (raw == null) break;
engine.pushMsg(false, env.context(new Message(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter);
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(null));
}
catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]");
}
} }
} }
catch (EngineException e) { Values.printError(e, null); }
} }
catch (IOException e) { for (var i = 0; ; i++) {
e.printStackTrace(); try {
return; var raw = Reading.read();
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 (SyntaxException ex) { }
if (exited[0]) return; catch (IOException e) {
System.out.println("Syntax error:" + ex.msg); System.out.println(e.toString());
} exited = true;
catch (RuntimeException ex) { }
if (exited[0]) return; catch (RuntimeException ex) {
if (!exited) {
System.out.println("Internal error ocurred:"); System.out.println("Internal error ocurred:");
ex.printStackTrace(); ex.printStackTrace();
} }
catch (InterruptedException e) { return; } }
if (exited[0]) return; if (exited) {
}); debugTask.interrupt();
engineTask.interrupt();
}
}
private static void initEnv() {
environment = Internals.apply(environment);
environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
exited = true;
throw new InterruptException();
}));
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)));
return func.call(_ctx);
}
catch (IOException e) {
throw new EngineException("Couldn't open do.js");
}
}));
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 = 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, tsEnv,
new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null
).await();
System.out.println("Loaded typescript!");
engine.pushMsg(
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)");
}
}
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();
reader.setDaemon(true); reader.setDaemon(true);
reader.setName("STD Reader"); reader.setName("STD Reader");
reader.start(); reader.start();

View File

@@ -1,7 +1,20 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
public class Metadata { public class Metadata {
public static final String VERSION = "${VERSION}"; private static final String VERSION = "${VERSION}";
public static final String AUTHOR = "${AUTHOR}"; private static final String AUTHOR = "${AUTHOR}";
public static final String NAME = "${NAME}"; 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;
}
} }

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import me.topchetoeu.jscript.exceptions.UncheckedException;
public class Reading {
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static synchronized String read() throws IOException {
return reader.readLine();
}
public static String streamToString(InputStream in) {
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) {
return streamToString(resourceToStream(name));
}
}

View 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;
}
}

View File

@@ -1,11 +1,20 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.Vector; 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 class CompileTarget {
public final Vector<Instruction> target = new Vector<>(); public final Vector<Instruction> target = new Vector<>();
public final Map<Long, Instruction[]> functions; public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints;
private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
public Instruction add(Instruction instr) { public Instruction add(Instruction instr) {
target.add(instr); target.add(instr);
@@ -14,14 +23,44 @@ public class CompileTarget {
public Instruction set(int i, Instruction instr) { public Instruction set(int i, Instruction instr) {
return target.set(i, instr); return target.set(i, instr);
} }
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);
}
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) { public Instruction get(int i) {
return target.get(i); return target.get(i);
} }
public int size() { return target.size(); } 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 Instruction[] array() { return target.toArray(Instruction[]::new); }
public CompileTarget(Map<Long, Instruction[]> functions) { 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.functions = functions;
this.breakpoints = breakpoints;
} }
} }

View File

@@ -1,66 +1,64 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.Vector; import java.util.Vector;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.control.ContinueStatement; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.compilation.values.FunctionStatement; import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement { public class CompoundStatement extends Statement {
public final Statement[] statements; 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 @Override
public void declare(ScopeRecord varsScope) { public void declare(ScopeRecord varsScope) {
for (var stm : statements) { for (var stm : statements) stm.declare(varsScope);
stm.declare(varsScope);
}
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
for (var stm : statements) { List<Statement> statements = new Vector<Statement>();
if (stm instanceof FunctionStatement) { if (separateFuncs) for (var stm : this.statements) {
int start = target.size(); if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
((FunctionStatement)stm).compile(target, scope, null, true); stm.compile(target, scope, false);
target.get(start).setDebug(true);
target.add(Instruction.discard());
} }
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++) { if (!polluted && pollute) {
var stm = statements[i]; target.add(Instruction.loadValue(loc(), null));
if (stm instanceof FunctionStatement) continue;
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
else stm.compileWithDebug(target, scope, pollute);
} }
} }
@Override public CompoundStatement setEnd(Location loc) {
public Statement optimize() { this.end = loc;
var res = new Vector<Statement>(statements.length); return this;
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(Location loc, Statement ...statements) { public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
super(loc); super(loc);
this.separateFuncs = separateFuncs;
this.statements = statements; this.statements = statements;
} }
} }

View File

@@ -0,0 +1,29 @@
package me.topchetoeu.jscript.compilation;
public class FunctionBody {
public final Instruction[] instructions;
public final String[] captureNames, localNames;
public final int localsN, argsN;
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(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];
}
}

View File

@@ -10,7 +10,8 @@ public class Instruction {
THROW, THROW,
THROW_SYNTAX, THROW_SYNTAX,
DELETE, DELETE,
TRY, TRY_START,
TRY_END,
NOP, NOP,
CALL, CALL,
@@ -33,7 +34,6 @@ public class Instruction {
LOAD_REGEX, LOAD_REGEX,
DUP, DUP,
MOVE,
STORE_VAR, STORE_VAR,
STORE_MEMBER, STORE_MEMBER,
@@ -45,61 +45,35 @@ public class Instruction {
TYPEOF, TYPEOF,
OPERATION; OPERATION;
// TYPEOF, }
// INSTANCEOF(true), public static enum BreakpointType {
// IN(true), NONE,
STEP_OVER,
STEP_IN;
// MULTIPLY(true), public boolean shouldStepIn() {
// DIVIDE(true), return this != NONE;
// MODULO(true), }
// ADD(true), public boolean shouldStepOver() {
// SUBTRACT(true), return this == STEP_OVER;
}
// 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 final Type type; public final Type type;
public final Object[] params; public final Object[] params;
public Location location; public Location location;
public boolean debugged; 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) { public Instruction locate(Location loc) {
this.location = loc; this.location = loc;
return this; return this;
} }
public Instruction setDebug(boolean debug) {
debugged = debug;
return this;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T get(int i) { public <T> T get(int i) {
@@ -134,34 +108,32 @@ public class Instruction {
this.params = params; this.params = params;
} }
public static Instruction tryInstr(int n, int catchN, int finallyN) { public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
return new Instruction(null, Type.TRY, n, catchN, finallyN); return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
} }
public static Instruction throwInstr() { public static Instruction tryEnd(Location loc) {
return new Instruction(null, Type.THROW); return new Instruction(loc, Type.TRY_END);
} }
public static Instruction throwSyntax(SyntaxException err) { public static Instruction throwInstr(Location loc) {
return new Instruction(null, Type.THROW_SYNTAX, err.getMessage()); return new Instruction(loc, Type.THROW);
} }
public static Instruction delete() { public static Instruction throwSyntax(Location loc, SyntaxException err) {
return new Instruction(null, Type.DELETE); return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage());
} }
public static Instruction ret() { public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(null, Type.RETURN); return new Instruction(loc, Type.THROW_SYNTAX, err);
} }
public static Instruction debug() { public static Instruction delete(Location loc) {
return new Instruction(null, Type.NOP, "debug"); return new Instruction(loc, Type.DELETE);
} }
public static Instruction debugVarNames(String[] names) { public static Instruction ret(Location loc) {
var args = new Object[names.length + 1]; return new Instruction(loc, Type.RETURN);
args[0] = "dbg_vars"; }
public static Instruction debug(Location loc) {
System.arraycopy(names, 0, args, 1, names.length); return new Instruction(loc, Type.NOP, "debug");
return new Instruction(null, Type.NOP, args);
} }
public static Instruction nop(Object ...params) { public static Instruction nop(Location loc, Object ...params) {
for (var param : params) { for (var param : params) {
if (param instanceof String) continue; if (param instanceof String) continue;
if (param instanceof Boolean) continue; if (param instanceof Boolean) continue;
@@ -171,109 +143,104 @@ public class Instruction {
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls."); 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) { public static Instruction call(Location loc, int argn) {
return new Instruction(null, Type.CALL, argn); return new Instruction(loc, Type.CALL, argn);
} }
public static Instruction callNew(int argn) { public static Instruction callNew(Location loc, int argn) {
return new Instruction(null, Type.CALL_NEW, argn); return new Instruction(loc, Type.CALL_NEW, argn);
} }
public static Instruction jmp(int offset) { public static Instruction jmp(Location loc, int offset) {
return new Instruction(null, Type.JMP, offset); return new Instruction(loc, Type.JMP, offset);
} }
public static Instruction jmpIf(int offset) { public static Instruction jmpIf(Location loc, int offset) {
return new Instruction(null, Type.JMP_IF, offset); return new Instruction(loc, Type.JMP_IF, offset);
} }
public static Instruction jmpIfNot(int offset) { public static Instruction jmpIfNot(Location loc, int offset) {
return new Instruction(null, Type.JMP_IFN, offset); return new Instruction(loc, Type.JMP_IFN, offset);
} }
public static Instruction loadValue(Object val) { public static Instruction loadValue(Location loc, Object val) {
return new Instruction(null, Type.LOAD_VALUE, val); return new Instruction(loc, Type.LOAD_VALUE, val);
} }
public static Instruction makeVar(String name) { public static Instruction makeVar(Location loc, String name) {
return new Instruction(null, Type.MAKE_VAR, name); return new Instruction(loc, Type.MAKE_VAR, name);
} }
public static Instruction loadVar(Object i) { public static Instruction loadVar(Location loc, Object i) {
return new Instruction(null, Type.LOAD_VAR, i); return new Instruction(loc, Type.LOAD_VAR, i);
} }
public static Instruction loadGlob() { public static Instruction loadGlob(Location loc) {
return new Instruction(null, Type.LOAD_GLOB); return new Instruction(loc, Type.LOAD_GLOB);
} }
public static Instruction loadMember() { public static Instruction loadMember(Location loc) {
return new Instruction(null, Type.LOAD_MEMBER); 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(); 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) { public static Instruction loadRegex(Location loc, String pattern, String flags) {
return new Instruction(null, Type.LOAD_REGEX, pattern, flags); return new Instruction(loc, Type.LOAD_REGEX, pattern, flags);
} }
public static Instruction loadFunc(long id, int varN, int len, int[] captures) { public static Instruction loadFunc(Location loc, long id, int[] captures) {
var args = new Object[3 + captures.length]; var args = new Object[1 + captures.length];
args[0] = id; args[0] = id;
args[1] = varN; for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
args[2] = len; return new Instruction(loc, Type.LOAD_FUNC, args);
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
return new Instruction(null, Type.LOAD_FUNC, args);
} }
public static Instruction loadObj() { public static Instruction loadObj(Location loc) {
return new Instruction(null, Type.LOAD_OBJ); return new Instruction(loc, Type.LOAD_OBJ);
} }
public static Instruction loadArr(int count) { public static Instruction loadArr(Location loc, int count) {
return new Instruction(null, Type.LOAD_ARR, count); return new Instruction(loc, Type.LOAD_ARR, count);
} }
public static Instruction dup() { public static Instruction dup(Location loc) {
return new Instruction(null, Type.DUP, 0, 1); return new Instruction(loc, Type.DUP, 1);
} }
public static Instruction dup(int count, int offset) { public static Instruction dup(Location loc, int count) {
return new Instruction(null, Type.DUP, offset, count); return new Instruction(loc, Type.DUP, count);
}
public static Instruction move(int count, int offset) {
return new Instruction(null, Type.MOVE, offset, count);
} }
public static Instruction storeSelfFunc(int i) { public static Instruction storeSelfFunc(Location loc, int i) {
return new Instruction(null, Type.STORE_SELF_FUNC, i); return new Instruction(loc, Type.STORE_SELF_FUNC, i);
} }
public static Instruction storeVar(Object i) { public static Instruction storeVar(Location loc, Object i) {
return new Instruction(null, Type.STORE_VAR, i, false); return new Instruction(loc, Type.STORE_VAR, i, false);
} }
public static Instruction storeVar(Object i, boolean keep) { public static Instruction storeVar(Location loc, Object i, boolean keep) {
return new Instruction(null, Type.STORE_VAR, i, keep); return new Instruction(loc, Type.STORE_VAR, i, keep);
} }
public static Instruction storeMember() { public static Instruction storeMember(Location loc) {
return new Instruction(null, Type.STORE_MEMBER, false); return new Instruction(loc, Type.STORE_MEMBER, false);
} }
public static Instruction storeMember(boolean keep) { public static Instruction storeMember(Location loc, boolean keep) {
return new Instruction(null, Type.STORE_MEMBER, keep); return new Instruction(loc, Type.STORE_MEMBER, keep);
} }
public static Instruction discard() { public static Instruction discard(Location loc) {
return new Instruction(null, Type.DISCARD); return new Instruction(loc, Type.DISCARD);
} }
public static Instruction typeof() { public static Instruction typeof(Location loc) {
return new Instruction(null, Type.TYPEOF); return new Instruction(loc, Type.TYPEOF);
} }
public static Instruction typeof(Object varName) { public static Instruction typeof(Location loc, Object varName) {
return new Instruction(null, Type.TYPEOF, varName); return new Instruction(loc, Type.TYPEOF, varName);
} }
public static Instruction keys() { public static Instruction keys(Location loc, boolean forInFormat) {
return new Instruction(null, Type.KEYS); return new Instruction(loc, Type.KEYS, forInFormat);
} }
public static Instruction defProp() { public static Instruction defProp(Location loc) {
return new Instruction(null, Type.DEF_PROP); return new Instruction(loc, Type.DEF_PROP);
} }
public static Instruction operation(Operation op) { public static Instruction operation(Location loc, Operation op) {
return new Instruction(null, Type.OPERATION, op); return new Instruction(loc, Type.OPERATION, op);
} }
@Override @Override

View File

@@ -1,20 +1,26 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public abstract class Statement { public abstract class Statement {
private Location _loc; private Location _loc;
public boolean pure() { return false; } public boolean pure() { return false; }
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute);
public void declare(ScopeRecord varsScope) { } 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(); int start = target.size();
compile(target, scope, pollute); compile(target, scope, pollute);
if (target.size() != start) target.get(start).setDebug(true);
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; } public Location loc() { return _loc; }

View File

@@ -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;
}
}

View File

@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement; import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -10,10 +11,12 @@ public class VariableDeclareStatement extends Statement {
public static class Pair { public static class Pair {
public final String name; public final String name;
public final Statement value; public final Statement value;
public final Location location;
public Pair(String name, Statement value) { public Pair(String name, Statement value, Location location) {
this.name = name; this.name = name;
this.value = value; this.value = value;
this.location = location;
} }
} }
@@ -30,19 +33,16 @@ public class VariableDeclareStatement extends Statement {
for (var entry : values) { for (var entry : values) {
if (entry.name == null) continue; if (entry.name == null) continue;
var key = scope.getKey(entry.name); var key = scope.getKey(entry.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
if (entry.value instanceof FunctionStatement) { if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
target.add(Instruction.storeVar(key).locate(loc())); if (entry.value != null) {
} FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
else if (entry.value != null) { target.add(Instruction.storeVar(entry.location, key));
entry.value.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(loc()));
} }
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public VariableDeclareStatement(Location loc, List<Pair> values) { public VariableDeclareStatement(Location loc, List<Pair> values) {

View File

@@ -11,8 +11,8 @@ public class BreakStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop("break", label).locate(loc())); target.add(Instruction.nop(loc(), "break", label));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public BreakStatement(Location loc, String label) { public BreakStatement(Location loc, String label) {

View File

@@ -11,8 +11,8 @@ public class ContinueStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop("cont", label).locate(loc())); target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public ContinueStatement(Location loc, String label) { public ContinueStatement(Location loc, String label) {

View File

@@ -9,8 +9,8 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DebugStatement extends Statement { public class DebugStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.debug().locate(loc())); target.add(Instruction.debug(loc()));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public DebugStatement(Location loc) { public DebugStatement(Location loc) {

View File

@@ -15,8 +15,8 @@ public class DeleteStatement extends Statement {
value.compile(target, scope, true); value.compile(target, scope, true);
key.compile(target, scope, true); key.compile(target, scope, true);
target.add(Instruction.delete().locate(loc())); target.add(Instruction.delete(loc()));
if (!pollute) target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), true));
} }
public DeleteStatement(Location loc, Statement key, Statement value) { public DeleteStatement(Location loc, Statement key, Statement value) {

View File

@@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; 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.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class DoWhileStatement extends Statement { public class DoWhileStatement extends Statement {
public final Statement condition, body; public final Statement condition, body;
@@ -20,54 +18,14 @@ public class DoWhileStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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(); int start = target.size();
body.compileWithDebug(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
condition.compile(target, scope, true); condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1); WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(start - end).locate(loc())); target.add(Instruction.jmpIf(loc(), start - end));
}
@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);
} }
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) { public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {

View File

@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -12,6 +13,7 @@ public class ForInStatement extends Statement {
public final boolean isDeclaration; public final boolean isDeclaration;
public final Statement varValue, object, body; public final Statement varValue, object, body;
public final String label; public final String label;
public final Location varLocation;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
@@ -22,52 +24,45 @@ public class ForInStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var key = scope.getKey(varName); var key = scope.getKey(varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
int first = target.size();
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
if (varValue != null) { if (varValue != null) {
varValue.compile(target, scope, true); varValue.compile(target, scope, true);
target.add(Instruction.storeVar(scope.getKey(varName))); target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
} }
object.compile(target, scope, true); object.compile(target, scope, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys()); target.add(Instruction.keys(loc(), true));
int start = target.size(); int start = target.size();
target.add(Instruction.dup()); target.add(Instruction.dup(loc()));
target.add(Instruction.loadMember("length")); target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.loadValue(0)); target.add(Instruction.operation(loc(), Operation.EQUALS));
target.add(Instruction.operation(Operation.LESS_EQUALS));
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(loc()));
target.add(Instruction.dup()); target.add(Instruction.loadMember(varLocation, "value"));
target.add(Instruction.dup()); target.add(Instruction.storeVar(object.loc(), key));
target.add(Instruction.loadMember("length")); target.setDebug(BreakpointType.STEP_OVER);
target.add(Instruction.loadValue(1));
target.add(Instruction.operation(Operation.SUBTRACT));
target.add(Instruction.dup(1, 2));
target.add(Instruction.loadValue("length"));
target.add(Instruction.dup(1, 2));
target.add(Instruction.storeMember());
target.add(Instruction.loadMember());
target.add(Instruction.storeVar(key));
for (var i = start; i < target.size(); i++) target.get(i).locate(loc()); body.compile(target, scope, false, BreakpointType.STEP_OVER);
body.compileWithDebug(target, scope, false);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(loc(), start - end));
target.add(Instruction.discard().locate(loc())); target.add(Instruction.discard(loc()));
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
target.get(first).locate(loc());
} }
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
super(loc); super(loc);
this.varLocation = varLocation;
this.label = label; this.label = label;
this.isDeclaration = isDecl; this.isDeclaration = isDecl;
this.varName = varName; this.varName = varName;

View File

@@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction; 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.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class ForStatement extends Statement { public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body; public final Statement declaration, assignment, condition, body;
@@ -20,58 +18,22 @@ public class ForStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
declaration.compile(target, scope, false); declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
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;
}
int start = target.size(); int start = target.size();
condition.compile(target, scope, true); condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compile(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size(); int beforeAssign = target.size();
assignment.compileWithDebug(target, scope, false); assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
}
@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);
} }
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) { 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.assignment = assignment;
this.body = body; 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
))
);
}
} }

View File

@@ -2,13 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; 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;
import me.topchetoeu.jscript.compilation.Statement; 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.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class IfStatement extends Statement { public class IfStatement extends Statement {
public final Statement condition, body, elseBody; public final Statement condition, body, elseBody;
@@ -19,53 +16,31 @@ public class IfStatement extends Statement {
if (elseBody != null) elseBody.declare(globScope); if (elseBody != null) elseBody.declare(globScope);
} }
@Override @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { condition.compile(target, scope, true, breakpoint);
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);
if (elseBody == null) { if (elseBody == null) {
int i = target.size(); int i = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compileWithDebug(target, scope, pollute); body.compile(target, scope, pollute, breakpoint);
int endI = target.size(); int endI = target.size();
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc())); target.set(i, Instruction.jmpIfNot(loc(), endI - i));
} }
else { else {
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compileWithDebug(target, scope, pollute); body.compile(target, scope, pollute, breakpoint);
target.add(Instruction.nop()); target.add(Instruction.nop(null));
int mid = target.size(); int mid = target.size();
elseBody.compileWithDebug(target, scope, pollute); elseBody.compile(target, scope, pollute, breakpoint);
int end = target.size(); int end = target.size();
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc())); target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc())); target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
} }
} }
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@Override compile(target, scope, pollute, BreakpointType.STEP_IN);
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);
} }
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) { public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {

View File

@@ -11,9 +11,9 @@ public class ReturnStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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); else value.compile(target, scope, true);
target.add(Instruction.ret().locate(loc())); target.add(Instruction.ret(loc()));
} }
public ReturnStatement(Location loc, Statement value) { public ReturnStatement(Location loc, Statement value) {

View File

@@ -6,6 +6,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -33,45 +34,47 @@ public class SwitchStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var caseMap = new HashMap<Integer, Integer>(); var caseToStatement = new HashMap<Integer, Integer>();
var stmIndexMap = 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) { for (var ccase : cases) {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup(loc()));
ccase.value.compile(target, scope, true); ccase.value.compile(target, scope, true);
target.add(Instruction.operation(Operation.EQUALS).locate(loc())); target.add(Instruction.operation(loc(), Operation.EQUALS));
caseMap.put(target.size(), ccase.statementI); caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop()); target.add(Instruction.nop(null));
} }
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
for (var stm : body) { for (var stm : body) {
stmIndexMap.put(stmIndexMap.size(), target.size()); statementToIndex.put(statementToIndex.size(), target.size());
stm.compileWithDebug(target, scope, false); stm.compile(target, scope, false, BreakpointType.STEP_OVER);
} }
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc())); int end = target.size();
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc()); 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); var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { 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()) { for (var el : caseToStatement.entrySet()) {
var loc = target.get(el.getKey()).location; var i = statementToIndex.get(el.getValue());
var i = stmIndexMap.get(el.getValue()); if (i == null) i = end;
if (i == null) i = target.size(); target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true));
} }
target.add(Instruction.discard().locate(loc()));
} }
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {

View File

@@ -12,7 +12,7 @@ public class ThrowStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.throwInstr().locate(loc())); target.add(Instruction.throwInstr(loc()));
} }
public ThrowStatement(Location loc, Statement value) { public ThrowStatement(Location loc, Statement value) {

View File

@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; 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.GlobalScope;
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord; import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -22,31 +23,32 @@ public class TryStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
target.add(Instruction.nop()); 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); tryBody.compile(target, scope, false);
tryN = target.size() - start; target.add(Instruction.tryEnd(loc()));
if (catchBody != null) { if (catchBody != null) {
int tmp = target.size(); catchStart = target.size() - start;
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope; var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
local.define(name, true); local.define(name, true);
catchBody.compile(target, scope, false); catchBody.compile(target, scope, false);
local.undefine(); local.undefine();
catchN = target.size() - tmp; target.add(Instruction.tryEnd(loc()));
} }
if (finallyBody != null) { if (finallyBody != null) {
int tmp = target.size(); finallyStart = target.size() - start;
finallyBody.compile(target, scope, false); 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())); target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); 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) { public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {

View File

@@ -3,13 +3,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.CompileTarget; 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;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type; 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.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class WhileStatement extends Statement { public class WhileStatement extends Statement {
public final Statement condition, body; public final Statement condition, body;
@@ -21,43 +18,19 @@ public class WhileStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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(); int start = target.size();
condition.compile(target, scope, true); condition.compile(target, scope, true);
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compile(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
replaceBreaks(target, label, mid + 1, end, start, end + 1); replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
}
@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);
} }
public WhileStatement(Location loc, String label, Statement condition, Statement body) { 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++) { for (int i = start; i < end; i++) {
var instr = target.get(i); var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { 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.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
target.get(i).location = instr.location;
} }
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { 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.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
target.get(i).location = instr.location;
} }
} }
} }
// 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
// ))
// );
// }
} }

View File

@@ -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.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
@@ -9,23 +9,29 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ArrayStatement extends Statement { public class ArrayStatement extends Statement {
public final Statement[] statements; public final Statement[] statements;
@Override @Override public boolean pure() {
public boolean pure() { return true; } for (var stm : statements) {
if (!stm.pure()) return false;
}
return true;
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadArr(statements.length).locate(loc())); target.add(Instruction.loadArr(loc(), statements.length));
var i = 0;
for (var el : statements) { for (var i = 0; i < statements.length; i++) {
var el = statements[i];
if (el != null) { if (el != null) {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(i).locate(loc())); target.add(Instruction.loadValue(loc(), i));
el.compile(target, scope, true); 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) { public ArrayStatement(Location loc, Statement[] statements) {

View File

@@ -4,35 +4,47 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CallStatement extends Statement { public class CallStatement extends Statement {
public final Statement func; public final Statement func;
public final Statement[] args; public final Statement[] args;
public final boolean isNew;
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
if (func instanceof IndexStatement) { if (isNew) func.compile(target, scope, true);
else if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, scope, true, true); ((IndexStatement)func).compile(target, scope, true, true);
} }
else { else {
target.add(Instruction.loadValue(null).locate(loc())); target.add(Instruction.loadValue(loc(), null));
func.compile(target, scope, true); func.compile(target, scope, true);
} }
for (var arg : args) arg.compile(target, scope, true); for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.call(args.length).locate(loc()).setDebug(true)); if (isNew) target.add(Instruction.callNew(loc(), args.length));
if (!pollute) target.add(Instruction.discard().locate(loc())); 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); super(loc);
this.isNew = isNew;
this.func = func; this.func = func;
this.args = args; 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); super(loc);
this.isNew = isNew;
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key)); this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
this.args = args; this.args = args;
} }

View File

@@ -15,8 +15,12 @@ public class ChangeStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix); 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(loc(), addAmount));
target.add(Instruction.operation(loc(), Operation.SUBTRACT));
}
} }
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

@@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ConstantStatement extends Statement { public class ConstantStatement extends Statement {
public final Object value; public final Object value;
@Override @Override public boolean pure() { return true; }
public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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) { public ConstantStatement(Location loc, Object val) {

View File

@@ -1,23 +1,20 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; 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; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DiscardStatement extends Statement { public class DiscardStatement extends Statement {
public final Statement value; public final Statement value;
@Override public boolean pure() { return value.pure(); }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false); value.compile(target, scope, false);
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
@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);
} }
public DiscardStatement(Location loc, Statement val) { public DiscardStatement(Location loc, Statement val) {

View File

@@ -5,25 +5,28 @@ import java.util.Random;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
public class FunctionStatement extends Statement { public class FunctionStatement extends Statement {
public final CompoundStatement body; public final CompoundStatement body;
public final String name; public final String varName;
public final String[] args; public final String[] args;
public final boolean statement;
public final Location end;
private static Random rand = new Random(); private static Random rand = new Random();
@Override @Override public boolean pure() { return varName == null && statement; }
public boolean pure() { return name == null; }
@Override @Override
public void declare(ScopeRecord scope) { 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) { public static void checkBreakAndCont(CompileTarget target, int start) {
@@ -39,74 +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 i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) { for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])){ if (args[i].equals(args[j])) {
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."))); throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
return;
} }
} }
} }
var subscope = scope.child();
int start = target.size(); var id = rand.nextLong();
var funcTarget = new CompileTarget(target.functions); var subscope = scope.child();
var subtarget = new CompileTarget(target.functions, target.breakpoints);
subscope.define("this"); subscope.define("this");
var argsVar = subscope.define("arguments"); var argsVar = subscope.define("arguments");
if (args.length > 0) { if (args.length > 0) {
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
funcTarget.add(Instruction.loadVar(argsVar).locate(loc())); subtarget.add(Instruction.loadVar(loc(), argsVar));
funcTarget.add(Instruction.loadMember(i).locate(loc())); subtarget.add(Instruction.loadMember(loc(), i));
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc())); subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
} }
} }
if (!isStatement && this.name != null) { if (!statement && this.varName != null) {
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name))); subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
subtarget.setDebug(bp);
} }
body.declare(subscope); body.declare(subscope);
funcTarget.add(Instruction.debugVarNames(subscope.locals())); body.compile(subtarget, subscope, false);
body.compile(funcTarget, subscope, false); subtarget.add(Instruction.ret(end));
funcTarget.add(Instruction.ret().locate(loc())); checkBreakAndCont(subtarget, 0);
checkBreakAndCont(funcTarget, start);
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())); return id;
target.functions.put(id, funcTarget.array());
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()));
}
if (this.name != null && isStatement) {
var key = scope.getKey(this.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
}
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, null, false);
if (!pollute) target.add(Instruction.discard().locate(loc()));
} }
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (this.varName != null) name = this.varName;
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));
}
}
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, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc); super(loc);
this.name = name;
this.end = end;
this.varName = varName;
this.statement = statement;
this.args = args; this.args = args;
this.body = body; 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);
}
} }

View File

@@ -7,12 +7,11 @@ import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class GlobalThisStatement extends Statement { public class GlobalThisStatement extends Statement {
@Override @Override public boolean pure() { return true; }
public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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) { public GlobalThisStatement(Location loc) {

View File

@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -18,20 +19,22 @@ public class IndexAssignStatement extends Statement {
if (operation != null) { if (operation != null) {
object.compile(target, scope, true); object.compile(target, scope, true);
index.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); 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()).setDebug(true)); target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(BreakpointType.STEP_IN);
} }
else { else {
object.compile(target, scope, true); object.compile(target, scope, true);
index.compile(target, scope, true); index.compile(target, scope, true);
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true)); target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(BreakpointType.STEP_IN);
} }
} }

View File

@@ -5,6 +5,7 @@ import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -12,26 +13,23 @@ public class IndexStatement extends AssignableStatement {
public final Statement object; public final Statement object;
public final Statement index; public final Statement index;
@Override
public boolean pure() { return true; }
@Override @Override
public Statement toAssign(Statement val, Operation operation) { public Statement toAssign(Statement val, Operation operation) {
return new IndexAssignStatement(loc(), object, index, val, operation); return new IndexAssignStatement(loc(), object, index, val, operation);
} }
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
int start = 0;
object.compile(target, scope, true); object.compile(target, scope, true);
if (dupObj) target.add(Instruction.dup().locate(loc())); if (dupObj) target.add(Instruction.dup(loc()));
if (index instanceof ConstantStatement) { if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
target.setDebug(BreakpointType.STEP_IN);
return; return;
} }
index.compile(target, scope, true); index.compile(target, scope, true);
target.add(Instruction.loadMember().locate(loc())); target.add(Instruction.loadMember(loc()));
target.get(start).setDebug(true); target.setDebug(BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {

View File

@@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
public class LazyAndStatement extends Statement { public class LazyAndStatement extends Statement {
public final Statement first, second; public final Statement first, second;
@Override @Override public boolean pure() { return first.pure() && second.pure(); }
public boolean pure() {
return first.pure() && second.pure();
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@@ -26,12 +23,12 @@ public class LazyAndStatement extends Statement {
} }
first.compile(target, scope, true); first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute); 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) { public LazyAndStatement(Location loc, Statement first, Statement second) {

View File

@@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
public class LazyOrStatement extends Statement { public class LazyOrStatement extends Statement {
public final Statement first, second; public final Statement first, second;
@Override @Override public boolean pure() { return first.pure() && second.pure(); }
public boolean pure() {
return first.pure() && second.pure();
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@@ -26,12 +23,12 @@ public class LazyOrStatement extends Statement {
} }
first.compile(target, scope, true); first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute); 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) { public LazyOrStatement(Location loc, Statement first, Statement second) {

View File

@@ -1,27 +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()).setDebug(true));
}
public NewStatement(Location loc, Statement func, Statement ...args) {
super(loc);
this.func = func;
this.args = args;
}
}

View File

@@ -14,17 +14,24 @@ public class ObjectStatement extends Statement {
public final Map<Object, FunctionStatement> getters; public final Map<Object, FunctionStatement> getters;
public final Map<Object, FunctionStatement> setters; public final Map<Object, FunctionStatement> setters;
@Override public boolean pure() {
for (var el : map.values()) {
if (!el.pure()) return false;
}
return true;
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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()) { for (var el : map.entrySet()) {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(el.getKey()).locate(loc())); target.add(Instruction.loadValue(loc(), el.getKey()));
var val = el.getValue(); var val = el.getValue();
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false); FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
else val.compile(target, scope, true); target.add(Instruction.storeMember(loc()));
target.add(Instruction.storeMember().locate(loc()));
} }
var keys = new ArrayList<Object>(); var keys = new ArrayList<Object>();
@@ -32,19 +39,19 @@ public class ObjectStatement extends Statement {
keys.addAll(setters.keySet()); keys.addAll(setters.keySet());
for (var key : keys) { for (var key : keys) {
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc())); if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
else target.add(Instruction.loadValue((Double)key).locate(loc())); else target.add(Instruction.loadValue(loc(), (Double)key));
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true); 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); 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) { public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {

View File

@@ -4,62 +4,29 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; 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 class OperationStatement extends Statement {
public final Statement[] args; public final Statement[] args;
public final Operation operation; public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) { for (var arg : args) {
arg.compile(target, scope, true); arg.compile(target, scope, true);
} }
if (pollute) target.add(Instruction.operation(operation).locate(loc())); if (pollute) target.add(Instruction.operation(loc(), operation));
else target.add(Instruction.discard().locate(loc())); else target.add(Instruction.discard(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));
}
catch (InterruptedException e) { return null; }
}
return new OperationStatement(loc(), operation, args);
} }
public OperationStatement(Location loc, Operation operation, Statement ...args) { public OperationStatement(Location loc, Operation operation, Statement ...args) {

View File

@@ -9,13 +9,13 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class RegexStatement extends Statement { public class RegexStatement extends Statement {
public final String pattern, flags; public final String pattern, flags;
@Override // Not really pure, since a function is called, but can be ignored.
public boolean pure() { return true; } @Override public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadRegex(pattern, flags).locate(loc())); target.add(Instruction.loadRegex(loc(), pattern, flags));
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
public RegexStatement(Location loc, String pattern, String flags) { public RegexStatement(Location loc, String pattern, String flags) {

View File

@@ -4,44 +4,26 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; 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.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class TypeofStatement extends Statement { public class TypeofStatement extends Statement {
public final Statement value; public final Statement value;
@Override // Not really pure, since a variable from the global scope could be accessed,
public boolean pure() { return true; } // which could lead to code execution, that would get omitted
@Override public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value instanceof VariableStatement) { if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name); var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) { if (i instanceof String) {
target.add(Instruction.typeof((String)i).locate(loc())); target.add(Instruction.typeof(loc(), (String)i));
return; return;
} }
} }
value.compile(target, scope, pollute); value.compile(target, scope, pollute);
target.add(Instruction.typeof().locate(loc())); target.add(Instruction.typeof(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);
} }
public TypeofStatement(Location loc, Statement value) { public TypeofStatement(Location loc, Statement value) {

View File

@@ -12,23 +12,21 @@ public class VariableAssignStatement extends Statement {
public final Statement value; public final Statement value;
public final Operation operation; public final Operation operation;
@Override public boolean pure() { return false; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name); var i = scope.getKey(name);
if (operation != null) { if (operation != null) {
target.add(Instruction.loadVar(i).locate(loc())); target.add(Instruction.loadVar(loc(), i));
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); FunctionStatement.compileWithName(value, target, scope, true, name);
else value.compile(target, scope, true); target.add(Instruction.operation(loc(), operation));
target.add(Instruction.operation(operation).locate(loc())); target.add(Instruction.storeVar(loc(), i, pollute));
target.add(Instruction.storeVar(i, false).locate(loc()));
} }
else { else {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); FunctionStatement.compileWithName(value, target, scope, true, name);
else value.compile(target, scope, true); target.add(Instruction.storeVar(loc(), i, pollute));
target.add(Instruction.storeVar(i, false).locate(loc()));
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
} }
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {

View File

@@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableIndexStatement extends Statement { public class VariableIndexStatement extends Statement {
public final int index; public final int index;
@Override @Override public boolean pure() { return true; }
public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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) { public VariableIndexStatement(Location loc, int i) {

View File

@@ -11,8 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableStatement extends AssignableStatement { public class VariableStatement extends AssignableStatement {
public final String name; public final String name;
@Override @Override public boolean pure() { return false; }
public boolean pure() { return true; }
@Override @Override
public Statement toAssign(Statement val, Operation operation) { public Statement toAssign(Statement val, Operation operation) {
@@ -22,8 +21,8 @@ public class VariableStatement extends AssignableStatement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name); var i = scope.getKey(name);
target.add(Instruction.loadVar(i).locate(loc())); target.add(Instruction.loadVar(loc(), i));
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
public VariableStatement(Location loc, String name) { public VariableStatement(Location loc, String name) {

View File

@@ -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;
}
}

View File

@@ -1,27 +1,124 @@
package me.topchetoeu.jscript.engine; 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.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values; 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 { public class Context {
public final Environment env; private final Stack<Environment> env = new Stack<>();
public final Message message; private final ArrayList<CodeFrame> frames = new ArrayList<>();
public final Engine engine;
public FunctionValue compile(String filename, String raw) throws InterruptedException { public Environment environment() {
var res = Values.toString(this, env.compile.call(this, null, raw, filename)); return env.empty() ? null : env.peek();
return Parsing.compile(message.engine.functions, env, filename, res);
} }
public Context setEnv(Environment env) { private Context pushEnv(Environment env) {
return new Context(env, message); this.env.push(env);
return this;
} }
public Context setMsg(Message msg) { private void popEnv() {
return new Context(env, msg); if (!env.empty()) this.env.pop();
} }
public Context(Environment env, Message msg) { public FunctionValue compile(Filename filename, String raw) {
this.env = env; var env = environment();
this.message = msg; var result = env.compile.call(this, null, raw, filename.toString(), env);
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;
}
engine.onSource(filename, raw, breakpoints, map);
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) {
this.engine = engine;
}
public Context(Engine engine, Environment env) {
this(engine);
if (env != null) this.pushEnv(env);
} }
} }

View File

@@ -1,43 +1,44 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Map;
import java.util.Map.Entry;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Data implements Iterable<Entry<DataKey<?>, ?>> { public class Data {
private HashMap<DataKey<Object>, Object> data = new HashMap<>(); private HashMap<DataKey<Object>, Object> data = new HashMap<>();
public Data copy() { public Data copy() {
return new Data().addAll(this); return new Data().addAll(this);
} }
public Data addAll(Iterable<Entry<DataKey<?>, ?>> data) { public Data addAll(Map<DataKey<?>, ?> data) {
for (var el : data) { for (var el : data.entrySet()) {
add((DataKey<Object>)el.getKey(), (Object)el.getValue()); get((DataKey<Object>)el.getKey(), (Object)el.getValue());
}
return this;
}
public Data addAll(Data data) {
for (var el : data.data.entrySet()) {
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
} }
return this; return this;
} }
public <T> T remove(DataKey<T> key) {
return (T)data.remove(key);
}
public <T> Data set(DataKey<T> key, T val) { public <T> Data set(DataKey<T> key, T val) {
if (val == null) data.remove(key); data.put((DataKey<Object>)key, (Object)val);
else data.put((DataKey<Object>)key, (Object)val);
return this; return this;
} }
public <T> T add(DataKey<T> key, T val) { public <T> T get(DataKey<T> key, T val) {
if (data.containsKey(key)) return (T)data.get(key); if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
else { set(key, val);
if (val == null) data.remove(key); return val;
else data.put((DataKey<Object>)key, (Object)val);
return val;
}
} }
public <T> T get(DataKey<T> key) { public <T> T get(DataKey<T> key) {
return get(key, null); if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
} return null;
public <T> T get(DataKey<T> key, T defaultVal) {
if (!has(key)) return defaultVal;
else return (T)data.get(key);
} }
public boolean has(DataKey<?> key) { return data.containsKey(key); } public boolean has(DataKey<?> key) { return data.containsKey(key); }
@@ -52,9 +53,4 @@ public class Data implements Iterable<Entry<DataKey<?>, ?>> {
public int increase(DataKey<Integer> key) { public int increase(DataKey<Integer> key) {
return increase(key, 1, 0); return increase(key, 1, 0);
} }
@Override
public Iterator<Entry<DataKey<?>, ?>> iterator() {
return (Iterator<Entry<DataKey<?>, ?>>)data.entrySet();
}
} }

View File

@@ -1,87 +1,123 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.LinkedBlockingDeque; import java.util.TreeSet;
import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.debug.DebugController;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.mapping.SourceMap;
public class Engine { public class Engine implements DebugController {
private class UncompiledFunction extends FunctionValue { private class UncompiledFunction extends FunctionValue {
public final String filename; public final Filename filename;
public final String raw; public final String raw;
public final Environment env; private FunctionValue compiled = null;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
ctx = ctx.setEnv(env); if (compiled == null) compiled = ctx.compile(filename, raw);
return ctx.compile(filename, raw).call(ctx, thisArg, args); return compiled.call(ctx, thisArg, args);
} }
public UncompiledFunction(Environment env, String filename, String raw) { public UncompiledFunction(Filename filename, String raw) {
super(filename, 0); super(filename + "", 0);
this.filename = filename; this.filename = filename;
this.raw = raw; this.raw = raw;
this.env = env;
} }
} }
private static class Task { private static class Task implements Comparable<Task> {
public final FunctionValue func; public final FunctionValue func;
public final Object thisArg; public final Object thisArg;
public final Object[] args; public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>(); public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Message msg; public final Context ctx;
public final boolean micro;
public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) { public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
this.msg = ctx; this.ctx = ctx;
this.func = func; this.func = func;
this.thisArg = thisArg; this.thisArg = thisArg;
this.args = args; 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; 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 int id = ++nextId;
public final HashMap<Long, Instruction[]> functions = new HashMap<>(); public final boolean debugging;
public int maxStackFrames = 10000;
private void runTask(Task task) throws InterruptedException { private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private final HashMap<Filename, SourceMap> maps = new HashMap<>();
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()),
maps.get(source.getKey())
);
this.debugger = debugger;
return true;
}
public synchronized boolean detachDebugger() {
if (!debugging || this.debugger == null) return false;
this.debugger = null;
return true;
}
private void runTask(Task task) {
try { try {
task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args)); task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
}
catch (InterruptedException e) {
task.notifier.error(new RuntimeException(e));
throw e;
}
catch (EngineException e) {
task.notifier.error(e);
} }
catch (RuntimeException e) { catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e); task.notifier.error(e);
e.printStackTrace();
} }
} }
private void run() { public void run(boolean untilEmpty) {
while (true) { while (!untilEmpty || !tasks.isEmpty()) {
try { try {
runTask(macroTasks.take()); runTask(tasks.take());
while (!microTasks.isEmpty()) {
runTask(microTasks.take());
}
} }
catch (InterruptedException e) { catch (InterruptedException | InterruptException e) {
for (var msg : macroTasks) { for (var msg : tasks) msg.notifier.error(new InterruptException(e));
msg.notifier.error(new RuntimeException(e));
}
break; break;
} }
} }
@@ -89,7 +125,7 @@ public class Engine {
public Thread start() { public Thread start() {
if (this.thread == null) { if (this.thread == null) {
this.thread = new Thread(this::run, "JavaScript Runner #" + id); this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
this.thread.start(); this.thread.start();
} }
return this.thread; return this.thread;
@@ -101,21 +137,39 @@ public class Engine {
public boolean inExecThread() { public boolean inExecThread() {
return Thread.currentThread() == thread; return Thread.currentThread() == thread;
} }
public boolean isRunning() { public synchronized boolean isRunning() {
return this.thread != null; return this.thread != null;
} }
public Awaitable<Object> pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx, func, thisArg, args); var msg = new Task(new Context(this, env), func, thisArg, args, micro);
if (micro) microTasks.addLast(msg); tasks.add(msg);
else macroTasks.addLast(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args); return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
} }
// public Engine() { @Override
// this.typeRegister = new NativeTypeRegister(); 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) {
this.debugging = debugging;
}
} }

View File

@@ -1,36 +1,72 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; 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.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.RootFilesystem;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider; 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<>(); private HashMap<String, ObjectValue> prototypes = new HashMap<>();
public final Data data = new Data(); public final Data data = new Data();
public final HashMap<String, Symbol> symbols = new HashMap<>(); public static final HashMap<String, Symbol> symbols = new HashMap<>();
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappersProvider; public WrappersProvider wrappers;
@Native public FunctionValue compile; public PermissionsProvider permissions = null;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { public final RootFilesystem filesystem = new RootFilesystem(this);
throw EngineException.ofError("Regular expressions not supported.").setContext(ctx); 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) { @Native public ObjectValue proto(String name) {
return prototypes.get(name); return prototypes.get(name);
@@ -40,13 +76,7 @@ public class Environment {
} }
@Native public Symbol symbol(String name) { @Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) return getSymbol(name);
return symbols.get(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
} }
@NativeGetter("global") public ObjectValue getGlobal() { @NativeGetter("global") public ObjectValue getGlobal() {
@@ -57,7 +87,8 @@ public class Environment {
} }
@Native public Environment fork() { @Native public Environment fork() {
var res = new Environment(compile, wrappersProvider, global); var res = new Environment(compile, null, global);
res.wrappers = wrappers.fork(res);
res.regexConstructor = regexConstructor; res.regexConstructor = regexConstructor;
res.prototypes = new HashMap<>(prototypes); res.prototypes = new HashMap<>(prototypes);
return res; return res;
@@ -65,20 +96,41 @@ public class Environment {
@Native public Environment child() { @Native public Environment child() {
var res = fork(); var res = fork();
res.global = res.global.globalChild(); 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; return res;
} }
public Context context(Message msg) { @Override public boolean hasPermission(Permission perm, char delim) {
return new Context(this, msg); 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, 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) { 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 (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope(); if (global == null) global = new GlobalScope();
this.wrappersProvider = nativeConverter; this.wrappers = nativeConverter;
this.compile = compile;
this.global = global; this.global = global;
} }
public Environment() {
this(null, null, null);
}
} }

View File

@@ -1,63 +0,0 @@
package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public class Message {
public final Engine engine;
private final ArrayList<CodeFrame> frames = new ArrayList<>();
public int maxStackFrames = 1000;
public final Data data = new Data();
public List<CodeFrame> frames() { return Collections.unmodifiableList(frames); }
public Message addData(Data data) {
this.data.addAll(data);
return this;
}
public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException {
this.frames.add(frame);
if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!");
return this;
}
public boolean popFrame(CodeFrame frame) {
if (this.frames.size() == 0) return false;
if (this.frames.get(this.frames.size() - 1) != frame) return false;
this.frames.remove(this.frames.size() - 1);
return true;
}
public List<String> stackTrace() {
var res = new ArrayList<String>();
for (var el : frames) {
var name = el.function.name;
var 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 (!res.equals("")) res.add(trace);
}
return res;
}
public Context context(Environment env) {
return new Context(env, this);
}
public Message(Engine engine) {
this.engine = engine;
}
}

View File

@@ -5,5 +5,8 @@ import me.topchetoeu.jscript.engine.values.ObjectValue;
public interface WrappersProvider { public interface WrappersProvider {
public ObjectValue getProto(Class<?> obj); public ObjectValue getProto(Class<?> obj);
public ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj); public FunctionValue getConstr(Class<?> obj);
public WrappersProvider fork(Environment env);
} }

View File

@@ -0,0 +1,51 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
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 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, SourceMap map);
/**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @param loc The most recent location the code frame has been at
* @param returnVal The return value of the instruction, Runners.NO_RETURN if none
* @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
* @return Whether or not the frame should restart
*/
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.
* @param ctx The context of execution
* @param frame The code frame which was popped out
*/
void onFramePop(Context ctx, CodeFrame frame);
}

View File

@@ -0,0 +1,34 @@
package me.topchetoeu.jscript.engine.debug;
public interface DebugHandler {
void enable(V8Message msg);
void disable(V8Message msg);
void setBreakpointByUrl(V8Message msg);
void removeBreakpoint(V8Message msg);
void continueToLocation(V8Message msg);
void getScriptSource(V8Message msg);
void getPossibleBreakpoints(V8Message msg);
void resume(V8Message msg);
void pause(V8Message msg);
void stepInto(V8Message msg);
void stepOut(V8Message msg);
void stepOver(V8Message msg);
void setPauseOnExceptions(V8Message msg);
void evaluateOnCallFrame(V8Message msg);
void getProperties(V8Message msg);
void releaseObjectGroup(V8Message msg);
void releaseObject(V8Message msg);
/**
* This method might not execute the actual code for well-known requests
*/
void callFunctionOn(V8Message msg);
void runtimeEnable(V8Message msg);
}

View File

@@ -0,0 +1,245 @@
package me.topchetoeu.jscript.engine.debug;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
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;
import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class DebugServer {
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
private final byte[] favicon, index, protocol;
private final Notifier connNotifier = new Notifier();
private static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes());
}
// SILENCE JAVA
private MessageDigest getDigestInstance() {
try {
return MessageDigest.getInstance("sha1");
}
catch (Throwable e) { throw new UncheckedException(e); }
}
private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func);
res.setName(name);
res.start();
return res;
}
private void handle(WebSocket ws, Debugger debugger) {
WebSocketMessage raw;
debugger.connect();
while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) {
ws.send(new V8Error("Expected a text message."));
continue;
}
V8Message msg;
try {
msg = new V8Message(raw.textData());
}
catch (SyntaxException e) {
ws.send(new V8Error(e.getMessage()));
return;
}
try {
switch (msg.name) {
case "Debugger.enable":
connNotifier.next();
debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue;
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
case "Debugger.resume": debugger.resume(msg); continue;
case "Debugger.pause": debugger.pause(msg); continue;
case "Debugger.stepInto": debugger.stepInto(msg); continue;
case "Debugger.stepOut": debugger.stepOut(msg); continue;
case "Debugger.stepOver": debugger.stepOver(msg); continue;
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue;
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
case "Runtime.releaseObject": debugger.releaseObject(msg); continue;
case "Runtime.getProperties": debugger.getProperties(msg); continue;
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
// case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue;
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
}
if (
msg.name.startsWith("DOM.") ||
msg.name.startsWith("DOMDebugger.") ||
msg.name.startsWith("Emulation.") ||
msg.name.startsWith("Input.") ||
msg.name.startsWith("Network.") ||
msg.name.startsWith("Page.")
) ws.send(new V8Error("This isn't a browser..."));
else ws.send(new V8Error("This API is not supported yet."));
}
catch (Throwable e) {
e.printStackTrace();
throw new UncheckedException(e);
}
}
debugger.disconnect();
}
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
var key = req.headers.get("sec-websocket-key");
if (key == null) {
req.writeResponse(
426, "Upgrade Required", "text/txt",
"Expected a WS upgrade".getBytes()
);
return;
}
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
));
req.writeCode(101, "Switching Protocols");
req.writeHeader("Connection", "Upgrade");
req.writeHeader("Sec-WebSocket-Accept", resKey);
req.writeLastHeader("Upgrade", "WebSocket");
var ws = new WebSocket(socket);
var debugger = debuggerProvider.getDebugger(ws, req);
if (debugger == null) {
ws.close();
return;
}
runAsync(() -> {
try { handle(ws, debugger); }
catch (RuntimeException e) {
ws.send(new V8Error(e.getMessage()));
}
finally { ws.close(); debugger.disconnect(); }
}, "Debug Handler");
}
public void awaitConnection() {
connNotifier.await();
}
public void run(InetSocketAddress address) {
try {
ServerSocket server = new ServerSocket();
server.bind(address);
try {
while (true) {
var socket = server.accept();
var req = HttpRequest.read(socket);
if (req == null) continue;
switch (req.path) {
case "/json/version":
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
break;
case "/json/list":
case "/json": {
var res = new JSONList();
for (var el : targets.entrySet()) {
res.add(new JSONMap()
.set("description", "JScript debugger")
.set("favicon", "/favicon.ico")
.set("id", el.getKey())
.set("type", "node")
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
);
}
send(req, JSON.stringify(res));
break;
}
case "/json/protocol":
req.writeResponse(200, "OK", "application/json", protocol);
break;
case "/json/new":
case "/json/activate":
case "/json/close":
case "/devtools/inspector.html":
req.writeResponse(
501, "Not Implemented", "text/txt",
"This feature isn't (and probably won't be) implemented.".getBytes()
);
break;
case "/":
case "/index.html":
req.writeResponse(200, "OK", "text/html", index);
break;
case "/favicon.ico":
req.writeResponse(200, "OK", "image/png", favicon);
break;
default:
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
onWsConnect(req, socket, targets.get(req.path.substring(1)));
}
break;
}
}
}
finally { server.close(); }
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public Thread start(InetSocketAddress address, boolean daemon) {
var res = new Thread(() -> run(address), "Debug Server");
res.setDaemon(daemon);
res.start();
return res;
}
public DebugServer() {
try {
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); }
}
}

View File

@@ -0,0 +1,6 @@
package me.topchetoeu.jscript.engine.debug;
public interface Debugger extends DebugHandler, DebugController {
void connect();
void disconnect();
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.engine.debug;
public interface DebuggerProvider {
Debugger getDebugger(WebSocket socket, HttpRequest req);
}

View File

@@ -0,0 +1,102 @@
package me.topchetoeu.jscript.engine.debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Map;
public class HttpRequest {
public final String method;
public final String path;
public final Map<String, String> headers;
public final OutputStream out;
public void writeCode(int code, String name) {
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeLastHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeadersEnd() {
try { out.write("\n".getBytes()); }
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, byte[] data) {
writeCode(code, name);
writeHeader("Content-Type", type);
writeLastHeader("Content-Length", data.length + "");
try {
out.write(data);
out.close();
}
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, InputStream data) {
try {
writeResponse(code, name, type, data.readAllBytes());
}
catch (IOException e) { }
}
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
this.method = method;
this.path = path;
this.headers = headers;
this.out = out;
}
// We dont need no http library
public static HttpRequest read(Socket socket) {
try {
var str = socket.getInputStream();
var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine();
var i1 = line.indexOf(" ");
var i2 = line.indexOf(" ", i1 + 1);
if (i1 < 0 || i2 < 0) {
socket.close();
return null;
}
var method = line.substring(0, i1).trim().toUpperCase();
var path = line.substring(i1 + 1, i2).trim();
var headers = new HashMap<String, String>();
while (!(line = lines.readLine()).isEmpty()) {
var i = line.indexOf(":");
if (i < 0) continue;
var name = line.substring(0, i).trim().toLowerCase();
var value = line.substring(i + 1).trim();
if (name.length() == 0) continue;
headers.put(name, value);
}
if (headers.containsKey("content-length")) {
try {
var i = Integer.parseInt(headers.get("content-length"));
str.skip(i);
}
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
}
return new HttpRequest(method, path, headers, socket.getOutputStream());
}
catch (IOException | NullPointerException e) { return null; }
}
}

View File

@@ -0,0 +1,970 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
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.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
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.events.Notifier;
import me.topchetoeu.jscript.exceptions.EngineException;
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_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,
STEPPING_IN,
STEPPING_OUT,
STEPPING_OVER,
PAUSED_NORMAL,
PAUSED_EXCEPTION,
}
private static enum CatchType {
NONE,
UNCAUGHT,
ALL,
}
private static class Source {
public final int id;
public final Filename filename;
public final String source;
public final TreeSet<Location> breakpoints;
public Source(int id, Filename filename, String source, TreeSet<Location> breakpoints) {
this.id = id;
this.filename = filename;
this.source = source;
this.breakpoints = breakpoints;
}
}
private static class Breakpoint {
public final int id;
public final Location location;
public final String condition;
public Breakpoint(int id, Location location, String condition) {
this.id = id;
this.location = location;
this.condition = condition;
}
}
private static class BreakpointCandidate {
public final int id;
public final String condition;
public final Pattern pattern;
public final int line, start;
public final HashSet<Breakpoint> resolvedBreakpoints = new HashSet<>();
public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) {
this.id = id;
this.pattern = pattern;
this.line = line;
this.start = start;
if (condition != null && condition.trim().equals("")) condition = null;
this.condition = condition;
}
}
private class Frame {
public CodeFrame frame;
public CodeFunction func;
public int id;
public ObjectValue local, capture, global, valstack;
public JSONMap serialized;
public Location location;
public boolean debugData = false;
public void updateLoc(Location loc) {
if (loc == null) return;
this.location = loc;
}
public Frame(Context ctx, CodeFrame frame, int id) {
this.frame = frame;
this.func = frame.function;
this.id = id;
this.global = frame.function.environment.global.obj;
this.local = frame.getLocalScope(ctx, true);
this.capture = frame.getCaptureScope(ctx, true);
this.local.setPrototype(ctx, capture);
this.capture.setPrototype(ctx, global);
this.valstack = frame.getValStackScope(ctx);
debugData = true;
this.serialized = new JSONMap()
.set("callFrameId", id + "")
.set("functionName", func.name)
.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)))
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
);
}
}
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;
}
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;
public RunResult(Context ctx, Object result, EngineException error) {
this.ctx = ctx;
this.result = result;
this.error = error;
}
}
public boolean enabled = true;
public CatchType execptionType = CatchType.NONE;
public State state = State.RESUMED;
public final WebSocket ws;
public final Engine target;
private ObjectValue emptyObject = new ObjectValue();
private HashMap<Integer, BreakpointCandidate> idToBptCand = new HashMap<>();
private HashMap<Integer, Breakpoint> idToBreakpoint = new HashMap<>();
private HashMap<Location, Breakpoint> locToBreakpoint = new HashMap<>();
private HashSet<Location> tmpBreakpts = new HashSet<>();
private HashMap<Filename, Integer> filenameToId = new HashMap<>();
private HashMap<Integer, Source> idToSource = new HashMap<>();
private ArrayList<Source> pendingSources = new ArrayList<>();
private HashMap<Integer, Frame> idToFrame = new HashMap<>();
private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>();
private HashMap<Integer, ObjRef> idToObject = new HashMap<>();
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
private HashMap<String, ArrayList<ObjRef>> objectGroups = new HashMap<>();
private Notifier updateNotifier = new Notifier();
private boolean pendingPause = false;
private int nextId = 0;
private Frame stepOutFrame = null, currFrame = null;
private int stepOutPtr = 0;
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;
}
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)) {
var id = nextId();
var fr = new Frame(ctx, frame, id);
idToFrame.put(id, fr);
codeFrameToFrame.put(frame, fr);
}
currFrame = codeFrameToFrame.get(frame);
}
private JSONList serializeFrames(Context ctx) {
var res = new JSONList();
var frames = ctx.frames();
for (var i = frames.size() - 1; i >= 0; i--) {
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;
}
private Location correctLocation(Source source, Location loc) {
var set = source.breakpoints;
if (set.contains(loc)) return loc;
var tail = set.tailSet(loc);
if (tail.isEmpty()) return null;
return tail.first();
}
private Location deserializeLocation(JSONElement el, boolean correct) {
if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
var id = Integer.parseInt(el.map().string("scriptId"));
var line = (int)el.map().number("lineNumber") + 1;
var column = (int)el.map().number("columnNumber") + 1;
if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id));
var res = new Location(line, column, idToSource.get(id).filename);
if (correct) res = correctLocation(idToSource.get(id), res);
return res;
}
private JSONMap serializeLocation(Location loc) {
var source = filenameToId.get(loc.filename());
return new JSONMap()
.set("scriptId", source + "")
.set("lineNumber", loc.line() - 1)
.set("columnNumber", loc.start() - 1);
}
private Integer objectId(Context ctx, ObjectValue obj) {
if (objectToId.containsKey(obj)) return objectToId.get(obj);
else {
int id = nextId();
var ref = new ObjRef(ctx, obj);
objectToId.put(obj, id);
idToObject.put(id, ref);
return id;
}
}
private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
val = Values.normalize(null, val);
if (val == Values.NULL) {
return new JSONMap()
.set("type", "object")
.set("subtype", "null")
.setNull("value")
.set("description", "null");
}
if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
var id = objectId(ctx, obj);
var type = "object";
String subtype = null;
String className = null;
if (obj instanceof FunctionValue) type = "function";
if (obj instanceof ArrayValue) subtype = "array";
try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); }
catch (Exception e) { }
var res = new JSONMap()
.set("type", type)
.set("objectId", id + "");
if (subtype != null) res.set("subtype", subtype);
if (className != null) {
res.set("className", 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;
}
if (val == null) return new JSONMap().set("type", "undefined");
if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val);
if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val);
if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString());
if (val instanceof Number) {
var num = (double)(Number)val;
var res = new JSONMap().set("type", "number");
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0");
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
else res.set("value", num);
return res;
}
throw new IllegalArgumentException("Unexpected JS object.");
}
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;
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) {
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"))).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.;
}
var res = val.get("value");
if (res == null) return null;
else return JSON.toJs(res);
}
private JSONMap serializeException(Context ctx, EngineException err) {
String text = null;
try {
text = Values.toString(ctx, err.value);
}
catch (EngineException e) {
text = "[error while stringifying]";
}
var res = new JSONMap()
.set("exceptionId", nextId())
.set("exception", serializeObj(ctx, err.value))
.set("text", text);
return res;
}
private void resume(State state) {
this.state = state;
ws.send(new V8Event("Debugger.resumed", new JSONMap()));
updateNotifier.next();
}
private void pauseDebug(Context ctx, Breakpoint bp) {
state = State.PAUSED_NORMAL;
var map = new JSONMap()
.set("callFrames", serializeFrames(ctx))
.set("reason", "debugCommand");
if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + ""));
ws.send(new V8Event("Debugger.paused", map));
}
private void pauseException(Context ctx) {
state = State.PAUSED_EXCEPTION;
var map = new JSONMap()
.set("callFrames", serializeFrames(ctx))
.set("reason", "exception");
ws.send(new V8Event("Debugger.paused", map));
}
private void sendSource(Source src) {
ws.send(new V8Event("Debugger.scriptParsed", new JSONMap()
.set("scriptId", src.id + "")
.set("hash", src.source.hashCode())
.set("url", src.filename + "")
));
}
private void addBreakpoint(Breakpoint bpt) {
idToBreakpoint.put(bpt.id, bpt);
locToBreakpoint.put(bpt.location, bpt);
ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap()
.set("breakpointId", bpt.id)
.set("location", serializeLocation(bpt.location))
));
}
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();
env.global = new GlobalScope(codeFrame.local);
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);
try { return new RunResult(ctx, awaiter.await(), null); }
catch (EngineException e) { return new RunResult(ctx, null, e); }
}
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());
for (var el : pendingSources) sendSource(el);
pendingSources.clear();
updateNotifier.next();
}
@Override public synchronized void disable(V8Message msg) {
enabled = false;
ws.send(msg.respond());
updateNotifier.next();
}
@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 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;
var res = new JSONList();
for (var loc : src.breakpoints.tailSet(start, true)) {
if (end != null && loc.compareTo(end) > 0) break;
res.add(serializeLocation(loc));
}
ws.send(msg.respond(new JSONMap().set("locations", res)));
}
@Override public synchronized void pause(V8Message msg) {
pendingPause = true;
ws.send(msg.respond());
}
@Override public synchronized void resume(V8Message msg) {
resume(State.RESUMED);
ws.send(msg.respond(new JSONMap()));
}
@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", "").trim();
if (cond.equals("")) cond = null;
if (cond != null) cond = "(" + cond + ")";
Pattern regex;
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
else regex = Pattern.compile(msg.params.string("urlRegex"));
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond);
idToBptCand.put(bpcd.id, bpcd);
var locs = new JSONList();
for (var src : idToSource.values()) {
if (regex.matcher(src.filename.toString()).matches()) {
var loc = correctLocation(src, new Location(line, col, src.filename));
if (loc == null) continue;
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
bpcd.resolvedBreakpoints.add(bp);
locs.add(serializeLocation(loc));
addBreakpoint(bp);
}
}
ws.send(msg.respond(new JSONMap()
.set("breakpointId", bpcd.id + "")
.set("locations", locs)
));
}
@Override public synchronized void removeBreakpoint(V8Message msg) {
var id = Integer.parseInt(msg.params.string("breakpointId"));
if (idToBptCand.containsKey(id)) {
var bpcd = idToBptCand.get(id);
for (var bp : bpcd.resolvedBreakpoints) {
idToBreakpoint.remove(bp.id);
locToBreakpoint.remove(bp.location);
}
idToBptCand.remove(id);
}
else if (idToBreakpoint.containsKey(id)) {
var bp = idToBreakpoint.remove(id);
locToBreakpoint.remove(bp.location);
}
ws.send(msg.respond());
}
@Override public synchronized void continueToLocation(V8Message msg) {
var loc = deserializeLocation(msg.params.get("location"), true);
tmpBreakpts.add(loc);
resume(State.RESUMED);
ws.send(msg.respond());
}
@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;
}
ws.send(msg.respond());
}
@Override public synchronized void stepInto(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_IN);
ws.send(msg.respond());
}
}
@Override public synchronized void stepOut(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_OUT);
ws.send(msg.respond());
}
}
@Override public synchronized void stepOver(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_OVER);
ws.send(msg.respond());
}
}
@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);
var cf = idToFrame.get(cfId);
var res = run(cf, expr);
if (group != null) addObjectGroup(group, 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 synchronized void releaseObjectGroup(V8Message msg) {
var group = msg.params.string("objectGroup");
releaseGroup(group);
ws.send(msg.respond());
}
@Override public synchronized void releaseObject(V8Message msg) {
var id = Integer.parseInt(msg.params.string("objectId"));
var ref = idToObject.get(id);
ref.held = false;
if (ref.shouldRelease()) {
objectToId.remove(ref.obj);
idToObject.remove(id);
}
ws.send(msg.respond());
}
@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 = ref.ctx;
if (obj != emptyObject && obj != null) {
for (var key : obj.keys(true)) {
var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(ctx, key));
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", true);
res.add(propDesc);
}
else {
propDesc.set("name", Values.toString(ctx, key));
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
propDesc.set("writable", obj.memberWritable(key));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", true);
res.add(propDesc);
}
}
var proto = obj.getPrototype(ctx);
var protoDesc = new JSONMap();
protoDesc.set("name", "__proto__");
protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto));
protoDesc.set("writable", true);
protoDesc.set("enumerable", false);
protoDesc.set("configurable", false);
protoDesc.set("isOwn", true);
res.add(protoDesc);
}
ws.send(msg.respond(new JSONMap().set("result", res)));
}
@Override public synchronized void callFunctionOn(V8Message msg) {
var src = msg.params.string("functionDeclaration");
var args = msg.params
.list("arguments", new JSONList())
.stream()
.map(v -> v.map())
.map(this::deserializeArgument)
.collect(Collectors.toList());
var byValue = msg.params.bool("returnByValue", false);
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=");
if (start < 0) break;
var end = src.indexOf("\n", start);
if (end < 0) src = src.substring(0, start);
else src = src.substring(0, start) + src.substring(end + 1);
}
try {
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));
}
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)))));
}
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"));
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 synchronized void runtimeEnable(V8Message msg) {
ws.send(msg.respond());
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations, SourceMap map) {
int id = nextId();
var src = new Source(id, filename, source, locations);
idToSource.put(id, src);
filenameToId.put(filename, id);
for (var bpcd : idToBptCand.values()) {
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename));
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
if (loc == null) continue;
bpcd.resolvedBreakpoints.add(bp);
addBreakpoint(bp);
}
if (!enabled) pendingSources.add(src);
else sendSource(src);
}
@Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false;
boolean isBreakpointable;
Location loc;
Frame frame;
synchronized (this) {
frame = codeFrameToFrame.get(cf);
if (!frame.debugData) return false;
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:
case STEPPING_OVER:
if (stepOutFrame.frame == frame.frame) {
if (returnVal != Runners.NO_RETURN || error != null) {
state = State.STEPPING_OUT;
continue;
}
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 (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 synchronized void connect() {
if (!target.attachDebugger(this)) {
ws.send(new V8Error("A debugger is already attached to this engine."));
}
}
@Override public synchronized void disconnect() {
target.detachDebugger();
enabled = false;
updateNotifier.next();
}
public SimpleDebugger(WebSocket ws, Engine target) {
this.ws = ws;
this.target = target;
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Error {
public final String message;
public V8Error(String message) {
this.message = message;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap().set("error", new JSONMap()
.set("message", message)
));
}
}

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Event {
public final String name;
public final JSONMap params;
public V8Event(String name, JSONMap params) {
this.name = name;
this.params = params;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
);
}
}

View File

@@ -0,0 +1,50 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.Map;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Message {
public final String name;
public final int id;
public final JSONMap params;
public V8Message(String name, int id, Map<String, JSONElement> params) {
this.name = name;
this.params = new JSONMap(params);
this.id = id;
}
public V8Result respond(JSONMap result) {
return new V8Result(id, result);
}
public V8Result respond() {
return new V8Result(id, new JSONMap());
}
public V8Message(JSONMap raw) {
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
this.name = raw.string("method");
this.id = (int)raw.number("id");
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
}
public V8Message(String raw) {
this(JSON.parse(null, raw).map());
}
public JSONMap toMap() {
var res = new JSONMap();
return res;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
.set("id", id)
);
}
}

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Result {
public final int id;
public final JSONMap result;
public V8Result(int id, JSONMap result) {
this.id = id;
this.result = result;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("id", id)
.set("result", result)
);
}
}

View File

@@ -0,0 +1,216 @@
package me.topchetoeu.jscript.engine.debug;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
public class WebSocket implements AutoCloseable {
public long maxLength = 1 << 20;
private Socket socket;
private boolean closed = false;
private OutputStream out() {
try { return socket.getOutputStream(); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
private InputStream in() {
try { return socket.getInputStream(); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
private long readLen(int byteLen) {
long res = 0;
try {
if (byteLen == 126) {
res |= in().read() << 8;
res |= in().read();
return res;
}
else if (byteLen == 127) {
res |= in().read() << 56;
res |= in().read() << 48;
res |= in().read() << 40;
res |= in().read() << 32;
res |= in().read() << 24;
res |= in().read() << 16;
res |= in().read() << 8;
res |= in().read();
return res;
}
else return byteLen;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
private byte[] readMask(boolean has) {
if (has) {
try { return new byte[] {
(byte)in().read(),
(byte)in().read(),
(byte)in().read(),
(byte)in().read()
}; }
catch (IOException e) { throw new UncheckedIOException(e); }
}
else return new byte[4];
}
private void writeLength(int len) {
try {
if (len < 126) {
out().write((int)len);
}
else if (len <= 0xFFFF) {
out().write(126);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
else {
out().write(127);
out().write((len >> 56) & 0xFF);
out().write((len >> 48) & 0xFF);
out().write((len >> 40) & 0xFF);
out().write((len >> 32) & 0xFF);
out().write((len >> 24) & 0xFF);
out().write((len >> 16) & 0xFF);
out().write((len >> 8) & 0xFF);
out().write(len & 0xFF);
}
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
private synchronized void write(int type, byte[] data) {
try {
int i;
for (i = 0; i < data.length / 0xFFFF; i++) {
out().write(type);
writeLength(0xFFFF);
out().write(data, i * 0xFFFF, 0xFFFF);
type = 0;
}
out().write(type | 0x80);
writeLength(data.length % 0xFFFF);
out().write(data, i * 0xFFFF, data.length % 0xFFFF);
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void send(String data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(2, data);
}
public void send(WebSocketMessage msg) {
if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData());
}
public void send(Object data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes());
}
public void close(String reason) {
if (socket != null) {
try {
write(8, reason.getBytes());
socket.close();
}
catch (Throwable e) { }
}
socket = null;
closed = true;
}
public void close() {
close("");
}
private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason);
close(reason);
return null;
}
private byte[] readData() {
try {
var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else {
var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else {
for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)];
}
return buff;
}
}
return null;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public WebSocketMessage receive() {
try {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
if (finId < 0) break;
var fin = (finId & 0x80) != 0;
int id = finId & 0x0F;
if (id == 0x8) { close(); return null; }
if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray());
continue;
}
if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData();
if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff);
if (!fin) continue;
var raw = data.toByteArray();
if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw);
}
}
catch (IOException e) {
close();
}
return null;
}
public WebSocket(Socket socket) {
this.socket = socket;
}
}

View File

@@ -0,0 +1,29 @@
package me.topchetoeu.jscript.engine.debug;
public class WebSocketMessage {
public static enum Type {
Text,
Binary,
}
public final Type type;
private final Object data;
public final String textData() {
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
return (String)data;
}
public final byte[] binaryData() {
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
return (byte[])data;
}
public WebSocketMessage(String data) {
this.type = Type.Text;
this.data = data;
}
public WebSocketMessage(byte[] data) {
this.type = Type.Binary;
this.data = data;
}
}

View File

@@ -1,44 +1,90 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
import java.util.List;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ScopeValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame { public class CodeFrame {
private class TryCtx { public static enum TryState {
public static final int STATE_TRY = 0; TRY,
public static final int STATE_CATCH = 1; CATCH,
public static final int STATE_FINALLY_THREW = 2; FINALLY,
public static final int STATE_FINALLY_RETURNED = 3; }
public static final int STATE_FINALLY_JUMPED = 4;
public final boolean hasCatch, hasFinally; public static class TryCtx {
public final int tryStart, catchStart, finallyStart, end; public final int start, end, catchStart, finallyStart;
public int state; public final int restoreStackPtr;
public Object retVal; public final TryState state;
public EngineException err; public final EngineException error;
public int jumpPtr; public final PendingResult result;
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) { public boolean hasCatch() { return catchStart >= 0; }
hasCatch = catchN >= 0; public boolean hasFinally() { return finallyStart >= 0; }
hasFinally = finallyN >= 0;
if (catchN < 0) catchN = 0; public boolean inBounds(int ptr) {
if (finallyN < 0) finallyN = 0; return ptr >= start && ptr < end;
}
this.tryStart = tryStart; public TryCtx _catch(EngineException e) {
this.catchStart = tryStart + tryN; if (error != null) e.setCause(error);
this.finallyStart = catchStart + catchN; return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
this.end = finallyStart + finallyN; }
this.jumpPtr = end; public TryCtx _finally(PendingResult res) {
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
}
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);
} }
} }
@@ -47,15 +93,62 @@ public class CodeFrame {
public final Object[] args; public final Object[] args;
public final Stack<TryCtx> tryStack = new Stack<>(); public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function; public final CodeFunction function;
public Object[] stack = new Object[32]; public Object[] stack = new Object[32];
public int stackPtr = 0; public int stackPtr = 0;
public int codePtr = 0; public int codePtr = 0;
public boolean jumpFlag = false; public boolean jumpFlag = false, popTryFlag = false;
private Location prevLoc = null; private Location prevLoc = null;
public void addTry(int n, int catchN, int finallyN) { public ObjectValue getLocalScope(Context ctx, boolean props) {
var res = new TryCtx(codePtr + 1, n, catchN, finallyN); var names = new String[scope.locals.length];
for (int i = 0; i < scope.locals.length; i++) {
var name = "local_" + (i - 2);
if (i == 0) name = "this";
else if (i == 1) name = "arguments";
else if (i < function.localNames.length) name = function.localNames[i];
names[i] = name;
}
return new ScopeValue(scope.locals, names);
}
public ObjectValue getCaptureScope(Context ctx, boolean props) {
var names = new String[scope.captures.length];
for (int i = 0; i < scope.captures.length; i++) {
var name = "capture_" + (i - 2);
if (i < function.captureNames.length) name = function.captureNames[i];
names[i] = name;
}
return new ScopeValue(scope.captures, names);
}
public ObjectValue getValStackScope(Context ctx) {
return new ObjectValue() {
@Override
protected Object getField(Context ctx, Object key) {
var i = (int)Values.toNumber(ctx, key);
if (i < 0 || i >= stackPtr) return null;
else return stack[i];
}
@Override
protected boolean hasField(Context ctx, Object key) {
return true;
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < stackPtr; i++) res.add(i);
return res;
}
};
}
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); tryStack.add(res);
} }
@@ -93,153 +186,122 @@ public class CodeFrame {
stack[stackPtr++] = Values.normalize(ctx, val); stack[stackPtr++] = Values.normalize(ctx, val);
} }
private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException { public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (err.value instanceof ObjectValue) {
Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause);
}
err.cause = cause;
}
private Object nextNoTry(Context ctx) throws InterruptedException {
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
if (codePtr < 0 || codePtr >= function.body.length) return null;
var instr = function.body[codePtr];
var loc = instr.location;
if (loc != null) prevLoc = loc;
try {
this.jumpFlag = false;
return Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
throw e.add(function.name, prevLoc).setContext(ctx);
}
}
public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException {
if (value != Runners.NO_RETURN) push(ctx, value); 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) { if (returnValue == Runners.NO_RETURN && error == null) {
try { returnValue = nextNoTry(ctx); } try {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (instr == null) returnValue = null;
else {
// System.out.println(instr + "@" + instr.location);
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (instr.location != null) prevLoc = instr.location;
try {
this.jumpFlag = this.popTryFlag = false;
returnValue = Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
error = e.add(ctx, function.name, prevLoc);
}
}
}
catch (EngineException e) { error = e; } catch (EngineException e) { error = e; }
} }
while (!tryStack.empty()) { while (!tryStack.empty()) {
var tryCtx = tryStack.peek(); var tryCtx = tryStack.peek();
var newState = -1; TryCtx newCtx = null;
switch (tryCtx.state) { if (error != null) {
case TryCtx.STATE_TRY: if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
if (error != null) { else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
if (tryCtx.hasCatch) {
tryCtx.err = error;
newState = TryCtx.STATE_CATCH;
}
else if (tryCtx.hasFinally) {
tryCtx.err = error;
newState = TryCtx.STATE_FINALLY_THREW;
}
break;
}
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally) {
tryCtx.retVal = error;
newState = TryCtx.STATE_FINALLY_RETURNED;
}
break;
}
else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) 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_CATCH:
if (error != null) {
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 return Runners.NO_RETURN;
break;
case TryCtx.STATE_FINALLY_RETURNED:
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 (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
else codePtr = tryCtx.end;
}
else return Runners.NO_RETURN;
break;
} }
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
if (newState == -1) {
tryStack.pop();
continue;
} }
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
}
else if (!this.popTryFlag) newCtx = tryCtx;
tryCtx.state = newState; if (newCtx != null) {
switch (newState) { if (newCtx != tryCtx) {
case TryCtx.STATE_CATCH: switch (newCtx.state) {
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); case CATCH:
codePtr = tryCtx.catchStart; if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
break; codePtr = tryCtx.catchStart;
default: 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:
}
tryStack.pop();
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; 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;
}
} }
return Runners.NO_RETURN;
} }
if (error != null) throw error.setContext(ctx); if (error != null) {
if (returnValue != Runners.NO_RETURN) return returnValue; 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, instr, returnValue, null, false);
return returnValue;
}
return Runners.NO_RETURN; return Runners.NO_RETURN;
} }
public Object run(Context ctx) throws InterruptedException {
try {
ctx.message.pushFrame(ctx, this);
while (true) {
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
if (res != Runners.NO_RETURN) return res;
}
}
finally {
ctx.message.popFrame(this);
}
}
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
this.args = args.clone(); this.args = args.clone();
this.scope = new LocalScope(func.localsN, func.captures); this.scope = new LocalScope(func.localsN, func.captures);

View File

@@ -4,6 +4,7 @@ import java.util.Collections;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
@@ -22,52 +23,37 @@ public class Runners {
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) { public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
throw new EngineException(frame.pop()); throw new EngineException(frame.pop());
} }
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
throw EngineException.ofSyntax((String)instr.get(0)); throw EngineException.ofSyntax((String)instr.get(0));
} }
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
return Values.call(ctx, func, thisArg, args);
}
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var func = frame.pop(); var func = frame.pop();
var thisArg = frame.pop(); var thisArg = frame.pop();
frame.push(ctx, call(ctx, func, thisArg, callArgs)); frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop(); var funcObj = frame.pop();
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
// }
// else {
// var proto = Values.getMember(ctx, funcObj, "prototype");
// var obj = new ObjectValue();
// obj.setPrototype(ctx, proto);
// call(ctx, funcObj, obj, callArgs);
// frame.push(ctx, obj);
// }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
var name = (String)instr.get(0); var name = (String)instr.get(0);
ctx.env.global.define(name); ctx.environment().global.define(name);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) {
var setter = frame.pop(); var setter = frame.pop();
var getter = frame.pop(); var getter = frame.pop();
var name = frame.pop(); var name = frame.pop();
@@ -82,7 +68,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) {
var type = frame.pop(); var type = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@@ -97,54 +83,51 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var arr = new ObjectValue();
var i = 0;
var members = Values.getMembers(ctx, val, false, false); var members = Values.getMembers(ctx, val, false, false);
Collections.reverse(members); Collections.reverse(members);
frame.push(ctx, null);
for (var el : members) { for (var el : members) {
if (el instanceof Symbol) continue; if (el instanceof Symbol) continue;
arr.defineProperty(ctx, i++, el); var obj = new ObjectValue();
obj.defineProperty(ctx, "value", el);
frame.push(ctx, obj);
} }
arr.defineProperty(ctx, "length", i);
frame.push(ctx, arr);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
frame.addTry(instr.get(0), instr.get(1), instr.get(2)); 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++; frame.codePtr++;
return NO_RETURN; 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) { 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++) { for (var i = 0; i < count; i++) {
frame.push(ctx, frame.peek(offset + count - 1)); frame.push(ctx, frame.peek(count - 1));
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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) { public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, null); frame.push(ctx, null);
frame.codePtr++; frame.codePtr++;
@@ -155,10 +138,10 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i)); if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i));
else frame.push(ctx, frame.scope.get((int)i).get(ctx)); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++; frame.codePtr++;
@@ -170,7 +153,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.env.global.obj); frame.push(ctx, ctx.environment().global.obj);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@@ -183,23 +166,20 @@ public class Runners {
} }
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
long id = (Long)instr.get(0); long id = (Long)instr.get(0);
int localsN = (Integer)instr.get(1); var captures = new ValueVariable[instr.params.length - 1];
int len = (Integer)instr.get(2);
var captures = new ValueVariable[instr.params.length - 3];
for (var i = 3; i < instr.params.length; i++) { for (var i = 1; i < instr.params.length; i++) {
captures[i - 3] = frame.scope.get(instr.get(i)); captures[i - 1] = frame.scope.get(instr.get(i));
} }
var body = ctx.message.engine.functions.get(id); var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body);
frame.push(ctx, func); frame.push(ctx, func);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@@ -212,12 +192,12 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, instr.get(0)); frame.push(ctx, instr.get(0));
return execLoadMember(ctx, instr, frame); return execLoadMember(ctx, instr, frame);
} }
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@@ -227,7 +207,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@@ -237,11 +217,11 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val); if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val);
else frame.scope.get((int)i).set(ctx, val); else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++; frame.codePtr++;
@@ -258,7 +238,7 @@ public class Runners {
frame.jumpFlag = true; frame.jumpFlag = true;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.toBoolean(frame.pop())) { if (Values.toBoolean(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
@@ -266,7 +246,7 @@ public class Runners {
else frame.codePtr ++; else frame.codePtr ++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.not(frame.pop())) { if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
@@ -275,7 +255,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
var obj = frame.pop(); var obj = frame.pop();
var index = frame.pop(); var index = frame.pop();
@@ -283,13 +263,13 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
String name = instr.get(0); String name = instr.get(0);
Object obj; Object obj;
if (name != null) { if (name != null) {
if (ctx.env.global.has(ctx, name)) { if (ctx.environment().global.has(ctx, name)) {
obj = ctx.env.global.get(ctx, name); obj = ctx.environment().global.get(ctx, name);
} }
else obj = null; else obj = null;
} }
@@ -300,31 +280,21 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
if (instr.is(0, "dbg_names")) {
var names = new String[instr.params.length - 1];
for (var i = 0; i < instr.params.length - 1; i++) {
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
names[i] = (String)instr.params[i + 1];
}
frame.scope.setNames(names);
}
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
var key = frame.pop(); var key = frame.pop();
var val = frame.pop(); var val = frame.pop();
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
frame.push(ctx, true);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
Operation op = instr.get(0); Operation op = instr.get(0);
var args = new Object[op.operands]; var args = new Object[op.operands];
@@ -335,7 +305,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
switch (instr.type) { switch (instr.type) {
case NOP: return execNop(ctx, instr, frame); case NOP: return execNop(ctx, instr, frame);
case RETURN: return execReturn(ctx, instr, frame); case RETURN: return execReturn(ctx, instr, frame);
@@ -343,10 +313,10 @@ public class Runners {
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame); case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
case CALL: return execCall(ctx, instr, frame); case CALL: return execCall(ctx, instr, frame);
case CALL_NEW: return execCallNew(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 DUP: return execDup(ctx, instr, frame);
case MOVE: return execMove(ctx, instr, frame);
case LOAD_VALUE: return execLoadValue(ctx, instr, frame); case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
case LOAD_VAR: return execLoadVar(ctx, instr, frame); case LOAD_VAR: return execLoadVar(ctx, instr, frame);
case LOAD_OBJ: return execLoadObj(ctx, instr, frame); case LOAD_OBJ: return execLoadObj(ctx, instr, frame);

View File

@@ -12,10 +12,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord { public class GlobalScope implements ScopeRecord {
public final ObjectValue obj; public final ObjectValue obj;
@Override public boolean has(Context ctx, String name) {
public GlobalScope parent() { return null; }
public boolean has(Context ctx, String name) throws InterruptedException {
return obj.hasMember(ctx, name, false); return obj.hasMember(ctx, name, false);
} }
public Object getKey(String name) { public Object getKey(String name) {
@@ -28,17 +25,11 @@ public class GlobalScope implements ScopeRecord {
return new GlobalScope(obj); return new GlobalScope(obj);
} }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(this); return new LocalScopeRecord();
} }
public Object define(String name) { public Object define(String name) {
try { if (obj.hasMember(null, name, true)) return name;
if (obj.hasMember(null, name, true)) return name;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return name;
}
obj.defineProperty(null, name, null); obj.defineProperty(null, name, null);
return name; return name;
} }
@@ -59,11 +50,11 @@ public class GlobalScope implements ScopeRecord {
define(null, val.name, readonly, val); define(null, val.name, readonly, val);
} }
public Object get(Context ctx, String name) throws InterruptedException { public Object get(Context ctx, String name) {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return obj.getMember(ctx, name); else return obj.getMember(ctx, name);
} }
public void set(Context ctx, String name, Object val) throws InterruptedException { public void set(Context ctx, String name, Object val) {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
} }

View File

@@ -3,43 +3,21 @@ package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList; import java.util.ArrayList;
public class LocalScope { public class LocalScope {
private String[] names;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public final ValueVariable[] locals; public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>(); public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) { public ValueVariable get(int i) {
if (i >= locals.length) if (i >= locals.length) return catchVars.get(i - locals.length);
return catchVars.get(i - locals.length);
if (i >= 0) return locals[i]; if (i >= 0) return locals[i];
else return captures[~i]; else return captures[~i];
} }
public String[] getNames() {
var res = new String[locals.length];
for (var i = 0; i < locals.length; i++) {
if (names == null || i >= names.length) res[i] = "local_" + i;
else res[i] = names[i];
}
return res;
}
public void setNames(String[] names) {
this.names = names;
}
public int size() { public int size() {
return captures.length + locals.length; return captures.length + locals.length;
} }
public void toGlobal(GlobalScope global) {
var names = getNames();
for (var i = 0; i < names.length; i++) {
global.define(names[i], locals[i]);
}
}
public LocalScope(int n, ValueVariable[] captures) { public LocalScope(int n, ValueVariable[] captures) {
locals = new ValueVariable[n]; locals = new ValueVariable[n];
this.captures = captures; this.captures = captures;

View File

@@ -2,24 +2,21 @@ package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList; import java.util.ArrayList;
import me.topchetoeu.jscript.engine.Context;
public class LocalScopeRecord implements ScopeRecord { public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent; public final LocalScopeRecord parent;
public final GlobalScope global;
private final ArrayList<String> captures = new ArrayList<>(); private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>(); private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() { public String[] locals() {
return locals.toArray(String[]::new); return locals.toArray(String[]::new);
} }
@Override
public LocalScopeRecord parent() { return parent; }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(this, global); return new LocalScopeRecord(this);
} }
public int localsCount() { public int localsCount() {
@@ -59,12 +56,6 @@ public class LocalScopeRecord implements ScopeRecord {
return name; return name;
} }
public boolean has(Context ctx, String name) throws InterruptedException {
return
global.has(ctx, name) ||
locals.contains(name) ||
parent != null && parent.has(ctx, name);
}
public Object define(String name, boolean force) { public Object define(String name, boolean force) {
if (!force && locals.contains(name)) return locals.indexOf(name); if (!force && locals.contains(name)) return locals.indexOf(name);
locals.add(name); locals.add(name);
@@ -77,12 +68,10 @@ public class LocalScopeRecord implements ScopeRecord {
locals.remove(locals.size() - 1); locals.remove(locals.size() - 1);
} }
public LocalScopeRecord(GlobalScope global) { public LocalScopeRecord() {
this.parent = null; this.parent = null;
this.global = global;
} }
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) { public LocalScopeRecord(LocalScopeRecord parent) {
this.parent = parent; this.parent = parent;
this.global = global;
} }
} }

View File

@@ -3,6 +3,5 @@ package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord { public interface ScopeRecord {
public Object getKey(String name); public Object getKey(String name);
public Object define(String name); public Object define(String name);
public ScopeRecord parent();
public LocalScopeRecord child(); public LocalScopeRecord child();
} }

View File

@@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public interface Variable { public interface Variable {
Object get(Context ctx) throws InterruptedException; Object get(Context ctx);
default boolean readonly() { return true; } default boolean readonly() { return true; }
default void set(Context ctx, Object val) throws InterruptedException { } default void set(Context ctx, Object val) { }
} }

View File

@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
private Object[] values; private Object[] values;
private int size; private int size;
private void alloc(int index) { private Object[] alloc(int index) {
if (index < values.length) return; index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2; if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index]; var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length); System.arraycopy(values, 0, arr, 0, values.length);
values = arr; return arr;
} }
public int size() { return size; } public int size() { return size; }
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
if (val < 0) return false; if (val < 0) return false;
if (size > val) shrink(size - val); if (size > val) shrink(size - val);
else { else {
alloc(val); values = alloc(val);
size = val; size = val;
} }
return true; return true;
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
public void set(Context ctx, int i, Object val) { public void set(Context ctx, int i, Object val) {
if (i < 0) return; if (i < 0) return;
alloc(i); values = alloc(i);
val = Values.normalize(ctx, val); val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED; if (val == null) val = UNDEFINED;
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
if (i >= size) size = i + 1; if (i >= size) size = i + 1;
} }
public boolean has(int i) { public boolean has(int i) {
return i >= 0 && i < values.length && values[i] != null; return i >= 0 && i < size && values[i] != null;
} }
public void remove(int i) { public void remove(int i) {
if (i < 0 || i >= values.length) return; if (i < 0 || i >= values.length) return;
@@ -84,8 +85,9 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) { public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once // Iterate in reverse to reallocate at most once
for (var i = count - 1; i >= 0; i--) { for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null); if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null); if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(ctx, i + destStart, values[i + sourceStart]); else arr.set(ctx, i + destStart, values[i + sourceStart]);
} }
} }
@@ -97,7 +99,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
} }
public void move(int srcI, int dstI, int n) { public void move(int srcI, int dstI, int n) {
alloc(dstI + n); values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n); System.arraycopy(values, srcI, values, dstI, n);
@@ -122,7 +124,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
} }
@Override @Override
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = ((Number)key).doubleValue(); var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -133,7 +135,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { protected boolean setField(Context ctx, Object key, Object val) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -145,7 +147,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.setField(ctx, key, val); return super.setField(ctx, key, val);
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -156,7 +158,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }
@Override @Override
protected void deleteField(Context ctx, Object key) throws InterruptedException { protected void deleteField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -212,7 +214,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]); for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
} }
public static ArrayValue of(Context ctx, Collection<Object> values) { public static ArrayValue of(Context ctx, Collection<?> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new)); return new ArrayValue(ctx, values.toArray(Object[]::new));
} }
} }

View File

@@ -1,16 +1,18 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue { public class CodeFunction extends FunctionValue {
public final int localsN; public final int localsN;
public final int length;
public final Instruction[] body; public final Instruction[] body;
public final String[] captureNames, localNames;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public Environment environment; public Environment environment;
@@ -28,16 +30,28 @@ public class CodeFunction extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment)); var frame = new CodeFrame(ctx, thisArg, args, this);
try {
ctx.pushFrame(frame);
while (true) {
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
if (res != Runners.NO_RETURN) return res;
}
}
finally {
ctx.popFrame(frame);
}
} }
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
super(name, length); super(name, body.argsN);
this.captures = captures; this.captures = captures;
this.captureNames = body.captureNames;
this.localNames = body.localNames;
this.environment = environment; this.environment = environment;
this.localsN = localsN; this.localsN = body.localsN;
this.length = length; this.body = body.instructions;
this.body = body;
} }
} }

View File

@@ -11,31 +11,31 @@ public abstract class FunctionValue extends ObjectValue {
@Override @Override
public String toString() { public String toString() {
return "function(...) { ...}"; return String.format("function %s(...)", name);
} }
public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException; public abstract Object call(Context ctx, Object thisArg, Object ...args);
public Object call(Context ctx) throws InterruptedException { public Object call(Context ctx) {
return call(ctx, null); return call(ctx, null);
} }
@Override @Override
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (key.equals("name")) return name; if ("name".equals(key)) return name;
if (key.equals("length")) return length; if ("length".equals(key)) return length;
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { protected boolean setField(Context ctx, Object key, Object val) {
if (key.equals("name")) name = Values.toString(ctx, val); if ("name".equals(key)) name = Values.toString(ctx, val);
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val); else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val); else return super.setField(ctx, key, val);
return true; return true;
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
if (key.equals("name")) return true; if ("name".equals(key)) return true;
if (key.equals("length")) return true; if ("length".equals(key)) return true;
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }

View File

@@ -4,13 +4,13 @@ import me.topchetoeu.jscript.engine.Context;
public class NativeFunction extends FunctionValue { public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner { public static interface NativeFunctionRunner {
Object run(Context ctx, Object thisArg, Object[] args) throws InterruptedException; Object run(Context ctx, Object thisArg, Object[] args);
} }
public final NativeFunctionRunner action; public final NativeFunctionRunner action;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
return action.run(ctx, thisArg, args); return action.run(ctx, thisArg, args);
} }

View File

@@ -7,12 +7,24 @@ public class NativeWrapper extends ObjectValue {
public final Object wrapped; public final Object wrapped;
@Override @Override
public ObjectValue getPrototype(Context ctx) throws InterruptedException { public ObjectValue getPrototype(Context ctx) {
if (prototype == NATIVE_PROTO) if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
else return super.getPrototype(ctx); 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) { public NativeWrapper(Object wrapped) {
this.wrapped = wrapped; this.wrapped = wrapped;
prototype = NATIVE_PROTO; prototype = NATIVE_PROTO;

View File

@@ -103,8 +103,7 @@ public class ObjectValue {
) return true; ) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) if (!memberConfigurable(key)) return false;
return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
@@ -145,19 +144,17 @@ public class ObjectValue {
return true; return true;
} }
public ObjectValue getPrototype(Context ctx) throws InterruptedException { public ObjectValue getPrototype(Context ctx) {
try { try {
if (prototype == OBJ_PROTO) return ctx.env.proto("object"); if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
if (prototype == ARR_PROTO) return ctx.env.proto("array"); if (prototype == ARR_PROTO) return ctx.environment().proto("array");
if (prototype == FUNC_PROTO) return ctx.env.proto("function"); if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
if (prototype == ERR_PROTO) return ctx.env.proto("error"); if (prototype == ERR_PROTO) return ctx.environment().proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr");
}
catch (NullPointerException e) {
return null;
} }
catch (NullPointerException e) { return null; }
return (ObjectValue)prototype; return (ObjectValue)prototype;
} }
@@ -172,14 +169,14 @@ public class ObjectValue {
else if (Values.isObject(val)) { else if (Values.isObject(val)) {
var obj = Values.object(val); var obj = Values.object(val);
if (ctx != null && ctx.env != null) { if (ctx != null && ctx.environment() != null) {
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj; else prototype = obj;
} }
else prototype = obj; else prototype = obj;
@@ -203,19 +200,19 @@ public class ObjectValue {
return true; return true;
} }
protected Property getProperty(Context ctx, Object key) throws InterruptedException { protected Property getProperty(Context ctx, Object key) {
if (properties.containsKey(key)) return properties.get(key); if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key); if (proto != null) return proto.getProperty(ctx, key);
else return null; else return null;
} }
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (values.containsKey(key)) return values.get(key); if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key); if (proto != null) return proto.getField(ctx, key);
else return null; else return null;
} }
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { protected boolean setField(Context ctx, Object key, Object val) {
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(ctx, key); ((FunctionValue)val).name = Values.toString(ctx, key);
} }
@@ -223,14 +220,14 @@ public class ObjectValue {
values.put(key, val); values.put(key, val);
return true; return true;
} }
protected void deleteField(Context ctx, Object key) throws InterruptedException { protected void deleteField(Context ctx, Object key) {
values.remove(key); values.remove(key);
} }
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
return values.containsKey(key); return values.containsKey(key);
} }
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException { public final Object getMember(Context ctx, Object key, Object thisArg) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if ("__proto__".equals(key)) { if ("__proto__".equals(key)) {
@@ -246,11 +243,11 @@ public class ObjectValue {
} }
else return getField(ctx, key); else return getField(ctx, key);
} }
public final Object getMember(Context ctx, Object key) throws InterruptedException { public final Object getMember(Context ctx, Object key) {
return getMember(ctx, key, this); return getMember(ctx, key, this);
} }
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException { public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
var prop = getProperty(ctx, key); var prop = getProperty(ctx, key);
@@ -265,25 +262,25 @@ public class ObjectValue {
values.put(key, val); values.put(key, val);
return true; 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 if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val); else return setField(ctx, key, val);
} }
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) throws InterruptedException { public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) {
return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps); return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps);
} }
public final boolean hasMember(Context ctx, Object key, boolean own) throws InterruptedException { public final boolean hasMember(Context ctx, Object key, boolean own) {
key = Values.normalize(ctx, key); 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 (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true; if (properties.containsKey(key)) return true;
if (own) return false; if (own) return false;
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
return proto != null && proto.hasMember(ctx, key, own); return proto != null && proto.hasMember(ctx, key, own);
} }
public final boolean deleteMember(Context ctx, Object key) throws InterruptedException { public final boolean deleteMember(Context ctx, Object key) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (!memberConfigurable(key)) return false; if (!memberConfigurable(key)) return false;
@@ -294,7 +291,7 @@ public class ObjectValue {
return true; return true;
} }
public final ObjectValue getMemberDescriptor(Context ctx, Object key) throws InterruptedException { public final ObjectValue getMemberDescriptor(Context ctx, Object key) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
var prop = properties.get(key); var prop = properties.get(key);

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.jscript.engine.values;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class ScopeValue extends ObjectValue {
public final ValueVariable[] variables;
public final HashMap<String, Integer> names = new HashMap<>();
@Override
protected Object getField(Context ctx, Object key) {
if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (names.containsKey(key)) {
variables[names.get(key)].set(ctx, val);
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
protected void deleteField(Context ctx, Object key) {
if (names.containsKey(key)) return;
super.deleteField(ctx, key);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (names.containsKey(key)) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet());
return res;
}
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i);
this.nonConfigurableSet.add(names[i]);
}
}
}

View File

@@ -16,8 +16,28 @@ import me.topchetoeu.jscript.engine.frame.ConvertHint;
import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.lib.PromiseLib;
public class Values { 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 final Object NULL = new Object();
public static boolean isObject(Object val) { return val instanceof ObjectValue; } public static boolean isObject(Object val) { return val instanceof ObjectValue; }
@@ -50,8 +70,7 @@ public class Values {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T wrapper(Object val, Class<T> clazz) { 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; var res = (NativeWrapper)val;
if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped; if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped;
else return null; else return null;
@@ -67,7 +86,7 @@ public class Values {
return "object"; return "object";
} }
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) throws InterruptedException { private static Object tryCallConvertFunc(Context ctx, Object obj, String name) {
var func = getMember(ctx, obj, name); var func = getMember(ctx, obj, name);
if (func != null) { if (func != null) {
@@ -88,7 +107,7 @@ public class Values {
obj == NULL; obj == NULL;
} }
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException { public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) {
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
if (isPrimitive(obj)) return obj; if (isPrimitive(obj)) return obj;
@@ -96,37 +115,32 @@ public class Values {
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
if (ctx != null) { if (ctx != null) {
try { try { return tryCallConvertFunc(ctx, obj, first); }
return tryCallConvertFunc(ctx, obj, first); catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); }
}
catch (EngineException unused) {
return tryCallConvertFunc(ctx, obj, second);
}
} }
throw EngineException.ofType("Value couldn't be converted to a primitive."); throw EngineException.ofType("Value couldn't be converted to a primitive.");
} }
public static boolean toBoolean(Object obj) { public static boolean toBoolean(Object obj) {
if (obj == NULL || obj == null) return false; 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 String && ((String)obj).equals("")) return false;
if (obj instanceof Boolean) return (Boolean)obj; if (obj instanceof Boolean) return (Boolean)obj;
return true; return true;
} }
public static double toNumber(Context ctx, Object obj) throws InterruptedException { public static double toNumber(Context ctx, Object obj) {
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val instanceof Number) return number(val); if (val instanceof Number) return number(val);
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
if (val instanceof String) { if (val instanceof String) {
try { try { return Double.parseDouble((String)val); }
return Double.parseDouble((String)val); catch (NumberFormatException e) { return Double.NaN; }
} catch (Throwable e) { throw new UncheckedException(e); }
catch (NumberFormatException e) { }
} }
return Double.NaN; return Double.NaN;
} }
public static String toString(Context ctx, Object obj) throws InterruptedException { public static String toString(Context ctx, Object obj) {
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val == null) return "undefined"; if (val == null) return "undefined";
@@ -141,52 +155,52 @@ public class Values {
} }
if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
if (val instanceof String) return (String)val; 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"; return "Unknown value";
} }
public static Object add(Context ctx, Object a, Object b) throws InterruptedException { public static Object add(Context ctx, Object a, Object b) {
if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b); if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
else return toNumber(ctx, a) + toNumber(ctx, b); else return toNumber(ctx, a) + toNumber(ctx, b);
} }
public static double subtract(Context ctx, Object a, Object b) throws InterruptedException { public static double subtract(Context ctx, Object a, Object b) {
return toNumber(ctx, a) - toNumber(ctx, b); return toNumber(ctx, a) - toNumber(ctx, b);
} }
public static double multiply(Context ctx, Object a, Object b) throws InterruptedException { public static double multiply(Context ctx, Object a, Object b) {
return toNumber(ctx, a) * toNumber(ctx, b); return toNumber(ctx, a) * toNumber(ctx, b);
} }
public static double divide(Context ctx, Object a, Object b) throws InterruptedException { public static double divide(Context ctx, Object a, Object b) {
return toNumber(ctx, a) / toNumber(ctx, b); return toNumber(ctx, a) / toNumber(ctx, b);
} }
public static double modulo(Context ctx, Object a, Object b) throws InterruptedException { public static double modulo(Context ctx, Object a, Object b) {
return toNumber(ctx, a) % toNumber(ctx, b); return toNumber(ctx, a) % toNumber(ctx, b);
} }
public static double negative(Context ctx, Object obj) throws InterruptedException { public static double negative(Context ctx, Object obj) {
return -toNumber(ctx, obj); return -toNumber(ctx, obj);
} }
public static int and(Context ctx, Object a, Object b) throws InterruptedException { public static int and(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) & (int)toNumber(ctx, b); return (int)toNumber(ctx, a) & (int)toNumber(ctx, b);
} }
public static int or(Context ctx, Object a, Object b) throws InterruptedException { public static int or(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) | (int)toNumber(ctx, b); return (int)toNumber(ctx, a) | (int)toNumber(ctx, b);
} }
public static int xor(Context ctx, Object a, Object b) throws InterruptedException { public static int xor(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b); return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b);
} }
public static int bitwiseNot(Context ctx, Object obj) throws InterruptedException { public static int bitwiseNot(Context ctx, Object obj) {
return ~(int)toNumber(ctx, obj); return ~(int)toNumber(ctx, obj);
} }
public static int shiftLeft(Context ctx, Object a, Object b) throws InterruptedException { public static int shiftLeft(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) << (int)toNumber(ctx, b); return (int)toNumber(ctx, a) << (int)toNumber(ctx, b);
} }
public static int shiftRight(Context ctx, Object a, Object b) throws InterruptedException { public static int shiftRight(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b); return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b);
} }
public static long unsignedShiftRight(Context ctx, Object a, Object b) throws InterruptedException { public static long unsignedShiftRight(Context ctx, Object a, Object b) {
long _a = (long)toNumber(ctx, a); long _a = (long)toNumber(ctx, a);
long _b = (long)toNumber(ctx, b); long _b = (long)toNumber(ctx, b);
@@ -195,19 +209,25 @@ public class Values {
return _a >>> _b; return _a >>> _b;
} }
public static int compare(Context ctx, Object a, Object b) throws InterruptedException { public static CompareResult compare(Context ctx, Object a, Object b) {
a = toPrimitive(ctx, a, ConvertHint.VALUEOF); a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
b = toPrimitive(ctx, b, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b); if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
else return Double.compare(toNumber(ctx, a), toNumber(ctx, 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) { public static boolean not(Object obj) {
return !toBoolean(obj); return !toBoolean(obj);
} }
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) throws InterruptedException { public static boolean isInstanceOf(Context ctx, Object obj, Object proto) {
if (obj == null || obj == NULL || proto == null || proto == NULL) return false; if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
var val = getPrototype(ctx, obj); var val = getPrototype(ctx, obj);
@@ -219,7 +239,7 @@ public class Values {
return false; return false;
} }
public static Object operation(Context ctx, Operation op, Object ...args) throws InterruptedException { public static Object operation(Context ctx, Operation op, Object ...args) {
switch (op) { switch (op) {
case ADD: return add(ctx, args[0], args[1]); case ADD: return add(ctx, args[0], args[1]);
case SUBTRACT: return subtract(ctx, args[0], args[1]); case SUBTRACT: return subtract(ctx, args[0], args[1]);
@@ -236,10 +256,10 @@ public class Values {
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]); case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
case LOOSE_NOT_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: return compare(ctx, args[0], args[1]).greater();
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0; case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual();
case LESS: return compare(ctx, args[0], args[1]) < 0; case LESS: return compare(ctx, args[0], args[1]).less();
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0; case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
case INVERSE: return bitwiseNot(ctx, args[0]); case INVERSE: return bitwiseNot(ctx, args[0]);
case NOT: return not(args[0]); case NOT: return not(args[0]);
@@ -260,7 +280,7 @@ public class Values {
} }
} }
public static Object getMember(Context ctx, Object obj, Object key) throws InterruptedException { public static Object getMember(Context ctx, Object obj, Object key) {
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
@@ -276,25 +296,30 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
if (proto == null) return key.equals("__proto__") ? NULL : null; if (proto == null) return "__proto__".equals(key) ? NULL : null;
else if (key != null && key.equals("__proto__")) return proto; else if (key != null && "__proto__".equals(key)) return proto;
else return proto.getMember(ctx, key, obj); else return proto.getMember(ctx, key, obj);
} }
public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException { 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); 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 undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); 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); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
return proto.setMember(ctx, key, val, obj, true); return proto.setMember(ctx, key, val, obj, true);
} }
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) throws InterruptedException { public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) {
if (obj == null || obj == NULL) return false; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); 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 (isObject(obj)) return object(obj).hasMember(ctx, key, own);
if (obj instanceof String && key instanceof Number) { if (obj instanceof String && key instanceof Number) {
@@ -308,31 +333,31 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
return proto != null && proto.hasMember(ctx, key, own); return proto != null && proto.hasMember(ctx, key, own);
} }
public static boolean deleteMember(Context ctx, Object obj, Object key) throws InterruptedException { public static boolean deleteMember(Context ctx, Object obj, Object key) {
if (obj == null || obj == NULL) return false; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (isObject(obj)) return object(obj).deleteMember(ctx, key); if (isObject(obj)) return object(obj).deleteMember(ctx, key);
else return false; else return false;
} }
public static ObjectValue getPrototype(Context ctx, Object obj) throws InterruptedException { public static ObjectValue getPrototype(Context ctx, Object obj) {
if (obj == null || obj == NULL) return null; if (obj == null || obj == NULL) return null;
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
if (isObject(obj)) return object(obj).getPrototype(ctx); if (isObject(obj)) return object(obj).getPrototype(ctx);
if (ctx == null) return null; if (ctx == null) return null;
if (obj instanceof String) return ctx.env.proto("string"); if (obj instanceof String) return ctx.environment().proto("string");
else if (obj instanceof Number) return ctx.env.proto("number"); else if (obj instanceof Number) return ctx.environment().proto("number");
else if (obj instanceof Boolean) return ctx.env.proto("bool"); else if (obj instanceof Boolean) return ctx.environment().proto("bool");
else if (obj instanceof Symbol) return ctx.env.proto("symbol"); else if (obj instanceof Symbol) return ctx.environment().proto("symbol");
return null; return null;
} }
public static boolean setPrototype(Context ctx, Object obj, Object proto) throws InterruptedException { public static boolean setPrototype(Context ctx, Object obj, Object proto) {
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
return isObject(obj) && object(obj).setPrototype(ctx, proto); return isObject(obj) && object(obj).setPrototype(ctx, proto);
} }
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException { public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) {
List<Object> res = new ArrayList<>(); List<Object> res = new ArrayList<>();
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable); if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
@@ -352,7 +377,7 @@ public class Values {
return res; return res;
} }
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) {
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key); if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
else if (obj instanceof String && key instanceof Number) { else if (obj instanceof String && key instanceof Number) {
var i = ((Number)key).intValue(); var i = ((Number)key).intValue();
@@ -370,19 +395,24 @@ public class Values {
else return null; else return null;
} }
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args); return function(func).call(ctx, thisArg, args);
} }
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { public static Object callNew(Context ctx, Object func, Object ...args) {
var res = new ObjectValue(); var res = new ObjectValue();
var proto = Values.getMember(ctx, func, "prototype"); try {
res.setPrototype(ctx, proto); var proto = Values.getMember(ctx, func, "prototype");
res.setPrototype(ctx, proto);
var ret = call(ctx, func, res, args); var ret = call(ctx, func, res, args);
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret; if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res; return res;
}
catch (IllegalArgumentException e) {
throw EngineException.ofType("Tried to call new on an invalid constructor.");
}
} }
public static boolean strictEquals(Context ctx, Object a, Object b) { public static boolean strictEquals(Context ctx, Object a, Object b) {
@@ -395,7 +425,7 @@ public class Values {
return a == b || a.equals(b); return a == b || a.equals(b);
} }
public static boolean looseEqual(Context ctx, Object a, Object b) throws InterruptedException { public static boolean looseEqual(Context ctx, Object a, Object b) {
a = normalize(ctx, a); b = normalize(ctx, b); a = normalize(ctx, a); b = normalize(ctx, b);
// In loose equality, null is equivalent to undefined // In loose equality, null is equivalent to undefined
@@ -446,14 +476,14 @@ public class Values {
if (val instanceof Class) { if (val instanceof Class) {
if (ctx == null) return null; if (ctx == null) return null;
else return ctx.env.wrappersProvider.getConstr((Class<?>)val); else return ctx.environment().wrappers.getConstr((Class<?>)val);
} }
return new NativeWrapper(val); return new NativeWrapper(val);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException { public static <T> T convert(Context ctx, Object obj, Class<T> clazz) {
if (clazz == Void.class) return null; if (clazz == Void.class) return null;
if (obj instanceof NativeWrapper) { if (obj instanceof NativeWrapper) {
@@ -517,10 +547,10 @@ public class Values {
throw new ConvertException(type(obj), clazz.getSimpleName()); 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 () -> { return () -> {
try { try {
var symbol = ctx.env.symbol("Symbol.iterator"); var symbol = ctx.environment().symbol("Symbol.iterator");
var iteratorFunc = getMember(ctx, obj, symbol); var iteratorFunc = getMember(ctx, obj, symbol);
if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
@@ -536,7 +566,7 @@ public class Values {
public boolean consumed = true; public boolean consumed = true;
private FunctionValue next = (FunctionValue)nextFunc; private FunctionValue next = (FunctionValue)nextFunc;
private void loadNext() throws InterruptedException { private void loadNext() {
if (next == null) value = null; if (next == null) value = null;
else if (consumed) { else if (consumed) {
var curr = object(next.call(ctx, iterator)); var curr = object(next.call(ctx, iterator));
@@ -551,63 +581,89 @@ public class Values {
@Override @Override
public boolean hasNext() { public boolean hasNext() {
try { loadNext();
loadNext(); return next != null;
return next != null;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
} }
@Override @Override
public Object next() { public Object next() {
try { loadNext();
loadNext(); var res = value;
var res = value; value = null;
value = null; consumed = true;
consumed = true; return res;
return res;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
} }
}; };
} }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
catch (IllegalArgumentException | NullPointerException e) { catch (IllegalArgumentException | NullPointerException e) {
return Collections.emptyIterator(); return Collections.emptyIterator();
} }
}; };
} }
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException { public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue(); var res = new ObjectValue();
try { try {
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator"); var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
} }
catch (IllegalArgumentException | NullPointerException e) { } catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> { res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true)); if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else return new ObjectValue(ctx, Map.of("value", it.next())); else {
var obj = new ObjectValue();
obj.defineProperty(_ctx, "value", it.next());
return obj;
}
})); }));
return res; return res;
} }
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException { public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
return fromJavaIterator(ctx, it.iterator()); return toJSIterator(ctx, it.iterator());
} }
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException { 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);
return;
}
if (passed.contains(val)) { if (passed.contains(val)) {
System.out.print("[circular]"); System.out.print("[circular]");
return; return;
@@ -616,10 +672,7 @@ public class Values {
var printed = true; var printed = true;
if (val instanceof FunctionValue) { if (val instanceof FunctionValue) {
System.out.print("function "); System.out.print(val.toString());
var name = Values.getMember(ctx, val, "name");
if (name != null) System.out.print(Values.toString(ctx, name));
System.out.print("(...)");
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null; var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
if (loc != null) System.out.print(" @ " + loc); if (loc != null) System.out.print(" @ " + loc);
@@ -649,7 +702,7 @@ public class Values {
passed.add(val); passed.add(val);
var obj = (ObjectValue)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("{}"); if (!printed) System.out.println("{}");
} }
else { else {
@@ -671,22 +724,24 @@ public class Values {
for (int i = 0; i < tab; i++) System.out.print(" "); for (int i = 0; i < tab; i++) System.out.print(" ");
System.out.print("}"); System.out.print("}");
passed.remove(val);
} }
passed.remove(val);
} }
else if (val == null) System.out.print("undefined"); else if (val == null) System.out.print("undefined");
else if (val == Values.NULL) System.out.print("null"); else if (val == Values.NULL) System.out.print("null");
else if (val instanceof String) System.out.print("'" + val + "'"); else if (val instanceof String) System.out.print("'" + val + "'");
else System.out.print(Values.toString(ctx, val)); else System.out.print(Values.toString(ctx, val));
} }
public static void printValue(Context ctx, Object val) throws InterruptedException { public static void printValue(Context ctx, Object val) {
printValue(ctx, val, new HashSet<>(), 0); printValue(ctx, val, new HashSet<>(), 0);
} }
public static void printError(RuntimeException err, String prefix) throws InterruptedException { public static void printError(RuntimeException err, String prefix) {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
try { try {
if (err instanceof EngineException) { if (err instanceof EngineException) {
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx)); var ee = ((EngineException)err);
System.out.println(prefix + " " + ee.toString(new Context(ee.engine, ee.env)));
} }
else if (err instanceof SyntaxException) { else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg); System.out.println("Syntax error:" + ((SyntaxException)err).msg);

View File

@@ -1,7 +1,9 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public interface Awaitable<T> { public interface Awaitable<T> {
T await() throws FinishedException, InterruptedException; T await() throws FinishedException;
default Observable<T> toObservable() { default Observable<T> toObservable() {
return sub -> { return sub -> {
@@ -10,9 +12,7 @@ public interface Awaitable<T> {
sub.next(await()); sub.next(await());
sub.finish(); sub.finish();
} }
catch (InterruptedException | FinishedException e) { catch (InterruptException | FinishedException e) { sub.finish(); }
sub.finish();
}
catch (RuntimeException e) { catch (RuntimeException e) {
sub.error(e); sub.error(e);
} }

View File

@@ -19,7 +19,7 @@ public class DataNotifier<T> implements Awaitable<T> {
isErr = false; isErr = false;
notifier.next(); notifier.next();
} }
public T await() throws InterruptedException { public T await() {
notifier.await(); notifier.await();
try { try {

View File

@@ -1,5 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Notifier { public class Notifier {
private boolean ok = false; private boolean ok = false;
@@ -7,8 +9,11 @@ public class Notifier {
ok = true; ok = true;
notifyAll(); notifyAll();
} }
public synchronized void await() throws InterruptedException { public synchronized void await() {
while (!ok) wait(); try {
ok = false; while (!ok) wait();
ok = false;
}
catch (InterruptedException e) { throw new InterruptException(e); }
} }
} }

View File

@@ -5,35 +5,68 @@ import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException { 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 final Object value;
public EngineException cause; public EngineException cause;
public Context ctx = null; public Environment env = null;
public final List<String> stackTrace = new ArrayList<>(); public Engine engine = null;
public final List<StackElement> stackTrace = new ArrayList<>();
public EngineException add(String name, Location location) { public EngineException add(Context ctx, String name, Location location) {
var res = ""; var el = new StackElement(ctx, location, name);
if (el.function == null && el.location == null) return this;
if (location != null) res += "at " + location.toString() + " "; setCtx(ctx.environment(), ctx.engine);
if (name != null && !name.equals("")) res += "in " + name + " "; stackTrace.add(el);
this.stackTrace.add(res.trim());
return this; return this;
} }
public EngineException setCause(EngineException cause) { public EngineException setCause(EngineException cause) {
this.cause = cause; this.cause = cause;
return this; return this;
} }
public EngineException setContext(Context ctx) { public EngineException setCtx(Environment env, Engine engine) {
this.ctx = ctx; if (this.env == null) this.env = env;
if (this.engine == null) this.engine = engine;
return this; return this;
} }
public String toString(Context ctx) throws InterruptedException { public String toString(Context ctx) {
var ss = new StringBuilder(); var ss = new StringBuilder();
try { try {
ss.append(Values.toString(ctx, value)).append('\n'); ss.append(Values.toString(ctx, value)).append('\n');
@@ -41,10 +74,10 @@ public class EngineException extends RuntimeException {
catch (EngineException e) { catch (EngineException e) {
ss.append("[Error while stringifying]\n"); ss.append("[Error while stringifying]\n");
} }
// for (var line : stackTrace) { 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'); if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1); ss.deleteCharAt(ss.length() - 1);
return ss.toString(); return ss.toString();
} }
@@ -70,7 +103,7 @@ public class EngineException extends RuntimeException {
return new EngineException(err(null, msg, PlaceholderProto.ERROR)); return new EngineException(err(null, msg, PlaceholderProto.ERROR));
} }
public static EngineException ofSyntax(SyntaxException e) { 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) { public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.exceptions;
public class InterruptException extends RuntimeException {
public InterruptException() { }
public InterruptException(Throwable e) {
super(e);
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.exceptions;
public class UncheckedException extends RuntimeException {
public UncheckedException(Throwable err) {
super(err);
}
}

View File

@@ -0,0 +1,9 @@
package me.topchetoeu.jscript.exceptions;
import java.io.IOException;
public class UncheckedIOException extends RuntimeException {
public UncheckedIOException(IOException e) {
super(e);
}
}

View File

@@ -1,7 +1,13 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
public enum EntryType { public enum EntryType {
NONE, NONE("none"),
FILE, FILE("file"),
FOLDER, FOLDER("folder");
public final String name;
private EntryType(String name) {
this.name = name;
}
} }

View File

@@ -1,27 +1,37 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.IOException; import me.topchetoeu.jscript.Buffer;
public interface File { public interface File {
int read() throws IOException, InterruptedException; int read(byte[] buff);
boolean write(byte val) throws IOException, InterruptedException; void write(byte[] buff);
long tell() throws IOException, InterruptedException; long seek(long offset, int pos);
void seek(long offset, int pos) throws IOException, InterruptedException; void close();
void close() throws IOException, InterruptedException;
Permissions perms();
default String readToString() throws IOException, InterruptedException { default String readToString() {
seek(0, 2); long len = seek(0, 2);
long len = tell();
if (len < 0) return null; if (len < 0) return null;
seek(0, 0); seek(0, 0);
byte[] res = new byte[(int)len];
for (var i = 0; i < len; i++) { byte[] res = new byte[(int)len];
res[i] = (byte)read(); len = read(res);
}
return new String(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());
}
} }

Some files were not shown because too many files have changed in this diff Show More