Compare commits
83 Commits
v0.3.1-alp
...
v0.6.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c6379eb24 | |||
|
380a5c720a
|
|||
|
76c3d377af
|
|||
|
42f443572a
|
|||
|
773bc72f3e
|
|||
|
0b5178e9fd
|
|||
|
8cffcff7db
|
|||
|
60bbaaccd4
|
|||
|
60b1762462
|
|||
|
34434965d2
|
|||
|
fe86123f0f
|
|||
|
d5e6edfa8b
|
|||
|
73345062ca
|
|||
|
124341969c
|
|||
|
8defd93855
|
|||
|
6c57e0e9f2
|
|||
|
f1932914ee
|
|||
|
977701e601
|
|||
|
e8a7ac8da8
|
|||
|
6b1cb852c2
|
|||
|
b59a003086
|
|||
|
1902e41f61
|
|||
|
27162ef8ac
|
|||
|
4f22e76d2b
|
|||
|
8924e7aadc
|
|||
| 1d0e31a423 | |||
|
ab56908171
|
|||
|
eb14bb080c
|
|||
|
f52f47cdb4
|
|||
|
567eaa8514
|
|||
|
2cfdd8e335
|
|||
|
4b1ec671e2
|
|||
|
b127aadcf6
|
|||
|
b6eaff65ca
|
|||
|
443dc0ffa1
|
|||
|
e107dd3507
|
|||
|
6af3c70fce
|
|||
|
8b743f49d1
|
|||
|
e1ce384815
|
|||
|
86d205e521
|
|||
|
f0ad936e5b
|
|||
|
4a1473c5be
|
|||
|
4111dbf5c4
|
|||
|
1666682dc2
|
|||
|
f2b33d0233
|
|||
|
f5a0b6eaf7
|
|||
|
829bea755d
|
|||
|
4b0dcffd13
|
|||
|
987f8b8f00
|
|||
|
55e3d46bc2
|
|||
|
3e25068219
|
|||
|
7ecb8bfabb
|
|||
|
488deea164
|
|||
|
ed08041335
|
|||
|
0a4149ba81
|
|||
|
30f5d619c3
|
|||
|
e7dbe91374
|
|||
|
455f5a613e
|
|||
|
1eeac3ae97
|
|||
|
1acd78e119
|
|||
|
df9932874d
|
|||
|
b47d1a7576
|
|||
|
fdfa8d7713
|
|||
|
f5d1287948
|
|||
|
15f4278cb1
|
|||
| df8465cb49 | |||
|
e3104c223c
|
|||
|
1d0bae3de8
|
|||
|
b66acd3089
|
|||
|
e326847287
|
|||
|
26591d6631
|
|||
|
af31b1ab79
|
|||
|
f885d4349f
|
|||
|
d57044acb7
|
|||
|
7df4e3b03f
|
|||
|
ed1009ab69
|
|||
|
f856cdf37e
|
|||
|
4f82574b8c
|
|||
|
0ae24148d8
|
|||
|
ac128d17f4
|
|||
|
6508f15bb0
|
|||
|
69f93b4f87
|
|||
|
b675411925
|
5
.github/workflows/tagged-release.yml
vendored
5
.github/workflows/tagged-release.yml
vendored
@@ -11,6 +11,11 @@ jobs:
|
|||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: '11'
|
||||||
- name: Clone repository
|
- name: Clone repository
|
||||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||||
with:
|
with:
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -4,31 +4,42 @@
|
|||||||
|
|
||||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
**WARNING: Currently, this code is mostly 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 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`:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var engine = new PolyfillEngine(new File("."));
|
var engine = new Engine(true /* false if you dont want debugging */);
|
||||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
var env = new Environment(null, null, null);
|
||||||
|
var debugger = new DebugServer();
|
||||||
|
|
||||||
|
// Create one target for the engine and start debugging server
|
||||||
|
debugger.targets.put("target", (socket, req) -> new SimpleDebugger(socket, engine));
|
||||||
|
debugger.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||||
|
|
||||||
|
// Queue code to load internal libraries and start engine
|
||||||
|
engine.pushMsg(false, null, new Internals().getApplier(env));
|
||||||
engine.start();
|
engine.start();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
var raw = in.readLine();
|
var raw = Reading.read();
|
||||||
|
if (raw == null) break;
|
||||||
|
|
||||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
// Push a message to the engine with the raw REPL code
|
||||||
Values.printValue(engine.context(), res);
|
var res = engine.pushMsg(
|
||||||
System.out.println();
|
false, new Context(engine).pushEnv(env),
|
||||||
|
new Filename("jscript", "repl.js"), raw, null
|
||||||
|
).await();
|
||||||
|
|
||||||
|
Values.printValue(null, res);
|
||||||
}
|
}
|
||||||
catch (EngineException e) {
|
catch (EngineException e) { Values.printError(e, ""); }
|
||||||
try {
|
catch (SyntaxException ex) {
|
||||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
System.out.println("Syntax error:" + ex.msg);
|
||||||
}
|
|
||||||
catch (InterruptedException _e) { return; }
|
|
||||||
}
|
}
|
||||||
catch (IOException | InterruptedException e) { return; }
|
catch (IOException e) { }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
7
build.js
7
build.js
@@ -1,7 +1,7 @@
|
|||||||
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 conf = {
|
const conf = {
|
||||||
name: "java-jscript",
|
name: "java-jscript",
|
||||||
@@ -10,8 +10,6 @@ const conf = {
|
|||||||
version: argv[3]
|
version: argv[3]
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(conf)
|
|
||||||
|
|
||||||
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
|
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
|
||||||
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
|
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
|
||||||
|
|
||||||
@@ -76,6 +74,7 @@ async function compileJava() {
|
|||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
1004
src/assets/debugger/protocol.json
Normal file
1004
src/assets/debugger/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
113
src/assets/js/bootstrap.js
vendored
Normal file
113
src/assets/js/bootstrap.js
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
(function (ts, env, libs) {
|
||||||
|
var src = '', version = 0;
|
||||||
|
var lib = libs.concat([
|
||||||
|
'declare function exit(): never;',
|
||||||
|
'declare function go(): any;',
|
||||||
|
'declare function getTsDeclarations(): string[];'
|
||||||
|
]).join('');
|
||||||
|
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
|
||||||
|
var environments = {};
|
||||||
|
var declSnapshots = [];
|
||||||
|
|
||||||
|
var settings = {
|
||||||
|
outDir: "/out",
|
||||||
|
declarationDir: "/out",
|
||||||
|
target: ts.ScriptTarget.ES5,
|
||||||
|
lib: [ ],
|
||||||
|
module: ts.ModuleKind.None,
|
||||||
|
declaration: true,
|
||||||
|
stripInternal: true,
|
||||||
|
downlevelIteration: true,
|
||||||
|
forceConsistentCasingInFileNames: true,
|
||||||
|
experimentalDecorators: true,
|
||||||
|
strict: true,
|
||||||
|
sourceMap: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var reg = ts.createDocumentRegistry();
|
||||||
|
var service = ts.createLanguageService({
|
||||||
|
getCurrentDirectory: function() { return "/"; },
|
||||||
|
getDefaultLibFileName: function() { return "/lib.d.ts"; },
|
||||||
|
getScriptFileNames: function() {
|
||||||
|
var res = [ "/src.ts", "/lib.d.ts" ];
|
||||||
|
for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
getCompilationSettings: function () { return settings; },
|
||||||
|
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
|
||||||
|
|
||||||
|
getScriptSnapshot: function(filename) {
|
||||||
|
if (filename === "/lib.d.ts") return libSnapshot;
|
||||||
|
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||||
|
|
||||||
|
var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
|
||||||
|
if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
|
||||||
|
return declSnapshots[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("File '" + filename + "' doesn't exist.");
|
||||||
|
},
|
||||||
|
getScriptVersion: function (filename) {
|
||||||
|
if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0;
|
||||||
|
else return version;
|
||||||
|
},
|
||||||
|
}, reg);
|
||||||
|
|
||||||
|
service.getEmitOutput("/lib.d.ts");
|
||||||
|
log("Loaded libraries!");
|
||||||
|
|
||||||
|
var oldCompile = env.compile;
|
||||||
|
|
||||||
|
function compile(code, filename, env) {
|
||||||
|
src = code;
|
||||||
|
version++;
|
||||||
|
|
||||||
|
if (!environments[env.id]) environments[env.id] = []
|
||||||
|
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 !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
breakpoints: compiled.breakpoints,
|
||||||
|
mapChain: compiled.mapChain.concat(map.mappings),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply(env) {
|
||||||
|
env.compile = compile;
|
||||||
|
env.global.getTsDeclarations = function() {
|
||||||
|
return environments[env.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(env);
|
||||||
|
})(arguments[0], arguments[1], arguments[2]);
|
||||||
@@ -19,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]> {
|
||||||
@@ -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,35 @@ 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>;
|
||||||
|
readonly mode: Promise<'' | 'r' | 'rw'>;
|
||||||
|
|
||||||
|
read(n: number): Promise<number[]>;
|
||||||
|
write(buff: number[]): Promise<void>;
|
||||||
|
close(): Promise<void>;
|
||||||
|
setPointer(val: number): Promise<void>;
|
||||||
|
}
|
||||||
|
interface Filesystem {
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -508,11 +536,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]>;
|
||||||
18
src/assets/js/ts.js
Normal file
18
src/assets/js/ts.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
53
src/me/topchetoeu/jscript/Buffer.java
Normal file
53
src/me/topchetoeu/jscript/Buffer.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package me.topchetoeu.jscript;
|
||||||
|
|
||||||
|
public class Buffer {
|
||||||
|
private byte[] data;
|
||||||
|
private int length;
|
||||||
|
|
||||||
|
public void write(int i, byte[] val) {
|
||||||
|
if (i + val.length > data.length) {
|
||||||
|
var newCap = i + val.length + 1;
|
||||||
|
if (newCap < data.length * 2) newCap = data.length * 2;
|
||||||
|
|
||||||
|
var tmp = new byte[newCap];
|
||||||
|
System.arraycopy(this.data, 0, tmp, 0, length);
|
||||||
|
this.data = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(val, 0, data, i, val.length);
|
||||||
|
if (i + val.length > length) length = i + val.length;
|
||||||
|
}
|
||||||
|
public int read(int i, byte[] buff) {
|
||||||
|
int n = buff.length;
|
||||||
|
if (i + n > length) n = length - i;
|
||||||
|
System.arraycopy(data, i, buff, 0, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append(byte b) {
|
||||||
|
write(length, new byte[] { b });
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] data() {
|
||||||
|
var res = new byte[length];
|
||||||
|
System.arraycopy(this.data, 0, res, 0, length);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public int length() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Buffer(byte[] data) {
|
||||||
|
this.data = new byte[data.length];
|
||||||
|
this.length = data.length;
|
||||||
|
System.arraycopy(data, 0, this.data, 0, data.length);
|
||||||
|
}
|
||||||
|
public Buffer(int capacity) {
|
||||||
|
this.data = new byte[capacity];
|
||||||
|
this.length = 0;
|
||||||
|
}
|
||||||
|
public Buffer() {
|
||||||
|
this.data = new byte[128];
|
||||||
|
this.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,15 @@ public class Filename {
|
|||||||
|
|
||||||
|
|
||||||
public Filename(String protocol, String path) {
|
public Filename(String protocol, String path) {
|
||||||
|
path = path.trim();
|
||||||
|
protocol = protocol.trim();
|
||||||
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package me.topchetoeu.jscript;
|
package me.topchetoeu.jscript;
|
||||||
|
|
||||||
public class Location implements Comparable<Location> {
|
public class Location implements Comparable<Location> {
|
||||||
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "internal"));
|
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
|
||||||
private int line;
|
private int line;
|
||||||
private int start;
|
private int start;
|
||||||
private Filename filename;
|
private Filename filename;
|
||||||
@@ -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))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,22 +10,21 @@ import me.topchetoeu.jscript.engine.Engine;
|
|||||||
import me.topchetoeu.jscript.engine.Environment;
|
import me.topchetoeu.jscript.engine.Environment;
|
||||||
import me.topchetoeu.jscript.engine.debug.DebugServer;
|
import me.topchetoeu.jscript.engine.debug.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.NativeFunction;
|
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||||
|
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.events.Observer;
|
import me.topchetoeu.jscript.events.Observer;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
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;
|
||||||
|
|
||||||
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();
|
||||||
@@ -35,71 +34,135 @@ public class Main {
|
|||||||
Values.printError(err, null);
|
Values.printError(err, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
public void finish() {
|
||||||
engineTask.interrupt();
|
engineTask.interrupt();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
static Thread engineTask, debugTask;
|
||||||
|
static Engine engine = new Engine(true);
|
||||||
|
static DebugServer debugServer = new DebugServer();
|
||||||
|
static Environment environment = new Environment(null, null, null);
|
||||||
|
|
||||||
|
static int j = 0;
|
||||||
|
static boolean exited = false;
|
||||||
|
static String[] args;
|
||||||
|
|
||||||
|
private static void reader() {
|
||||||
|
try {
|
||||||
|
for (var arg : args) {
|
||||||
|
try {
|
||||||
|
if (arg.equals("--ts")) initTypescript();
|
||||||
|
else {
|
||||||
|
var file = Path.of(arg);
|
||||||
|
var raw = Files.readString(file);
|
||||||
|
var res = engine.pushMsg(
|
||||||
|
false, new Context(engine, 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, new Context(engine, 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(Path.of(".").toAbsolutePath()));
|
||||||
|
}
|
||||||
|
private static void initEngine() {
|
||||||
|
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
|
||||||
|
engineTask = engine.start();
|
||||||
|
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||||
|
}
|
||||||
|
private static void initTypescript() {
|
||||||
|
try {
|
||||||
|
var tsEnv = Internals.apply(new Environment(null, null, null));
|
||||||
|
tsEnv.stackVisible = false;
|
||||||
|
tsEnv.global.define(null, "module", false, new ObjectValue());
|
||||||
|
var bsEnv = Internals.apply(new Environment(null, null, null));
|
||||||
|
bsEnv.stackVisible = false;
|
||||||
|
|
||||||
|
engine.pushMsg(
|
||||||
|
false, new Context(engine, tsEnv),
|
||||||
|
new Filename("jscript", "ts.js"),
|
||||||
|
Reading.resourceToString("js/ts.js"), null
|
||||||
|
).await();
|
||||||
|
System.out.println("Loaded typescript!");
|
||||||
|
|
||||||
|
var ctx = new Context(engine, bsEnv);
|
||||||
|
|
||||||
|
engine.pushMsg(
|
||||||
|
false, ctx,
|
||||||
|
new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
|
||||||
|
tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
|
||||||
|
).await();
|
||||||
|
}
|
||||||
|
catch (EngineException e) {
|
||||||
|
Values.printError(e, "(while initializing TS)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
|
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
|
||||||
engine = new Engine();
|
|
||||||
|
|
||||||
env = new Environment(null, null, null);
|
Main.args = args;
|
||||||
var exited = new boolean[1];
|
var reader = new Thread(Main::reader);
|
||||||
var server = new DebugServer();
|
|
||||||
server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine));
|
|
||||||
|
|
||||||
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
|
initEnv();
|
||||||
new Internals().apply(env);
|
initEngine();
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}), null);
|
|
||||||
|
|
||||||
engineTask = engine.start();
|
|
||||||
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
|
||||||
|
|
||||||
var reader = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
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) { return; }
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable e) { throw new UncheckedException(e); }
|
|
||||||
if (exited[0]) debugTask.interrupt();
|
|
||||||
});
|
|
||||||
reader.setDaemon(true);
|
reader.setDaemon(true);
|
||||||
reader.setName("STD Reader");
|
reader.setName("STD Reader");
|
||||||
reader.start();
|
reader.start();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/me/topchetoeu/jscript/compilation/CalculateResult.java
Normal file
21
src/me/topchetoeu/jscript/compilation/CalculateResult.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
|
|
||||||
|
public final class CalculateResult {
|
||||||
|
public final boolean exists;
|
||||||
|
public final Object value;
|
||||||
|
|
||||||
|
public final boolean isTruthy() {
|
||||||
|
return exists && Values.toBoolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalculateResult(Object value) {
|
||||||
|
this.exists = true;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
public CalculateResult() {
|
||||||
|
this.exists = false;
|
||||||
|
this.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.engine.Environment;
|
||||||
|
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||||
|
|
||||||
public class CompileTarget {
|
public class CompileTarget {
|
||||||
public final Vector<Instruction> target = new Vector<>();
|
public final Vector<Instruction> target = new Vector<>();
|
||||||
public final Map<Long, FunctionBody> functions;
|
public final Map<Long, FunctionBody> functions;
|
||||||
public final TreeSet<Location> breakpoints;
|
public final TreeSet<Location> breakpoints;
|
||||||
|
private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
|
||||||
|
|
||||||
public Instruction add(Instruction instr) {
|
public Instruction add(Instruction instr) {
|
||||||
target.add(instr);
|
target.add(instr);
|
||||||
@@ -18,19 +23,42 @@ public class CompileTarget {
|
|||||||
public Instruction set(int i, Instruction instr) {
|
public Instruction set(int i, Instruction instr) {
|
||||||
return target.set(i, instr);
|
return target.set(i, instr);
|
||||||
}
|
}
|
||||||
public void setDebug(int i) {
|
public void setDebug(int i, BreakpointType type) {
|
||||||
breakpoints.add(target.get(i).location);
|
var instr = target.get(i);
|
||||||
|
instr.breakpoint = type;
|
||||||
|
|
||||||
|
if (type == BreakpointType.NONE) {
|
||||||
|
breakpoints.remove(target.get(i).location);
|
||||||
|
bpToInstr.remove(instr.location, instr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
breakpoints.add(target.get(i).location);
|
||||||
|
|
||||||
|
var old = bpToInstr.put(instr.location, instr);
|
||||||
|
if (old != null) old.breakpoint = BreakpointType.NONE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public void setDebug() {
|
public void setDebug(BreakpointType type) {
|
||||||
setDebug(target.size() - 1);
|
setDebug(target.size() - 1, type);
|
||||||
}
|
}
|
||||||
public Instruction get(int i) {
|
public Instruction get(int i) {
|
||||||
return target.get(i);
|
return target.get(i);
|
||||||
}
|
}
|
||||||
public int size() { return target.size(); }
|
public int size() { return target.size(); }
|
||||||
|
public Location lastLoc(Location fallback) {
|
||||||
|
if (target.size() == 0) return fallback;
|
||||||
|
else return target.get(target.size() - 1).location;
|
||||||
|
}
|
||||||
|
|
||||||
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
||||||
|
|
||||||
|
public 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) {
|
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
|
||||||
this.functions = functions;
|
this.functions = functions;
|
||||||
this.breakpoints = breakpoints;
|
this.breakpoints = breakpoints;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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() {
|
public static Instruction keys(Location loc, boolean forInFormat) {
|
||||||
return new Instruction(null, Type.KEYS);
|
return new Instruction(loc, Type.KEYS, forInFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction defProp() {
|
public static Instruction defProp(Location loc) {
|
||||||
return new Instruction(null, Type.DEF_PROP);
|
return new Instruction(loc, Type.DEF_PROP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction operation(Operation op) {
|
public static Instruction operation(Location loc, Operation op) {
|
||||||
return new Instruction(null, Type.OPERATION, op);
|
return new Instruction(loc, Type.OPERATION, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class ThrowSyntaxStatement extends Statement {
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
|
target.add(Instruction.throwSyntax(loc(), name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThrowSyntaxStatement(SyntaxException e) {
|
||||||
|
super(e.loc);
|
||||||
|
this.name = e.msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
@@ -32,23 +33,16 @@ public class VariableDeclareStatement extends Statement {
|
|||||||
for (var entry : values) {
|
for (var entry : values) {
|
||||||
if (entry.name == null) continue;
|
if (entry.name == null) continue;
|
||||||
var key = scope.getKey(entry.name);
|
var key = scope.getKey(entry.name);
|
||||||
int start = target.size();
|
|
||||||
|
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location));
|
if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
|
||||||
|
|
||||||
if (entry.value instanceof FunctionStatement) {
|
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 (target.size() != start) target.setDebug(start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ public class BreakStatement extends Statement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
target.add(Instruction.nop("break", label).locate(loc()));
|
target.add(Instruction.nop(loc(), "break", label));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BreakStatement(Location loc, String label) {
|
public BreakStatement(Location loc, String label) {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ public class ContinueStatement extends Statement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
target.add(Instruction.nop("cont", label).locate(loc()));
|
target.add(Instruction.nop(loc(), "cont", label));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContinueStatement(Location loc, String label) {
|
public ContinueStatement(Location loc, String label) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|||||||
public class DebugStatement extends Statement {
|
public class DebugStatement extends Statement {
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
target.add(Instruction.debug().locate(loc()));
|
target.add(Instruction.debug(loc()));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebugStatement(Location loc) {
|
public DebugStatement(Location loc) {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ public class DeleteStatement extends Statement {
|
|||||||
value.compile(target, scope, true);
|
value.compile(target, scope, true);
|
||||||
key.compile(target, scope, true);
|
key.compile(target, scope, true);
|
||||||
|
|
||||||
target.add(Instruction.delete().locate(loc()));
|
target.add(Instruction.delete(loc()));
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
|
|||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
|
|
||||||
public class DoWhileStatement extends Statement {
|
public class DoWhileStatement extends Statement {
|
||||||
public final Statement condition, body;
|
public final Statement condition, body;
|
||||||
@@ -20,54 +18,14 @@ public class DoWhileStatement extends Statement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (condition instanceof ConstantStatement) {
|
|
||||||
int start = target.size();
|
|
||||||
body.compile(target, scope, false);
|
|
||||||
int end = target.size();
|
|
||||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
|
||||||
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
|
||||||
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
|
|
||||||
}
|
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
body.compileWithDebug(target, scope, false);
|
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
int mid = target.size();
|
int mid = target.size();
|
||||||
condition.compile(target, scope, true);
|
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||||
int end = target.size();
|
int end = target.size();
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||||
target.add(Instruction.jmpIf(start - end).locate(loc()));
|
target.add(Instruction.jmpIf(loc(), start - end));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
var cond = condition.optimize();
|
|
||||||
var b = body.optimize();
|
|
||||||
|
|
||||||
if (b instanceof CompoundStatement) {
|
|
||||||
var comp = (CompoundStatement)b;
|
|
||||||
if (comp.statements.length > 0) {
|
|
||||||
var last = comp.statements[comp.statements.length - 1];
|
|
||||||
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
|
|
||||||
if (last instanceof BreakStatement) {
|
|
||||||
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
|
|
||||||
return new CompoundStatement(loc());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (b instanceof ContinueStatement) {
|
|
||||||
b = new CompoundStatement(loc());
|
|
||||||
}
|
|
||||||
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
|
|
||||||
|
|
||||||
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
|
|
||||||
else return new DoWhileStatement(loc(), label, cond, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
|
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ public class ForInStatement extends Statement {
|
|||||||
public final boolean isDeclaration;
|
public final boolean isDeclaration;
|
||||||
public final Statement varValue, object, body;
|
public final Statement varValue, object, body;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
public final Location varLocation;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void declare(ScopeRecord globScope) {
|
public void declare(ScopeRecord globScope) {
|
||||||
@@ -22,52 +24,45 @@ public class ForInStatement extends Statement {
|
|||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
var key = scope.getKey(varName);
|
var key = scope.getKey(varName);
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
|
||||||
|
int first = target.size();
|
||||||
|
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
|
||||||
|
|
||||||
if (varValue != null) {
|
if (varValue != null) {
|
||||||
varValue.compile(target, scope, true);
|
varValue.compile(target, scope, true);
|
||||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
object.compile(target, scope, true);
|
object.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||||
target.add(Instruction.keys());
|
target.add(Instruction.keys(loc(), true));
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup(loc()));
|
||||||
target.add(Instruction.loadMember("length"));
|
target.add(Instruction.loadValue(loc(), null));
|
||||||
target.add(Instruction.loadValue(0));
|
target.add(Instruction.operation(loc(), Operation.EQUALS));
|
||||||
target.add(Instruction.operation(Operation.LESS_EQUALS));
|
|
||||||
int mid = target.size();
|
int mid = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(loc()));
|
||||||
|
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.loadMember(varLocation, "value"));
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.storeVar(object.loc(), key));
|
||||||
target.add(Instruction.loadMember("length"));
|
target.setDebug(BreakpointType.STEP_OVER);
|
||||||
target.add(Instruction.loadValue(1));
|
|
||||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
|
||||||
target.add(Instruction.dup(1, 2));
|
|
||||||
target.add(Instruction.loadValue("length"));
|
|
||||||
target.add(Instruction.dup(1, 2));
|
|
||||||
target.add(Instruction.storeMember());
|
|
||||||
target.add(Instruction.loadMember());
|
|
||||||
target.add(Instruction.storeVar(key));
|
|
||||||
|
|
||||||
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
|
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
body.compileWithDebug(target, scope, false);
|
|
||||||
|
|
||||||
int end = target.size();
|
int end = target.size();
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
target.add(Instruction.jmp(loc(), start - end));
|
||||||
target.add(Instruction.discard().locate(loc()));
|
target.add(Instruction.discard(loc()));
|
||||||
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
|
target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
|
target.get(first).locate(loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
|
this.varLocation = varLocation;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.isDeclaration = isDecl;
|
this.isDeclaration = isDecl;
|
||||||
this.varName = varName;
|
this.varName = varName;
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
|
|||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
|
|
||||||
public class ForStatement extends Statement {
|
public class ForStatement extends Statement {
|
||||||
public final Statement declaration, assignment, condition, body;
|
public final Statement declaration, assignment, condition, body;
|
||||||
@@ -20,58 +18,22 @@ public class ForStatement extends Statement {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
declaration.compile(target, scope, false);
|
declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
if (condition instanceof ConstantStatement) {
|
|
||||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
|
||||||
int start = target.size();
|
|
||||||
body.compile(target, scope, false);
|
|
||||||
int mid = target.size();
|
|
||||||
assignment.compileWithDebug(target, scope, false);
|
|
||||||
int end = target.size();
|
|
||||||
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
|
|
||||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
condition.compile(target, scope, true);
|
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||||
int mid = target.size();
|
int mid = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
body.compile(target, scope, false);
|
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
int beforeAssign = target.size();
|
int beforeAssign = target.size();
|
||||||
assignment.compileWithDebug(target, scope, false);
|
assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
int end = target.size();
|
int end = target.size();
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
target.add(Instruction.jmp(loc(), start - end));
|
||||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
var decl = declaration.optimize();
|
|
||||||
var asgn = assignment.optimize();
|
|
||||||
var cond = condition.optimize();
|
|
||||||
var b = body.optimize();
|
|
||||||
|
|
||||||
if (asgn.pure()) {
|
|
||||||
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
|
|
||||||
else return new CompoundStatement(loc(),
|
|
||||||
decl, new WhileStatement(loc(), label, cond, b)
|
|
||||||
).optimize();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
|
|
||||||
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
|
|
||||||
);
|
|
||||||
else if (b instanceof BreakStatement) return decl;
|
|
||||||
|
|
||||||
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
|
|
||||||
else return new ForStatement(loc(), label, decl, cond, asgn, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
|
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
|
||||||
@@ -82,14 +44,4 @@ public class ForStatement extends Statement {
|
|||||||
this.assignment = assignment;
|
this.assignment = assignment;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
|
|
||||||
return new CompoundStatement(loc,
|
|
||||||
declaration,
|
|
||||||
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
|
|
||||||
body,
|
|
||||||
increment
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
|
|||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
|
|
||||||
public class IfStatement extends Statement {
|
public class IfStatement extends Statement {
|
||||||
public final Statement condition, body, elseBody;
|
public final Statement condition, body, elseBody;
|
||||||
@@ -19,53 +16,31 @@ public class IfStatement extends Statement {
|
|||||||
if (elseBody != null) elseBody.declare(globScope);
|
if (elseBody != null) elseBody.declare(globScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
condition.compile(target, scope, true, breakpoint);
|
||||||
if (condition instanceof ConstantStatement) {
|
|
||||||
if (Values.not(((ConstantStatement)condition).value)) {
|
|
||||||
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
body.compileWithDebug(target, scope, pollute);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
condition.compile(target, scope, true);
|
|
||||||
|
|
||||||
if (elseBody == null) {
|
if (elseBody == null) {
|
||||||
int i = target.size();
|
int i = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
body.compileWithDebug(target, scope, pollute);
|
body.compile(target, scope, pollute, breakpoint);
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
|
target.set(i, Instruction.jmpIfNot(loc(), endI - i));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
body.compileWithDebug(target, scope, pollute);
|
body.compile(target, scope, pollute, breakpoint);
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
int mid = target.size();
|
int mid = target.size();
|
||||||
elseBody.compileWithDebug(target, scope, pollute);
|
elseBody.compile(target, scope, pollute, breakpoint);
|
||||||
int end = target.size();
|
int end = target.size();
|
||||||
|
|
||||||
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
|
target.set(start, Instruction.jmpIfNot(loc(), mid - start));
|
||||||
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc()));
|
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
@Override
|
compile(target, scope, pollute, BreakpointType.STEP_IN);
|
||||||
public Statement optimize() {
|
|
||||||
var cond = condition.optimize();
|
|
||||||
var b = body.optimize();
|
|
||||||
var e = elseBody == null ? null : elseBody.optimize();
|
|
||||||
|
|
||||||
if (b.pure()) b = new CompoundStatement(null);
|
|
||||||
if (e != null && e.pure()) e = null;
|
|
||||||
|
|
||||||
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
|
|
||||||
else return new IfStatement(loc(), cond, b, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
|
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public class ReturnStatement extends Statement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
|
if (value == null) target.add(Instruction.loadValue(loc(), null));
|
||||||
else value.compile(target, scope, true);
|
else value.compile(target, scope, true);
|
||||||
target.add(Instruction.ret().locate(loc()));
|
target.add(Instruction.ret(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReturnStatement(Location loc, Statement value) {
|
public ReturnStatement(Location loc, Statement value) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
@@ -33,46 +34,47 @@ public class SwitchStatement extends Statement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
var caseMap = new HashMap<Integer, Integer>();
|
var caseToStatement = new HashMap<Integer, Integer>();
|
||||||
var stmIndexMap = new HashMap<Integer, Integer>();
|
var statementToIndex = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
value.compile(target, scope, true);
|
value.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
for (var ccase : cases) {
|
for (var ccase : cases) {
|
||||||
target.add(Instruction.dup().locate(loc()));
|
target.add(Instruction.dup(loc()));
|
||||||
ccase.value.compile(target, scope, true);
|
ccase.value.compile(target, scope, true);
|
||||||
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
|
target.add(Instruction.operation(loc(), Operation.EQUALS));
|
||||||
caseMap.put(target.size(), ccase.statementI);
|
caseToStatement.put(target.size(), ccase.statementI);
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
|
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
|
|
||||||
for (var stm : body) {
|
for (var stm : body) {
|
||||||
stmIndexMap.put(stmIndexMap.size(), target.size());
|
statementToIndex.put(statementToIndex.size(), target.size());
|
||||||
stm.compileWithDebug(target, scope, false);
|
stm.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
|
int end = target.size();
|
||||||
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc());
|
target.add(Instruction.discard(loc()));
|
||||||
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
|
|
||||||
for (int i = start; i < target.size(); i++) {
|
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
|
||||||
|
else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start));
|
||||||
|
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
var instr = target.get(i);
|
var instr = target.get(i);
|
||||||
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
|
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
|
||||||
target.set(i, Instruction.jmp(target.size() - i).locate(instr.location));
|
target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var el : caseMap.entrySet()) {
|
for (var el : caseToStatement.entrySet()) {
|
||||||
var loc = target.get(el.getKey()).location;
|
var i = statementToIndex.get(el.getValue());
|
||||||
var i = stmIndexMap.get(el.getValue());
|
if (i == null) i = end;
|
||||||
if (i == null) i = target.size();
|
target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
|
||||||
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc));
|
|
||||||
target.setDebug(el.getKey());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target.add(Instruction.discard().locate(loc()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
|
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class ThrowStatement extends Statement {
|
|||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
value.compile(target, scope, true);
|
value.compile(target, scope, true);
|
||||||
target.add(Instruction.throwInstr().locate(loc()));
|
target.add(Instruction.throwInstr(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ThrowStatement(Location loc, Statement value) {
|
public ThrowStatement(Location loc, Statement value) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||||
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
@@ -22,31 +23,32 @@ public class TryStatement extends Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
|
|
||||||
int start = target.size(), tryN, catchN = -1, finN = -1;
|
int start = target.size(), catchStart = -1, finallyStart = -1;
|
||||||
|
|
||||||
tryBody.compile(target, scope, false);
|
tryBody.compile(target, scope, false);
|
||||||
tryN = target.size() - start;
|
target.add(Instruction.tryEnd(loc()));
|
||||||
|
|
||||||
if (catchBody != null) {
|
if (catchBody != null) {
|
||||||
int tmp = target.size();
|
catchStart = target.size() - start;
|
||||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||||
local.define(name, true);
|
local.define(name, true);
|
||||||
catchBody.compile(target, scope, false);
|
catchBody.compile(target, scope, false);
|
||||||
local.undefine();
|
local.undefine();
|
||||||
catchN = target.size() - tmp;
|
target.add(Instruction.tryEnd(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finallyBody != null) {
|
if (finallyBody != null) {
|
||||||
int tmp = target.size();
|
finallyStart = target.size() - start;
|
||||||
finallyBody.compile(target, scope, false);
|
finallyBody.compile(target, scope, false);
|
||||||
finN = target.size() - tmp;
|
target.add(Instruction.tryEnd(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
|
target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
target.setDebug(start - 1, BreakpointType.STEP_OVER);
|
||||||
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||||
|
|||||||
@@ -3,13 +3,10 @@ package me.topchetoeu.jscript.compilation.control;
|
|||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
|
|
||||||
public class WhileStatement extends Statement {
|
public class WhileStatement extends Statement {
|
||||||
public final Statement condition, body;
|
public final Statement condition, body;
|
||||||
@@ -21,43 +18,19 @@ public class WhileStatement extends Statement {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (condition instanceof ConstantStatement) {
|
|
||||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
|
||||||
int start = target.size();
|
|
||||||
body.compile(target, scope, false);
|
|
||||||
int end = target.size();
|
|
||||||
replaceBreaks(target, label, start, end, start, end + 1);
|
|
||||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
condition.compile(target, scope, true);
|
condition.compile(target, scope, true);
|
||||||
int mid = target.size();
|
int mid = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
body.compile(target, scope, false);
|
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
int end = target.size();
|
int end = target.size();
|
||||||
|
|
||||||
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
target.add(Instruction.jmp(loc(), start - end));
|
||||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
var cond = condition.optimize();
|
|
||||||
var b = body.optimize();
|
|
||||||
|
|
||||||
if (b instanceof ContinueStatement) {
|
|
||||||
b = new CompoundStatement(loc());
|
|
||||||
}
|
|
||||||
else if (b instanceof BreakStatement) return new DiscardStatement(loc(), cond).optimize();
|
|
||||||
|
|
||||||
if (b.pure()) return new WhileStatement(loc(), label, cond, new CompoundStatement(null));
|
|
||||||
else return new WhileStatement(loc(), label, cond, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
|
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||||
@@ -71,23 +44,11 @@ public class WhileStatement extends Statement {
|
|||||||
for (int i = start; i < end; i++) {
|
for (int i = start; i < end; i++) {
|
||||||
var instr = target.get(i);
|
var instr = target.get(i);
|
||||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||||
target.set(i, Instruction.jmp(continuePoint - i));
|
target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
|
||||||
target.get(i).location = instr.location;
|
|
||||||
}
|
}
|
||||||
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
||||||
target.set(i, Instruction.jmp(breakPoint - i));
|
target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
|
||||||
target.get(i).location = instr.location;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
|
|
||||||
// return new CompoundStatement(loc,
|
|
||||||
// declaration,
|
|
||||||
// new WhileStatement(loc, label, condition, new CompoundStatement(loc,
|
|
||||||
// body,
|
|
||||||
// increment
|
|
||||||
// ))
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -4,36 +4,47 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
public class CallStatement extends Statement {
|
public class CallStatement extends Statement {
|
||||||
public final Statement func;
|
public final Statement func;
|
||||||
public final Statement[] args;
|
public final Statement[] args;
|
||||||
|
public final boolean isNew;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||||
if (func instanceof IndexStatement) {
|
if (isNew) func.compile(target, scope, true);
|
||||||
|
else if (func instanceof IndexStatement) {
|
||||||
((IndexStatement)func).compile(target, scope, true, true);
|
((IndexStatement)func).compile(target, scope, true, true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
target.add(Instruction.loadValue(null).locate(loc()));
|
target.add(Instruction.loadValue(loc(), null));
|
||||||
func.compile(target, scope, true);
|
func.compile(target, scope, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var arg : args) arg.compile(target, scope, true);
|
for (var arg : args) arg.compile(target, scope, true);
|
||||||
|
|
||||||
target.add(Instruction.call(args.length).locate(loc()));
|
if (isNew) target.add(Instruction.callNew(loc(), args.length));
|
||||||
target.setDebug();
|
else target.add(Instruction.call(loc(), args.length));
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
target.setDebug(type);
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard(loc()));
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
|
compile(target, scope, pollute, BreakpointType.STEP_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallStatement(Location loc, Statement func, Statement ...args) {
|
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
|
||||||
super(loc);
|
super(loc);
|
||||||
|
this.isNew = isNew;
|
||||||
this.func = func;
|
this.func = func;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
public CallStatement(Location loc, Statement obj, Object key, Statement ...args) {
|
public CallStatement(Location loc, boolean isNew, Statement obj, Object key, Statement ...args) {
|
||||||
super(loc);
|
super(loc);
|
||||||
|
this.isNew = isNew;
|
||||||
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
|
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ public class ChangeStatement extends Statement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix);
|
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
if (!pollute) target.add(Instruction.discard(loc()));
|
||||||
|
else if (postfix) {
|
||||||
|
target.add(Instruction.loadValue(loc(), addAmount));
|
||||||
|
target.add(Instruction.operation(loc(), Operation.SUBTRACT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|||||||
public class ConstantStatement extends Statement {
|
public class ConstantStatement extends Statement {
|
||||||
public final Object value;
|
public final Object value;
|
||||||
|
|
||||||
@Override
|
@Override public boolean pure() { return true; }
|
||||||
public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.loadValue(value).locate(loc()));
|
if (pollute) target.add(Instruction.loadValue(loc(), value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConstantStatement(Location loc, Object val) {
|
public ConstantStatement(Location loc, Object val) {
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
public class DiscardStatement extends Statement {
|
public class DiscardStatement extends Statement {
|
||||||
public final Statement value;
|
public final Statement value;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return value.pure(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
value.compile(target, scope, false);
|
value.compile(target, scope, false);
|
||||||
|
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
if (value == null) return this;
|
|
||||||
var val = value.optimize();
|
|
||||||
if (val.pure()) return new ConstantStatement(loc(), null);
|
|
||||||
else return new DiscardStatement(loc(), val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DiscardStatement(Location loc, Statement val) {
|
public DiscardStatement(Location loc, Statement val) {
|
||||||
@@ -8,23 +8,25 @@ 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.BreakpointType;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class FunctionStatement extends Statement {
|
public class FunctionStatement extends Statement {
|
||||||
public final CompoundStatement body;
|
public final CompoundStatement body;
|
||||||
public final String name;
|
public final String varName;
|
||||||
public final String[] args;
|
public final String[] args;
|
||||||
|
public final boolean statement;
|
||||||
|
public final Location end;
|
||||||
|
|
||||||
private static Random rand = new Random();
|
private static Random rand = new Random();
|
||||||
|
|
||||||
@Override
|
@Override public boolean pure() { return varName == null && statement; }
|
||||||
public boolean pure() { return name == null; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void declare(ScopeRecord scope) {
|
public void declare(ScopeRecord scope) {
|
||||||
if (name != null) scope.define(name);
|
if (varName != null && statement) scope.define(varName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkBreakAndCont(CompileTarget target, int start) {
|
public static void checkBreakAndCont(CompileTarget target, int start) {
|
||||||
@@ -40,73 +42,98 @@ public class FunctionStatement extends Statement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) {
|
private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) {
|
||||||
for (var i = 0; i < args.length; i++) {
|
for (var i = 0; i < args.length; i++) {
|
||||||
for (var j = 0; j < i; j++) {
|
for (var j = 0; j < i; j++) {
|
||||||
if (args[i].equals(args[j])){
|
if (args[i].equals(args[j])) {
|
||||||
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.")));
|
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var subscope = scope.child();
|
|
||||||
|
|
||||||
int start = target.size();
|
var id = rand.nextLong();
|
||||||
var funcTarget = new CompileTarget(target.functions, target.breakpoints);
|
var subscope = scope.child();
|
||||||
|
var subtarget = new CompileTarget(target.functions, target.breakpoints);
|
||||||
|
|
||||||
subscope.define("this");
|
subscope.define("this");
|
||||||
var argsVar = subscope.define("arguments");
|
var argsVar = subscope.define("arguments");
|
||||||
|
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
for (var i = 0; i < args.length; i++) {
|
for (var i = 0; i < args.length; i++) {
|
||||||
funcTarget.add(Instruction.loadVar(argsVar).locate(loc()));
|
subtarget.add(Instruction.loadVar(loc(), argsVar));
|
||||||
funcTarget.add(Instruction.loadMember(i).locate(loc()));
|
subtarget.add(Instruction.loadMember(loc(), i));
|
||||||
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isStatement && this.name != null) {
|
if (!statement && this.varName != null) {
|
||||||
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
|
||||||
|
subtarget.setDebug(bp);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.declare(subscope);
|
body.declare(subscope);
|
||||||
body.compile(funcTarget, subscope, false);
|
body.compile(subtarget, subscope, false);
|
||||||
funcTarget.add(Instruction.ret().locate(loc()));
|
subtarget.add(Instruction.ret(end));
|
||||||
checkBreakAndCont(funcTarget, start);
|
checkBreakAndCont(subtarget, 0);
|
||||||
|
|
||||||
var id = rand.nextLong();
|
if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures()));
|
||||||
|
target.functions.put(id, new FunctionBody(
|
||||||
|
subscope.localsCount(), args.length,
|
||||||
|
subtarget.array(), subscope.captures(), subscope.locals()
|
||||||
|
));
|
||||||
|
|
||||||
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
return id;
|
||||||
target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
|
|
||||||
|
|
||||||
if (name == null) name = this.name;
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
target.add(Instruction.dup().locate(loc()));
|
|
||||||
target.add(Instruction.loadValue("name").locate(loc()));
|
|
||||||
target.add(Instruction.loadValue(name).locate(loc()));
|
|
||||||
target.add(Instruction.storeMember().locate(loc()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.name != null && isStatement) {
|
|
||||||
var key = scope.getKey(this.name);
|
|
||||||
|
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
|
||||||
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
|
||||||
compile(target, scope, null, false);
|
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
if (this.varName != null) name = this.varName;
|
||||||
|
|
||||||
|
var hasVar = this.varName != null && statement;
|
||||||
|
var hasName = name != null;
|
||||||
|
|
||||||
|
compileBody(target, scope, pollute || hasVar || hasName, bp);
|
||||||
|
|
||||||
|
if (hasName) {
|
||||||
|
if (pollute || hasVar) target.add(Instruction.dup(loc()));
|
||||||
|
target.add(Instruction.loadValue(loc(), "name"));
|
||||||
|
target.add(Instruction.loadValue(loc(), name));
|
||||||
|
target.add(Instruction.storeMember(loc()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasVar) {
|
||||||
|
var key = scope.getKey(this.varName);
|
||||||
|
|
||||||
|
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
|
||||||
|
target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
|
||||||
|
compile(target, scope, pollute, name, BreakpointType.NONE);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) {
|
||||||
|
compile(target, scope, pollute, (String)null, bp);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
|
compile(target, scope, pollute, (String)null, BreakpointType.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.name = name;
|
|
||||||
|
this.end = end;
|
||||||
|
this.varName = varName;
|
||||||
|
this.statement = statement;
|
||||||
|
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
|
||||||
|
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
|
||||||
|
else stm.compile(target, scope, pollute);
|
||||||
|
}
|
||||||
|
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
|
||||||
|
else stm.compile(target, scope, pollute, bp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
|||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
public class GlobalThisStatement extends Statement {
|
public class GlobalThisStatement extends Statement {
|
||||||
@Override
|
@Override public boolean pure() { return true; }
|
||||||
public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.loadGlob().locate(loc()));
|
if (pollute) target.add(Instruction.loadGlob(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GlobalThisStatement(Location loc) {
|
public GlobalThisStatement(Location loc) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
@@ -18,22 +19,22 @@ public class IndexAssignStatement extends Statement {
|
|||||||
if (operation != null) {
|
if (operation != null) {
|
||||||
object.compile(target, scope, true);
|
object.compile(target, scope, true);
|
||||||
index.compile(target, scope, true);
|
index.compile(target, scope, true);
|
||||||
target.add(Instruction.dup(2, 0).locate(loc()));
|
target.add(Instruction.dup(loc(), 2));
|
||||||
|
|
||||||
target.add(Instruction.loadMember().locate(loc()));
|
target.add(Instruction.loadMember(loc()));
|
||||||
value.compile(target, scope, true);
|
value.compile(target, scope, true);
|
||||||
target.add(Instruction.operation(operation).locate(loc()));
|
target.add(Instruction.operation(loc(), operation));
|
||||||
|
|
||||||
target.add(Instruction.storeMember(pollute).locate(loc()));
|
target.add(Instruction.storeMember(loc(), pollute));
|
||||||
target.setDebug();
|
target.setDebug(BreakpointType.STEP_IN);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
object.compile(target, scope, true);
|
object.compile(target, scope, true);
|
||||||
index.compile(target, scope, true);
|
index.compile(target, scope, true);
|
||||||
value.compile(target, scope, true);
|
value.compile(target, scope, true);
|
||||||
|
|
||||||
target.add(Instruction.storeMember(pollute).locate(loc()));
|
target.add(Instruction.storeMember(loc(), pollute));
|
||||||
target.setDebug();
|
target.setDebug(BreakpointType.STEP_IN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import me.topchetoeu.jscript.compilation.AssignableStatement;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
|
|
||||||
@@ -12,26 +13,23 @@ public class IndexStatement extends AssignableStatement {
|
|||||||
public final Statement object;
|
public final Statement object;
|
||||||
public final Statement index;
|
public final Statement index;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Statement toAssign(Statement val, Operation operation) {
|
public Statement toAssign(Statement val, Operation operation) {
|
||||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||||
}
|
}
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||||
object.compile(target, scope, true);
|
object.compile(target, scope, true);
|
||||||
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
if (dupObj) target.add(Instruction.dup(loc()));
|
||||||
if (index instanceof ConstantStatement) {
|
if (index instanceof ConstantStatement) {
|
||||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
|
||||||
target.setDebug();
|
target.setDebug(BreakpointType.STEP_IN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
index.compile(target, scope, true);
|
index.compile(target, scope, true);
|
||||||
target.add(Instruction.loadMember().locate(loc()));
|
target.add(Instruction.loadMember(loc()));
|
||||||
target.setDebug();
|
target.setDebug(BreakpointType.STEP_IN);
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
if (!pollute) target.add(Instruction.discard(loc()));
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
|
|||||||
public class LazyAndStatement extends Statement {
|
public class LazyAndStatement extends Statement {
|
||||||
public final Statement first, second;
|
public final Statement first, second;
|
||||||
|
|
||||||
@Override
|
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||||
public boolean pure() {
|
|
||||||
return first.pure() && second.pure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
@@ -26,12 +23,12 @@ public class LazyAndStatement extends Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
first.compile(target, scope, true);
|
first.compile(target, scope, true);
|
||||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
if (pollute) target.add(Instruction.dup(loc()));
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
target.add(Instruction.discard().locate(loc()));
|
if (pollute) target.add(Instruction.discard(loc()));
|
||||||
second.compile(target, scope, pollute);
|
second.compile(target, scope, pollute);
|
||||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
|
|||||||
public class LazyOrStatement extends Statement {
|
public class LazyOrStatement extends Statement {
|
||||||
public final Statement first, second;
|
public final Statement first, second;
|
||||||
|
|
||||||
@Override
|
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||||
public boolean pure() {
|
|
||||||
return first.pure() && second.pure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
@@ -26,12 +23,12 @@ public class LazyOrStatement extends Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
first.compile(target, scope, true);
|
first.compile(target, scope, true);
|
||||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
if (pollute) target.add(Instruction.dup(loc()));
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
target.add(Instruction.nop());
|
target.add(Instruction.nop(null));
|
||||||
target.add(Instruction.discard().locate(loc()));
|
if (pollute) target.add(Instruction.discard(loc()));
|
||||||
second.compile(target, scope, pollute);
|
second.compile(target, scope, pollute);
|
||||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
target.set(start, Instruction.jmpIf(loc(), target.size() - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|
||||||
|
|
||||||
public class NewStatement extends Statement {
|
|
||||||
public final Statement func;
|
|
||||||
public final Statement[] args;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
|
||||||
func.compile(target, scope, true);
|
|
||||||
|
|
||||||
for (var arg : args) arg.compile(target, scope, true);
|
|
||||||
|
|
||||||
target.add(Instruction.callNew(args.length).locate(loc()));
|
|
||||||
target.setDebug();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NewStatement(Location loc, Statement func, Statement ...args) {
|
|
||||||
super(loc);
|
|
||||||
this.func = func;
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,24 @@ public class ObjectStatement extends Statement {
|
|||||||
public final Map<Object, FunctionStatement> getters;
|
public final Map<Object, FunctionStatement> getters;
|
||||||
public final Map<Object, FunctionStatement> setters;
|
public final Map<Object, FunctionStatement> setters;
|
||||||
|
|
||||||
|
@Override public boolean pure() {
|
||||||
|
for (var el : map.values()) {
|
||||||
|
if (!el.pure()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
target.add(Instruction.loadObj().locate(loc()));
|
target.add(Instruction.loadObj(loc()));
|
||||||
|
|
||||||
for (var el : map.entrySet()) {
|
for (var el : map.entrySet()) {
|
||||||
target.add(Instruction.dup().locate(loc()));
|
target.add(Instruction.dup(loc()));
|
||||||
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
|
target.add(Instruction.loadValue(loc(), el.getKey()));
|
||||||
var val = el.getValue();
|
var val = el.getValue();
|
||||||
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
|
FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
|
||||||
else val.compile(target, scope, true);
|
target.add(Instruction.storeMember(loc()));
|
||||||
target.add(Instruction.storeMember().locate(loc()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var keys = new ArrayList<Object>();
|
var keys = new ArrayList<Object>();
|
||||||
@@ -32,19 +39,19 @@ public class ObjectStatement extends Statement {
|
|||||||
keys.addAll(setters.keySet());
|
keys.addAll(setters.keySet());
|
||||||
|
|
||||||
for (var key : keys) {
|
for (var key : keys) {
|
||||||
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
|
if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
|
||||||
else target.add(Instruction.loadValue((Double)key).locate(loc()));
|
else target.add(Instruction.loadValue(loc(), (Double)key));
|
||||||
|
|
||||||
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
|
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
|
||||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
else target.add(Instruction.loadValue(loc(), null));
|
||||||
|
|
||||||
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
|
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
|
||||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
else target.add(Instruction.loadValue(loc(), null));
|
||||||
|
|
||||||
target.add(Instruction.defProp().locate(loc()));
|
target.add(Instruction.defProp(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
if (!pollute) target.add(Instruction.discard(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
||||||
|
|||||||
@@ -4,57 +4,29 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
|
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
|
||||||
|
|
||||||
public class OperationStatement extends Statement {
|
public class OperationStatement extends Statement {
|
||||||
public final Statement[] args;
|
public final Statement[] args;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
|
|
||||||
|
@Override public boolean pure() {
|
||||||
|
for (var el : args) {
|
||||||
|
if (!el.pure()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
for (var arg : args) {
|
for (var arg : args) {
|
||||||
arg.compile(target, scope, true);
|
arg.compile(target, scope, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.operation(operation).locate(loc()));
|
if (pollute) target.add(Instruction.operation(loc(), operation));
|
||||||
else target.add(Instruction.discard().locate(loc()));
|
else target.add(Instruction.discard(loc()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean pure() {
|
|
||||||
for (var arg : args) {
|
|
||||||
if (!arg.pure()) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
var args = new Statement[this.args.length];
|
|
||||||
var allConst = true;
|
|
||||||
|
|
||||||
for (var i = 0; i < this.args.length; i++) {
|
|
||||||
args[i] = this.args[i].optimize();
|
|
||||||
if (!(args[i] instanceof ConstantStatement)) allConst = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allConst) {
|
|
||||||
var vals = new Object[this.args.length];
|
|
||||||
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
vals[i] = ((ConstantStatement)args[i]).value;
|
|
||||||
}
|
|
||||||
|
|
||||||
try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
|
|
||||||
catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OperationStatement(loc(), operation, args);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OperationStatement(Location loc, Operation operation, Statement ...args) {
|
public OperationStatement(Location loc, Operation operation, Statement ...args) {
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|||||||
public class RegexStatement extends Statement {
|
public 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) {
|
||||||
|
|||||||
@@ -4,44 +4,26 @@ import me.topchetoeu.jscript.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.compilation.control.ArrayStatement;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
|
|
||||||
public class TypeofStatement extends Statement {
|
public class TypeofStatement extends Statement {
|
||||||
public final Statement value;
|
public final Statement value;
|
||||||
|
|
||||||
@Override
|
// Not really pure, since a variable from the global scope could be accessed,
|
||||||
public boolean pure() { return true; }
|
// which could lead to code execution, that would get omitted
|
||||||
|
@Override public boolean pure() { return true; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (value instanceof VariableStatement) {
|
if (value instanceof VariableStatement) {
|
||||||
var i = scope.getKey(((VariableStatement)value).name);
|
var i = scope.getKey(((VariableStatement)value).name);
|
||||||
if (i instanceof String) {
|
if (i instanceof String) {
|
||||||
target.add(Instruction.typeof((String)i).locate(loc()));
|
target.add(Instruction.typeof(loc(), (String)i));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value.compile(target, scope, pollute);
|
value.compile(target, scope, pollute);
|
||||||
target.add(Instruction.typeof().locate(loc()));
|
target.add(Instruction.typeof(loc()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
var val = value.optimize();
|
|
||||||
|
|
||||||
if (val instanceof ConstantStatement) {
|
|
||||||
return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value));
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
val instanceof ObjectStatement ||
|
|
||||||
val instanceof ArrayStatement ||
|
|
||||||
val instanceof GlobalThisStatement
|
|
||||||
) return new ConstantStatement(loc(), "object");
|
|
||||||
else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function");
|
|
||||||
|
|
||||||
return new TypeofStatement(loc(), val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeofStatement(Location loc, Statement value) {
|
public TypeofStatement(Location loc, Statement value) {
|
||||||
|
|||||||
@@ -12,23 +12,21 @@ public class VariableAssignStatement extends Statement {
|
|||||||
public final Statement value;
|
public final Statement value;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return false; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
var i = scope.getKey(name);
|
var i = scope.getKey(name);
|
||||||
if (operation != null) {
|
if (operation != null) {
|
||||||
target.add(Instruction.loadVar(i).locate(loc()));
|
target.add(Instruction.loadVar(loc(), i));
|
||||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
FunctionStatement.compileWithName(value, target, scope, true, name);
|
||||||
else value.compile(target, scope, true);
|
target.add(Instruction.operation(loc(), operation));
|
||||||
target.add(Instruction.operation(operation).locate(loc()));
|
target.add(Instruction.storeVar(loc(), i, pollute));
|
||||||
target.add(Instruction.storeVar(i, false).locate(loc()));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
FunctionStatement.compileWithName(value, target, scope, true, name);
|
||||||
else value.compile(target, scope, true);
|
target.add(Instruction.storeVar(loc(), i, pollute));
|
||||||
target.add(Instruction.storeVar(i, false).locate(loc()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
|
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|||||||
public class VariableIndexStatement extends Statement {
|
public class VariableIndexStatement extends Statement {
|
||||||
public final int index;
|
public final int index;
|
||||||
|
|
||||||
@Override
|
@Override public boolean pure() { return true; }
|
||||||
public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.loadVar(index).locate(loc()));
|
if (pollute) target.add(Instruction.loadVar(loc(), index));
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableIndexStatement(Location loc, int i) {
|
public VariableIndexStatement(Location loc, int i) {
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|||||||
public class VariableStatement extends AssignableStatement {
|
public class VariableStatement extends AssignableStatement {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@Override
|
@Override public boolean pure() { return false; }
|
||||||
public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Statement toAssign(Statement val, Operation operation) {
|
public Statement toAssign(Statement val, Operation operation) {
|
||||||
@@ -22,8 +21,8 @@ public class VariableStatement extends AssignableStatement {
|
|||||||
@Override
|
@Override
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||||
var i = scope.getKey(name);
|
var i = scope.getKey(name);
|
||||||
target.add(Instruction.loadVar(i).locate(loc()));
|
target.add(Instruction.loadVar(loc(), i));
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
if (!pollute) target.add(Instruction.discard(loc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableStatement(Location loc, String name) {
|
public VariableStatement(Location loc, String name) {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
|
||||||
|
|
||||||
public class VoidStatement extends Statement {
|
|
||||||
public final Statement value;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
|
||||||
if (value != null) value.compile(target, scope, false);
|
|
||||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Statement optimize() {
|
|
||||||
if (value == null) return this;
|
|
||||||
var val = value.optimize();
|
|
||||||
if (val.pure()) return new ConstantStatement(loc(), null);
|
|
||||||
else return new VoidStatement(loc(), val);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VoidStatement(Location loc, Statement value) {
|
|
||||||
super(loc);
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,25 @@
|
|||||||
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.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() {
|
||||||
@@ -27,20 +35,91 @@ public class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FunctionValue compile(Filename filename, String raw) {
|
public FunctionValue compile(Filename filename, String raw) {
|
||||||
var src = Values.toString(this, environment().compile.call(this, null, raw, filename));
|
var env = environment();
|
||||||
var debugger = StackData.getDebugger(this);
|
var result = env.compile.call(this, null, raw, filename.toString(), env);
|
||||||
var breakpoints = new TreeSet<Location>();
|
|
||||||
var res = Parsing.compile(engine.functions, breakpoints, environment(), filename, src);
|
var function = (FunctionValue)Values.getMember(this, result, "function");
|
||||||
if (debugger != null) debugger.onSource(filename, src, breakpoints);
|
if (!engine.debugging) return function;
|
||||||
|
|
||||||
|
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
|
||||||
|
var breakpoints = new TreeSet<>(
|
||||||
|
Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
|
||||||
|
.map(v -> Location.parse(Values.toString(this, v)))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
var maps = new SourceMap[rawMapChain.length];
|
||||||
|
|
||||||
|
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
|
||||||
|
|
||||||
|
var map = SourceMap.chain(maps);
|
||||||
|
|
||||||
|
if (map != null) {
|
||||||
|
var newBreakpoints = new TreeSet<Location>();
|
||||||
|
for (var bp : breakpoints) {
|
||||||
|
bp = map.toCompiled(bp);
|
||||||
|
if (bp != null) newBreakpoints.add(bp);
|
||||||
|
}
|
||||||
|
breakpoints = newBreakpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.onSource(filename, raw, breakpoints, map);
|
||||||
|
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void pushFrame(CodeFrame frame) {
|
||||||
|
frames.add(frame);
|
||||||
|
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
|
||||||
|
pushEnv(frame.function.environment);
|
||||||
|
engine.onFramePush(this, frame);
|
||||||
|
}
|
||||||
|
public boolean popFrame(CodeFrame frame) {
|
||||||
|
if (frames.size() == 0) return false;
|
||||||
|
if (frames.get(frames.size() - 1) != frame) return false;
|
||||||
|
frames.remove(frames.size() - 1);
|
||||||
|
popEnv();
|
||||||
|
engine.onFramePop(this, frame);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public CodeFrame peekFrame() {
|
||||||
|
if (frames.size() == 0) return null;
|
||||||
|
return frames.get(frames.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CodeFrame> frames() {
|
||||||
|
return Collections.unmodifiableList(frames);
|
||||||
|
}
|
||||||
|
public List<String> stackTrace() {
|
||||||
|
var res = new ArrayList<String>();
|
||||||
|
|
||||||
|
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||||
|
var el = frames.get(i);
|
||||||
|
var name = el.function.name;
|
||||||
|
Location loc = null;
|
||||||
|
|
||||||
|
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
|
||||||
|
if (loc == null) loc = el.function.loc();
|
||||||
|
|
||||||
|
var trace = "";
|
||||||
|
|
||||||
|
if (loc != null) trace += "at " + loc.toString() + " ";
|
||||||
|
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
||||||
|
|
||||||
|
trace = trace.trim();
|
||||||
|
|
||||||
|
if (!trace.equals("")) res.add(trace);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
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);
|
||||||
|
this.pushEnv(env);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
package me.topchetoeu.jscript.engine;
|
package me.topchetoeu.jscript.engine;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
import me.topchetoeu.jscript.Filename;
|
||||||
|
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.engine.debug.DebugController;
|
||||||
|
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||||
import me.topchetoeu.jscript.events.Awaitable;
|
import me.topchetoeu.jscript.events.Awaitable;
|
||||||
import me.topchetoeu.jscript.events.DataNotifier;
|
import me.topchetoeu.jscript.events.DataNotifier;
|
||||||
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||||
|
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||||
|
|
||||||
public class Engine {
|
public class Engine implements DebugController {
|
||||||
private class UncompiledFunction extends FunctionValue {
|
private class UncompiledFunction extends FunctionValue {
|
||||||
public final Filename filename;
|
public final Filename filename;
|
||||||
public final String raw;
|
public final String raw;
|
||||||
@@ -29,30 +36,71 @@ public class Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Task {
|
private static class Task implements Comparable<Task> {
|
||||||
public final FunctionValue func;
|
public final FunctionValue func;
|
||||||
public final Object thisArg;
|
public final Object thisArg;
|
||||||
public final Object[] args;
|
public final Object[] args;
|
||||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||||
public final Context ctx;
|
public final Context ctx;
|
||||||
|
public final boolean micro;
|
||||||
|
|
||||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) {
|
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.func = func;
|
this.func = func;
|
||||||
this.thisArg = thisArg;
|
this.thisArg = thisArg;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
|
this.micro = micro;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Task other) {
|
||||||
|
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int nextId = 0;
|
private static int nextId = 0;
|
||||||
|
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||||
private Thread thread;
|
|
||||||
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
|
|
||||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
|
||||||
|
|
||||||
public final int id = ++nextId;
|
public final int id = ++nextId;
|
||||||
public final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
public final boolean debugging;
|
||||||
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000);
|
public int maxStackFrames = 10000;
|
||||||
|
|
||||||
|
private final HashMap<Filename, String> sources = new HashMap<>();
|
||||||
|
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
|
||||||
|
private final HashMap<Filename, SourceMap> maps = new HashMap<>();
|
||||||
|
|
||||||
|
public Location mapToCompiled(Location location) {
|
||||||
|
var map = maps.get(location.filename());
|
||||||
|
if (map == null) return location;
|
||||||
|
return map.toCompiled(location);
|
||||||
|
}
|
||||||
|
public Location mapToOriginal(Location location) {
|
||||||
|
var map = maps.get(location.filename());
|
||||||
|
if (map == null) return location;
|
||||||
|
return map.toOriginal(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DebugController debugger;
|
||||||
|
private Thread thread;
|
||||||
|
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
|
||||||
|
|
||||||
|
public synchronized boolean attachDebugger(DebugController debugger) {
|
||||||
|
if (!debugging || this.debugger != null) return false;
|
||||||
|
|
||||||
|
for (var source : sources.entrySet()) debugger.onSource(
|
||||||
|
source.getKey(), source.getValue(),
|
||||||
|
bpts.get(source.getKey()),
|
||||||
|
maps.get(source.getKey())
|
||||||
|
);
|
||||||
|
|
||||||
|
this.debugger = debugger;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public synchronized boolean detachDebugger() {
|
||||||
|
if (!debugging || this.debugger == null) return false;
|
||||||
|
this.debugger = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void runTask(Task task) {
|
private void runTask(Task task) {
|
||||||
try {
|
try {
|
||||||
@@ -64,18 +112,12 @@ public class Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void run(boolean untilEmpty) {
|
public void run(boolean untilEmpty) {
|
||||||
while (!untilEmpty || !macroTasks.isEmpty()) {
|
while (!untilEmpty || !tasks.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
runTask(macroTasks.take());
|
runTask(tasks.take());
|
||||||
|
|
||||||
while (!microTasks.isEmpty()) {
|
|
||||||
runTask(microTasks.take());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (InterruptedException | InterruptException e) {
|
catch (InterruptedException | InterruptException e) {
|
||||||
for (var msg : macroTasks) {
|
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
|
||||||
msg.notifier.error(new InterruptException(e));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,17 +137,39 @@ public class Engine {
|
|||||||
public boolean inExecThread() {
|
public boolean inExecThread() {
|
||||||
return Thread.currentThread() == thread;
|
return Thread.currentThread() == thread;
|
||||||
}
|
}
|
||||||
public boolean isRunning() {
|
public synchronized boolean isRunning() {
|
||||||
return this.thread != null;
|
return this.thread != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
|
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);
|
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro);
|
||||||
if (micro) microTasks.addLast(msg);
|
tasks.add(msg);
|
||||||
else macroTasks.addLast(msg);
|
|
||||||
return msg.notifier;
|
return msg.notifier;
|
||||||
}
|
}
|
||||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
|
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);
|
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFramePush(Context ctx, CodeFrame frame) {
|
||||||
|
if (debugging && debugger != null) debugger.onFramePush(ctx, frame);
|
||||||
|
}
|
||||||
|
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||||
|
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
|
||||||
|
}
|
||||||
|
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||||
|
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
|
||||||
|
if (!debugging) return;
|
||||||
|
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
|
||||||
|
sources.put(filename, source);
|
||||||
|
bpts.put(filename, breakpoints);
|
||||||
|
maps.put(filename, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine(boolean debugging) {
|
||||||
|
this.debugging = debugging;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,65 @@
|
|||||||
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.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.permissions.Permission;
|
||||||
|
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||||
|
|
||||||
public class Environment {
|
public class Environment implements PermissionsProvider {
|
||||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||||
|
|
||||||
public final Data data = new Data();
|
public final Data data = new Data();
|
||||||
public final HashMap<String, Symbol> symbols = new HashMap<>();
|
public static final HashMap<String, Symbol> symbols = new HashMap<>();
|
||||||
|
|
||||||
public GlobalScope global;
|
public GlobalScope global;
|
||||||
public WrappersProvider wrappers;
|
public WrappersProvider wrappers;
|
||||||
|
public PermissionsProvider permissions = null;
|
||||||
|
public final RootFilesystem filesystem = new RootFilesystem(this);
|
||||||
|
|
||||||
@Native public FunctionValue compile;
|
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);
|
||||||
});
|
});
|
||||||
@@ -40,13 +77,7 @@ public class Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Native public Symbol symbol(String name) {
|
@Native public Symbol symbol(String name) {
|
||||||
if (symbols.containsKey(name))
|
return getSymbol(name);
|
||||||
return symbols.get(name);
|
|
||||||
else {
|
|
||||||
var res = new Symbol(name);
|
|
||||||
symbols.put(name, res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NativeGetter("global") public ObjectValue getGlobal() {
|
@NativeGetter("global") public ObjectValue getGlobal() {
|
||||||
@@ -57,7 +88,8 @@ public class Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Native public Environment fork() {
|
@Native public Environment fork() {
|
||||||
var res = new Environment(compile, wrappers, global);
|
var res = new Environment(compile, null, global);
|
||||||
|
res.wrappers = wrappers.fork(res);
|
||||||
res.regexConstructor = regexConstructor;
|
res.regexConstructor = regexConstructor;
|
||||||
res.prototypes = new HashMap<>(prototypes);
|
res.prototypes = new HashMap<>(prototypes);
|
||||||
return res;
|
return res;
|
||||||
@@ -68,20 +100,32 @@ public class Environment {
|
|||||||
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).pushEnv(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.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();
|
|
||||||
var dbg = getDebugger(ctx);
|
|
||||||
if (dbg != null) dbg.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;
|
|
||||||
var loc = el.function.loc();
|
|
||||||
var trace = "";
|
|
||||||
|
|
||||||
if (loc != null) trace += "at " + loc.toString() + " ";
|
|
||||||
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
|
||||||
|
|
||||||
trace = trace.trim();
|
|
||||||
|
|
||||||
if (!trace.equals("")) res.add(trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Debugger getDebugger(Context ctx) {
|
|
||||||
return ctx.data.get(DEBUGGER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,4 +7,6 @@ public interface WrappersProvider {
|
|||||||
public ObjectValue getProto(Class<?> obj);
|
public ObjectValue getProto(Class<?> obj);
|
||||||
public ObjectValue getNamespace(Class<?> obj);
|
public ObjectValue getNamespace(Class<?> obj);
|
||||||
public FunctionValue getConstr(Class<?> obj);
|
public FunctionValue getConstr(Class<?> obj);
|
||||||
|
|
||||||
|
public WrappersProvider fork(Environment env);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -37,7 +48,4 @@ public interface DebugController {
|
|||||||
* @param frame The code frame which was popped out
|
* @param frame The code frame which was popped out
|
||||||
*/
|
*/
|
||||||
void onFramePop(Context ctx, CodeFrame frame);
|
void onFramePop(Context ctx, CodeFrame frame);
|
||||||
|
|
||||||
void connect();
|
|
||||||
void disconnect();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ 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.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
||||||
@@ -18,11 +20,12 @@ 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<>();
|
||||||
|
|
||||||
private final byte[] favicon, index, protocol;
|
private final byte[] favicon, index, protocol;
|
||||||
|
private final Notifier connNotifier = new Notifier();
|
||||||
|
|
||||||
private static void send(HttpRequest req, String val) throws IOException {
|
private static void send(HttpRequest req, String val) throws IOException {
|
||||||
req.writeResponse(200, "OK", "application/json", val.getBytes());
|
req.writeResponse(200, "OK", "application/json", val.getBytes());
|
||||||
@@ -67,10 +70,11 @@ public class DebugServer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
switch (msg.name) {
|
switch (msg.name) {
|
||||||
case "Debugger.enable": debugger.enable(msg); continue;
|
case "Debugger.enable":
|
||||||
|
connNotifier.next();
|
||||||
|
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;
|
||||||
@@ -151,6 +155,10 @@ public class DebugServer {
|
|||||||
}, "Debug Handler");
|
}, "Debug Handler");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void awaitConnection() {
|
||||||
|
connNotifier.await();
|
||||||
|
}
|
||||||
|
|
||||||
public void run(InetSocketAddress address) {
|
public void run(InetSocketAddress address) {
|
||||||
try {
|
try {
|
||||||
ServerSocket server = new ServerSocket();
|
ServerSocket server = new ServerSocket();
|
||||||
@@ -224,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); }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package me.topchetoeu.jscript.engine.debug;
|
package me.topchetoeu.jscript.engine.debug;
|
||||||
|
|
||||||
public interface Debugger extends DebugHandler, DebugController {
|
public interface Debugger extends DebugHandler, DebugController {
|
||||||
|
void connect();
|
||||||
|
void disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,21 +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.lib.DateLib;
|
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||||
import me.topchetoeu.jscript.lib.MapLib;
|
import me.topchetoeu.jscript.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.lib.PromiseLib;
|
|
||||||
import me.topchetoeu.jscript.lib.RegExpLib;
|
|
||||||
import me.topchetoeu.jscript.lib.SetLib;
|
|
||||||
import me.topchetoeu.jscript.lib.GeneratorLib.Generator;
|
|
||||||
|
|
||||||
|
// 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,13 +106,13 @@ public class SimpleDebugger implements Debugger {
|
|||||||
public CodeFrame frame;
|
public CodeFrame frame;
|
||||||
public CodeFunction func;
|
public CodeFunction func;
|
||||||
public int id;
|
public int id;
|
||||||
public ObjectValue local, capture, global;
|
public ObjectValue local, capture, global, valstack;
|
||||||
public JSONMap serialized;
|
public JSONMap serialized;
|
||||||
public Location location;
|
public Location location;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,31 +120,43 @@ 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);
|
||||||
this.capture = frame.getCaptureScope(ctx, true);
|
this.capture = frame.getCaptureScope(ctx, true);
|
||||||
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);
|
||||||
|
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)))
|
}
|
||||||
);
|
}
|
||||||
}
|
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;
|
||||||
@@ -155,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;
|
||||||
@@ -176,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)) {
|
||||||
@@ -207,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;
|
||||||
@@ -250,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, boolean recurse) {
|
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) {
|
||||||
@@ -277,14 +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";
|
||||||
|
|
||||||
if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp";
|
try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); }
|
||||||
if (Values.isWrapper(val, DateLib.class)) subtype = "date";
|
|
||||||
if (Values.isWrapper(val, MapLib.class)) subtype = "map";
|
|
||||||
if (Values.isWrapper(val, SetLib.class)) subtype = "set";
|
|
||||||
if (Values.isWrapper(val, Generator.class)) subtype = "generator";
|
|
||||||
if (Values.isWrapper(val, PromiseLib.class)) subtype = "promise";
|
|
||||||
|
|
||||||
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
|
|
||||||
catch (Exception e) { }
|
catch (Exception e) { }
|
||||||
|
|
||||||
var res = new JSONMap()
|
var res = new JSONMap()
|
||||||
@@ -294,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +367,7 @@ public class SimpleDebugger implements Debugger {
|
|||||||
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
|
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
|
||||||
else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
|
else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
|
||||||
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
|
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
|
||||||
|
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0");
|
||||||
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
|
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
|
||||||
else res.set("value", num);
|
else res.set("value", num);
|
||||||
|
|
||||||
@@ -320,35 +377,43 @@ public class SimpleDebugger implements Debugger {
|
|||||||
throw new IllegalArgumentException("Unexpected JS object.");
|
throw new IllegalArgumentException("Unexpected JS object.");
|
||||||
}
|
}
|
||||||
private JSONMap serializeObj(Context ctx, Object val) {
|
private JSONMap serializeObj(Context ctx, Object val) {
|
||||||
return serializeObj(ctx, val, true);
|
return serializeObj(ctx, val, false);
|
||||||
}
|
}
|
||||||
private void setObjectGroup(String name, Object val) {
|
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) {
|
||||||
@@ -364,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;
|
||||||
@@ -412,19 +476,14 @@ public class SimpleDebugger implements Debugger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RunResult run(Frame codeFrame, String code) {
|
private RunResult run(Frame codeFrame, String code) {
|
||||||
var engine = new Engine();
|
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
|
||||||
|
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);
|
|
||||||
local.setPrototype(null, capture);
|
|
||||||
env.global = new GlobalScope(local);
|
|
||||||
|
|
||||||
var ctx = new Context(engine).pushEnv(env);
|
var ctx = new Context(engine).pushEnv(env);
|
||||||
var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), "(" + code + ")", codeFrame.frame.thisArg, codeFrame.frame.args);
|
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||||
|
|
||||||
engine.run(true);
|
engine.run(true);
|
||||||
|
|
||||||
@@ -432,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());
|
||||||
|
|
||||||
@@ -441,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;
|
||||||
@@ -466,35 +598,29 @@ 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", "").trim();
|
||||||
|
|
||||||
|
if (cond.equals("")) cond = null;
|
||||||
|
if (cond != null) cond = "(" + cond + ")";
|
||||||
|
|
||||||
Pattern regex;
|
Pattern regex;
|
||||||
|
|
||||||
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
|
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
|
||||||
else regex = Pattern.compile(msg.params.string("urlRegex"));
|
else regex = Pattern.compile(msg.params.string("urlRegex"));
|
||||||
|
|
||||||
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, null);
|
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond);
|
||||||
idToBptCand.put(bpcd.id, bpcd);
|
idToBptCand.put(bpcd.id, bpcd);
|
||||||
|
|
||||||
var locs = new JSONList();
|
var locs = new JSONList();
|
||||||
@@ -516,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)) {
|
||||||
@@ -533,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);
|
||||||
@@ -542,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);
|
||||||
@@ -582,36 +717,37 @@ 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 own = msg.params.bool("ownProperties");
|
var obj = ref.obj;
|
||||||
var accessorPropertiesOnly = msg.params.bool("accessorPropertiesOnly", false);
|
|
||||||
var currOwn = true;
|
|
||||||
|
|
||||||
var res = new JSONList();
|
var res = new JSONList();
|
||||||
|
var ctx = ref.ctx;
|
||||||
|
|
||||||
while (obj != emptyObject && obj != null) {
|
if (obj != emptyObject && obj != null) {
|
||||||
var ctx = objectToCtx.get(obj);
|
|
||||||
|
|
||||||
for (var key : obj.keys(true)) {
|
for (var key : obj.keys(true)) {
|
||||||
var propDesc = new JSONMap();
|
var propDesc = new JSONMap();
|
||||||
|
|
||||||
@@ -623,40 +759,35 @@ public class SimpleDebugger implements Debugger {
|
|||||||
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
|
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
|
||||||
propDesc.set("enumerable", obj.memberEnumerable(key));
|
propDesc.set("enumerable", obj.memberEnumerable(key));
|
||||||
propDesc.set("configurable", obj.memberConfigurable(key));
|
propDesc.set("configurable", obj.memberConfigurable(key));
|
||||||
propDesc.set("isOwn", currOwn);
|
propDesc.set("isOwn", true);
|
||||||
res.add(propDesc);
|
res.add(propDesc);
|
||||||
}
|
}
|
||||||
else if (!accessorPropertiesOnly) {
|
else {
|
||||||
propDesc.set("name", Values.toString(ctx, key));
|
propDesc.set("name", Values.toString(ctx, key));
|
||||||
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
|
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
|
||||||
propDesc.set("writable", obj.memberWritable(key));
|
propDesc.set("writable", obj.memberWritable(key));
|
||||||
propDesc.set("enumerable", obj.memberEnumerable(key));
|
propDesc.set("enumerable", obj.memberEnumerable(key));
|
||||||
propDesc.set("configurable", obj.memberConfigurable(key));
|
propDesc.set("configurable", obj.memberConfigurable(key));
|
||||||
propDesc.set("isOwn", currOwn);
|
propDesc.set("isOwn", true);
|
||||||
res.add(propDesc);
|
res.add(propDesc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj = obj.getPrototype(ctx);
|
var proto = obj.getPrototype(ctx);
|
||||||
|
|
||||||
if (currOwn) {
|
var protoDesc = new JSONMap();
|
||||||
var protoDesc = new JSONMap();
|
protoDesc.set("name", "__proto__");
|
||||||
protoDesc.set("name", "__proto__");
|
protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto));
|
||||||
protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj));
|
protoDesc.set("writable", true);
|
||||||
protoDesc.set("writable", true);
|
protoDesc.set("enumerable", false);
|
||||||
protoDesc.set("enumerable", false);
|
protoDesc.set("configurable", false);
|
||||||
protoDesc.set("configurable", false);
|
protoDesc.set("isOwn", true);
|
||||||
protoDesc.set("isOwn", currOwn);
|
res.add(protoDesc);
|
||||||
res.add(protoDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
currOwn = false;
|
|
||||||
if (own) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
||||||
@@ -664,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=");
|
||||||
@@ -676,49 +809,42 @@ public class SimpleDebugger implements Debugger {
|
|||||||
else src = src.substring(0, start) + src.substring(end + 1);
|
else src = src.substring(0, start) + src.substring(end + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (src) {
|
try {
|
||||||
case CHROME_GET_PROP_FUNC: {
|
Object res = null;
|
||||||
var path = JSON.parse(new Filename("tmp", "json"), (String)args.get(0)).list();
|
if (compare(src, VSCODE_EMPTY)) res = emptyObject;
|
||||||
Object res = thisArg;
|
else if (compare(src, VSCODE_SELF)) res = thisArg;
|
||||||
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el));
|
else if (compare(src, CHROME_GET_PROP_FUNC)) {
|
||||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
|
res = thisArg;
|
||||||
|
for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(ctx, res, JSON.toJs(el));
|
||||||
|
}
|
||||||
|
else if (compare(src, VSCODE_CALL)) {
|
||||||
|
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
|
||||||
|
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
|
||||||
|
}
|
||||||
|
else if (compare(src, VSCODE_AUTOCOMPLETE)) {
|
||||||
|
var target = args.get(0);
|
||||||
|
if (target == null) target = thisArg;
|
||||||
|
res = vscodeAutoSuggest(ctx, target, Values.toString(ctx, args.get(1)), Values.toBoolean(args.get(2)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case VSCODE_STRINGIFY_VAL:
|
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res, byValue))));
|
||||||
case VSCODE_STRINGIFY_PROPS:
|
|
||||||
case VSCODE_SHALLOW_COPY:
|
|
||||||
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_SYMBOL_REQUEST:
|
|
||||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, new ArrayValue(ctx)))));
|
|
||||||
break;
|
|
||||||
case VSCODE_CALL: {
|
|
||||||
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
|
|
||||||
try { ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg))))); }
|
|
||||||
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
ws.send(new V8Error("A non well-known function was used with callFunctionOn."));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
filenameToId.put(filename, id);
|
|
||||||
idToSource.put(id, src);
|
idToSource.put(id, src);
|
||||||
|
filenameToId.put(filename, id);
|
||||||
|
|
||||||
for (var bpcd : idToBptCand.values()) {
|
for (var bpcd : idToBptCand.values()) {
|
||||||
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
|
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
|
||||||
@@ -735,102 +861,110 @@ 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
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null;
|
if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location));
|
||||||
|
loc = frame.location;
|
||||||
|
isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn());
|
||||||
|
|
||||||
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
|
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));
|
|
||||||
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() {
|
||||||
target.data.set(StackData.DEBUGGER, this);
|
if (!target.attachDebugger(this)) {
|
||||||
|
ws.send(new V8Error("A debugger is already attached to this engine."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@Override public void disconnect() {
|
@Override public synchronized void disconnect() {
|
||||||
target.data.remove(StackData.DEBUGGER);
|
target.detachDebugger();
|
||||||
enabled = false;
|
enabled = false;
|
||||||
updateNotifier.next();
|
updateNotifier.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SimpleDebugger(WebSocket ws, Engine target) {
|
public SimpleDebugger(WebSocket ws, Engine target) {
|
||||||
this.ws = ws;
|
this.ws = ws;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SimpleDebugger get(WebSocket ws, Engine target) {
|
|
||||||
if (target.data.has(StackData.DEBUGGER)) {
|
|
||||||
ws.send(new V8Error("A debugger is already attached to this engine."));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var res = new SimpleDebugger(ws, target);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package me.topchetoeu.jscript.engine.debug;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
|
||||||
import me.topchetoeu.jscript.json.JSON;
|
import me.topchetoeu.jscript.json.JSON;
|
||||||
import me.topchetoeu.jscript.json.JSONElement;
|
import me.topchetoeu.jscript.json.JSONElement;
|
||||||
import me.topchetoeu.jscript.json.JSONMap;
|
import me.topchetoeu.jscript.json.JSONMap;
|
||||||
@@ -33,7 +32,7 @@ public class V8Message {
|
|||||||
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
||||||
}
|
}
|
||||||
public V8Message(String raw) {
|
public V8Message(String raw) {
|
||||||
this(JSON.parse(new Filename("jscript", "json-msg"), raw).map());
|
this(JSON.parse(null, raw).map());
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONMap toMap() {
|
public JSONMap toMap() {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
|||||||
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
||||||
|
|
||||||
public class WebSocket implements AutoCloseable {
|
public class WebSocket implements AutoCloseable {
|
||||||
public long maxLength = 2000000;
|
public long maxLength = 1 << 20;
|
||||||
|
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
@@ -61,39 +61,48 @@ public class WebSocket implements AutoCloseable {
|
|||||||
else return new byte[4];
|
else return new byte[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLength(long len) {
|
private void writeLength(int len) {
|
||||||
try {
|
try {
|
||||||
if (len < 126) {
|
if (len < 126) {
|
||||||
out().write((int)len);
|
out().write((int)len);
|
||||||
}
|
}
|
||||||
else if (len < 0xFFFF) {
|
else if (len <= 0xFFFF) {
|
||||||
out().write(126);
|
out().write(126);
|
||||||
out().write((int)(len >> 8) & 0xFF);
|
out().write((int)(len >> 8) & 0xFF);
|
||||||
out().write((int)len & 0xFF);
|
out().write((int)len & 0xFF);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
out().write(127);
|
out().write(127);
|
||||||
out().write((int)(len >> 56) & 0xFF);
|
out().write((len >> 56) & 0xFF);
|
||||||
out().write((int)(len >> 48) & 0xFF);
|
out().write((len >> 48) & 0xFF);
|
||||||
out().write((int)(len >> 40) & 0xFF);
|
out().write((len >> 40) & 0xFF);
|
||||||
out().write((int)(len >> 32) & 0xFF);
|
out().write((len >> 32) & 0xFF);
|
||||||
out().write((int)(len >> 24) & 0xFF);
|
out().write((len >> 24) & 0xFF);
|
||||||
out().write((int)(len >> 16) & 0xFF);
|
out().write((len >> 16) & 0xFF);
|
||||||
out().write((int)(len >> 8) & 0xFF);
|
out().write((len >> 8) & 0xFF);
|
||||||
out().write((int)len & 0xFF);
|
out().write(len & 0xFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||||
}
|
}
|
||||||
private synchronized void write(int type, byte[] data) {
|
private synchronized void write(int type, byte[] data) {
|
||||||
try {
|
try {
|
||||||
out().write(type | 0x80);
|
int i;
|
||||||
writeLength(data.length);
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (i = 0; i < data.length / 0xFFFF; i++) {
|
||||||
out().write(data[i]);
|
out().write(type);
|
||||||
|
writeLength(0xFFFF);
|
||||||
|
out().write(data, i * 0xFFFF, 0xFFFF);
|
||||||
|
type = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out().write(type | 0x80);
|
||||||
|
writeLength(data.length % 0xFFFF);
|
||||||
|
out().write(data, i * 0xFFFF, data.length % 0xFFFF);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(String data) {
|
public void send(String data) {
|
||||||
@@ -109,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());
|
||||||
}
|
}
|
||||||
@@ -192,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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package me.topchetoeu.jscript.engine.frame;
|
package me.topchetoeu.jscript.engine.frame;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
import me.topchetoeu.jscript.engine.StackData;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.LocalScope;
|
import me.topchetoeu.jscript.engine.scope.LocalScope;
|
||||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||||
@@ -17,32 +17,74 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
|||||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||||
|
|
||||||
public class CodeFrame {
|
public class CodeFrame {
|
||||||
private class TryCtx {
|
public static enum TryState {
|
||||||
public static final int STATE_TRY = 0;
|
TRY,
|
||||||
public static final int STATE_CATCH = 1;
|
CATCH,
|
||||||
public static final int STATE_FINALLY_THREW = 2;
|
FINALLY,
|
||||||
public static final int STATE_FINALLY_RETURNED = 3;
|
}
|
||||||
public static final int STATE_FINALLY_JUMPED = 4;
|
|
||||||
|
|
||||||
public final boolean hasCatch, hasFinally;
|
public static class TryCtx {
|
||||||
public final int tryStart, catchStart, finallyStart, end;
|
public final int start, end, catchStart, finallyStart;
|
||||||
public int state;
|
public final int restoreStackPtr;
|
||||||
public Object retVal;
|
public final TryState state;
|
||||||
public EngineException err;
|
public final EngineException error;
|
||||||
public int jumpPtr;
|
public final PendingResult result;
|
||||||
|
|
||||||
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) {
|
public boolean hasCatch() { return catchStart >= 0; }
|
||||||
hasCatch = catchN >= 0;
|
public boolean hasFinally() { return finallyStart >= 0; }
|
||||||
hasFinally = finallyN >= 0;
|
|
||||||
|
|
||||||
if (catchN < 0) catchN = 0;
|
public boolean inBounds(int ptr) {
|
||||||
if (finallyN < 0) finallyN = 0;
|
return ptr >= start && ptr < end;
|
||||||
|
}
|
||||||
|
|
||||||
this.tryStart = tryStart;
|
public TryCtx _catch(EngineException e) {
|
||||||
this.catchStart = tryStart + tryN;
|
if (error != null) e.setCause(error);
|
||||||
this.finallyStart = catchStart + catchN;
|
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
|
||||||
this.end = finallyStart + finallyN;
|
}
|
||||||
this.jumpPtr = end;
|
public TryCtx _finally(PendingResult res) {
|
||||||
|
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
|
||||||
|
this.catchStart = catchStart;
|
||||||
|
this.finallyStart = finallyStart;
|
||||||
|
this.restoreStackPtr = stackPtr;
|
||||||
|
this.result = res == null ? PendingResult.ofNone() : res;
|
||||||
|
this.state = state;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.error = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PendingResult {
|
||||||
|
public final boolean isReturn, isJump, isThrow;
|
||||||
|
public final Object value;
|
||||||
|
public final EngineException error;
|
||||||
|
public final int ptr;
|
||||||
|
public final Instruction instruction;
|
||||||
|
|
||||||
|
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
|
||||||
|
this.instruction = instr;
|
||||||
|
this.isReturn = isReturn;
|
||||||
|
this.isJump = isJump;
|
||||||
|
this.isThrow = isThrow;
|
||||||
|
this.value = value;
|
||||||
|
this.error = error;
|
||||||
|
this.ptr = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PendingResult ofNone() {
|
||||||
|
return new PendingResult(null, false, false, false, null, null, 0);
|
||||||
|
}
|
||||||
|
public static PendingResult ofReturn(Object value, Instruction instr) {
|
||||||
|
return new PendingResult(instr, true, false, false, value, null, 0);
|
||||||
|
}
|
||||||
|
public static PendingResult ofThrow(EngineException error, Instruction instr) {
|
||||||
|
return new PendingResult(instr, false, false, true, null, error, 0);
|
||||||
|
}
|
||||||
|
public static PendingResult ofJump(int codePtr, Instruction instr) {
|
||||||
|
return new PendingResult(instr, false, true, false, null, null, codePtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,11 +93,10 @@ public class CodeFrame {
|
|||||||
public final Object[] args;
|
public final Object[] args;
|
||||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||||
public final CodeFunction function;
|
public final CodeFunction function;
|
||||||
|
|
||||||
public Object[] stack = new Object[32];
|
public Object[] stack = new Object[32];
|
||||||
public int stackPtr = 0;
|
public int stackPtr = 0;
|
||||||
public int codePtr = 0;
|
public int codePtr = 0;
|
||||||
public boolean jumpFlag = false;
|
public boolean jumpFlag = false, popTryFlag = false;
|
||||||
private Location prevLoc = null;
|
private Location prevLoc = null;
|
||||||
|
|
||||||
public ObjectValue getLocalScope(Context ctx, boolean props) {
|
public ObjectValue getLocalScope(Context ctx, boolean props) {
|
||||||
@@ -84,9 +125,30 @@ public class CodeFrame {
|
|||||||
|
|
||||||
return new ScopeValue(scope.captures, names);
|
return new ScopeValue(scope.captures, names);
|
||||||
}
|
}
|
||||||
|
public ObjectValue getValStackScope(Context ctx) {
|
||||||
|
return new ObjectValue() {
|
||||||
|
@Override
|
||||||
|
protected Object getField(Context ctx, Object key) {
|
||||||
|
var i = (int)Values.toNumber(ctx, key);
|
||||||
|
if (i < 0 || i >= stackPtr) return null;
|
||||||
|
else return stack[i];
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected boolean hasField(Context ctx, Object key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public List<Object> keys(boolean includeNonEnumerable) {
|
||||||
|
var res = super.keys(includeNonEnumerable);
|
||||||
|
for (var i = 0; i < stackPtr; i++) res.add(i);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void addTry(int n, int catchN, int finallyN) {
|
public void addTry(int start, int end, int catchStart, int finallyStart) {
|
||||||
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
var err = tryStack.empty() ? null : tryStack.peek().error;
|
||||||
|
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
|
||||||
|
|
||||||
tryStack.add(res);
|
tryStack.add(res);
|
||||||
}
|
}
|
||||||
@@ -124,141 +186,115 @@ public class CodeFrame {
|
|||||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCause(Context ctx, EngineException err, EngineException cause) {
|
|
||||||
err.cause = cause;
|
|
||||||
}
|
|
||||||
private Object nextNoTry(Context ctx, Instruction instr) {
|
|
||||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
|
||||||
if (codePtr < 0 || codePtr >= function.body.length) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.jumpFlag = false;
|
|
||||||
return Runners.exec(ctx, instr, this);
|
|
||||||
}
|
|
||||||
catch (EngineException e) {
|
|
||||||
throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
|
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
|
||||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||||
var debugger = StackData.getDebugger(ctx);
|
|
||||||
|
Instruction instr = null;
|
||||||
|
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
|
||||||
|
|
||||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||||
try {
|
try {
|
||||||
var instr = function.body[codePtr];
|
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||||
|
|
||||||
if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
|
if (instr == null) returnValue = null;
|
||||||
returnValue = nextNoTry(ctx, instr);
|
else {
|
||||||
|
// System.out.println(instr + "@" + instr.location);
|
||||||
|
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
|
||||||
|
|
||||||
|
if (instr.location != null) prevLoc = instr.location;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.jumpFlag = this.popTryFlag = false;
|
||||||
|
returnValue = Runners.exec(ctx, instr, this);
|
||||||
|
}
|
||||||
|
catch (EngineException e) {
|
||||||
|
error = e.add(ctx, function.name, prevLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (EngineException e) { error = e; }
|
catch (EngineException e) { error = e; }
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!tryStack.empty()) {
|
while (!tryStack.empty()) {
|
||||||
var tryCtx = tryStack.peek();
|
var tryCtx = tryStack.peek();
|
||||||
var newState = -1;
|
TryCtx newCtx = null;
|
||||||
|
|
||||||
switch (tryCtx.state) {
|
if (error != null) {
|
||||||
case TryCtx.STATE_TRY:
|
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
||||||
if (error != null) {
|
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
|
||||||
if (tryCtx.hasCatch) {
|
|
||||||
tryCtx.err = error;
|
|
||||||
newState = TryCtx.STATE_CATCH;
|
|
||||||
}
|
|
||||||
else if (tryCtx.hasFinally) {
|
|
||||||
tryCtx.err = error;
|
|
||||||
newState = TryCtx.STATE_FINALLY_THREW;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (returnValue != Runners.NO_RETURN) {
|
|
||||||
if (tryCtx.hasFinally) {
|
|
||||||
tryCtx.retVal = returnValue;
|
|
||||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN;
|
|
||||||
|
|
||||||
if (tryCtx.hasFinally) {
|
|
||||||
if (jumpFlag) tryCtx.jumpPtr = codePtr;
|
|
||||||
else tryCtx.jumpPtr = tryCtx.end;
|
|
||||||
newState = TryCtx.STATE_FINALLY_JUMPED;
|
|
||||||
}
|
|
||||||
else codePtr = tryCtx.end;
|
|
||||||
break;
|
|
||||||
case TryCtx.STATE_CATCH:
|
|
||||||
if (error != null) {
|
|
||||||
if (tryCtx.hasFinally) {
|
|
||||||
tryCtx.err = error;
|
|
||||||
newState = TryCtx.STATE_FINALLY_THREW;
|
|
||||||
}
|
|
||||||
setCause(ctx, error, tryCtx.err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (returnValue != Runners.NO_RETURN) {
|
|
||||||
if (tryCtx.hasFinally) {
|
|
||||||
tryCtx.retVal = returnValue;
|
|
||||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (codePtr >= tryCtx.catchStart && codePtr < tryCtx.finallyStart) return Runners.NO_RETURN;
|
|
||||||
|
|
||||||
if (tryCtx.hasFinally) {
|
|
||||||
if (jumpFlag) tryCtx.jumpPtr = codePtr;
|
|
||||||
else tryCtx.jumpPtr = tryCtx.end;
|
|
||||||
newState = TryCtx.STATE_FINALLY_JUMPED;
|
|
||||||
}
|
|
||||||
else codePtr = tryCtx.end;
|
|
||||||
break;
|
|
||||||
case TryCtx.STATE_FINALLY_THREW:
|
|
||||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
|
||||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
|
|
||||||
else return Runners.NO_RETURN;
|
|
||||||
break;
|
|
||||||
case TryCtx.STATE_FINALLY_RETURNED:
|
|
||||||
if (returnValue == Runners.NO_RETURN) {
|
|
||||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
|
|
||||||
else return Runners.NO_RETURN;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TryCtx.STATE_FINALLY_JUMPED:
|
|
||||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
|
||||||
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
|
|
||||||
else codePtr = tryCtx.end;
|
|
||||||
}
|
|
||||||
else return Runners.NO_RETURN;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else if (returnValue != Runners.NO_RETURN) {
|
||||||
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
|
||||||
|
|
||||||
if (newState == -1) {
|
|
||||||
tryStack.pop();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
||||||
|
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
|
||||||
|
}
|
||||||
|
else if (!this.popTryFlag) newCtx = tryCtx;
|
||||||
|
|
||||||
tryCtx.state = newState;
|
if (newCtx != null) {
|
||||||
switch (newState) {
|
if (newCtx != tryCtx) {
|
||||||
case TryCtx.STATE_CATCH:
|
switch (newCtx.state) {
|
||||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
case CATCH:
|
||||||
codePtr = tryCtx.catchStart;
|
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
|
||||||
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true);
|
codePtr = tryCtx.catchStart;
|
||||||
break;
|
stackPtr = tryCtx.restoreStackPtr;
|
||||||
default:
|
break;
|
||||||
|
case FINALLY:
|
||||||
|
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||||
|
codePtr = tryCtx.finallyStart;
|
||||||
|
stackPtr = tryCtx.restoreStackPtr;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
tryStack.pop();
|
||||||
|
tryStack.push(newCtx);
|
||||||
|
}
|
||||||
|
error = null;
|
||||||
|
returnValue = Runners.NO_RETURN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||||
|
|
||||||
|
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
|
||||||
codePtr = tryCtx.finallyStart;
|
codePtr = tryCtx.finallyStart;
|
||||||
|
stackPtr = tryCtx.restoreStackPtr;
|
||||||
|
tryStack.pop();
|
||||||
|
tryStack.push(tryCtx._finally(null));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tryStack.pop();
|
||||||
|
codePtr = tryCtx.end;
|
||||||
|
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
|
||||||
|
if (tryCtx.result.isJump) {
|
||||||
|
codePtr = tryCtx.result.ptr;
|
||||||
|
jumpFlag = true;
|
||||||
|
}
|
||||||
|
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
||||||
|
if (tryCtx.result.isThrow) {
|
||||||
|
error = tryCtx.result.error;
|
||||||
|
}
|
||||||
|
if (error != null) error.setCause(tryCtx.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Runners.NO_RETURN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false);
|
var caught = false;
|
||||||
|
|
||||||
|
for (var frame : ctx.frames()) {
|
||||||
|
for (var tryCtx : frame.tryStack) {
|
||||||
|
if (tryCtx.state == TryState.TRY) caught = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.engine.onInstruction(ctx, this, instr, null, error, caught);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
if (returnValue != Runners.NO_RETURN) {
|
if (returnValue != Runners.NO_RETURN) {
|
||||||
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
|
ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.Collections;
|
|||||||
|
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
|
import me.topchetoeu.jscript.engine.Engine;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||||
@@ -26,16 +27,12 @@ public class Runners {
|
|||||||
throw EngineException.ofSyntax((String)instr.get(0));
|
throw EngineException.ofSyntax((String)instr.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
|
|
||||||
return Values.call(ctx, func, thisArg, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, call(ctx, func, thisArg, callArgs));
|
frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
|
||||||
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
@@ -46,17 +43,6 @@ public class Runners {
|
|||||||
|
|
||||||
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||||
|
|
||||||
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
|
||||||
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// var proto = Values.getMember(ctx, funcObj, "prototype");
|
|
||||||
// var obj = new ObjectValue();
|
|
||||||
// obj.setPrototype(ctx, proto);
|
|
||||||
// call(ctx, funcObj, obj, callArgs);
|
|
||||||
// frame.push(ctx, obj);
|
|
||||||
// }
|
|
||||||
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
@@ -100,51 +86,48 @@ public class Runners {
|
|||||||
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 arr = new ObjectValue();
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
var members = Values.getMembers(ctx, val, false, false);
|
var members = Values.getMembers(ctx, val, false, false);
|
||||||
Collections.reverse(members);
|
Collections.reverse(members);
|
||||||
|
|
||||||
|
frame.push(ctx, null);
|
||||||
|
|
||||||
for (var el : members) {
|
for (var el : members) {
|
||||||
if (el instanceof Symbol) continue;
|
if (el instanceof Symbol) continue;
|
||||||
arr.defineProperty(ctx, i++, el);
|
var obj = new ObjectValue();
|
||||||
|
obj.defineProperty(ctx, "value", el);
|
||||||
|
frame.push(ctx, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.defineProperty(ctx, "length", i);
|
|
||||||
|
|
||||||
frame.push(ctx, arr);
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) {
|
public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
|
||||||
frame.addTry(instr.get(0), instr.get(1), instr.get(2));
|
int start = frame.codePtr + 1;
|
||||||
|
int catchStart = (int)instr.get(0);
|
||||||
|
int finallyStart = (int)instr.get(1);
|
||||||
|
if (finallyStart >= 0) finallyStart += start;
|
||||||
|
if (catchStart >= 0) catchStart += start;
|
||||||
|
int end = (int)instr.get(2) + start;
|
||||||
|
frame.addTry(start, end, catchStart, finallyStart);
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
|
public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
|
||||||
|
frame.popTryFlag = true;
|
||||||
|
return NO_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
|
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
|
||||||
int offset = instr.get(0), count = instr.get(1);
|
int count = instr.get(0);
|
||||||
|
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
frame.push(ctx, frame.peek(offset + count - 1));
|
frame.push(ctx, frame.peek(count - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
public static Object execMove(Context ctx, Instruction instr, CodeFrame frame) {
|
|
||||||
int offset = instr.get(0), count = instr.get(1);
|
|
||||||
|
|
||||||
var tmp = frame.take(offset);
|
|
||||||
var res = frame.take(count);
|
|
||||||
|
|
||||||
for (var i = 0; i < offset; i++) frame.push(ctx, tmp[i]);
|
|
||||||
for (var i = 0; i < count; i++) frame.push(ctx, res[i]);
|
|
||||||
|
|
||||||
frame.codePtr++;
|
|
||||||
return NO_RETURN;
|
|
||||||
}
|
|
||||||
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
|
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
|
||||||
frame.push(ctx, null);
|
frame.push(ctx, null);
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
@@ -183,16 +166,13 @@ public class Runners {
|
|||||||
}
|
}
|
||||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||||
long id = (Long)instr.get(0);
|
long id = (Long)instr.get(0);
|
||||||
int localsN = (Integer)instr.get(1);
|
var captures = new ValueVariable[instr.params.length - 1];
|
||||||
int len = (Integer)instr.get(2);
|
|
||||||
var captures = new ValueVariable[instr.params.length - 3];
|
|
||||||
|
|
||||||
for (var i = 3; i < instr.params.length; i++) {
|
for (var i = 1; i < instr.params.length; i++) {
|
||||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
captures[i - 1] = frame.scope.get(instr.get(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = ctx.engine.functions.get(id);
|
var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
|
||||||
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
|
|
||||||
|
|
||||||
frame.push(ctx, func);
|
frame.push(ctx, func);
|
||||||
|
|
||||||
@@ -310,7 +290,6 @@ public class Runners {
|
|||||||
var val = frame.pop();
|
var val = frame.pop();
|
||||||
|
|
||||||
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
||||||
frame.push(ctx, true);
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
@@ -334,10 +313,10 @@ public class Runners {
|
|||||||
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
||||||
case CALL: return execCall(ctx, instr, frame);
|
case CALL: return execCall(ctx, instr, frame);
|
||||||
case CALL_NEW: return execCallNew(ctx, instr, frame);
|
case CALL_NEW: return execCallNew(ctx, instr, frame);
|
||||||
case TRY: return execTry(ctx, instr, frame);
|
case TRY_START: return execTryStart(ctx, instr, frame);
|
||||||
|
case TRY_END: return execTryEnd(ctx, instr, frame);
|
||||||
|
|
||||||
case DUP: return execDup(ctx, instr, frame);
|
case DUP: return execDup(ctx, instr, frame);
|
||||||
case MOVE: return execMove(ctx, instr, frame);
|
|
||||||
case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
|
case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
|
||||||
case LOAD_VAR: return execLoadVar(ctx, instr, frame);
|
case LOAD_VAR: return execLoadVar(ctx, instr, frame);
|
||||||
case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
|
case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ 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 GlobalScope parent() { return null; }
|
|
||||||
|
|
||||||
public boolean has(Context ctx, String name) {
|
public boolean has(Context ctx, String name) {
|
||||||
return obj.hasMember(ctx, name, false);
|
return obj.hasMember(ctx, name, false);
|
||||||
}
|
}
|
||||||
@@ -28,7 +25,7 @@ public class GlobalScope implements ScopeRecord {
|
|||||||
return new GlobalScope(obj);
|
return new GlobalScope(obj);
|
||||||
}
|
}
|
||||||
public LocalScopeRecord child() {
|
public LocalScopeRecord child() {
|
||||||
return new LocalScopeRecord(this);
|
return new LocalScopeRecord();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object define(String name) {
|
public Object define(String name) {
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ public class LocalScope {
|
|||||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||||
|
|
||||||
public ValueVariable get(int i) {
|
public ValueVariable get(int i) {
|
||||||
if (i >= locals.length)
|
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||||
return catchVars.get(i - locals.length);
|
|
||||||
if (i >= 0) return locals[i];
|
if (i >= 0) return locals[i];
|
||||||
else return captures[~i];
|
else return captures[~i];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ package me.topchetoeu.jscript.engine.scope;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
|
||||||
|
|
||||||
public class LocalScopeRecord implements ScopeRecord {
|
public class LocalScopeRecord implements ScopeRecord {
|
||||||
public final LocalScopeRecord parent;
|
public final LocalScopeRecord parent;
|
||||||
public final GlobalScope global;
|
|
||||||
|
|
||||||
private final ArrayList<String> captures = new ArrayList<>();
|
private final ArrayList<String> captures = new ArrayList<>();
|
||||||
private final ArrayList<String> locals = new ArrayList<>();
|
private final ArrayList<String> locals = new ArrayList<>();
|
||||||
@@ -18,11 +15,8 @@ public class LocalScopeRecord implements ScopeRecord {
|
|||||||
return locals.toArray(String[]::new);
|
return locals.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public LocalScopeRecord parent() { return parent; }
|
|
||||||
|
|
||||||
public LocalScopeRecord child() {
|
public LocalScopeRecord child() {
|
||||||
return new LocalScopeRecord(this, global);
|
return new LocalScopeRecord(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int localsCount() {
|
public int localsCount() {
|
||||||
@@ -62,12 +56,6 @@ public class LocalScopeRecord implements ScopeRecord {
|
|||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
public boolean has(Context ctx, String name) {
|
|
||||||
return
|
|
||||||
global.has(ctx, name) ||
|
|
||||||
locals.contains(name) ||
|
|
||||||
parent != null && parent.has(ctx, name);
|
|
||||||
}
|
|
||||||
public Object define(String name, boolean force) {
|
public Object define(String name, boolean force) {
|
||||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||||
locals.add(name);
|
locals.add(name);
|
||||||
@@ -80,12 +68,10 @@ public class LocalScopeRecord implements ScopeRecord {
|
|||||||
locals.remove(locals.size() - 1);
|
locals.remove(locals.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalScopeRecord(GlobalScope global) {
|
public LocalScopeRecord() {
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
this.global = global;
|
|
||||||
}
|
}
|
||||||
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
|
public LocalScopeRecord(LocalScopeRecord parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.global = global;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ package me.topchetoeu.jscript.engine.scope;
|
|||||||
public interface ScopeRecord {
|
public interface ScopeRecord {
|
||||||
public Object getKey(String name);
|
public Object getKey(String name);
|
||||||
public Object define(String name);
|
public Object define(String name);
|
||||||
public ScopeRecord parent();
|
|
||||||
public LocalScopeRecord child();
|
public LocalScopeRecord child();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
private Object[] values;
|
private Object[] values;
|
||||||
private int size;
|
private int size;
|
||||||
|
|
||||||
private void alloc(int index) {
|
private Object[] alloc(int index) {
|
||||||
if (index < values.length) return;
|
index++;
|
||||||
|
if (index < values.length) return values;
|
||||||
if (index < values.length * 2) index = values.length * 2;
|
if (index < values.length * 2) index = values.length * 2;
|
||||||
|
|
||||||
var arr = new Object[index];
|
var arr = new Object[index];
|
||||||
System.arraycopy(values, 0, arr, 0, values.length);
|
System.arraycopy(values, 0, arr, 0, values.length);
|
||||||
values = arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() { return size; }
|
public int size() { return size; }
|
||||||
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
if (val < 0) return false;
|
if (val < 0) return false;
|
||||||
if (size > val) shrink(size - val);
|
if (size > val) shrink(size - val);
|
||||||
else {
|
else {
|
||||||
alloc(val);
|
values = alloc(val);
|
||||||
size = val;
|
size = val;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
public void set(Context ctx, int i, Object val) {
|
public void set(Context ctx, int i, Object val) {
|
||||||
if (i < 0) return;
|
if (i < 0) return;
|
||||||
|
|
||||||
alloc(i);
|
values = alloc(i);
|
||||||
|
|
||||||
val = Values.normalize(ctx, val);
|
val = Values.normalize(ctx, val);
|
||||||
if (val == null) val = UNDEFINED;
|
if (val == null) val = UNDEFINED;
|
||||||
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
if (i >= size) size = i + 1;
|
if (i >= size) size = i + 1;
|
||||||
}
|
}
|
||||||
public boolean has(int i) {
|
public boolean has(int i) {
|
||||||
return i >= 0 && i < values.length && values[i] != null;
|
return i >= 0 && i < size && values[i] != null;
|
||||||
}
|
}
|
||||||
public void remove(int i) {
|
public void remove(int i) {
|
||||||
if (i < 0 || i >= values.length) return;
|
if (i < 0 || i >= values.length) return;
|
||||||
@@ -84,8 +85,9 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||||
// Iterate in reverse to reallocate at most once
|
// Iterate in reverse to reallocate at most once
|
||||||
for (var i = count - 1; i >= 0; i--) {
|
for (var i = count - 1; i >= 0; i--) {
|
||||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
|
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
|
||||||
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
||||||
|
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
|
||||||
else arr.set(ctx, i + destStart, values[i + sourceStart]);
|
else arr.set(ctx, i + destStart, values[i + sourceStart]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +99,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void move(int srcI, int dstI, int n) {
|
public void move(int srcI, int dstI, int n) {
|
||||||
alloc(dstI + n);
|
values = alloc(dstI + n);
|
||||||
|
|
||||||
System.arraycopy(values, srcI, values, dstI, n);
|
System.arraycopy(values, srcI, values, dstI, n);
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ 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 int length;
|
|
||||||
public final Instruction[] body;
|
public final Instruction[] body;
|
||||||
public final String[] captureNames, localNames;
|
public final String[] captureNames, localNames;
|
||||||
public final ValueVariable[] captures;
|
public final ValueVariable[] captures;
|
||||||
@@ -35,7 +33,7 @@ public class CodeFunction extends FunctionValue {
|
|||||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||||
var frame = new CodeFrame(ctx, thisArg, args, this);
|
var frame = new CodeFrame(ctx, thisArg, args, this);
|
||||||
try {
|
try {
|
||||||
StackData.pushFrame(ctx, frame);
|
ctx.pushFrame(frame);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||||
@@ -43,18 +41,17 @@ public class CodeFunction extends FunctionValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
StackData.popFrame(ctx, frame);
|
ctx.popFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) {
|
public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
|
||||||
super(name, length);
|
super(name, body.argsN);
|
||||||
this.captures = captures;
|
this.captures = captures;
|
||||||
this.captureNames = body.captureNames;
|
this.captureNames = body.captureNames;
|
||||||
this.localNames = body.localNames;
|
this.localNames = body.localNames;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.localsN = localsN;
|
this.localsN = body.localsN;
|
||||||
this.length = length;
|
|
||||||
this.body = body.instructions;
|
this.body = body.instructions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public abstract class FunctionValue extends ObjectValue {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "function(...) { ...}";
|
return String.format("function %s(...)", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Object call(Context ctx, Object thisArg, Object ...args);
|
public abstract Object call(Context ctx, Object thisArg, Object ...args);
|
||||||
@@ -21,21 +21,21 @@ public abstract class FunctionValue extends ObjectValue {
|
|||||||
|
|
||||||
@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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ public class NativeWrapper extends ObjectValue {
|
|||||||
else return super.getPrototype(ctx);
|
else return super.getPrototype(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return wrapped.toString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return wrapped.equals(obj);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return wrapped.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
public NativeWrapper(Object wrapped) {
|
public NativeWrapper(Object wrapped) {
|
||||||
this.wrapped = wrapped;
|
this.wrapped = wrapped;
|
||||||
prototype = NATIVE_PROTO;
|
prototype = NATIVE_PROTO;
|
||||||
|
|||||||
@@ -103,8 +103,7 @@ public class ObjectValue {
|
|||||||
) return true;
|
) return true;
|
||||||
|
|
||||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||||
if (!memberConfigurable(key))
|
if (!memberConfigurable(key)) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
nonWritableSet.remove(key);
|
nonWritableSet.remove(key);
|
||||||
nonEnumerableSet.remove(key);
|
nonEnumerableSet.remove(key);
|
||||||
@@ -263,7 +262,7 @@ public class ObjectValue {
|
|||||||
values.put(key, val);
|
values.put(key, val);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (key.equals("__proto__")) return setPrototype(ctx, val);
|
else if ("__proto__".equals(key)) return setPrototype(ctx, val);
|
||||||
else if (nonWritableSet.contains(key)) return false;
|
else if (nonWritableSet.contains(key)) return false;
|
||||||
else return setField(ctx, key, val);
|
else return setField(ctx, key, val);
|
||||||
}
|
}
|
||||||
@@ -274,7 +273,7 @@ public class ObjectValue {
|
|||||||
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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -117,6 +135,7 @@ public class Values {
|
|||||||
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
|
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
|
||||||
if (val instanceof String) {
|
if (val instanceof String) {
|
||||||
try { return Double.parseDouble((String)val); }
|
try { return Double.parseDouble((String)val); }
|
||||||
|
catch (NumberFormatException e) { return Double.NaN; }
|
||||||
catch (Throwable e) { throw new UncheckedException(e); }
|
catch (Throwable e) { throw new UncheckedException(e); }
|
||||||
}
|
}
|
||||||
return Double.NaN;
|
return Double.NaN;
|
||||||
@@ -136,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";
|
||||||
}
|
}
|
||||||
@@ -190,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) {
|
||||||
@@ -231,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]);
|
||||||
@@ -271,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);
|
||||||
@@ -289,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) {
|
||||||
@@ -371,13 +401,18 @@ public class Values {
|
|||||||
}
|
}
|
||||||
public static Object callNew(Context ctx, Object func, Object ...args) {
|
public static Object callNew(Context ctx, Object func, Object ...args) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
var proto = Values.getMember(ctx, func, "prototype");
|
try {
|
||||||
res.setPrototype(ctx, proto);
|
var proto = Values.getMember(ctx, func, "prototype");
|
||||||
|
res.setPrototype(ctx, proto);
|
||||||
|
|
||||||
var ret = call(ctx, func, res, args);
|
var ret = call(ctx, func, res, args);
|
||||||
|
|
||||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||||
return res;
|
return res;
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw EngineException.ofType("Tried to call new on an invalid constructor.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
||||||
@@ -512,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");
|
||||||
@@ -565,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 {
|
||||||
@@ -576,17 +611,59 @@ public class Values {
|
|||||||
|
|
||||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||||
else return new ObjectValue(ctx, Map.of("value", it.next()));
|
else {
|
||||||
|
var obj = new ObjectValue();
|
||||||
|
obj.defineProperty(_ctx, "value", it.next());
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) {
|
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) {
|
||||||
|
System.out.print(val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (passed.contains(val)) {
|
if (passed.contains(val)) {
|
||||||
System.out.print("[circular]");
|
System.out.print("[circular]");
|
||||||
return;
|
return;
|
||||||
@@ -595,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);
|
||||||
@@ -628,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 {
|
||||||
@@ -650,8 +724,9 @@ public class Values {
|
|||||||
for (int i = 0; i < tab; i++) System.out.print(" ");
|
for (int i = 0; i < tab; i++) System.out.print(" ");
|
||||||
System.out.print("}");
|
System.out.print("}");
|
||||||
|
|
||||||
passed.remove(val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passed.remove(val);
|
||||||
}
|
}
|
||||||
else if (val == null) System.out.print("undefined");
|
else if (val == null) System.out.print("undefined");
|
||||||
else if (val == Values.NULL) System.out.print("null");
|
else if (val == Values.NULL) System.out.print("null");
|
||||||
|
|||||||
@@ -12,19 +12,48 @@ import me.topchetoeu.jscript.engine.values.Values;
|
|||||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||||
|
|
||||||
public class EngineException extends RuntimeException {
|
public class EngineException extends RuntimeException {
|
||||||
|
public static class StackElement {
|
||||||
|
public final Location location;
|
||||||
|
public final String function;
|
||||||
|
public final Context ctx;
|
||||||
|
|
||||||
|
public boolean visible() {
|
||||||
|
return ctx == null || ctx.environment() == null || ctx.environment().stackVisible;
|
||||||
|
}
|
||||||
|
public String toString() {
|
||||||
|
var res = "";
|
||||||
|
var loc = location;
|
||||||
|
|
||||||
|
if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc);
|
||||||
|
|
||||||
|
if (loc != null) res += "at " + loc.toString() + " ";
|
||||||
|
if (function != null && !function.equals("")) res += "in " + function + " ";
|
||||||
|
|
||||||
|
return res.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackElement(Context ctx, Location location, String function) {
|
||||||
|
if (function != null) function = function.trim();
|
||||||
|
if (function.equals("")) function = null;
|
||||||
|
|
||||||
|
if (ctx == null) this.ctx = null;
|
||||||
|
else this.ctx = new Context(ctx.engine).pushEnv(ctx.environment());
|
||||||
|
this.location = location;
|
||||||
|
this.function = function;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final Object value;
|
public final Object value;
|
||||||
public EngineException cause;
|
public EngineException cause;
|
||||||
public Environment env = null;
|
public Environment env = null;
|
||||||
public Engine engine = null;
|
public Engine engine = null;
|
||||||
public final List<String> stackTrace = new ArrayList<>();
|
public final List<StackElement> stackTrace = new ArrayList<>();
|
||||||
|
|
||||||
public EngineException add(String name, Location location) {
|
public EngineException add(Context ctx, String name, Location location) {
|
||||||
var res = "";
|
var el = new StackElement(ctx, location, name);
|
||||||
|
if (el.function == null && el.location == null) return this;
|
||||||
if (location != null) res += "at " + location.toString() + " ";
|
setCtx(ctx.environment(), ctx.engine);
|
||||||
if (name != null && !name.equals("")) res += "in " + name + " ";
|
stackTrace.add(el);
|
||||||
|
|
||||||
this.stackTrace.add(res.trim());
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public EngineException setCause(EngineException cause) {
|
public EngineException setCause(EngineException cause) {
|
||||||
@@ -45,10 +74,10 @@ public class EngineException extends RuntimeException {
|
|||||||
catch (EngineException e) {
|
catch (EngineException e) {
|
||||||
ss.append("[Error while stringifying]\n");
|
ss.append("[Error while stringifying]\n");
|
||||||
}
|
}
|
||||||
// for (var line : stackTrace) {
|
for (var line : stackTrace) {
|
||||||
// ss.append(" ").append(line).append('\n');
|
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
|
||||||
// }
|
}
|
||||||
// if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||||
ss.deleteCharAt(ss.length() - 1);
|
ss.deleteCharAt(ss.length() - 1);
|
||||||
return ss.toString();
|
return ss.toString();
|
||||||
}
|
}
|
||||||
@@ -74,7 +103,7 @@ public class EngineException extends RuntimeException {
|
|||||||
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
|
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
|
||||||
}
|
}
|
||||||
public static EngineException ofSyntax(SyntaxException e) {
|
public static EngineException ofSyntax(SyntaxException e) {
|
||||||
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc);
|
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc);
|
||||||
}
|
}
|
||||||
public static EngineException ofSyntax(String msg) {
|
public static EngineException ofSyntax(String msg) {
|
||||||
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
|
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
public enum EntryType {
|
public enum EntryType {
|
||||||
NONE,
|
NONE("none"),
|
||||||
FILE,
|
FILE("file"),
|
||||||
FOLDER,
|
FOLDER("folder");
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
private EntryType(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,41 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import me.topchetoeu.jscript.Buffer;
|
||||||
|
|
||||||
public interface File {
|
public interface File {
|
||||||
int read() throws IOException, InterruptedException;
|
int read(byte[] buff);
|
||||||
boolean write(byte val) throws IOException, InterruptedException;
|
void write(byte[] buff);
|
||||||
long tell() throws IOException, InterruptedException;
|
long getPtr();
|
||||||
void seek(long offset, int pos) throws IOException, InterruptedException;
|
void setPtr(long offset, int pos);
|
||||||
void close() throws IOException, InterruptedException;
|
void close();
|
||||||
Permissions perms();
|
Mode mode();
|
||||||
|
|
||||||
default String readToString() throws IOException, InterruptedException {
|
default String readToString() {
|
||||||
seek(0, 2);
|
setPtr(0, 2);
|
||||||
long len = tell();
|
long len = getPtr();
|
||||||
if (len < 0) return null;
|
if (len < 0) return null;
|
||||||
|
|
||||||
seek(0, 0);
|
setPtr(0, 0);
|
||||||
byte[] res = new byte[(int)len];
|
|
||||||
|
|
||||||
for (var i = 0; i < len; i++) {
|
byte[] res = new byte[(int)len];
|
||||||
res[i] = (byte)read();
|
read(res);
|
||||||
}
|
|
||||||
|
|
||||||
return new String(res);
|
return new String(res);
|
||||||
}
|
}
|
||||||
|
default String readLine() {
|
||||||
|
var res = new Buffer();
|
||||||
|
var buff = new byte[1];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (read(buff) == 0) {
|
||||||
|
if (res.length() == 0) return null;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buff[0] == '\n') break;
|
||||||
|
|
||||||
|
res.write(res.length(), buff);
|
||||||
|
}
|
||||||
|
return new String(res.data());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
11
src/me/topchetoeu/jscript/filesystem/FileStat.java
Normal file
11
src/me/topchetoeu/jscript/filesystem/FileStat.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
public class FileStat {
|
||||||
|
public final Mode mode;
|
||||||
|
public final EntryType type;
|
||||||
|
|
||||||
|
public FileStat(Mode mode, EntryType type) {
|
||||||
|
this.mode = mode;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public interface Filesystem {
|
public interface Filesystem {
|
||||||
File open(String path) throws IOException, InterruptedException;
|
File open(String path, Mode mode) throws FilesystemException;
|
||||||
boolean mkdir(String path) throws IOException, InterruptedException;
|
void create(String path, EntryType type) throws FilesystemException;
|
||||||
EntryType type(String path) throws IOException, InterruptedException;
|
FileStat stat(String path) throws FilesystemException;
|
||||||
boolean rm(String path) throws IOException, InterruptedException;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
|
|
||||||
|
public class FilesystemException extends RuntimeException {
|
||||||
|
public static enum FSCode {
|
||||||
|
DOESNT_EXIST(0x1),
|
||||||
|
NOT_FILE(0x2),
|
||||||
|
NOT_FOLDER(0x3),
|
||||||
|
NO_PERMISSIONS_R(0x4),
|
||||||
|
NO_PERMISSIONS_RW(0x5),
|
||||||
|
FOLDER_NOT_EMPTY(0x6),
|
||||||
|
ALREADY_EXISTS(0x7),
|
||||||
|
FOLDER_EXISTS(0x8);
|
||||||
|
|
||||||
|
public final int code;
|
||||||
|
|
||||||
|
private FSCode(int code) { this.code = code; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String[] MESSAGES = {
|
||||||
|
"How did we get here?",
|
||||||
|
"The file or folder '%s' doesn't exist or is inaccessible.",
|
||||||
|
"'%s' is not a file",
|
||||||
|
"'%s' is not a folder",
|
||||||
|
"No permissions to read '%s'",
|
||||||
|
"No permissions to write '%s'",
|
||||||
|
"Can't delete '%s', since it is a full folder.",
|
||||||
|
"'%s' already exists."
|
||||||
|
};
|
||||||
|
|
||||||
|
public final String message, filename;
|
||||||
|
public final FSCode code;
|
||||||
|
|
||||||
|
public FilesystemException(String message, String filename, FSCode code) {
|
||||||
|
super(code + ": " + String.format(message, filename));
|
||||||
|
this.message = message;
|
||||||
|
this.code = code;
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
public FilesystemException(String filename, FSCode code) {
|
||||||
|
super(code + ": " + String.format(MESSAGES[code.code], filename));
|
||||||
|
this.message = MESSAGES[code.code];
|
||||||
|
this.code = code;
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EngineException toEngineException() {
|
||||||
|
var res = EngineException.ofError("IOError", getMessage());
|
||||||
|
Values.setMember(null, res.value, "code", code);
|
||||||
|
Values.setMember(null, res.value, "filename", filename.toString());
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class InaccessibleFile implements File {
|
|
||||||
public static final InaccessibleFile INSTANCE = new InaccessibleFile();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException, InterruptedException {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean write(byte val) throws IOException, InterruptedException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long tell() throws IOException, InterruptedException {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seek(long offset, int pos) throws IOException, InterruptedException { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException, InterruptedException { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Permissions perms() {
|
|
||||||
return Permissions.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InaccessibleFile() { }
|
|
||||||
}
|
|
||||||
@@ -1,53 +1,67 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import me.topchetoeu.jscript.Buffer;
|
||||||
|
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||||
|
|
||||||
public class MemoryFile implements File {
|
public class MemoryFile implements File {
|
||||||
private int ptr;
|
private int ptr;
|
||||||
private Permissions mode;
|
private Mode mode;
|
||||||
public final byte[] data;
|
private Buffer data;
|
||||||
|
private String filename;
|
||||||
|
|
||||||
|
public Buffer data() { return data; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException, InterruptedException {
|
public int read(byte[] buff) {
|
||||||
if (data == null || !mode.readable || ptr >= data.length) return -1;
|
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||||
return data[ptr++];
|
var res = data.read(ptr, buff);
|
||||||
|
ptr += res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buff) {
|
||||||
|
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
||||||
|
|
||||||
|
data.write(ptr, buff);
|
||||||
|
ptr += buff.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean write(byte val) throws IOException, InterruptedException {
|
public long getPtr() {
|
||||||
if (data == null || !mode.writable || ptr >= data.length) return false;
|
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||||
data[ptr++] = val;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long tell() throws IOException, InterruptedException {
|
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek(long offset, int pos) throws IOException, InterruptedException {
|
public void setPtr(long offset, int pos) {
|
||||||
if (data == null) return;
|
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||||
|
|
||||||
if (pos == 0) ptr = (int)offset;
|
if (pos == 0) ptr = (int)offset;
|
||||||
else if (pos == 1) ptr += (int)offset;
|
else if (pos == 1) ptr += (int)offset;
|
||||||
else if (pos == 2) ptr = data.length - (int)offset;
|
else if (pos == 2) ptr = data.length() - (int)offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException, InterruptedException {
|
public void close() {
|
||||||
mode = null;
|
mode = Mode.NONE;
|
||||||
ptr = 0;
|
ptr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Permissions perms() {
|
public Mode mode() {
|
||||||
if (data == null) return Permissions.NONE;
|
if (data == null) return Mode.NONE;
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryFile(byte[] buff, Permissions mode) {
|
public MemoryFile(String filename, Buffer buff, Mode mode) {
|
||||||
|
this.filename = filename;
|
||||||
this.data = buff;
|
this.data = buff;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
|
||||||
|
var res = new StringBuilder();
|
||||||
|
|
||||||
|
for (var el : list) res.append(el.getName()).append('\n');
|
||||||
|
|
||||||
|
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
90
src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java
Normal file
90
src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.Buffer;
|
||||||
|
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||||
|
|
||||||
|
public class MemoryFilesystem implements Filesystem {
|
||||||
|
public final Mode mode;
|
||||||
|
private HashMap<Path, Buffer> files = new HashMap<>();
|
||||||
|
private HashSet<Path> folders = new HashSet<>();
|
||||||
|
|
||||||
|
private Path getPath(String name) {
|
||||||
|
return Path.of("/" + name.replace("\\", "/")).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(String path, EntryType type) {
|
||||||
|
var _path = getPath(path);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case FILE:
|
||||||
|
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
|
||||||
|
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
|
||||||
|
if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
|
||||||
|
files.put(_path, new Buffer());
|
||||||
|
break;
|
||||||
|
case FOLDER:
|
||||||
|
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
|
||||||
|
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
|
||||||
|
folders.add(_path);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case NONE:
|
||||||
|
if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File open(String path, Mode perms) {
|
||||||
|
var _path = getPath(path);
|
||||||
|
var pcount = _path.getNameCount();
|
||||||
|
|
||||||
|
if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms);
|
||||||
|
else if (folders.contains(_path)) {
|
||||||
|
var res = new StringBuilder();
|
||||||
|
for (var folder : folders) {
|
||||||
|
if (pcount + 1 != folder.getNameCount()) continue;
|
||||||
|
if (!folder.startsWith(_path)) continue;
|
||||||
|
res.append(folder.toFile().getName()).append('\n');
|
||||||
|
}
|
||||||
|
for (var file : files.keySet()) {
|
||||||
|
if (pcount + 1 != file.getNameCount()) continue;
|
||||||
|
if (!file.startsWith(_path)) continue;
|
||||||
|
res.append(file.toFile().getName()).append('\n');
|
||||||
|
}
|
||||||
|
return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
|
||||||
|
}
|
||||||
|
else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileStat stat(String path) {
|
||||||
|
var _path = getPath(path);
|
||||||
|
|
||||||
|
if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE);
|
||||||
|
else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER);
|
||||||
|
else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryFilesystem put(String path, byte[] data) {
|
||||||
|
var _path = getPath(path);
|
||||||
|
var _curr = "/";
|
||||||
|
|
||||||
|
for (var seg : _path) {
|
||||||
|
create(_curr, EntryType.FOLDER);
|
||||||
|
_curr += seg + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
files.put(_path, new Buffer(data));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryFilesystem(Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
folders.add(Path.of("/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/me/topchetoeu/jscript/filesystem/Mode.java
Normal file
31
src/me/topchetoeu/jscript/filesystem/Mode.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
public enum Mode {
|
||||||
|
NONE("", false, false),
|
||||||
|
READ("r", true, false),
|
||||||
|
READ_WRITE("rw", true, true);
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
public final boolean readable;
|
||||||
|
public final boolean writable;
|
||||||
|
|
||||||
|
public Mode intersect(Mode other) {
|
||||||
|
if (this == NONE || other == NONE) return NONE;
|
||||||
|
if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE;
|
||||||
|
return READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mode(String mode, boolean r, boolean w) {
|
||||||
|
this.name = mode;
|
||||||
|
this.readable = r;
|
||||||
|
this.writable = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mode parse(String mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case "r": return READ;
|
||||||
|
case "rw": return READ_WRITE;
|
||||||
|
default: return NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
|
||||||
|
|
||||||
public enum Permissions {
|
|
||||||
NONE("", false, false),
|
|
||||||
READ("r", true, false),
|
|
||||||
READ_WRITE("rw", true, true);
|
|
||||||
|
|
||||||
public final String readMode;
|
|
||||||
public final boolean readable;
|
|
||||||
public final boolean writable;
|
|
||||||
|
|
||||||
private Permissions(String mode, boolean r, boolean w) {
|
|
||||||
this.readMode = mode;
|
|
||||||
this.readable = r;
|
|
||||||
this.writable = w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +1,62 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||||
|
|
||||||
public class PhysicalFile implements File {
|
public class PhysicalFile implements File {
|
||||||
|
private String filename;
|
||||||
private RandomAccessFile file;
|
private RandomAccessFile file;
|
||||||
private Permissions perms;
|
private Mode perms;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException, InterruptedException {
|
public int read(byte[] buff) {
|
||||||
if (file == null || !perms.readable) return -1;
|
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||||
else return file.read();
|
else try { return file.read(buff); }
|
||||||
|
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buff) {
|
||||||
|
if (file == null || !perms.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
|
||||||
|
else try { file.write(buff); }
|
||||||
|
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean write(byte val) throws IOException, InterruptedException {
|
public long getPtr() {
|
||||||
if (file == null || !perms.writable) return false;
|
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||||
file.write(val);
|
else try { return file.getFilePointer(); }
|
||||||
return true;
|
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setPtr(long offset, int pos) {
|
||||||
|
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (pos == 1) pos += file.getFilePointer();
|
||||||
|
else if (pos == 2) pos += file.length();
|
||||||
|
file.seek(pos);
|
||||||
|
}
|
||||||
|
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long tell() throws IOException, InterruptedException {
|
public void close() {
|
||||||
if (file == null) return 0;
|
|
||||||
return file.getFilePointer();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void seek(long offset, int pos) throws IOException, InterruptedException {
|
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
if (pos == 0) file.seek(pos);
|
try { file.close(); }
|
||||||
else if (pos == 1) file.seek(file.getFilePointer() + pos);
|
catch (IOException e) {} // SHUT
|
||||||
else if (pos == 2) file.seek(file.length() + pos);
|
file = null;
|
||||||
|
perms = Mode.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException, InterruptedException {
|
public Mode mode() { return perms; }
|
||||||
if (file == null) return;
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public PhysicalFile(String path, Mode mode) throws FileNotFoundException {
|
||||||
public Permissions perms() { return perms; }
|
if (mode == Mode.NONE) file = null;
|
||||||
|
else try { file = new RandomAccessFile(path, mode.name); }
|
||||||
public PhysicalFile(String path, Permissions mode) throws IOException {
|
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
|
||||||
if (mode == Permissions.NONE) file = null;
|
|
||||||
else file = new RandomAccessFile(path, mode.readMode);
|
|
||||||
|
|
||||||
perms = mode;
|
perms = mode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
package me.topchetoeu.jscript.filesystem;
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||||
|
|
||||||
public class PhysicalFilesystem implements Filesystem {
|
public class PhysicalFilesystem implements Filesystem {
|
||||||
public final Path root;
|
public final Path root;
|
||||||
|
|
||||||
private Permissions getPerms(Path path) {
|
|
||||||
var file = path.toFile();
|
|
||||||
if (!path.startsWith(root)) return Permissions.NONE;
|
|
||||||
if (file.canRead() && file.canWrite()) return Permissions.READ_WRITE;
|
|
||||||
if (file.canRead()) return Permissions.READ;
|
|
||||||
|
|
||||||
return Permissions.NONE;
|
|
||||||
}
|
|
||||||
private Path getPath(String name) {
|
private Path getPath(String name) {
|
||||||
return root.resolve(name);
|
return root.resolve(name.replace("\\", "/")).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMode(Path path, Mode mode) {
|
||||||
|
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
|
||||||
|
if (mode.readable && !path.toFile().canRead()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
|
||||||
|
if (mode.writable && !path.toFile().canWrite()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File open(String path) throws IOException, InterruptedException {
|
public File open(String path, Mode perms) {
|
||||||
var _path = root.resolve(path);
|
var _path = getPath(path);
|
||||||
|
|
||||||
var perms = getPerms(_path);
|
|
||||||
if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE;
|
|
||||||
|
|
||||||
var f = _path.toFile();
|
var f = _path.toFile();
|
||||||
|
|
||||||
if (f.isDirectory()) {
|
checkMode(_path, perms);
|
||||||
var res = new StringBuilder();
|
|
||||||
|
|
||||||
for (var child : f.listFiles()) res.append(child.toString()).append('\n');
|
|
||||||
|
|
||||||
return new MemoryFile(res.toString().getBytes(), Permissions.READ);
|
if (f.isDirectory()) return MemoryFile.fromFileList(path, f.listFiles());
|
||||||
|
else try { return new PhysicalFile(path, perms); }
|
||||||
|
catch (FileNotFoundException e) { throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(String path, EntryType type) {
|
||||||
|
var _path = getPath(path);
|
||||||
|
var f = _path.toFile();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case FILE:
|
||||||
|
try {
|
||||||
|
if (!f.createNewFile()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS);
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
catch (IOException e) { throw new FilesystemException(_path.toString(), FSCode.NO_PERMISSIONS_RW); }
|
||||||
|
case FOLDER:
|
||||||
|
if (!f.mkdir()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS);
|
||||||
|
else break;
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
if (!f.delete()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
|
||||||
|
else break;
|
||||||
}
|
}
|
||||||
else return new PhysicalFile(path, perms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean mkdir(String path) throws IOException, InterruptedException {
|
public FileStat stat(String path) {
|
||||||
var _path = getPath(path);
|
var _path = getPath(path);
|
||||||
var perms = getPerms(_path);
|
|
||||||
var f = _path.toFile();
|
var f = _path.toFile();
|
||||||
|
|
||||||
if (!perms.writable) return false;
|
if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
|
||||||
else return f.mkdir();
|
checkMode(_path, Mode.READ);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return new FileStat(
|
||||||
public EntryType type(String path) throws IOException, InterruptedException {
|
f.canWrite() ? Mode.READ_WRITE : Mode.READ,
|
||||||
var _path = getPath(path);
|
f.isFile() ? EntryType.FILE : EntryType.FOLDER
|
||||||
var perms = getPerms(_path);
|
);
|
||||||
var f = _path.toFile();
|
|
||||||
|
|
||||||
if (perms == Permissions.NONE) return EntryType.NONE;
|
|
||||||
else if (f.isFile()) return EntryType.FILE;
|
|
||||||
else return EntryType.FOLDER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean rm(String path) throws IOException, InterruptedException {
|
|
||||||
var _path = getPath(path);
|
|
||||||
var perms = getPerms(_path);
|
|
||||||
var f = _path.toFile();
|
|
||||||
|
|
||||||
if (!perms.writable) return false;
|
|
||||||
else return f.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhysicalFilesystem(Path root) {
|
public PhysicalFilesystem(Path root) {
|
||||||
this.root = root;
|
this.root = root.toAbsolutePath().normalize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/me/topchetoeu/jscript/filesystem/RootFilesystem.java
Normal file
57
src/me/topchetoeu/jscript/filesystem/RootFilesystem.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package me.topchetoeu.jscript.filesystem;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.Filename;
|
||||||
|
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||||
|
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||||
|
|
||||||
|
public class RootFilesystem implements Filesystem {
|
||||||
|
public final Map<String, Filesystem> protocols = new HashMap<>();
|
||||||
|
public final PermissionsProvider perms;
|
||||||
|
|
||||||
|
private boolean canRead(String _path) {
|
||||||
|
return perms.hasPermission("jscript.file.read:" + _path, '/');
|
||||||
|
}
|
||||||
|
private boolean canWrite(String _path) {
|
||||||
|
return perms.hasPermission("jscript.file.write:" + _path, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void modeAllowed(String _path, Mode mode) throws FilesystemException {
|
||||||
|
if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R);
|
||||||
|
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public File open(String path, Mode perms) throws FilesystemException {
|
||||||
|
var filename = Filename.parse(path);
|
||||||
|
var protocol = protocols.get(filename.protocol);
|
||||||
|
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
||||||
|
modeAllowed(filename.toString(), perms);
|
||||||
|
|
||||||
|
try { return protocol.open(filename.path, perms); }
|
||||||
|
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
||||||
|
}
|
||||||
|
@Override public void create(String path, EntryType type) throws FilesystemException {
|
||||||
|
var filename = Filename.parse(path);
|
||||||
|
var protocol = protocols.get(filename.protocol);
|
||||||
|
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
||||||
|
modeAllowed(filename.toString(), Mode.READ_WRITE);
|
||||||
|
|
||||||
|
try { protocol.create(filename.path, type); }
|
||||||
|
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
||||||
|
}
|
||||||
|
@Override public FileStat stat(String path) throws FilesystemException {
|
||||||
|
var filename = Filename.parse(path);
|
||||||
|
var protocol = protocols.get(filename.protocol);
|
||||||
|
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
|
||||||
|
modeAllowed(filename.toString(), Mode.READ);
|
||||||
|
|
||||||
|
try { return protocol.stat(path); }
|
||||||
|
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public RootFilesystem(PermissionsProvider perms) {
|
||||||
|
this.perms = perms;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
|
|
||||||
var val = target.values.get(name);
|
var val = target.values.get(name);
|
||||||
|
|
||||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()));
|
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()), true, true, false);
|
||||||
|
|
||||||
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
|
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
else getter = new OverloadFunction("get " + name);
|
else getter = new OverloadFunction("get " + name);
|
||||||
|
|
||||||
getter.add(Overload.fromMethod(method, get.thisArg()));
|
getter.add(Overload.fromMethod(method, get.thisArg()));
|
||||||
target.defineProperty(null, name, getter, setter, true, true);
|
target.defineProperty(null, name, getter, setter, true, false);
|
||||||
}
|
}
|
||||||
if (set != null) {
|
if (set != null) {
|
||||||
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
|
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
|
||||||
@@ -70,7 +70,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
else setter = new OverloadFunction("set " + name);
|
else setter = new OverloadFunction("set " + name);
|
||||||
|
|
||||||
setter.add(Overload.fromMethod(method, set.thisArg()));
|
setter.add(Overload.fromMethod(method, set.thisArg()));
|
||||||
target.defineProperty(null, name, getter, setter, true, true);
|
target.defineProperty(null, name, getter, setter, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,12 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getName(Class<?> clazz) {
|
||||||
|
var classNat = clazz.getAnnotation(Native.class);
|
||||||
|
if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim();
|
||||||
|
else return clazz.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a prototype for the given class.
|
* Generates a prototype for the given class.
|
||||||
* The returned object will have appropriate wrappers for all instance members.
|
* The returned object will have appropriate wrappers for all instance members.
|
||||||
@@ -117,6 +123,8 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
|
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
|
|
||||||
|
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
|
||||||
|
|
||||||
for (var overload : clazz.getDeclaredMethods()) {
|
for (var overload : clazz.getDeclaredMethods()) {
|
||||||
var init = overload.getAnnotation(NativeInit.class);
|
var init = overload.getAnnotation(NativeInit.class);
|
||||||
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
||||||
@@ -137,7 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
* @param clazz The class for which a constructor should be generated
|
* @param clazz The class for which a constructor should be generated
|
||||||
*/
|
*/
|
||||||
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
||||||
FunctionValue func = new OverloadFunction(clazz.getName());
|
FunctionValue func = new OverloadFunction(getName(clazz));
|
||||||
|
|
||||||
for (var overload : clazz.getDeclaredConstructors()) {
|
for (var overload : clazz.getDeclaredConstructors()) {
|
||||||
var nat = overload.getAnnotation(Native.class);
|
var nat = overload.getAnnotation(Native.class);
|
||||||
@@ -157,7 +165,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (((OverloadFunction)func).overloads.size() == 0) {
|
if (((OverloadFunction)func).overloads.size() == 0) {
|
||||||
func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
|
func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
|
||||||
}
|
}
|
||||||
|
|
||||||
applyMethods(ctx, false, func, clazz);
|
applyMethods(ctx, false, func, clazz);
|
||||||
@@ -211,8 +219,8 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
if (constr == null) constr = makeConstructor(env, clazz);
|
if (constr == null) constr = makeConstructor(env, clazz);
|
||||||
if (proto == null) proto = makeProto(env, clazz);
|
if (proto == null) proto = makeProto(env, clazz);
|
||||||
|
|
||||||
proto.values.put("constructor", constr);
|
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||||
constr.values.put("prototype", proto);
|
constr.defineProperty(null, "prototype", proto, true, false, false);
|
||||||
|
|
||||||
prototypes.put(clazz, proto);
|
prototypes.put(clazz, proto);
|
||||||
constructors.put(clazz, constr);
|
constructors.put(clazz, constr);
|
||||||
@@ -240,6 +248,11 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
return constructors.get(clazz);
|
return constructors.get(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappersProvider fork(Environment env) {
|
||||||
|
return new NativeWrapperProvider(env);
|
||||||
|
}
|
||||||
|
|
||||||
public void setProto(Class<?> clazz, ObjectValue value) {
|
public void setProto(Class<?> clazz, ObjectValue value) {
|
||||||
prototypes.put(clazz, value);
|
prototypes.put(clazz, value);
|
||||||
}
|
}
|
||||||
@@ -247,7 +260,27 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
constructors.put(clazz, value);
|
constructors.put(clazz, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initError() {
|
||||||
|
var proto = new ObjectValue();
|
||||||
|
proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> {
|
||||||
|
if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage();
|
||||||
|
else return null;
|
||||||
|
}));
|
||||||
|
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
|
||||||
|
|
||||||
|
var constr = makeConstructor(null, Throwable.class);
|
||||||
|
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||||
|
constr.defineProperty(null, "prototype", proto, true, false, false);
|
||||||
|
|
||||||
|
proto.setPrototype(null, getProto(Object.class));
|
||||||
|
constr.setPrototype(null, getConstr(Object.class));
|
||||||
|
|
||||||
|
setProto(Throwable.class, proto);
|
||||||
|
setConstr(Throwable.class, constr);
|
||||||
|
}
|
||||||
|
|
||||||
public NativeWrapperProvider(Environment env) {
|
public NativeWrapperProvider(Environment env) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
|
initError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,11 @@ public class Overload {
|
|||||||
public static Overload setterFromField(Field field) {
|
public static Overload setterFromField(Field field) {
|
||||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
if (Modifier.isFinal(field.getModifiers())) return null;
|
||||||
return new Overload(
|
return new Overload(
|
||||||
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false, false,
|
(ctx, th, args) -> {
|
||||||
|
field.set(th, args[0]); return null;
|
||||||
|
}, false, false,
|
||||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||||
new Class[0]
|
new Class[] { field.getType() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ 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.values.FunctionValue;
|
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||||
|
|
||||||
public class OverloadFunction extends FunctionValue {
|
public class OverloadFunction extends FunctionValue {
|
||||||
public final List<Overload> overloads = new ArrayList<>();
|
public final List<Overload> overloads = new ArrayList<>();
|
||||||
@@ -81,17 +83,28 @@ public class OverloadFunction extends FunctionValue {
|
|||||||
catch (InvocationTargetException e) {
|
catch (InvocationTargetException e) {
|
||||||
var loc = Location.INTERNAL;
|
var loc = Location.INTERNAL;
|
||||||
if (e.getTargetException() instanceof EngineException) {
|
if (e.getTargetException() instanceof EngineException) {
|
||||||
throw ((EngineException)e.getTargetException()).add(name, loc);
|
throw ((EngineException)e.getTargetException()).add(ctx, name, loc);
|
||||||
}
|
}
|
||||||
else if (e.getTargetException() instanceof NullPointerException) {
|
else if (e.getTargetException() instanceof NullPointerException) {
|
||||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
|
e.printStackTrace();
|
||||||
|
throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, loc);
|
||||||
|
}
|
||||||
|
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||||
|
throw new InterruptException();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc);
|
var target = e.getTargetException();
|
||||||
|
var targetClass = target.getClass();
|
||||||
|
var err = new NativeWrapper(e.getTargetException());
|
||||||
|
|
||||||
|
err.defineProperty(ctx, "message", target.getMessage());
|
||||||
|
err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass));
|
||||||
|
|
||||||
|
throw new EngineException(err).add(ctx, name, loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ReflectiveOperationException e) {
|
catch (ReflectiveOperationException e) {
|
||||||
throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL);
|
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user