Compare commits

...

87 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
164 changed files with 10488 additions and 9983 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@@ -15,7 +15,7 @@ jobs:
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: '17' 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:

29
.gitignore vendored
View File

@@ -1,11 +1,18 @@
.vscode *
.gradle
.ignore !/src
/out !/src/**/*
/build
/bin /src/assets/js/ts.js
/dst
/*.js !/tests
!/build.js !/tests/**/*
/dead-code
/Metadata.java !/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/build.js
!/LICENSE
!/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);
} }
})(); })();

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

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]>;

File diff suppressed because it is too large Load Diff

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

@@ -1,6 +1,7 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.File; import java.io.File;
import java.nio.file.Path;
public class Filename { public class Filename {
public final String protocol; public final String protocol;
@@ -40,9 +41,7 @@ public class Filename {
return true; return true;
} }
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) { public Filename(String protocol, String path) {
@@ -51,4 +50,16 @@ public class Filename {
this.protocol = protocol; this.protocol = protocol;
this.path = path; 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

@@ -71,4 +71,23 @@ public class Location implements Comparable<Location> {
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

@@ -8,7 +8,6 @@ import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Context; 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.StackData;
import me.topchetoeu.jscript.engine.debug.DebugServer; import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger; import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
@@ -19,15 +18,14 @@ import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.filesystem.Mode;
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.lib.Internals;
import me.topchetoeu.jscript.modules.ModuleRepo;
public class Main { public class Main {
static Thread engineTask, debugTask; public static class Printer implements Observer<Object> {
static Engine engine;
static Environment env;
static int j = 0;
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) { public void next(Object data) {
Values.printValue(null, data); Values.printValue(null, data);
System.out.println(); System.out.println();
@@ -37,115 +35,134 @@ public class Main {
Values.printError(err, null); Values.printError(err, null);
} }
@Override
public void finish() { public void finish() {
engineTask.interrupt(); engineTask.interrupt();
} }
}; }
public static void main(String args[]) { static Thread engineTask, debugTask;
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); static Engine engine = new Engine(true);
engine = new Engine(true); static DebugServer debugServer = new DebugServer();
static Environment environment = new Environment(null, null, null);
env = new Environment(null, null, null); static int j = 0;
var exited = new boolean[1]; static boolean exited = false;
var server = new DebugServer(); static String[] args;
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
engineTask = engine.start();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
new Internals().apply(env);
env.global.define("exit", _ctx -> {
exited[0] = true;
throw new InterruptException();
});
env.global.define("go", _ctx -> {
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");
}
});
// TODO: make better API
env.global.define(true, new NativeFunction("include", (_ctx, th, __args) -> {
try {
var currFilename = StackData.peekFrame(_ctx).function.loc().filename();
var loc = Path.of("").toAbsolutePath();
if (currFilename.protocol.equals("file")) loc = Path.of(currFilename.path).getParent();
var path = loc.resolve(Path.of(__args.length >= 1 ? Values.toString(_ctx, __args[0]) : ""));
var src = Files.readString(path);
var func = _ctx.compile(Filename.fromFile(path.toFile()), src);
var callArgs = new ArrayValue();
if (__args.length >= 2 && __args[1] instanceof ArrayValue) callArgs = (ArrayValue)__args[1];
return func.call(_ctx, null, callArgs);
}
catch (IOException e) { throw EngineException.ofError("IOError", "Couldn't open file."); }
}));
return null;
}), null).await();
private static void reader() {
try { try {
var tsEnv = env.child(); for (var arg : args) {
try {
if (arg.equals("--ts")) initTypescript();
else {
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file.toFile()),
raw, null
).await();
Values.printValue(null, res);
System.out.println();
}
}
catch (EngineException e) { Values.printError(e, null); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
if (raw == null) break;
var res = engine.pushMsg(
false, environment,
new Filename("jscript", "repl/" + i + ".js"),
raw, null
).await();
Values.printValue(null, res);
System.out.println();
}
catch (EngineException e) { Values.printError(e, null); }
catch (SyntaxException e) { Values.printError(e, null); }
}
}
catch (IOException e) {
System.out.println(e.toString());
exited = true;
}
catch (RuntimeException ex) {
if (!exited) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited) {
debugTask.interrupt();
engineTask.interrupt();
}
}
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()); tsEnv.global.define(null, "module", false, new ObjectValue());
var bsEnv = Internals.apply(new Environment(null, null, null));
bsEnv.stackVisible = false;
engine.pushMsg( engine.pushMsg(
false, new Context(engine).pushEnv(tsEnv), false, tsEnv,
new Filename("jscript", "ts.js"), new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null Reading.resourceToString("js/ts.js"), null
).await(); ).await();
System.out.println("Loaded typescript!"); System.out.println("Loaded typescript!");
var ctx = new Context(engine).pushEnv(env.child());
engine.pushMsg( engine.pushMsg(
false, ctx, false, bsEnv,
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) tsEnv.global.get(new Context(engine, bsEnv), "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
).await(); ).await();
} }
catch (EngineException e) { catch (EngineException e) {
Values.printError(e, "(while initializing TS)"); Values.printError(e, "(while initializing TS)");
} }
}
var reader = new Thread(() -> { public static void main(String args[]) {
try { System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
for (var arg : args) {
try { Main.args = args;
var file = Path.of(arg); var reader = new Thread(Main::reader);
var raw = Files.readString(file);
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await()); initEnv();
} initEngine();
catch (EngineException e) { Values.printError(e, ""); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
if (raw == null) break;
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
}
catch (IOException e) { exited[0] = true; }
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (!exited[0]) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited[0]) debugTask.interrupt();
});
reader.setDaemon(true); reader.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

@@ -15,22 +15,13 @@ public class Reading {
} }
public static String streamToString(InputStream in) { public static String streamToString(InputStream in) {
try { try { return new String(in.readAllBytes()); }
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 (Throwable e) { throw new UncheckedException(e); } 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) { public static String resourceToString(String name) {
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); return streamToString(resourceToStream(name));
if (str == null) return null;
return streamToString(str);
} }
} }

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,38 +1,66 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.Map; import java.util.HashMap;
import java.util.TreeSet; import java.util.Map;
import java.util.Vector; import java.util.TreeSet;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.Location;
public class CompileTarget { import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
public final Vector<Instruction> target = new Vector<>(); import me.topchetoeu.jscript.engine.Environment;
public final Map<Long, FunctionBody> functions; import me.topchetoeu.jscript.engine.values.CodeFunction;
public final TreeSet<Location> breakpoints;
public class CompileTarget {
public Instruction add(Instruction instr) { public final Vector<Instruction> target = new Vector<>();
target.add(instr); public final Map<Long, FunctionBody> functions;
return instr; public final TreeSet<Location> breakpoints;
} private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
public Instruction set(int i, Instruction instr) {
return target.set(i, instr); public Instruction add(Instruction instr) {
} target.add(instr);
public void setDebug(int i) { return instr;
breakpoints.add(target.get(i).location); }
} public Instruction set(int i, Instruction instr) {
public void setDebug() { return target.set(i, instr);
setDebug(target.size() - 1); }
} public void setDebug(int i, BreakpointType type) {
public Instruction get(int i) { var instr = target.get(i);
return target.get(i); instr.breakpoint = type;
}
public int size() { return target.size(); } if (type == BreakpointType.NONE) {
breakpoints.remove(target.get(i).location);
public Instruction[] array() { return target.toArray(Instruction[]::new); } bpToInstr.remove(instr.location, instr);
}
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) { else {
this.functions = functions; breakpoints.add(target.get(i).location);
this.breakpoints = breakpoints;
} var old = bpToInstr.put(instr.location, instr);
} if (old != null) old.breakpoint = BreakpointType.NONE;
}
}
public void setDebug(BreakpointType type) {
setDebug(target.size() - 1, type);
}
public Instruction get(int i) {
return target.get(i);
}
public int size() { return target.size(); }
public Location lastLoc(Location fallback) {
if (target.size() == 0) return fallback;
else return target.get(target.size() - 1).location;
}
public Instruction[] array() { return target.toArray(Instruction[]::new); }
public FunctionBody body() {
return functions.get(0l);
}
public CodeFunction func(Environment env) {
return new CodeFunction(env, "", body());
}
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions;
this.breakpoints = breakpoints;
}
}

View File

@@ -1,68 +1,54 @@
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; 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.setDebug(start);
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);
} }
if (end != null) {
target.add(Instruction.nop().locate(end));
target.setDebug();
}
}
@Override
public Statement optimize() {
var res = new Vector<Statement>(statements.length);
for (var i = 0; i < statements.length; i++) {
var stm = statements[i].optimize();
if (i < statements.length - 1 && stm.pure()) continue;
res.add(stm);
if (
stm instanceof ContinueStatement ||
stm instanceof ReturnStatement ||
stm instanceof ThrowStatement ||
stm instanceof ContinueStatement
) break;
}
if (res.size() == 1) return res.get(0);
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
} }
public CompoundStatement setEnd(Location loc) { public CompoundStatement setEnd(Location loc) {
@@ -70,8 +56,9 @@ public class CompoundStatement extends Statement {
return this; return this;
} }
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

@@ -3,13 +3,25 @@ package me.topchetoeu.jscript.compilation;
public class FunctionBody { public class FunctionBody {
public final Instruction[] instructions; public final Instruction[] instructions;
public final String[] captureNames, localNames; public final String[] captureNames, localNames;
public final int localsN, argsN;
public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) { public FunctionBody(int localsN, int argsN, Instruction[] instructions, String[] captureNames, String[] localNames) {
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions; this.instructions = instructions;
this.captureNames = captureNames; this.captureNames = captureNames;
this.localNames = localNames; this.localNames = localNames;
} }
public FunctionBody(Instruction[] instructions) { public FunctionBody(int localsN, int argsN, Instruction[] instructions) {
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions;
this.captureNames = new String[0];
this.localNames = new String[0];
}
public FunctionBody(Instruction... instructions) {
this.argsN = 0;
this.localsN = 2;
this.instructions = instructions; this.instructions = instructions;
this.captureNames = new String[0]; this.captureNames = new String[0];
this.localNames = 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,51 +45,30 @@ 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 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;
@@ -129,26 +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 ret(Location loc) {
return new Instruction(loc, Type.RETURN);
}
public static Instruction debug(Location loc) {
return new Instruction(loc, Type.NOP, "debug");
} }
public static Instruction nop(Object ...params) { public static Instruction nop(Location loc, Object ...params) {
for (var param : params) { for (var param : params) {
if (param instanceof String) continue; if (param instanceof String) continue;
if (param instanceof Boolean) continue; if (param instanceof Boolean) continue;
@@ -158,109 +143,104 @@ public class Instruction {
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls."); 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(boolean forInFormat) { public static Instruction keys(Location loc, boolean forInFormat) {
return new Instruction(null, Type.KEYS, forInFormat); 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.setDebug(start);
if (target.size() != start) {
target.get(start).locate(loc());
target.setDebug(start, type);
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.NONE);
} }
public Location loc() { return _loc; } 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

@@ -1,58 +1,52 @@
package me.topchetoeu.jscript.compilation; 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.values.FunctionStatement; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableDeclareStatement extends Statement {
public static class Pair { public class VariableDeclareStatement extends Statement {
public final String name; public static class Pair {
public final Statement value; public final String name;
public final Location location; public final Statement value;
public final Location location;
public Pair(String name, Statement value, Location location) {
this.name = name; public Pair(String name, Statement value, Location location) {
this.value = value; this.name = name;
this.location = location; this.value = value;
} this.location = location;
} }
}
public final List<Pair> values;
public final List<Pair> values;
@Override
public void declare(ScopeRecord varsScope) { @Override
for (var key : values) { public void declare(ScopeRecord varsScope) {
varsScope.define(key.name); for (var key : values) {
} varsScope.define(key.name);
} }
@Override }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @Override
for (var entry : values) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (entry.name == null) continue; for (var entry : values) {
var key = scope.getKey(entry.name); if (entry.name == null) continue;
int start = target.size(); var key = scope.getKey(entry.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location)); if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
if (entry.value instanceof FunctionStatement) { if (entry.value != null) {
((FunctionStatement)entry.value).compile(target, scope, entry.name, false); FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
target.add(Instruction.storeVar(key).locate(entry.location)); target.add(Instruction.storeVar(entry.location, key));
} }
else if (entry.value != null) { }
entry.value.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(entry.location)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
if (target.size() != start) target.setDebug(start); public VariableDeclareStatement(Location loc, List<Pair> values) {
} super(loc);
this.values = values;
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); }
} }
public VariableDeclareStatement(Location loc, List<Pair> values) {
super(loc);
this.values = values;
}
}

View File

@@ -1,22 +1,22 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class BreakStatement extends Statement { public class BreakStatement extends Statement {
public final String label; public final String label;
@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) {
super(loc); super(loc);
this.label = label; this.label = label;
} }
} }

View File

@@ -1,22 +1,22 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ContinueStatement extends Statement { public class ContinueStatement extends Statement {
public final String label; public final String label;
@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) {
super(loc); super(loc);
this.label = label; this.label = label;
} }
} }

View File

@@ -1,19 +1,19 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; 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) {
super(loc); super(loc);
} }
} }

View File

@@ -1,27 +1,27 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DeleteStatement extends Statement { public class DeleteStatement extends Statement {
public final Statement key; public final Statement key;
public final Statement value; public final Statement value;
@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);
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) {
super(loc); super(loc);
this.key = key; this.key = key;
this.value = value; this.value = value;
} }
} }

View File

@@ -1,79 +1,37 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction.BreakpointType;
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 DoWhileStatement extends Statement {
public final Statement condition, body;
public class DoWhileStatement extends Statement { public final String label;
public final Statement condition, body;
public final String label; @Override
public void declare(ScopeRecord globScope) {
@Override body.declare(globScope);
public void declare(ScopeRecord globScope) { }
body.declare(globScope);
} @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@Override int start = target.size();
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { body.compile(target, scope, false, BreakpointType.STEP_OVER);
if (condition instanceof ConstantStatement) { int mid = target.size();
int start = target.size(); condition.compile(target, scope, true, BreakpointType.STEP_OVER);
body.compile(target, scope, false); int end = target.size();
int end = target.size();
if (Values.toBoolean(((ConstantStatement)condition).value)) { WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1); target.add(Instruction.jmpIf(loc(), start - end));
} }
else {
target.add(Instruction.jmp(start - end).locate(loc())); public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1); super(loc);
} this.label = label;
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); this.condition = condition;
return; this.body = body;
} }
}
int start = target.size();
body.compileWithDebug(target, scope, false);
int mid = target.size();
condition.compile(target, scope, true);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(start - end).locate(loc()));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof CompoundStatement) {
var comp = (CompoundStatement)b;
if (comp.statements.length > 0) {
var last = comp.statements[comp.statements.length - 1];
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
if (last instanceof BreakStatement) {
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
return new CompoundStatement(loc());
}
}
}
else if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
else return new DoWhileStatement(loc(), label, cond, b);
}
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
}

View File

@@ -1,73 +1,73 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ForInStatement extends Statement {
public final String varName; public class ForInStatement extends Statement {
public final boolean isDeclaration; public final String varName;
public final Statement varValue, object, body; public final boolean isDeclaration;
public final String label; public final Statement varValue, object, body;
public final Location varLocation; public final String label;
public final Location varLocation;
@Override
public void declare(ScopeRecord globScope) { @Override
body.declare(globScope); public void declare(ScopeRecord globScope) {
if (isDeclaration) globScope.define(varName); body.declare(globScope);
} if (isDeclaration) globScope.define(varName);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @Override
var key = scope.getKey(varName); public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var key = scope.getKey(varName);
int first = target.size();
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) {
varValue.compile(target, scope, true); if (varValue != null) {
target.add(Instruction.storeVar(scope.getKey(varName))); varValue.compile(target, scope, true);
} target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
}
object.compileWithDebug(target, scope, true);
target.add(Instruction.keys(true)); object.compile(target, scope, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(loc(), true));
int start = target.size();
target.add(Instruction.dup()); int start = target.size();
target.add(Instruction.loadValue(null)); target.add(Instruction.dup(loc()));
target.add(Instruction.operation(Operation.EQUALS)); target.add(Instruction.loadValue(loc(), null));
int mid = target.size(); target.add(Instruction.operation(loc(), Operation.EQUALS));
target.add(Instruction.nop()); int mid = target.size();
target.add(Instruction.nop(loc()));
target.add(Instruction.loadMember("value").locate(varLocation));
target.setDebug(); target.add(Instruction.loadMember(varLocation, "value"));
target.add(Instruction.storeVar(key)); target.add(Instruction.storeVar(object.loc(), key));
target.setDebug(BreakpointType.STEP_OVER);
body.compileWithDebug(target, scope, false);
body.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size();
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));
target.add(Instruction.discard()); target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIf(end - mid + 1)); target.add(Instruction.discard(loc()));
if (pollute) target.add(Instruction.loadValue(null)); target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
target.get(first).locate(loc()); if (pollute) target.add(Instruction.loadValue(loc(), null));
target.setDebug(first); target.get(first).locate(loc());
} }
public ForInStatement(Location loc, Location varLocation, 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.varLocation = varLocation;
this.label = label; this.label = label;
this.isDeclaration = isDecl; this.isDeclaration = isDecl;
this.varName = varName; this.varName = varName;
this.varValue = varValue; this.varValue = varValue;
this.object = object; this.object = object;
this.body = body; this.body = body;
} }
} }

View File

@@ -1,95 +1,47 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.CompileTarget;
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 final Statement declaration, assignment, condition, body;
public class ForStatement extends Statement { public final String label;
public final Statement declaration, assignment, condition, body;
public final String label; @Override
public void declare(ScopeRecord globScope) {
@Override declaration.declare(globScope);
public void declare(ScopeRecord globScope) { body.declare(globScope);
declaration.declare(globScope); }
body.declare(globScope); @Override
} public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@Override declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
declaration.compile(target, scope, false); int start = target.size();
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
if (condition instanceof ConstantStatement) { int mid = target.size();
if (Values.toBoolean(((ConstantStatement)condition).value)) { target.add(Instruction.nop(null));
int start = target.size(); body.compile(target, scope, false, BreakpointType.STEP_OVER);
body.compile(target, scope, false); int beforeAssign = target.size();
int mid = target.size(); assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
assignment.compileWithDebug(target, scope, false); int end = target.size();
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); target.add(Instruction.jmp(loc(), start - end));
} target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
return; if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
int start = target.size(); public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
condition.compile(target, scope, true); super(loc);
int mid = target.size(); this.label = label;
target.add(Instruction.nop()); this.declaration = declaration;
body.compile(target, scope, false); this.condition = condition;
int beforeAssign = target.size(); this.assignment = assignment;
assignment.compileWithDebug(target, scope, false); this.body = body;
int end = target.size(); }
}
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(start - end).locate(loc()));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
}
@Override
public Statement optimize() {
var decl = declaration.optimize();
var asgn = assignment.optimize();
var cond = condition.optimize();
var b = body.optimize();
if (asgn.pure()) {
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
else return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, b)
).optimize();
}
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
);
else if (b instanceof BreakStatement) return decl;
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
else return new ForStatement(loc(), label, decl, cond, asgn, b);
}
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
super(loc);
this.label = label;
this.declaration = declaration;
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
return new CompoundStatement(loc,
declaration,
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
body,
increment
))
);
}
}

View File

