Compare commits
178 Commits
v0.2.3-alp
...
v0.8.7-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
285960bdd6
|
|||
|
cf99845f6b
|
|||
|
48bd1e2015
|
|||
|
304665904f
|
|||
|
56ae3a85a6
|
|||
|
0178cb2194
|
|||
|
a2cb5cd473
|
|||
|
c123427e77
|
|||
|
7ac5ded185
|
|||
|
769d6ae8fc
|
|||
|
afb99ffc70
|
|||
|
46136e77e2
|
|||
|
b460b87318
|
|||
|
e772f0b50d
|
|||
|
187ad55291
|
|||
|
8156a1733f
|
|||
|
d1937fdb63
|
|||
|
3f826cc85d
|
|||
|
af35d7f20b
|
|||
|
cfa0e001b9
|
|||
|
c10d071346
|
|||
|
89eea7d62b
|
|||
| 18d22a1282 | |||
|
72a0d39d0b
|
|||
|
d8585a20bf
|
|||
|
e4c9a8756e
|
|||
|
c6e6425c7e
|
|||
|
292ca64cb9
|
|||
|
4572db5c46
|
|||
|
0251c4689d
|
|||
|
3173919b49
|
|||
|
45f133c6b0
|
|||
|
34276d720c
|
|||
|
2c634778c3
|
|||
|
4aa757e625
|
|||
|
918f2623cd
|
|||
|
a321fc14bc
|
|||
|
07a6f18b16
|
|||
|
5f4011aa0c
|
|||
|
71f735b812
|
|||
|
e575b3287e
|
|||
|
4fa5f5a815
|
|||
|
a61c6a494e
|
|||
|
978ee8db79
|
|||
|
e372941e99
|
|||
|
c36a0db860
|
|||
|
d6ee59363f
|
|||
|
d5fd6e650e
|
|||
|
c0b895e00a
|
|||
|
9ea5cd9277
|
|||
|
aaf9a6fa45
|
|||
|
579f09c837
|
|||
|
3343262e72
|
|||
|
153a1a9a49
|
|||
|
bf38587271
|
|||
|
21534efd60
|
|||
|
802f2f3f52
|
|||
|
38acc20a6f
|
|||
|
d7f6010319
|
|||
|
87f8975275
|
|||
| 09eb6507dc | |||
|
2f58f6b245
|
|||
|
4bc363485f
|
|||
|
8e01db637b
|
|||
|
1c64912786
|
|||
|
28265a8f44
|
|||
|
e9e020512e
|
|||
|
4b0bbf5190
|
|||
|
031f78ebf1
|
|||
|
562f1f9425
|
|||
|
82a09e8865
|
|||
|
90da2db1fb
|
|||
|
3d275c52c0
|
|||
|
797585f539
|
|||
|
7a301eba8f
|
|||
|
1b2068a274
|
|||
|
078d7ed95f
|
|||
|
93973c12b1
|
|||
|
cad4f34b51
|
|||
|
d3571d6ee2
|
|||
|
caf9131cde
|
|||
| 8c6379eb24 | |||
|
380a5c720a
|
|||
|
76c3d377af
|
|||
|
42f443572a
|
|||
|
773bc72f3e
|
|||
|
0b5178e9fd
|
|||
|
8cffcff7db
|
|||
|
60bbaaccd4
|
|||
|
60b1762462
|
|||
|
34434965d2
|
|||
|
fe86123f0f
|
|||
|
d5e6edfa8b
|
|||
|
73345062ca
|
|||
|
124341969c
|
|||
|
8defd93855
|
|||
|
6c57e0e9f2
|
|||
|
f1932914ee
|
|||
|
977701e601
|
|||
|
e8a7ac8da8
|
|||
|
6b1cb852c2
|
|||
|
b59a003086
|
|||
|
1902e41f61
|
|||
|
27162ef8ac
|
|||
|
4f22e76d2b
|
|||
|
8924e7aadc
|
|||
| 1d0e31a423 | |||
|
ab56908171
|
|||
|
eb14bb080c
|
|||
|
f52f47cdb4
|
|||
|
567eaa8514
|
|||
|
2cfdd8e335
|
|||
|
4b1ec671e2
|
|||
|
b127aadcf6
|
|||
|
b6eaff65ca
|
|||
|
443dc0ffa1
|
|||
|
e107dd3507
|
|||
|
6af3c70fce
|
|||
|
8b743f49d1
|
|||
|
e1ce384815
|
|||
|
86d205e521
|
|||
|
f0ad936e5b
|
|||
|
4a1473c5be
|
|||
|
4111dbf5c4
|
|||
|
1666682dc2
|
|||
|
f2b33d0233
|
|||
|
f5a0b6eaf7
|
|||
|
829bea755d
|
|||
|
4b0dcffd13
|
|||
|
987f8b8f00
|
|||
|
55e3d46bc2
|
|||
|
3e25068219
|
|||
|
7ecb8bfabb
|
|||
|
488deea164
|
|||
|
ed08041335
|
|||
|
0a4149ba81
|
|||
|
30f5d619c3
|
|||
|
e7dbe91374
|
|||
|
455f5a613e
|
|||
|
1eeac3ae97
|
|||
|
1acd78e119
|
|||
|
df9932874d
|
|||
|
b47d1a7576
|
|||
|
fdfa8d7713
|
|||
|
f5d1287948
|
|||
|
15f4278cb1
|
|||
| df8465cb49 | |||
|
e3104c223c
|
|||
|
1d0bae3de8
|
|||
|
b66acd3089
|
|||
|
e326847287
|
|||
|
26591d6631
|
|||
|
af31b1ab79
|
|||
|
f885d4349f
|
|||
|
d57044acb7
|
|||
|
7df4e3b03f
|
|||
|
ed1009ab69
|
|||
|
f856cdf37e
|
|||
|
4f82574b8c
|
|||
|
0ae24148d8
|
|||
|
ac128d17f4
|
|||
|
6508f15bb0
|
|||
|
69f93b4f87
|
|||
|
b675411925
|
|||
|
d1e93c2088
|
|||
|
942db54546
|
|||
|
d20df66982
|
|||
| 16a9e5d761 | |||
|
dc9d84a370
|
|||
|
edb71daef4
|
|||
|
4b84309df6
|
|||
|
d2d9fa9738
|
|||
|
a4e5f7f471
|
|||
|
cc044374ba
|
|||
|
517e3e6657
|
|||
|
926b9c17d8
|
|||
|
fc705e7383
|
|||
|
a17ec737b7
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* -text
|
||||
15
.github/workflows/tagged-release.yml
vendored
15
.github/workflows/tagged-release.yml
vendored
@@ -11,15 +11,22 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Clone repository
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||
with:
|
||||
branch: 'master' # fuck this political bullshitshit, took me an hour to fix this
|
||||
branch: 'master'
|
||||
owner: 'TopchetoEU'
|
||||
repository: 'java-jscript'
|
||||
- name: "Build"
|
||||
- name: Build
|
||||
run: |
|
||||
cd java-jscript; node ./build.js release ${{ github.ref }}
|
||||
cd java-jscript; gradle build
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
@@ -27,4 +34,4 @@ jobs:
|
||||
prerelease: false
|
||||
files: |
|
||||
java-jscript/LICENSE
|
||||
java-jscript/dst/*.jar
|
||||
java-jscript/build/libs/*.jar
|
||||
35
.gitignore
vendored
35
.gitignore
vendored
@@ -1,11 +1,24 @@
|
||||
.vscode
|
||||
.gradle
|
||||
.ignore
|
||||
/out
|
||||
/build
|
||||
/bin
|
||||
/dst
|
||||
/*.js
|
||||
!/build.js
|
||||
/dead-code
|
||||
/Metadata.java
|
||||
*
|
||||
|
||||
!/src
|
||||
!/src/**/*
|
||||
|
||||
!/doc
|
||||
!/doc/**/*
|
||||
|
||||
!/tests
|
||||
!/tests/**/*
|
||||
|
||||
!/.github
|
||||
!/.github/**/*
|
||||
|
||||
!/.gitignore
|
||||
!/.gitattributes
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
!/settings.gradle
|
||||
!/build.gradle
|
||||
!/gradle.properties
|
||||
!/gradle
|
||||
!/gradle/wrapper
|
||||
!/gradle/wrapper/gradle-wrapper.properties
|
||||
37
README.md
37
README.md
@@ -2,33 +2,28 @@
|
||||
|
||||
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
|
||||
|
||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
||||
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
|
||||
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||
|
||||
## Example
|
||||
|
||||
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
|
||||
The following is going to execute a simple javascript statement:
|
||||
|
||||
```java
|
||||
var engine = new PolyfillEngine(new File("."));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine.start();
|
||||
var engine = new Engine();
|
||||
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
|
||||
var env = Internals.apply(new Environment());
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
// Queue code to load internal libraries and start engine
|
||||
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
|
||||
// Run the engine on the same thread, until the event loop runs empty
|
||||
engine.run(true);
|
||||
|
||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
||||
Values.printValue(engine.context(), res);
|
||||
System.out.println();
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
}
|
||||
catch (InterruptedException _e) { return; }
|
||||
}
|
||||
catch (IOException | InterruptedException e) { return; }
|
||||
}
|
||||
// Get our result
|
||||
System.out.println(awaitable.await());
|
||||
```
|
||||
|
||||
## NOTE:
|
||||
|
||||
To setup the typescript bundle in your sources, run `node build.js init-ts`. This will download the latest version of typescript, minify it, and add it to your src folder. If you are going to work with the `node build.js debug|release` command, this is not a necessary step.
|
||||
|
||||
32
build.gradle
Normal file
32
build.gradle
Normal file
@@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
id "application"
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(11)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest.attributes["Main-class"] = project.main_class
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs = [ "src/java" ]
|
||||
main.resources.srcDirs = [ "src/assets" ]
|
||||
}
|
||||
|
||||
processResources {
|
||||
filesMatching "metadata.json", {
|
||||
expand(
|
||||
version: project.project_version,
|
||||
name: project.project_name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
base.archivesName = project.project_name
|
||||
version = project.project_version
|
||||
group = project.project_group
|
||||
79
build.js
79
build.js
@@ -1,79 +0,0 @@
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs/promises');
|
||||
const pt = require('path');
|
||||
const { argv } = require('process');
|
||||
|
||||
const conf = {
|
||||
name: "java-jscript",
|
||||
author: "TopchetoEU",
|
||||
javahome: "",
|
||||
version: argv[3]
|
||||
};
|
||||
|
||||
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
|
||||
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
|
||||
|
||||
async function* find(src, dst, wildcard) {
|
||||
const stat = await fs.stat(src);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
for (const el of await fs.readdir(src)) {
|
||||
for await (const res of find(pt.join(src, el), dst ? pt.join(dst, el) : undefined, wildcard)) yield res;
|
||||
}
|
||||
}
|
||||
else if (stat.isFile() && wildcard(src)) yield dst ? { src, dst } : src;
|
||||
}
|
||||
async function copy(src, dst, wildcard) {
|
||||
const promises = [];
|
||||
|
||||
for await (const el of find(src, dst, wildcard)) {
|
||||
promises.push((async () => {
|
||||
await fs.mkdir(pt.dirname(el.dst), { recursive: true });
|
||||
await fs.copyFile(el.src, el.dst);
|
||||
})());
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
function run(cmd, ...args) {
|
||||
return new Promise((res, rej) => {
|
||||
const proc = spawn(cmd, args, { stdio: 'inherit' });
|
||||
proc.once('exit', code => {
|
||||
if (code === 0) res(code);
|
||||
else rej(new Error(`Process ${cmd} exited with code ${code}.`));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function compileJava() {
|
||||
try {
|
||||
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
|
||||
.replace('${VERSION}', conf.version)
|
||||
.replace('${NAME}', conf.name)
|
||||
.replace('${AUTHOR}', conf.author)
|
||||
);
|
||||
const args = ['--release', '11', ];
|
||||
if (argv[2] === 'debug') args.push('-g');
|
||||
args.push('-d', 'dst/classes', 'Metadata.java');
|
||||
|
||||
for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path);
|
||||
await run(conf.javahome + 'javac', ...args);
|
||||
}
|
||||
finally {
|
||||
await fs.rm('Metadata.java');
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
try { await fs.rm('dst', { recursive: true }); } catch {}
|
||||
await copy('src', 'dst/classes', v => !v.endsWith('.java'));
|
||||
await compileJava();
|
||||
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
|
||||
}
|
||||
catch (e) {
|
||||
if (argv[2] === 'debug') throw e;
|
||||
else console.log(e.toString());
|
||||
}
|
||||
})();
|
||||
4
gradle.properties
Normal file
4
gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
project_group = me.topchetoeu
|
||||
project_name = jscript
|
||||
project_version = 0.8.6-beta
|
||||
main_class = me.topchetoeu.jscript.utils.JScriptRepl
|
||||
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
5
settings.gradle
Normal file
5
settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
|
||||
}
|
||||
|
||||
rootProject.name = properties.project_name
|
||||
BIN
src/assets/debugger/favicon.png
Normal file
BIN
src/assets/debugger/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
30
src/assets/debugger/index.html
Normal file
30
src/assets/debugger/index.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JScript Debugger</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
This is the debugger of JScript. It implement the <a href="https://chromedevtools.github.io/devtools-protocol/1-2/">V8 Debugging protocol</a>,
|
||||
so you can use the devtools in chrome. <br>
|
||||
The debugger is still in early development, so please report any issues to
|
||||
<a href="https://github.com/TopchetoEU/java-jscript/issues">the github repo</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here are the available entrypoints:
|
||||
<ul>
|
||||
<li><a href="json/version">/json/version</a> - version and other stuff about the JScript engine</li>
|
||||
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
|
||||
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
|
||||
<li>/(any target) - websocket entrypoints for debugging</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Running ${NAME} v${VERSION} by ${AUTHOR}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
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
5
src/assets/metadata.json
Normal file
5
src/assets/metadata.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "${version}",
|
||||
"name": "${name}",
|
||||
"author": "TopchetoEU"
|
||||
}
|
||||
53
src/java/me/topchetoeu/jscript/common/Buffer.java
Normal file
53
src/java/me/topchetoeu/jscript/common/Buffer.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
57
src/java/me/topchetoeu/jscript/common/Filename.java
Normal file
57
src/java/me/topchetoeu/jscript/common/Filename.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class Filename {
|
||||
public final String protocol;
|
||||
public final String path;
|
||||
|
||||
@Override public String toString() {
|
||||
return protocol + "://" + path;
|
||||
}
|
||||
@Override public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + protocol.hashCode();
|
||||
result = prime * result + path.hashCode();
|
||||
return result;
|
||||
}
|
||||
@Override public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
|
||||
var other = (Filename) obj;
|
||||
|
||||
if (protocol == null) {
|
||||
if (other.protocol != null) return false;
|
||||
}
|
||||
else if (!protocol.equals(other.protocol)) return false;
|
||||
|
||||
if (path == null) {
|
||||
if (other.path != null) return false;
|
||||
}
|
||||
else if (!path.equals(other.path)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Filename(String protocol, String path) {
|
||||
path = path.trim();
|
||||
protocol = protocol.trim();
|
||||
this.protocol = protocol;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static Filename parse(String val) {
|
||||
var i = val.indexOf("://");
|
||||
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
|
||||
else return new Filename("file", val.trim());
|
||||
}
|
||||
public static Path normalize(String path) {
|
||||
return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
|
||||
}
|
||||
public static Filename fromFile(File file) {
|
||||
return new Filename("file", file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package me.topchetoeu.jscript;
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
public class Location {
|
||||
public static final Location INTERNAL = new Location(0, 0, "<internal>");
|
||||
public class Location implements Comparable<Location> {
|
||||
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
|
||||
private int line;
|
||||
private int start;
|
||||
private String filename;
|
||||
private Filename filename;
|
||||
|
||||
public int line() { return line; }
|
||||
public int start() { return start; }
|
||||
public String filename() { return filename; }
|
||||
public Filename filename() { return filename; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return filename + ":" + line + ":" + start;
|
||||
return filename.toString() + ":" + line + ":" + start;
|
||||
}
|
||||
|
||||
public Location add(int n, boolean clone) {
|
||||
@@ -55,9 +55,39 @@ public class Location {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Location(int line, int start, String filename) {
|
||||
@Override
|
||||
public int compareTo(Location other) {
|
||||
int a = filename.toString().compareTo(other.filename.toString());
|
||||
int b = Integer.compare(line, other.line);
|
||||
int c = Integer.compare(start, other.start);
|
||||
|
||||
if (a != 0) return a;
|
||||
if (b != 0) return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
public Location(int line, int start, Filename filename) {
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public static Location parse(String raw) {
|
||||
int i0 = -1, i1 = -1;
|
||||
for (var i = raw.length() - 1; i >= 0; i--) {
|
||||
if (raw.charAt(i) == ':') {
|
||||
if (i1 == -1) i1 = i;
|
||||
else if (i0 == -1) {
|
||||
i0 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Location(
|
||||
Integer.parseInt(raw.substring(i0 + 1, i1)),
|
||||
Integer.parseInt(raw.substring(i1 + 1)),
|
||||
Filename.parse(raw.substring(0, i0))
|
||||
);
|
||||
}
|
||||
}
|
||||
29
src/java/me/topchetoeu/jscript/common/Metadata.java
Normal file
29
src/java/me/topchetoeu/jscript/common/Metadata.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import me.topchetoeu.jscript.common.json.JSON;
|
||||
|
||||
public class Metadata {
|
||||
private static final String VERSION;
|
||||
private static final String AUTHOR;
|
||||
private static final String NAME;
|
||||
|
||||
static {
|
||||
var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map();
|
||||
VERSION = data.string("version");
|
||||
AUTHOR = data.string("author");
|
||||
NAME = data.string("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;
|
||||
}
|
||||
}
|
||||
33
src/java/me/topchetoeu/jscript/common/Reading.java
Normal file
33
src/java/me/topchetoeu/jscript/common/Reading.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
public class Reading {
|
||||
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
|
||||
public static synchronized String readline() throws IOException {
|
||||
return reader.readLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given stream to a string
|
||||
* @param in
|
||||
* @return
|
||||
*/
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
return new String(in.readAllBytes());
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
}
|
||||
public static InputStream resourceToStream(String name) {
|
||||
return Reading.class.getResourceAsStream("/" + name);
|
||||
}
|
||||
public static String resourceToString(String name) {
|
||||
return streamToString(resourceToStream(name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
public interface ResultRunnable<T> {
|
||||
T run();
|
||||
}
|
||||
95
src/java/me/topchetoeu/jscript/common/VLQ.java
Normal file
95
src/java/me/topchetoeu/jscript/common/VLQ.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VLQ {
|
||||
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
private static long[] toArray(List<Long> list) {
|
||||
var arr = new long[list.size()];
|
||||
for (var i = 0; i < list.size(); i++) arr[i] = list.get(i);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static String encode(long... arr) {
|
||||
var raw = new StringBuilder();
|
||||
|
||||
for (var data : arr) {
|
||||
var b = data < 0 ? 1 : 0;
|
||||
data = Math.abs(data);
|
||||
b |= (int)(data & 0b1111) << 1;
|
||||
data >>= 4;
|
||||
b |= data > 0 ? 0x20 : 0;;
|
||||
raw.append(ALPHABET.charAt(b));
|
||||
|
||||
while (data > 0) {
|
||||
b = (int)(data & 0b11111);
|
||||
data >>= 5;
|
||||
b |= data > 0 ? 0x20 : 0;
|
||||
raw.append(ALPHABET.charAt(b));
|
||||
}
|
||||
}
|
||||
|
||||
return raw.toString();
|
||||
}
|
||||
public static long[] decode(String val) {
|
||||
if (val.length() == 0) return new long[0];
|
||||
|
||||
var list = new ArrayList<Long>();
|
||||
|
||||
for (var i = 0; i < val.length();) {
|
||||
var sign = 1;
|
||||
var curr = ALPHABET.indexOf(val.charAt(i++));
|
||||
var cont = (curr & 0x20) == 0x20;
|
||||
if ((curr & 1) == 1) sign = -1;
|
||||
long res = (curr & 0b11110) >> 1;
|
||||
var n = 4;
|
||||
|
||||
for (; i < val.length() && cont;) {
|
||||
curr = ALPHABET.indexOf(val.charAt(i++));
|
||||
cont = (curr & 0x20) == 0x20;
|
||||
res |= (curr & 0b11111) << n;
|
||||
n += 5;
|
||||
if (!cont) break;
|
||||
}
|
||||
|
||||
list.add(res * sign);
|
||||
}
|
||||
|
||||
return toArray(list);
|
||||
}
|
||||
|
||||
public static String encodeMapping(long[][][] arr) {
|
||||
var res = new StringBuilder();
|
||||
var semicolon = false;
|
||||
|
||||
for (var line : arr) {
|
||||
var comma = false;
|
||||
|
||||
if (semicolon) res.append(";");
|
||||
semicolon = true;
|
||||
|
||||
for (var el : line) {
|
||||
if (comma) res.append(",");
|
||||
comma = true;
|
||||
res.append(encode(el));
|
||||
}
|
||||
}
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
public static long[][][] decodeMapping(String val) {
|
||||
var lines = new ArrayList<long[][]>();
|
||||
|
||||
for (var line : val.split(";", -1)) {
|
||||
var elements = new ArrayList<long[]>();
|
||||
for (var el : line.split(",", -1)) {
|
||||
elements.add(decode(el));
|
||||
}
|
||||
lines.add(elements.toArray(long[][]::new));
|
||||
}
|
||||
|
||||
return lines.toArray(long[][][]::new);
|
||||
}
|
||||
}
|
||||
27
src/java/me/topchetoeu/jscript/common/events/Awaitable.java
Normal file
27
src/java/me/topchetoeu/jscript/common/events/Awaitable.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.common.events;
|
||||
|
||||
public interface Awaitable<T> {
|
||||
public static interface ResultHandler<T> {
|
||||
public void onResult(T data);
|
||||
}
|
||||
public static interface ErrorHandler {
|
||||
public void onError(RuntimeException error);
|
||||
}
|
||||
|
||||
T await();
|
||||
|
||||
default void handle(ResultHandler<T> onResult, ErrorHandler onError) {
|
||||
var thread = new Thread(() -> {
|
||||
try {
|
||||
onResult.onResult(await());
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
onError.onError(e);
|
||||
}
|
||||
}, "Awaiter");
|
||||
thread.start();
|
||||
}
|
||||
default void handle(ResultHandler<T> onResult) {
|
||||
handle(onResult, err -> {});
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,31 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public class DataNotifier<T> implements Awaitable<T> {
|
||||
private Notifier notifier = new Notifier();
|
||||
private boolean isErr;
|
||||
private T val;
|
||||
private RuntimeException err;
|
||||
|
||||
public void error(RuntimeException t) {
|
||||
err = t;
|
||||
isErr = true;
|
||||
notifier.next();
|
||||
}
|
||||
public void error(Throwable t) {
|
||||
error(new RuntimeException(t));
|
||||
}
|
||||
public void next(T val) {
|
||||
this.val = val;
|
||||
isErr = false;
|
||||
notifier.next();
|
||||
}
|
||||
public T await() throws InterruptedException {
|
||||
notifier.await();
|
||||
|
||||
try {
|
||||
if (isErr) throw err;
|
||||
else return val;
|
||||
}
|
||||
finally {
|
||||
this.err = null;
|
||||
this.val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.events;
|
||||
|
||||
public class DataNotifier<T> implements Awaitable<T> {
|
||||
private Notifier notifier = new Notifier();
|
||||
private boolean isErr;
|
||||
private T val;
|
||||
private RuntimeException err;
|
||||
|
||||
public void error(RuntimeException t) {
|
||||
err = t;
|
||||
isErr = true;
|
||||
notifier.next();
|
||||
}
|
||||
public void next(T val) {
|
||||
this.val = val;
|
||||
isErr = false;
|
||||
notifier.next();
|
||||
}
|
||||
public T await() {
|
||||
notifier.await();
|
||||
|
||||
try {
|
||||
if (isErr) throw err;
|
||||
else return val;
|
||||
}
|
||||
finally {
|
||||
this.err = null;
|
||||
this.val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/java/me/topchetoeu/jscript/common/events/Notifier.java
Normal file
19
src/java/me/topchetoeu/jscript/common/events/Notifier.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.jscript.common.events;
|
||||
|
||||
import me.topchetoeu.jscript.core.exceptions.InterruptException;
|
||||
|
||||
public class Notifier {
|
||||
private boolean ok = false;
|
||||
|
||||
public synchronized void next() {
|
||||
ok = true;
|
||||
notifyAll();
|
||||
}
|
||||
public synchronized void await() {
|
||||
try {
|
||||
while (!ok) wait();
|
||||
ok = false;
|
||||
}
|
||||
catch (InterruptedException e) { throw new InterruptException(e); }
|
||||
}
|
||||
}
|
||||
@@ -1,154 +1,243 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.parsing.Operator;
|
||||
import me.topchetoeu.jscript.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.parsing.Token;
|
||||
|
||||
public class JSON {
|
||||
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||
return Parsing.parseIdentifier(tokens, i);
|
||||
}
|
||||
public static ParseRes<String> parseString(String filename, List<Token> tokens, int i) {
|
||||
var res = Parsing.parseString(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Double> parseNumber(String filename, List<Token> tokens, int i) {
|
||||
var res = Parsing.parseNumber(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n);
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Boolean> parseBool(String filename, List<Token> tokens, int i) {
|
||||
var id = parseIdentifier(tokens, i);
|
||||
|
||||
if (!id.isSuccess()) return ParseRes.failed();
|
||||
else if (id.result.equals("true")) return ParseRes.res(true, 1);
|
||||
else if (id.result.equals("false")) return ParseRes.res(false, 1);
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<?> parseValue(String filename, List<Token> tokens, int i) {
|
||||
return ParseRes.any(
|
||||
parseString(filename, tokens, i),
|
||||
parseNumber(filename, tokens, i),
|
||||
parseBool(filename, tokens, i),
|
||||
parseMap(filename, tokens, i),
|
||||
parseList(filename, tokens, i)
|
||||
);
|
||||
}
|
||||
|
||||
public static ParseRes<JSONMap> parseMap(String filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONMap();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var name = ParseRes.any(
|
||||
parseIdentifier(tokens, i + n),
|
||||
parseString(filename, tokens, i + n),
|
||||
parseNumber(filename, tokens, i + n)
|
||||
);
|
||||
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
|
||||
else n += name.n;
|
||||
|
||||
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
|
||||
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
|
||||
}
|
||||
n++;
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.put(name.result.toString(), JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static ParseRes<JSONList> parseList(String filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONList();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.add(JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static JSONElement parse(String filename, String raw) {
|
||||
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
|
||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||
else return JSONElement.of(res.result);
|
||||
}
|
||||
|
||||
public static String stringify(JSONElement el) {
|
||||
if (el.isNumber()) return Double.toString(el.number());
|
||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||
if (el.isNull()) return "null";
|
||||
if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||
if (el.isList()) {
|
||||
var res = new StringBuilder().append("[");
|
||||
for (int i = 0; i < el.list().size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(el.list().get(i)));
|
||||
}
|
||||
res.append("]");
|
||||
return res.toString();
|
||||
}
|
||||
if (el.isMap()) {
|
||||
var res = new StringBuilder().append("{");
|
||||
var entries = el.map().entrySet().stream().collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
|
||||
res.append(":");
|
||||
res.append(stringify(entries.get(i).getValue()));
|
||||
}
|
||||
res.append("}");
|
||||
return res.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static String stringify(JSONMap map) {
|
||||
return stringify(JSONElement.of(map));
|
||||
}
|
||||
public static String stringify(JSONList list) {
|
||||
return stringify(JSONElement.of(list));
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.core.parsing.Operator;
|
||||
import me.topchetoeu.jscript.core.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.core.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.core.parsing.Token;
|
||||
|
||||
public class JSON {
|
||||
public static Object toJs(JSONElement val) {
|
||||
if (val.isBoolean()) return val.bool();
|
||||
if (val.isString()) return val.string();
|
||||
if (val.isNumber()) return val.number();
|
||||
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
|
||||
if (val.isMap()) {
|
||||
var res = new ObjectValue();
|
||||
for (var el : val.map().entrySet()) {
|
||||
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
if (val.isNull()) return Values.NULL;
|
||||
return null;
|
||||
}
|
||||
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
|
||||
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
|
||||
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
|
||||
if (val instanceof String) return JSONElement.string((String)val);
|
||||
if (val == Values.NULL) return JSONElement.NULL;
|
||||
if (val instanceof ArrayValue) {
|
||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
||||
prev.add(val);
|
||||
|
||||
var res = new JSONList();
|
||||
|
||||
for (var el : ((ArrayValue)val).toArray()) {
|
||||
var jsonEl = fromJs(ctx, el, prev);
|
||||
if (jsonEl == null) jsonEl = JSONElement.NULL;
|
||||
res.add(jsonEl);
|
||||
}
|
||||
|
||||
prev.remove(val);
|
||||
return JSONElement.of(res);
|
||||
}
|
||||
if (val instanceof ObjectValue) {
|
||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
||||
prev.add(val);
|
||||
|
||||
var res = new JSONMap();
|
||||
|
||||
for (var el : Values.getMembers(ctx, val, false, false)) {
|
||||
var jsonEl = fromJs(ctx, Values.getMember(ctx, val, el), prev);
|
||||
if (jsonEl == null) continue;
|
||||
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
|
||||
}
|
||||
|
||||
prev.remove(val);
|
||||
return JSONElement.of(res);
|
||||
}
|
||||
if (val == null) return null;
|
||||
return null;
|
||||
}
|
||||
public static JSONElement fromJs(Context ctx, Object val) {
|
||||
return fromJs(ctx, val, new HashSet<>());
|
||||
}
|
||||
|
||||
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||
return Parsing.parseIdentifier(tokens, i);
|
||||
}
|
||||
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
|
||||
var res = Parsing.parseString(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
|
||||
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
|
||||
if (minus) i++;
|
||||
|
||||
var res = Parsing.parseNumber(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
|
||||
var id = parseIdentifier(tokens, i);
|
||||
|
||||
if (!id.isSuccess()) return ParseRes.failed();
|
||||
else if (id.result.equals("true")) return ParseRes.res(true, 1);
|
||||
else if (id.result.equals("false")) return ParseRes.res(false, 1);
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
|
||||
return ParseRes.any(
|
||||
parseString(filename, tokens, i),
|
||||
parseNumber(filename, tokens, i),
|
||||
parseBool(filename, tokens, i),
|
||||
parseMap(filename, tokens, i),
|
||||
parseList(filename, tokens, i)
|
||||
);
|
||||
}
|
||||
|
||||
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONMap();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var name = ParseRes.any(
|
||||
parseIdentifier(tokens, i + n),
|
||||
parseString(filename, tokens, i + n),
|
||||
parseNumber(filename, tokens, i + n)
|
||||
);
|
||||
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
|
||||
else n += name.n;
|
||||
|
||||
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
|
||||
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
|
||||
}
|
||||
n++;
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.put(name.result.toString(), JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONList();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.add(JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static JSONElement parse(Filename filename, String raw) {
|
||||
if (filename == null) filename = new Filename("jscript", "json");
|
||||
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
|
||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||
else return JSONElement.of(res.result);
|
||||
}
|
||||
|
||||
public static String stringify(JSONElement el) {
|
||||
if (el.isNumber()) return Double.toString(el.number());
|
||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||
if (el.isNull()) return "null";
|
||||
if (el.isString()) {
|
||||
var res = new StringBuilder("\"");
|
||||
var alphabet = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
for (var c : el.string().toCharArray()) {
|
||||
if (c < 32 || c >= 127) {
|
||||
res
|
||||
.append("\\u")
|
||||
.append(alphabet[(c >> 12) & 0xF])
|
||||
.append(alphabet[(c >> 8) & 0xF])
|
||||
.append(alphabet[(c >> 4) & 0xF])
|
||||
.append(alphabet[(c >> 0) & 0xF]);
|
||||
}
|
||||
else if (c == '\\')
|
||||
res.append("\\\\");
|
||||
else if (c == '"')
|
||||
res.append("\\\"");
|
||||
else res.append(c);
|
||||
}
|
||||
|
||||
return res.append('"').toString();
|
||||
}
|
||||
if (el.isList()) {
|
||||
var res = new StringBuilder().append("[");
|
||||
for (int i = 0; i < el.list().size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(el.list().get(i)));
|
||||
}
|
||||
res.append("]");
|
||||
return res.toString();
|
||||
}
|
||||
if (el.isMap()) {
|
||||
var res = new StringBuilder().append("{");
|
||||
var entries = el.map().entrySet().stream().collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
|
||||
res.append(":");
|
||||
res.append(stringify(entries.get(i).getValue()));
|
||||
}
|
||||
res.append("}");
|
||||
return res.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static String stringify(JSONMap map) {
|
||||
return stringify(JSONElement.of(map));
|
||||
}
|
||||
public static String stringify(JSONList list) {
|
||||
return stringify(JSONElement.of(list));
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,88 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
public class JSONElement {
|
||||
public static enum Type {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
NULL,
|
||||
LIST,
|
||||
MAP,
|
||||
}
|
||||
|
||||
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
|
||||
|
||||
public static JSONElement map(JSONMap val) {
|
||||
return new JSONElement(Type.MAP, val);
|
||||
}
|
||||
public static JSONElement list(JSONList val) {
|
||||
return new JSONElement(Type.LIST, val);
|
||||
}
|
||||
public static JSONElement string(String val) {
|
||||
return new JSONElement(Type.STRING, val);
|
||||
}
|
||||
public static JSONElement number(double val) {
|
||||
return new JSONElement(Type.NUMBER, val);
|
||||
}
|
||||
public static JSONElement bool(boolean val) {
|
||||
return new JSONElement(Type.BOOLEAN, val);
|
||||
}
|
||||
|
||||
public static JSONElement of(Object val) {
|
||||
if (val instanceof JSONMap) return map((JSONMap)val);
|
||||
else if (val instanceof JSONList) return list((JSONList)val);
|
||||
else if (val instanceof String) return string((String)val);
|
||||
else if (val instanceof Boolean) return bool((Boolean)val);
|
||||
else if (val instanceof Number) return number(((Number)val).doubleValue());
|
||||
else if (val == null) return NULL;
|
||||
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
private final Object value;
|
||||
|
||||
public boolean isMap() { return type == Type.MAP; }
|
||||
public boolean isList() { return type == Type.LIST; }
|
||||
public boolean isString() { return type == Type.STRING; }
|
||||
public boolean isNumber() { return type == Type.NUMBER; }
|
||||
public boolean isBoolean() { return type == Type.BOOLEAN; }
|
||||
public boolean isNull() { return type == Type.NULL; }
|
||||
|
||||
public JSONMap map() {
|
||||
if (!isMap()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONMap)value;
|
||||
}
|
||||
public JSONList list() {
|
||||
if (!isList()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONList)value;
|
||||
}
|
||||
public String string() {
|
||||
if (!isString()) throw new IllegalStateException("Element is not a string.");
|
||||
return (String)value;
|
||||
}
|
||||
public double number() {
|
||||
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
|
||||
return (double)value;
|
||||
}
|
||||
public boolean bool() {
|
||||
if (!isNumber()) throw new IllegalStateException("Element is not a boolean.");
|
||||
return (boolean)value;
|
||||
}
|
||||
|
||||
private JSONElement(Type type, Object val) {
|
||||
this.type = type;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
public class JSONElement {
|
||||
public static enum Type {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
NULL,
|
||||
LIST,
|
||||
MAP,
|
||||
}
|
||||
|
||||
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
|
||||
|
||||
public static JSONElement map(JSONMap val) {
|
||||
return new JSONElement(Type.MAP, val);
|
||||
}
|
||||
public static JSONElement list(JSONList val) {
|
||||
return new JSONElement(Type.LIST, val);
|
||||
}
|
||||
public static JSONElement string(String val) {
|
||||
return new JSONElement(Type.STRING, val);
|
||||
}
|
||||
public static JSONElement number(double val) {
|
||||
return new JSONElement(Type.NUMBER, val);
|
||||
}
|
||||
public static JSONElement bool(boolean val) {
|
||||
return new JSONElement(Type.BOOLEAN, val);
|
||||
}
|
||||
|
||||
public static JSONElement of(Object val) {
|
||||
if (val instanceof JSONMap) return map((JSONMap)val);
|
||||
else if (val instanceof JSONList) return list((JSONList)val);
|
||||
else if (val instanceof String) return string((String)val);
|
||||
else if (val instanceof Boolean) return bool((Boolean)val);
|
||||
else if (val instanceof Number) return number(((Number)val).doubleValue());
|
||||
else if (val == null) return NULL;
|
||||
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
private final Object value;
|
||||
|
||||
public boolean isMap() { return type == Type.MAP; }
|
||||
public boolean isList() { return type == Type.LIST; }
|
||||
public boolean isString() { return type == Type.STRING; }
|
||||
public boolean isNumber() { return type == Type.NUMBER; }
|
||||
public boolean isBoolean() { return type == Type.BOOLEAN; }
|
||||
public boolean isNull() { return type == Type.NULL; }
|
||||
|
||||
public JSONMap map() {
|
||||
if (!isMap()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONMap)value;
|
||||
}
|
||||
public JSONList list() {
|
||||
if (!isList()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONList)value;
|
||||
}
|
||||
public String string() {
|
||||
if (!isString()) throw new IllegalStateException("Element is not a string.");
|
||||
return (String)value;
|
||||
}
|
||||
public double number() {
|
||||
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
|
||||
return (double)value;
|
||||
}
|
||||
public boolean bool() {
|
||||
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
|
||||
return (boolean)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isMap()) return "{...}";
|
||||
if (isList()) return "[...]";
|
||||
if (isString()) return (String)value;
|
||||
if (isString()) return (String)value;
|
||||
if (isNumber()) return (double)value + "";
|
||||
if (isBoolean()) return (boolean)value + "";
|
||||
if (isNull()) return "null";
|
||||
return "";
|
||||
}
|
||||
|
||||
private JSONElement(Type type, Object val) {
|
||||
this.type = type;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,26 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONList extends ArrayList<JSONElement> {
|
||||
public JSONList() {}
|
||||
public JSONList(JSONElement ...els) {
|
||||
super(List.of(els));
|
||||
}
|
||||
|
||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONList extends ArrayList<JSONElement> {
|
||||
public JSONList() {}
|
||||
public JSONList(JSONElement ...els) {
|
||||
super(List.of(els));
|
||||
}
|
||||
public JSONList(Collection<JSONElement> els) {
|
||||
super(els);
|
||||
}
|
||||
|
||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
|
||||
|
||||
}
|
||||
@@ -1,150 +1,150 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class JSONMap implements Map<String, JSONElement> {
|
||||
private Map<String, JSONElement> elements = new HashMap<>();
|
||||
|
||||
public JSONElement get(String path) {
|
||||
var curr = this;
|
||||
var segs = path.split("\\.");
|
||||
var i = 0;
|
||||
|
||||
while (true) {
|
||||
var tmp = curr.elements.get(segs[i++]);
|
||||
if (i == segs.length) return tmp;
|
||||
if (!tmp.isMap()) return null;
|
||||
curr = tmp.map();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMap(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isMap();
|
||||
}
|
||||
public boolean isList(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isList();
|
||||
}
|
||||
public boolean isString(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isString();
|
||||
}
|
||||
public boolean isNumber(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNumber();
|
||||
}
|
||||
public boolean isBoolean(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isBoolean();
|
||||
}
|
||||
public boolean isNull(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNull();
|
||||
}
|
||||
public boolean contains(String path) {
|
||||
return get(path) != null;
|
||||
}
|
||||
|
||||
public JSONMap map(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
||||
return el.map();
|
||||
}
|
||||
public JSONMap map(String path, JSONMap defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isMap()) return el.map();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONList list(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
||||
return el.list();
|
||||
}
|
||||
public JSONList list(String path, JSONList defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isList()) return el.list();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public String string(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
||||
return el.string();
|
||||
}
|
||||
public String string(String path, String defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isString()) return el.string();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public double number(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
||||
return el.number();
|
||||
}
|
||||
public double number(String path, double defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isNumber()) return el.number();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public boolean bool(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
||||
return el.bool();
|
||||
}
|
||||
public boolean bool(String path, boolean defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isBoolean()) return el.bool();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
|
||||
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
|
||||
@Override
|
||||
public int size() { return elements.size(); }
|
||||
@Override
|
||||
public boolean isEmpty() { return elements.isEmpty(); }
|
||||
@Override
|
||||
public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||
@Override
|
||||
public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||
@Override
|
||||
public JSONElement get(Object key) { return elements.get(key); }
|
||||
@Override
|
||||
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||
@Override
|
||||
public JSONElement remove(Object key) { return elements.remove(key); }
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||
|
||||
@Override
|
||||
public void clear() { elements.clear(); }
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() { return elements.keySet(); }
|
||||
@Override
|
||||
public Collection<JSONElement> values() { return elements.values(); }
|
||||
@Override
|
||||
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||
|
||||
public JSONMap() { }
|
||||
public JSONMap(Map<String, JSONElement> els) {
|
||||
this.elements = new HashMap<>(els);
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class JSONMap implements Map<String, JSONElement> {
|
||||
private Map<String, JSONElement> elements = new HashMap<>();
|
||||
|
||||
public JSONElement get(String path) {
|
||||
var curr = this;
|
||||
var segs = path.split("\\.");
|
||||
var i = 0;
|
||||
|
||||
while (true) {
|
||||
var tmp = curr.elements.get(segs[i++]);
|
||||
if (i == segs.length) return tmp;
|
||||
if (!tmp.isMap()) return null;
|
||||
curr = tmp.map();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMap(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isMap();
|
||||
}
|
||||
public boolean isList(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isList();
|
||||
}
|
||||
public boolean isString(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isString();
|
||||
}
|
||||
public boolean isNumber(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNumber();
|
||||
}
|
||||
public boolean isBoolean(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isBoolean();
|
||||
}
|
||||
public boolean isNull(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNull();
|
||||
}
|
||||
public boolean contains(String path) {
|
||||
return get(path) != null;
|
||||
}
|
||||
|
||||
public JSONMap map(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.map();
|
||||
}
|
||||
public JSONMap map(String path, JSONMap defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isMap()) return el.map();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONList list(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.list();
|
||||
}
|
||||
public JSONList list(String path, JSONList defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isList()) return el.list();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public String string(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.string();
|
||||
}
|
||||
public String string(String path, String defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isString()) return el.string();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public double number(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.number();
|
||||
}
|
||||
public double number(String path, double defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isNumber()) return el.number();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public boolean bool(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.bool();
|
||||
}
|
||||
public boolean bool(String path, boolean defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isBoolean()) return el.bool();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
|
||||
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
|
||||
@Override
|
||||
public int size() { return elements.size(); }
|
||||
@Override
|
||||
public boolean isEmpty() { return elements.isEmpty(); }
|
||||
@Override
|
||||
public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||
@Override
|
||||
public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||
@Override
|
||||
public JSONElement get(Object key) { return elements.get(key); }
|
||||
@Override
|
||||
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||
@Override
|
||||
public JSONElement remove(Object key) { return elements.remove(key); }
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||
|
||||
@Override
|
||||
public void clear() { elements.clear(); }
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() { return elements.keySet(); }
|
||||
@Override
|
||||
public Collection<JSONElement> values() { return elements.values(); }
|
||||
@Override
|
||||
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||
|
||||
public JSONMap() { }
|
||||
public JSONMap(Map<String, JSONElement> els) {
|
||||
this.elements = new HashMap<>(els);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
|
||||
public abstract class AssignableStatement extends Statement {
|
||||
public abstract Statement toAssign(Statement val, Operation operation);
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.core.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.Environment;
|
||||
import me.topchetoeu.jscript.core.engine.values.CodeFunction;
|
||||
|
||||
public class CompileTarget {
|
||||
public final Vector<Instruction> target = new Vector<>();
|
||||
public final Map<Long, FunctionBody> functions;
|
||||
public final TreeSet<Location> breakpoints;
|
||||
private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
|
||||
|
||||
public Instruction add(Instruction instr) {
|
||||
target.add(instr);
|
||||
return instr;
|
||||
}
|
||||
public Instruction set(int i, Instruction instr) {
|
||||
return target.set(i, instr);
|
||||
}
|
||||
public void setDebug(int i, BreakpointType type) {
|
||||
var instr = target.get(i);
|
||||
instr.breakpoint = type;
|
||||
|
||||
if (type == BreakpointType.NONE) {
|
||||
breakpoints.remove(target.get(i).location);
|
||||
bpToInstr.remove(instr.location, instr);
|
||||
}
|
||||
else {
|
||||
breakpoints.add(target.get(i).location);
|
||||
|
||||
var old = bpToInstr.put(instr.location, instr);
|
||||
if (old != null) old.breakpoint = BreakpointType.NONE;
|
||||
}
|
||||
}
|
||||
public void setDebug(BreakpointType type) {
|
||||
setDebug(target.size() - 1, type);
|
||||
}
|
||||
public Instruction get(int i) {
|
||||
return target.get(i);
|
||||
}
|
||||
public int size() { return target.size(); }
|
||||
public Location lastLoc(Location fallback) {
|
||||
if (target.size() == 0) return fallback;
|
||||
else return target.get(target.size() - 1).location;
|
||||
}
|
||||
|
||||
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
||||
|
||||
public FunctionBody body() {
|
||||
return functions.get(0l);
|
||||
}
|
||||
public CodeFunction func(Environment env) {
|
||||
return new CodeFunction(env, "", body());
|
||||
}
|
||||
|
||||
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
|
||||
this.functions = functions;
|
||||
this.breakpoints = breakpoints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class CompoundStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
public final boolean separateFuncs;
|
||||
public Location end;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var stm : statements) {
|
||||
if (!stm.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var stm : statements) stm.declare(varsScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||
List<Statement> statements = new Vector<Statement>();
|
||||
if (separateFuncs) for (var stm : this.statements) {
|
||||
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
|
||||
stm.compile(target, scope, false);
|
||||
}
|
||||
else statements.add(stm);
|
||||
}
|
||||
else statements = List.of(this.statements);
|
||||
|
||||
var polluted = false;
|
||||
|
||||
for (var i = 0; i < statements.size(); i++) {
|
||||
var stm = statements.get(i);
|
||||
|
||||
if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
|
||||
}
|
||||
|
||||
if (!polluted && pollute) {
|
||||
target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
}
|
||||
|
||||
public CompoundStatement setEnd(Location loc) {
|
||||
this.end = loc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
|
||||
super(loc);
|
||||
this.separateFuncs = separateFuncs;
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
public class FunctionBody {
|
||||
public final Instruction[] instructions;
|
||||
public final String[] captureNames, localNames;
|
||||
public final int localsN, argsN;
|
||||
|
||||
public FunctionBody(int localsN, int argsN, Instruction[] instructions, String[] captureNames, String[] localNames) {
|
||||
this.argsN = argsN;
|
||||
this.localsN = localsN;
|
||||
this.instructions = instructions;
|
||||
this.captureNames = captureNames;
|
||||
this.localNames = localNames;
|
||||
}
|
||||
public FunctionBody(int localsN, int argsN, Instruction[] instructions) {
|
||||
this.argsN = argsN;
|
||||
this.localsN = localsN;
|
||||
this.instructions = instructions;
|
||||
this.captureNames = new String[0];
|
||||
this.localNames = new String[0];
|
||||
}
|
||||
public FunctionBody(Instruction... instructions) {
|
||||
this.argsN = 0;
|
||||
this.localsN = 2;
|
||||
this.instructions = instructions;
|
||||
this.captureNames = new String[0];
|
||||
this.localNames = new String[0];
|
||||
}
|
||||
}
|
||||
256
src/java/me/topchetoeu/jscript/core/compilation/Instruction.java
Normal file
256
src/java/me/topchetoeu/jscript/core/compilation/Instruction.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||
|
||||
public class Instruction {
|
||||
public static enum Type {
|
||||
RETURN,
|
||||
THROW,
|
||||
THROW_SYNTAX,
|
||||
DELETE,
|
||||
TRY_START,
|
||||
TRY_END,
|
||||
NOP,
|
||||
|
||||
CALL,
|
||||
CALL_NEW,
|
||||
JMP_IF,
|
||||
JMP_IFN,
|
||||
JMP,
|
||||
|
||||
LOAD_VALUE,
|
||||
|
||||
LOAD_VAR,
|
||||
LOAD_MEMBER,
|
||||
LOAD_VAL_MEMBER,
|
||||
LOAD_GLOB,
|
||||
|
||||
LOAD_FUNC,
|
||||
LOAD_ARR,
|
||||
LOAD_OBJ,
|
||||
STORE_SELF_FUNC,
|
||||
LOAD_REGEX,
|
||||
|
||||
DUP,
|
||||
|
||||
STORE_VAR,
|
||||
STORE_MEMBER,
|
||||
DISCARD,
|
||||
|
||||
MAKE_VAR,
|
||||
DEF_PROP,
|
||||
KEYS,
|
||||
|
||||
TYPEOF,
|
||||
OPERATION;
|
||||
}
|
||||
public static enum BreakpointType {
|
||||
NONE,
|
||||
STEP_OVER,
|
||||
STEP_IN;
|
||||
|
||||
public boolean shouldStepIn() {
|
||||
return this != NONE;
|
||||
}
|
||||
public boolean shouldStepOver() {
|
||||
return this == STEP_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
public final Object[] params;
|
||||
public Location location;
|
||||
public BreakpointType breakpoint = BreakpointType.NONE;
|
||||
|
||||
public Instruction setDbgData(Instruction other) {
|
||||
this.location = other.location;
|
||||
this.breakpoint = other.breakpoint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Instruction locate(Location loc) {
|
||||
this.location = loc;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(int i) {
|
||||
if (i >= params.length || i < 0) return null;
|
||||
return (T)params[i];
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(int i, T defaultVal) {
|
||||
if (i >= params.length || i < 0) return defaultVal;
|
||||
return (T)params[i];
|
||||
}
|
||||
public boolean match(Object ...args) {
|
||||
if (args.length != params.length) return false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
var a = params[i];
|
||||
var b = args[i];
|
||||
if (a == null || b == null) {
|
||||
if (!(a == null && b == null)) return false;
|
||||
}
|
||||
if (!a.equals(b)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public boolean is(int i, Object arg) {
|
||||
if (params.length <= i) return false;
|
||||
return params[i].equals(arg);
|
||||
}
|
||||
|
||||
private Instruction(Location location, Type type, Object ...params) {
|
||||
this.location = location;
|
||||
this.type = type;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
|
||||
return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
|
||||
}
|
||||
public static Instruction tryEnd(Location loc) {
|
||||
return new Instruction(loc, Type.TRY_END);
|
||||
}
|
||||
public static Instruction throwInstr(Location loc) {
|
||||
return new Instruction(loc, Type.THROW);
|
||||
}
|
||||
public static Instruction throwSyntax(Location loc, SyntaxException err) {
|
||||
return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage());
|
||||
}
|
||||
public static Instruction throwSyntax(Location loc, String err) {
|
||||
return new Instruction(loc, Type.THROW_SYNTAX, err);
|
||||
}
|
||||
public static Instruction delete(Location loc) {
|
||||
return new Instruction(loc, Type.DELETE);
|
||||
}
|
||||
public static Instruction ret(Location loc) {
|
||||
return new Instruction(loc, Type.RETURN);
|
||||
}
|
||||
public static Instruction debug(Location loc) {
|
||||
return new Instruction(loc, Type.NOP, "debug");
|
||||
}
|
||||
|
||||
public static Instruction nop(Location loc, Object ...params) {
|
||||
for (var param : params) {
|
||||
if (param instanceof String) continue;
|
||||
if (param instanceof Boolean) continue;
|
||||
if (param instanceof Double) continue;
|
||||
if (param instanceof Integer) continue;
|
||||
if (param == null) continue;
|
||||
|
||||
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
|
||||
}
|
||||
return new Instruction(loc, Type.NOP, params);
|
||||
}
|
||||
|
||||
public static Instruction call(Location loc, int argn) {
|
||||
return new Instruction(loc, Type.CALL, argn);
|
||||
}
|
||||
public static Instruction callNew(Location loc, int argn) {
|
||||
return new Instruction(loc, Type.CALL_NEW, argn);
|
||||
}
|
||||
public static Instruction jmp(Location loc, int offset) {
|
||||
return new Instruction(loc, Type.JMP, offset);
|
||||
}
|
||||
public static Instruction jmpIf(Location loc, int offset) {
|
||||
return new Instruction(loc, Type.JMP_IF, offset);
|
||||
}
|
||||
public static Instruction jmpIfNot(Location loc, int offset) {
|
||||
return new Instruction(loc, Type.JMP_IFN, offset);
|
||||
}
|
||||
|
||||
public static Instruction loadValue(Location loc, Object val) {
|
||||
return new Instruction(loc, Type.LOAD_VALUE, val);
|
||||
}
|
||||
|
||||
public static Instruction makeVar(Location loc, String name) {
|
||||
return new Instruction(loc, Type.MAKE_VAR, name);
|
||||
}
|
||||
public static Instruction loadVar(Location loc, Object i) {
|
||||
return new Instruction(loc, Type.LOAD_VAR, i);
|
||||
}
|
||||
public static Instruction loadGlob(Location loc) {
|
||||
return new Instruction(loc, Type.LOAD_GLOB);
|
||||
}
|
||||
public static Instruction loadMember(Location loc) {
|
||||
return new Instruction(loc, Type.LOAD_MEMBER);
|
||||
}
|
||||
public static Instruction loadMember(Location loc, Object key) {
|
||||
if (key instanceof Number) key = ((Number)key).doubleValue();
|
||||
return new Instruction(loc, Type.LOAD_VAL_MEMBER, key);
|
||||
}
|
||||
|
||||
public static Instruction loadRegex(Location loc, String pattern, String flags) {
|
||||
return new Instruction(loc, Type.LOAD_REGEX, pattern, flags);
|
||||
}
|
||||
public static Instruction loadFunc(Location loc, long id, int[] captures) {
|
||||
var args = new Object[1 + captures.length];
|
||||
args[0] = id;
|
||||
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
|
||||
return new Instruction(loc, Type.LOAD_FUNC, args);
|
||||
}
|
||||
public static Instruction loadObj(Location loc) {
|
||||
return new Instruction(loc, Type.LOAD_OBJ);
|
||||
}
|
||||
public static Instruction loadArr(Location loc, int count) {
|
||||
return new Instruction(loc, Type.LOAD_ARR, count);
|
||||
}
|
||||
public static Instruction dup(Location loc) {
|
||||
return new Instruction(loc, Type.DUP, 1);
|
||||
}
|
||||
public static Instruction dup(Location loc, int count) {
|
||||
return new Instruction(loc, Type.DUP, count);
|
||||
}
|
||||
|
||||
public static Instruction storeSelfFunc(Location loc, int i) {
|
||||
return new Instruction(loc, Type.STORE_SELF_FUNC, i);
|
||||
}
|
||||
public static Instruction storeVar(Location loc, Object i) {
|
||||
return new Instruction(loc, Type.STORE_VAR, i, false);
|
||||
}
|
||||
public static Instruction storeVar(Location loc, Object i, boolean keep) {
|
||||
return new Instruction(loc, Type.STORE_VAR, i, keep);
|
||||
}
|
||||
public static Instruction storeMember(Location loc) {
|
||||
return new Instruction(loc, Type.STORE_MEMBER, false);
|
||||
}
|
||||
public static Instruction storeMember(Location loc, boolean keep) {
|
||||
return new Instruction(loc, Type.STORE_MEMBER, keep);
|
||||
}
|
||||
public static Instruction discard(Location loc) {
|
||||
return new Instruction(loc, Type.DISCARD);
|
||||
}
|
||||
|
||||
public static Instruction typeof(Location loc) {
|
||||
return new Instruction(loc, Type.TYPEOF);
|
||||
}
|
||||
public static Instruction typeof(Location loc, Object varName) {
|
||||
return new Instruction(loc, Type.TYPEOF, varName);
|
||||
}
|
||||
|
||||
public static Instruction keys(Location loc, boolean forInFormat) {
|
||||
return new Instruction(loc, Type.KEYS, forInFormat);
|
||||
}
|
||||
|
||||
public static Instruction defProp(Location loc) {
|
||||
return new Instruction(loc, Type.DEF_PROP);
|
||||
}
|
||||
|
||||
public static Instruction operation(Location loc, Operation op) {
|
||||
return new Instruction(loc, Type.OPERATION, op);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var res = type.toString();
|
||||
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
res += " " + params[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class Statement {
|
||||
private Location _loc;
|
||||
|
||||
public boolean pure() { return false; }
|
||||
public void declare(ScopeRecord varsScope) { }
|
||||
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||
int start = target.size();
|
||||
compile(target, scope, pollute);
|
||||
|
||||
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 void setLoc(Location loc) { _loc = loc; }
|
||||
|
||||
protected Statement(Location loc) {
|
||||
this._loc = loc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.core.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;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,52 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableDeclareStatement extends Statement {
|
||||
public static class Pair {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
|
||||
public Pair(String name, Statement value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<Pair> values;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var key : values) {
|
||||
varsScope.define(key.name);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var entry : values) {
|
||||
if (entry.name == null) continue;
|
||||
var key = scope.getKey(entry.name);
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
|
||||
if (entry.value instanceof FunctionStatement) {
|
||||
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
}
|
||||
else if (entry.value != null) {
|
||||
entry.value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||
super(loc);
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableDeclareStatement extends Statement {
|
||||
public static class Pair {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
public final Location location;
|
||||
|
||||
public Pair(String name, Statement value, Location location) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<Pair> values;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var key : values) {
|
||||
varsScope.define(key.name);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var entry : values) {
|
||||
if (entry.name == null) continue;
|
||||
var key = scope.getKey(entry.name);
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
|
||||
|
||||
if (entry.value != null) {
|
||||
FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.storeVar(entry.location, key));
|
||||
}
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||
super(loc);
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class BreakStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop(loc(), "break", label));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public BreakStatement(Location loc, String label) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ContinueStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop(loc(), "cont", label));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public ContinueStatement(Location loc, String label) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class DebugStatement extends Statement {
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.debug(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public DebugStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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 DeleteStatement extends Statement {
|
||||
public final Statement key;
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
key.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.delete().locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||
super(loc);
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class DeleteStatement extends Statement {
|
||||
public final Statement key;
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
key.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.delete(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), true));
|
||||
}
|
||||
|
||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||
super(loc);
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class DoWhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
int start = target.size();
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
int mid = target.size();
|
||||
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
target.add(Instruction.jmpIf(loc(), start - end));
|
||||
}
|
||||
|
||||
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ForInStatement extends Statement {
|
||||
public final String varName;
|
||||
public final boolean isDeclaration;
|
||||
public final Statement varValue, object, body;
|
||||
public final String label;
|
||||
public final Location varLocation;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
if (isDeclaration) globScope.define(varName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var key = scope.getKey(varName);
|
||||
|
||||
int first = target.size();
|
||||
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
varValue.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.keys(loc(), true));
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), null));
|
||||
target.add(Instruction.operation(loc(), Operation.EQUALS));
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop(loc()));
|
||||
|
||||
target.add(Instruction.loadMember(varLocation, "value"));
|
||||
target.add(Instruction.storeVar(object.loc(), key));
|
||||
target.setDebug(BreakpointType.STEP_OVER);
|
||||
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(loc(), start - end));
|
||||
target.add(Instruction.discard(loc()));
|
||||
target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
target.get(first).locate(loc());
|
||||
}
|
||||
|
||||
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
super(loc);
|
||||
this.varLocation = varLocation;
|
||||
this.label = label;
|
||||
this.isDeclaration = isDecl;
|
||||
this.varName = varName;
|
||||
this.varValue = varValue;
|
||||
this.object = object;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ForStatement extends Statement {
|
||||
public final Statement declaration, assignment, condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
declaration.declare(globScope);
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int start = target.size();
|
||||
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
int beforeAssign = target.size();
|
||||
assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(loc(), start - end));
|
||||
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.declaration = declaration;
|
||||
this.condition = condition;
|
||||
this.assignment = assignment;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class IfStatement extends Statement {
|
||||
public final Statement condition, body, elseBody;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
if (elseBody != null) elseBody.declare(globScope);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
|
||||
condition.compile(target, scope, true, breakpoint);
|
||||
|
||||
if (elseBody == null) {
|
||||
int i = target.size();
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, pollute, breakpoint);
|
||||
int endI = target.size();
|
||||
target.set(i, Instruction.jmpIfNot(loc(), endI - i));
|
||||
}
|
||||
else {
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, pollute, breakpoint);
|
||||
target.add(Instruction.nop(null));
|
||||
int mid = target.size();
|
||||
elseBody.compile(target, scope, pollute, breakpoint);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(loc(), mid - start));
|
||||
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
|
||||
}
|
||||
}
|
||||
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, pollute, BreakpointType.STEP_IN);
|
||||
}
|
||||
|
||||
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
|
||||
super(loc);
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
this.elseBody = elseBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ReturnStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value == null) target.add(Instruction.loadValue(loc(), null));
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.ret(loc()));
|
||||
}
|
||||
|
||||
public ReturnStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class SwitchStatement extends Statement {
|
||||
public static class SwitchCase {
|
||||
public final Statement value;
|
||||
public final int statementI;
|
||||
|
||||
public SwitchCase(Statement value, int statementI) {
|
||||
this.value = value;
|
||||
this.statementI = statementI;
|
||||
}
|
||||
}
|
||||
|
||||
public final Statement value;
|
||||
public final SwitchCase[] cases;
|
||||
public final Statement[] body;
|
||||
public final int defaultI;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var stm : body) stm.declare(varsScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var caseToStatement = new HashMap<Integer, Integer>();
|
||||
var statementToIndex = new HashMap<Integer, Integer>();
|
||||
|
||||
value.compile(target, scope, true, BreakpointType.STEP_OVER);
|
||||
|
||||
for (var ccase : cases) {
|
||||
target.add(Instruction.dup(loc()));
|
||||
ccase.value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(loc(), Operation.EQUALS));
|
||||
caseToStatement.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop(null));
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
|
||||
target.add(Instruction.nop(null));
|
||||
|
||||
for (var stm : body) {
|
||||
statementToIndex.put(statementToIndex.size(), target.size());
|
||||
stm.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
}
|
||||
|
||||
int end = target.size();
|
||||
target.add(Instruction.discard(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
|
||||
else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start));
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
|
||||
target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location));
|
||||
}
|
||||
}
|
||||
for (var el : caseToStatement.entrySet()) {
|
||||
var i = statementToIndex.get(el.getValue());
|
||||
if (i == null) i = end;
|
||||
target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
this.defaultI = defaultI;
|
||||
this.cases = cases;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ThrowStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.throwInstr(loc()));
|
||||
}
|
||||
|
||||
public ThrowStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,61 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class TryStatement extends Statement {
|
||||
public final Statement tryBody;
|
||||
public final Statement catchBody;
|
||||
public final Statement finallyBody;
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
tryBody.declare(globScope);
|
||||
if (catchBody != null) catchBody.declare(globScope);
|
||||
if (finallyBody != null) finallyBody.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop());
|
||||
|
||||
int start = target.size(), tryN, catchN = -1, finN = -1;
|
||||
|
||||
tryBody.compile(target, scope, false);
|
||||
tryN = target.size() - start;
|
||||
|
||||
if (catchBody != null) {
|
||||
int tmp = target.size();
|
||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||
local.define(name, true);
|
||||
catchBody.compile(target, scope, false);
|
||||
local.undefine();
|
||||
catchN = target.size() - tmp;
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
int tmp = target.size();
|
||||
finallyBody.compile(target, scope, false);
|
||||
finN = target.size() - tmp;
|
||||
}
|
||||
|
||||
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
super(loc);
|
||||
this.tryBody = tryBody;
|
||||
this.catchBody = catchBody;
|
||||
this.finallyBody = finallyBody;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.core.engine.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class TryStatement extends Statement {
|
||||
public final Statement tryBody;
|
||||
public final Statement catchBody;
|
||||
public final Statement finallyBody;
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
tryBody.declare(globScope);
|
||||
if (catchBody != null) catchBody.declare(globScope);
|
||||
if (finallyBody != null) finallyBody.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
|
||||
target.add(Instruction.nop(null));
|
||||
|
||||
int start = target.size(), catchStart = -1, finallyStart = -1;
|
||||
|
||||
tryBody.compile(target, scope, false);
|
||||
target.add(Instruction.tryEnd(loc()));
|
||||
|
||||
if (catchBody != null) {
|
||||
catchStart = target.size() - start;
|
||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||
local.define(name, true);
|
||||
catchBody.compile(target, scope, false);
|
||||
local.undefine();
|
||||
target.add(Instruction.tryEnd(loc()));
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
finallyStart = target.size() - start;
|
||||
finallyBody.compile(target, scope, false);
|
||||
target.add(Instruction.tryEnd(loc()));
|
||||
}
|
||||
|
||||
target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
|
||||
target.setDebug(start - 1, BreakpointType.STEP_OVER);
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
super(loc);
|
||||
this.tryBody = tryBody;
|
||||
this.catchBody = catchBody;
|
||||
this.finallyBody = finallyBody;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.topchetoeu.jscript.core.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class WhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
int start = target.size();
|
||||
condition.compile(target, scope, true);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop(null));
|
||||
body.compile(target, scope, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(loc(), start - end));
|
||||
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static void replaceBreaks(CompileTarget target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
|
||||
}
|
||||
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var stm : statements) {
|
||||
if (!stm.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadArr(loc(), statements.length));
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var el = statements[i];
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), i));
|
||||
el.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class CallStatement extends Statement {
|
||||
public final Statement func;
|
||||
public final Statement[] args;
|
||||
public final boolean isNew;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
|
||||
if (isNew) func.compile(target, scope, true);
|
||||
else if (func instanceof IndexStatement) {
|
||||
((IndexStatement)func).compile(target, scope, true, true);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.loadValue(loc(), null));
|
||||
func.compile(target, scope, true);
|
||||
}
|
||||
|
||||
for (var arg : args) arg.compile(target, scope, true);
|
||||
|
||||
if (isNew) target.add(Instruction.callNew(loc(), args.length));
|
||||
else target.add(Instruction.call(loc(), args.length));
|
||||
target.setDebug(type);
|
||||
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, pollute, BreakpointType.STEP_IN);
|
||||
}
|
||||
|
||||
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
|
||||
super(loc);
|
||||
this.isNew = isNew;
|
||||
this.func = func;
|
||||
this.args = args;
|
||||
}
|
||||
public CallStatement(Location loc, boolean isNew, Statement obj, Object key, Statement ...args) {
|
||||
super(loc);
|
||||
this.isNew = isNew;
|
||||
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ChangeStatement extends Statement {
|
||||
public final AssignableStatement value;
|
||||
public final double addAmount;
|
||||
public final boolean postfix;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
|
||||
if (!pollute) target.add(Instruction.discard(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) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
this.addAmount = addAmount;
|
||||
this.postfix = postfix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ConstantStatement extends Statement {
|
||||
public final Object value;
|
||||
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), value));
|
||||
}
|
||||
|
||||
public ConstantStatement(Location loc, Object val) {
|
||||
super(loc);
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class DiscardStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override public boolean pure() { return value.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, false);
|
||||
if (pollute) target.add(Instruction.loadValue(loc(), null));
|
||||
}
|
||||
|
||||
public DiscardStatement(Location loc, Statement val) {
|
||||
super(loc);
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.core.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||
|
||||
public class FunctionStatement extends Statement {
|
||||
public final CompoundStatement body;
|
||||
public final String varName;
|
||||
public final String[] args;
|
||||
public final boolean statement;
|
||||
public final Location end;
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
@Override public boolean pure() { return varName == null && statement; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord scope) {
|
||||
if (varName != null && statement) scope.define(varName);
|
||||
}
|
||||
|
||||
public static void checkBreakAndCont(CompileTarget target, int start) {
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
if (target.get(i).type == Type.NOP) {
|
||||
if (target.get(i).is(0, "break") ) {
|
||||
throw new SyntaxException(target.get(i).location, "Break was placed outside a loop.");
|
||||
}
|
||||
if (target.get(i).is(0, "cont")) {
|
||||
throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var j = 0; j < i; j++) {
|
||||
if (args[i].equals(args[j])) {
|
||||
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var id = rand.nextLong();
|
||||
var subscope = scope.child();
|
||||
var subtarget = new CompileTarget(target.functions, target.breakpoints);
|
||||
|
||||
subscope.define("this");
|
||||
var argsVar = subscope.define("arguments");
|
||||
|
||||
if (args.length > 0) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
subtarget.add(Instruction.loadVar(loc(), argsVar));
|
||||
subtarget.add(Instruction.loadMember(loc(), i));
|
||||
subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
|
||||
}
|
||||
}
|
||||
|
||||
if (!statement && this.varName != null) {
|
||||
subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
|
||||
subtarget.setDebug(bp);
|
||||
}
|
||||
|
||||
body.declare(subscope);
|
||||
body.compile(subtarget, subscope, false);
|
||||
subtarget.add(Instruction.ret(end));
|
||||
checkBreakAndCont(subtarget, 0);
|
||||
|
||||
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()
|
||||
));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
this.end = end;
|
||||
this.varName = varName;
|
||||
this.statement = statement;
|
||||
|
||||
this.args = args;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
|
||||
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
|
||||
else stm.compile(target, scope, pollute);
|
||||
}
|
||||
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
|
||||
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
|
||||
else stm.compile(target, scope, pollute, bp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class GlobalThisStatement extends Statement {
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadGlob(loc()));
|
||||
}
|
||||
|
||||
public GlobalThisStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,48 @@
|
||||
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.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class IndexAssignStatement extends Statement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (operation != null) {
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.dup(2, 0).locate(loc()));
|
||||
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
|
||||
}
|
||||
else {
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
|
||||
}
|
||||
}
|
||||
|
||||
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.operation = operation;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class IndexAssignStatement extends Statement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (operation != null) {
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.dup(loc(), 2));
|
||||
|
||||
target.add(Instruction.loadMember(loc()));
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(loc(), operation));
|
||||
|
||||
target.add(Instruction.storeMember(loc(), pollute));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
}
|
||||
else {
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.storeMember(loc(), pollute));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
}
|
||||
}
|
||||
|
||||
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.operation = operation;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,49 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class IndexStatement extends AssignableStatement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||
}
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||
int start = 0;
|
||||
object.compile(target, scope, true);
|
||||
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
||||
if (index instanceof ConstantStatement) {
|
||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
||||
return;
|
||||
}
|
||||
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
target.get(start).setDebug(true);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, false, pollute);
|
||||
}
|
||||
|
||||
public IndexStatement(Location loc, Statement object, Statement index) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
}
|
||||
public IndexStatement(Location loc, Statement object, Object index) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = new ConstantStatement(loc, index);
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class IndexStatement extends AssignableStatement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||
}
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||
object.compile(target, scope, true);
|
||||
if (dupObj) target.add(Instruction.dup(loc()));
|
||||
if (index instanceof ConstantStatement) {
|
||||
target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
return;
|
||||
}
|
||||
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.loadMember(loc()));
|
||||
target.setDebug(BreakpointType.STEP_IN);
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, false, pollute);
|
||||
}
|
||||
|
||||
public IndexStatement(Location loc, Statement object, Statement index) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
}
|
||||
public IndexStatement(Location loc, Statement object, Object index) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = new ConstantStatement(loc, index);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,39 @@
|
||||
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;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class LazyAndStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
first.compile(target, scope, pollute);
|
||||
}
|
||||
else second.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
|
||||
public class LazyAndStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
first.compile(target, scope, pollute);
|
||||
}
|
||||
else second.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop(null));
|
||||
if (pollute) target.add(Instruction.discard(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
|
||||
}
|
||||
|
||||
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,39 @@
|
||||
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;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class LazyOrStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
second.compile(target, scope, pollute);
|
||||
}
|
||||
else first.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
|
||||
public class LazyOrStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
second.compile(target, scope, pollute);
|
||||
}
|
||||
else first.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop(null));
|
||||
if (pollute) target.add(Instruction.discard(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIf(loc(), target.size() - start));
|
||||
}
|
||||
|
||||
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class ObjectStatement extends Statement {
|
||||
public final Map<Object, Statement> map;
|
||||
public final Map<Object, FunctionStatement> getters;
|
||||
public final Map<Object, FunctionStatement> setters;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var el : map.values()) {
|
||||
if (!el.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadObj(loc()));
|
||||
|
||||
for (var el : map.entrySet()) {
|
||||
target.add(Instruction.dup(loc()));
|
||||
target.add(Instruction.loadValue(loc(), el.getKey()));
|
||||
var val = el.getValue();
|
||||
FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
|
||||
target.add(Instruction.storeMember(loc()));
|
||||
}
|
||||
|
||||
var keys = new ArrayList<Object>();
|
||||
keys.addAll(getters.keySet());
|
||||
keys.addAll(setters.keySet());
|
||||
|
||||
for (var key : keys) {
|
||||
if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
|
||||
else target.add(Instruction.loadValue(loc(), (Double)key));
|
||||
|
||||
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(loc(), null));
|
||||
|
||||
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(loc(), null));
|
||||
|
||||
target.add(Instruction.defProp(loc()));
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
||||
super(loc);
|
||||
this.map = map;
|
||||
this.getters = getters;
|
||||
this.setters = setters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class OperationStatement extends Statement {
|
||||
public final Statement[] args;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public boolean pure() {
|
||||
for (var el : args) {
|
||||
if (!el.pure()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var arg : args) {
|
||||
arg.compile(target, scope, true);
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.operation(loc(), operation));
|
||||
else target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public OperationStatement(Location loc, Operation operation, Statement ...args) {
|
||||
super(loc);
|
||||
this.operation = operation;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class RegexStatement extends Statement {
|
||||
public final String pattern, flags;
|
||||
|
||||
// Not really pure, since a function is called, but can be ignored.
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadRegex(loc(), pattern, flags));
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public RegexStatement(Location loc, String pattern, String flags) {
|
||||
super(loc);
|
||||
this.pattern = pattern;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class TypeofStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
// Not really pure, since a variable from the global scope could be accessed,
|
||||
// which could lead to code execution, that would get omitted
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value instanceof VariableStatement) {
|
||||
var i = scope.getKey(((VariableStatement)value).name);
|
||||
if (i instanceof String) {
|
||||
target.add(Instruction.typeof(loc(), (String)i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
value.compile(target, scope, pollute);
|
||||
target.add(Instruction.typeof(loc()));
|
||||
}
|
||||
|
||||
public TypeofStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableAssignStatement extends Statement {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public boolean pure() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
if (operation != null) {
|
||||
target.add(Instruction.loadVar(loc(), i));
|
||||
FunctionStatement.compileWithName(value, target, scope, true, name);
|
||||
target.add(Instruction.operation(loc(), operation));
|
||||
target.add(Instruction.storeVar(loc(), i, pollute));
|
||||
}
|
||||
else {
|
||||
FunctionStatement.compileWithName(value, target, scope, true, name);
|
||||
target.add(Instruction.storeVar(loc(), i, pollute));
|
||||
}
|
||||
}
|
||||
|
||||
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
|
||||
super(loc);
|
||||
this.name = name;
|
||||
this.value = val;
|
||||
this.operation = operation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableIndexStatement extends Statement {
|
||||
public final int index;
|
||||
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadVar(loc(), index));
|
||||
}
|
||||
|
||||
public VariableIndexStatement(Location loc, int i) {
|
||||
super(loc);
|
||||
this.index = i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.topchetoeu.jscript.core.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.core.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.compilation.Statement;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableStatement extends AssignableStatement {
|
||||
public final String name;
|
||||
|
||||
@Override public boolean pure() { return false; }
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new VariableAssignStatement(loc(), name, val, operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
target.add(Instruction.loadVar(loc(), i));
|
||||
if (!pollute) target.add(Instruction.discard(loc()));
|
||||
}
|
||||
|
||||
public VariableStatement(Location loc, String name) {
|
||||
super(loc);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
166
src/java/me/topchetoeu/jscript/core/engine/Context.java
Normal file
166
src/java/me/topchetoeu/jscript/core/engine/Context.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.lib.EnvironmentLib;
|
||||
import me.topchetoeu.jscript.utils.mapping.SourceMap;
|
||||
|
||||
public class Context implements Extensions {
|
||||
public static final Context NULL = new Context(null);
|
||||
|
||||
public final Context parent;
|
||||
public final Environment environment;
|
||||
public final CodeFrame frame;
|
||||
public final Engine engine;
|
||||
public final int stackSize;
|
||||
|
||||
@Override public <T> void add(Symbol key, T obj) {
|
||||
if (environment != null) environment.add(key, obj);
|
||||
else if (engine != null) engine.add(key, obj);
|
||||
}
|
||||
@Override public <T> T get(Symbol key) {
|
||||
if (environment != null && environment.has(key)) return environment.get(key);
|
||||
else if (engine != null && engine.has(key)) return engine.get(key);
|
||||
return null;
|
||||
}
|
||||
@Override public boolean has(Symbol key) {
|
||||
return
|
||||
environment != null && environment.has(key) ||
|
||||
engine != null && engine.has(key);
|
||||
}
|
||||
@Override public boolean remove(Symbol key) {
|
||||
var res = false;
|
||||
|
||||
if (environment != null) res |= environment.remove(key);
|
||||
else if (engine != null) res |= engine.remove(key);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Override public Iterable<Symbol> keys() {
|
||||
if (engine == null && environment == null) return List.of();
|
||||
if (engine == null) return environment.keys();
|
||||
if (environment == null) return engine.keys();
|
||||
|
||||
return () -> Stream.concat(
|
||||
StreamSupport.stream(engine.keys().spliterator(), false),
|
||||
StreamSupport.stream(environment.keys().spliterator(), false)
|
||||
).distinct().iterator();
|
||||
}
|
||||
|
||||
public FunctionValue compile(Filename filename, String raw) {
|
||||
var env = environment;
|
||||
var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), new EnvironmentLib(env));
|
||||
|
||||
var function = (FunctionValue)Values.getMember(this, result, "function");
|
||||
if (!DebugContext.enabled(this)) 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;
|
||||
}
|
||||
|
||||
DebugContext.get(this).onSource(filename, raw, breakpoints, map);
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
public Context pushFrame(CodeFrame frame) {
|
||||
var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Iterable<CodeFrame> frames() {
|
||||
var self = this;
|
||||
return () -> new Iterator<CodeFrame>() {
|
||||
private Context curr = self;
|
||||
|
||||
private void update() {
|
||||
while (curr != null && curr.frame == null) curr = curr.parent;
|
||||
}
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
update();
|
||||
return curr != null;
|
||||
}
|
||||
@Override public CodeFrame next() {
|
||||
update();
|
||||
var res = curr.frame;
|
||||
curr = curr.parent;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<String> stackTrace() {
|
||||
var res = new ArrayList<String>();
|
||||
|
||||
for (var el : frames()) {
|
||||
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;
|
||||
}
|
||||
|
||||
private Context(Context parent, Environment environment, CodeFrame frame, Engine engine, int stackSize) {
|
||||
this.parent = parent;
|
||||
this.environment = environment;
|
||||
this.frame = frame;
|
||||
this.engine = engine;
|
||||
this.stackSize = stackSize;
|
||||
|
||||
if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) {
|
||||
throw EngineException.ofRange("Stack overflow!");
|
||||
}
|
||||
}
|
||||
|
||||
public Context(Engine engine) {
|
||||
this(null, null, null, engine, 0);
|
||||
}
|
||||
public Context(Engine engine, Environment env) {
|
||||
this(null, env, null, engine, 0);
|
||||
}
|
||||
}
|
||||
57
src/java/me/topchetoeu/jscript/core/engine/Engine.java
Normal file
57
src/java/me/topchetoeu/jscript/core/engine/Engine.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.events.Awaitable;
|
||||
import me.topchetoeu.jscript.core.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
||||
|
||||
public class Engine extends EventLoop implements Extensions {
|
||||
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||
|
||||
private final Environment env = new Environment();
|
||||
|
||||
@Override
|
||||
public <T> void add(Symbol key, T obj) {
|
||||
this.env.add(key, obj);
|
||||
}
|
||||
@Override
|
||||
public <T> T get(Symbol key) {
|
||||
return this.env.get(key);
|
||||
}
|
||||
@Override
|
||||
public boolean has(Symbol key) {
|
||||
return this.env.has(key);
|
||||
}
|
||||
@Override
|
||||
public boolean remove(Symbol key) {
|
||||
return this.env.remove(key);
|
||||
}
|
||||
@Override
|
||||
public Iterable<Symbol> keys() {
|
||||
return env.keys();
|
||||
}
|
||||
|
||||
public Engine copy() {
|
||||
var res = new Engine();
|
||||
res.env.addAll(env);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
|
||||
return pushMsg(() -> {
|
||||
return func.call(new Context(this, env), thisArg, args);
|
||||
}, micro);
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(() -> {
|
||||
var ctx = new Context(this, env);
|
||||
return ctx.compile(filename, raw).call(new Context(this, env), thisArg, args);
|
||||
}, micro);
|
||||
}
|
||||
|
||||
public Engine() {
|
||||
}
|
||||
}
|
||||
128
src/java/me/topchetoeu/jscript/core/engine/Environment.java
Normal file
128
src/java/me/topchetoeu/jscript/core/engine/Environment.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.core.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.core.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Environment implements Extensions {
|
||||
|
||||
public static final HashMap<String, Symbol> symbols = new HashMap<>();
|
||||
|
||||
public static final Symbol WRAPPERS = Symbol.get("Environment.wrappers");
|
||||
public static final Symbol COMPILE_FUNC = Symbol.get("Environment.compile");
|
||||
|
||||
public static final Symbol REGEX_CONSTR = Symbol.get("Environment.regexConstructor");
|
||||
public static final Symbol STACK = Symbol.get("Environment.stack");
|
||||
public static final Symbol MAX_STACK_COUNT = Symbol.get("Environment.maxStackCount");
|
||||
public static final Symbol HIDE_STACK = Symbol.get("Environment.hideStack");
|
||||
|
||||
public static final Symbol OBJECT_PROTO = Symbol.get("Environment.objectPrototype");
|
||||
public static final Symbol FUNCTION_PROTO = Symbol.get("Environment.functionPrototype");
|
||||
public static final Symbol ARRAY_PROTO = Symbol.get("Environment.arrayPrototype");
|
||||
public static final Symbol BOOL_PROTO = Symbol.get("Environment.boolPrototype");
|
||||
public static final Symbol NUMBER_PROTO = Symbol.get("Environment.numberPrototype");
|
||||
public static final Symbol STRING_PROTO = Symbol.get("Environment.stringPrototype");
|
||||
public static final Symbol SYMBOL_PROTO = Symbol.get("Environment.symbolPrototype");
|
||||
public static final Symbol ERROR_PROTO = Symbol.get("Environment.errorPrototype");
|
||||
public static final Symbol SYNTAX_ERR_PROTO = Symbol.get("Environment.syntaxErrorPrototype");
|
||||
public static final Symbol TYPE_ERR_PROTO = Symbol.get("Environment.typeErrorPrototype");
|
||||
public static final Symbol RANGE_ERR_PROTO = Symbol.get("Environment.rangeErrorPrototype");
|
||||
|
||||
private HashMap<Symbol, Object> data = new HashMap<>();
|
||||
|
||||
public GlobalScope global;
|
||||
public WrapperProvider wrappers;
|
||||
|
||||
@Override public <T> void add(Symbol key, T obj) {
|
||||
data.put(key, obj);
|
||||
}
|
||||
@Override public <T> T get(Symbol key) {
|
||||
return (T)data.get(key);
|
||||
}
|
||||
@Override public boolean remove(Symbol key) {
|
||||
if (data.containsKey(key)) {
|
||||
data.remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override public boolean has(Symbol key) {
|
||||
return data.containsKey(key);
|
||||
}
|
||||
@Override public Iterable<Symbol> keys() {
|
||||
return data.keySet();
|
||||
}
|
||||
|
||||
public static FunctionValue compileFunc(Extensions ext) {
|
||||
return ext.init(COMPILE_FUNC, new NativeFunction("compile", args -> {
|
||||
var source = args.getString(0);
|
||||
var filename = args.getString(1);
|
||||
var env = Values.wrapper(Values.getMember(args.ctx, args.get(2), Symbol.get("env")), Environment.class);
|
||||
var isDebug = DebugContext.enabled(args.ctx);
|
||||
var res = new ObjectValue();
|
||||
|
||||
var target = Parsing.compile(env, Filename.parse(filename), source);
|
||||
Engine.functions.putAll(target.functions);
|
||||
Engine.functions.remove(0l);
|
||||
|
||||
res.defineProperty(args.ctx, "function", target.func(env));
|
||||
res.defineProperty(args.ctx, "mapChain", new ArrayValue());
|
||||
|
||||
if (isDebug) res.defineProperty(
|
||||
args.ctx, "breakpoints",
|
||||
ArrayValue.of(args.ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))
|
||||
);
|
||||
|
||||
return res;
|
||||
}));
|
||||
}
|
||||
public static FunctionValue regexConstructor(Extensions ext) {
|
||||
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", args -> {
|
||||
throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx.environment, args.ctx.engine);
|
||||
}));
|
||||
}
|
||||
|
||||
public Environment copy() {
|
||||
var res = new Environment(null, global);
|
||||
|
||||
res.wrappers = wrappers.fork(res);
|
||||
res.global = global;
|
||||
res.data.putAll(data);
|
||||
|
||||
return res;
|
||||
}
|
||||
public Environment child() {
|
||||
var res = copy();
|
||||
res.global = res.global.globalChild();
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Engine engine) {
|
||||
return new Context(engine, this);
|
||||
}
|
||||
|
||||
public Environment(WrapperProvider nativeConverter, GlobalScope global) {
|
||||
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
|
||||
if (global == null) global = new GlobalScope();
|
||||
|
||||
this.wrappers = nativeConverter;
|
||||
this.global = global;
|
||||
}
|
||||
public Environment() {
|
||||
this(null, null);
|
||||
}
|
||||
}
|
||||
81
src/java/me/topchetoeu/jscript/core/engine/EventLoop.java
Normal file
81
src/java/me/topchetoeu/jscript/core/engine/EventLoop.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import me.topchetoeu.jscript.common.ResultRunnable;
|
||||
import me.topchetoeu.jscript.common.events.Awaitable;
|
||||
import me.topchetoeu.jscript.common.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.core.exceptions.InterruptException;
|
||||
|
||||
public class EventLoop {
|
||||
private static class Task implements Comparable<Task> {
|
||||
public final ResultRunnable<?> runnable;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final boolean micro;
|
||||
|
||||
public Task(ResultRunnable<?> runnable, boolean micro) {
|
||||
this.runnable = runnable;
|
||||
this.micro = micro;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Task other) {
|
||||
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
|
||||
private Thread thread;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Awaitable<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
|
||||
var msg = new Task(runnable, micro);
|
||||
tasks.add(msg);
|
||||
return (Awaitable<T>)msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(Runnable runnable, boolean micro) {
|
||||
return pushMsg(() -> { runnable.run(); return null; }, micro);
|
||||
}
|
||||
|
||||
public void run(boolean untilEmpty) {
|
||||
while (!untilEmpty || !tasks.isEmpty()) {
|
||||
try {
|
||||
var task = tasks.take();
|
||||
|
||||
try {
|
||||
task.notifier.next(task.runnable.run());
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (e instanceof InterruptException) throw e;
|
||||
task.notifier.error(e);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Thread thread() {
|
||||
return thread;
|
||||
}
|
||||
public Thread start() {
|
||||
if (thread == null) {
|
||||
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
|
||||
thread.start();
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
public void stop() {
|
||||
if (thread != null) thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
|
||||
public boolean inLoopThread() {
|
||||
return Thread.currentThread() == thread;
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return this.thread != null;
|
||||
}
|
||||
}
|
||||
34
src/java/me/topchetoeu/jscript/core/engine/Extensions.java
Normal file
34
src/java/me/topchetoeu/jscript/core/engine/Extensions.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
||||
|
||||
public interface Extensions {
|
||||
<T> T get(Symbol key);
|
||||
<T> void add(Symbol key, T obj);
|
||||
Iterable<Symbol> keys();
|
||||
|
||||
boolean has(Symbol key);
|
||||
boolean remove(Symbol key);
|
||||
|
||||
default boolean hasNotNull(Symbol key) {
|
||||
return has(key) && get(key) != null;
|
||||
}
|
||||
|
||||
default <T> T get(Symbol key, T defaultVal) {
|
||||
if (has(key)) return get(key);
|
||||
else return defaultVal;
|
||||
}
|
||||
|
||||
default <T> T init(Symbol key, T val) {
|
||||
if (has(key)) return get(key);
|
||||
else {
|
||||
add(key, val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
default void addAll(Extensions source) {
|
||||
for (var key : source.keys()) {
|
||||
add(key, source.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
public enum Operation {
|
||||
INSTANCEOF(2, false),
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.topchetoeu.jscript.core.engine;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
|
||||
public interface WrapperProvider {
|
||||
public ObjectValue getProto(Class<?> obj);
|
||||
public ObjectValue getNamespace(Class<?> obj);
|
||||
public FunctionValue getConstr(Class<?> obj);
|
||||
|
||||
public WrapperProvider fork(Environment env);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package me.topchetoeu.jscript.core.engine.debug;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.Extensions;
|
||||
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.utils.mapping.SourceMap;
|
||||
|
||||
public class DebugContext implements DebugController {
|
||||
public static final Symbol ENV_KEY = Symbol.get("Engine.debug");
|
||||
public static final Symbol IGNORE = Symbol.get("Engine.ignoreDebug");
|
||||
|
||||
private HashMap<Filename, String> sources;
|
||||
private HashMap<Filename, TreeSet<Location>> bpts;
|
||||
private HashMap<Filename, SourceMap> maps;
|
||||
private DebugController debugger;
|
||||
|
||||
public boolean attachDebugger(DebugController debugger) {
|
||||
if (this.debugger != null) return false;
|
||||
|
||||
if (sources != null) {
|
||||
for (var source : sources.entrySet()) debugger.onSource(
|
||||
source.getKey(), source.getValue(),
|
||||
bpts.get(source.getKey()),
|
||||
maps.get(source.getKey())
|
||||
);
|
||||
}
|
||||
|
||||
this.debugger = debugger;
|
||||
return true;
|
||||
}
|
||||
public boolean detachDebugger() {
|
||||
this.debugger = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public DebugController debugger() {
|
||||
if (debugger == null) return DebugController.empty();
|
||||
else return debugger;
|
||||
}
|
||||
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public void onFramePush(Context ctx, CodeFrame frame) {
|
||||
if (debugger != null) debugger.onFramePush(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (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 (debugger != null) debugger.onSource(filename, source, breakpoints, map);
|
||||
if (sources != null) sources.put(filename, source);
|
||||
if (bpts != null) bpts.put(filename, breakpoints);
|
||||
if (maps != null) maps.put(filename, map);
|
||||
}
|
||||
|
||||
public Location mapToCompiled(Location location) {
|
||||
if (maps == null) return location;
|
||||
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toCompiled(location);
|
||||
}
|
||||
public Location mapToOriginal(Location location) {
|
||||
if (maps == null) return location;
|
||||
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toOriginal(location);
|
||||
}
|
||||
|
||||
private DebugContext(boolean enabled) {
|
||||
if (enabled) {
|
||||
sources = new HashMap<>();
|
||||
bpts = new HashMap<>();
|
||||
maps = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public DebugContext() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public static boolean enabled(Extensions exts) {
|
||||
return exts.hasNotNull(ENV_KEY) && !exts.has(IGNORE);
|
||||
}
|
||||
public static DebugContext get(Extensions exts) {
|
||||
if (enabled(exts)) return exts.get(ENV_KEY);
|
||||
else return new DebugContext(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package me.topchetoeu.jscript.core.engine.debug;
|
||||
|
||||
import java.util.TreeSet;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.utils.mapping.SourceMap;
|
||||
|
||||
public interface DebugController {
|
||||
/**
|
||||
* Called when a script has been loaded
|
||||
* @param filename The name of the source
|
||||
* @param source The name of the source
|
||||
* @param breakpoints A set of all the breakpointable locations in this source
|
||||
* @param map The source map associated with this file. null if this source map isn't mapped
|
||||
*/
|
||||
void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
|
||||
|
||||
/**
|
||||
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
|
||||
* This function might pause in order to await debugging commands.
|
||||
* @param ctx The context of execution
|
||||
* @param frame The frame in which execution is occuring
|
||||
* @param instruction The instruction which was or will be executed
|
||||
* @param loc The most recent location the code frame has been at
|
||||
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
|
||||
* @param error The error that the instruction threw, null if none
|
||||
* @param caught Whether or not the error has been caught
|
||||
* @return Whether or not the frame should restart
|
||||
*/
|
||||
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
|
||||
|
||||
/**
|
||||
* Called immediatly before a frame has been pushed on the frame stack.
|
||||
* This function might pause in order to await debugging commands.
|
||||
* @param ctx The context of execution
|
||||
* @param frame The code frame which was pushed
|
||||
*/
|
||||
void onFramePush(Context ctx, CodeFrame frame);
|
||||
/**
|
||||
* Called immediatly after a frame has been popped out of the frame stack.
|
||||
* This function might pause in order to await debugging commands.
|
||||
* @param ctx The context of execution
|
||||
* @param frame The code frame which was popped out
|
||||
*/
|
||||
void onFramePop(Context ctx, CodeFrame frame);
|
||||
|
||||
public static DebugController empty() {
|
||||
return new DebugController () {
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) { }
|
||||
@Override public void onFramePush(Context ctx, CodeFrame frame) { }
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) { }
|
||||
};
|
||||
}
|
||||
}
|
||||
332
src/java/me/topchetoeu/jscript/core/engine/frame/CodeFrame.java
Normal file
332
src/java/me/topchetoeu/jscript/core/engine/frame/CodeFrame.java
Normal file
@@ -0,0 +1,332 @@
|
||||
package me.topchetoeu.jscript.core.engine.frame;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.core.engine.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.ScopeValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.core.exceptions.InterruptException;
|
||||
|
||||
public class CodeFrame {
|
||||
public static enum TryState {
|
||||
TRY,
|
||||
CATCH,
|
||||
FINALLY,
|
||||
}
|
||||
|
||||
public static class TryCtx {
|
||||
public final int start, end, catchStart, finallyStart;
|
||||
public final int restoreStackPtr;
|
||||
public final TryState state;
|
||||
public final EngineException error;
|
||||
public final PendingResult result;
|
||||
|
||||
public boolean hasCatch() { return catchStart >= 0; }
|
||||
public boolean hasFinally() { return finallyStart >= 0; }
|
||||
|
||||
public boolean inBounds(int ptr) {
|
||||
return ptr >= start && ptr < end;
|
||||
}
|
||||
|
||||
public void setCause(EngineException target) {
|
||||
if (error != null) target.setCause(error);
|
||||
}
|
||||
public TryCtx _catch(EngineException e) {
|
||||
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
|
||||
}
|
||||
public TryCtx _finally(PendingResult res) {
|
||||
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public final LocalScope scope;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||
public final CodeFunction function;
|
||||
public final Context ctx;
|
||||
public Object[] stack = new Object[32];
|
||||
public int stackPtr = 0;
|
||||
public int codePtr = 0;
|
||||
public boolean jumpFlag = false, popTryFlag = false;
|
||||
private Location prevLoc = null;
|
||||
|
||||
public ObjectValue getLocalScope(boolean props) {
|
||||
var names = new String[scope.locals.length];
|
||||
|
||||
for (int i = 0; i < scope.locals.length; i++) {
|
||||
var name = "local_" + (i - 2);
|
||||
|
||||
if (i == 0) name = "this";
|
||||
else if (i == 1) name = "arguments";
|
||||
else if (i < function.localNames.length) name = function.localNames[i];
|
||||
|
||||
names[i] = name;
|
||||
}
|
||||
|
||||
return new ScopeValue(scope.locals, names);
|
||||
}
|
||||
public ObjectValue getCaptureScope(boolean props) {
|
||||
var names = new String[scope.captures.length];
|
||||
|
||||
for (int i = 0; i < scope.captures.length; i++) {
|
||||
var name = "capture_" + (i - 2);
|
||||
if (i < function.captureNames.length) name = function.captureNames[i];
|
||||
names[i] = name;
|
||||
}
|
||||
|
||||
return new ScopeValue(scope.captures, names);
|
||||
}
|
||||
public ObjectValue getValStackScope() {
|
||||
return new ObjectValue() {
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
var i = (int)Values.toNumber(ctx, key);
|
||||
if (i < 0 || i >= stackPtr) return null;
|
||||
else return stack[i];
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
for (var i = 0; i < stackPtr; i++) res.add(i);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void addTry(int start, int end, int catchStart, int finallyStart) {
|
||||
var err = tryStack.empty() ? null : tryStack.peek().error;
|
||||
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
|
||||
|
||||
tryStack.add(res);
|
||||
}
|
||||
|
||||
public Object peek() {
|
||||
return peek(0);
|
||||
}
|
||||
public Object peek(int offset) {
|
||||
if (stackPtr <= offset) return null;
|
||||
else return stack[stackPtr - 1 - offset];
|
||||
}
|
||||
public Object pop() {
|
||||
if (stackPtr == 0) return null;
|
||||
return stack[--stackPtr];
|
||||
}
|
||||
public Object[] take(int n) {
|
||||
int srcI = stackPtr - n;
|
||||
if (srcI < 0) srcI = 0;
|
||||
|
||||
int dstI = n + srcI - stackPtr;
|
||||
int copyN = stackPtr - srcI;
|
||||
|
||||
Object[] res = new Object[n];
|
||||
System.arraycopy(stack, srcI, res, dstI, copyN);
|
||||
stackPtr -= copyN;
|
||||
|
||||
return res;
|
||||
}
|
||||
public void push(Object val) {
|
||||
if (stack.length <= stackPtr) {
|
||||
var newStack = new Object[stack.length * 2];
|
||||
System.arraycopy(stack, 0, newStack, 0, stack.length);
|
||||
stack = newStack;
|
||||
}
|
||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
public Object next(Object value, Object returnValue, EngineException error) {
|
||||
if (value != Values.NO_RETURN) push(value);
|
||||
|
||||
Instruction instr = null;
|
||||
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
|
||||
|
||||
if (returnValue == Values.NO_RETURN && error == null) {
|
||||
try {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||
|
||||
if (instr == null) returnValue = null;
|
||||
else {
|
||||
DebugContext.get(ctx).onInstruction(ctx, this, instr, Values.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; }
|
||||
}
|
||||
|
||||
while (!tryStack.empty()) {
|
||||
var tryCtx = tryStack.peek();
|
||||
TryCtx newCtx = null;
|
||||
|
||||
if (error != null) {
|
||||
tryCtx.setCause(error);
|
||||
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
||||
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
|
||||
}
|
||||
else if (returnValue != Values.NO_RETURN) {
|
||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
|
||||
}
|
||||
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
|
||||
}
|
||||
else if (!this.popTryFlag) newCtx = tryCtx;
|
||||
|
||||
if (newCtx != null) {
|
||||
if (newCtx != tryCtx) {
|
||||
switch (newCtx.state) {
|
||||
case CATCH:
|
||||
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
break;
|
||||
case FINALLY:
|
||||
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
codePtr = tryCtx.finallyStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
default:
|
||||
}
|
||||
|
||||
tryStack.pop();
|
||||
tryStack.push(newCtx);
|
||||
}
|
||||
error = null;
|
||||
returnValue = Values.NO_RETURN;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
popTryFlag = false;
|
||||
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
|
||||
codePtr = tryCtx.finallyStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
tryStack.pop();
|
||||
tryStack.push(tryCtx._finally(null));
|
||||
break;
|
||||
}
|
||||
else {
|
||||
tryStack.pop();
|
||||
codePtr = tryCtx.end;
|
||||
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
|
||||
if (!jumpFlag && returnValue == Values.NO_RETURN && error == null) {
|
||||
if (tryCtx.result.isJump) {
|
||||
codePtr = tryCtx.result.ptr;
|
||||
jumpFlag = true;
|
||||
}
|
||||
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
||||
if (error == null && tryCtx.result.isThrow) {
|
||||
error = tryCtx.result.error;
|
||||
}
|
||||
}
|
||||
if (error != null) tryCtx.setCause(error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
var caught = false;
|
||||
|
||||
for (var frame : ctx.frames()) {
|
||||
for (var tryCtx : frame.tryStack) {
|
||||
if (tryCtx.state == TryState.TRY) caught = true;
|
||||
}
|
||||
}
|
||||
|
||||
DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught);
|
||||
throw error;
|
||||
}
|
||||
if (returnValue != Values.NO_RETURN) {
|
||||
DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public void onPush() {
|
||||
DebugContext.get(ctx).onFramePush(ctx, this);
|
||||
}
|
||||
public void onPop() {
|
||||
DebugContext.get(ctx.parent).onFramePop(ctx.parent, this);
|
||||
}
|
||||
|
||||
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
|
||||
this.args = args.clone();
|
||||
this.scope = new LocalScope(func.localsN, func.captures);
|
||||
this.scope.get(0).set(null, thisArg);
|
||||
var argsObj = new ArrayValue();
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
argsObj.set(ctx, i, args[i]);
|
||||
}
|
||||
this.scope.get(1).value = argsObj;
|
||||
|
||||
this.thisArg = thisArg;
|
||||
this.function = func;
|
||||
this.ctx = ctx.pushFrame(this);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
public enum ConvertHint {
|
||||
TOSTRING,
|
||||
VALUEOF,
|
||||
package me.topchetoeu.jscript.core.engine.frame;
|
||||
|
||||
public enum ConvertHint {
|
||||
TOSTRING,
|
||||
VALUEOF,
|
||||
}
|
||||
@@ -1,380 +1,355 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class Runners {
|
||||
public static final Object NO_RETURN = new Object();
|
||||
|
||||
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return frame.pop();
|
||||
}
|
||||
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
throw new EngineException(frame.pop());
|
||||
}
|
||||
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
throw EngineException.ofSyntax((String)instr.get(0));
|
||||
}
|
||||
|
||||
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||
return Values.call(ctx, func, thisArg, args);
|
||||
}
|
||||
|
||||
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var func = frame.pop();
|
||||
var thisArg = frame.pop();
|
||||
|
||||
frame.push(ctx, call(ctx, func, thisArg, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
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++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var name = (String)instr.get(0);
|
||||
ctx.env.global.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var setter = frame.pop();
|
||||
var getter = frame.pop();
|
||||
var name = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined.");
|
||||
if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object.");
|
||||
Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false);
|
||||
|
||||
frame.push(ctx, obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var type = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!Values.isPrimitive(type)) {
|
||||
var proto = Values.getMember(ctx, type, "prototype");
|
||||
frame.push(ctx, Values.isInstanceOf(ctx, obj, proto));
|
||||
}
|
||||
else {
|
||||
frame.push(ctx, false);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var val = frame.pop();
|
||||
|
||||
var arr = new ObjectValue();
|
||||
var i = 0;
|
||||
|
||||
var members = Values.getMembers(ctx, val, false, false);
|
||||
Collections.reverse(members);
|
||||
for (var el : members) {
|
||||
if (el instanceof Symbol) continue;
|
||||
arr.defineProperty(ctx, i++, el);
|
||||
}
|
||||
|
||||
arr.defineProperty(ctx, "length", i);
|
||||
|
||||
frame.push(ctx, arr);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.addTry(instr.get(0), instr.get(1), instr.get(2));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int offset = instr.get(0), count = instr.get(1);
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
frame.push(ctx, frame.peek(offset + count - 1));
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execMove(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int offset = instr.get(0), count = instr.get(1);
|
||||
|
||||
var tmp = frame.take(offset);
|
||||
var res = frame.take(count);
|
||||
|
||||
for (var i = 0; i < offset; i++) frame.push(ctx, tmp[i]);
|
||||
for (var i = 0; i < count; i++) frame.push(ctx, res[i]);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, null);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, instr.get(0));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i));
|
||||
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, new ObjectValue());
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.env.global.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var res = new ArrayValue();
|
||||
res.setSize(instr.get(0));
|
||||
frame.push(ctx, res);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
long id = (Long)instr.get(0);
|
||||
int localsN = (Integer)instr.get(1);
|
||||
int len = (Integer)instr.get(2);
|
||||
var captures = new ValueVariable[instr.params.length - 3];
|
||||
|
||||
for (var i = 3; i < instr.params.length; i++) {
|
||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var body = ctx.message.engine.functions.get(id);
|
||||
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body);
|
||||
|
||||
frame.push(ctx, func);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
try {
|
||||
frame.push(ctx, Values.getMember(ctx, obj, key));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType(e.getMessage());
|
||||
}
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.push(ctx, instr.get(0));
|
||||
return execLoadMember(ctx, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.pop();
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var val = frame.pop();
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
|
||||
if ((boolean)instr.get(0)) frame.push(ctx, val);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
if (Values.toBoolean(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
if (Values.not(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var obj = frame.pop();
|
||||
var index = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.hasMember(ctx, obj, index, false));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
String name = instr.get(0);
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (ctx.env.global.has(ctx, name)) {
|
||||
obj = ctx.env.global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
else obj = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.type(obj));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
if (instr.is(0, "dbg_names")) {
|
||||
var names = new String[instr.params.length - 1];
|
||||
for (var i = 0; i < instr.params.length - 1; i++) {
|
||||
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
|
||||
names[i] = (String)instr.params[i + 1];
|
||||
}
|
||||
frame.scope.setNames(names);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var key = frame.pop();
|
||||
var val = frame.pop();
|
||||
|
||||
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
||||
frame.push(ctx, true);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
Operation op = instr.get(0);
|
||||
var args = new Object[op.operands];
|
||||
|
||||
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.operation(ctx, op, args));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(ctx, instr, frame);
|
||||
case RETURN: return execReturn(ctx, instr, frame);
|
||||
case THROW: return execThrow(ctx, instr, frame);
|
||||
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
||||
case CALL: return execCall(ctx, instr, frame);
|
||||
case CALL_NEW: return execCallNew(ctx, instr, frame);
|
||||
case TRY: return execTry(ctx, instr, frame);
|
||||
|
||||
case DUP: return execDup(ctx, instr, frame);
|
||||
case MOVE: return execMove(ctx, instr, frame);
|
||||
case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
|
||||
case LOAD_VAR: return execLoadVar(ctx, instr, frame);
|
||||
case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
|
||||
case LOAD_ARR: return execLoadArr(ctx, instr, frame);
|
||||
case LOAD_FUNC: return execLoadFunc(ctx, instr, frame);
|
||||
case LOAD_MEMBER: return execLoadMember(ctx, instr, frame);
|
||||
case LOAD_VAL_MEMBER: return execLoadKeyMember(ctx, instr, frame);
|
||||
case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame);
|
||||
case LOAD_GLOB: return execLoadGlob(ctx, instr, frame);
|
||||
|
||||
case DISCARD: return execDiscard(ctx, instr, frame);
|
||||
case STORE_MEMBER: return execStoreMember(ctx, instr, frame);
|
||||
case STORE_VAR: return execStoreVar(ctx, instr, frame);
|
||||
case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame);
|
||||
case MAKE_VAR: return execMakeVar(ctx, instr, frame);
|
||||
|
||||
case KEYS: return execKeys(ctx, instr, frame);
|
||||
case DEF_PROP: return execDefProp(ctx, instr, frame);
|
||||
case TYPEOF: return execTypeof(ctx, instr, frame);
|
||||
case DELETE: return execDelete(ctx, instr, frame);
|
||||
|
||||
case JMP: return execJmp(ctx, instr, frame);
|
||||
case JMP_IF: return execJmpIf(ctx, instr, frame);
|
||||
case JMP_IFN: return execJmpIfNot(ctx, instr, frame);
|
||||
|
||||
case OPERATION: return execOperation(ctx, instr, frame);
|
||||
|
||||
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.frame;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.Engine;
|
||||
import me.topchetoeu.jscript.core.engine.Environment;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
|
||||
public class Runners {
|
||||
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return frame.pop();
|
||||
}
|
||||
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
throw new EngineException(frame.pop());
|
||||
}
|
||||
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
throw EngineException.ofSyntax((String)instr.get(0));
|
||||
}
|
||||
|
||||
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var func = frame.pop();
|
||||
var thisArg = frame.pop();
|
||||
|
||||
frame.push(Values.call(ctx, func, thisArg, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
frame.push(Values.callNew(ctx, funcObj, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
ctx.environment.global.define(name);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var setter = frame.pop();
|
||||
var getter = frame.pop();
|
||||
var name = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
if (setter != null && !(setter instanceof FunctionValue)) throw EngineException.ofType("Setter must be a function or undefined.");
|
||||
if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object.");
|
||||
Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false);
|
||||
|
||||
frame.push(obj);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var type = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!Values.isPrimitive(type)) {
|
||||
var proto = Values.getMember(ctx, type, "prototype");
|
||||
frame.push(Values.isInstanceOf(ctx, obj, proto));
|
||||
}
|
||||
else {
|
||||
frame.push(false);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
|
||||
var members = Values.getMembers(ctx, val, false, false);
|
||||
Collections.reverse(members);
|
||||
|
||||
frame.push(null);
|
||||
|
||||
for (var el : members) {
|
||||
if (el instanceof Symbol) continue;
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "value", el);
|
||||
frame.push(obj);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int start = frame.codePtr + 1;
|
||||
int catchStart = (int)instr.get(0);
|
||||
int finallyStart = (int)instr.get(1);
|
||||
if (finallyStart >= 0) finallyStart += start;
|
||||
if (catchStart >= 0) catchStart += start;
|
||||
int end = (int)instr.get(2) + start;
|
||||
frame.addTry(start, end, catchStart, finallyStart);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.popTryFlag = true;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int count = instr.get(0);
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
frame.push(frame.peek(count - 1));
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(null);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(instr.get(0));
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx.environment.global.get(ctx, (String)i));
|
||||
else frame.push(frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(new ObjectValue());
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx.environment.global.obj);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var res = new ArrayValue();
|
||||
res.setSize(instr.get(0));
|
||||
frame.push(res);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
long id = (Long)instr.get(0);
|
||||
var captures = new ValueVariable[instr.params.length - 1];
|
||||
|
||||
for (var i = 1; i < instr.params.length; i++) {
|
||||
captures[i - 1] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var func = new CodeFunction(ctx.environment, "", Engine.functions.get(id), captures);
|
||||
|
||||
frame.push(func);
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
try {
|
||||
frame.push(Values.getMember(ctx, obj, key));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType(e.getMessage());
|
||||
}
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(instr.get(0));
|
||||
return execLoadMember(ctx, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (ctx.hasNotNull(Environment.REGEX_CONSTR)) {
|
||||
frame.push(Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1)));
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofSyntax("Regex is not supported.");
|
||||
}
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.pop();
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
|
||||
if ((boolean)instr.get(0)) frame.push(val);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) ctx.environment.global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (Values.toBoolean(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (Values.not(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var obj = frame.pop();
|
||||
var index = frame.pop();
|
||||
|
||||
frame.push(Values.hasMember(ctx, obj, index, false));
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
String name = instr.get(0);
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (ctx.environment.global.has(ctx, name)) {
|
||||
obj = ctx.environment.global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
else obj = frame.pop();
|
||||
|
||||
frame.push(Values.type(obj));
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var key = frame.pop();
|
||||
var val = frame.pop();
|
||||
|
||||
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
Operation op = instr.get(0);
|
||||
var args = new Object[op.operands];
|
||||
|
||||
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
|
||||
|
||||
frame.push(Values.operation(ctx, op, args));
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(ctx, instr, frame);
|
||||
case RETURN: return execReturn(ctx, instr, frame);
|
||||
case THROW: return execThrow(ctx, instr, frame);
|
||||
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
||||
case CALL: return execCall(ctx, instr, frame);
|
||||
case CALL_NEW: return execCallNew(ctx, instr, frame);
|
||||
case TRY_START: return execTryStart(ctx, instr, frame);
|
||||
case TRY_END: return execTryEnd(ctx, instr, frame);
|
||||
|
||||
case DUP: return execDup(ctx, instr, frame);
|
||||
case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
|
||||
case LOAD_VAR: return execLoadVar(ctx, instr, frame);
|
||||
case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
|
||||
case LOAD_ARR: return execLoadArr(ctx, instr, frame);
|
||||
case LOAD_FUNC: return execLoadFunc(ctx, instr, frame);
|
||||
case LOAD_MEMBER: return execLoadMember(ctx, instr, frame);
|
||||
case LOAD_VAL_MEMBER: return execLoadKeyMember(ctx, instr, frame);
|
||||
case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame);
|
||||
case LOAD_GLOB: return execLoadGlob(ctx, instr, frame);
|
||||
|
||||
case DISCARD: return execDiscard(ctx, instr, frame);
|
||||
case STORE_MEMBER: return execStoreMember(ctx, instr, frame);
|
||||
case STORE_VAR: return execStoreVar(ctx, instr, frame);
|
||||
case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame);
|
||||
case MAKE_VAR: return execMakeVar(ctx, instr, frame);
|
||||
|
||||
case KEYS: return execKeys(ctx, instr, frame);
|
||||
case DEF_PROP: return execDefProp(ctx, instr, frame);
|
||||
case TYPEOF: return execTypeof(ctx, instr, frame);
|
||||
case DELETE: return execDelete(ctx, instr, frame);
|
||||
|
||||
case JMP: return execJmp(ctx, instr, frame);
|
||||
case JMP_IF: return execJmpIf(ctx, instr, frame);
|
||||
case JMP_IFN: return execJmpIfNot(ctx, instr, frame);
|
||||
|
||||
case OPERATION: return execOperation(ctx, instr, frame);
|
||||
|
||||
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package me.topchetoeu.jscript.core.engine.scope;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
|
||||
public class GlobalScope implements ScopeRecord {
|
||||
public final ObjectValue obj;
|
||||
|
||||
public boolean has(Context ctx, String name) {
|
||||
return Values.hasMember(null, obj, name, false);
|
||||
}
|
||||
public Object getKey(String name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
public GlobalScope globalChild() {
|
||||
var obj = new ObjectValue();
|
||||
Values.setPrototype(null, obj, this.obj);
|
||||
return new GlobalScope(obj);
|
||||
}
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord();
|
||||
}
|
||||
|
||||
public Object define(String name) {
|
||||
if (Values.hasMember(Context.NULL, obj, name, false)) return name;
|
||||
obj.defineProperty(Context.NULL, name, null);
|
||||
return name;
|
||||
}
|
||||
public void define(String name, Variable val) {
|
||||
obj.defineProperty(Context.NULL, name,
|
||||
new NativeFunction("get " + name, args -> val.get(args.ctx)),
|
||||
new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }),
|
||||
true, true
|
||||
);
|
||||
}
|
||||
public void define(Context ctx, String name, boolean readonly, Object val) {
|
||||
obj.defineProperty(ctx, name, val, readonly, true, true);
|
||||
}
|
||||
public void define(String ...names) {
|
||||
for (var n : names) define(n);
|
||||
}
|
||||
public void define(boolean readonly, FunctionValue val) {
|
||||
define(null, val.name, readonly, val);
|
||||
}
|
||||
|
||||
public Object get(Context ctx, String name) {
|
||||
if (!Values.hasMember(ctx, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
else return Values.getMember(ctx, obj, name);
|
||||
}
|
||||
public void set(Context ctx, String name, Object val) {
|
||||
if (!Values.hasMember(ctx, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
if (!Values.setMember(ctx, obj, name, val)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
|
||||
}
|
||||
|
||||
public Set<String> keys() {
|
||||
var res = new HashSet<String>();
|
||||
|
||||
for (var key : keys()) {
|
||||
if (key instanceof String) res.add((String)key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public GlobalScope() {
|
||||
this.obj = new ObjectValue();
|
||||
}
|
||||
public GlobalScope(ObjectValue val) {
|
||||
this.obj = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.core.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScope {
|
||||
public final ValueVariable[] captures;
|
||||
public final ValueVariable[] locals;
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
||||
|
||||
public int size() {
|
||||
return captures.length + locals.length;
|
||||
}
|
||||
|
||||
public LocalScope(int n, ValueVariable[] captures) {
|
||||
locals = new ValueVariable[n];
|
||||
this.captures = captures;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
locals[i] = new ValueVariable(false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,77 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
public final GlobalScope global;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalScopeRecord parent() { return parent; }
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this, global);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
|
||||
public int[] getCaptures() {
|
||||
var buff = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var name : captures) {
|
||||
var index = parent.getKey(name);
|
||||
if (index instanceof Integer) buff[i++] = (int)index;
|
||||
}
|
||||
|
||||
var res = new int[i];
|
||||
System.arraycopy(buff, 0, res, 0, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object getKey(String name) {
|
||||
var capI = captures.indexOf(name);
|
||||
var locI = locals.lastIndexOf(name);
|
||||
if (locI >= 0) return locI;
|
||||
if (capI >= 0) return ~capI;
|
||||
if (parent != null) {
|
||||
var res = parent.getKey(name);
|
||||
if (res != null && res instanceof Integer) {
|
||||
captures.add(name);
|
||||
return -captures.size();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
public boolean has(Context ctx, String name) throws InterruptedException {
|
||||
return
|
||||
global.has(ctx, name) ||
|
||||
locals.contains(name) ||
|
||||
parent != null && parent.has(ctx, name);
|
||||
}
|
||||
public Object define(String name, boolean force) {
|
||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
public Object define(String name) {
|
||||
return define(name, false);
|
||||
}
|
||||
public void undefine() {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord(GlobalScope global) {
|
||||
this.parent = null;
|
||||
this.global = global;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
|
||||
this.parent = parent;
|
||||
this.global = global;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] captures() {
|
||||
return captures.toArray(String[]::new);
|
||||
}
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
|
||||
public int[] getCaptures() {
|
||||
var buff = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var name : captures) {
|
||||
var index = parent.getKey(name);
|
||||
if (index instanceof Integer) buff[i++] = (int)index;
|
||||
}
|
||||
|
||||
var res = new int[i];
|
||||
System.arraycopy(buff, 0, res, 0, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object getKey(String name) {
|
||||
var capI = captures.indexOf(name);
|
||||
var locI = locals.lastIndexOf(name);
|
||||
if (locI >= 0) return locI;
|
||||
if (capI >= 0) return ~capI;
|
||||
if (parent != null) {
|
||||
var res = parent.getKey(name);
|
||||
if (res != null && res instanceof Integer) {
|
||||
captures.add(name);
|
||||
return -captures.size();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
public Object define(String name, boolean force) {
|
||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
public Object define(String name) {
|
||||
return define(name, false);
|
||||
}
|
||||
public void undefine() {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord() {
|
||||
this.parent = null;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public ScopeRecord parent();
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.scope;
|
||||
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
@@ -1,28 +1,28 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class ValueVariable implements Variable {
|
||||
public boolean readonly;
|
||||
public Object value;
|
||||
|
||||
@Override
|
||||
public boolean readonly() { return readonly; }
|
||||
|
||||
@Override
|
||||
public Object get(Context ctx) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Context ctx, Object val) {
|
||||
if (readonly) return;
|
||||
this.value = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
public ValueVariable(boolean readonly, Object val) {
|
||||
this.readonly = readonly;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.scope;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
|
||||
public class ValueVariable implements Variable {
|
||||
public boolean readonly;
|
||||
public Object value;
|
||||
|
||||
@Override
|
||||
public boolean readonly() { return readonly; }
|
||||
|
||||
@Override
|
||||
public Object get(Context ctx) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Context ctx, Object val) {
|
||||
if (readonly) return;
|
||||
this.value = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
public ValueVariable(boolean readonly, Object val) {
|
||||
this.readonly = readonly;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.topchetoeu.jscript.core.engine.scope;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
|
||||
public interface Variable {
|
||||
Object get(Context ctx);
|
||||
default boolean readonly() { return true; }
|
||||
default void set(Context ctx, Object val) { }
|
||||
}
|
||||
@@ -1,218 +1,222 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
// TODO: Make methods generic
|
||||
public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
private static final Object UNDEFINED = new Object();
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
private void alloc(int index) {
|
||||
if (index < values.length) return;
|
||||
if (index < values.length * 2) index = values.length * 2;
|
||||
|
||||
var arr = new Object[index];
|
||||
System.arraycopy(values, 0, arr, 0, values.length);
|
||||
values = arr;
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
public boolean setSize(int val) {
|
||||
if (val < 0) return false;
|
||||
if (size > val) shrink(size - val);
|
||||
else {
|
||||
alloc(val);
|
||||
size = val;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Object get(int i) {
|
||||
if (i < 0 || i >= size) return null;
|
||||
var res = values[i];
|
||||
if (res == UNDEFINED) return null;
|
||||
else return res;
|
||||
}
|
||||
public void set(Context ctx, int i, Object val) {
|
||||
if (i < 0) return;
|
||||
|
||||
alloc(i);
|
||||
|
||||
val = Values.normalize(ctx, val);
|
||||
if (val == null) val = UNDEFINED;
|
||||
values[i] = val;
|
||||
if (i >= size) size = i + 1;
|
||||
}
|
||||
public boolean has(int i) {
|
||||
return i >= 0 && i < values.length && values[i] != null;
|
||||
}
|
||||
public void remove(int i) {
|
||||
if (i < 0 || i >= values.length) return;
|
||||
values[i] = null;
|
||||
}
|
||||
public void shrink(int n) {
|
||||
if (n >= values.length) {
|
||||
values = new Object[16];
|
||||
size = 0;
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
values[--size] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
Object[] res = new Object[size];
|
||||
copyTo(res, 0, 0, size);
|
||||
return res;
|
||||
}
|
||||
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
|
||||
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
|
||||
else arr[i + sourceStart] = values[i + destStart];
|
||||
}
|
||||
}
|
||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||
// Iterate in reverse to reallocate at most once
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
|
||||
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
||||
else arr.set(ctx, i + destStart, values[i + sourceStart]);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
set(ctx, i + destStart, arr[i + sourceStart]);
|
||||
}
|
||||
}
|
||||
|
||||
public void move(int srcI, int dstI, int n) {
|
||||
alloc(dstI + n);
|
||||
|
||||
System.arraycopy(values, srcI, values, dstI, n);
|
||||
|
||||
if (dstI + n >= size) size = dstI + n;
|
||||
}
|
||||
|
||||
public void sort(Comparator<Object> comparator) {
|
||||
Arrays.sort(values, 0, size, (a, b) -> {
|
||||
var _a = 0;
|
||||
var _b = 0;
|
||||
|
||||
if (a == UNDEFINED) _a = 1;
|
||||
if (a == null) _a = 2;
|
||||
|
||||
if (b == UNDEFINED) _b = 1;
|
||||
if (b == null) _b = 2;
|
||||
|
||||
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
|
||||
|
||||
return comparator.compare(a, b);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key instanceof Number) {
|
||||
var i = ((Number)key).doubleValue();
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
return get((int)i);
|
||||
}
|
||||
}
|
||||
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
set(ctx, (int)i, val);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
return has((int)i);
|
||||
}
|
||||
}
|
||||
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected void deleteField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
remove((int)i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.deleteField(ctx, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
for (var i = 0; i < size(); i++) {
|
||||
if (has(i)) res.add(i);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
if (!hasNext()) return null;
|
||||
return get(i++);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ArrayValue() {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
values = new Object[16];
|
||||
size = 0;
|
||||
}
|
||||
public ArrayValue(int cap) {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
values = new Object[cap];
|
||||
size = 0;
|
||||
}
|
||||
public ArrayValue(Context ctx, Object ...values) {
|
||||
this();
|
||||
this.values = new Object[values.length];
|
||||
size = values.length;
|
||||
|
||||
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
|
||||
}
|
||||
|
||||
public static ArrayValue of(Context ctx, Collection<Object> values) {
|
||||
return new ArrayValue(ctx, values.toArray(Object[]::new));
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
|
||||
// TODO: Make methods generic
|
||||
public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
private static final Object UNDEFINED = new Object();
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
private Object[] alloc(int index) {
|
||||
index++;
|
||||
if (index < values.length) return values;
|
||||
if (index < values.length * 2) index = values.length * 2;
|
||||
|
||||
var arr = new Object[index];
|
||||
System.arraycopy(values, 0, arr, 0, values.length);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
public boolean setSize(int val) {
|
||||
if (val < 0) return false;
|
||||
if (size > val) shrink(size - val);
|
||||
else {
|
||||
values = alloc(val);
|
||||
size = val;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Object get(int i) {
|
||||
if (i < 0 || i >= size) return null;
|
||||
var res = values[i];
|
||||
if (res == UNDEFINED) return null;
|
||||
else return res;
|
||||
}
|
||||
public void set(Context ctx, int i, Object val) {
|
||||
if (i < 0) return;
|
||||
|
||||
values = alloc(i);
|
||||
|
||||
val = Values.normalize(ctx, val);
|
||||
if (val == null) val = UNDEFINED;
|
||||
values[i] = val;
|
||||
if (i >= size) size = i + 1;
|
||||
}
|
||||
public boolean has(int i) {
|
||||
return i >= 0 && i < size && values[i] != null;
|
||||
}
|
||||
public void remove(int i) {
|
||||
if (i < 0 || i >= values.length) return;
|
||||
values[i] = null;
|
||||
}
|
||||
public void shrink(int n) {
|
||||
if (n >= values.length) {
|
||||
values = new Object[16];
|
||||
size = 0;
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
values[--size] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
Object[] res = new Object[size];
|
||||
copyTo(res, 0, 0, size);
|
||||
return res;
|
||||
}
|
||||
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
|
||||
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
|
||||
else arr[i + sourceStart] = values[i + destStart];
|
||||
}
|
||||
}
|
||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||
// Iterate in reverse to reallocate at most once
|
||||
if (destStart + count > arr.size) arr.size = destStart + count;
|
||||
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
set(ctx, i + destStart, arr[i + sourceStart]);
|
||||
}
|
||||
}
|
||||
|
||||
public void move(int srcI, int dstI, int n) {
|
||||
values = alloc(dstI + n);
|
||||
|
||||
System.arraycopy(values, srcI, values, dstI, n);
|
||||
|
||||
if (dstI + n >= size) size = dstI + n;
|
||||
}
|
||||
|
||||
public void sort(Comparator<Object> comparator) {
|
||||
Arrays.sort(values, 0, size, (a, b) -> {
|
||||
var _a = 0;
|
||||
var _b = 0;
|
||||
|
||||
if (a == UNDEFINED) _a = 1;
|
||||
if (a == null) _a = 2;
|
||||
|
||||
if (b == UNDEFINED) _b = 1;
|
||||
if (b == null) _b = 2;
|
||||
|
||||
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
|
||||
|
||||
return comparator.compare(a, b);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (key instanceof Number) {
|
||||
var i = ((Number)key).doubleValue();
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
return get((int)i);
|
||||
}
|
||||
}
|
||||
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
set(ctx, (int)i, val);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
return has((int)i);
|
||||
}
|
||||
}
|
||||
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected void deleteField(Context ctx, Object key) {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
remove((int)i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.deleteField(ctx, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
for (var i = 0; i < size(); i++) {
|
||||
if (has(i)) res.add(i);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
if (!hasNext()) return null;
|
||||
return get(i++);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ArrayValue() {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
values = new Object[16];
|
||||
size = 0;
|
||||
}
|
||||
public ArrayValue(int cap) {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
values = new Object[cap];
|
||||
size = 0;
|
||||
}
|
||||
public ArrayValue(Context ctx, Object ...values) {
|
||||
this();
|
||||
this.values = new Object[values.length];
|
||||
size = values.length;
|
||||
|
||||
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
|
||||
}
|
||||
|
||||
public static ArrayValue of(Context ctx, Collection<?> values) {
|
||||
return new ArrayValue(ctx, values.toArray(Object[]::new));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.core.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.Environment;
|
||||
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ValueVariable;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
public final int localsN;
|
||||
public final Instruction[] body;
|
||||
public final String[] captureNames, localNames;
|
||||
public final ValueVariable[] captures;
|
||||
public Environment environment;
|
||||
|
||||
public Location loc() {
|
||||
for (var instr : body) {
|
||||
if (instr.location != null) return instr.location;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public String readable() {
|
||||
var loc = loc();
|
||||
if (loc == null) return name;
|
||||
else if (name.equals("")) return loc.toString();
|
||||
else return name + "@" + loc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var frame = new CodeFrame(ctx, thisArg, args, this);
|
||||
|
||||
frame.onPush();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
if (res != Values.NO_RETURN) return res;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
frame.onPop();
|
||||
}
|
||||
}
|
||||
|
||||
public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
|
||||
super(name, body.argsN);
|
||||
this.captures = captures;
|
||||
this.captureNames = body.captureNames;
|
||||
this.localNames = body.localNames;
|
||||
this.environment = environment;
|
||||
this.localsN = body.localsN;
|
||||
this.body = body.instructions;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,70 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public abstract class FunctionValue extends ObjectValue {
|
||||
public String name = "";
|
||||
public boolean special = false;
|
||||
public int length;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "function(...) { ...}";
|
||||
}
|
||||
|
||||
public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException;
|
||||
public Object call(Context ctx) throws InterruptedException {
|
||||
return call(ctx, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("name")) return name;
|
||||
if (key.equals("length")) return length;
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
if (key.equals("name")) name = Values.toString(ctx, val);
|
||||
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
|
||||
else return super.setField(ctx, key, val);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("name")) return true;
|
||||
if (key.equals("length")) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
if (includeNonEnumerable) {
|
||||
res.add("name");
|
||||
res.add("length");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionValue(String name, int length) {
|
||||
super(PlaceholderProto.FUNCTION);
|
||||
|
||||
if (name == null) name = "";
|
||||
this.length = length;
|
||||
this.name = name;
|
||||
|
||||
nonConfigurableSet.add("name");
|
||||
nonEnumerableSet.add("name");
|
||||
nonWritableSet.add("length");
|
||||
nonConfigurableSet.add("length");
|
||||
nonEnumerableSet.add("length");
|
||||
|
||||
var proto = new ObjectValue();
|
||||
proto.defineProperty(null, "constructor", this, true, false, false);
|
||||
this.defineProperty(null, "prototype", proto, true, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
|
||||
public abstract class FunctionValue extends ObjectValue {
|
||||
public String name = "";
|
||||
public boolean special = false;
|
||||
public int length;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("function %s(...)", name);
|
||||
}
|
||||
|
||||
public abstract Object call(Context ctx, Object thisArg, Object ...args);
|
||||
public Object call(Context ctx) {
|
||||
return call(ctx, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if ("name".equals(key)) return name;
|
||||
if ("length".equals(key)) return length;
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if ("name".equals(key)) name = Values.toString(ctx, val);
|
||||
else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
|
||||
else return super.setField(ctx, key, val);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if ("name".equals(key)) return true;
|
||||
if ("length".equals(key)) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
if (includeNonEnumerable) {
|
||||
res.add("name");
|
||||
res.add("length");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionValue(String name, int length) {
|
||||
super(PlaceholderProto.FUNCTION);
|
||||
|
||||
if (name == null) name = "";
|
||||
this.length = length;
|
||||
this.name = name;
|
||||
|
||||
nonConfigurableSet.add("name");
|
||||
nonEnumerableSet.add("name");
|
||||
nonWritableSet.add("length");
|
||||
nonConfigurableSet.add("length");
|
||||
nonEnumerableSet.add("length");
|
||||
|
||||
var proto = new ObjectValue();
|
||||
proto.defineProperty(null, "constructor", this, true, false, false);
|
||||
this.defineProperty(null, "prototype", proto, true, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class NativeFunction extends FunctionValue {
|
||||
public static interface NativeFunctionRunner {
|
||||
Object run(Context ctx, Object thisArg, Object[] args) throws InterruptedException;
|
||||
}
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
return action.run(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
||||
super(name, 0);
|
||||
this.action = action;
|
||||
}
|
||||
public NativeFunction(NativeFunctionRunner action) {
|
||||
super("", 0);
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
|
||||
public class NativeFunction extends FunctionValue {
|
||||
public static interface NativeFunctionRunner {
|
||||
Object run(Arguments args);
|
||||
}
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
return action.run(new Arguments(ctx, thisArg, args));
|
||||
}
|
||||
|
||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
||||
super(name, 0);
|
||||
this.action = action;
|
||||
}
|
||||
public NativeFunction(NativeFunctionRunner action) {
|
||||
super("", 0);
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
|
||||
public class NativeWrapper extends ObjectValue {
|
||||
private static final Object NATIVE_PROTO = new Object();
|
||||
public final Object wrapped;
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(Context ctx) {
|
||||
if (prototype == NATIVE_PROTO) return ctx.environment.wrappers.getProto(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return wrapped.equals(obj);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return wrapped.hashCode();
|
||||
}
|
||||
|
||||
public NativeWrapper(Object wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
prototype = NATIVE_PROTO;
|
||||
}
|
||||
}
|
||||
@@ -1,347 +1,354 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class ObjectValue {
|
||||
public static enum PlaceholderProto {
|
||||
NONE,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
FUNCTION,
|
||||
ERROR,
|
||||
SYNTAX_ERROR,
|
||||
TYPE_ERROR,
|
||||
RANGE_ERROR,
|
||||
}
|
||||
public static enum State {
|
||||
NORMAL,
|
||||
NO_EXTENSIONS,
|
||||
SEALED,
|
||||
FROZEN,
|
||||
}
|
||||
|
||||
public static class Property {
|
||||
public final FunctionValue getter;
|
||||
public final FunctionValue setter;
|
||||
|
||||
public Property(FunctionValue getter, FunctionValue setter) {
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Object OBJ_PROTO = new Object();
|
||||
private static final Object ARR_PROTO = new Object();
|
||||
private static final Object FUNC_PROTO = new Object();
|
||||
private static final Object ERR_PROTO = new Object();
|
||||
private static final Object SYNTAX_ERR_PROTO = new Object();
|
||||
private static final Object TYPE_ERR_PROTO = new Object();
|
||||
private static final Object RANGE_ERR_PROTO = new Object();
|
||||
|
||||
protected Object prototype;
|
||||
|
||||
public State state = State.NORMAL;
|
||||
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
|
||||
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
|
||||
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
|
||||
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
|
||||
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
|
||||
|
||||
public final boolean memberWritable(Object key) {
|
||||
if (state == State.FROZEN) return false;
|
||||
return !values.containsKey(key) || !nonWritableSet.contains(key);
|
||||
}
|
||||
public final boolean memberConfigurable(Object key) {
|
||||
if (state == State.SEALED || state == State.FROZEN) return false;
|
||||
return !nonConfigurableSet.contains(key);
|
||||
}
|
||||
public final boolean memberEnumerable(Object key) {
|
||||
return !nonEnumerableSet.contains(key);
|
||||
}
|
||||
public final boolean extensible() {
|
||||
return state == State.NORMAL;
|
||||
}
|
||||
|
||||
public final void preventExtensions() {
|
||||
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
|
||||
}
|
||||
public final void seal() {
|
||||
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
|
||||
}
|
||||
public final void freeze() {
|
||||
state = State.FROZEN;
|
||||
}
|
||||
|
||||
public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
|
||||
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
|
||||
boolean reconfigured =
|
||||
writable != memberWritable(key) ||
|
||||
configurable != memberConfigurable(key) ||
|
||||
enumerable != memberEnumerable(key);
|
||||
|
||||
if (!reconfigured) {
|
||||
if (!memberWritable(key)) {
|
||||
var a = values.get(key);
|
||||
var b = val;
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
properties.containsKey(key) &&
|
||||
values.get(key) == val &&
|
||||
!reconfigured
|
||||
) return true;
|
||||
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key))
|
||||
return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
properties.remove(key);
|
||||
values.remove(key);
|
||||
|
||||
if (!writable) nonWritableSet.add(key);
|
||||
if (!configurable) nonConfigurableSet.add(key);
|
||||
if (!enumerable) nonEnumerableSet.add(key);
|
||||
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
public final boolean defineProperty(Context ctx, Object key, Object val) {
|
||||
return defineProperty(ctx, key, val, true, true, true);
|
||||
}
|
||||
public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
||||
key = Values.normalize(ctx, key);
|
||||
if (
|
||||
properties.containsKey(key) &&
|
||||
properties.get(key).getter == getter &&
|
||||
properties.get(key).setter == setter &&
|
||||
!configurable == nonConfigurableSet.contains(key) &&
|
||||
!enumerable == nonEnumerableSet.contains(key)
|
||||
) return true;
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key)) return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
properties.remove(key);
|
||||
values.remove(key);
|
||||
|
||||
if (!configurable) nonConfigurableSet.add(key);
|
||||
if (!enumerable) nonEnumerableSet.add(key);
|
||||
|
||||
properties.put(key, new Property(getter, setter));
|
||||
return true;
|
||||
}
|
||||
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.env.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.env.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (ObjectValue)prototype;
|
||||
}
|
||||
public final boolean setPrototype(Context ctx, Object val) {
|
||||
val = Values.normalize(ctx, val);
|
||||
|
||||
if (!extensible()) return false;
|
||||
if (val == null || val == Values.NULL) {
|
||||
prototype = null;
|
||||
return true;
|
||||
}
|
||||
else if (Values.isObject(val)) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null && ctx.env != null) {
|
||||
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public final boolean setPrototype(PlaceholderProto val) {
|
||||
if (!extensible()) return false;
|
||||
switch (val) {
|
||||
case OBJECT: prototype = OBJ_PROTO; break;
|
||||
case FUNCTION: prototype = FUNC_PROTO; break;
|
||||
case ARRAY: prototype = ARR_PROTO; break;
|
||||
case ERROR: prototype = ERR_PROTO; break;
|
||||
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
|
||||
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
|
||||
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
|
||||
case NONE: prototype = null; break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Property getProperty(Context ctx, Object key) throws InterruptedException {
|
||||
if (properties.containsKey(key)) return properties.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getProperty(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
if (values.containsKey(key)) return values.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getField(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
|
||||
((FunctionValue)val).name = Values.toString(ctx, key);
|
||||
}
|
||||
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
protected void deleteField(Context ctx, Object key) throws InterruptedException {
|
||||
values.remove(key);
|
||||
}
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
return values.containsKey(key);
|
||||
}
|
||||
|
||||
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if ("__proto__".equals(key)) {
|
||||
var res = getPrototype(ctx);
|
||||
return res == null ? Values.NULL : res;
|
||||
}
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
|
||||
if (prop != null) {
|
||||
if (prop.getter == null) return null;
|
||||
else return prop.getter.call(ctx, Values.normalize(ctx, thisArg));
|
||||
}
|
||||
else return getField(ctx, key);
|
||||
}
|
||||
public final Object getMember(Context ctx, Object key) throws InterruptedException {
|
||||
return getMember(ctx, key, this);
|
||||
}
|
||||
|
||||
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
if (prop != null) {
|
||||
if (prop.setter == null) return false;
|
||||
prop.setter.call(ctx, Values.normalize(ctx, thisArg), val);
|
||||
return true;
|
||||
}
|
||||
else if (onlyProps) return false;
|
||||
else if (!extensible() && !values.containsKey(key)) return false;
|
||||
else if (key == null) {
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
else if (key.equals("__proto__")) return setPrototype(ctx, val);
|
||||
else if (nonWritableSet.contains(key)) return false;
|
||||
else return setField(ctx, key, val);
|
||||
}
|
||||
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) throws InterruptedException {
|
||||
return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps);
|
||||
}
|
||||
|
||||
public final boolean hasMember(Context ctx, Object key, boolean own) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (key != null && key.equals("__proto__")) return true;
|
||||
if (hasField(ctx, key)) return true;
|
||||
if (properties.containsKey(key)) return true;
|
||||
if (own) return false;
|
||||
var proto = getPrototype(ctx);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public final boolean deleteMember(Context ctx, Object key) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (!memberConfigurable(key)) return false;
|
||||
properties.remove(key);
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
deleteField(ctx, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public final ObjectValue getMemberDescriptor(Context ctx, Object key) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
var prop = properties.get(key);
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(ctx, "configurable", memberConfigurable(key));
|
||||
res.defineProperty(ctx, "enumerable", memberEnumerable(key));
|
||||
|
||||
if (prop != null) {
|
||||
res.defineProperty(ctx, "get", prop.getter);
|
||||
res.defineProperty(ctx, "set", prop.setter);
|
||||
}
|
||||
else if (hasField(ctx, key)) {
|
||||
res.defineProperty(ctx, "value", values.get(key));
|
||||
res.defineProperty(ctx, "writable", memberWritable(key));
|
||||
}
|
||||
else return null;
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = new ArrayList<Object>();
|
||||
|
||||
for (var key : values.keySet()) {
|
||||
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
|
||||
res.add(key);
|
||||
}
|
||||
for (var key : properties.keySet()) {
|
||||
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
|
||||
res.add(key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public ObjectValue(Context ctx, Map<?, ?> values) {
|
||||
this(PlaceholderProto.OBJECT);
|
||||
for (var el : values.entrySet()) {
|
||||
defineProperty(ctx, el.getKey(), el.getValue());
|
||||
}
|
||||
}
|
||||
public ObjectValue(PlaceholderProto proto) {
|
||||
nonConfigurableSet.add("__proto__");
|
||||
nonEnumerableSet.add("__proto__");
|
||||
setPrototype(proto);
|
||||
}
|
||||
public ObjectValue() {
|
||||
this(PlaceholderProto.OBJECT);
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.Environment;
|
||||
|
||||
public class ObjectValue {
|
||||
public static enum PlaceholderProto {
|
||||
NONE,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
FUNCTION,
|
||||
ERROR,
|
||||
SYNTAX_ERROR,
|
||||
TYPE_ERROR,
|
||||
RANGE_ERROR,
|
||||
}
|
||||
public static enum State {
|
||||
NORMAL,
|
||||
NO_EXTENSIONS,
|
||||
SEALED,
|
||||
FROZEN,
|
||||
}
|
||||
|
||||
public static class Property {
|
||||
public final FunctionValue getter;
|
||||
public final FunctionValue setter;
|
||||
|
||||
public Property(FunctionValue getter, FunctionValue setter) {
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Object OBJ_PROTO = new Object();
|
||||
private static final Object ARR_PROTO = new Object();
|
||||
private static final Object FUNC_PROTO = new Object();
|
||||
private static final Object ERR_PROTO = new Object();
|
||||
private static final Object SYNTAX_ERR_PROTO = new Object();
|
||||
private static final Object TYPE_ERR_PROTO = new Object();
|
||||
private static final Object RANGE_ERR_PROTO = new Object();
|
||||
|
||||
protected Object prototype;
|
||||
|
||||
public State state = State.NORMAL;
|
||||
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
|
||||
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
|
||||
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
|
||||
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
|
||||
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
|
||||
|
||||
private Property getProperty(Context ctx, Object key) {
|
||||
if (properties.containsKey(key)) return properties.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getProperty(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
|
||||
public final boolean memberWritable(Object key) {
|
||||
if (state == State.FROZEN) return false;
|
||||
return !values.containsKey(key) || !nonWritableSet.contains(key);
|
||||
}
|
||||
public final boolean memberConfigurable(Object key) {
|
||||
if (state == State.SEALED || state == State.FROZEN) return false;
|
||||
return !nonConfigurableSet.contains(key);
|
||||
}
|
||||
public final boolean memberEnumerable(Object key) {
|
||||
return !nonEnumerableSet.contains(key);
|
||||
}
|
||||
public final boolean extensible() {
|
||||
return state == State.NORMAL;
|
||||
}
|
||||
|
||||
public final void preventExtensions() {
|
||||
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
|
||||
}
|
||||
public final void seal() {
|
||||
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
|
||||
}
|
||||
public final void freeze() {
|
||||
state = State.FROZEN;
|
||||
}
|
||||
|
||||
public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
|
||||
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
|
||||
boolean reconfigured =
|
||||
writable != memberWritable(key) ||
|
||||
configurable != memberConfigurable(key) ||
|
||||
enumerable != memberEnumerable(key);
|
||||
|
||||
if (!reconfigured) {
|
||||
if (!memberWritable(key)) {
|
||||
var a = values.get(key);
|
||||
var b = val;
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
properties.containsKey(key) &&
|
||||
values.get(key) == val &&
|
||||
!reconfigured
|
||||
) return true;
|
||||
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key)) return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
properties.remove(key);
|
||||
values.remove(key);
|
||||
|
||||
if (!writable) nonWritableSet.add(key);
|
||||
if (!configurable) nonConfigurableSet.add(key);
|
||||
if (!enumerable) nonEnumerableSet.add(key);
|
||||
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
public final boolean defineProperty(Context ctx, Object key, Object val) {
|
||||
return defineProperty(ctx, key, val, true, true, true);
|
||||
}
|
||||
public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
||||
key = Values.normalize(ctx, key);
|
||||
if (
|
||||
properties.containsKey(key) &&
|
||||
properties.get(key).getter == getter &&
|
||||
properties.get(key).setter == setter &&
|
||||
!configurable == nonConfigurableSet.contains(key) &&
|
||||
!enumerable == nonEnumerableSet.contains(key)
|
||||
) return true;
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key)) return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
properties.remove(key);
|
||||
values.remove(key);
|
||||
|
||||
if (!configurable) nonConfigurableSet.add(key);
|
||||
if (!enumerable) nonEnumerableSet.add(key);
|
||||
|
||||
properties.put(key, new Property(getter, setter));
|
||||
return true;
|
||||
}
|
||||
|
||||
public ObjectValue getPrototype(Context ctx) {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.get(Environment.OBJECT_PROTO);
|
||||
if (prototype == ARR_PROTO) return ctx.get(Environment.ARRAY_PROTO);
|
||||
if (prototype == FUNC_PROTO) return ctx.get(Environment.FUNCTION_PROTO);
|
||||
if (prototype == ERR_PROTO) return ctx.get(Environment.ERROR_PROTO);
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.get(Environment.RANGE_ERR_PROTO);
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.get(Environment.SYNTAX_ERR_PROTO);
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.get(Environment.TYPE_ERR_PROTO);
|
||||
}
|
||||
catch (NullPointerException e) { return null; }
|
||||
|
||||
return (ObjectValue)prototype;
|
||||
}
|
||||
public final boolean setPrototype(PlaceholderProto val) {
|
||||
if (!extensible()) return false;
|
||||
switch (val) {
|
||||
case OBJECT: prototype = OBJ_PROTO; break;
|
||||
case FUNCTION: prototype = FUNC_PROTO; break;
|
||||
case ARRAY: prototype = ARR_PROTO; break;
|
||||
case ERROR: prototype = ERR_PROTO; break;
|
||||
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
|
||||
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
|
||||
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
|
||||
case NONE: prototype = null; break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method, used to get the value of a field. If a property is bound to
|
||||
* this key, but not a field, this method should return null.
|
||||
*/
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (values.containsKey(key)) return values.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getField(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
/**
|
||||
* Changes the value of a field, that is bound to the given key. If no field is
|
||||
* bound to this key, a new field should be created with the given value
|
||||
* @return Whether or not the operation was successful
|
||||
*/
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
|
||||
((FunctionValue)val).name = Values.toString(ctx, key);
|
||||
}
|
||||
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Deletes the field bound to the given key.
|
||||
*/
|
||||
protected void deleteField(Context ctx, Object key) {
|
||||
values.remove(key);
|
||||
}
|
||||
/**
|
||||
* Returns whether or not there is a field bound to the given key.
|
||||
* This must ignore properties
|
||||
*/
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
return values.containsKey(key);
|
||||
}
|
||||
|
||||
public final Object getMember(Context ctx, Object key, Object thisArg) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if ("__proto__".equals(key)) {
|
||||
var res = getPrototype(ctx);
|
||||
return res == null ? Values.NULL : res;
|
||||
}
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
|
||||
if (prop != null) {
|
||||
if (prop.getter == null) return null;
|
||||
else return prop.getter.call(ctx, Values.normalize(ctx, thisArg));
|
||||
}
|
||||
else return getField(ctx, key);
|
||||
}
|
||||
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) {
|
||||
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
if (prop != null) {
|
||||
if (prop.setter == null) return false;
|
||||
prop.setter.call(ctx, Values.normalize(ctx, thisArg), val);
|
||||
return true;
|
||||
}
|
||||
else if (onlyProps) return false;
|
||||
else if (!extensible() && !values.containsKey(key)) return false;
|
||||
else if (key == null) {
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
else if ("__proto__".equals(key)) return setPrototype(ctx, val);
|
||||
else if (nonWritableSet.contains(key)) return false;
|
||||
else return setField(ctx, key, val);
|
||||
}
|
||||
public final boolean hasMember(Context ctx, Object key, boolean own) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (key != null && "__proto__".equals(key)) return true;
|
||||
if (hasField(ctx, key)) return true;
|
||||
if (properties.containsKey(key)) return true;
|
||||
if (own) return false;
|
||||
var proto = getPrototype(ctx);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public final boolean deleteMember(Context ctx, Object key) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (!memberConfigurable(key)) return false;
|
||||
properties.remove(key);
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
deleteField(ctx, key);
|
||||
return true;
|
||||
}
|
||||
public final boolean setPrototype(Context ctx, Object val) {
|
||||
val = Values.normalize(ctx, val);
|
||||
|
||||
if (!extensible()) return false;
|
||||
if (val == null || val == Values.NULL) {
|
||||
prototype = null;
|
||||
return true;
|
||||
}
|
||||
else if (val instanceof ObjectValue) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null) {
|
||||
if (obj == ctx.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final ObjectValue getMemberDescriptor(Context ctx, Object key) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
var prop = properties.get(key);
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(ctx, "configurable", memberConfigurable(key));
|
||||
res.defineProperty(ctx, "enumerable", memberEnumerable(key));
|
||||
|
||||
if (prop != null) {
|
||||
res.defineProperty(ctx, "get", prop.getter);
|
||||
res.defineProperty(ctx, "set", prop.setter);
|
||||
}
|
||||
else if (hasField(ctx, key)) {
|
||||
res.defineProperty(ctx, "value", values.get(key));
|
||||
res.defineProperty(ctx, "writable", memberWritable(key));
|
||||
}
|
||||
else return null;
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = new ArrayList<Object>();
|
||||
|
||||
for (var key : values.keySet()) {
|
||||
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
|
||||
res.add(key);
|
||||
}
|
||||
for (var key : properties.keySet()) {
|
||||
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
|
||||
res.add(key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public ObjectValue(Context ctx, Map<?, ?> values) {
|
||||
this(PlaceholderProto.OBJECT);
|
||||
for (var el : values.entrySet()) {
|
||||
defineProperty(ctx, el.getKey(), el.getValue());
|
||||
}
|
||||
}
|
||||
public ObjectValue(PlaceholderProto proto) {
|
||||
nonConfigurableSet.add("__proto__");
|
||||
nonEnumerableSet.add("__proto__");
|
||||
setPrototype(proto);
|
||||
}
|
||||
public ObjectValue() {
|
||||
this(PlaceholderProto.OBJECT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.scope.ValueVariable;
|
||||
|
||||
public class ScopeValue extends ObjectValue {
|
||||
public final ValueVariable[] variables;
|
||||
public final HashMap<String, Integer> names = new HashMap<>();
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (names.containsKey(key)) {
|
||||
variables[names.get(key)].set(ctx, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null && proto.hasMember(ctx, key, false) && proto.setField(ctx, key, val)) return true;
|
||||
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
protected void deleteField(Context ctx, Object key) {
|
||||
if (names.containsKey(key)) return;
|
||||
super.deleteField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (names.containsKey(key)) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
res.addAll(names.keySet());
|
||||
return res;
|
||||
}
|
||||
|
||||
public ScopeValue(ValueVariable[] variables, String[] names) {
|
||||
this.variables = variables;
|
||||
for (var i = 0; i < names.length && i < variables.length; i++) {
|
||||
this.names.put(names[i], i);
|
||||
this.nonConfigurableSet.add(names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public final class Symbol {
|
||||
private static final HashMap<String, Symbol> registry = new HashMap<>();
|
||||
|
||||
public final String value;
|
||||
|
||||
public Symbol(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value == null) return "Symbol";
|
||||
else return "@@" + value;
|
||||
}
|
||||
|
||||
public static Symbol get(String name) {
|
||||
if (registry.containsKey(name)) return registry.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
registry.put(name, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
package me.topchetoeu.jscript.core.engine.values;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
@@ -10,19 +12,37 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.Environment;
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
import me.topchetoeu.jscript.core.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.core.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.lib.PromiseLib;
|
||||
|
||||
public class Values {
|
||||
public static final Object NULL = new Object();
|
||||
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 NO_RETURN = new Object();
|
||||
|
||||
public static boolean isObject(Object val) { return val instanceof ObjectValue; }
|
||||
public static boolean isFunction(Object val) { return val instanceof FunctionValue; }
|
||||
public static boolean isArray(Object val) { return val instanceof ArrayValue; }
|
||||
public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; }
|
||||
public static boolean isWrapper(Object val, Class<?> clazz) {
|
||||
if (!isWrapper(val)) return false;
|
||||
@@ -50,8 +70,7 @@ public class Values {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T wrapper(Object val, Class<T> clazz) {
|
||||
if (!isWrapper(val)) return null;
|
||||
|
||||
if (!isWrapper(val)) val = new NativeWrapper(val);
|
||||
var res = (NativeWrapper)val;
|
||||
if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped;
|
||||
else return null;
|
||||
@@ -67,11 +86,11 @@ public class Values {
|
||||
return "object";
|
||||
}
|
||||
|
||||
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) throws InterruptedException {
|
||||
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) {
|
||||
var func = getMember(ctx, obj, name);
|
||||
|
||||
if (func != null) {
|
||||
var res = ((FunctionValue)func).call(ctx, obj);
|
||||
if (func instanceof FunctionValue) {
|
||||
var res = Values.call(ctx, func, obj);
|
||||
if (isPrimitive(res)) return res;
|
||||
}
|
||||
|
||||
@@ -88,7 +107,7 @@ public class Values {
|
||||
obj == NULL;
|
||||
}
|
||||
|
||||
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException {
|
||||
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) {
|
||||
obj = normalize(ctx, obj);
|
||||
if (isPrimitive(obj)) return obj;
|
||||
|
||||
@@ -96,37 +115,31 @@ public class Values {
|
||||
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
|
||||
|
||||
if (ctx != null) {
|
||||
try {
|
||||
return tryCallConvertFunc(ctx, obj, first);
|
||||
}
|
||||
catch (EngineException unused) {
|
||||
return tryCallConvertFunc(ctx, obj, second);
|
||||
}
|
||||
try { return tryCallConvertFunc(ctx, obj, first); }
|
||||
catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); }
|
||||
}
|
||||
|
||||
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||
}
|
||||
public static boolean toBoolean(Object obj) {
|
||||
if (obj == NULL || obj == null) return false;
|
||||
if (obj instanceof Number && number(obj) == 0) return false;
|
||||
if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false;
|
||||
if (obj instanceof String && ((String)obj).equals("")) return false;
|
||||
if (obj instanceof Boolean) return (Boolean)obj;
|
||||
return true;
|
||||
}
|
||||
public static double toNumber(Context ctx, Object obj) throws InterruptedException {
|
||||
public static double toNumber(Context ctx, Object obj) {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val instanceof Number) return number(val);
|
||||
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
|
||||
if (val instanceof String) {
|
||||
try {
|
||||
return Double.parseDouble((String)val);
|
||||
}
|
||||
catch (NumberFormatException e) { }
|
||||
try { return Double.parseDouble((String)val); }
|
||||
catch (NumberFormatException e) { return Double.NaN; }
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
public static String toString(Context ctx, Object obj) throws InterruptedException {
|
||||
public static String toString(Context ctx, Object obj) {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val == null) return "undefined";
|
||||
@@ -141,52 +154,52 @@ public class Values {
|
||||
}
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return ((Symbol)val).toString();
|
||||
if (val instanceof Symbol) return val.toString();
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
|
||||
public static Object add(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static Object add(Context ctx, Object a, Object b) {
|
||||
if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
|
||||
else return toNumber(ctx, a) + toNumber(ctx, b);
|
||||
}
|
||||
public static double subtract(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double subtract(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) - toNumber(ctx, b);
|
||||
}
|
||||
public static double multiply(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double multiply(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) * toNumber(ctx, b);
|
||||
}
|
||||
public static double divide(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double divide(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) / toNumber(ctx, b);
|
||||
}
|
||||
public static double modulo(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double modulo(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) % toNumber(ctx, b);
|
||||
}
|
||||
|
||||
public static double negative(Context ctx, Object obj) throws InterruptedException {
|
||||
public static double negative(Context ctx, Object obj) {
|
||||
return -toNumber(ctx, obj);
|
||||
}
|
||||
|
||||
public static int and(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int and(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) & (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int or(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int or(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) | (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int xor(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int xor(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int bitwiseNot(Context ctx, Object obj) throws InterruptedException {
|
||||
public static int bitwiseNot(Context ctx, Object obj) {
|
||||
return ~(int)toNumber(ctx, obj);
|
||||
}
|
||||
|
||||
public static int shiftLeft(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int shiftLeft(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) << (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int shiftRight(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int shiftRight(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b);
|
||||
}
|
||||
public static long unsignedShiftRight(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static long unsignedShiftRight(Context ctx, Object a, Object b) {
|
||||
long _a = (long)toNumber(ctx, a);
|
||||
long _b = (long)toNumber(ctx, b);
|
||||
|
||||
@@ -195,19 +208,25 @@ public class Values {
|
||||
return _a >>> _b;
|
||||
}
|
||||
|
||||
public static int compare(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static CompareResult compare(Context ctx, Object a, Object b) {
|
||||
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
|
||||
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
|
||||
|
||||
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b);
|
||||
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
|
||||
if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
|
||||
|
||||
var _a = toNumber(ctx, a);
|
||||
var _b = toNumber(ctx, b);
|
||||
|
||||
if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL;
|
||||
|
||||
return CompareResult.from(Double.compare(_a, _b));
|
||||
}
|
||||
|
||||
public static boolean not(Object obj) {
|
||||
return !toBoolean(obj);
|
||||
}
|
||||
|
||||
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) throws InterruptedException {
|
||||
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) {
|
||||
if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
|
||||
var val = getPrototype(ctx, obj);
|
||||
|
||||
@@ -219,7 +238,7 @@ public class Values {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Object operation(Context ctx, Operation op, Object ...args) throws InterruptedException {
|
||||
public static Object operation(Context ctx, Operation op, Object ...args) {
|
||||
switch (op) {
|
||||
case ADD: return add(ctx, args[0], args[1]);
|
||||
case SUBTRACT: return subtract(ctx, args[0], args[1]);
|
||||
@@ -236,10 +255,10 @@ public class Values {
|
||||
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
|
||||
case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]);
|
||||
|
||||
case GREATER: return compare(ctx, args[0], args[1]) > 0;
|
||||
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0;
|
||||
case LESS: return compare(ctx, args[0], args[1]) < 0;
|
||||
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0;
|
||||
case GREATER: return compare(ctx, args[0], args[1]).greater();
|
||||
case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual();
|
||||
case LESS: return compare(ctx, args[0], args[1]).less();
|
||||
case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
|
||||
|
||||
case INVERSE: return bitwiseNot(ctx, args[0]);
|
||||
case NOT: return not(args[0]);
|
||||
@@ -260,11 +279,11 @@ public class Values {
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getMember(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
public static Object getMember(Context ctx, Object obj, Object key) {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
|
||||
if (isObject(obj)) return object(obj).getMember(ctx, key);
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
var i = number(key);
|
||||
@@ -276,26 +295,31 @@ public class Values {
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
|
||||
if (proto == null) return key.equals("__proto__") ? NULL : null;
|
||||
else if (key != null && key.equals("__proto__")) return proto;
|
||||
if (proto == null) return "__proto__".equals(key) ? NULL : null;
|
||||
else if (key != null && "__proto__".equals(key)) return proto;
|
||||
else return proto.getMember(ctx, key, obj);
|
||||
}
|
||||
public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException {
|
||||
public static Object getMemberPath(Context ctx, Object obj, Object ...path) {
|
||||
var res = obj;
|
||||
for (var key : path) res = getMember(ctx, res, key);
|
||||
return res;
|
||||
}
|
||||
public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
|
||||
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
|
||||
if (key.equals("__proto__")) return setPrototype(ctx, obj, val);
|
||||
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
|
||||
if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false);
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto.setMember(ctx, key, val, obj, true);
|
||||
}
|
||||
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) throws InterruptedException {
|
||||
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
if (key.equals("__proto__")) return true;
|
||||
if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
|
||||
if ("__proto__".equals(key)) return true;
|
||||
if (obj instanceof ObjectValue) return object(obj).hasMember(ctx, key, own);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
var i = number(key);
|
||||
@@ -308,34 +332,39 @@ public class Values {
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public static boolean deleteMember(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
public static boolean deleteMember(Context ctx, Object obj, Object key) {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
if (isObject(obj)) return object(obj).deleteMember(ctx, key);
|
||||
if (obj instanceof ObjectValue) return object(obj).deleteMember(ctx, key);
|
||||
else return false;
|
||||
}
|
||||
public static ObjectValue getPrototype(Context ctx, Object obj) throws InterruptedException {
|
||||
public static ObjectValue getPrototype(Context ctx, Object obj) {
|
||||
if (obj == null || obj == NULL) return null;
|
||||
obj = normalize(ctx, obj);
|
||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ctx);
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (obj instanceof String) return ctx.env.proto("string");
|
||||
else if (obj instanceof Number) return ctx.env.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.env.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
|
||||
if (obj instanceof String) return ctx.get(Environment.STRING_PROTO);
|
||||
else if (obj instanceof Number) return ctx.get(Environment.NUMBER_PROTO);
|
||||
else if (obj instanceof Boolean) return ctx.get(Environment.BOOL_PROTO);
|
||||
else if (obj instanceof Symbol) return ctx.get(Environment.SYMBOL_PROTO);
|
||||
|
||||
return null;
|
||||
}
|
||||
public static boolean setPrototype(Context ctx, Object obj, Object proto) throws InterruptedException {
|
||||
public static boolean setPrototype(Context ctx, Object obj, Object proto) {
|
||||
obj = normalize(ctx, obj);
|
||||
return isObject(obj) && object(obj).setPrototype(ctx, proto);
|
||||
return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ctx, proto);
|
||||
}
|
||||
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException {
|
||||
public static void makePrototypeChain(Context ctx, Object... chain) {
|
||||
for(var i = 1; i < chain.length; i++) {
|
||||
setPrototype(ctx, chain[i], chain[i - 1]);
|
||||
}
|
||||
}
|
||||
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) {
|
||||
List<Object> res = new ArrayList<>();
|
||||
|
||||
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
|
||||
if (obj instanceof ObjectValue) res = object(obj).keys(includeNonEnumerable);
|
||||
if (obj instanceof String) {
|
||||
for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i);
|
||||
}
|
||||
@@ -345,14 +374,14 @@ public class Values {
|
||||
|
||||
while (proto != null) {
|
||||
res.addAll(proto.keys(includeNonEnumerable));
|
||||
proto = proto.getPrototype(ctx);
|
||||
proto = getPrototype(ctx, proto);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) {
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
|
||||
else if (obj instanceof String && key instanceof Number) {
|
||||
var i = ((Number)key).intValue();
|
||||
@@ -370,19 +399,24 @@ public class Values {
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
return function(func).call(ctx, thisArg, args);
|
||||
}
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException {
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) {
|
||||
var res = new ObjectValue();
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
try {
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
setPrototype(ctx, res, 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;
|
||||
return res;
|
||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||
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) {
|
||||
@@ -395,7 +429,7 @@ public class Values {
|
||||
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
public static boolean looseEqual(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static boolean looseEqual(Context ctx, Object a, Object b) {
|
||||
a = normalize(ctx, a); b = normalize(ctx, b);
|
||||
|
||||
// In loose equality, null is equivalent to undefined
|
||||
@@ -446,14 +480,14 @@ public class Values {
|
||||
|
||||
if (val instanceof Class) {
|
||||
if (ctx == null) return null;
|
||||
else return ctx.env.wrappersProvider.getConstr((Class<?>)val);
|
||||
else return ctx.environment.wrappers.getConstr((Class<?>)val);
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) {
|
||||
if (clazz == Void.class) return null;
|
||||
|
||||
if (obj instanceof NativeWrapper) {
|
||||
@@ -513,37 +547,40 @@ public class Values {
|
||||
|
||||
if (obj == null) return null;
|
||||
if (clazz.isInstance(obj)) return (T)obj;
|
||||
if (clazz.isAssignableFrom(NativeWrapper.class)) {
|
||||
return (T)new NativeWrapper(obj);
|
||||
}
|
||||
|
||||
throw new ConvertException(type(obj), clazz.getSimpleName());
|
||||
}
|
||||
|
||||
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
|
||||
public static Iterable<Object> fromJSIterator(Context ctx, Object obj) {
|
||||
return () -> {
|
||||
try {
|
||||
var symbol = ctx.env.symbol("Symbol.iterator");
|
||||
var symbol = Symbol.get("Symbol.iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||
if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator();
|
||||
var iterator = iteratorFunc instanceof FunctionValue ?
|
||||
((FunctionValue)iteratorFunc).call(ctx, obj, obj) :
|
||||
iteratorFunc;
|
||||
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
|
||||
if (!isFunction(nextFunc)) return Collections.emptyIterator();
|
||||
if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator();
|
||||
|
||||
return new Iterator<Object>() {
|
||||
private Object value = null;
|
||||
public boolean consumed = true;
|
||||
private FunctionValue next = (FunctionValue)nextFunc;
|
||||
|
||||
private void loadNext() throws InterruptedException {
|
||||
private void loadNext() {
|
||||
if (next == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = object(next.call(ctx, iterator));
|
||||
var curr = next.call(ctx, iterator);
|
||||
if (curr == null) { next = null; value = null; }
|
||||
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
|
||||
if (toBoolean(Values.getMember(ctx, curr, "done"))) { next = null; value = null; }
|
||||
else {
|
||||
this.value = curr.getMember(ctx, "value");
|
||||
this.value = Values.getMember(ctx, curr, "value");
|
||||
consumed = false;
|
||||
}
|
||||
}
|
||||
@@ -551,156 +588,184 @@ public class Values {
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
loadNext();
|
||||
return next != null;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
loadNext();
|
||||
return next != null;
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
try {
|
||||
loadNext();
|
||||
var res = value;
|
||||
value = null;
|
||||
consumed = true;
|
||||
return res;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
loadNext();
|
||||
var res = value;
|
||||
value = null;
|
||||
consumed = true;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException {
|
||||
public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
var key = getMember(ctx, getMember(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
|
||||
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(args.ctx, "value", it.next());
|
||||
return obj;
|
||||
}
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException {
|
||||
return fromJavaIterator(ctx, it.iterator());
|
||||
public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
|
||||
return toJSIterator(ctx, it.iterator());
|
||||
}
|
||||
|
||||
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException {
|
||||
if (passed.contains(val)) {
|
||||
System.out.print("[circular]");
|
||||
return;
|
||||
public static ObjectValue toJSAsyncIterator(Context ctx, Iterator<?> it) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
var key = getMemberPath(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||
else {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(args.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 String toReadable(Context ctx, Object val, HashSet<Object> passed, int tab) {
|
||||
if (tab == 0 && val instanceof String) return (String)val;
|
||||
|
||||
if (passed.contains(val)) return "[circular]";
|
||||
|
||||
var printed = true;
|
||||
var res = new StringBuilder();
|
||||
|
||||
if (val instanceof FunctionValue) {
|
||||
System.out.print("function ");
|
||||
var name = Values.getMember(ctx, val, "name");
|
||||
if (name != null) System.out.print(Values.toString(ctx, name));
|
||||
System.out.print("(...)");
|
||||
res.append(val.toString());
|
||||
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
|
||||
|
||||
if (loc != null) System.out.print(" @ " + loc);
|
||||
if (loc != null) res.append(" @ " + loc);
|
||||
}
|
||||
else if (val instanceof ArrayValue) {
|
||||
System.out.print("[");
|
||||
res.append("[");
|
||||
var obj = ((ArrayValue)val);
|
||||
for (int i = 0; i < obj.size(); i++) {
|
||||
if (i != 0) System.out.print(", ");
|
||||
else System.out.print(" ");
|
||||
if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab);
|
||||
else System.out.print("<empty>");
|
||||
if (i != 0) res.append(", ");
|
||||
else res.append(" ");
|
||||
if (obj.has(i)) res.append(toReadable(ctx, obj.get(i), passed, tab));
|
||||
else res.append("<empty>");
|
||||
}
|
||||
System.out.print(" ] ");
|
||||
res.append(" ] ");
|
||||
}
|
||||
else if (val instanceof NativeWrapper) {
|
||||
var obj = ((NativeWrapper)val).wrapped;
|
||||
System.out.print("Native " + obj.toString() + " ");
|
||||
res.append("Native " + obj.toString() + " ");
|
||||
}
|
||||
else printed = false;
|
||||
|
||||
if (val instanceof ObjectValue) {
|
||||
if (tab > 3) {
|
||||
System.out.print("{...}");
|
||||
return;
|
||||
return "{...}";
|
||||
}
|
||||
|
||||
passed.add(val);
|
||||
|
||||
var obj = (ObjectValue)val;
|
||||
if (obj.values.size() + obj.properties.size() == 0) {
|
||||
if (!printed) System.out.println("{}");
|
||||
if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) {
|
||||
if (!printed) res.append("{}\n");
|
||||
}
|
||||
else {
|
||||
System.out.println("{");
|
||||
res.append("{\n");
|
||||
|
||||
for (var el : obj.values.entrySet()) {
|
||||
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
|
||||
printValue(ctx, el.getKey(), passed, tab + 1);
|
||||
System.out.print(": ");
|
||||
printValue(ctx, el.getValue(), passed, tab + 1);
|
||||
System.out.println(",");
|
||||
for (int i = 0; i < tab + 1; i++) res.append(" ");
|
||||
res.append(toReadable(ctx, el.getKey(), passed, tab + 1));
|
||||
res.append(": ");
|
||||
res.append(toReadable(ctx, el.getValue(), passed, tab + 1));
|
||||
res.append(",\n");
|
||||
}
|
||||
for (var el : obj.properties.entrySet()) {
|
||||
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
|
||||
printValue(ctx, el.getKey(), passed, tab + 1);
|
||||
System.out.println(": [prop],");
|
||||
for (int i = 0; i < tab + 1; i++) res.append(" ");
|
||||
res.append(toReadable(ctx, el.getKey(), passed, tab + 1));
|
||||
res.append(": [prop],\n");
|
||||
}
|
||||
|
||||
for (int i = 0; i < tab; i++) System.out.print(" ");
|
||||
System.out.print("}");
|
||||
|
||||
passed.remove(val);
|
||||
|
||||
for (int i = 0; i < tab; i++) res.append(" ");
|
||||
res.append("}");
|
||||
}
|
||||
|
||||
passed.remove(val);
|
||||
}
|
||||
else if (val == null) System.out.print("undefined");
|
||||
else if (val == Values.NULL) System.out.print("null");
|
||||
else if (val instanceof String) System.out.print("'" + val + "'");
|
||||
else System.out.print(Values.toString(ctx, val));
|
||||
else if (val == null) return "undefined";
|
||||
else if (val == Values.NULL) return "null";
|
||||
else if (val instanceof String) return "'" + val + "'";
|
||||
else return Values.toString(ctx, val);
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
public static void printValue(Context ctx, Object val) throws InterruptedException {
|
||||
printValue(ctx, val, new HashSet<>(), 0);
|
||||
|
||||
public static String toReadable(Context ctx, Object val) {
|
||||
return toReadable(ctx, val, new HashSet<>(), 0);
|
||||
}
|
||||
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
|
||||
public static String errorToReadable(RuntimeException err, String prefix) {
|
||||
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
|
||||
if (err instanceof EngineException) {
|
||||
var ee = ((EngineException)err);
|
||||
try {
|
||||
return prefix + " " + ee.toString(new Context(ee.engine, ee.env));
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
|
||||
}
|
||||
else if (err.getCause() instanceof InterruptedException) return;
|
||||
else {
|
||||
System.out.println("Internal error ocurred:");
|
||||
err.printStackTrace();
|
||||
catch (EngineException ex) {
|
||||
return prefix + " " + toReadable(new Context(ee.engine, ee.env), ee.value);
|
||||
}
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught ");
|
||||
Values.printValue(null, ((EngineException)err).value);
|
||||
System.out.println();
|
||||
else if (err instanceof SyntaxException) {
|
||||
return prefix + " SyntaxError " + ((SyntaxException)err).msg;
|
||||
}
|
||||
else if (err.getCause() instanceof InterruptedException) return "";
|
||||
else {
|
||||
var str = new ByteArrayOutputStream();
|
||||
err.printStackTrace(new PrintStream(str));
|
||||
|
||||
return prefix + " internal error " + str.toString();
|
||||
}
|
||||
}
|
||||
public static void printValue(Context ctx, Object val) {
|
||||
System.out.print(toReadable(ctx, val));
|
||||
}
|
||||
public static void printError(RuntimeException err, String prefix) {
|
||||
System.out.println(errorToReadable(err, prefix));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
package me.topchetoeu.jscript.core.exceptions;
|
||||
|
||||
public class ConvertException extends RuntimeException {
|
||||
public final String source, target;
|
||||
@@ -0,0 +1,123 @@
|
||||
package me.topchetoeu.jscript.core.exceptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.engine.Context;
|
||||
import me.topchetoeu.jscript.core.engine.Engine;
|
||||
import me.topchetoeu.jscript.core.engine.Environment;
|
||||
import me.topchetoeu.jscript.core.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.core.engine.values.Values;
|
||||
import me.topchetoeu.jscript.core.engine.values.ObjectValue.PlaceholderProto;
|
||||
|
||||
public class EngineException extends RuntimeException {
|
||||
public static class StackElement {
|
||||
public final Location location;
|
||||
public final String function;
|
||||
public final Context ctx;
|
||||
|
||||
public boolean visible() {
|
||||
return ctx == null || !ctx.get(Environment.HIDE_STACK, false);
|
||||
}
|
||||
public String toString() {
|
||||
var res = "";
|
||||
var loc = location;
|
||||
|
||||
if (loc != null && ctx != null && ctx.engine != null) loc = DebugContext.get(ctx).mapToCompiled(loc);
|
||||
|
||||
if (loc != null) res += "at " + loc.toString() + " ";
|
||||
if (function != null && !function.equals("")) res += "in " + function + " ";
|
||||
|
||||
return res.trim();
|
||||
}
|
||||
|
||||
public StackElement(Context ctx, Location location, String function) {
|
||||
if (function != null) function = function.trim();
|
||||
if (function.equals("")) function = null;
|
||||
|
||||
if (ctx == null) this.ctx = null;
|
||||
else this.ctx = new Context(ctx.engine, ctx.environment);
|
||||
this.location = location;
|
||||
this.function = function;
|
||||
}
|
||||
}
|
||||
|
||||
public final Object value;
|
||||
public EngineException cause;
|
||||
public Environment env = null;
|
||||
public Engine engine = null;
|
||||
public final List<StackElement> stackTrace = new ArrayList<>();
|
||||
|
||||
public EngineException add(Context ctx, String name, Location location) {
|
||||
var el = new StackElement(ctx, location, name);
|
||||
if (el.function == null && el.location == null) return this;
|
||||
setCtx(ctx.environment, ctx.engine);
|
||||
stackTrace.add(el);
|
||||
return this;
|
||||
}
|
||||
public EngineException setCause(EngineException cause) {
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
public EngineException setCtx(Environment env, Engine engine) {
|
||||
if (this.env == null) this.env = env;
|
||||
if (this.engine == null) this.engine = engine;
|
||||
return this;
|
||||
}
|
||||
public EngineException setCtx(Context ctx) {
|
||||
if (this.env == null) this.env = ctx.environment;
|
||||
if (this.engine == null) this.engine = ctx.engine;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString(Context ctx) {
|
||||
var ss = new StringBuilder();
|
||||
try {
|
||||
ss.append(Values.toString(ctx, value)).append('\n');
|
||||
}
|
||||
catch (EngineException e) {
|
||||
ss.append("[Error while stringifying]\n");
|
||||
}
|
||||
for (var line : stackTrace) {
|
||||
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
|
||||
}
|
||||
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
ss.deleteCharAt(ss.length() - 1);
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
private static Object err(String name, String msg, PlaceholderProto proto) {
|
||||
var res = new ObjectValue(proto);
|
||||
if (name != null) res.defineProperty(null, "name", name);
|
||||
res.defineProperty(null, "message", msg);
|
||||
return res;
|
||||
}
|
||||
|
||||
public EngineException(Object error) {
|
||||
super(error == null ? "null" : error.toString());
|
||||
|
||||
this.value = error;
|
||||
this.cause = null;
|
||||
}
|
||||
|
||||
public static EngineException ofError(String name, String msg) {
|
||||
return new EngineException(err(name, msg, PlaceholderProto.ERROR));
|
||||
}
|
||||
public static EngineException ofError(String msg) {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
|
||||
}
|
||||
public static EngineException ofSyntax(SyntaxException e) {
|
||||
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc);
|
||||
}
|
||||
public static EngineException ofSyntax(String msg) {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
|
||||
}
|
||||
public static EngineException ofType(String msg) {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR));
|
||||
}
|
||||
public static EngineException ofRange(String msg) {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.core.exceptions;
|
||||
|
||||
public class InterruptException extends RuntimeException {
|
||||
public InterruptException() { }
|
||||
public InterruptException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
|
||||
public class SyntaxException extends RuntimeException {
|
||||
public final Location loc;
|
||||
public final String msg;
|
||||
|
||||
public SyntaxException(Location loc, String msg) {
|
||||
super(String.format("Syntax error (at %s): %s", loc, msg));
|
||||
this.loc = loc;
|
||||
this.msg = msg;
|
||||
}
|
||||
package me.topchetoeu.jscript.core.exceptions;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
|
||||
public class SyntaxException extends RuntimeException {
|
||||
public final Location loc;
|
||||
public final String msg;
|
||||
|
||||
public SyntaxException(Location loc, String msg) {
|
||||
super(String.format("Syntax error (at %s): %s", loc, msg));
|
||||
this.loc = loc;
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +1,113 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
|
||||
public enum Operator {
|
||||
MULTIPLY("*", Operation.MULTIPLY, 13),
|
||||
DIVIDE("/", Operation.DIVIDE, 12),
|
||||
MODULO("%", Operation.MODULO, 12),
|
||||
SUBTRACT("-", Operation.SUBTRACT, 11),
|
||||
ADD("+", Operation.ADD, 11),
|
||||
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
|
||||
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
|
||||
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
|
||||
GREATER(">", Operation.GREATER, 9),
|
||||
LESS("<", Operation.LESS, 9),
|
||||
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
|
||||
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
|
||||
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
|
||||
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
|
||||
EQUALS("==", Operation.LOOSE_EQUALS, 8),
|
||||
LOOSE_EQUALS("===", Operation.EQUALS, 8),
|
||||
AND("&", Operation.AND, 7),
|
||||
XOR("^", Operation.XOR, 6),
|
||||
OR("|", Operation.OR, 5),
|
||||
LAZY_AND("&&", 4),
|
||||
LAZY_OR("||", 3),
|
||||
ASSIGN_SHIFT_LEFT("<<=", 2, true),
|
||||
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
|
||||
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
|
||||
ASSIGN_AND("&=", 2, true),
|
||||
ASSIGN_OR("|=", 2, true),
|
||||
ASSIGN_XOR("^=", 2, true),
|
||||
ASSIGN_MODULO("%=", 2, true),
|
||||
ASSIGN_DIVIDE("/=", 2, true),
|
||||
ASSIGN_MULTIPLY("*=", 2, true),
|
||||
ASSIGN_SUBTRACT("-=", 2, true),
|
||||
ASSIGN_ADD("+=", 2, true),
|
||||
ASSIGN("=", 2, true),
|
||||
SEMICOLON(";"),
|
||||
COLON(":"),
|
||||
PAREN_OPEN("("),
|
||||
PAREN_CLOSE(")"),
|
||||
BRACKET_OPEN("["),
|
||||
BRACKET_CLOSE("]"),
|
||||
BRACE_OPEN("{"),
|
||||
BRACE_CLOSE("}"),
|
||||
DOT("."),
|
||||
COMMA(","),
|
||||
NOT("!"),
|
||||
QUESTION("?"),
|
||||
INVERSE("~"),
|
||||
INCREASE("++"),
|
||||
DECREASE("--");
|
||||
|
||||
public final String value;
|
||||
public final Operation operation;
|
||||
public final int precedence;
|
||||
public final boolean reverse;
|
||||
private static final Map<String, Operator> ops = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (var el : Operator.values()) {
|
||||
ops.put(el.value, el);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssign() { return precedence == 2; }
|
||||
|
||||
public static Operator parse(String val) {
|
||||
return ops.get(val);
|
||||
}
|
||||
|
||||
private Operator() {
|
||||
this.value = null;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence, boolean reverse) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
|
||||
private Operator(String value, Operation funcName, int precedence) {
|
||||
this. value = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
||||
this.value = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
package me.topchetoeu.jscript.core.parsing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.core.engine.Operation;
|
||||
|
||||
public enum Operator {
|
||||
MULTIPLY("*", Operation.MULTIPLY, 13),
|
||||
DIVIDE("/", Operation.DIVIDE, 12),
|
||||
MODULO("%", Operation.MODULO, 12),
|
||||
SUBTRACT("-", Operation.SUBTRACT, 11),
|
||||
ADD("+", Operation.ADD, 11),
|
||||
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
|
||||
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
|
||||
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
|
||||
GREATER(">", Operation.GREATER, 9),
|
||||
LESS("<", Operation.LESS, 9),
|
||||
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
|
||||
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
|
||||
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
|
||||
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
|
||||
EQUALS("==", Operation.LOOSE_EQUALS, 8),
|
||||
LOOSE_EQUALS("===", Operation.EQUALS, 8),
|
||||
AND("&", Operation.AND, 7),
|
||||
XOR("^", Operation.XOR, 6),
|
||||
OR("|", Operation.OR, 5),
|
||||
LAZY_AND("&&", 4),
|
||||
LAZY_OR("||", 3),
|
||||
ASSIGN_SHIFT_LEFT("<<=", 2, true),
|
||||
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
|
||||
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
|
||||
ASSIGN_AND("&=", 2, true),
|
||||
ASSIGN_OR("|=", 2, true),
|
||||
ASSIGN_XOR("^=", 2, true),
|
||||
ASSIGN_MODULO("%=", 2, true),
|
||||
ASSIGN_DIVIDE("/=", 2, true),
|
||||
ASSIGN_MULTIPLY("*=", 2, true),
|
||||
ASSIGN_SUBTRACT("-=", 2, true),
|
||||
ASSIGN_ADD("+=", 2, true),
|
||||
ASSIGN("=", 2, true),
|
||||
SEMICOLON(";"),
|
||||
COLON(":"),
|
||||
PAREN_OPEN("("),
|
||||
PAREN_CLOSE(")"),
|
||||
BRACKET_OPEN("["),
|
||||
BRACKET_CLOSE("]"),
|
||||
BRACE_OPEN("{"),
|
||||
BRACE_CLOSE("}"),
|
||||
DOT("."),
|
||||
COMMA(","),
|
||||
NOT("!"),
|
||||
QUESTION("?"),
|
||||
INVERSE("~"),
|
||||
INCREASE("++"),
|
||||
DECREASE("--");
|
||||
|
||||
public final String value;
|
||||
public final Operation operation;
|
||||
public final int precedence;
|
||||
public final boolean reverse;
|
||||
private static final Map<String, Operator> ops = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (var el : Operator.values()) {
|
||||
ops.put(el.value, el);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssign() { return precedence == 2; }
|
||||
|
||||
public static Operator parse(String val) {
|
||||
return ops.get(val);
|
||||
}
|
||||
|
||||
private Operator() {
|
||||
this.value = null;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence, boolean reverse) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
|
||||
private Operator(String value, Operation funcName, int precedence) {
|
||||
this. value = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
||||
this.value = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +1,100 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.parsing.Parsing.Parser;
|
||||
|
||||
public class ParseRes<T> {
|
||||
public static enum State {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
ERROR;
|
||||
|
||||
public boolean isSuccess() { return this == SUCCESS; }
|
||||
public boolean isFailed() { return this == FAILED; }
|
||||
public boolean isError() { return this == ERROR; }
|
||||
}
|
||||
|
||||
public final ParseRes.State state;
|
||||
public final String error;
|
||||
public final T result;
|
||||
public final int n;
|
||||
|
||||
private ParseRes(ParseRes.State state, String error, T result, int readN) {
|
||||
this.result = result;
|
||||
this.n = readN;
|
||||
this.state = state;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public ParseRes<T> setN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, i);
|
||||
}
|
||||
public ParseRes<T> addN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, this.n + i);
|
||||
}
|
||||
public <T2> ParseRes<T2> transform() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
||||
return new ParseRes<>(state, error, null, 0);
|
||||
}
|
||||
public TestRes toTest() {
|
||||
if (isSuccess()) return TestRes.res(n);
|
||||
else if (isError()) return TestRes.error(null, error);
|
||||
else return TestRes.failed();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return state.isSuccess(); }
|
||||
public boolean isFailed() { return state.isFailed(); }
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static <T> ParseRes<T> failed() {
|
||||
return new ParseRes<T>(State.FAILED, null, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
return new ParseRes<>(State.ERROR, other.error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> res(T val, int i) {
|
||||
return new ParseRes<>(State.SUCCESS, null, val, i);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
|
||||
ParseRes<? extends T> best = null;
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
if (parser.isSuccess()) {
|
||||
if (best == null || best.n < parser.n) best = parser;
|
||||
}
|
||||
else if (parser.isError() && error.isFailed()) error = parser.transform();
|
||||
}
|
||||
|
||||
if (best != null) return best;
|
||||
else return error;
|
||||
}
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
var res = parser.parse(null, tokens, 0);
|
||||
if (res.isSuccess()) return res;
|
||||
else if (res.isError() && error.isFailed()) error = res.transform();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
package me.topchetoeu.jscript.core.parsing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.core.parsing.Parsing.Parser;
|
||||
|
||||
public class ParseRes<T> {
|
||||
public static enum State {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
ERROR;
|
||||
|
||||
public boolean isSuccess() { return this == SUCCESS; }
|
||||
public boolean isFailed() { return this == FAILED; }
|
||||
public boolean isError() { return this == ERROR; }
|
||||
}
|
||||
|
||||
public final ParseRes.State state;
|
||||
public final String error;
|
||||
public final T result;
|
||||
public final int n;
|
||||
|
||||
private ParseRes(ParseRes.State state, String error, T result, int readN) {
|
||||
this.result = result;
|
||||
this.n = readN;
|
||||
this.state = state;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public ParseRes<T> setN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, i);
|
||||
}
|
||||
public ParseRes<T> addN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, this.n + i);
|
||||
}
|
||||
public <T2> ParseRes<T2> transform() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
||||
return new ParseRes<>(state, error, null, 0);
|
||||
}
|
||||
public TestRes toTest() {
|
||||
if (isSuccess()) return TestRes.res(n);
|
||||
else if (isError()) return TestRes.error(null, error);
|
||||
else return TestRes.failed();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return state.isSuccess(); }
|
||||
public boolean isFailed() { return state.isFailed(); }
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static <T> ParseRes<T> failed() {
|
||||
return new ParseRes<T>(State.FAILED, null, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
return new ParseRes<>(State.ERROR, other.error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> res(T val, int i) {
|
||||
return new ParseRes<>(State.SUCCESS, null, val, i);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
|
||||
return any(List.of(parsers));
|
||||
}
|
||||
public static <T> ParseRes<? extends T> any(List<ParseRes<? extends T>> parsers) {
|
||||
ParseRes<? extends T> best = null;
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
if (parser.isSuccess()) {
|
||||
if (best == null || best.n < parser.n) best = parser;
|
||||
}
|
||||
else if (parser.isError() && error.isFailed()) error = parser.transform();
|
||||
}
|
||||
|
||||
if (best != null) return best;
|
||||
else return error;
|
||||
}
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
var res = parser.parse(null, tokens, 0);
|
||||
if (res.isSuccess()) return res;
|
||||
else if (res.isError() && error.isFailed()) error = res.transform();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user