@@ -1,77 +1,52 @@
package me.topchetoeu.jscript.compilation.control; 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.DiscardStatement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class IfStatement extends Statement {
import me.topchetoeu.jscript.engine.values.Values; public final Statement condition, body, elseBody;
public class IfStatement extends Statement { @Override
public final Statement condition, body, elseBody; public void declare(ScopeRecord globScope) {
body.declare(globScope);
@Override if (elseBody != null) elseBody.declare(globScope);
public void declare(ScopeRecord globScope) { }
body.declare(globScope);
if (elseBody != null) elseBody.declare(globScope); @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
} condition.compile(target, scope, true, breakpoint);
@Override if (elseBody == null) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { int i = target.size();
if (condition instanceof ConstantStatement) { target.add(Instruction.nop(null));
if (Values.not(((ConstantStatement)condition).value)) { body.compile(target, scope, pollute, breakpoint);
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute); int endI = target.size();
} target.set(i, Instruction.jmpIfNot(loc(), endI - i));
else { }
body.compileWithDebug(target, scope, pollute); else {
} int start = target.size();
target.add(Instruction.nop(null));
return; body.compile(target, scope, pollute, breakpoint);
} target.add(Instruction.nop(null));
int mid = target.size();
condition.compile(target, scope, true); elseBody.compile(target, scope, pollute, breakpoint);
int end = target.size();
if (elseBody == null) {
int i = target.size(); target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.add(Instruction.nop()); target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
body.compileWithDebug(target, scope, pollute); }
int endI = target.size(); }
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc())); @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
} compile(target, scope, pollute, BreakpointType.STEP_IN);
else { }
int start = target.size();
target.add(Instruction.nop()); public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
body.compileWithDebug(target, scope, pollute); super(loc);
target.add(Instruction.nop()); this.condition = condition;
int mid = target.size(); this.body = body;
elseBody.compileWithDebug(target, scope, pollute); this.elseBody = elseBody;
int end = target.size(); }
}
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc()));
}
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
var e = elseBody == null ? null : elseBody.optimize();
if (b.pure()) b = new CompoundStatement(null);
if (e != null && e.pure()) e = null;
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
else return new IfStatement(loc(), cond, b, e);
}
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
super(loc);
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
}
}

View File

@@ -1,23 +1,23 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ReturnStatement extends Statement { public class ReturnStatement extends Statement {
public final Statement value; public final Statement value;
@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) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
} }

View File

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

View File

@@ -1,22 +1,22 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ThrowStatement extends Statement { public class ThrowStatement extends Statement {
public final Statement value; public final Statement value;
@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) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
} }

View File

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

View File

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

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

View File

@@ -1,32 +1,32 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement; 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.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement { public class ChangeStatement extends Statement {
public final AssignableStatement value; public final AssignableStatement value;
public final double addAmount; public final double addAmount;
public final boolean postfix; public final boolean postfix;
@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, true); 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) { else if (postfix) {
target.add(Instruction.loadValue(addAmount)); target.add(Instruction.loadValue(loc(), addAmount));
target.add(Instruction.operation(Operation.SUBTRACT)); 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) {
super(loc); super(loc);
this.value = value; this.value = value;
this.addAmount = addAmount; this.addAmount = addAmount;
this.postfix = postfix; this.postfix = postfix;
} }
} }

View File

@@ -1,38 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CommaStatement extends Statement {
public final Statement[] values;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var i = 0; i < values.length; i++) {
values[i].compile(target, scope, i == values.length - 1 && pollute);
}
}
@Override
public Statement optimize() {
var res = new Vector<Statement>(values.length);
for (var i = 0; i < values.length; i++) {
var stm = values[i].optimize();
if (i < values.length - 1 && stm.pure()) continue;
res.add(stm);
}
if (res.size() == 1) return res.get(0);
else return new CommaStatement(loc(), res.toArray(Statement[]::new));
}
public CommaStatement(Location loc, Statement ...args) {
super(loc);
this.values = args;
}
}

View File

@@ -1,24 +1,23 @@
package me.topchetoeu.jscript.compilation.values; 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;
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.engine.scope.ScopeRecord; 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(loc(), value));
if (pollute) target.add(Instruction.loadValue(value).locate(loc())); }
}
public ConstantStatement(Location loc, Object val) {
public ConstantStatement(Location loc, Object val) { super(loc);
super(loc); this.value = val;
this.value = val; }
} }
}

View File

@@ -1,27 +1,24 @@
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.engine.scope.ScopeRecord; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
public class DiscardStatement extends Statement { import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public final Statement value;
public class DiscardStatement extends Statement {
@Override public final Statement value;
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false); @Override public boolean pure() { return value.pure(); }
} @Override
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
public Statement optimize() { value.compile(target, scope, false);
if (value == null) return this; if (pollute) target.add(Instruction.loadValue(loc(), null));
var val = value.optimize(); }
if (val.pure()) return new ConstantStatement(loc(), null);
else return new DiscardStatement(loc(), val); public DiscardStatement(Location loc, Statement val) {
} super(loc);
this.value = val;
public DiscardStatement(Location loc, Statement val) { }
super(loc); }
this.value = val;
}
}

View File

@@ -1,112 +1,139 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import java.util.Random; 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.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.Type; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class FunctionStatement extends Statement {
public final CompoundStatement body; public class FunctionStatement extends Statement {
public final String name; public final CompoundStatement body;
public final String[] args; public final String varName;
public final String[] args;
private static Random rand = new Random(); public final boolean statement;
public final Location end;
@Override
public boolean pure() { return name == null; } private static Random rand = new Random();
@Override @Override public boolean pure() { return varName == null && statement; }
public void declare(ScopeRecord scope) {
if (name != null) scope.define(name); @Override
} public void declare(ScopeRecord scope) {
if (varName != null && statement) scope.define(varName);
public static void checkBreakAndCont(CompileTarget target, int start) { }
for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) { public static void checkBreakAndCont(CompileTarget target, int start) {
if (target.get(i).is(0, "break") ) { for (int i = start; i < target.size(); i++) {
throw new SyntaxException(target.get(i).location, "Break was placed outside a loop."); if (target.get(i).type == Type.NOP) {
} if (target.get(i).is(0, "break") ) {
if (target.get(i).is(0, "cont")) { throw new SyntaxException(target.get(i).location, "Break was placed outside a loop.");
throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop."); }
} if (target.get(i).is(0, "cont")) {
} throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop.");
} }
} }
}
public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) { }
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) { private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) {
if (args[i].equals(args[j])){ for (var i = 0; i < args.length; i++) {
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."))); for (var j = 0; j < i; j++) {
return; if (args[i].equals(args[j])) {
} throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
} }
} }
var subscope = scope.child(); }
int start = target.size(); var id = rand.nextLong();
var funcTarget = new CompileTarget(target.functions, target.breakpoints); var subscope = scope.child();
var subtarget = new CompileTarget(target.functions, target.breakpoints);
subscope.define("this");
var argsVar = subscope.define("arguments"); subscope.define("this");
var argsVar = subscope.define("arguments");
if (args.length > 0) {
for (var i = 0; i < args.length; i++) { if (args.length > 0) {
funcTarget.add(Instruction.loadVar(argsVar).locate(loc())); for (var i = 0; i < args.length; i++) {
funcTarget.add(Instruction.loadMember(i).locate(loc())); subtarget.add(Instruction.loadVar(loc(), argsVar));
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc())); subtarget.add(Instruction.loadMember(loc(), i));
} subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
} }
}
if (!isStatement && this.name != null) {
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name))); if (!statement && this.varName != null) {
} subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
subtarget.setDebug(bp);
body.declare(subscope); }
body.compile(funcTarget, subscope, false);
funcTarget.add(Instruction.ret().locate(loc())); body.declare(subscope);
checkBreakAndCont(funcTarget, start); body.compile(subtarget, subscope, false);
subtarget.add(Instruction.ret(end));
var id = rand.nextLong(); checkBreakAndCont(subtarget, 0);
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures()));
target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals())); target.functions.put(id, new FunctionBody(
subscope.localsCount(), args.length,
if (name == null) name = this.name; subtarget.array(), subscope.captures(), subscope.locals()
));
if (name != null) {
target.add(Instruction.dup().locate(loc())); return id;
target.add(Instruction.loadValue("name").locate(loc())); }
target.add(Instruction.loadValue(name).locate(loc()));
target.add(Instruction.storeMember().locate(loc())); public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
} if (this.varName != null) name = this.varName;
if (this.name != null && isStatement) { var hasVar = this.varName != null && statement;
var key = scope.getKey(this.name); var hasName = name != null;
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc())); compileBody(target, scope, pollute || hasVar || hasName, bp);
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
} if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup(loc()));
} target.add(Instruction.loadValue(loc(), "name"));
@Override target.add(Instruction.loadValue(loc(), name));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { target.add(Instruction.storeMember(loc()));
compile(target, scope, null, false); }
if (!pollute) target.add(Instruction.discard().locate(loc()));
} if (hasVar) {
var key = scope.getKey(this.varName);
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
super(loc); if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
this.name = name; target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false));
}
this.args = args; }
this.body = body; 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);
this.end = end;
this.varName = varName;
this.statement = statement;
this.args = args;
this.body = body;
}
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
else stm.compile(target, scope, pollute);
}
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
else stm.compile(target, scope, pollute, bp);
}
}

View File

@@ -1,21 +1,20 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
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.Instruction; 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(loc()));
if (pollute) target.add(Instruction.loadGlob().locate(loc())); }
}
public GlobalThisStatement(Location loc) {
public GlobalThisStatement(Location loc) { super(loc);
super(loc); }
} }
}

View File

@@ -1,47 +1,48 @@
package me.topchetoeu.jscript.compilation.values; 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;
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.engine.Operation; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IndexAssignStatement extends Statement {
public final Statement object; public class IndexAssignStatement extends Statement {
public final Statement index; public final Statement object;
public final Statement value; public final Statement index;
public final Operation operation; public final Statement value;
public final Operation operation;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @Override
if (operation != null) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
object.compile(target, scope, true); if (operation != null) {
index.compile(target, scope, true); object.compile(target, scope, true);
target.add(Instruction.dup(2, 0).locate(loc())); index.compile(target, scope, true);
target.add(Instruction.dup(loc(), 2));
target.add(Instruction.loadMember().locate(loc()));
value.compile(target, scope, true); target.add(Instruction.loadMember(loc()));
target.add(Instruction.operation(operation).locate(loc())); value.compile(target, scope, true);
target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeMember(pollute).locate(loc()));
target.setDebug(); target.add(Instruction.storeMember(loc(), pollute));
} target.setDebug(BreakpointType.STEP_IN);
else { }
object.compile(target, scope, true); else {
index.compile(target, scope, true); object.compile(target, scope, true);
value.compile(target, scope, true); index.compile(target, scope, true);
value.compile(target, scope, true);
target.add(Instruction.storeMember(pollute).locate(loc()));
target.setDebug(); target.add(Instruction.storeMember(loc(), pollute));
} target.setDebug(BreakpointType.STEP_IN);
} }
}
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
super(loc); public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
this.object = object; super(loc);
this.index = index; this.object = object;
this.value = value; this.index = index;
this.operation = operation; this.value = value;
} this.operation = operation;
} }
}

View File

@@ -1,51 +1,49 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement; 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.engine.Operation; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IndexStatement extends AssignableStatement {
public final Statement object; public class IndexStatement extends AssignableStatement {
public final Statement index; public final Statement object;
public final Statement index;
@Override
public boolean pure() { return true; } @Override
public Statement toAssign(Statement val, Operation operation) {
@Override return new IndexAssignStatement(loc(), object, index, val, operation);
public Statement toAssign(Statement val, Operation operation) { }
return new IndexAssignStatement(loc(), object, index, val, operation); public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
} object.compile(target, scope, true);
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { if (dupObj) target.add(Instruction.dup(loc()));
object.compile(target, scope, true); if (index instanceof ConstantStatement) {
if (dupObj) target.add(Instruction.dup().locate(loc())); target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
if (index instanceof ConstantStatement) { target.setDebug(BreakpointType.STEP_IN);
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); return;
target.setDebug(); }
return;
} index.compile(target, scope, true);
target.add(Instruction.loadMember(loc()));
index.compile(target, scope, true); target.setDebug(BreakpointType.STEP_IN);
target.add(Instruction.loadMember().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
target.setDebug(); }
if (!pollute) target.add(Instruction.discard().locate(loc())); @Override
} public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@Override compile(target, scope, false, pollute);
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { }
compile(target, scope, false, pollute);
} public IndexStatement(Location loc, Statement object, Statement index) {
super(loc);
public IndexStatement(Location loc, Statement object, Statement index) { this.object = object;
super(loc); this.index = index;
this.object = object; }
this.index = index; public IndexStatement(Location loc, Statement object, Object index) {
} super(loc);
public IndexStatement(Location loc, Statement object, Object index) { this.object = object;
super(loc); this.index = new ConstantStatement(loc, index);
this.object = object; }
this.index = new ConstantStatement(loc, index); }
}
}

View File

@@ -1,42 +1,39 @@
package me.topchetoeu.jscript.compilation.values; 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;
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.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values; 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
} public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (first instanceof ConstantStatement) {
@Override if (Values.not(((ConstantStatement)first).value)) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { first.compile(target, scope, pollute);
if (first instanceof ConstantStatement) { }
if (Values.not(((ConstantStatement)first).value)) { else second.compile(target, scope, pollute);
first.compile(target, scope, pollute); return;
} }
else second.compile(target, scope, pollute);
return; first.compile(target, scope, true);
} if (pollute) target.add(Instruction.dup(loc()));
int start = target.size();
first.compile(target, scope, true); target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.discard(loc()));
int start = target.size(); second.compile(target, scope, pollute);
target.add(Instruction.nop()); target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
if (pollute) target.add(Instruction.discard().locate(loc())); }
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc())); public LazyAndStatement(Location loc, Statement first, Statement second) {
} super(loc);
this.first = first;
public LazyAndStatement(Location loc, Statement first, Statement second) { this.second = second;
super(loc); }
this.first = first; }
this.second = second;
}
}

View File

@@ -1,42 +1,39 @@
package me.topchetoeu.jscript.compilation.values; 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;
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.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values; 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
} public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (first instanceof ConstantStatement) {
@Override if (Values.not(((ConstantStatement)first).value)) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { second.compile(target, scope, pollute);
if (first instanceof ConstantStatement) { }
if (Values.not(((ConstantStatement)first).value)) { else first.compile(target, scope, pollute);
second.compile(target, scope, pollute); return;
} }
else first.compile(target, scope, pollute);
return; first.compile(target, scope, true);
} if (pollute) target.add(Instruction.dup(loc()));
int start = target.size();
first.compile(target, scope, true); target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.discard(loc()));
int start = target.size(); second.compile(target, scope, pollute);
target.add(Instruction.nop()); target.set(start, Instruction.jmpIf(loc(), target.size() - start));
if (pollute) target.add(Instruction.discard().locate(loc())); }
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc())); public LazyOrStatement(Location loc, Statement first, Statement second) {
} super(loc);
this.first = first;
public LazyOrStatement(Location loc, Statement first, Statement second) { this.second = second;
super(loc); }
this.first = first; }
this.second = second;
}
}

View File

@@ -1,28 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class NewStatement extends Statement {
public final Statement func;
public final Statement[] args;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
func.compile(target, scope, true);
for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.callNew(args.length).locate(loc()));
target.setDebug();
}
public NewStatement(Location loc, Statement func, Statement ...args) {
super(loc);
this.func = func;
this.args = args;
}
}

View File

@@ -1,56 +1,63 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ObjectStatement extends Statement { public class ObjectStatement extends Statement {
public final Map<Object, Statement> map; public final Map<Object, Statement> map;
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 @Override public boolean pure() {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { for (var el : map.values()) {
target.add(Instruction.loadObj().locate(loc())); if (!el.pure()) return false;
}
for (var el : map.entrySet()) {
target.add(Instruction.dup().locate(loc())); return true;
target.add(Instruction.loadValue(el.getKey()).locate(loc())); }
var val = el.getValue();
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false); @Override
else val.compile(target, scope, true); public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.storeMember().locate(loc())); target.add(Instruction.loadObj(loc()));
}
for (var el : map.entrySet()) {
var keys = new ArrayList<Object>(); target.add(Instruction.dup(loc()));
keys.addAll(getters.keySet()); target.add(Instruction.loadValue(loc(), el.getKey()));
keys.addAll(setters.keySet()); var val = el.getValue();
FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
for (var key : keys) { target.add(Instruction.storeMember(loc()));
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc())); }
else target.add(Instruction.loadValue((Double)key).locate(loc()));
var keys = new ArrayList<Object>();
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true); keys.addAll(getters.keySet());
else target.add(Instruction.loadValue(null).locate(loc())); keys.addAll(setters.keySet());
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true); for (var key : keys) {
else target.add(Instruction.loadValue(null).locate(loc())); if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
else target.add(Instruction.loadValue(loc(), (Double)key));
target.add(Instruction.defProp().locate(loc()));
} if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
else target.add(Instruction.loadValue(loc(), null));
if (!pollute) target.add(Instruction.discard().locate(loc()));
} if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
else target.add(Instruction.loadValue(loc(), null));
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
super(loc); target.add(Instruction.defProp(loc()));
this.map = map; }
this.getters = getters;
this.setters = setters; if (!pollute) target.add(Instruction.discard(loc()));
} }
}
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
super(loc);
this.map = map;
this.getters = getters;
this.setters = setters;
}
}

View File

@@ -1,65 +1,37 @@
package me.topchetoeu.jscript.compilation.values; 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;
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; public class OperationStatement extends Statement {
import me.topchetoeu.jscript.exceptions.EngineException; public final Statement[] args;
public final Operation operation;
public class OperationStatement extends Statement {
public final Statement[] args; @Override public boolean pure() {
public final Operation operation; for (var el : args) {
if (!el.pure()) return false;
@Override }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) { return true;
arg.compile(target, scope, true); }
}
@Override
if (pollute) target.add(Instruction.operation(operation).locate(loc())); public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
else target.add(Instruction.discard().locate(loc())); for (var arg : args) {
} arg.compile(target, scope, true);
}
@Override
public boolean pure() { if (pollute) target.add(Instruction.operation(loc(), operation));
for (var arg : args) { else target.add(Instruction.discard(loc()));
if (!arg.pure()) return false; }
}
return true; public OperationStatement(Location loc, Operation operation, Statement ...args) {
} super(loc);
this.operation = operation;
@Override this.args = args;
public Statement optimize() { }
var args = new Statement[this.args.length]; }
var allConst = true;
for (var i = 0; i < this.args.length; i++) {
args[i] = this.args[i].optimize();
if (!(args[i] instanceof ConstantStatement)) allConst = false;
}
if (allConst) {
var vals = new Object[this.args.length];
for (var i = 0; i < args.length; i++) {
vals[i] = ((ConstantStatement)args[i]).value;
}
try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
}
return new OperationStatement(loc(), operation, args);
}
public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
}

View File

@@ -1,26 +1,26 @@
package me.topchetoeu.jscript.compilation.values; 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;
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.engine.scope.ScopeRecord; 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) {
super(loc); super(loc);
this.pattern = pattern; this.pattern = pattern;
this.flags = flags; this.flags = flags;
} }
} }

View File

@@ -1,51 +1,33 @@
package me.topchetoeu.jscript.compilation.values; 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;
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 final Statement value;
public class TypeofStatement extends Statement {
public final Statement value; // Not really pure, since a variable from the global scope could be accessed,
// which could lead to code execution, that would get omitted
@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 (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(loc(), (String)i));
target.add(Instruction.typeof((String)i).locate(loc())); return;
return; }
} }
} value.compile(target, scope, pollute);
value.compile(target, scope, pollute); target.add(Instruction.typeof(loc()));
target.add(Instruction.typeof().locate(loc())); }
}
public TypeofStatement(Location loc, Statement value) {
@Override super(loc);
public Statement optimize() { this.value = value;
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) {
super(loc);
this.value = value;
}
}

View File

@@ -1,38 +1,38 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
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;
public class VariableAssignStatement extends Statement { public class VariableAssignStatement extends Statement {
public final String name; public final String name;
public final Statement value; public final Statement value;
public final Operation operation; public final Operation operation;
@Override @Override public boolean pure() { return false; }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name); @Override
if (operation != null) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadVar(i).locate(loc())); var i = scope.getKey(name);
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); if (operation != null) {
else value.compile(target, scope, true); target.add(Instruction.loadVar(loc(), i));
target.add(Instruction.operation(operation).locate(loc())); FunctionStatement.compileWithName(value, target, scope, true, name);
target.add(Instruction.storeVar(i, pollute).locate(loc())); target.add(Instruction.operation(loc(), operation));
} target.add(Instruction.storeVar(loc(), i, pollute));
else { }
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); else {
else value.compile(target, scope, true); FunctionStatement.compileWithName(value, target, scope, true, name);
target.add(Instruction.storeVar(i, pollute).locate(loc())); target.add(Instruction.storeVar(loc(), i, pollute));
} }
} }
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
super(loc); super(loc);
this.name = name; this.name = name;
this.value = val; this.value = val;
this.operation = operation; this.operation = operation;
} }
} }

View File

@@ -1,24 +1,23 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
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.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; 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(loc(), index));
if (pollute) target.add(Instruction.loadVar(index).locate(loc())); }
}
public VariableIndexStatement(Location loc, int i) {
public VariableIndexStatement(Location loc, int i) { super(loc);
super(loc); this.index = i;
this.index = i; }
} }
}

View File

@@ -1,33 +1,32 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement; 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.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; 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) { return new VariableAssignStatement(loc(), name, val, operation);
return new VariableAssignStatement(loc(), name, val, operation); }
}
@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(loc(), i));
target.add(Instruction.loadVar(i).locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
if (!pollute) target.add(Instruction.discard().locate(loc())); }
}
public VariableStatement(Location loc, String name) {
public VariableStatement(Location loc, String name) { super(loc);
super(loc); this.name = name;
this.name = 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,59 +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.Stack;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; 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.ObjectValue;
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 {
private final Stack<Environment> env = new Stack<>(); private final Stack<Environment> env = new Stack<>();
public final Data data; private final ArrayList<CodeFrame> frames = new ArrayList<>();
public final Engine engine; public final Engine engine;
public Environment environment() { public Environment environment() {
return env.empty() ? null : env.peek(); return env.empty() ? null : env.peek();
} }
public Context pushEnv(Environment env) { private Context pushEnv(Environment env) {
this.env.push(env); this.env.push(env);
return this; return this;
} }
public void popEnv() { private void popEnv() {
if (!env.empty()) this.env.pop(); if (!env.empty()) this.env.pop();
} }
public FunctionValue compile(Filename filename, String raw) { public FunctionValue compile(Filename filename, String raw) {
var transpiled = environment().compile.call(this, null, raw, filename.toString()); var env = environment();
String source = null; var result = env.compile.call(this, null, raw, filename.toString(), env);
FunctionValue runner = null;
if (transpiled instanceof ObjectValue) { var function = (FunctionValue)Values.getMember(this, result, "function");
source = Values.toString(this, Values.getMember(this, transpiled, "source")); if (!engine.debugging) return function;
var _runner = Values.getMember(this, transpiled, "runner");
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner; var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
var breakpoints = new TreeSet<>(
Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
.map(v -> Location.parse(Values.toString(this, v)))
.collect(Collectors.toList())
);
var maps = new SourceMap[rawMapChain.length];
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
var map = SourceMap.chain(maps);
if (map != null) {
var newBreakpoints = new TreeSet<Location>();
for (var bp : breakpoints) {
bp = map.toCompiled(bp);
if (bp != null) newBreakpoints.add(bp);
}
breakpoints = newBreakpoints;
} }
else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>(); engine.onSource(filename, raw, breakpoints, map);
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res); return function;
}
public void pushFrame(CodeFrame frame) {
frames.add(frame);
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
pushEnv(frame.function.environment);
engine.onFramePush(this, frame);
}
public boolean popFrame(CodeFrame frame) {
if (frames.size() == 0) return false;
if (frames.get(frames.size() - 1) != frame) return false;
frames.remove(frames.size() - 1);
popEnv();
engine.onFramePop(this, frame);
return true;
}
public CodeFrame peekFrame() {
if (frames.size() == 0) return null;
return frames.get(frames.size() - 1);
}
public List<CodeFrame> frames() {
return Collections.unmodifiableList(frames);
}
public List<String> stackTrace() {
var res = new ArrayList<String>();
for (var i = frames.size() - 1; i >= 0; i--) {
var el = frames.get(i);
var name = el.function.name;
Location loc = null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!trace.equals("")) res.add(trace);
}
return res; return res;
} }
public Context(Engine engine, Data data) { public Context(Engine engine) {
this.data = new Data(engine.data);
if (data != null) this.data.addAll(data);
this.engine = engine; this.engine = engine;
} }
public Context(Engine engine) { public Context(Engine engine, Environment env) {
this(engine, null); this(engine);
if (env != null) this.pushEnv(env);
} }
} }

View File

@@ -5,7 +5,6 @@ import java.util.Map;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Data { public class Data {
public final Data parent;
private HashMap<DataKey<Object>, Object> data = new HashMap<>(); private HashMap<DataKey<Object>, Object> data = new HashMap<>();
public Data copy() { public Data copy() {
@@ -33,19 +32,12 @@ public class Data {
return this; return this;
} }
public <T> T get(DataKey<T> key, T val) { public <T> T get(DataKey<T> key, T val) {
for (var it = this; it != null; it = it.parent) { if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
if (it.data.containsKey(key)) {
return (T)it.data.get((DataKey<Object>)key);
}
}
set(key, val); set(key, val);
return val; return val;
} }
public <T> T get(DataKey<T> key) { public <T> T get(DataKey<T> key) {
for (var it = this; it != null; it = it.parent) { if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
}
return null; return null;
} }
public boolean has(DataKey<?> key) { return data.containsKey(key); } public boolean has(DataKey<?> key) { return data.containsKey(key); }
@@ -61,11 +53,4 @@ public class Data {
public int increase(DataKey<Integer> key) { public int increase(DataKey<Integer> key) {
return increase(key, 1, 0); return increase(key, 1, 0);
} }
public Data() {
this.parent = null;
}
public Data(Data parent) {
this.parent = parent;
}
} }

View File

@@ -1,155 +1,175 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody; 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.debug.DebugController;
import me.topchetoeu.jscript.engine.frame.CodeFrame; 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.exceptions.InterruptException;
import me.topchetoeu.jscript.mapping.SourceMap;
public class Engine implements DebugController {
private class UncompiledFunction extends FunctionValue { public class Engine implements DebugController {
public final Filename filename; private class UncompiledFunction extends FunctionValue {
public final String raw; public final Filename filename;
private FunctionValue compiled = null; public final String raw;
private FunctionValue compiled = null;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) { @Override
if (compiled == null) compiled = ctx.compile(filename, raw); public Object call(Context ctx, Object thisArg, Object ...args) {
return compiled.call(ctx, thisArg, args); if (compiled == null) compiled = ctx.compile(filename, raw);
} return compiled.call(ctx, thisArg, args);
}
public UncompiledFunction(Filename filename, String raw) {
super(filename + "", 0); public UncompiledFunction(Filename filename, String raw) {
this.filename = filename; super(filename + "", 0);
this.raw = raw; this.filename = filename;
} this.raw = raw;
} }
}
private static class Task {
public final FunctionValue func; private static class Task implements Comparable<Task> {
public final Object thisArg; public final FunctionValue func;
public final Object[] args; public final Object thisArg;
public final DataNotifier<Object> notifier = new DataNotifier<>(); public final Object[] args;
public final Context ctx; public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Context ctx;
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) { public final boolean micro;
this.ctx = ctx;
this.func = func; public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
this.thisArg = thisArg; this.ctx = ctx;
this.args = args; this.func = func;
} this.thisArg = thisArg;
} this.args = args;
this.micro = micro;
private static int nextId = 0; }
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
@Override
private Thread thread; public int compareTo(Task other) {
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>(); return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>(); }
}
public final int id = ++nextId;
public final Data data = new Data().set(StackData.MAX_FRAMES, 200); private static int nextId = 0;
public final boolean debugging; public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>(); public final int id = ++nextId;
private DebugController debugger; public final boolean debugging;
public int maxStackFrames = 10000;
public boolean attachDebugger(DebugController debugger) {
if (!debugging || this.debugger != null) return false; private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
for (var source : sources.entrySet()) { private final HashMap<Filename, SourceMap> maps = new HashMap<>();
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey()));
} public Location mapToCompiled(Location location) {
var map = maps.get(location.filename());
this.debugger = debugger; if (map == null) return location;
return true; return map.toCompiled(location);
} }
public boolean detachDebugger() { public Location mapToOriginal(Location location) {
if (!debugging || this.debugger == null) return false; var map = maps.get(location.filename());
this.debugger = null; if (map == null) return location;
return true; return map.toOriginal(location);
} }
@Override public void onFramePop(Context ctx, CodeFrame frame) { private DebugController debugger;
if (debugging && debugger != null) debugger.onFramePop(ctx, frame); private Thread thread;
} private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
@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); public synchronized boolean attachDebugger(DebugController debugger) {
else return false; if (!debugging || this.debugger != null) return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) { for (var source : sources.entrySet()) debugger.onSource(
if (!debugging) return; source.getKey(), source.getValue(),
if (debugger != null) debugger.onSource(filename, source, breakpoints); bpts.get(source.getKey()),
sources.put(filename, source); maps.get(source.getKey())
bpts.put(filename, breakpoints); );
}
this.debugger = debugger;
private void runTask(Task task) { return true;
try { }
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); public synchronized boolean detachDebugger() {
} if (!debugging || this.debugger == null) return false;
catch (RuntimeException e) { this.debugger = null;
if (e instanceof InterruptException) throw e; return true;
task.notifier.error(e); }
}
} private void runTask(Task task) {
public void run(boolean untilEmpty) { try {
while (!untilEmpty || !macroTasks.isEmpty()) { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
try { }
runTask(macroTasks.take()); catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
while (!microTasks.isEmpty()) { task.notifier.error(e);
runTask(microTasks.take()); }
} }
} public void run(boolean untilEmpty) {
catch (InterruptedException | InterruptException e) { while (!untilEmpty || !tasks.isEmpty()) {
for (var msg : macroTasks) { try {
msg.notifier.error(new InterruptException(e)); runTask(tasks.take());
} }
break; catch (InterruptedException | InterruptException e) {
} for (var msg : tasks) msg.notifier.error(new InterruptException(e));
} break;
} }
}
public Thread start() { }
if (this.thread == null) {
this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id); public Thread start() {
this.thread.start(); if (this.thread == null) {
} this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
return this.thread; this.thread.start();
} }
public void stop() { return this.thread;
thread.interrupt(); }
thread = null; public void stop() {
} thread.interrupt();
public boolean inExecThread() { thread = null;
return Thread.currentThread() == thread; }
} public boolean inExecThread() {
public boolean isRunning() { return Thread.currentThread() == thread;
return this.thread != null; }
} public synchronized boolean isRunning() {
return this.thread != null;
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { }
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args);
if (micro) microTasks.addLast(msg); public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
else macroTasks.addLast(msg); var msg = new Task(new Context(this, env), func, thisArg, args, micro);
return msg.notifier; tasks.add(msg);
} return msg.notifier;
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { }
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
} return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
}
public Engine(boolean debugging) {
this.debugging = debugging; @Override
} public void onFramePush(Context ctx, CodeFrame frame) {
} if (debugging && debugger != null) debugger.onFramePush(ctx, frame);
}
@Override public void onFramePop(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
}
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
sources.put(filename, source);
bpts.put(filename, breakpoints);
maps.put(filename, map);
}
public Engine(boolean debugging) {
this.debugging = debugging;
}
}

View File

@@ -1,19 +1,30 @@
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();
@@ -22,16 +33,41 @@ public class Environment {
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappers; public WrappersProvider wrappers;
@Native public FunctionValue compile; public PermissionsProvider permissions = null;
public final RootFilesystem filesystem = new RootFilesystem(this);
public final RootModuleRepo modules = new RootModuleRepo();
public String moduleCwd = "/";
private static int nextId = 0;
@Native public boolean stackVisible = true;
@Native public int id = ++nextId;
@Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
var source = Values.toString(ctx, args[0]);
var filename = Values.toString(ctx, args[1]);
var isDebug = Values.toBoolean(args[2]);
var env = Values.wrapper(args[2], Environment.class);
var res = new ObjectValue();
var target = Parsing.compile(env, Filename.parse(filename), source);
Engine.functions.putAll(target.functions);
Engine.functions.remove(0l);
res.defineProperty(ctx, "function", target.func(env));
res.defineProperty(ctx, "mapChain", new ArrayValue());
if (isDebug) {
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
}
return res;
});
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); 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,12 +76,7 @@ public class Environment {
} }
@Native public Symbol symbol(String name) { @Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name); return getSymbol(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
} }
@NativeGetter("global") public ObjectValue getGlobal() { @NativeGetter("global") public ObjectValue getGlobal() {
@@ -65,23 +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(Engine engine, Data data) { @Override public boolean hasPermission(Permission perm, char delim) {
return new Context(engine, data).pushEnv(this); return permissions == null || permissions.hasPermission(perm, delim);
} }
@Override public boolean hasPermission(Permission perm) {
return permissions == null || permissions.hasPermission(perm);
}
public Context context(Engine engine) { public Context context(Engine engine) {
return new Context(engine).pushEnv(this); return new Context(engine, this);
}
public static Symbol getSymbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
} }
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { 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.wrappers = nativeConverter; this.wrappers = nativeConverter;
this.compile = compile;
this.global = global; this.global = global;
} }
public Environment() {
this(null, null, null);
}
} }

View File

@@ -1,65 +0,0 @@
package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.debug.Debugger;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public class StackData {
public static final DataKey<ArrayList<CodeFrame>> FRAMES = new DataKey<>();
public static final DataKey<Integer> MAX_FRAMES = new DataKey<>();
public static final DataKey<Debugger> DEBUGGER = new DataKey<>();
public static void pushFrame(Context ctx, CodeFrame frame) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
frames.add(frame);
if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) throw EngineException.ofRange("Stack overflow!");
ctx.pushEnv(frame.function.environment);
}
public static boolean popFrame(Context ctx, CodeFrame frame) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
if (frames.size() == 0) return false;
if (frames.get(frames.size() - 1) != frame) return false;
frames.remove(frames.size() - 1);
ctx.popEnv();
ctx.engine.onFramePop(ctx, frame);
return true;
}
public static CodeFrame peekFrame(Context ctx) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
if (frames.size() == 0) return null;
return frames.get(frames.size() - 1);
}
public static List<CodeFrame> frames(Context ctx) {
return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>()));
}
public static List<String> stackTrace(Context ctx) {
var res = new ArrayList<String>();
var frames = frames(ctx);
for (var i = frames.size() - 1; i >= 0; i--) {
var el = frames.get(i);
var name = el.function.name;
Location loc = null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!trace.equals("")) res.add(trace);
}
return res;
}
}

View File

@@ -8,13 +8,17 @@ import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.mapping.SourceMap;
public interface DebugController { public interface DebugController {
/** /**
* Called when a script has been loaded * Called when a script has been loaded
* @param breakpoints * @param filename The name of the source
* @param source The name of the source
* @param breakpoints A set of all the breakpointable locations in this source
* @param map The source map associated with this file. null if this source map isn't mapped
*/ */
void onSource(Filename filename, String source, TreeSet<Location> breakpoints); void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
/** /**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
@@ -30,6 +34,13 @@ public interface DebugController {
*/ */
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); 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. * Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands. * This function might pause in order to await debugging commands.

View File

@@ -4,7 +4,6 @@ public interface DebugHandler {
void enable(V8Message msg); void enable(V8Message msg);
void disable(V8Message msg); void disable(V8Message msg);
void setBreakpoint(V8Message msg);
void setBreakpointByUrl(V8Message msg); void setBreakpointByUrl(V8Message msg);
void removeBreakpoint(V8Message msg); void removeBreakpoint(V8Message msg);
void continueToLocation(V8Message msg); void continueToLocation(V8Message msg);

View File

@@ -9,6 +9,7 @@ import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.Metadata; import me.topchetoeu.jscript.Metadata;
import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.events.Notifier; import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
@@ -19,7 +20,7 @@ import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class DebugServer { public class DebugServer {
public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION; public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
public final HashMap<String, DebuggerProvider> targets = new HashMap<>(); public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
@@ -74,7 +75,6 @@ public class DebugServer {
debugger.enable(msg); continue; debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue; case "Debugger.disable": debugger.disable(msg); continue;
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue; case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
@@ -232,13 +232,12 @@ public class DebugServer {
public DebugServer() { public DebugServer() {
try { try {
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); this.index = Reading.resourceToString("debugger/index.html")
this.index = index .replace("${NAME}", Metadata.name())
.replace("${NAME}", Metadata.NAME) .replace("${VERSION}", Metadata.version())
.replace("${VERSION}", Metadata.VERSION) .replace("${AUTHOR}", Metadata.author())
.replace("${AUTHOR}", Metadata.AUTHOR)
.getBytes(); .getBytes();
} }
catch (IOException e) { throw new UncheckedIOException(e); } catch (IOException e) { throw new UncheckedIOException(e); }

View File

@@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -15,7 +15,6 @@ import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.GlobalScope;
@@ -31,15 +30,24 @@ import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap; 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 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 CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}";
public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}";
public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}";
public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}";
public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}";
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}"; public static final String VSCODE_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 { private static enum State {
RESUMED, RESUMED,
@@ -104,7 +112,7 @@ public class SimpleDebugger implements Debugger {
public boolean debugData = false; public boolean debugData = false;
public void updateLoc(Location loc) { public void updateLoc(Location loc) {
serialized.set("location", serializeLocation(loc)); if (loc == null) return;
this.location = loc; this.location = loc;
} }
@@ -112,8 +120,6 @@ public class SimpleDebugger implements Debugger {
this.frame = frame; this.frame = frame;
this.func = frame.function; this.func = frame.function;
this.id = id; this.id = id;
this.local = new ObjectValue();
this.location = frame.function.loc();
this.global = frame.function.environment.global.obj; this.global = frame.function.environment.global.obj;
this.local = frame.getLocalScope(ctx, true); this.local = frame.getLocalScope(ctx, true);
@@ -121,24 +127,36 @@ public class SimpleDebugger implements Debugger {
this.local.setPrototype(ctx, capture); this.local.setPrototype(ctx, capture);
this.capture.setPrototype(ctx, global); this.capture.setPrototype(ctx, global);
this.valstack = frame.getValStackScope(ctx); this.valstack = frame.getValStackScope(ctx);
debugData = true;
if (location != null) { this.serialized = new JSONMap()
debugData = true; .set("callFrameId", id + "")
this.serialized = new JSONMap() .set("functionName", func.name)
.set("callFrameId", id + "") .set("scopeChain", new JSONList()
.set("functionName", func.name) .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
.set("location", serializeLocation(location)) .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
.set("scopeChain", new JSONList() .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) .add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
.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 class RunResult { private static class RunResult {
public final Context ctx; public final Context ctx;
public final Object result; public final Object result;
public final EngineException error; public final EngineException error;
@@ -151,7 +169,7 @@ public class SimpleDebugger implements Debugger {
} }
public boolean enabled = true; public boolean enabled = true;
public CatchType execptionType = CatchType.ALL; public CatchType execptionType = CatchType.NONE;
public State state = State.RESUMED; public State state = State.RESUMED;
public final WebSocket ws; public final WebSocket ws;
@@ -172,23 +190,50 @@ public class SimpleDebugger implements Debugger {
private HashMap<Integer, Frame> idToFrame = new HashMap<>(); private HashMap<Integer, Frame> idToFrame = new HashMap<>();
private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>(); private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>();
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>(); private HashMap<Integer, ObjRef> idToObject = new HashMap<>();
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>(); private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
private HashMap<ObjectValue, Context> objectToCtx = new HashMap<>(); private HashMap<String, ArrayList<ObjRef>> objectGroups = new HashMap<>();
private HashMap<String, ArrayList<ObjectValue>> objectGroups = new HashMap<>();
private Notifier updateNotifier = new Notifier(); private Notifier updateNotifier = new Notifier();
private boolean pendingPause = false;
private int nextId = new Random().nextInt() & 0x7FFFFFFF; private int nextId = 0;
private Location prevLocation = null;
private Frame stepOutFrame = null, currFrame = null; private Frame stepOutFrame = null, currFrame = null;
private int stepOutPtr = 0;
private int nextId() { private boolean compare(String src, String target) {
return nextId++ ^ 1630022591 /* big prime */; 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 void updateFrames(Context ctx) { private int nextId() {
var frame = StackData.peekFrame(ctx); return nextId++;
}
private synchronized void updateFrames(Context ctx) {
var frame = ctx.peekFrame();
if (frame == null) return; if (frame == null) return;
if (!codeFrameToFrame.containsKey(frame)) { if (!codeFrameToFrame.containsKey(frame)) {
@@ -203,10 +248,13 @@ public class SimpleDebugger implements Debugger {
} }
private JSONList serializeFrames(Context ctx) { private JSONList serializeFrames(Context ctx) {
var res = new JSONList(); var res = new JSONList();
var frames = StackData.frames(ctx); var frames = ctx.frames();
for (var i = frames.size() - 1; i >= 0; i--) { for (var i = frames.size() - 1; i >= 0; i--) {
res.add(codeFrameToFrame.get(frames.get(i)).serialized); var frame = codeFrameToFrame.get(frames.get(i));
if (frame.location == null) continue;
frame.serialized.set("location", serializeLocation(frame.location));
if (frame.location != null) res.add(frame.serialized);
} }
return res; return res;
@@ -246,13 +294,13 @@ public class SimpleDebugger implements Debugger {
if (objectToId.containsKey(obj)) return objectToId.get(obj); if (objectToId.containsKey(obj)) return objectToId.get(obj);
else { else {
int id = nextId(); int id = nextId();
var ref = new ObjRef(ctx, obj);
objectToId.put(obj, id); objectToId.put(obj, id);
if (ctx != null) objectToCtx.put(obj, ctx); idToObject.put(id, ref);
idToObject.put(id, obj);
return id; return id;
} }
} }
private JSONMap serializeObj(Context ctx, Object val) { private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
val = Values.normalize(null, val); val = Values.normalize(null, val);
if (val == Values.NULL) { if (val == Values.NULL) {
@@ -273,7 +321,7 @@ public class SimpleDebugger implements Debugger {
if (obj instanceof FunctionValue) type = "function"; if (obj instanceof FunctionValue) type = "function";
if (obj instanceof ArrayValue) subtype = "array"; if (obj instanceof ArrayValue) subtype = "array";
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); } try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); }
catch (Exception e) { } catch (Exception e) { }
var res = new JSONMap() var res = new JSONMap()
@@ -283,9 +331,28 @@ public class SimpleDebugger implements Debugger {
if (subtype != null) res.set("subtype", subtype); if (subtype != null) res.set("subtype", subtype);
if (className != null) { if (className != null) {
res.set("className", className); res.set("className", className);
res.set("description", "{ " + className + " }");
} }
if (obj instanceof ArrayValue) res.set("description", "Array(" + ((ArrayValue)obj).size() + ")");
else if (obj instanceof FunctionValue) res.set("description", obj.toString());
else {
var defaultToString = false;
try {
defaultToString =
Values.getMember(ctx, obj, "toString") ==
Values.getMember(ctx, ctx.environment().proto("object"), "toString");
}
catch (Exception e) { }
try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(ctx, obj) + " }")); }
catch (EngineException e) { res.set("description", className); }
}
if (byValue) try { res.put("value", JSON.fromJs(ctx, obj)); }
catch (Exception e) { }
return res; return res;
} }
@@ -309,33 +376,44 @@ public class SimpleDebugger implements Debugger {
throw new IllegalArgumentException("Unexpected JS object."); throw new IllegalArgumentException("Unexpected JS object.");
} }
private void setObjectGroup(String name, Object val) { private JSONMap serializeObj(Context ctx, Object val) {
return serializeObj(ctx, val, false);
}
private void addObjectGroup(String name, Object val) {
if (val instanceof ObjectValue) { if (val instanceof ObjectValue) {
var obj = (ObjectValue)val; var obj = (ObjectValue)val;
var id = objectToId.getOrDefault(obj, -1);
if (id < 0) return;
if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj); var ref = idToObject.get(id);
else objectGroups.put(name, new ArrayList<>(List.of(obj)));
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) { private void releaseGroup(String name) {
var objs = objectGroups.remove(name); var objs = objectGroups.remove(name);
if (objs != null) { if (objs != null) for (var obj : objs) {
for (var obj : objs) { if (obj.heldGroups.remove(name) && obj.shouldRelease()) {
var id = objectToId.remove(obj); var id = objectToId.remove(obj.obj);
if (id != null) idToObject.remove(id); if (id != null) idToObject.remove(id);
} }
} }
} }
private Object deserializeArgument(JSONMap val) { private Object deserializeArgument(JSONMap val) {
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))); if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))).obj;
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
case "NaN": return Double.NaN; case "NaN": return Double.NaN;
case "-Infinity": return Double.NEGATIVE_INFINITY; case "-Infinity": return Double.NEGATIVE_INFINITY;
case "Infinity": return Double.POSITIVE_INFINITY; case "Infinity": return Double.POSITIVE_INFINITY;
case "-0": return -0.; case "-0": return -0.;
} }
return JSON.toJs(val.get("value")); var res = val.get("value");
if (res == null) return null;
else return JSON.toJs(res);
} }
private JSONMap serializeException(Context ctx, EngineException err) { private JSONMap serializeException(Context ctx, EngineException err) {
@@ -351,7 +429,6 @@ public class SimpleDebugger implements Debugger {
var res = new JSONMap() var res = new JSONMap()
.set("exceptionId", nextId()) .set("exceptionId", nextId())
.set("exception", serializeObj(ctx, err.value)) .set("exception", serializeObj(ctx, err.value))
.set("exception", serializeObj(ctx, err.value))
.set("text", text); .set("text", text);
return res; return res;
@@ -399,19 +476,14 @@ public class SimpleDebugger implements Debugger {
} }
private RunResult run(Frame codeFrame, String code) { 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 engine = new Engine(false);
var env = codeFrame.func.environment.fork(); var env = codeFrame.func.environment.fork();
ObjectValue global = env.global.obj, env.global = new GlobalScope(codeFrame.local);
capture = codeFrame.frame.getCaptureScope(null, enabled),
local = codeFrame.frame.getLocalScope(null, enabled);
capture.setPrototype(null, global); var ctx = new Context(engine, env);
local.setPrototype(null, capture); var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
env.global = new GlobalScope(local);
var ctx = new Context(engine).pushEnv(env);
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true); engine.run(true);
@@ -419,7 +491,80 @@ public class SimpleDebugger implements Debugger {
catch (EngineException e) { return new RunResult(ctx, null, e); } catch (EngineException e) { return new RunResult(ctx, null, e); }
} }
@Override public void enable(V8Message msg) { private ObjectValue vscodeAutoSuggest(Context ctx, Object target, String query, boolean variable) {
var res = new ArrayValue();
var passed = new HashSet<String>();
var tildas = "~";
if (target == null) target = ctx.environment().getGlobal();
for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) {
for (var el : Values.getMembers(ctx, proto, true, true)) {
var strKey = Values.toString(ctx, el);
if (passed.contains(strKey)) continue;
passed.add(strKey);
var val = Values.getMember(ctx, Values.getMemberDescriptor(ctx, proto, el), "value");
var desc = new ObjectValue();
var sortText = "";
if (strKey.startsWith(query)) sortText += "0@";
else if (strKey.toLowerCase().startsWith(query.toLowerCase())) sortText += "1@";
else if (strKey.contains(query)) sortText += "2@";
else if (strKey.toLowerCase().contains(query.toLowerCase())) sortText += "3@";
else sortText += "4@";
sortText += tildas + strKey;
desc.defineProperty(ctx, "label", strKey);
desc.defineProperty(ctx, "sortText", sortText);
if (val instanceof FunctionValue) {
if (strKey.equals("constructor")) desc.defineProperty(ctx, "type", "name");
else desc.defineProperty(ctx, "type", variable ? "function" : "method");
}
else desc.defineProperty(ctx, "type", variable ? "variable" : "property");
switch (Values.type(val)) {
case "number":
case "boolean":
desc.defineProperty(ctx, "detail", Values.toString(ctx, val));
break;
case "object":
if (val == Values.NULL) desc.defineProperty(ctx, "detail", "null");
else try {
desc.defineProperty(ctx, "detail", Values.getMemberPath(ctx, target, "constructor", "name"));
}
catch (IllegalArgumentException e) {
desc.defineProperty(ctx, "detail", "object");
}
break;
case "function": {
var type = "fn(";
for (var i = 0; i < ((FunctionValue)val).length; i++) {
if (i != 0) type += ",";
type += "?";
}
type += ")";
desc.defineProperty(ctx, "detail", type);
break;
}
default:
desc.defineProperty(ctx, "type", Values.type(val));
break;
}
res.set(ctx, res.size(), desc);
}
tildas += "~";
variable = true;
}
var resObj = new ObjectValue();
resObj.defineProperty(ctx, "result", res);
resObj.defineProperty(ctx, "isArray", target instanceof ArrayValue);
return resObj;
}
@Override public synchronized void enable(V8Message msg) {
enabled = true; enabled = true;
ws.send(msg.respond()); ws.send(msg.respond());
@@ -428,17 +573,17 @@ public class SimpleDebugger implements Debugger {
updateNotifier.next(); updateNotifier.next();
} }
@Override public void disable(V8Message msg) { @Override public synchronized void disable(V8Message msg) {
enabled = false; enabled = false;
ws.send(msg.respond()); ws.send(msg.respond());
updateNotifier.next(); updateNotifier.next();
} }
@Override public void getScriptSource(V8Message msg) { @Override public synchronized void getScriptSource(V8Message msg) {
int id = Integer.parseInt(msg.params.string("scriptId")); int id = Integer.parseInt(msg.params.string("scriptId"));
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
} }
@Override public void getPossibleBreakpoints(V8Message msg) { @Override public synchronized void getPossibleBreakpoints(V8Message msg) {
var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId")));
var start = deserializeLocation(msg.params.get("start"), false); var start = deserializeLocation(msg.params.get("start"), false);
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
@@ -453,30 +598,21 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond(new JSONMap().set("locations", res))); ws.send(msg.respond(new JSONMap().set("locations", res)));
} }
@Override public void pause(V8Message msg) { @Override public synchronized void pause(V8Message msg) {
pendingPause = true;
ws.send(msg.respond());
} }
@Override public synchronized void resume(V8Message msg) {
@Override public void resume(V8Message msg) {
resume(State.RESUMED); resume(State.RESUMED);
ws.send(msg.respond(new JSONMap())); ws.send(msg.respond(new JSONMap()));
} }
@Override public void setBreakpoint(V8Message msg) { @Override public synchronized void setBreakpointByUrl(V8Message msg) {
// int id = nextId();
// var loc = deserializeLocation(msg.params.get("location"), true);
// var bpt = new Breakpoint(id, loc, null);
// breakpoints.put(loc, bpt);
// idToBrpt.put(id, bpt);
// ws.send(msg.respond(new JSONMap()
// .set("breakpointId", id)
// .set("actualLocation", serializeLocation(loc))
// ));
}
@Override public void setBreakpointByUrl(V8Message msg) {
var line = (int)msg.params.number("lineNumber") + 1; var line = (int)msg.params.number("lineNumber") + 1;
var col = (int)msg.params.number("columnNumber", 0) + 1; var col = (int)msg.params.number("columnNumber", 0) + 1;
var cond = msg.params.string("condition", null); var cond = msg.params.string("condition", "").trim();
if (cond.equals("")) cond = null;
if (cond != null) cond = "(" + cond + ")"; if (cond != null) cond = "(" + cond + ")";
Pattern regex; Pattern regex;
@@ -506,7 +642,7 @@ public class SimpleDebugger implements Debugger {
.set("locations", locs) .set("locations", locs)
)); ));
} }
@Override public void removeBreakpoint(V8Message msg) { @Override public synchronized void removeBreakpoint(V8Message msg) {
var id = Integer.parseInt(msg.params.string("breakpointId")); var id = Integer.parseInt(msg.params.string("breakpointId"));
if (idToBptCand.containsKey(id)) { if (idToBptCand.containsKey(id)) {
@@ -523,7 +659,7 @@ public class SimpleDebugger implements Debugger {
} }
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void continueToLocation(V8Message msg) { @Override public synchronized void continueToLocation(V8Message msg) {
var loc = deserializeLocation(msg.params.get("location"), true); var loc = deserializeLocation(msg.params.get("location"), true);
tmpBreakpts.add(loc); tmpBreakpts.add(loc);
@@ -532,39 +668,48 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void setPauseOnExceptions(V8Message msg) { @Override public synchronized void setPauseOnExceptions(V8Message msg) {
ws.send(new V8Error("i dont wanna to")); 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 void stepInto(V8Message msg) { @Override public synchronized void stepInto(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else { else {
prevLocation = currFrame.location;
stepOutFrame = currFrame; stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_IN); resume(State.STEPPING_IN);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
@Override public void stepOut(V8Message msg) { @Override public synchronized void stepOut(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else { else {
prevLocation = currFrame.location;
stepOutFrame = currFrame; stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_OUT); resume(State.STEPPING_OUT);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
@Override public void stepOver(V8Message msg) { @Override public synchronized void stepOver(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else { else {
prevLocation = currFrame.location;
stepOutFrame = currFrame; stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_OVER); resume(State.STEPPING_OVER);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
@Override public void evaluateOnCallFrame(V8Message msg) { @Override public synchronized void evaluateOnCallFrame(V8Message msg) {
var cfId = Integer.parseInt(msg.params.string("callFrameId")); var cfId = Integer.parseInt(msg.params.string("callFrameId"));
var expr = msg.params.string("expression"); var expr = msg.params.string("expression");
var group = msg.params.string("objectGroup", null); var group = msg.params.string("objectGroup", null);
@@ -572,38 +717,43 @@ public class SimpleDebugger implements Debugger {
var cf = idToFrame.get(cfId); var cf = idToFrame.get(cfId);
var res = run(cf, expr); var res = run(cf, expr);
if (group != null) setObjectGroup(group, res.result); if (group != null) addObjectGroup(group, res.result);
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result)))); 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))));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
} }
@Override public void releaseObjectGroup(V8Message msg) { @Override public synchronized void releaseObjectGroup(V8Message msg) {
var group = msg.params.string("objectGroup"); var group = msg.params.string("objectGroup");
releaseGroup(group); releaseGroup(group);
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override @Override public synchronized void releaseObject(V8Message msg) {
public void releaseObject(V8Message msg) {
var id = Integer.parseInt(msg.params.string("objectId")); var id = Integer.parseInt(msg.params.string("objectId"));
var obj = idToObject.remove(id); var ref = idToObject.get(id);
if (obj != null) objectToId.remove(obj); ref.held = false;
if (ref.shouldRelease()) {
objectToId.remove(ref.obj);
idToObject.remove(id);
}
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void getProperties(V8Message msg) { @Override public synchronized void getProperties(V8Message msg) {
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); var ref = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var obj = ref.obj;
var res = new JSONList(); var res = new JSONList();
var ctx = objectToCtx.get(obj); var ctx = ref.ctx;
if (obj != emptyObject) { if (obj != emptyObject && obj != null) {
for (var key : obj.keys(true)) { for (var key : obj.keys(true)) {
var propDesc = new JSONMap(); var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) { if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key); var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(ctx, key)); propDesc.set("name", Values.toString(ctx, key));
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
@@ -637,7 +787,7 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond(new JSONMap().set("result", res))); ws.send(msg.respond(new JSONMap().set("result", res)));
} }
@Override public void callFunctionOn(V8Message msg) { @Override public synchronized void callFunctionOn(V8Message msg) {
var src = msg.params.string("functionDeclaration"); var src = msg.params.string("functionDeclaration");
var args = msg.params var args = msg.params
.list("arguments", new JSONList()) .list("arguments", new JSONList())
@@ -645,9 +795,11 @@ public class SimpleDebugger implements Debugger {
.map(v -> v.map()) .map(v -> v.map())
.map(this::deserializeArgument) .map(this::deserializeArgument)
.collect(Collectors.toList()); .collect(Collectors.toList());
var byValue = msg.params.bool("returnByValue", false);
var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); var thisArgRef = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var ctx = objectToCtx.get(thisArg); var thisArg = thisArgRef.obj;
var ctx = thisArgRef.ctx;
while (true) { while (true) {
var start = src.lastIndexOf("//# sourceURL="); var start = src.lastIndexOf("//# sourceURL=");
@@ -658,47 +810,36 @@ public class SimpleDebugger implements Debugger {
} }
try { try {
switch (src) { Object res = null;
case CHROME_GET_PROP_FUNC: { if (compare(src, VSCODE_EMPTY)) res = emptyObject;
var path = JSON.parse(null, (String)args.get(0)).list(); else if (compare(src, VSCODE_SELF)) res = thisArg;
Object res = thisArg; else if (compare(src, CHROME_GET_PROP_FUNC)) {
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el)); res = thisArg;
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(ctx, res, JSON.toJs(el));
return;
}
case VSCODE_STRINGIFY_VAL:
case VSCODE_STRINGIFY_PROPS:
case VSCODE_SHALLOW_COPY:
case VSCODE_SYMBOL_REQUEST:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
break;
case VSCODE_FLATTEN_ARRAY:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
break;
case VSCODE_CALL: {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
break;
}
default:
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
break;
// default: {
// var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx);
// if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args);
// ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
// }
} }
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)))); } catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
} }
@Override @Override public synchronized void runtimeEnable(V8Message msg) {
public void runtimeEnable(V8Message msg) {
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) { @Override public void onSource(Filename filename, String source, TreeSet<Location> locations, SourceMap map) {
int id = nextId(); int id = nextId();
var src = new Source(id, filename, source, locations); var src = new Source(id, filename, source, locations);
@@ -720,87 +861,103 @@ public class SimpleDebugger implements Debugger {
@Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { @Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false; if (!enabled) return false;
updateFrames(ctx); boolean isBreakpointable;
var frame = codeFrameToFrame.get(cf); Location loc;
Frame frame;
if (!frame.debugData) return false; synchronized (this) {
frame = codeFrameToFrame.get(cf);
if (instruction.location != null) frame.updateLoc(instruction.location); if (!frame.debugData) return false;
var loc = frame.location;
var isBreakpointable = loc != null && (
idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) ||
returnVal != Runners.NO_RETURN
);
// TODO: FIXXXX if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location));
// if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; loc = frame.location;
isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn());
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
pauseException(ctx); 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);
} }
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 (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
while (enabled) { while (enabled) {
switch (state) { synchronized (this) {
case PAUSED_EXCEPTION: switch (state) {
case PAUSED_NORMAL: break; case PAUSED_EXCEPTION:
case PAUSED_NORMAL: break;
case STEPPING_OUT: case STEPPING_OUT:
case RESUMED: return false; case RESUMED: return false;
case STEPPING_IN:
if (!prevLocation.equals(loc)) { case STEPPING_IN:
if (isBreakpointable) pauseDebug(ctx, null); case STEPPING_OVER:
else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null); if (stepOutFrame.frame == frame.frame) {
else return false; if (returnVal != Runners.NO_RETURN || error != null) {
} state = State.STEPPING_OUT;
else return false; continue;
break; }
case STEPPING_OVER: else if (stepOutPtr != frame.frame.codePtr) {
if (stepOutFrame.frame == frame.frame) { if (state == State.STEPPING_IN && instruction.breakpoint.shouldStepIn()) {
if (isBreakpointable && ( pauseDebug(ctx, null);
!loc.filename().equals(prevLocation.filename()) || break;
loc.line() != prevLocation.line() }
)) pauseDebug(ctx, null); else if (state == State.STEPPING_OVER && instruction.breakpoint.shouldStepOver()) {
else return false; pauseDebug(ctx, null);
} break;
else return false; }
break; }
}
return false;
}
} }
updateNotifier.await(); updateNotifier.await();
} }
return false; 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) { @Override public void onFramePop(Context ctx, CodeFrame frame) {
updateFrames(ctx); updateFrames(ctx);
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
catch (NullPointerException e) { } catch (NullPointerException e) { }
if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); if (ctx.frames().size() == 0) {
else if (stepOutFrame != null && stepOutFrame.frame == frame && if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED);
(state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) }
) { else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) {
// if (state == State.STEPPING_OVER) state = State.STEPPING_IN; state = State.STEPPING_IN;
// else { stepOutFrame = currFrame;
pauseDebug(ctx, null);
updateNotifier.await();
// }
} }
} }
@Override public void connect() { @Override public synchronized void connect() {
if (!target.attachDebugger(this)) { if (!target.attachDebugger(this)) {
ws.send(new V8Error("A debugger is already attached to this engine.")); ws.send(new V8Error("A debugger is already attached to this engine."));
} }
} }
@Override public void disconnect() { @Override public synchronized void disconnect() {
target.detachDebugger(); target.detachDebugger();
enabled = false; enabled = false;
updateNotifier.next(); updateNotifier.next();

View File

@@ -118,8 +118,6 @@ public class WebSocket implements AutoCloseable {
else send(msg.textData()); else send(msg.textData());
} }
public void send(Object data) { public void send(Object data) {
// TODO: Remove
// System.out.println("SEND: " + data);
if (closed) throw new IllegalStateException("Object is closed."); if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes()); write(1, data.toString().getBytes());
} }
@@ -201,10 +199,6 @@ public class WebSocket implements AutoCloseable {
if (!fin) continue; if (!fin) continue;
var raw = data.toByteArray(); var raw = data.toByteArray();
// TODO: Remove
// System.out.println("RECEIVED: " + new String(raw));
if (type == 1) return new WebSocketMessage(new String(raw)); if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw); else return new WebSocketMessage(raw);
} }

View File

@@ -1,305 +1,318 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
import java.util.List; 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.engine.Context; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ScopeValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.ScopeValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame {
public static class TryCtx { public class CodeFrame {
public static final int STATE_TRY = 0; public static enum TryState {
public static final int STATE_CATCH = 1; TRY,
public static final int STATE_FINALLY_THREW = 2; CATCH,
public static final int STATE_FINALLY_RETURNED = 3; FINALLY,
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;
public boolean inBounds(int ptr) {
if (catchN < 0) catchN = 0; return ptr >= start && ptr < end;
if (finallyN < 0) finallyN = 0; }
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 final LocalScope scope; public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
public final Object thisArg; this.catchStart = catchStart;
public final Object[] args; this.finallyStart = finallyStart;
public final Stack<TryCtx> tryStack = new Stack<>(); this.restoreStackPtr = stackPtr;
public final CodeFunction function; this.result = res == null ? PendingResult.ofNone() : res;
this.state = state;
public Object[] stack = new Object[32]; this.start = start;
public int stackPtr = 0; this.end = end;
public int codePtr = 0; this.error = err;
public boolean jumpFlag = false; }
private Location prevLoc = null; }
public ObjectValue getLocalScope(Context ctx, boolean props) { private static class PendingResult {
var names = new String[scope.locals.length]; public final boolean isReturn, isJump, isThrow;
public final Object value;
for (int i = 0; i < scope.locals.length; i++) { public final EngineException error;
var name = "local_" + (i - 2); public final int ptr;
public final Instruction instruction;
if (i == 0) name = "this";
else if (i == 1) name = "arguments"; private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
else if (i < function.localNames.length) name = function.localNames[i]; this.instruction = instr;
this.isReturn = isReturn;
names[i] = name; this.isJump = isJump;
} this.isThrow = isThrow;
this.value = value;
return new ScopeValue(scope.locals, names); this.error = error;
} this.ptr = ptr;
public ObjectValue getCaptureScope(Context ctx, boolean props) { }
var names = new String[scope.captures.length];
public static PendingResult ofNone() {
for (int i = 0; i < scope.captures.length; i++) { return new PendingResult(null, false, false, false, null, null, 0);
var name = "capture_" + (i - 2); }
if (i < function.captureNames.length) name = function.captureNames[i]; public static PendingResult ofReturn(Object value, Instruction instr) {
names[i] = name; return new PendingResult(instr, true, false, false, value, null, 0);
} }
public static PendingResult ofThrow(EngineException error, Instruction instr) {
return new ScopeValue(scope.captures, names); return new PendingResult(instr, false, false, true, null, error, 0);
} }
public ObjectValue getValStackScope(Context ctx) { public static PendingResult ofJump(int codePtr, Instruction instr) {
return new ObjectValue() { return new PendingResult(instr, false, true, false, null, null, codePtr);
@Override }
protected Object getField(Context ctx, Object key) { }
var i = (int)Values.toNumber(ctx, key);
if (i < 0 || i >= stackPtr) return null; public final LocalScope scope;
else return stack[i]; public final Object thisArg;
} public final Object[] args;
@Override public final Stack<TryCtx> tryStack = new Stack<>();
protected boolean hasField(Context ctx, Object key) { public final CodeFunction function;
return true; public Object[] stack = new Object[32];
} public int stackPtr = 0;
@Override public int codePtr = 0;
public List<Object> keys(boolean includeNonEnumerable) { public boolean jumpFlag = false, popTryFlag = false;
var res = super.keys(includeNonEnumerable); private Location prevLoc = null;
for (var i = 0; i < stackPtr; i++) res.add(i);
return res; public ObjectValue getLocalScope(Context ctx, boolean props) {
} var names = new String[scope.locals.length];
};
} for (int i = 0; i < scope.locals.length; i++) {
var name = "local_" + (i - 2);
public void addTry(int n, int catchN, int finallyN) {
var res = new TryCtx(codePtr + 1, n, catchN, finallyN); if (i == 0) name = "this";
if (!tryStack.empty()) res.err = tryStack.peek().err; else if (i == 1) name = "arguments";
else if (i < function.localNames.length) name = function.localNames[i];
tryStack.add(res);
} names[i] = name;
}
public Object peek() {
return peek(0); return new ScopeValue(scope.locals, names);
} }
public Object peek(int offset) { public ObjectValue getCaptureScope(Context ctx, boolean props) {
if (stackPtr <= offset) return null; var names = new String[scope.captures.length];
else return stack[stackPtr - 1 - offset];
} for (int i = 0; i < scope.captures.length; i++) {
public Object pop() { var name = "capture_" + (i - 2);
if (stackPtr == 0) return null; if (i < function.captureNames.length) name = function.captureNames[i];
return stack[--stackPtr]; names[i] = name;
} }
public Object[] take(int n) {
int srcI = stackPtr - n; return new ScopeValue(scope.captures, names);
if (srcI < 0) srcI = 0; }
public ObjectValue getValStackScope(Context ctx) {
int dstI = n + srcI - stackPtr; return new ObjectValue() {
int copyN = stackPtr - srcI; @Override
protected Object getField(Context ctx, Object key) {
Object[] res = new Object[n]; var i = (int)Values.toNumber(ctx, key);
System.arraycopy(stack, srcI, res, dstI, copyN); if (i < 0 || i >= stackPtr) return null;
stackPtr -= copyN; else return stack[i];
}
return res; @Override
} protected boolean hasField(Context ctx, Object key) {
public void push(Context ctx, Object val) { return true;
if (stack.length <= stackPtr) { }
var newStack = new Object[stack.length * 2]; @Override
System.arraycopy(stack, 0, newStack, 0, stack.length); public List<Object> keys(boolean includeNonEnumerable) {
stack = newStack; var res = super.keys(includeNonEnumerable);
} for (var i = 0; i < stackPtr; i++) res.add(i);
stack[stackPtr++] = Values.normalize(ctx, val); return res;
} }
};
private void setCause(Context ctx, EngineException err, EngineException cause) { }
err.setCause(cause);
} public void addTry(int start, int end, int catchStart, int finallyStart) {
var err = tryStack.empty() ? null : tryStack.peek().error;
public Object next(Context ctx, Object value, Object returnValue, EngineException error) { var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
if (value != Runners.NO_RETURN) push(ctx, value);
tryStack.add(res);
if (returnValue == Runners.NO_RETURN && error == null) { }
try {
if (Thread.currentThread().isInterrupted()) throw new InterruptException(); public Object peek() {
return peek(0);
var instr = function.body[codePtr]; }
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); public Object peek(int offset) {
if (stackPtr <= offset) return null;
if (codePtr < 0 || codePtr >= function.body.length) returnValue = null; else return stack[stackPtr - 1 - offset];
else { }
if (instr.location != null) prevLoc = instr.location; public Object pop() {
if (stackPtr == 0) return null;
try { return stack[--stackPtr];
this.jumpFlag = false; }
returnValue = Runners.exec(ctx, instr, this); public Object[] take(int n) {
} int srcI = stackPtr - n;
catch (EngineException e) { if (srcI < 0) srcI = 0;
error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
} int dstI = n + srcI - stackPtr;
} int copyN = stackPtr - srcI;
}
catch (EngineException e) { error = e; } Object[] res = new Object[n];
} System.arraycopy(stack, srcI, res, dstI, copyN);
stackPtr -= copyN;
while (!tryStack.empty()) {
var tryCtx = tryStack.peek(); return res;
var newState = -1; }
public void push(Context ctx, Object val) {
switch (tryCtx.state) { if (stack.length <= stackPtr) {
case TryCtx.STATE_TRY: var newStack = new Object[stack.length * 2];
if (error != null) { System.arraycopy(stack, 0, newStack, 0, stack.length);
if (tryCtx.hasCatch) { stack = newStack;
tryCtx.err = error; }
newState = TryCtx.STATE_CATCH; stack[stackPtr++] = Values.normalize(ctx, val);
} }
else if (tryCtx.hasFinally) {
tryCtx.err = error; public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
newState = TryCtx.STATE_FINALLY_THREW; if (value != Runners.NO_RETURN) push(ctx, value);
}
break; Instruction instr = null;
} if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally) { if (returnValue == Runners.NO_RETURN && error == null) {
tryCtx.retVal = returnValue; try {
newState = TryCtx.STATE_FINALLY_RETURNED; if (Thread.currentThread().isInterrupted()) throw new InterruptException();
}
break; if (instr == null) returnValue = null;
} else {
else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN; // System.out.println(instr + "@" + instr.location);
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (tryCtx.hasFinally) {
if (jumpFlag) tryCtx.jumpPtr = codePtr; if (instr.location != null) prevLoc = instr.location;
else tryCtx.jumpPtr = tryCtx.end;
newState = TryCtx.STATE_FINALLY_JUMPED; try {
} this.jumpFlag = this.popTryFlag = false;
else codePtr = tryCtx.end; returnValue = Runners.exec(ctx, instr, this);
break; }
case TryCtx.STATE_CATCH: catch (EngineException e) {
if (error != null) { error = e.add(ctx, function.name, prevLoc);
setCause(ctx, error, tryCtx.err); }
if (tryCtx.hasFinally) { }
tryCtx.err = error; }
newState = TryCtx.STATE_FINALLY_THREW; catch (EngineException e) { error = e; }
} }
break;
} while (!tryStack.empty()) {
else if (returnValue != Runners.NO_RETURN) { var tryCtx = tryStack.peek();
if (tryCtx.hasFinally) { TryCtx newCtx = null;
tryCtx.retVal = returnValue;
newState = TryCtx.STATE_FINALLY_RETURNED; if (error != null) {
} if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
break; else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
} }
else if (codePtr >= tryCtx.catchStart && codePtr < tryCtx.finallyStart) return Runners.NO_RETURN; else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
if (tryCtx.hasFinally) { }
if (jumpFlag) tryCtx.jumpPtr = codePtr; else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
else tryCtx.jumpPtr = tryCtx.end; if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
newState = TryCtx.STATE_FINALLY_JUMPED; }
} else if (!this.popTryFlag) newCtx = tryCtx;
else codePtr = tryCtx.end;
break; if (newCtx != null) {
case TryCtx.STATE_FINALLY_THREW: if (newCtx != tryCtx) {
if (error != null) setCause(ctx, error, tryCtx.err); switch (newCtx.state) {
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; case CATCH:
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN; if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
break; codePtr = tryCtx.catchStart;
case TryCtx.STATE_FINALLY_RETURNED: stackPtr = tryCtx.restoreStackPtr;
if (error != null) setCause(ctx, error, tryCtx.err); break;
if (returnValue == Runners.NO_RETURN) { case FINALLY:
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal; if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
else return Runners.NO_RETURN; codePtr = tryCtx.finallyStart;
} stackPtr = tryCtx.restoreStackPtr;
break; default:
case TryCtx.STATE_FINALLY_JUMPED: }
if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { tryStack.pop();
if (!jumpFlag) codePtr = tryCtx.jumpPtr; tryStack.push(newCtx);
else codePtr = tryCtx.end; }
} error = null;
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN; returnValue = Runners.NO_RETURN;
break; break;
} }
else {
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); popTryFlag = false;
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (newState == -1) {
var err = tryCtx.err; if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
tryStack.pop(); codePtr = tryCtx.finallyStart;
if (!tryStack.isEmpty()) tryStack.peek().err = err; stackPtr = tryCtx.restoreStackPtr;
continue; tryStack.pop();
} tryStack.push(tryCtx._finally(null));
break;
tryCtx.state = newState; }
switch (newState) { else {
case TryCtx.STATE_CATCH: tryStack.pop();
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); codePtr = tryCtx.end;
codePtr = tryCtx.catchStart; if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true); if (tryCtx.result.isJump) {
break; codePtr = tryCtx.result.ptr;
default: jumpFlag = true;
codePtr = tryCtx.finallyStart; }
} if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
if (tryCtx.result.isThrow) {
return Runners.NO_RETURN; error = tryCtx.result.error;
} }
if (error != null) error.setCause(tryCtx.error);
if (error != null) { continue;
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false); }
throw error; }
} }
if (returnValue != Runners.NO_RETURN) {
ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); if (error != null) {
return returnValue; var caught = false;
}
for (var frame : ctx.frames()) {
return Runners.NO_RETURN; for (var tryCtx : frame.tryStack) {
} if (tryCtx.state == TryState.TRY) caught = true;
}
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { }
this.args = args.clone();
this.scope = new LocalScope(func.localsN, func.captures); ctx.engine.onInstruction(ctx, this, instr, null, error, caught);
this.scope.get(0).set(null, thisArg); throw error;
var argsObj = new ArrayValue(); }
for (var i = 0; i < args.length; i++) { if (returnValue != Runners.NO_RETURN) {
argsObj.set(ctx, i, args[i]); ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
} return returnValue;
this.scope.get(1).value = argsObj; }
this.thisArg = thisArg; return Runners.NO_RETURN;
this.function = func; }
}
} public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
this.args = args.clone();
this.scope = new LocalScope(func.localsN, func.captures);
this.scope.get(0).set(null, thisArg);
var argsObj = new ArrayValue();
for (var i = 0; i < args.length; i++) {
argsObj.set(ctx, i, args[i]);
}
this.scope.get(1).value = argsObj;
this.thisArg = thisArg;
this.function = func;
}
}

View File

@@ -1,6 +1,6 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
public enum ConvertHint { public enum ConvertHint {
TOSTRING, TOSTRING,
VALUEOF, VALUEOF,
} }

View File

@@ -1,9 +1,9 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
public class InstructionResult { public class InstructionResult {
public final Object value; public final Object value;
public InstructionResult(Object value) { public InstructionResult(Object value) {
this.value = value; this.value = value;
} }
} }

View File

@@ -1,367 +1,350 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
import java.util.Collections; 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.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;
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.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
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;
public class Runners { public class Runners {
public static final Object NO_RETURN = new Object(); public static final Object NO_RETURN = new Object();
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) { public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
return frame.pop(); return frame.pop();
} }
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) { public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
throw EngineException.ofSyntax((String)instr.get(0)); throw EngineException.ofSyntax((String)instr.get(0));
} }
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) { public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
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, Values.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) { 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));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
var name = (String)instr.get(0); var name = (String)instr.get(0);
ctx.environment().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) { 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();
var obj = frame.pop(); var obj = frame.pop();
if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined."); if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined.");
if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined."); if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined.");
if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object."); if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object.");
Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false); Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false);
frame.push(ctx, obj); frame.push(ctx, obj);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) { 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();
if (!Values.isPrimitive(type)) { if (!Values.isPrimitive(type)) {
var proto = Values.getMember(ctx, type, "prototype"); var proto = Values.getMember(ctx, type, "prototype");
frame.push(ctx, Values.isInstanceOf(ctx, obj, proto)); frame.push(ctx, Values.isInstanceOf(ctx, obj, proto));
} }
else { else {
frame.push(ctx, false); frame.push(ctx, false);
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) { public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
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); frame.push(ctx, null);
for (var el : members) { for (var el : members) {
if (el instanceof Symbol) continue; if (el instanceof Symbol) continue;
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.defineProperty(ctx, "value", el); obj.defineProperty(ctx, "value", el);
frame.push(ctx, obj); frame.push(ctx, obj);
} }
// var arr = new ObjectValue();
frame.codePtr++;
// var members = Values.getMembers(ctx, val, false, false); return NO_RETURN;
// Collections.reverse(members); }
// for (var el : members) {
// if (el instanceof Symbol) continue; public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
// arr.defineProperty(ctx, i++, el); int start = frame.codePtr + 1;
// } int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1);
// arr.defineProperty(ctx, "length", i); if (finallyStart >= 0) finallyStart += start;
if (catchStart >= 0) catchStart += start;
// frame.push(ctx, arr); int end = (int)instr.get(2) + start;
frame.codePtr++; frame.addTry(start, end, catchStart, finallyStart);
return NO_RETURN; frame.codePtr++;
} return NO_RETURN;
}
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) { public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
frame.addTry(instr.get(0), instr.get(1), instr.get(2)); frame.popTryFlag = true;
frame.codePtr++; return NO_RETURN;
return NO_RETURN; }
}
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) { int count = instr.get(0);
int offset = instr.get(0), count = instr.get(1);
for (var i = 0; i < count; i++) {
for (var i = 0; i < count; i++) { frame.push(ctx, frame.peek(count - 1));
frame.push(ctx, frame.peek(offset + count - 1)); }
}
frame.codePtr++;
frame.codePtr++; return NO_RETURN;
return NO_RETURN; }
} public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
public static Object execMove(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, null);
int offset = instr.get(0), count = instr.get(1); frame.codePtr++;
return NO_RETURN;
var tmp = frame.take(offset); }
var res = frame.take(count); public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, instr.get(0));
for (var i = 0; i < offset; i++) frame.push(ctx, tmp[i]); frame.codePtr++;
for (var i = 0; i < count; i++) frame.push(ctx, res[i]); return NO_RETURN;
}
frame.codePtr++; public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
return NO_RETURN; var i = instr.get(0);
}
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) { if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i));
frame.push(ctx, null); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++;
return NO_RETURN; frame.codePtr++;
} return NO_RETURN;
public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) { }
frame.push(ctx, instr.get(0)); public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) {
frame.codePtr++; frame.push(ctx, new ObjectValue());
return NO_RETURN; frame.codePtr++;
} return NO_RETURN;
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { }
var i = instr.get(0); public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.environment().global.obj);
if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i)); frame.codePtr++;
else frame.push(ctx, frame.scope.get((int)i).get(ctx)); return NO_RETURN;
}
frame.codePtr++; public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) {
return NO_RETURN; var res = new ArrayValue();
} res.setSize(instr.get(0));
public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, res);
frame.push(ctx, new ObjectValue()); frame.codePtr++;
frame.codePtr++; return NO_RETURN;
return NO_RETURN; }
} public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { long id = (Long)instr.get(0);
frame.push(ctx, ctx.environment().global.obj); var captures = new ValueVariable[instr.params.length - 1];
frame.codePtr++;
return NO_RETURN; for (var i = 1; i < instr.params.length; i++) {
} captures[i - 1] = frame.scope.get(instr.get(i));
public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) { }
var res = new ArrayValue();
res.setSize(instr.get(0)); var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
frame.push(ctx, res);
frame.codePtr++; frame.push(ctx, func);
return NO_RETURN;
} frame.codePtr++;
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) { return NO_RETURN;
long id = (Long)instr.get(0); }
int localsN = (Integer)instr.get(1); public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
int len = (Integer)instr.get(2); var key = frame.pop();
var captures = new ValueVariable[instr.params.length - 3]; var obj = frame.pop();
for (var i = 3; i < instr.params.length; i++) { try {
captures[i - 3] = frame.scope.get(instr.get(i)); frame.push(ctx, Values.getMember(ctx, obj, key));
} }
catch (IllegalArgumentException e) {
var body = Engine.functions.get(id); throw EngineException.ofType(e.getMessage());
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body); }
frame.codePtr++;
frame.push(ctx, func); return NO_RETURN;
}
frame.codePtr++; public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
return NO_RETURN; frame.push(ctx, instr.get(0));
} return execLoadMember(ctx, instr, frame);
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) { }
var key = frame.pop(); public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
var obj = frame.pop(); frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.codePtr++;
try { return NO_RETURN;
frame.push(ctx, Values.getMember(ctx, obj, key)); }
}
catch (IllegalArgumentException e) { public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) {
throw EngineException.ofType(e.getMessage()); frame.pop();
} frame.codePtr++;
frame.codePtr++; return NO_RETURN;
return NO_RETURN; }
} public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) { var val = frame.pop();
frame.push(ctx, instr.get(0)); var key = frame.pop();
return execLoadMember(ctx, instr, frame); var obj = frame.pop();
}
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); if ((boolean)instr.get(0)) frame.push(ctx, val);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
frame.pop(); var i = instr.get(0);
frame.codePtr++;
return NO_RETURN; if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val);
} else frame.scope.get((int)i).set(ctx, val);
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); frame.codePtr++;
var key = frame.pop(); return NO_RETURN;
var obj = frame.pop(); }
public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) {
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
if ((boolean)instr.get(0)) frame.push(ctx, val); frame.codePtr++;
frame.codePtr++; return NO_RETURN;
return NO_RETURN; }
}
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); frame.codePtr += (int)instr.get(0);
var i = instr.get(0); frame.jumpFlag = true;
return NO_RETURN;
if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val); }
else frame.scope.get((int)i).set(ctx, val); public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.toBoolean(frame.pop())) {
frame.codePtr++; frame.codePtr += (int)instr.get(0);
return NO_RETURN; frame.jumpFlag = true;
} }
public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) { else frame.codePtr ++;
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function); return NO_RETURN;
frame.codePtr++; }
return NO_RETURN; public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
} if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0);
public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) { frame.jumpFlag = true;
frame.codePtr += (int)instr.get(0); }
frame.jumpFlag = true; else frame.codePtr ++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.toBoolean(frame.pop())) { public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
frame.codePtr += (int)instr.get(0); var obj = frame.pop();
frame.jumpFlag = true; var index = frame.pop();
}
else frame.codePtr ++; frame.push(ctx, Values.hasMember(ctx, obj, index, false));
return NO_RETURN; frame.codePtr++;
} return NO_RETURN;
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) { }
if (Values.not(frame.pop())) { public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
frame.codePtr += (int)instr.get(0); String name = instr.get(0);
frame.jumpFlag = true; Object obj;
}
else frame.codePtr ++; if (name != null) {
return NO_RETURN; if (ctx.environment().global.has(ctx, name)) {
} obj = ctx.environment().global.get(ctx, name);
}
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) { else obj = null;
var obj = frame.pop(); }
var index = frame.pop(); else obj = frame.pop();
frame.push(ctx, Values.hasMember(ctx, obj, index, false)); frame.push(ctx, Values.type(obj));
frame.codePtr++;
return NO_RETURN; frame.codePtr++;
} return NO_RETURN;
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) { }
String name = instr.get(0); public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
Object obj; frame.codePtr++;
return NO_RETURN;
if (name != null) { }
if (ctx.environment().global.has(ctx, name)) {
obj = ctx.environment().global.get(ctx, name); public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
} var key = frame.pop();
else obj = null; var val = frame.pop();
}
else obj = frame.pop(); if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
frame.codePtr++;
frame.push(ctx, Values.type(obj)); return NO_RETURN;
}
frame.codePtr++;
return NO_RETURN; public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
} Operation op = instr.get(0);
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { var args = new Object[op.operands];
frame.codePtr++;
return NO_RETURN; for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
}
frame.push(ctx, Values.operation(ctx, op, args));
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) { frame.codePtr++;
var key = frame.pop(); return NO_RETURN;
var val = frame.pop(); }
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, true); switch (instr.type) {
frame.codePtr++; case NOP: return execNop(ctx, instr, frame);
return NO_RETURN; case RETURN: return execReturn(ctx, instr, frame);
} case THROW: return execThrow(ctx, instr, frame);
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) { case CALL: return execCall(ctx, instr, frame);
Operation op = instr.get(0); case CALL_NEW: return execCallNew(ctx, instr, frame);
var args = new Object[op.operands]; case TRY_START: return execTryStart(ctx, instr, frame);
case TRY_END: return execTryEnd(ctx, instr, frame);
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
case DUP: return execDup(ctx, instr, frame);
frame.push(ctx, Values.operation(ctx, op, args)); case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
frame.codePtr++; case LOAD_VAR: return execLoadVar(ctx, instr, frame);
return NO_RETURN; case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
} case LOAD_ARR: return execLoadArr(ctx, instr, frame);
case LOAD_FUNC: return execLoadFunc(ctx, instr, frame);
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) { case LOAD_MEMBER: return execLoadMember(ctx, instr, frame);
switch (instr.type) { case LOAD_VAL_MEMBER: return execLoadKeyMember(ctx, instr, frame);
case NOP: return execNop(ctx, instr, frame); case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame);
case RETURN: return execReturn(ctx, instr, frame); case LOAD_GLOB: return execLoadGlob(ctx, instr, frame);
case THROW: return execThrow(ctx, instr, frame);
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame); case DISCARD: return execDiscard(ctx, instr, frame);
case CALL: return execCall(ctx, instr, frame); case STORE_MEMBER: return execStoreMember(ctx, instr, frame);
case CALL_NEW: return execCallNew(ctx, instr, frame); case STORE_VAR: return execStoreVar(ctx, instr, frame);
case TRY: return execTry(ctx, instr, frame); case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame);
case MAKE_VAR: return execMakeVar(ctx, instr, frame);
case DUP: return execDup(ctx, instr, frame);
case MOVE: return execMove(ctx, instr, frame); case KEYS: return execKeys(ctx, instr, frame);
case LOAD_VALUE: return execLoadValue(ctx, instr, frame); case DEF_PROP: return execDefProp(ctx, instr, frame);
case LOAD_VAR: return execLoadVar(ctx, instr, frame); case TYPEOF: return execTypeof(ctx, instr, frame);
case LOAD_OBJ: return execLoadObj(ctx, instr, frame); case DELETE: return execDelete(ctx, instr, frame);
case LOAD_ARR: return execLoadArr(ctx, instr, frame);
case LOAD_FUNC: return execLoadFunc(ctx, instr, frame); case JMP: return execJmp(ctx, instr, frame);
case LOAD_MEMBER: return execLoadMember(ctx, instr, frame); case JMP_IF: return execJmpIf(ctx, instr, frame);
case LOAD_VAL_MEMBER: return execLoadKeyMember(ctx, instr, frame); case JMP_IFN: return execJmpIfNot(ctx, instr, frame);
case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame);
case LOAD_GLOB: return execLoadGlob(ctx, instr, frame); case OPERATION: return execOperation(ctx, instr, frame);
case DISCARD: return execDiscard(ctx, instr, frame); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
case STORE_MEMBER: return execStoreMember(ctx, instr, frame); }
case STORE_VAR: return execStoreVar(ctx, instr, frame); }
case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame); }
case MAKE_VAR: return execMakeVar(ctx, instr, frame);
case KEYS: return execKeys(ctx, instr, frame);
case DEF_PROP: return execDefProp(ctx, instr, frame);
case TYPEOF: return execTypeof(ctx, instr, frame);
case DELETE: return execDelete(ctx, instr, frame);
case JMP: return execJmp(ctx, instr, frame);
case JMP_IF: return execJmpIf(ctx, instr, frame);
case JMP_IFN: return execJmpIfNot(ctx, instr, frame);
case OPERATION: return execOperation(ctx, instr, frame);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
}
}
}

View File

@@ -1,81 +1,78 @@
package me.topchetoeu.jscript.engine.scope; package me.topchetoeu.jscript.engine.scope;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
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.exceptions.EngineException; 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; } return obj.hasMember(ctx, name, false);
}
public boolean has(Context ctx, String name) { public Object getKey(String name) {
return obj.hasMember(ctx, name, false); return name;
} }
public Object getKey(String name) {
return name; public GlobalScope globalChild() {
} var obj = new ObjectValue();
obj.setPrototype(null, this.obj);
public GlobalScope globalChild() { return new GlobalScope(obj);
var obj = new ObjectValue(); }
obj.setPrototype(null, this.obj); public LocalScopeRecord child() {
return new GlobalScope(obj); return new LocalScopeRecord();
} }
public LocalScopeRecord child() {
return new LocalScopeRecord(this); public Object define(String name) {
} if (obj.hasMember(null, name, true)) return name;
obj.defineProperty(null, name, null);
public Object define(String name) { return name;
if (obj.hasMember(null, name, true)) return name; }
obj.defineProperty(null, name, null); public void define(String name, Variable val) {
return name; obj.defineProperty(null, name,
} new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)),
public void define(String name, Variable val) { new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }),
obj.defineProperty(null, name, true, true
new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)), );
new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }), }
true, true public void define(Context ctx, String name, boolean readonly, Object val) {
); obj.defineProperty(ctx, name, val, readonly, true, true);
} }
public void define(Context ctx, String name, boolean readonly, Object val) { public void define(String ...names) {
obj.defineProperty(ctx, name, val, readonly, true, true); for (var n : names) define(n);
} }
public void define(String ...names) { public void define(boolean readonly, FunctionValue val) {
for (var n : names) define(n); define(null, val.name, readonly, val);
} }
public void define(boolean readonly, FunctionValue val) {
define(null, val.name, readonly, val); public Object get(Context ctx, String name) {
} if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return obj.getMember(ctx, name);
public Object get(Context ctx, String name) { }
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); public void set(Context ctx, String name, Object val) {
else return obj.getMember(ctx, name); 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.");
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.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); public Set<String> keys() {
} var res = new HashSet<String>();
public Set<String> keys() { for (var key : keys()) {
var res = new HashSet<String>(); if (key instanceof String) res.add((String)key);
}
for (var key : keys()) {
if (key instanceof String) res.add((String)key); return res;
} }
return res; public GlobalScope() {
} this.obj = new ObjectValue();
}
public GlobalScope() { public GlobalScope(ObjectValue val) {
this.obj = new ObjectValue(); this.obj = val;
} }
public GlobalScope(ObjectValue val) { }
this.obj = val;
}
}

View File

@@ -1,29 +1,29 @@
package me.topchetoeu.jscript.engine.scope; package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList; import java.util.ArrayList;
public class LocalScope { public class LocalScope {
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) return catchVars.get(i - locals.length); if (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 int size() { public int size() {
return captures.length + locals.length; return captures.length + locals.length;
} }
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;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
locals[i] = new ValueVariable(false, null); locals[i] = new ValueVariable(false, null);
} }
} }
} }

View File

@@ -1,91 +1,77 @@
package me.topchetoeu.jscript.engine.scope; 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 final LocalScopeRecord parent;
public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent; private final ArrayList<String> captures = new ArrayList<>();
public final GlobalScope global; private final ArrayList<String> locals = new ArrayList<>();
private final ArrayList<String> captures = new ArrayList<>(); public String[] captures() {
private final ArrayList<String> locals = new ArrayList<>(); return captures.toArray(String[]::new);
}
public String[] captures() { public String[] locals() {
return captures.toArray(String[]::new); return locals.toArray(String[]::new);
} }
public String[] locals() {
return locals.toArray(String[]::new); public LocalScopeRecord child() {
} return new LocalScopeRecord(this);
}
@Override
public LocalScopeRecord parent() { return parent; } public int localsCount() {
return locals.size();
public LocalScopeRecord child() { }
return new LocalScopeRecord(this, global); public int capturesCount() {
} return captures.size();
}
public int localsCount() {
return locals.size(); public int[] getCaptures() {
} var buff = new int[captures.size()];
public int capturesCount() { var i = 0;
return captures.size();
} for (var name : captures) {
var index = parent.getKey(name);
public int[] getCaptures() { if (index instanceof Integer) buff[i++] = (int)index;
var buff = new int[captures.size()]; }
var i = 0;
var res = new int[i];
for (var name : captures) { System.arraycopy(buff, 0, res, 0, i);
var index = parent.getKey(name);
if (index instanceof Integer) buff[i++] = (int)index; return res;
} }
var res = new int[i]; public Object getKey(String name) {
System.arraycopy(buff, 0, res, 0, i); var capI = captures.indexOf(name);
var locI = locals.lastIndexOf(name);
return res; if (locI >= 0) return locI;
} if (capI >= 0) return ~capI;
if (parent != null) {
public Object getKey(String name) { var res = parent.getKey(name);
var capI = captures.indexOf(name); if (res != null && res instanceof Integer) {
var locI = locals.lastIndexOf(name); captures.add(name);
if (locI >= 0) return locI; return -captures.size();
if (capI >= 0) return ~capI; }
if (parent != null) { }
var res = parent.getKey(name);
if (res != null && res instanceof Integer) { return name;
captures.add(name); }
return -captures.size(); public Object define(String name, boolean force) {
} if (!force && locals.contains(name)) return locals.indexOf(name);
} locals.add(name);
return locals.size() - 1;
return name; }
} public Object define(String name) {
public boolean has(Context ctx, String name) { return define(name, false);
return }
global.has(ctx, name) || public void undefine() {
locals.contains(name) || locals.remove(locals.size() - 1);
parent != null && parent.has(ctx, name); }
}
public Object define(String name, boolean force) { public LocalScopeRecord() {
if (!force && locals.contains(name)) return locals.indexOf(name); this.parent = null;
locals.add(name); }
return locals.size() - 1; public LocalScopeRecord(LocalScopeRecord parent) {
} this.parent = parent;
public Object define(String name) { }
return define(name, false); }
}
public void undefine() {
locals.remove(locals.size() - 1);
}
public LocalScopeRecord(GlobalScope global) {
this.parent = null;
this.global = global;
}
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
this.parent = parent;
this.global = global;
}
}

View File

@@ -1,8 +1,7 @@
package me.topchetoeu.jscript.engine.scope; 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

@@ -1,28 +1,28 @@
package me.topchetoeu.jscript.engine.scope; package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
public class ValueVariable implements Variable { public class ValueVariable implements Variable {
public boolean readonly; public boolean readonly;
public Object value; public Object value;
@Override @Override
public boolean readonly() { return readonly; } public boolean readonly() { return readonly; }
@Override @Override
public Object get(Context ctx) { public Object get(Context ctx) {
return value; return value;
} }
@Override @Override
public void set(Context ctx, Object val) { public void set(Context ctx, Object val) {
if (readonly) return; if (readonly) return;
this.value = Values.normalize(ctx, val); this.value = Values.normalize(ctx, val);
} }
public ValueVariable(boolean readonly, Object val) { public ValueVariable(boolean readonly, Object val) {
this.readonly = readonly; this.readonly = readonly;
this.value = val; this.value = val;
} }
} }

View File

@@ -1,9 +1,9 @@
package me.topchetoeu.jscript.engine.scope; 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); Object get(Context ctx);
default boolean readonly() { return true; } default boolean readonly() { return true; }
default void set(Context ctx, Object val) { } default void set(Context ctx, Object val) { }
} }

View File

@@ -1,220 +1,220 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
// TODO: Make methods generic // TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> { public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object(); private static final Object UNDEFINED = new Object();
private Object[] values; private Object[] values;
private int size; private int size;
private Object[] alloc(int index) { private Object[] alloc(int index) {
index++; index++;
if (index < values.length) return values; 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);
return arr; return arr;
} }
public int size() { return size; } public int size() { return size; }
public boolean setSize(int val) { public boolean setSize(int val) {
if (val < 0) return false; if (val < 0) return false;
if (size > val) shrink(size - val); if (size > val) shrink(size - val);
else { else {
values = alloc(val); values = alloc(val);
size = val; size = val;
} }
return true; return true;
} }
public Object get(int i) { public Object get(int i) {
if (i < 0 || i >= size) return null; if (i < 0 || i >= size) return null;
var res = values[i]; var res = values[i];
if (res == UNDEFINED) return null; if (res == UNDEFINED) return null;
else return res; else return res;
} }
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;
values = 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;
values[i] = val; values[i] = val;
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 < size && 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;
values[i] = null; values[i] = null;
} }
public void shrink(int n) { public void shrink(int n) {
if (n >= values.length) { if (n >= values.length) {
values = new Object[16]; values = new Object[16];
size = 0; size = 0;
} }
else { else {
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
values[--size] = null; values[--size] = null;
} }
} }
} }
public Object[] toArray() { public Object[] toArray() {
Object[] res = new Object[size]; Object[] res = new Object[size];
copyTo(res, 0, 0, size); copyTo(res, 0, 0, size);
return res; return res;
} }
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) { public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null; if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null; if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart]; else arr[i + sourceStart] = values[i + destStart];
} }
} }
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.remove(i + destStart); 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 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]);
} }
} }
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) { public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
set(ctx, i + destStart, arr[i + sourceStart]); set(ctx, i + destStart, arr[i + sourceStart]);
} }
} }
public void move(int srcI, int dstI, int n) { public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n); values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n); System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n; if (dstI + n >= size) size = dstI + n;
} }
public void sort(Comparator<Object> comparator) { public void sort(Comparator<Object> comparator) {
Arrays.sort(values, 0, size, (a, b) -> { Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0; var _a = 0;
var _b = 0; var _b = 0;
if (a == UNDEFINED) _a = 1; if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2; if (a == null) _a = 2;
if (b == UNDEFINED) _b = 1; if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2; if (b == null) _b = 2;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b); if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b); return comparator.compare(a, b);
}); });
} }
@Override @Override
protected Object getField(Context ctx, Object key) { 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) {
return get((int)i); return get((int)i);
} }
} }
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) { 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) {
set(ctx, (int)i, val); set(ctx, (int)i, val);
return true; return true;
} }
} }
return super.setField(ctx, key, val); return super.setField(ctx, key, val);
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) { 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) {
return has((int)i); return has((int)i);
} }
} }
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }
@Override @Override
protected void deleteField(Context ctx, Object key) { 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) {
remove((int)i); remove((int)i);
return; return;
} }
} }
super.deleteField(ctx, key); super.deleteField(ctx, key);
} }
@Override @Override
public List<Object> keys(boolean includeNonEnumerable) { public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable); var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) { for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i); if (has(i)) res.add(i);
} }
return res; return res;
} }
@Override @Override
public Iterator<Object> iterator() { public Iterator<Object> iterator() {
return new Iterator<Object>() { return new Iterator<Object>() {
private int i = 0; private int i = 0;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return i < size(); return i < size();
} }
@Override @Override
public Object next() { public Object next() {
if (!hasNext()) return null; if (!hasNext()) return null;
return get(i++); return get(i++);
} }
}; };
} }
public ArrayValue() { public ArrayValue() {
super(PlaceholderProto.ARRAY); super(PlaceholderProto.ARRAY);
values = new Object[16]; values = new Object[16];
size = 0; size = 0;
} }
public ArrayValue(int cap) { public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY); super(PlaceholderProto.ARRAY);
values = new Object[cap]; values = new Object[cap];
size = 0; size = 0;
} }
public ArrayValue(Context ctx, Object ...values) { public ArrayValue(Context ctx, Object ...values) {
this(); this();
this.values = new Object[values.length]; this.values = new Object[values.length];
size = values.length; size = values.length;
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<?> 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,60 +1,57 @@
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.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.StackData; 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.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 Instruction[] body;
public final int length; public final String[] captureNames, localNames;
public final Instruction[] body; public final ValueVariable[] captures;
public final String[] captureNames, localNames; public Environment environment;
public final ValueVariable[] captures;
public Environment environment; public Location loc() {
for (var instr : body) {
public Location loc() { if (instr.location != null) return instr.location;
for (var instr : body) { }
if (instr.location != null) return instr.location; return null;
} }
return null; public String readable() {
} var loc = loc();
public String readable() { if (loc == null) return name;
var loc = loc(); else if (name.equals("")) return loc.toString();
if (loc == null) return name; else return name + "@" + loc;
else if (name.equals("")) return loc.toString(); }
else return name + "@" + loc;
} @Override
public Object call(Context ctx, Object thisArg, Object ...args) {
@Override var frame = new CodeFrame(ctx, thisArg, args, this);
public Object call(Context ctx, Object thisArg, Object ...args) { try {
var frame = new CodeFrame(ctx, thisArg, args, this); ctx.pushFrame(frame);
try {
StackData.pushFrame(ctx, frame); while (true) {
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
while (true) { if (res != Runners.NO_RETURN) return res;
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); }
if (res != Runners.NO_RETURN) return res; }
} finally {
} ctx.popFrame(frame);
finally { }
StackData.popFrame(ctx, frame); }
}
} public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
super(name, body.argsN);
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) { this.captures = captures;
super(name, length); this.captureNames = body.captureNames;
this.captures = captures; this.localNames = body.localNames;
this.captureNames = body.captureNames; this.environment = environment;
this.localNames = body.localNames; this.localsN = body.localsN;
this.environment = environment; this.body = body.instructions;
this.localsN = localsN; }
this.length = length; }
this.body = body.instructions;
}
}

View File

@@ -1,70 +1,70 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public abstract class FunctionValue extends ObjectValue { public abstract class FunctionValue extends ObjectValue {
public String name = ""; public String name = "";
public boolean special = false; public boolean special = false;
public int length; public int length;
@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); public abstract Object call(Context ctx, Object thisArg, Object ...args);
public Object call(Context ctx) { public Object call(Context ctx) {
return call(ctx, null); return call(ctx, null);
} }
@Override @Override
protected Object getField(Context ctx, Object key) { 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) { 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) { 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);
} }
@Override @Override
public List<Object> keys(boolean includeNonEnumerable) { public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable); var res = super.keys(includeNonEnumerable);
if (includeNonEnumerable) { if (includeNonEnumerable) {
res.add("name"); res.add("name");
res.add("length"); res.add("length");
} }
return res; return res;
} }
public FunctionValue(String name, int length) { public FunctionValue(String name, int length) {
super(PlaceholderProto.FUNCTION); super(PlaceholderProto.FUNCTION);
if (name == null) name = ""; if (name == null) name = "";
this.length = length; this.length = length;
this.name = name; this.name = name;
nonConfigurableSet.add("name"); nonConfigurableSet.add("name");
nonEnumerableSet.add("name"); nonEnumerableSet.add("name");
nonWritableSet.add("length"); nonWritableSet.add("length");
nonConfigurableSet.add("length"); nonConfigurableSet.add("length");
nonEnumerableSet.add("length"); nonEnumerableSet.add("length");
var proto = new ObjectValue(); var proto = new ObjectValue();
proto.defineProperty(null, "constructor", this, true, false, false); proto.defineProperty(null, "constructor", this, true, false, false);
this.defineProperty(null, "prototype", proto, true, false, false); this.defineProperty(null, "prototype", proto, true, false, false);
} }
} }

View File

@@ -1,25 +1,25 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.Context; 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); 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) { public Object call(Context ctx, Object thisArg, Object ...args) {
return action.run(ctx, thisArg, args); return action.run(ctx, thisArg, args);
} }
public NativeFunction(String name, NativeFunctionRunner action) { public NativeFunction(String name, NativeFunctionRunner action) {
super(name, 0); super(name, 0);
this.action = action; this.action = action;
} }
public NativeFunction(NativeFunctionRunner action) { public NativeFunction(NativeFunctionRunner action) {
super("", 0); super("", 0);
this.action = action; this.action = action;
} }
} }

View File

@@ -1,19 +1,32 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public class NativeWrapper extends ObjectValue { public class NativeWrapper extends ObjectValue {
private static final Object NATIVE_PROTO = new Object(); private static final Object NATIVE_PROTO = new Object();
public final Object wrapped; public final Object wrapped;
@Override @Override
public ObjectValue getPrototype(Context ctx) { public ObjectValue getPrototype(Context ctx) {
if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass()); if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
else return super.getPrototype(ctx); else return super.getPrototype(ctx);
} }
public NativeWrapper(Object wrapped) { @Override
this.wrapped = wrapped; public String toString() {
prototype = NATIVE_PROTO; return wrapped.toString();
} }
} @Override
public boolean equals(Object obj) {
return wrapped.equals(obj);
}
@Override
public int hashCode() {
return wrapped.hashCode();
}
public NativeWrapper(Object wrapped) {
this.wrapped = wrapped;
prototype = NATIVE_PROTO;
}
}

View File

@@ -1,344 +1,344 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public class ObjectValue { public class ObjectValue {
public static enum PlaceholderProto { public static enum PlaceholderProto {
NONE, NONE,
OBJECT, OBJECT,
ARRAY, ARRAY,
FUNCTION, FUNCTION,
ERROR, ERROR,
SYNTAX_ERROR, SYNTAX_ERROR,
TYPE_ERROR, TYPE_ERROR,
RANGE_ERROR, RANGE_ERROR,
} }
public static enum State { public static enum State {
NORMAL, NORMAL,
NO_EXTENSIONS, NO_EXTENSIONS,
SEALED, SEALED,
FROZEN, FROZEN,
} }
public static class Property { public static class Property {
public final FunctionValue getter; public final FunctionValue getter;
public final FunctionValue setter; public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) { public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter; this.getter = getter;
this.setter = setter; this.setter = setter;
} }
} }
private static final Object OBJ_PROTO = new Object(); private static final Object OBJ_PROTO = new Object();
private static final Object ARR_PROTO = new Object(); private static final Object ARR_PROTO = new Object();
private static final Object FUNC_PROTO = new Object(); private static final Object FUNC_PROTO = new Object();
private static final Object ERR_PROTO = new Object(); private static final Object ERR_PROTO = new Object();
private static final Object SYNTAX_ERR_PROTO = new Object(); private static final Object SYNTAX_ERR_PROTO = new Object();
private static final Object TYPE_ERR_PROTO = new Object(); private static final Object TYPE_ERR_PROTO = new Object();
private static final Object RANGE_ERR_PROTO = new Object(); private static final Object RANGE_ERR_PROTO = new Object();
protected Object prototype; protected Object prototype;
public State state = State.NORMAL; public State state = State.NORMAL;
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>(); public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>(); public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>(); public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>(); public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>(); public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
public final boolean memberWritable(Object key) { public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false; if (state == State.FROZEN) return false;
return !values.containsKey(key) || !nonWritableSet.contains(key); return !values.containsKey(key) || !nonWritableSet.contains(key);
} }
public final boolean memberConfigurable(Object key) { public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false; if (state == State.SEALED || state == State.FROZEN) return false;
return !nonConfigurableSet.contains(key); return !nonConfigurableSet.contains(key);
} }
public final boolean memberEnumerable(Object key) { public final boolean memberEnumerable(Object key) {
return !nonEnumerableSet.contains(key); return !nonEnumerableSet.contains(key);
} }
public final boolean extensible() { public final boolean extensible() {
return state == State.NORMAL; return state == State.NORMAL;
} }
public final void preventExtensions() { public final void preventExtensions() {
if (state == State.NORMAL) state = State.NO_EXTENSIONS; if (state == State.NORMAL) state = State.NO_EXTENSIONS;
} }
public final void seal() { public final void seal() {
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED; if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
} }
public final void freeze() { public final void freeze() {
state = State.FROZEN; state = State.FROZEN;
} }
public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) { public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
boolean reconfigured = boolean reconfigured =
writable != memberWritable(key) || writable != memberWritable(key) ||
configurable != memberConfigurable(key) || configurable != memberConfigurable(key) ||
enumerable != memberEnumerable(key); enumerable != memberEnumerable(key);
if (!reconfigured) { if (!reconfigured) {
if (!memberWritable(key)) { if (!memberWritable(key)) {
var a = values.get(key); var a = values.get(key);
var b = val; var b = val;
if (a == null || b == null) return a == null && b == null; if (a == null || b == null) return a == null && b == null;
return a == b || a.equals(b); return a == b || a.equals(b);
} }
values.put(key, val); values.put(key, val);
return true; return true;
} }
if ( if (
properties.containsKey(key) && properties.containsKey(key) &&
values.get(key) == val && values.get(key) == val &&
!reconfigured !reconfigured
) 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)) return false; if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
properties.remove(key); properties.remove(key);
values.remove(key); values.remove(key);
if (!writable) nonWritableSet.add(key); if (!writable) nonWritableSet.add(key);
if (!configurable) nonConfigurableSet.add(key); if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key); if (!enumerable) nonEnumerableSet.add(key);
values.put(key, val); values.put(key, val);
return true; return true;
} }
public final boolean defineProperty(Context ctx, Object key, Object val) { public final boolean defineProperty(Context ctx, Object key, Object val) {
return defineProperty(ctx, key, val, true, true, true); return defineProperty(ctx, key, val, true, true, true);
} }
public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if ( if (
properties.containsKey(key) && properties.containsKey(key) &&
properties.get(key).getter == getter && properties.get(key).getter == getter &&
properties.get(key).setter == setter && properties.get(key).setter == setter &&
!configurable == nonConfigurableSet.contains(key) && !configurable == nonConfigurableSet.contains(key) &&
!enumerable == nonEnumerableSet.contains(key) !enumerable == nonEnumerableSet.contains(key)
) 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)) return false; if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
properties.remove(key); properties.remove(key);
values.remove(key); values.remove(key);
if (!configurable) nonConfigurableSet.add(key); if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key); if (!enumerable) nonEnumerableSet.add(key);
properties.put(key, new Property(getter, setter)); properties.put(key, new Property(getter, setter));
return true; return true;
} }
public ObjectValue getPrototype(Context ctx) { public ObjectValue getPrototype(Context ctx) {
try { try {
if (prototype == OBJ_PROTO) return ctx.environment().proto("object"); if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
if (prototype == ARR_PROTO) return ctx.environment().proto("array"); if (prototype == ARR_PROTO) return ctx.environment().proto("array");
if (prototype == FUNC_PROTO) return ctx.environment().proto("function"); if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
if (prototype == ERR_PROTO) return ctx.environment().proto("error"); if (prototype == ERR_PROTO) return ctx.environment().proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr"); if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr"); if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.environment().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;
} }
public final boolean setPrototype(Context ctx, Object val) { public final boolean setPrototype(Context ctx, Object val) {
val = Values.normalize(ctx, val); val = Values.normalize(ctx, val);
if (!extensible()) return false; if (!extensible()) return false;
if (val == null || val == Values.NULL) { if (val == null || val == Values.NULL) {
prototype = null; prototype = null;
return true; return true;
} }
else if (Values.isObject(val)) { else if (Values.isObject(val)) {
var obj = Values.object(val); var obj = Values.object(val);
if (ctx != null && ctx.environment() != null) { if (ctx != null && ctx.environment() != null) {
if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO; if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO; else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO; else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO; else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO; else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.environment().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;
return true; return true;
} }
return false; return false;
} }
public final boolean setPrototype(PlaceholderProto val) { public final boolean setPrototype(PlaceholderProto val) {
if (!extensible()) return false; if (!extensible()) return false;
switch (val) { switch (val) {
case OBJECT: prototype = OBJ_PROTO; break; case OBJECT: prototype = OBJ_PROTO; break;
case FUNCTION: prototype = FUNC_PROTO; break; case FUNCTION: prototype = FUNC_PROTO; break;
case ARRAY: prototype = ARR_PROTO; break; case ARRAY: prototype = ARR_PROTO; break;
case ERROR: prototype = ERR_PROTO; break; case ERROR: prototype = ERR_PROTO; break;
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
case NONE: prototype = null; break; case NONE: prototype = null; break;
} }
return true; return true;
} }
protected Property getProperty(Context ctx, Object key) { 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) { 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) { 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);
} }
values.put(key, val); values.put(key, val);
return true; return true;
} }
protected void deleteField(Context ctx, Object key) { protected void deleteField(Context ctx, Object key) {
values.remove(key); values.remove(key);
} }
protected boolean hasField(Context ctx, Object key) { 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) { 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)) {
var res = getPrototype(ctx); var res = getPrototype(ctx);
return res == null ? Values.NULL : res; return res == null ? Values.NULL : res;
} }
var prop = getProperty(ctx, key); var prop = getProperty(ctx, key);
if (prop != null) { if (prop != null) {
if (prop.getter == null) return null; if (prop.getter == null) return null;
else return prop.getter.call(ctx, Values.normalize(ctx, thisArg)); else return prop.getter.call(ctx, Values.normalize(ctx, thisArg));
} }
else return getField(ctx, key); else return getField(ctx, key);
} }
public final Object getMember(Context ctx, Object key) { 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) { 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);
if (prop != null) { if (prop != null) {
if (prop.setter == null) return false; if (prop.setter == null) return false;
prop.setter.call(ctx, Values.normalize(ctx, thisArg), val); prop.setter.call(ctx, Values.normalize(ctx, thisArg), val);
return true; return true;
} }
else if (onlyProps) return false; else if (onlyProps) return false;
else if (!extensible() && !values.containsKey(key)) return false; else if (!extensible() && !values.containsKey(key)) return false;
else if (key == null) { else if (key == null) {
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) { 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) { 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) { 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;
properties.remove(key); properties.remove(key);
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
deleteField(ctx, key); deleteField(ctx, key);
return true; return true;
} }
public final ObjectValue getMemberDescriptor(Context ctx, Object key) { 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);
var res = new ObjectValue(); var res = new ObjectValue();
res.defineProperty(ctx, "configurable", memberConfigurable(key)); res.defineProperty(ctx, "configurable", memberConfigurable(key));
res.defineProperty(ctx, "enumerable", memberEnumerable(key)); res.defineProperty(ctx, "enumerable", memberEnumerable(key));
if (prop != null) { if (prop != null) {
res.defineProperty(ctx, "get", prop.getter); res.defineProperty(ctx, "get", prop.getter);
res.defineProperty(ctx, "set", prop.setter); res.defineProperty(ctx, "set", prop.setter);
} }
else if (hasField(ctx, key)) { else if (hasField(ctx, key)) {
res.defineProperty(ctx, "value", values.get(key)); res.defineProperty(ctx, "value", values.get(key));
res.defineProperty(ctx, "writable", memberWritable(key)); res.defineProperty(ctx, "writable", memberWritable(key));
} }
else return null; else return null;
return res; return res;
} }
public List<Object> keys(boolean includeNonEnumerable) { public List<Object> keys(boolean includeNonEnumerable) {
var res = new ArrayList<Object>(); var res = new ArrayList<Object>();
for (var key : values.keySet()) { for (var key : values.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue; if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key); res.add(key);
} }
for (var key : properties.keySet()) { for (var key : properties.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue; if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key); res.add(key);
} }
return res; return res;
} }
public ObjectValue(Context ctx, Map<?, ?> values) { public ObjectValue(Context ctx, Map<?, ?> values) {
this(PlaceholderProto.OBJECT); this(PlaceholderProto.OBJECT);
for (var el : values.entrySet()) { for (var el : values.entrySet()) {
defineProperty(ctx, el.getKey(), el.getValue()); defineProperty(ctx, el.getKey(), el.getValue());
} }
} }
public ObjectValue(PlaceholderProto proto) { public ObjectValue(PlaceholderProto proto) {
nonConfigurableSet.add("__proto__"); nonConfigurableSet.add("__proto__");
nonEnumerableSet.add("__proto__"); nonEnumerableSet.add("__proto__");
setPrototype(proto); setPrototype(proto);
} }
public ObjectValue() { public ObjectValue() {
this(PlaceholderProto.OBJECT); this(PlaceholderProto.OBJECT);
} }
} }

View File

@@ -22,6 +22,9 @@ public class ScopeValue extends ObjectValue {
return true; 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); return super.setField(ctx, key, val);
} }
@Override @Override

View File

@@ -1,15 +1,15 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
public final class Symbol { public final class Symbol {
public final String value; public final String value;
public Symbol(String value) { public Symbol(String value) {
this.value = value; this.value = value;
} }
@Override @Override
public String toString() { public String toString() {
if (value == null) return "Symbol"; if (value == null) return "Symbol";
else return "@@" + value; else return "@@" + value;
} }
} }

View File

@@ -17,8 +17,27 @@ 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.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; }
@@ -51,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;
@@ -105,7 +123,7 @@ public class Values {
} }
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;
@@ -137,7 +155,7 @@ 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";
} }
@@ -191,12 +209,18 @@ public class Values {
return _a >>> _b; return _a >>> _b;
} }
public static int compare(Context ctx, Object a, Object b) { public static CompareResult compare(Context ctx, Object a, Object b) {
a = toPrimitive(ctx, a, ConvertHint.VALUEOF); 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) {
@@ -232,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]);
@@ -272,15 +296,20 @@ 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 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) { 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);
@@ -290,7 +319,7 @@ public class Values {
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) {
@@ -518,7 +547,7 @@ 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.environment().symbol("Symbol.iterator"); var symbol = ctx.environment().symbol("Symbol.iterator");
@@ -571,7 +600,7 @@ public class Values {
}; };
} }
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) { public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue(); var res = new ObjectValue();
try { try {
@@ -592,10 +621,43 @@ public class Values {
return res; return res;
} }
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) { public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
return fromJavaIterator(ctx, it.iterator()); return toJSIterator(ctx, it.iterator());
} }
public static ObjectValue toJSAsyncIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue();
try {
var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
return PromiseLib.await(ctx, () -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else {
var obj = new ObjectValue();
obj.defineProperty(_ctx, "value", it.next());
return obj;
}
});
}));
return res;
}
private static boolean isEmptyFunc(ObjectValue val) {
if (!(val instanceof FunctionValue)) return false;
if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false;
var proto = val.values.get("prototype");
if (!(proto instanceof ObjectValue)) return false;
var protoObj = (ObjectValue)proto;
if (protoObj.values.get("constructor") != val) return false;
if (protoObj.values.size() + protoObj.properties.size() != 1) return false;
return true;
}
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) { private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) {
if (tab == 0 && val instanceof String) { if (tab == 0 && val instanceof String) {
System.out.print(val); System.out.print(val);
@@ -610,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);
@@ -643,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 {
@@ -661,12 +720,13 @@ public class Values {
printValue(ctx, el.getKey(), passed, tab + 1); printValue(ctx, el.getKey(), passed, tab + 1);
System.out.println(": [prop],"); System.out.println(": [prop],");
} }
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");
@@ -681,7 +741,7 @@ public class Values {
try { try {
if (err instanceof EngineException) { if (err instanceof EngineException) {
var ee = ((EngineException)err); var ee = ((EngineException)err);
System.out.println(prefix + " " + ee.toString(new Context(ee.engine).pushEnv(ee.env))); System.out.println(prefix + " " + ee.toString(new Context(ee.engine, ee.env)));
} }
else if (err instanceof SyntaxException) { else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg); System.out.println("Syntax error:" + ((SyntaxException)err).msg);

View File

@@ -1,25 +1,25 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
public interface Awaitable<T> { public interface Awaitable<T> {
T await() throws FinishedException; T await() throws FinishedException;
default Observable<T> toObservable() { default Observable<T> toObservable() {
return sub -> { return sub -> {
var thread = new Thread(() -> { var thread = new Thread(() -> {
try { try {
sub.next(await()); sub.next(await());
sub.finish(); sub.finish();
} }
catch (InterruptException | FinishedException e) { sub.finish(); } catch (InterruptException | FinishedException e) { sub.finish(); }
catch (RuntimeException e) { catch (RuntimeException e) {
sub.error(e); sub.error(e);
} }
}, "Awaiter"); }, "Awaiter");
thread.start(); thread.start();
return () -> thread.interrupt(); return () -> thread.interrupt();
}; };
} }
} }

View File

@@ -1,34 +1,34 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public class DataNotifier<T> implements Awaitable<T> { public class DataNotifier<T> implements Awaitable<T> {
private Notifier notifier = new Notifier(); private Notifier notifier = new Notifier();
private boolean isErr; private boolean isErr;
private T val; private T val;
private RuntimeException err; private RuntimeException err;
public void error(RuntimeException t) { public void error(RuntimeException t) {
err = t; err = t;
isErr = true; isErr = true;
notifier.next(); notifier.next();
} }
public void error(Throwable t) { public void error(Throwable t) {
error(new RuntimeException(t)); error(new RuntimeException(t));
} }
public void next(T val) { public void next(T val) {
this.val = val; this.val = val;
isErr = false; isErr = false;
notifier.next(); notifier.next();
} }
public T await() { public T await() {
notifier.await(); notifier.await();
try { try {
if (isErr) throw err; if (isErr) throw err;
else return val; else return val;
} }
finally { finally {
this.err = null; this.err = null;
this.val = null; this.val = null;
} }
} }
} }

View File

@@ -1,49 +1,49 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import java.util.HashSet; import java.util.HashSet;
public class Event<T> implements Observer<T>, Observable<T> { public class Event<T> implements Observer<T>, Observable<T> {
private HashSet<Observer<T>> handlers = new HashSet<>(); private HashSet<Observer<T>> handlers = new HashSet<>();
public Handle on(Observer<T> handler) { public Handle on(Observer<T> handler) {
if (handlers == null) { if (handlers == null) {
handler.finish(); handler.finish();
return () -> {}; return () -> {};
} }
handlers.add(handler); handlers.add(handler);
return () -> { return () -> {
if (handlers == null) return; if (handlers == null) return;
handlers.remove(handler); handlers.remove(handler);
}; };
} }
public boolean isFinished() { public boolean isFinished() {
return handlers == null; return handlers == null;
} }
public void next(T value) { public void next(T value) {
if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
for (var handler : handlers) { for (var handler : handlers) {
handler.next(value); handler.next(value);
} }
} }
public void error(RuntimeException value) { public void error(RuntimeException value) {
if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
for (var handler : handlers) { for (var handler : handlers) {
handler.error(value); handler.error(value);
} }
handlers.clear(); handlers.clear();
handlers = null; handlers = null;
} }
public void finish() { public void finish() {
if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
for (var handler : handlers) { for (var handler : handlers) {
handler.finish(); handler.finish();
} }
handlers.clear(); handlers.clear();
handlers = null; handlers = null;
} }
} }

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public class FinishedException extends RuntimeException { public class FinishedException extends RuntimeException {
public FinishedException() { public FinishedException() {
super("The observable has ended."); super("The observable has ended.");
} }
} }

View File

@@ -1,5 +1,5 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Handle { public interface Handle {
void free(); void free();
} }

View File

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

View File

@@ -1,75 +1,75 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Observable<T> { public interface Observable<T> {
Handle on(Observer<T> val); Handle on(Observer<T> val);
default Handle once(Observer<T> observer) { default Handle once(Observer<T> observer) {
// Java is fucking retarded // Java is fucking retarded
var unhandler = new Handle[1]; var unhandler = new Handle[1];
var shouldUnsub = new boolean[1]; var shouldUnsub = new boolean[1];
unhandler[0] = on(new Observer<>() { unhandler[0] = on(new Observer<>() {
public void next(T data) { public void next(T data) {
observer.next(data); observer.next(data);
if (unhandler[0] == null) shouldUnsub[0] = true; if (unhandler[0] == null) shouldUnsub[0] = true;
else unhandler[0].free(); else unhandler[0].free();
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
observer.error(err); observer.error(err);
if (unhandler[0] == null) shouldUnsub[0] = true; if (unhandler[0] == null) shouldUnsub[0] = true;
else unhandler[0].free(); else unhandler[0].free();
} }
public void finish() { public void finish() {
observer.finish(); observer.finish();
if (unhandler[0] == null) shouldUnsub[0] = true; if (unhandler[0] == null) shouldUnsub[0] = true;
else unhandler[0].free(); else unhandler[0].free();
} }
}); });
if (shouldUnsub[0]) { if (shouldUnsub[0]) {
unhandler[0].free(); unhandler[0].free();
return () -> {}; return () -> {};
} }
else return unhandler[0]; else return unhandler[0];
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default Awaitable<T> toAwaitable() { default Awaitable<T> toAwaitable() {
return () -> { return () -> {
var notifier = new Notifier(); var notifier = new Notifier();
var valRef = new Object[1]; var valRef = new Object[1];
var isErrRef = new boolean[1]; var isErrRef = new boolean[1];
once(new Observer<>() { once(new Observer<>() {
public void next(T data) { public void next(T data) {
valRef[0] = data; valRef[0] = data;
notifier.next(); notifier.next();
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
isErrRef[0] = true; isErrRef[0] = true;
valRef[0] = err; valRef[0] = err;
notifier.next(); notifier.next();
} }
public void finish() { public void finish() {
isErrRef[0] = true; isErrRef[0] = true;
valRef[0] = new FinishedException(); valRef[0] = new FinishedException();
notifier.next(); notifier.next();
} }
}); });
notifier.await(); notifier.await();
if (isErrRef[0]) throw (RuntimeException)valRef[0]; if (isErrRef[0]) throw (RuntimeException)valRef[0];
else return (T)valRef[0]; else return (T)valRef[0];
}; };
} }
default Observable<T> encapsulate() { default Observable<T> encapsulate() {
return val -> on(val); return val -> on(val);
} }
default <T2> Observable<T2> pipe(Pipe<T, T2> pipe) { default <T2> Observable<T2> pipe(Pipe<T, T2> pipe) {
return sub -> on(pipe.apply(sub)); return sub -> on(pipe.apply(sub));
} }
default WarmObservable<T> warmUp() { default WarmObservable<T> warmUp() {
return new WarmObservable<>(this); return new WarmObservable<>(this);
} }
} }

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Observer<T> { public interface Observer<T> {
public void next(T data); public void next(T data);
public default void error(RuntimeException err) {} public default void error(RuntimeException err) {}
public default void finish() { } public default void finish() { }
} }

View File

@@ -1,59 +1,59 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Pipe<T, T2> { public interface Pipe<T, T2> {
Observer<T> apply(Observer<T2> obs); Observer<T> apply(Observer<T2> obs);
// void next(T val, Observer<T2> target); // void next(T val, Observer<T2> target);
// default void error(RuntimeException err, Observer<T2> target) { // default void error(RuntimeException err, Observer<T2> target) {
// target.error(err); // target.error(err);
// } // }
// default void finish(Observer<T2> target) { // default void finish(Observer<T2> target) {
// target.finish(); // target.finish();
// } // }
public static interface MapFunc<T1, T2> { public static interface MapFunc<T1, T2> {
T2 map(T1 val); T2 map(T1 val);
} }
public static <T1, T2> Pipe<T1, T2> map(MapFunc<T1, T2> func) { public static <T1, T2> Pipe<T1, T2> map(MapFunc<T1, T2> func) {
return o -> val -> o.next(func.map(val)); return o -> val -> o.next(func.map(val));
} }
public static <T> Pipe<T, T> filter(MapFunc<T, Boolean> func) { public static <T> Pipe<T, T> filter(MapFunc<T, Boolean> func) {
return o -> val -> { return o -> val -> {
if (func.map(val)) o.next(val); if (func.map(val)) o.next(val);
}; };
} }
public static <T> Pipe<T, T> skip(int n) { public static <T> Pipe<T, T> skip(int n) {
var i = new int[1]; var i = new int[1];
return target -> val -> { return target -> val -> {
if (i[0] >= n) target.next(val); if (i[0] >= n) target.next(val);
else i[0]++; else i[0]++;
}; };
} }
public static <T> Pipe<T, T> limit(int n) { public static <T> Pipe<T, T> limit(int n) {
return target -> new Observer<T>() { return target -> new Observer<T>() {
private int i; private int i;
public void next(T val) { public void next(T val) {
if (i >= n) target.finish(); if (i >= n) target.finish();
else { else {
target.next(val); target.next(val);
i++; i++;
} }
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
if (i < n) target.error(err); if (i < n) target.error(err);
} }
public void finish() { public void finish() {
if (i < n) target.finish(); if (i < n) target.finish();
} }
}; };
} }
public static <T> Pipe<T, T> first() { public static <T> Pipe<T, T> first() {
return limit(1); return limit(1);
} }
public static <T> Pipe<Observable<T>, T> merge() { public static <T> Pipe<Observable<T>, T> merge() {
return target -> val -> val.on(target); return target -> val -> val.on(target);
} }
} }

View File

@@ -1,46 +1,46 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import java.util.HashSet; import java.util.HashSet;
public class WarmObservable<T> implements Observable<T>, Handle { public class WarmObservable<T> implements Observable<T>, Handle {
private HashSet<Observer<T>> observers = new HashSet<>(); private HashSet<Observer<T>> observers = new HashSet<>();
private Handle handle; private Handle handle;
@Override @Override
public Handle on(Observer<T> val) { public Handle on(Observer<T> val) {
if (observers == null) return () -> {}; if (observers == null) return () -> {};
observers.add(val); observers.add(val);
return () -> observers.remove(val); return () -> observers.remove(val);
} }
@Override @Override
public void free() { public void free() {
if (observers == null) return; if (observers == null) return;
handle.free(); handle.free();
handle = null; handle = null;
observers = null; observers = null;
} }
public WarmObservable(Observable<T> observable) { public WarmObservable(Observable<T> observable) {
observable.on(new Observer<>() { observable.on(new Observer<>() {
public void next(T data) { public void next(T data) {
for (var obs : observers) obs.next(data); for (var obs : observers) obs.next(data);
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
for (var obs : observers) obs.error(err); for (var obs : observers) obs.error(err);
handle = null; handle = null;
observers = null; observers = null;
} }
public void finish() { public void finish() {
for (var obs : observers) obs.finish(); for (var obs : observers) obs.finish();
handle = null; handle = null;
observers = null; observers = null;
} }
}); });
} }
@Override @Override
public WarmObservable<T> warmUp() { public WarmObservable<T> warmUp() {
return this; return this;
} }
} }

View File

@@ -1,88 +1,117 @@
package me.topchetoeu.jscript.exceptions; package me.topchetoeu.jscript.exceptions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.Engine;
import me.topchetoeu.jscript.engine.Environment; 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 final Object value; public static class StackElement {
public EngineException cause; public final Location location;
public Environment env = null; public final String function;
public Engine engine = null; public final Context ctx;
public final List<String> stackTrace = new ArrayList<>();
public boolean visible() {
public EngineException add(String name, Location location) { return ctx == null || ctx.environment() == null || ctx.environment().stackVisible;
var res = ""; }
public String toString() {
if (location != null) res += "at " + location.toString() + " "; var res = "";
if (name != null && !name.equals("")) res += "in " + name + " "; var loc = location;
this.stackTrace.add(res.trim()); if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc);
return this;
} if (loc != null) res += "at " + loc.toString() + " ";
public EngineException setCause(EngineException cause) { if (function != null && !function.equals("")) res += "in " + function + " ";
this.cause = cause;
return this; return res.trim();
} }
public EngineException setCtx(Environment env, Engine engine) {
if (this.env == null) this.env = env; public StackElement(Context ctx, Location location, String function) {
if (this.engine == null) this.engine = engine; if (function != null) function = function.trim();
return this; if (function.equals("")) function = null;
}
if (ctx == null) this.ctx = null;
public String toString(Context ctx) { else this.ctx = new Context(ctx.engine, ctx.environment());
var ss = new StringBuilder(); this.location = location;
try { this.function = function;
ss.append(Values.toString(ctx, value)).append('\n'); }
} }
catch (EngineException e) {
ss.append("[Error while stringifying]\n"); public final Object value;
} public EngineException cause;
for (var line : stackTrace) { public Environment env = null;
ss.append(" ").append(line).append('\n'); public Engine engine = null;
} public final List<StackElement> stackTrace = new ArrayList<>();
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1); public EngineException add(Context ctx, String name, Location location) {
return ss.toString(); var el = new StackElement(ctx, location, name);
} if (el.function == null && el.location == null) return this;
setCtx(ctx.environment(), ctx.engine);
private static Object err(String name, String msg, PlaceholderProto proto) { stackTrace.add(el);
var res = new ObjectValue(proto); return this;
if (name != null) res.defineProperty(null, "name", name); }
res.defineProperty(null, "message", msg); public EngineException setCause(EngineException cause) {
return res; this.cause = cause;
} return this;
}
public EngineException(Object error) { public EngineException setCtx(Environment env, Engine engine) {
super(error == null ? "null" : error.toString()); if (this.env == null) this.env = env;
if (this.engine == null) this.engine = engine;
this.value = error; return this;
this.cause = null; }
}
public String toString(Context ctx) {
public static EngineException ofError(String name, String msg) { var ss = new StringBuilder();
return new EngineException(err(name, msg, PlaceholderProto.ERROR)); try {
} ss.append(Values.toString(ctx, value)).append('\n');
public static EngineException ofError(String msg) { }
return new EngineException(err(null, msg, PlaceholderProto.ERROR)); catch (EngineException e) {
} ss.append("[Error while stringifying]\n");
public static EngineException ofSyntax(SyntaxException e) { }
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); for (var line : stackTrace) {
} if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
public static EngineException ofSyntax(String msg) { }
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
} ss.deleteCharAt(ss.length() - 1);
public static EngineException ofType(String msg) { return ss.toString();
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR)); }
}
public static EngineException ofRange(String msg) { private static Object err(String name, String msg, PlaceholderProto proto) {
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR)); var res = new ObjectValue(proto);
} if (name != null) res.defineProperty(null, "name", name);
} res.defineProperty(null, "message", msg);
return res;
}
public EngineException(Object error) {
super(error == null ? "null" : error.toString());
this.value = error;
this.cause = null;
}
public static EngineException ofError(String name, String msg) {
return new EngineException(err(name, msg, PlaceholderProto.ERROR));
}
public static EngineException ofError(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
}
public static EngineException ofSyntax(SyntaxException e) {
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc);
}
public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
}
public static EngineException ofType(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR));
}
public static EngineException ofRange(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR));
}
}

View File

@@ -1,14 +1,14 @@
package me.topchetoeu.jscript.exceptions; package me.topchetoeu.jscript.exceptions;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
public class SyntaxException extends RuntimeException { public class SyntaxException extends RuntimeException {
public final Location loc; public final Location loc;
public final String msg; public final String msg;
public SyntaxException(Location loc, String msg) { public SyntaxException(Location loc, String msg) {
super(String.format("Syntax error (at %s): %s", loc, msg)); super(String.format("Syntax error (at %s): %s", loc, msg));
this.loc = loc; this.loc = loc;
this.msg = msg; this.msg = msg;
} }
} }

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