Compare commits
188 Commits
v0.2.0-alp
...
v0.9.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
e6399c1546
|
|||
|
c8253795b2
|
|||
|
49dd725669
|
|||
|
52489ad3a8
|
|||
|
c4d44547c8
|
|||
|
c6dc031cfd
|
|||
|
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
|
|||
|
952a4d631d
|
|||
| 005610ca40 | |||
|
9743a6c078
|
|||
|
21a6d20ac5
|
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"
|
runs-on: "ubuntu-latest"
|
||||||
|
|
||||||
steps:
|
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
|
- name: Clone repository
|
||||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||||
with:
|
with:
|
||||||
branch: 'master' # fuck this political bullshitshit, took me an hour to fix this
|
branch: 'master'
|
||||||
owner: 'TopchetoEU'
|
owner: 'TopchetoEU'
|
||||||
repository: 'java-jscript'
|
repository: 'java-jscript'
|
||||||
- name: "Build"
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd java-jscript; node ./build.js release ${{ github.ref }}
|
cd java-jscript; gradle build
|
||||||
|
|
||||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
with:
|
with:
|
||||||
@@ -27,4 +34,4 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
java-jscript/LICENSE
|
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
|
!/src
|
||||||
/out
|
!/src/**/*
|
||||||
/build
|
|
||||||
/bin
|
!/doc
|
||||||
/dst
|
!/doc/**/*
|
||||||
/*.js
|
|
||||||
!/build.js
|
!/tests
|
||||||
/dead-code
|
!/tests/**/*
|
||||||
/Metadata.java
|
|
||||||
|
!/.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**
|
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
|
||||||
|
|
||||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
|
||||||
|
|
||||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
|
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
|
The following is going to execute a simple javascript statement:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var engine = new PolyfillEngine(new File("."));
|
var engine = new Engine();
|
||||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
|
||||||
engine.start();
|
var env = Internals.apply(new Environment());
|
||||||
|
|
||||||
while (true) {
|
// Queue code to load internal libraries and start engine
|
||||||
try {
|
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
|
||||||
var raw = in.readLine();
|
// Run the engine on the same thread, until the event loop runs empty
|
||||||
|
engine.run(true);
|
||||||
|
|
||||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
// Get our result
|
||||||
Values.printValue(engine.context(), res);
|
System.out.println(awaitable.await());
|
||||||
System.out.println();
|
|
||||||
}
|
|
||||||
catch (EngineException e) {
|
|
||||||
try {
|
|
||||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
|
||||||
}
|
|
||||||
catch (InterruptedException _e) { return; }
|
|
||||||
}
|
|
||||||
catch (IOException | InterruptedException e) { return; }
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## NOTE:
|
||||||
|
|
||||||
|
To setup the typescript bundle in your sources, run `node build.js init-ts`. This will download the latest version of typescript, minify it, and add it to your src 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/java/me/topchetoeu/jscript/common/FunctionBody.java
Normal file
14
src/java/me/topchetoeu/jscript/common/FunctionBody.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
public class FunctionBody {
|
||||||
|
public final FunctionBody[] children;
|
||||||
|
public final Instruction[] instructions;
|
||||||
|
public final int localsN, argsN;
|
||||||
|
|
||||||
|
public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) {
|
||||||
|
this.children = children;
|
||||||
|
this.argsN = argsN;
|
||||||
|
this.localsN = localsN;
|
||||||
|
this.instructions = instructions;
|
||||||
|
}
|
||||||
|
}
|
||||||
369
src/java/me/topchetoeu/jscript/common/Instruction.java
Normal file
369
src/java/me/topchetoeu/jscript/common/Instruction.java
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class Instruction {
|
||||||
|
public static enum Type {
|
||||||
|
NOP(0),
|
||||||
|
RETURN(1),
|
||||||
|
THROW(2),
|
||||||
|
THROW_SYNTAX(3),
|
||||||
|
DELETE(4),
|
||||||
|
TRY_START(5),
|
||||||
|
TRY_END(6),
|
||||||
|
|
||||||
|
CALL(7),
|
||||||
|
CALL_NEW(8),
|
||||||
|
JMP_IF(9),
|
||||||
|
JMP_IFN(10),
|
||||||
|
JMP(11),
|
||||||
|
|
||||||
|
PUSH_UNDEFINED(12),
|
||||||
|
PUSH_NULL(13),
|
||||||
|
PUSH_BOOL(14),
|
||||||
|
PUSH_NUMBER(15),
|
||||||
|
PUSH_STRING(16),
|
||||||
|
|
||||||
|
LOAD_VAR(17),
|
||||||
|
LOAD_MEMBER(18),
|
||||||
|
LOAD_GLOB(20),
|
||||||
|
|
||||||
|
LOAD_FUNC(21),
|
||||||
|
LOAD_ARR(22),
|
||||||
|
LOAD_OBJ(23),
|
||||||
|
STORE_SELF_FUNC(24),
|
||||||
|
LOAD_REGEX(25),
|
||||||
|
|
||||||
|
DUP(26),
|
||||||
|
|
||||||
|
STORE_VAR(27),
|
||||||
|
STORE_MEMBER(28),
|
||||||
|
DISCARD(29),
|
||||||
|
|
||||||
|
MAKE_VAR(30),
|
||||||
|
DEF_PROP(31),
|
||||||
|
KEYS(32),
|
||||||
|
|
||||||
|
TYPEOF(33),
|
||||||
|
OPERATION(34);
|
||||||
|
|
||||||
|
private static final HashMap<Integer, Type> types = new HashMap<>();
|
||||||
|
public final int numeric;
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (var val : Type.values()) types.put(val.numeric, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type(int numeric) {
|
||||||
|
this.numeric = numeric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type fromNumeric(int i) {
|
||||||
|
return types.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(DataOutputStream writer) throws IOException {
|
||||||
|
var rawType = type.numeric;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case KEYS:
|
||||||
|
case PUSH_BOOL:
|
||||||
|
case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break;
|
||||||
|
case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break;
|
||||||
|
case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.writeByte(rawType);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case CALL: writer.writeInt(get(0)); break;
|
||||||
|
case CALL_NEW: writer.writeInt(get(0)); break;
|
||||||
|
case DUP: writer.writeInt(get(0)); break;
|
||||||
|
case JMP: writer.writeInt(get(0)); break;
|
||||||
|
case JMP_IF: writer.writeInt(get(0)); break;
|
||||||
|
case JMP_IFN: writer.writeInt(get(0)); break;
|
||||||
|
case LOAD_ARR: writer.writeInt(get(0)); break;
|
||||||
|
case LOAD_FUNC: {
|
||||||
|
writer.writeInt(params.length - 1);
|
||||||
|
|
||||||
|
for (var i = 0; i < params.length; i++) {
|
||||||
|
writer.writeInt(get(i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.writeInt(get(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LOAD_REGEX: writer.writeUTF(get(0)); break;
|
||||||
|
case LOAD_VAR: writer.writeInt(get(0)); break;
|
||||||
|
case MAKE_VAR: writer.writeUTF(get(0)); break;
|
||||||
|
case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
|
||||||
|
case PUSH_NUMBER: writer.writeDouble(get(0)); break;
|
||||||
|
case PUSH_STRING: writer.writeUTF(get(0)); break;
|
||||||
|
case STORE_SELF_FUNC: writer.writeInt(get(0)); break;
|
||||||
|
case STORE_VAR: writer.writeInt(get(0)); break;
|
||||||
|
case THROW_SYNTAX: writer.writeUTF(get(0));
|
||||||
|
case TRY_START:
|
||||||
|
writer.writeInt(get(0));
|
||||||
|
writer.writeInt(get(1));
|
||||||
|
writer.writeInt(get(2));
|
||||||
|
break;
|
||||||
|
case TYPEOF:
|
||||||
|
if (params.length > 0) writer.writeUTF(get(0));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Instruction(Type type, Object ...params) {
|
||||||
|
this.type = type;
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction read(DataInputStream stream) throws IOException {
|
||||||
|
var rawType = stream.readUnsignedByte();
|
||||||
|
var type = Type.fromNumeric(rawType & 127);
|
||||||
|
var flag = (rawType & 128) != 0;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case CALL: return call(stream.readInt());
|
||||||
|
case CALL_NEW: return callNew(stream.readInt());
|
||||||
|
case DEF_PROP: return defProp();
|
||||||
|
case DELETE: return delete();
|
||||||
|
case DISCARD: return discard();
|
||||||
|
case DUP: return dup(stream.readInt());
|
||||||
|
case JMP: return jmp(stream.readInt());
|
||||||
|
case JMP_IF: return jmpIf(stream.readInt());
|
||||||
|
case JMP_IFN: return jmpIfNot(stream.readInt());
|
||||||
|
case KEYS: return keys(flag);
|
||||||
|
case LOAD_ARR: return loadArr(stream.readInt());
|
||||||
|
case LOAD_FUNC: {
|
||||||
|
var captures = new int[stream.readInt()];
|
||||||
|
|
||||||
|
for (var i = 0; i < captures.length; i++) {
|
||||||
|
captures[i] = stream.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadFunc(stream.readInt(), captures);
|
||||||
|
}
|
||||||
|
case LOAD_GLOB: return loadGlob();
|
||||||
|
case LOAD_MEMBER: return loadMember();
|
||||||
|
case LOAD_OBJ: return loadObj();
|
||||||
|
case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
|
||||||
|
case LOAD_VAR: return loadVar(stream.readInt());
|
||||||
|
case MAKE_VAR: return makeVar(stream.readUTF());
|
||||||
|
case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
|
||||||
|
case PUSH_NULL: return pushNull();
|
||||||
|
case PUSH_UNDEFINED: return pushUndefined();
|
||||||
|
case PUSH_BOOL: return pushValue(flag);
|
||||||
|
case PUSH_NUMBER: return pushValue(stream.readDouble());
|
||||||
|
case PUSH_STRING: return pushValue(stream.readUTF());
|
||||||
|
case RETURN: return ret();
|
||||||
|
case STORE_MEMBER: return storeMember(flag);
|
||||||
|
case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt());
|
||||||
|
case STORE_VAR: return storeVar(stream.readInt(), flag);
|
||||||
|
case THROW: return throwInstr();
|
||||||
|
case THROW_SYNTAX: return throwSyntax(stream.readUTF());
|
||||||
|
case TRY_END: return tryEnd();
|
||||||
|
case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt());
|
||||||
|
case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
|
||||||
|
case NOP:
|
||||||
|
if (flag) return null;
|
||||||
|
else return nop();
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
|
||||||
|
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
|
||||||
|
}
|
||||||
|
public static Instruction tryEnd() {
|
||||||
|
return new Instruction(Type.TRY_END);
|
||||||
|
}
|
||||||
|
public static Instruction throwInstr() {
|
||||||
|
return new Instruction(Type.THROW);
|
||||||
|
}
|
||||||
|
public static Instruction throwSyntax(SyntaxException err) {
|
||||||
|
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
|
||||||
|
}
|
||||||
|
public static Instruction throwSyntax(String err) {
|
||||||
|
return new Instruction(Type.THROW_SYNTAX, err);
|
||||||
|
}
|
||||||
|
public static Instruction delete() {
|
||||||
|
return new Instruction(Type.DELETE);
|
||||||
|
}
|
||||||
|
public static Instruction ret() {
|
||||||
|
return new Instruction(Type.RETURN);
|
||||||
|
}
|
||||||
|
public static Instruction debug() {
|
||||||
|
return new Instruction(Type.NOP, "debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction nop(Object ...params) {
|
||||||
|
return new Instruction(Type.NOP, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction call(int argn) {
|
||||||
|
return new Instruction(Type.CALL, argn);
|
||||||
|
}
|
||||||
|
public static Instruction callNew(int argn) {
|
||||||
|
return new Instruction(Type.CALL_NEW, argn);
|
||||||
|
}
|
||||||
|
public static Instruction jmp(int offset) {
|
||||||
|
return new Instruction(Type.JMP, offset);
|
||||||
|
}
|
||||||
|
public static Instruction jmpIf(int offset) {
|
||||||
|
return new Instruction(Type.JMP_IF, offset);
|
||||||
|
}
|
||||||
|
public static Instruction jmpIfNot(int offset) {
|
||||||
|
return new Instruction(Type.JMP_IFN, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction pushUndefined() {
|
||||||
|
return new Instruction(Type.PUSH_UNDEFINED);
|
||||||
|
}
|
||||||
|
public static Instruction pushNull() {
|
||||||
|
return new Instruction(Type.PUSH_NULL);
|
||||||
|
}
|
||||||
|
public static Instruction pushValue(boolean val) {
|
||||||
|
return new Instruction(Type.PUSH_BOOL, val);
|
||||||
|
}
|
||||||
|
public static Instruction pushValue(double val) {
|
||||||
|
return new Instruction(Type.PUSH_NUMBER, val);
|
||||||
|
}
|
||||||
|
public static Instruction pushValue(String val) {
|
||||||
|
return new Instruction(Type.PUSH_STRING, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction makeVar(String name) {
|
||||||
|
return new Instruction(Type.MAKE_VAR, name);
|
||||||
|
}
|
||||||
|
public static Instruction loadVar(Object i) {
|
||||||
|
return new Instruction(Type.LOAD_VAR, i);
|
||||||
|
}
|
||||||
|
public static Instruction loadGlob() {
|
||||||
|
return new Instruction(Type.LOAD_GLOB);
|
||||||
|
}
|
||||||
|
public static Instruction loadMember() {
|
||||||
|
return new Instruction(Type.LOAD_MEMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction loadRegex(String pattern, String flags) {
|
||||||
|
return new Instruction(Type.LOAD_REGEX, pattern, flags);
|
||||||
|
}
|
||||||
|
public static Instruction loadFunc(int 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(Type.LOAD_FUNC, args);
|
||||||
|
}
|
||||||
|
public static Instruction loadObj() {
|
||||||
|
return new Instruction(Type.LOAD_OBJ);
|
||||||
|
}
|
||||||
|
public static Instruction loadArr(int count) {
|
||||||
|
return new Instruction(Type.LOAD_ARR, count);
|
||||||
|
}
|
||||||
|
public static Instruction dup() {
|
||||||
|
return new Instruction(Type.DUP, 1);
|
||||||
|
}
|
||||||
|
public static Instruction dup(int count) {
|
||||||
|
return new Instruction(Type.DUP, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction storeSelfFunc(int i) {
|
||||||
|
return new Instruction(Type.STORE_SELF_FUNC, i);
|
||||||
|
}
|
||||||
|
public static Instruction storeVar(Object i) {
|
||||||
|
return new Instruction(Type.STORE_VAR, i, false);
|
||||||
|
}
|
||||||
|
public static Instruction storeVar(Object i, boolean keep) {
|
||||||
|
return new Instruction(Type.STORE_VAR, i, keep);
|
||||||
|
}
|
||||||
|
public static Instruction storeMember() {
|
||||||
|
return new Instruction(Type.STORE_MEMBER, false);
|
||||||
|
}
|
||||||
|
public static Instruction storeMember(boolean keep) {
|
||||||
|
return new Instruction(Type.STORE_MEMBER, keep);
|
||||||
|
}
|
||||||
|
public static Instruction discard() {
|
||||||
|
return new Instruction(Type.DISCARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction typeof() {
|
||||||
|
return new Instruction(Type.TYPEOF);
|
||||||
|
}
|
||||||
|
public static Instruction typeof(String varName) {
|
||||||
|
return new Instruction(Type.TYPEOF, varName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction keys(boolean forInFormat) {
|
||||||
|
return new Instruction(Type.KEYS, forInFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction defProp() {
|
||||||
|
return new Instruction(Type.DEF_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction operation(Operation op) {
|
||||||
|
return new Instruction(Type.OPERATION, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
var res = type.toString();
|
||||||
|
|
||||||
|
for (int i = 0; i < params.length; i++) {
|
||||||
|
res += " " + params[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/java/me/topchetoeu/jscript/common/Location.java
Normal file
101
src/java/me/topchetoeu/jscript/common/Location.java
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class Location implements Comparable<Location> {
|
||||||
|
public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native"));
|
||||||
|
private int line;
|
||||||
|
private int start;
|
||||||
|
private Filename filename;
|
||||||
|
|
||||||
|
public int line() { return line; }
|
||||||
|
public int start() { return start; }
|
||||||
|
public Filename filename() { return filename; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
var res = new ArrayList<String>();
|
||||||
|
|
||||||
|
if (filename != null) res.add(filename.toString());
|
||||||
|
if (line >= 0) res.add(line + "");
|
||||||
|
if (start >= 0) res.add(start + "");
|
||||||
|
|
||||||
|
return String.join(":", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location add(int n, boolean clone) {
|
||||||
|
if (clone) return new Location(line, start + n, filename);
|
||||||
|
this.start += n;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Location add(int n) {
|
||||||
|
return add(n, false);
|
||||||
|
}
|
||||||
|
public Location nextLine() {
|
||||||
|
line++;
|
||||||
|
start = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Location clone() {
|
||||||
|
return new Location(line, start, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + line;
|
||||||
|
result = prime * result + start;
|
||||||
|
result = prime * result + ((filename == null) ? 0 : filename.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;
|
||||||
|
Location other = (Location) obj;
|
||||||
|
if (line != other.line) return false;
|
||||||
|
if (start != other.start) return false;
|
||||||
|
if (filename == null && other.filename != null) return false;
|
||||||
|
else if (!filename.equals(other.filename)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/java/me/topchetoeu/jscript/common/Operation.java
Normal file
54
src/java/me/topchetoeu/jscript/common/Operation.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public enum Operation {
|
||||||
|
INSTANCEOF(1, 2),
|
||||||
|
IN(2, 2),
|
||||||
|
|
||||||
|
MULTIPLY(3, 2),
|
||||||
|
DIVIDE(4, 2),
|
||||||
|
MODULO(5, 2),
|
||||||
|
ADD(6, 2),
|
||||||
|
SUBTRACT(7, 2),
|
||||||
|
|
||||||
|
USHIFT_RIGHT(8, 2),
|
||||||
|
SHIFT_RIGHT(9, 2),
|
||||||
|
SHIFT_LEFT(10, 2),
|
||||||
|
|
||||||
|
GREATER(11, 2),
|
||||||
|
LESS(12, 2),
|
||||||
|
GREATER_EQUALS(13, 2),
|
||||||
|
LESS_EQUALS(14, 2),
|
||||||
|
LOOSE_EQUALS(15, 2),
|
||||||
|
LOOSE_NOT_EQUALS(16, 2),
|
||||||
|
EQUALS(17, 2),
|
||||||
|
NOT_EQUALS(18, 2),
|
||||||
|
|
||||||
|
AND(19, 2),
|
||||||
|
OR(20, 2),
|
||||||
|
XOR(21, 2),
|
||||||
|
|
||||||
|
NEG(23, 1),
|
||||||
|
POS(24, 1),
|
||||||
|
NOT(25, 1),
|
||||||
|
INVERSE(26, 1);
|
||||||
|
|
||||||
|
private static final HashMap<Integer, Operation> operations = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (var val : Operation.values()) operations.put(val.numeric, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int numeric;
|
||||||
|
public final int operands;
|
||||||
|
|
||||||
|
private Operation(int numeric, int n) {
|
||||||
|
this.numeric = numeric;
|
||||||
|
this.operands = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation fromNumeric(int i) {
|
||||||
|
return operations.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/java/me/topchetoeu/jscript/common/Reading.java
Normal file
28
src/java/me/topchetoeu/jscript/common/Reading.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/java/me/topchetoeu/jscript/common/RefTracker.java
Normal file
23
src/java/me/topchetoeu/jscript/common/RefTracker.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public class RefTracker {
|
||||||
|
public static void onDestroy(Object obj, Runnable runnable) {
|
||||||
|
var queue = new ReferenceQueue<>();
|
||||||
|
var ref = new WeakReference<>(obj, queue);
|
||||||
|
obj = null;
|
||||||
|
|
||||||
|
var th = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
queue.remove();
|
||||||
|
ref.get();
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) { return; }
|
||||||
|
});
|
||||||
|
th.setDaemon(true);
|
||||||
|
th.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
public interface ResultRunnable<T> {
|
||||||
|
T run();
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package me.topchetoeu.jscript.engine.scope;
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.core.scope.LocalScopeRecord;
|
||||||
|
|
||||||
public interface ScopeRecord {
|
public interface ScopeRecord {
|
||||||
public Object getKey(String name);
|
public Object getKey(String name);
|
||||||
public Object define(String name);
|
public Object define(String name);
|
||||||
public ScopeRecord parent();
|
|
||||||
public LocalScopeRecord child();
|
public LocalScopeRecord child();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package me.topchetoeu.jscript.events;
|
package me.topchetoeu.jscript.common.events;
|
||||||
|
|
||||||
public class DataNotifier<T> implements Awaitable<T> {
|
public class DataNotifier<T> {
|
||||||
private Notifier notifier = new Notifier();
|
private Notifier notifier = new Notifier();
|
||||||
private boolean isErr;
|
private boolean isErr;
|
||||||
private T val;
|
private T val;
|
||||||
@@ -11,15 +11,12 @@ public class DataNotifier<T> implements Awaitable<T> {
|
|||||||
isErr = true;
|
isErr = true;
|
||||||
notifier.next();
|
notifier.next();
|
||||||
}
|
}
|
||||||
public void error(Throwable t) {
|
|
||||||
error(new RuntimeException(t));
|
|
||||||
}
|
|
||||||
public void next(T val) {
|
public void next(T val) {
|
||||||
this.val = val;
|
this.val = val;
|
||||||
isErr = false;
|
isErr = false;
|
||||||
notifier.next();
|
notifier.next();
|
||||||
}
|
}
|
||||||
public T await() throws InterruptedException {
|
public T await() {
|
||||||
notifier.await();
|
notifier.await();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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,29 +1,96 @@
|
|||||||
package me.topchetoeu.jscript.json;
|
package me.topchetoeu.jscript.common.json;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
import me.topchetoeu.jscript.parsing.Operator;
|
import me.topchetoeu.jscript.compilation.parsing.Operator;
|
||||||
import me.topchetoeu.jscript.parsing.ParseRes;
|
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.parsing.Parsing;
|
import me.topchetoeu.jscript.compilation.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.parsing.Token;
|
import me.topchetoeu.jscript.compilation.parsing.Token;
|
||||||
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
import me.topchetoeu.jscript.core.values.ArrayValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.Values;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class JSON {
|
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) {
|
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||||
return Parsing.parseIdentifier(tokens, i);
|
return Parsing.parseIdentifier(tokens, i);
|
||||||
}
|
}
|
||||||
public static ParseRes<String> parseString(String filename, List<Token> tokens, int i) {
|
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
|
||||||
var res = Parsing.parseString(filename, tokens, i);
|
var res = Parsing.parseString(filename, tokens, i);
|
||||||
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
||||||
else return res.transform();
|
else return res.transform();
|
||||||
}
|
}
|
||||||
public static ParseRes<Double> parseNumber(String filename, List<Token> tokens, int i) {
|
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);
|
var res = Parsing.parseNumber(filename, tokens, i);
|
||||||
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n);
|
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
|
||||||
else return res.transform();
|
else return res.transform();
|
||||||
}
|
}
|
||||||
public static ParseRes<Boolean> parseBool(String filename, List<Token> tokens, int i) {
|
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
|
||||||
var id = parseIdentifier(tokens, i);
|
var id = parseIdentifier(tokens, i);
|
||||||
|
|
||||||
if (!id.isSuccess()) return ParseRes.failed();
|
if (!id.isSuccess()) return ParseRes.failed();
|
||||||
@@ -32,7 +99,7 @@ public class JSON {
|
|||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<?> parseValue(String filename, List<Token> tokens, int i) {
|
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
|
||||||
return ParseRes.any(
|
return ParseRes.any(
|
||||||
parseString(filename, tokens, i),
|
parseString(filename, tokens, i),
|
||||||
parseNumber(filename, tokens, i),
|
parseNumber(filename, tokens, i),
|
||||||
@@ -42,7 +109,7 @@ public class JSON {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<JSONMap> parseMap(String filename, List<Token> tokens, int i) {
|
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||||
|
|
||||||
@@ -82,7 +149,7 @@ public class JSON {
|
|||||||
|
|
||||||
return ParseRes.res(values, n);
|
return ParseRes.res(values, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<JSONList> parseList(String filename, List<Token> tokens, int i) {
|
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
||||||
|
|
||||||
@@ -109,7 +176,8 @@ public class JSON {
|
|||||||
|
|
||||||
return ParseRes.res(values, n);
|
return ParseRes.res(values, n);
|
||||||
}
|
}
|
||||||
public static JSONElement parse(String filename, String raw) {
|
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);
|
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
|
||||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||||
@@ -120,7 +188,28 @@ public class JSON {
|
|||||||
if (el.isNumber()) return Double.toString(el.number());
|
if (el.isNumber()) return Double.toString(el.number());
|
||||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||||
if (el.isNull()) return "null";
|
if (el.isNull()) return "null";
|
||||||
if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
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()) {
|
if (el.isList()) {
|
||||||
var res = new StringBuilder().append("[");
|
var res = new StringBuilder().append("[");
|
||||||
for (int i = 0; i < el.list().size(); i++) {
|
for (int i = 0; i < el.list().size(); i++) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.json;
|
package me.topchetoeu.jscript.common.json;
|
||||||
|
|
||||||
public class JSONElement {
|
public class JSONElement {
|
||||||
public static enum Type {
|
public static enum Type {
|
||||||
@@ -65,10 +65,22 @@ public class JSONElement {
|
|||||||
return (double)value;
|
return (double)value;
|
||||||
}
|
}
|
||||||
public boolean bool() {
|
public boolean bool() {
|
||||||
if (!isNumber()) throw new IllegalStateException("Element is not a boolean.");
|
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
|
||||||
return (boolean)value;
|
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) {
|
private JSONElement(Type type, Object val) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.value = val;
|
this.value = val;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.json;
|
package me.topchetoeu.jscript.common.json;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -10,6 +10,9 @@ public class JSONList extends ArrayList<JSONElement> {
|
|||||||
public JSONList(JSONElement ...els) {
|
public JSONList(JSONElement ...els) {
|
||||||
super(List.of(els));
|
super(List.of(els));
|
||||||
}
|
}
|
||||||
|
public JSONList(Collection<JSONElement> els) {
|
||||||
|
super(els);
|
||||||
|
}
|
||||||
|
|
||||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||||
@@ -17,5 +20,7 @@ public class JSONList extends ArrayList<JSONElement> {
|
|||||||
public JSONList add(boolean 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(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(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,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.json;
|
package me.topchetoeu.jscript.common.json;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -51,7 +51,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
|||||||
|
|
||||||
public JSONMap map(String path) {
|
public JSONMap map(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||||
return el.map();
|
return el.map();
|
||||||
}
|
}
|
||||||
public JSONMap map(String path, JSONMap defaultVal) {
|
public JSONMap map(String path, JSONMap defaultVal) {
|
||||||
@@ -63,7 +63,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
|||||||
|
|
||||||
public JSONList list(String path) {
|
public JSONList list(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||||
return el.list();
|
return el.list();
|
||||||
}
|
}
|
||||||
public JSONList list(String path, JSONList defaultVal) {
|
public JSONList list(String path, JSONList defaultVal) {
|
||||||
@@ -75,7 +75,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
|||||||
|
|
||||||
public String string(String path) {
|
public String string(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||||
return el.string();
|
return el.string();
|
||||||
}
|
}
|
||||||
public String string(String path, String defaultVal) {
|
public String string(String path, String defaultVal) {
|
||||||
@@ -87,7 +87,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
|||||||
|
|
||||||
public double number(String path) {
|
public double number(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||||
return el.number();
|
return el.number();
|
||||||
}
|
}
|
||||||
public double number(String path, double defaultVal) {
|
public double number(String path, double defaultVal) {
|
||||||
@@ -99,7 +99,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
|||||||
|
|
||||||
public boolean bool(String path) {
|
public boolean bool(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||||
return el.bool();
|
return el.bool();
|
||||||
}
|
}
|
||||||
public boolean bool(String path, boolean defaultVal) {
|
public boolean bool(String path, boolean defaultVal) {
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package me.topchetoeu.jscript.common.mapping;
|
||||||
|
|
||||||
|
public enum ConvertType {
|
||||||
|
Exact,
|
||||||
|
Lower,
|
||||||
|
Upper,
|
||||||
|
Both,
|
||||||
|
}
|
||||||
190
src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java
Normal file
190
src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package me.topchetoeu.jscript.common.mapping;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.core.scope.LocalScopeRecord;
|
||||||
|
import me.topchetoeu.jscript.utils.mapping.SourceMap;
|
||||||
|
|
||||||
|
public class FunctionMap {
|
||||||
|
public static class FunctionMapBuilder {
|
||||||
|
private final TreeMap<Integer, Location> sourceMap = new TreeMap<>();
|
||||||
|
private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>();
|
||||||
|
|
||||||
|
public Location toLocation(int pc) {
|
||||||
|
return sourceMap.headMap(pc, true).firstEntry().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMapBuilder setDebug(Location loc, BreakpointType type) {
|
||||||
|
if (loc == null || type == null || type == BreakpointType.NONE) return this;
|
||||||
|
breakpoints.put(loc, type);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public FunctionMapBuilder setLocation(int i, Location loc) {
|
||||||
|
if (loc == null || i < 0) return this;
|
||||||
|
sourceMap.put(i, loc);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) {
|
||||||
|
setDebug(loc, type);
|
||||||
|
setLocation(i, loc);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location first() {
|
||||||
|
if (sourceMap.size() == 0) return null;
|
||||||
|
return sourceMap.firstEntry().getValue();
|
||||||
|
}
|
||||||
|
public Location last() {
|
||||||
|
if (sourceMap.size() == 0) return null;
|
||||||
|
return sourceMap.lastEntry().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMap build(String[] localNames, String[] captureNames) {
|
||||||
|
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
|
||||||
|
}
|
||||||
|
public FunctionMap build(LocalScopeRecord scope) {
|
||||||
|
return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures());
|
||||||
|
}
|
||||||
|
public FunctionMap build() {
|
||||||
|
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FunctionMapBuilder() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final FunctionMap EMPTY = new FunctionMap();
|
||||||
|
|
||||||
|
private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
|
||||||
|
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
|
||||||
|
|
||||||
|
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
|
||||||
|
|
||||||
|
public final String[] localNames, captureNames;
|
||||||
|
|
||||||
|
public Location toLocation(int pc, boolean approxiamte) {
|
||||||
|
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
|
||||||
|
var res = pcToLoc.get(pc);
|
||||||
|
if (!approxiamte || res != null) return res;
|
||||||
|
var entry = pcToLoc.headMap(pc, true).lastEntry();
|
||||||
|
if (entry == null) return null;
|
||||||
|
else return entry.getValue();
|
||||||
|
}
|
||||||
|
public Location toLocation(int pc) {
|
||||||
|
return toLocation(pc, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreakpointType getBreakpoint(int pc) {
|
||||||
|
return bps.getOrDefault(pc, BreakpointType.NONE);
|
||||||
|
}
|
||||||
|
public Location correctBreakpoint(Location loc) {
|
||||||
|
var set = bpLocs.get(loc.filename());
|
||||||
|
if (set == null) return null;
|
||||||
|
else return set.ceiling(loc);
|
||||||
|
}
|
||||||
|
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
|
||||||
|
var candidates = new HashMap<Filename, TreeSet<Location>>();
|
||||||
|
|
||||||
|
for (var name : bpLocs.keySet()) {
|
||||||
|
if (filename.matcher(name.toString()).matches()) {
|
||||||
|
candidates.put(name, bpLocs.get(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = new ArrayList<Location>(candidates.size());
|
||||||
|
for (var candidate : candidates.entrySet()) {
|
||||||
|
res.add(candidate.getValue().ceiling(new Location(line, column, candidate.getKey())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public List<Location> breakpoints(Location start, Location end) {
|
||||||
|
if (!Objects.equals(start.filename(), end.filename())) return List.of();
|
||||||
|
NavigableSet<Location> set = bpLocs.get(start.filename());
|
||||||
|
if (set == null) return List.of();
|
||||||
|
|
||||||
|
if (start != null) set = set.tailSet(start, true);
|
||||||
|
if (end != null) set = set.headSet(end, true);
|
||||||
|
|
||||||
|
return set.stream().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location start() {
|
||||||
|
if (pcToLoc.size() == 0) return null;
|
||||||
|
return pcToLoc.firstEntry().getValue();
|
||||||
|
}
|
||||||
|
public Location end() {
|
||||||
|
if (pcToLoc.size() == 0) return null;
|
||||||
|
return pcToLoc.lastEntry().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMap apply(SourceMap map) {
|
||||||
|
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
||||||
|
|
||||||
|
for (var el : pcToLoc.entrySet()) {
|
||||||
|
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.bps.putAll(bps);
|
||||||
|
|
||||||
|
for (var el : bpLocs.entrySet()) {
|
||||||
|
for (var loc : el.getValue()) {
|
||||||
|
loc = map.toCompiled(loc);
|
||||||
|
if (loc == null) continue;
|
||||||
|
|
||||||
|
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
|
||||||
|
res.bpLocs.get(loc.filename()).add(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMap clone() {
|
||||||
|
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
||||||
|
res.pcToLoc.putAll(this.pcToLoc);
|
||||||
|
res.bps.putAll(bps);
|
||||||
|
res.bpLocs.putAll(bpLocs);
|
||||||
|
res.pcToLoc.putAll(pcToLoc);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) {
|
||||||
|
var locToPc = new HashMap<Location, Integer>();
|
||||||
|
|
||||||
|
for (var el : map.entrySet()) {
|
||||||
|
pcToLoc.put(el.getKey(), el.getValue());
|
||||||
|
locToPc.putIfAbsent(el.getValue(), el.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var el : breakpoints.entrySet()) {
|
||||||
|
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
|
||||||
|
bps.put(locToPc.get(el.getKey()), el.getValue());
|
||||||
|
|
||||||
|
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
|
||||||
|
bpLocs.get(el.getKey().filename()).add(el.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.localNames = localNames;
|
||||||
|
this.captureNames = captureNames;
|
||||||
|
}
|
||||||
|
private FunctionMap() {
|
||||||
|
localNames = new String[0];
|
||||||
|
captureNames = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FunctionMapBuilder builder() {
|
||||||
|
return new FunctionMapBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.common.Location;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
|
||||||
public abstract class AssignableStatement extends Statement {
|
public abstract class AssignableStatement extends Statement {
|
||||||
public abstract AssignStatement toAssign(Statement val, Operation operation);
|
public abstract Statement toAssign(Statement val, Operation operation);
|
||||||
|
|
||||||
protected AssignableStatement(Location loc) {
|
protected AssignableStatement(Location loc) {
|
||||||
super(loc);
|
super(loc);
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
||||||
|
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
|
||||||
|
import me.topchetoeu.jscript.core.scope.LocalScopeRecord;
|
||||||
|
|
||||||
|
public class CompileResult {
|
||||||
|
public final Vector<Instruction> instructions = new Vector<>();
|
||||||
|
public final List<CompileResult> children = new LinkedList<>();
|
||||||
|
public final FunctionMapBuilder map = FunctionMap.builder();
|
||||||
|
public final LocalScopeRecord scope;
|
||||||
|
public int length = 0;
|
||||||
|
|
||||||
|
public int temp() {
|
||||||
|
instructions.add(null);
|
||||||
|
return instructions.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompileResult add(Instruction instr) {
|
||||||
|
instructions.add(instr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public CompileResult set(int i, Instruction instr) {
|
||||||
|
instructions.set(i, instr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Instruction get(int i) {
|
||||||
|
return instructions.get(i);
|
||||||
|
}
|
||||||
|
public int size() { return instructions.size(); }
|
||||||
|
|
||||||
|
public void setDebug(Location loc, BreakpointType type) {
|
||||||
|
map.setDebug(loc, type);
|
||||||
|
}
|
||||||
|
public void setLocation(int i, Location loc) {
|
||||||
|
map.setLocation(i, loc);
|
||||||
|
}
|
||||||
|
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
|
||||||
|
map.setLocationAndDebug(i, loc, type);
|
||||||
|
}
|
||||||
|
public void setDebug(BreakpointType type) {
|
||||||
|
setDebug(map.last(), type);
|
||||||
|
}
|
||||||
|
public void setLocation(Location type) {
|
||||||
|
setLocation(instructions.size() - 1, type);
|
||||||
|
}
|
||||||
|
public void setLocationAndDebug(Location loc, BreakpointType type) {
|
||||||
|
setLocationAndDebug(instructions.size() - 1, loc, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompileResult addChild(CompileResult child) {
|
||||||
|
this.children.add(child);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMap map() {
|
||||||
|
return map.build(scope);
|
||||||
|
}
|
||||||
|
public FunctionBody body() {
|
||||||
|
var builtChildren = new FunctionBody[children.size()];
|
||||||
|
|
||||||
|
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
|
||||||
|
|
||||||
|
return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompileResult(LocalScopeRecord scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||||
|
|
||||||
|
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(CompileResult target) {
|
||||||
|
for (var stm : statements) stm.declare(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, 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, 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, false, BreakpointType.STEP_OVER);
|
||||||
|
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!polluted && pollute) {
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/java/me/topchetoeu/jscript/compilation/Statement.java
Normal file
27
src/java/me/topchetoeu/jscript/compilation/Statement.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
|
||||||
|
public abstract class Statement {
|
||||||
|
private Location _loc;
|
||||||
|
|
||||||
|
public boolean pure() { return false; }
|
||||||
|
public void declare(CompileResult target) { }
|
||||||
|
|
||||||
|
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
|
int start = target.size();
|
||||||
|
compile(target, pollute);
|
||||||
|
if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
|
||||||
|
}
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, 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.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class ThrowSyntaxStatement extends Statement {
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.throwSyntax(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThrowSyntaxStatement(SyntaxException e) {
|
||||||
|
super(e.loc);
|
||||||
|
this.name = e.msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||||
|
|
||||||
|
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(CompileResult target) {
|
||||||
|
for (var key : values) {
|
||||||
|
target.scope.define(key.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
for (var entry : values) {
|
||||||
|
if (entry.name == null) continue;
|
||||||
|
var key = target.scope.getKey(entry.name);
|
||||||
|
|
||||||
|
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||||
|
|
||||||
|
if (entry.value != null) {
|
||||||
|
FunctionStatement.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
|
||||||
|
target.add(Instruction.storeVar(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||||
|
super(loc);
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class BreakStatement extends Statement {
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.nop("break", label));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreakStatement(Location loc, String label) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class ContinueStatement extends Statement {
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.nop(loc(), "cont", label));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContinueStatement(Location loc, String label) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class DebugStatement extends Statement {
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.debug());
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugStatement(Location loc) {
|
||||||
|
super(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class DeleteStatement extends Statement {
|
||||||
|
public final Statement key;
|
||||||
|
public final Statement value;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
value.compile(target, true);
|
||||||
|
key.compile(target, true);
|
||||||
|
|
||||||
|
target.add(Instruction.delete());
|
||||||
|
if (pollute) target.add(Instruction.pushValue(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||||
|
super(loc);
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class DoWhileStatement extends Statement {
|
||||||
|
public final Statement condition, body;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declare(CompileResult target) {
|
||||||
|
body.declare(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
int start = target.size();
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
int mid = target.size();
|
||||||
|
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
int end = target.size();
|
||||||
|
|
||||||
|
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||||
|
target.add(Instruction.jmpIf(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,69 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target) {
|
||||||
|
body.declare(target);
|
||||||
|
if (isDeclaration) target.scope.define(varName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
var key = target.scope.getKey(varName);
|
||||||
|
|
||||||
|
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||||
|
|
||||||
|
if (varValue != null) {
|
||||||
|
varValue.compile(target, true);
|
||||||
|
target.add(Instruction.storeVar(target.scope.getKey(varName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
object.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
target.add(Instruction.keys(true));
|
||||||
|
|
||||||
|
int start = target.size();
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
int mid = target.temp();
|
||||||
|
|
||||||
|
target.add(Instruction.pushValue("value")).setLocation(varLocation);
|
||||||
|
target.add(Instruction.loadMember()).setLocation(varLocation);
|
||||||
|
target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
int end = target.size();
|
||||||
|
|
||||||
|
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||||
|
|
||||||
|
target.add(Instruction.jmp(start - end));
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,45 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class ForStatement extends Statement {
|
||||||
|
public final Statement declaration, assignment, condition, body;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declare(CompileResult target) {
|
||||||
|
declaration.declare(target);
|
||||||
|
body.declare(target);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
declaration.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
int start = target.size();
|
||||||
|
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
int mid = target.temp();
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
int beforeAssign = target.size();
|
||||||
|
assignment.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
int end = target.size();
|
||||||
|
|
||||||
|
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||||
|
|
||||||
|
target.add(Instruction.jmp(start - end));
|
||||||
|
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,48 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class IfStatement extends Statement {
|
||||||
|
public final Statement condition, body, elseBody;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declare(CompileResult target) {
|
||||||
|
body.declare(target);
|
||||||
|
if (elseBody != null) elseBody.declare(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
|
||||||
|
condition.compile(target, true, breakpoint);
|
||||||
|
|
||||||
|
if (elseBody == null) {
|
||||||
|
int i = target.temp();
|
||||||
|
body.compile(target, pollute, breakpoint);
|
||||||
|
int endI = target.size();
|
||||||
|
target.set(i, Instruction.jmpIfNot(endI - i));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int start = target.temp();
|
||||||
|
body.compile(target, pollute, breakpoint);
|
||||||
|
int mid = target.temp();
|
||||||
|
elseBody.compile(target, pollute, breakpoint);
|
||||||
|
int end = target.size();
|
||||||
|
|
||||||
|
target.set(start, Instruction.jmpIfNot(mid - start + 1));
|
||||||
|
target.set(mid, Instruction.jmp(end - mid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, 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,22 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class ReturnStatement extends Statement {
|
||||||
|
public final Statement value;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (value == null) target.add(Instruction.pushUndefined());
|
||||||
|
else value.compile(target, true);
|
||||||
|
target.add(Instruction.ret()).setLocation(loc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReturnStatement(Location loc, Statement value) {
|
||||||
|
super(loc);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.Type;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target) {
|
||||||
|
for (var stm : body) stm.declare(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
var caseToStatement = new HashMap<Integer, Integer>();
|
||||||
|
var statementToIndex = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
|
value.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
for (var ccase : cases) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
ccase.value.compile(target, true);
|
||||||
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
caseToStatement.put(target.temp(), ccase.statementI);
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = target.temp();
|
||||||
|
|
||||||
|
for (var stm : body) {
|
||||||
|
statementToIndex.put(statementToIndex.size(), target.size());
|
||||||
|
stm.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = target.size();
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start));
|
||||||
|
else target.set(start, Instruction.jmp(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(end - i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var el : caseToStatement.entrySet()) {
|
||||||
|
var i = statementToIndex.get(el.getValue());
|
||||||
|
if (i == null) i = end;
|
||||||
|
target.set(el.getKey(), Instruction.jmpIf(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,21 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class ThrowStatement extends Statement {
|
||||||
|
public final Statement value;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
value.compile(target, true);
|
||||||
|
target.add(Instruction.throwInstr()).setLocation(loc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThrowStatement(Location loc, Statement value) {
|
||||||
|
super(loc);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target) {
|
||||||
|
tryBody.declare(target);
|
||||||
|
if (catchBody != null) catchBody.declare(target);
|
||||||
|
if (finallyBody != null) finallyBody.declare(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
|
||||||
|
int replace = target.temp();
|
||||||
|
|
||||||
|
int start = replace + 1, catchStart = -1, finallyStart = -1;
|
||||||
|
|
||||||
|
tryBody.compile(target, false);
|
||||||
|
target.add(Instruction.tryEnd());
|
||||||
|
|
||||||
|
if (catchBody != null) {
|
||||||
|
catchStart = target.size() - start;
|
||||||
|
target.scope.define(name, true);
|
||||||
|
catchBody.compile(target, false);
|
||||||
|
target.scope.undefine();
|
||||||
|
target.add(Instruction.tryEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finallyBody != null) {
|
||||||
|
finallyStart = target.size() - start;
|
||||||
|
finallyBody.compile(target, false);
|
||||||
|
target.add(Instruction.tryEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
|
||||||
|
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,52 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.Type;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class WhileStatement extends Statement {
|
||||||
|
public final Statement condition, body;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declare(CompileResult target) {
|
||||||
|
body.declare(target);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
int start = target.size();
|
||||||
|
condition.compile(target, true);
|
||||||
|
int mid = target.temp();
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
int end = target.size();
|
||||||
|
|
||||||
|
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||||
|
|
||||||
|
target.add(Instruction.jmp(start - end));
|
||||||
|
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
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(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
var instr = target.get(i);
|
||||||
|
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||||
|
target.set(i, Instruction.jmp(continuePoint - i));
|
||||||
|
}
|
||||||
|
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
||||||
|
target.set(i, Instruction.jmp(breakPoint - i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
|
||||||
public enum Operator {
|
public enum Operator {
|
||||||
MULTIPLY("*", Operation.MULTIPLY, 13),
|
MULTIPLY("*", Operation.MULTIPLY, 13),
|
||||||
@@ -55,7 +55,7 @@ public enum Operator {
|
|||||||
INCREASE("++"),
|
INCREASE("++"),
|
||||||
DECREASE("--");
|
DECREASE("--");
|
||||||
|
|
||||||
public final String value;
|
public final String readable;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
public final int precedence;
|
public final int precedence;
|
||||||
public final boolean reverse;
|
public final boolean reverse;
|
||||||
@@ -63,7 +63,7 @@ public enum Operator {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
for (var el : Operator.values()) {
|
for (var el : Operator.values()) {
|
||||||
ops.put(el.value, el);
|
ops.put(el.readable, el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,38 +74,38 @@ public enum Operator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Operator() {
|
private Operator() {
|
||||||
this.value = null;
|
this.readable = null;
|
||||||
this.operation = null;
|
this.operation = null;
|
||||||
this.precedence = -1;
|
this.precedence = -1;
|
||||||
this.reverse = false;
|
this.reverse = false;
|
||||||
}
|
}
|
||||||
private Operator(String value) {
|
private Operator(String value) {
|
||||||
this. value = value;
|
this.readable = value;
|
||||||
this.operation = null;
|
this.operation = null;
|
||||||
this.precedence = -1;
|
this.precedence = -1;
|
||||||
this.reverse = false;
|
this.reverse = false;
|
||||||
}
|
}
|
||||||
private Operator(String value, int precedence) {
|
private Operator(String value, int precedence) {
|
||||||
this. value = value;
|
this.readable = value;
|
||||||
this.operation = null;
|
this.operation = null;
|
||||||
this.precedence = precedence;
|
this.precedence = precedence;
|
||||||
this.reverse = false;
|
this.reverse = false;
|
||||||
}
|
}
|
||||||
private Operator(String value, int precedence, boolean reverse) {
|
private Operator(String value, int precedence, boolean reverse) {
|
||||||
this. value = value;
|
this.readable = value;
|
||||||
this.operation = null;
|
this.operation = null;
|
||||||
this.precedence = precedence;
|
this.precedence = precedence;
|
||||||
this.reverse = reverse;
|
this.reverse = reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Operator(String value, Operation funcName, int precedence) {
|
private Operator(String value, Operation funcName, int precedence) {
|
||||||
this. value = value;
|
this.readable = value;
|
||||||
this.operation = funcName;
|
this.operation = funcName;
|
||||||
this.precedence = precedence;
|
this.precedence = precedence;
|
||||||
this.reverse = false;
|
this.reverse = false;
|
||||||
}
|
}
|
||||||
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
||||||
this.value = value;
|
this.readable = value;
|
||||||
this.operation = funcName;
|
this.operation = funcName;
|
||||||
this.precedence = precedence;
|
this.precedence = precedence;
|
||||||
this.reverse = reverse;
|
this.reverse = reverse;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.common.Location;
|
||||||
import me.topchetoeu.jscript.parsing.Parsing.Parser;
|
import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser;
|
||||||
|
|
||||||
public class ParseRes<T> {
|
public class ParseRes<T> {
|
||||||
public static enum State {
|
public static enum State {
|
||||||
@@ -69,6 +69,9 @@ public class ParseRes<T> {
|
|||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
|
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> best = null;
|
||||||
ParseRes<? extends T> error = ParseRes.failed();
|
ParseRes<? extends T> error = ParseRes.failed();
|
||||||
|
|
||||||
@@ -1,44 +1,45 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
import me.topchetoeu.jscript.compilation.*;
|
import me.topchetoeu.jscript.compilation.*;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
|
||||||
import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
|
import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
|
||||||
import me.topchetoeu.jscript.compilation.control.*;
|
import me.topchetoeu.jscript.compilation.control.*;
|
||||||
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
||||||
|
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
|
||||||
import me.topchetoeu.jscript.compilation.values.*;
|
import me.topchetoeu.jscript.compilation.values.*;
|
||||||
import me.topchetoeu.jscript.engine.Environment;
|
import me.topchetoeu.jscript.core.scope.LocalScopeRecord;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
|
||||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
|
||||||
import me.topchetoeu.jscript.parsing.ParseRes.State;
|
|
||||||
|
|
||||||
// TODO: this has to be rewritten
|
// TODO: this has to be rewritten
|
||||||
public class Parsing {
|
public class Parsing {
|
||||||
public static interface Parser<T> {
|
public static interface Parser<T> {
|
||||||
ParseRes<T> parse(String filename, List<Token> tokens, int i);
|
ParseRes<T> parse(Filename filename, List<Token> tokens, int i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ObjProp {
|
private static class ObjProp {
|
||||||
public final Object name;
|
public final String name;
|
||||||
public final String access;
|
public final String access;
|
||||||
public final FunctionStatement func;
|
public final FunctionStatement func;
|
||||||
|
|
||||||
public ObjProp(Object name, String access, FunctionStatement func) {
|
public ObjProp(String name, String access, FunctionStatement func) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.access = access;
|
this.access = access;
|
||||||
this.func = func;
|
this.func = func;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final HashMap<Long, ArrayList<Instruction>> functions = new HashMap<>();
|
||||||
|
|
||||||
private static final HashSet<String> reserved = new HashSet<String>();
|
private static final HashSet<String> reserved = new HashSet<String>();
|
||||||
static {
|
static {
|
||||||
reserved.add("true");
|
reserved.add("true");
|
||||||
@@ -66,7 +67,7 @@ public class Parsing {
|
|||||||
reserved.add("delete");
|
reserved.add("delete");
|
||||||
reserved.add("break");
|
reserved.add("break");
|
||||||
reserved.add("continue");
|
reserved.add("continue");
|
||||||
reserved.add("debug");
|
reserved.add("debugger");
|
||||||
reserved.add("implements");
|
reserved.add("implements");
|
||||||
reserved.add("interface");
|
reserved.add("interface");
|
||||||
reserved.add("package");
|
reserved.add("package");
|
||||||
@@ -77,8 +78,6 @@ public class Parsing {
|
|||||||
// Although ES5 allow these, we will comply to ES6 here
|
// Although ES5 allow these, we will comply to ES6 here
|
||||||
reserved.add("const");
|
reserved.add("const");
|
||||||
reserved.add("let");
|
reserved.add("let");
|
||||||
reserved.add("async");
|
|
||||||
reserved.add("super");
|
|
||||||
// These are allowed too, however our parser considers them keywords
|
// These are allowed too, however our parser considers them keywords
|
||||||
reserved.add("undefined");
|
reserved.add("undefined");
|
||||||
reserved.add("arguments");
|
reserved.add("arguments");
|
||||||
@@ -88,7 +87,6 @@ public class Parsing {
|
|||||||
// We allow yield and await, because they're part of the custom async and generator functions
|
// We allow yield and await, because they're part of the custom async and generator functions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean isDigit(char c) {
|
public static boolean isDigit(char c) {
|
||||||
return c >= '0' && c <= '9';
|
return c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,7 @@ public class Parsing {
|
|||||||
private static final int CURR_MULTI_COMMENT = 8;
|
private static final int CURR_MULTI_COMMENT = 8;
|
||||||
private static final int CURR_SINGLE_COMMENT = 9;
|
private static final int CURR_SINGLE_COMMENT = 9;
|
||||||
|
|
||||||
private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, String filename, List<RawToken> tokens) {
|
private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List<RawToken> tokens) {
|
||||||
var res = currToken.toString();
|
var res = currToken.toString();
|
||||||
|
|
||||||
switch (currStage) {
|
switch (currStage) {
|
||||||
@@ -140,7 +138,7 @@ public class Parsing {
|
|||||||
|
|
||||||
// This method is so long because we're tokenizing the string using an iterative approach
|
// This method is so long because we're tokenizing the string using an iterative approach
|
||||||
// instead of a recursive descent parser. This is mainly done for performance reasons.
|
// instead of a recursive descent parser. This is mainly done for performance reasons.
|
||||||
private static ArrayList<RawToken> splitTokens(String filename, String raw) {
|
private static ArrayList<RawToken> splitTokens(Filename filename, String raw) {
|
||||||
var tokens = new ArrayList<RawToken>();
|
var tokens = new ArrayList<RawToken>();
|
||||||
var currToken = new StringBuilder(64);
|
var currToken = new StringBuilder(64);
|
||||||
|
|
||||||
@@ -216,8 +214,10 @@ public class Parsing {
|
|||||||
currToken.append(c);
|
currToken.append(c);
|
||||||
continue;
|
continue;
|
||||||
case CURR_SCIENTIFIC_NOT:
|
case CURR_SCIENTIFIC_NOT:
|
||||||
if (c == '-') currStage = CURR_NEG_SCIENTIFIC_NOT;
|
if (c == '-') {
|
||||||
else if (!isDigit(c)) {
|
if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT;
|
||||||
|
}
|
||||||
|
if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) {
|
||||||
i--; start--;
|
i--; start--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CURR_LITERAL:
|
case CURR_LITERAL:
|
||||||
if (isAlphanumeric(c) || c == '_') {
|
if (isAlphanumeric(c) || c == '_' || c == '$') {
|
||||||
currToken.append(c);
|
currToken.append(c);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -394,13 +394,17 @@ public class Parsing {
|
|||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int fromHex(char c) {
|
public static int fromHex(char c) {
|
||||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
if (c >= '0' && c <= '9') return c - '0';
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean inBounds(List<Token> tokens, int i) {
|
||||||
|
return i >= 0 && i < tokens.size();
|
||||||
|
}
|
||||||
|
|
||||||
private static String parseString(Location loc, String literal) {
|
private static String parseString(Location loc, String literal) {
|
||||||
var res = new StringBuilder();
|
var res = new StringBuilder();
|
||||||
|
|
||||||
@@ -565,20 +569,21 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
private static double parseNumber(Location loc, String value) {
|
private static double parseNumber(Location loc, String value) {
|
||||||
var res = parseNumber(false, value);
|
var res = parseNumber(false, value);
|
||||||
if (res == null) throw new SyntaxException(loc, "Invalid number format.");
|
if (res == null)
|
||||||
|
throw new SyntaxException(loc, "Invalid number format.");
|
||||||
else return res;
|
else return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Token> parseTokens(String filename, Collection<RawToken> tokens) {
|
private static List<Token> parseTokens(Filename filename, Collection<RawToken> tokens) {
|
||||||
var res = new ArrayList<Token>();
|
var res = new ArrayList<Token>();
|
||||||
|
|
||||||
for (var el : tokens) {
|
for (var el : tokens) {
|
||||||
var loc = new Location(el.line, el.start, filename);
|
var loc = new Location(el.line, el.start, filename);
|
||||||
switch (el.type) {
|
switch (el.type) {
|
||||||
case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break;
|
case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break;
|
||||||
case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value))); break;
|
case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value), el.value)); break;
|
||||||
case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value))); break;
|
case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value), el.value)); break;
|
||||||
case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value))); break;
|
case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value), el.value)); break;
|
||||||
case OPERATOR:
|
case OPERATOR:
|
||||||
Operator op = Operator.parse(el.value);
|
Operator op = Operator.parse(el.value);
|
||||||
if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value));
|
if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value));
|
||||||
@@ -590,11 +595,11 @@ public class Parsing {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Token> tokenize(String filename, String raw) {
|
public static List<Token> tokenize(Filename filename, String raw) {
|
||||||
return parseTokens(filename, splitTokens(filename, raw));
|
return parseTokens(filename, splitTokens(filename, raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Location getLoc(String filename, List<Token> tokens, int i) {
|
public static Location getLoc(Filename filename, List<Token> tokens, int i) {
|
||||||
if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename);
|
if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename);
|
||||||
if (i >= tokens.size()) i = tokens.size() - 1;
|
if (i >= tokens.size()) i = tokens.size() - 1;
|
||||||
return new Location(tokens.get(i).line, tokens.get(i).start, filename);
|
return new Location(tokens.get(i).line, tokens.get(i).start, filename);
|
||||||
@@ -605,49 +610,41 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isIdentifier()) {
|
if (tokens.get(i).isIdentifier()) {
|
||||||
return ParseRes.res(tokens.get(i).identifier(), 1);
|
return ParseRes.res(tokens.get(i).identifier(), 1);
|
||||||
}
|
}
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
else return ParseRes.failed();
|
||||||
return ParseRes.failed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public static ParseRes<Operator> parseOperator(List<Token> tokens, int i) {
|
public static ParseRes<Operator> parseOperator(List<Token> tokens, int i) {
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isOperator()) {
|
if (tokens.get(i).isOperator()) {
|
||||||
return ParseRes.res(tokens.get(i).operator(), 1);
|
return ParseRes.res(tokens.get(i).operator(), 1);
|
||||||
}
|
}
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
else return ParseRes.failed();
|
||||||
return ParseRes.failed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isIdentifier(List<Token> tokens, int i, String lit) {
|
public static boolean isIdentifier(List<Token> tokens, int i, String lit) {
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isIdentifier(lit)) {
|
if (tokens.get(i).isIdentifier(lit)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
else return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public static boolean isOperator(List<Token> tokens, int i, Operator op) {
|
public static boolean isOperator(List<Token> tokens, int i, Operator op) {
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isOperator(op)) {
|
if (tokens.get(i).isOperator(op)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
else return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public static boolean isStatementEnd(List<Token> tokens, int i) {
|
public static boolean isStatementEnd(List<Token> tokens, int i) {
|
||||||
if (isOperator(tokens, i, Operator.SEMICOLON)) return true;
|
if (isOperator(tokens, i, Operator.SEMICOLON)) return true;
|
||||||
@@ -660,34 +657,29 @@ public class Parsing {
|
|||||||
return !reserved.contains(name);
|
return !reserved.contains(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ConstantStatement> parseString(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ConstantStatement> parseString(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isString()) {
|
if (tokens.get(i).isString()) {
|
||||||
return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1);
|
return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1);
|
||||||
}
|
}
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
else return ParseRes.failed();
|
||||||
return ParseRes.failed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public static ParseRes<ConstantStatement> parseNumber(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ConstantStatement> parseNumber(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isNumber()) {
|
if (tokens.get(i).isNumber()) {
|
||||||
return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1);
|
return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1);
|
||||||
}
|
}
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
else return ParseRes.failed();
|
||||||
return ParseRes.failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public static ParseRes<RegexStatement> parseRegex(String filename, List<Token> tokens, int i) {
|
public static ParseRes<RegexStatement> parseRegex(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
try {
|
if (inBounds(tokens, i)) {
|
||||||
if (tokens.get(i).isRegex()) {
|
if (tokens.get(i).isRegex()) {
|
||||||
var val = tokens.get(i).regex();
|
var val = tokens.get(i).regex();
|
||||||
var index = val.lastIndexOf('/');
|
var index = val.lastIndexOf('/');
|
||||||
@@ -697,12 +689,10 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e) {
|
return ParseRes.failed();
|
||||||
return ParseRes.failed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ArrayStatement> parseArray(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ArrayStatement> parseArray(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
||||||
@@ -741,7 +731,7 @@ public class Parsing {
|
|||||||
return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n);
|
return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<List<String>> parseParamList(String filename, List<Token> tokens, int i) {
|
public static ParseRes<List<String>> parseParamList(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -773,17 +763,18 @@ public class Parsing {
|
|||||||
return ParseRes.res(args, n);
|
return ParseRes.res(args, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Object> parsePropName(String filename, List<Token> tokens, int i) {
|
public static ParseRes<String> parsePropName(Filename filename, List<Token> tokens, int i) {
|
||||||
var idRes = parseIdentifier(tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
if (idRes.isSuccess()) return ParseRes.res(idRes.result, 1);
|
|
||||||
var strRes = parseString(null, tokens, i);
|
|
||||||
if (strRes.isSuccess()) return ParseRes.res(strRes.result.value, 1);
|
|
||||||
var numRes = parseNumber(null, tokens, i);
|
|
||||||
if (numRes.isSuccess()) return ParseRes.res(numRes.result.value, 1);
|
|
||||||
|
|
||||||
return ParseRes.failed();
|
if (inBounds(tokens, i)) {
|
||||||
|
var token = tokens.get(i);
|
||||||
|
|
||||||
|
if (token.isNumber() || token.isIdentifier() || token.isString()) return ParseRes.res(token.rawValue, 1);
|
||||||
|
else return ParseRes.error(loc, "Expected identifier, string or number literal.");
|
||||||
|
}
|
||||||
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
public static ParseRes<ObjProp> parseObjectProp(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ObjProp> parseObjectProp(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -805,19 +796,21 @@ public class Parsing {
|
|||||||
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res);
|
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
|
var end = getLoc(filename, tokens, i + n - 1);
|
||||||
|
|
||||||
return ParseRes.res(new ObjProp(
|
return ParseRes.res(new ObjProp(
|
||||||
name, access,
|
name, access,
|
||||||
new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result)
|
new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result)
|
||||||
), n);
|
), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ObjectStatement> parseObject(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ObjectStatement> parseObject(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||||
|
|
||||||
var values = new LinkedHashMap<Object, Statement>();
|
var values = new LinkedHashMap<String, Statement>();
|
||||||
var getters = new LinkedHashMap<Object, FunctionStatement>();
|
var getters = new LinkedHashMap<String, FunctionStatement>();
|
||||||
var setters = new LinkedHashMap<Object, FunctionStatement>();
|
var setters = new LinkedHashMap<String, FunctionStatement>();
|
||||||
|
|
||||||
if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||||
n++;
|
n++;
|
||||||
@@ -867,7 +860,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n);
|
return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<NewStatement> parseNew(String filename, List<Token> tokens, int i) {
|
public static ParseRes<CallStatement> parseNew(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed();
|
||||||
@@ -878,12 +871,12 @@ public class Parsing {
|
|||||||
var callRes = parseCall(filename, tokens, i + n, valRes.result, 0);
|
var callRes = parseCall(filename, tokens, i + n, valRes.result, 0);
|
||||||
n += callRes.n;
|
n += callRes.n;
|
||||||
if (callRes.isError()) return callRes.transform();
|
if (callRes.isError()) return callRes.transform();
|
||||||
else if (callRes.isFailed()) return ParseRes.res(new NewStatement(loc, valRes.result), n);
|
else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n);
|
||||||
var call = (CallStatement)callRes.result;
|
var call = (CallStatement)callRes.result;
|
||||||
|
|
||||||
return ParseRes.res(new NewStatement(loc, call.func, call.args), n);
|
return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<TypeofStatement> parseTypeof(String filename, List<Token> tokens, int i) {
|
public static ParseRes<TypeofStatement> parseTypeof(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed();
|
||||||
@@ -894,7 +887,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new TypeofStatement(loc, valRes.result), n);
|
return ParseRes.res(new TypeofStatement(loc, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<VoidStatement> parseVoid(String filename, List<Token> tokens, int i) {
|
public static ParseRes<DiscardStatement> parseVoid(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed();
|
||||||
@@ -903,9 +896,9 @@ public class Parsing {
|
|||||||
if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes);
|
if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes);
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new VoidStatement(loc, valRes.result), n);
|
return ParseRes.res(new DiscardStatement(loc, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Statement> parseDelete(String filename, List<Token> tokens, int i) {
|
public static ParseRes<? extends Statement> parseDelete(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed();
|
||||||
@@ -926,7 +919,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<FunctionStatement> parseFunction(String filename, List<Token> tokens, int i, boolean statement) {
|
public static ParseRes<FunctionStatement> parseFunction(Filename filename, List<Token> tokens, int i, boolean statement) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -964,12 +957,13 @@ public class Parsing {
|
|||||||
|
|
||||||
var res = parseCompound(filename, tokens, i + n);
|
var res = parseCompound(filename, tokens, i + n);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
var end = getLoc(filename, tokens, i + n - 1);
|
||||||
|
|
||||||
if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, name, args.toArray(String[]::new), res.result), n);
|
if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n);
|
||||||
else return ParseRes.error(loc, "Expected a compound statement for function.", res);
|
else return ParseRes.error(loc, "Expected a compound statement for function.", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<OperationStatement> parseUnary(String filename, List<Token> tokens, int i) {
|
public static ParseRes<OperationStatement> parseUnary(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -988,9 +982,9 @@ public class Parsing {
|
|||||||
var res = parseValue(filename, tokens, n + i, 14);
|
var res = parseValue(filename, tokens, n + i, 14);
|
||||||
|
|
||||||
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
|
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
|
||||||
else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.value), res);
|
else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.readable), res);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeStatement> parsePrefixChange(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ChangeStatement> parsePrefixChange(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1007,7 +1001,7 @@ public class Parsing {
|
|||||||
if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator.");
|
if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator.");
|
||||||
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n);
|
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Statement> parseParens(String filename, List<Token> tokens, int i) {
|
public static ParseRes<? extends Statement> parseParens(Filename filename, List<Token> tokens, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed();
|
if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed();
|
||||||
|
|
||||||
@@ -1019,9 +1013,8 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(res.result, n);
|
return ParseRes.res(res.result, n);
|
||||||
}
|
}
|
||||||
@SuppressWarnings("all")
|
public static ParseRes<? extends Statement> parseSimple(Filename filename, List<Token> tokens, int i, boolean statement) {
|
||||||
public static ParseRes<? extends Statement> parseSimple(String filename, List<Token> tokens, int i, boolean statement) {
|
var res = new ArrayList<ParseRes<? extends Statement>>();
|
||||||
var res = new ArrayList<>();
|
|
||||||
|
|
||||||
if (!statement) {
|
if (!statement) {
|
||||||
res.add(parseObject(filename, tokens, i));
|
res.add(parseObject(filename, tokens, i));
|
||||||
@@ -1044,10 +1037,10 @@ public class Parsing {
|
|||||||
parseDelete(filename, tokens, i)
|
parseDelete(filename, tokens, i)
|
||||||
));
|
));
|
||||||
|
|
||||||
return ParseRes.any(res.toArray(ParseRes[]::new));
|
return ParseRes.any(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<VariableStatement> parseVariable(String filename, List<Token> tokens, int i) {
|
public static ParseRes<VariableStatement> parseVariable(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var literal = parseIdentifier(tokens, i);
|
var literal = parseIdentifier(tokens, i);
|
||||||
|
|
||||||
@@ -1055,7 +1048,6 @@ public class Parsing {
|
|||||||
|
|
||||||
if (!checkVarName(literal.result)) {
|
if (!checkVarName(literal.result)) {
|
||||||
if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported.");
|
if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported.");
|
||||||
if (literal.result.equals("async")) return ParseRes.error(loc, "'async' is not supported.");
|
|
||||||
if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported.");
|
if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported.");
|
||||||
if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported.");
|
if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported.");
|
||||||
return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result));
|
return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result));
|
||||||
@@ -1063,7 +1055,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new VariableStatement(loc, literal.result), 1);
|
return ParseRes.res(new VariableStatement(loc, literal.result), 1);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Statement> parseLiteral(String filename, List<Token> tokens, int i) {
|
public static ParseRes<? extends Statement> parseLiteral(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var id = parseIdentifier(tokens, i);
|
var id = parseIdentifier(tokens, i);
|
||||||
if (!id.isSuccess()) return id.transform();
|
if (!id.isSuccess()) return id.transform();
|
||||||
@@ -1075,10 +1067,10 @@ public class Parsing {
|
|||||||
return ParseRes.res(new ConstantStatement(loc, false), 1);
|
return ParseRes.res(new ConstantStatement(loc, false), 1);
|
||||||
}
|
}
|
||||||
if (id.result.equals("undefined")) {
|
if (id.result.equals("undefined")) {
|
||||||
return ParseRes.res(new ConstantStatement(loc, null), 1);
|
return ParseRes.res(ConstantStatement.ofUndefined(loc), 1);
|
||||||
}
|
}
|
||||||
if (id.result.equals("null")) {
|
if (id.result.equals("null")) {
|
||||||
return ParseRes.res(new ConstantStatement(loc, Values.NULL), 1);
|
return ParseRes.res(ConstantStatement.ofNull(loc), 1);
|
||||||
}
|
}
|
||||||
if (id.result.equals("this")) {
|
if (id.result.equals("this")) {
|
||||||
return ParseRes.res(new VariableIndexStatement(loc, 0), 1);
|
return ParseRes.res(new VariableIndexStatement(loc, 0), 1);
|
||||||
@@ -1091,7 +1083,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
public static ParseRes<IndexStatement> parseMember(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<IndexStatement> parseMember(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
|
|
||||||
@@ -1104,7 +1096,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n);
|
return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<IndexStatement> parseIndex(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<IndexStatement> parseIndex(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
|
|
||||||
@@ -1120,7 +1112,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n);
|
return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Statement> parseAssign(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<? extends Statement> parseAssign(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0 ;
|
int n = 0 ;
|
||||||
|
|
||||||
@@ -1132,10 +1124,12 @@ public class Parsing {
|
|||||||
var op = opRes.result;
|
var op = opRes.result;
|
||||||
if (!op.isAssign()) return ParseRes.failed();
|
if (!op.isAssign()) return ParseRes.failed();
|
||||||
|
|
||||||
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
|
if (!(prev instanceof AssignableStatement)) {
|
||||||
|
return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
|
||||||
|
}
|
||||||
|
|
||||||
var res = parseValue(filename, tokens, i + n, 2);
|
var res = parseValue(filename, tokens, i + n, 2);
|
||||||
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.value), res);
|
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.readable), res);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
Operation operation = null;
|
Operation operation = null;
|
||||||
@@ -1154,7 +1148,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n);
|
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<CallStatement> parseCall(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<CallStatement> parseCall(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
|
|
||||||
@@ -1172,8 +1166,7 @@ public class Parsing {
|
|||||||
prevArg = true;
|
prevArg = true;
|
||||||
}
|
}
|
||||||
else if (argRes.isError()) return argRes.transform();
|
else if (argRes.isError()) return argRes.transform();
|
||||||
else if (isOperator(tokens, i + n, Operator.COMMA)) {
|
else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) {
|
||||||
if (!prevArg) args.add(null);
|
|
||||||
prevArg = false;
|
prevArg = false;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
@@ -1181,12 +1174,12 @@ public class Parsing {
|
|||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else return ParseRes.failed();
|
else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n);
|
return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeStatement> parsePostfixChange(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<ChangeStatement> parsePostfixChange(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1204,7 +1197,7 @@ public class Parsing {
|
|||||||
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator.");
|
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator.");
|
||||||
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n);
|
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<OperationStatement> parseInstanceof(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<OperationStatement> parseInstanceof(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1217,7 +1210,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
|
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<OperationStatement> parseIn(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<OperationStatement> parseIn(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1230,7 +1223,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
|
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<CommaStatement> parseComma(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<CompoundStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
|
|
||||||
@@ -1241,9 +1234,9 @@ public class Parsing {
|
|||||||
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res);
|
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
return ParseRes.res(new CommaStatement(loc, prev, res.result), n);
|
return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<TernaryStatement> parseTernary(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<IfStatement> parseTernary(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
|
|
||||||
@@ -1260,9 +1253,9 @@ public class Parsing {
|
|||||||
if (!b.isSuccess()) return ParseRes.error(loc, "Expected a second value after the ternary operator.", b);
|
if (!b.isSuccess()) return ParseRes.error(loc, "Expected a second value after the ternary operator.", b);
|
||||||
n += b.n;
|
n += b.n;
|
||||||
|
|
||||||
return ParseRes.res(new TernaryStatement(loc, prev, a.result, b.result), n);
|
return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Statement> parseOperator(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
public static ParseRes<? extends Statement> parseOperator(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
var n = 0;
|
var n = 0;
|
||||||
|
|
||||||
@@ -1274,7 +1267,7 @@ public class Parsing {
|
|||||||
if (op.isAssign()) return parseAssign(filename, tokens, i + n - 1, prev, precedence);
|
if (op.isAssign()) return parseAssign(filename, tokens, i + n - 1, prev, precedence);
|
||||||
|
|
||||||
var res = parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1));
|
var res = parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1));
|
||||||
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.value), res);
|
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.readable), res);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
if (op == Operator.LAZY_AND) {
|
if (op == Operator.LAZY_AND) {
|
||||||
@@ -1287,7 +1280,7 @@ public class Parsing {
|
|||||||
return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n);
|
return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Statement> parseValue(String filename, List<Token> tokens, int i, int precedence, boolean statement) {
|
public static ParseRes<? extends Statement> parseValue(Filename filename, List<Token> tokens, int i, int precedence, boolean statement) {
|
||||||
Statement prev = null;
|
Statement prev = null;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1328,16 +1321,15 @@ public class Parsing {
|
|||||||
if (prev == null) return ParseRes.failed();
|
if (prev == null) return ParseRes.failed();
|
||||||
else return ParseRes.res(prev, n);
|
else return ParseRes.res(prev, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Statement> parseValue(String filename, List<Token> tokens, int i, int precedence) {
|
public static ParseRes<? extends Statement> parseValue(Filename filename, List<Token> tokens, int i, int precedence) {
|
||||||
return parseValue(filename, tokens, i, precedence, false);
|
return parseValue(filename, tokens, i, precedence, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Statement> parseValueStatement(String filename, List<Token> tokens, int i) {
|
public static ParseRes<? extends Statement> parseValueStatement(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
|
||||||
var valRes = parseValue(filename, tokens, i, 0, true);
|
var valRes = parseValue(filename, tokens, i, 0, true);
|
||||||
if (!valRes.isSuccess()) return valRes.transform();
|
if (!valRes.isSuccess()) return valRes.transform();
|
||||||
|
|
||||||
valRes.result.setLoc(loc);
|
// valRes.result.setLoc(loc);
|
||||||
var res = ParseRes.res(valRes.result, valRes.n);
|
var res = ParseRes.res(valRes.result, valRes.n);
|
||||||
|
|
||||||
if (isStatementEnd(tokens, i + res.n)) {
|
if (isStatementEnd(tokens, i + res.n)) {
|
||||||
@@ -1349,7 +1341,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res);
|
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res);
|
||||||
}
|
}
|
||||||
public static ParseRes<VariableDeclareStatement> parseVariableDeclare(String filename, List<Token> tokens, int i) {
|
public static ParseRes<VariableDeclareStatement> parseVariableDeclare(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed();
|
||||||
@@ -1362,6 +1354,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
var nameLoc = getLoc(filename, tokens, i + n);
|
||||||
var nameRes = parseIdentifier(tokens, i + n++);
|
var nameRes = parseIdentifier(tokens, i + n++);
|
||||||
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name.");
|
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name.");
|
||||||
|
|
||||||
@@ -1379,7 +1372,7 @@ public class Parsing {
|
|||||||
val = valRes.result;
|
val = valRes.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.add(new Pair(nameRes.result, val));
|
res.add(new Pair(nameRes.result, val, nameLoc));
|
||||||
|
|
||||||
if (isOperator(tokens, i + n, Operator.COMMA)) {
|
if (isOperator(tokens, i + n, Operator.COMMA)) {
|
||||||
n++;
|
n++;
|
||||||
@@ -1393,7 +1386,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ReturnStatement> parseReturn(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ReturnStatement> parseReturn(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed();
|
||||||
@@ -1405,8 +1398,7 @@ public class Parsing {
|
|||||||
|
|
||||||
var valRes = parseValue(filename, tokens, i + n, 0);
|
var valRes = parseValue(filename, tokens, i + n, 0);
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
if (valRes.isError())
|
if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes);
|
||||||
return ParseRes.error(loc, "Expected a return value.", valRes);
|
|
||||||
|
|
||||||
var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n);
|
var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n);
|
||||||
|
|
||||||
@@ -1417,7 +1409,7 @@ public class Parsing {
|
|||||||
else
|
else
|
||||||
return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
|
return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
|
||||||
}
|
}
|
||||||
public static ParseRes<ThrowStatement> parseThrow(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ThrowStatement> parseThrow(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed();
|
||||||
@@ -1435,7 +1427,7 @@ public class Parsing {
|
|||||||
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
|
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<BreakStatement> parseBreak(String filename, List<Token> tokens, int i) {
|
public static ParseRes<BreakStatement> parseBreak(Filename filename, List<Token> tokens, int i) {
|
||||||
if (!isIdentifier(tokens, i, "break")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i, "break")) return ParseRes.failed();
|
||||||
|
|
||||||
if (isStatementEnd(tokens, i + 1)) {
|
if (isStatementEnd(tokens, i + 1)) {
|
||||||
@@ -1453,7 +1445,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
|
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
|
||||||
}
|
}
|
||||||
public static ParseRes<ContinueStatement> parseContinue(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ContinueStatement> parseContinue(Filename filename, List<Token> tokens, int i) {
|
||||||
if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed();
|
||||||
|
|
||||||
if (isStatementEnd(tokens, i + 1)) {
|
if (isStatementEnd(tokens, i + 1)) {
|
||||||
@@ -1471,8 +1463,8 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
|
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
|
||||||
}
|
}
|
||||||
public static ParseRes<DebugStatement> parseDebug(String filename, List<Token> tokens, int i) {
|
public static ParseRes<DebugStatement> parseDebug(Filename filename, List<Token> tokens, int i) {
|
||||||
if (!isIdentifier(tokens, i, "debug")) return ParseRes.failed();
|
if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed();
|
||||||
|
|
||||||
if (isStatementEnd(tokens, i + 1)) {
|
if (isStatementEnd(tokens, i + 1)) {
|
||||||
if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2);
|
if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2);
|
||||||
@@ -1481,7 +1473,7 @@ public class Parsing {
|
|||||||
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
|
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<CompoundStatement> parseCompound(String filename, List<Token> tokens, int i) {
|
public static ParseRes<CompoundStatement> parseCompound(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||||
@@ -1507,7 +1499,7 @@ public class Parsing {
|
|||||||
statements.add(res.result);
|
statements.add(res.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)), n);
|
return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<String> parseLabel(List<Token> tokens, int i) {
|
public static ParseRes<String> parseLabel(List<Token> tokens, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@@ -1517,7 +1509,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(nameRes.result, n);
|
return ParseRes.res(nameRes.result, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<IfStatement> parseIf(String filename, List<Token> tokens, int i) {
|
public static ParseRes<IfStatement> parseIf(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1528,8 +1520,7 @@ public class Parsing {
|
|||||||
if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes);
|
if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes);
|
||||||
n += condRes.n;
|
n += condRes.n;
|
||||||
|
|
||||||
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE))
|
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition.");
|
||||||
return ParseRes.error(loc, "Expected a closing paren after if condition.");
|
|
||||||
|
|
||||||
var res = parseStatement(filename, tokens, i + n);
|
var res = parseStatement(filename, tokens, i + n);
|
||||||
if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res);
|
if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res);
|
||||||
@@ -1544,7 +1535,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n);
|
return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<WhileStatement> parseWhile(String filename, List<Token> tokens, int i) {
|
public static ParseRes<WhileStatement> parseWhile(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1566,7 +1557,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n);
|
return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<Statement> parseSwitchCase(String filename, List<Token> tokens, int i) {
|
public static ParseRes<Statement> parseSwitchCase(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1586,7 +1577,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(null, 2);
|
return ParseRes.res(null, 2);
|
||||||
}
|
}
|
||||||
public static ParseRes<SwitchStatement> parseSwitch(String filename, List<Token> tokens, int i) {
|
public static ParseRes<SwitchStatement> parseSwitch(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1646,7 +1637,7 @@ public class Parsing {
|
|||||||
statements.toArray(Statement[]::new)
|
statements.toArray(Statement[]::new)
|
||||||
), n);
|
), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<DoWhileStatement> parseDoWhile(String filename, List<Token> tokens, int i) {
|
public static ParseRes<DoWhileStatement> parseDoWhile(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1675,7 +1666,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon.");
|
else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon.");
|
||||||
}
|
}
|
||||||
public static ParseRes<Statement> parseFor(String filename, List<Token> tokens, int i) {
|
public static ParseRes<Statement> parseFor(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1689,15 +1680,14 @@ public class Parsing {
|
|||||||
|
|
||||||
if (isOperator(tokens, i + n, Operator.SEMICOLON)) {
|
if (isOperator(tokens, i + n, Operator.SEMICOLON)) {
|
||||||
n++;
|
n++;
|
||||||
decl = new CompoundStatement(loc);
|
decl = new CompoundStatement(loc, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var declRes = ParseRes.any(
|
var declRes = ParseRes.any(
|
||||||
parseVariableDeclare(filename, tokens, i + n),
|
parseVariableDeclare(filename, tokens, i + n),
|
||||||
parseValueStatement(filename, tokens, i + n)
|
parseValueStatement(filename, tokens, i + n)
|
||||||
);
|
);
|
||||||
if (!declRes.isSuccess())
|
if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
|
||||||
return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
|
|
||||||
n += declRes.n;
|
n += declRes.n;
|
||||||
decl = declRes.result;
|
decl = declRes.result;
|
||||||
}
|
}
|
||||||
@@ -1716,7 +1706,7 @@ public class Parsing {
|
|||||||
|
|
||||||
if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) {
|
if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) {
|
||||||
n++;
|
n++;
|
||||||
inc = new CompoundStatement(loc);
|
inc = new CompoundStatement(loc, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var incRes = parseValue(filename, tokens, i + n, 0);
|
var incRes = parseValue(filename, tokens, i + n, 0);
|
||||||
@@ -1733,7 +1723,7 @@ public class Parsing {
|
|||||||
|
|
||||||
return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n);
|
return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ForInStatement> parseForIn(String filename, List<Token> tokens, int i) {
|
public static ParseRes<ForInStatement> parseForIn(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1751,6 +1741,7 @@ public class Parsing {
|
|||||||
|
|
||||||
var nameRes = parseIdentifier(tokens, i + n);
|
var nameRes = parseIdentifier(tokens, i + n);
|
||||||
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop.");
|
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop.");
|
||||||
|
var nameLoc = getLoc(filename, tokens, i + n);
|
||||||
n += nameRes.n;
|
n += nameRes.n;
|
||||||
|
|
||||||
Statement varVal = null;
|
Statement varVal = null;
|
||||||
@@ -1784,9 +1775,9 @@ public class Parsing {
|
|||||||
if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes);
|
if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes);
|
||||||
n += bodyRes.n;
|
n += bodyRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
|
return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<TryStatement> parseCatch(String filename, List<Token> tokens, int i) {
|
public static ParseRes<TryStatement> parseCatch(Filename filename, List<Token> tokens, int i) {
|
||||||
var loc = getLoc(filename, tokens, i);
|
var loc = getLoc(filename, tokens, i);
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
@@ -1827,8 +1818,8 @@ public class Parsing {
|
|||||||
return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n);
|
return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Statement> parseStatement(String filename, List<Token> tokens, int i) {
|
public static ParseRes<? extends Statement> parseStatement(Filename filename, List<Token> tokens, int i) {
|
||||||
if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1);
|
if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1);
|
||||||
if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed.");
|
if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed.");
|
||||||
return ParseRes.any(
|
return ParseRes.any(
|
||||||
parseVariableDeclare(filename, tokens, i),
|
parseVariableDeclare(filename, tokens, i),
|
||||||
@@ -1850,7 +1841,7 @@ public class Parsing {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Statement[] parse(String filename, String raw) {
|
public static Statement[] parse(Filename filename, String raw) {
|
||||||
var tokens = tokenize(filename, raw);
|
var tokens = tokenize(filename, raw);
|
||||||
var list = new ArrayList<Statement>();
|
var list = new ArrayList<Statement>();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -1871,42 +1862,31 @@ public class Parsing {
|
|||||||
return list.toArray(Statement[]::new);
|
return list.toArray(Statement[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CodeFunction compile(Environment environment, Statement ...statements) {
|
public static CompileResult compile(Statement ...statements) {
|
||||||
var target = environment.global.globalChild();
|
var target = new CompileResult(new LocalScopeRecord());
|
||||||
var subscope = target.child();
|
var stm = new CompoundStatement(null, true, statements);
|
||||||
var res = new ArrayList<Instruction>();
|
|
||||||
var body = new CompoundStatement(null, statements);
|
|
||||||
// var optimized = body.optimize();
|
|
||||||
if (body instanceof CompoundStatement) body = (CompoundStatement)body;
|
|
||||||
else body = new CompoundStatement(null, new Statement[] { body });
|
|
||||||
|
|
||||||
subscope.define("this");
|
target.scope.define("this");
|
||||||
subscope.define("arguments");
|
target.scope.define("arguments");
|
||||||
|
|
||||||
body.declare(target);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
body.compile(res, subscope);
|
stm.compile(target, true);
|
||||||
FunctionStatement.checkBreakAndCont(res, 0);
|
FunctionStatement.checkBreakAndCont(target, 0);
|
||||||
}
|
}
|
||||||
catch (SyntaxException e) {
|
catch (SyntaxException e) {
|
||||||
res.clear();
|
target = new CompileResult(new LocalScopeRecord());
|
||||||
res.add(Instruction.throwSyntax(e));
|
|
||||||
|
target.scope.define("this");
|
||||||
|
target.scope.define("arguments");
|
||||||
|
|
||||||
|
target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.size() != 0 && res.get(res.size() - 1).type == Type.DISCARD) {
|
target.add(Instruction.ret()).setLocation(stm.loc());
|
||||||
res.set(res.size() - 1, Instruction.ret());
|
|
||||||
}
|
|
||||||
else res.add(Instruction.ret());
|
|
||||||
|
|
||||||
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.toArray(Instruction[]::new));
|
return target;
|
||||||
}
|
}
|
||||||
public static CodeFunction compile(Environment environment, String filename, String raw) {
|
public static CompileResult compile(Filename filename, String raw) {
|
||||||
try {
|
return compile(parse(filename, raw));
|
||||||
return compile(environment, parse(filename, raw));
|
|
||||||
}
|
|
||||||
catch (SyntaxException e) {
|
|
||||||
return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
public class RawToken {
|
public class RawToken {
|
||||||
public final String value;
|
public final String value;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.common.Location;
|
||||||
import me.topchetoeu.jscript.parsing.ParseRes.State;
|
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
|
||||||
|
|
||||||
public class TestRes {
|
public class TestRes {
|
||||||
public final State state;
|
public final State state;
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
public class Token {
|
public class Token {
|
||||||
public final Object value;
|
public final Object value;
|
||||||
|
public final String rawValue;
|
||||||
public final boolean isString;
|
public final boolean isString;
|
||||||
public final boolean isRegex;
|
public final boolean isRegex;
|
||||||
public final int line;
|
public final int line;
|
||||||
public final int start;
|
public final int start;
|
||||||
|
|
||||||
private Token(int line, int start, Object value, boolean isString, boolean isRegex) {
|
private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.rawValue = rawValue;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.isString = isString;
|
this.isString = isString;
|
||||||
this.isRegex = isRegex;
|
this.isRegex = isRegex;
|
||||||
}
|
}
|
||||||
private Token(int line, int start, Object value) {
|
private Token(int line, int start, Object value, String rawValue) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.rawValue = rawValue;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.isString = false;
|
this.isString = false;
|
||||||
@@ -37,19 +40,19 @@ public class Token {
|
|||||||
public String identifier() { return (String)value; }
|
public String identifier() { return (String)value; }
|
||||||
public Operator operator() { return (Operator)value; }
|
public Operator operator() { return (Operator)value; }
|
||||||
|
|
||||||
public static Token regex(int line, int start, String val) {
|
public static Token regex(int line, int start, String val, String rawValue) {
|
||||||
return new Token(line, start, val, false, true);
|
return new Token(line, start, val, rawValue, false, true);
|
||||||
}
|
}
|
||||||
public static Token string(int line, int start, String val) {
|
public static Token string(int line, int start, String val, String rawValue) {
|
||||||
return new Token(line, start, val, true, false);
|
return new Token(line, start, val, rawValue, true, false);
|
||||||
}
|
}
|
||||||
public static Token number(int line, int start, double val) {
|
public static Token number(int line, int start, double val, String rawValue) {
|
||||||
return new Token(line, start, val);
|
return new Token(line, start, val, rawValue);
|
||||||
}
|
}
|
||||||
public static Token identifier(int line, int start, String val) {
|
public static Token identifier(int line, int start, String val) {
|
||||||
return new Token(line, start, val);
|
return new Token(line, start, val, val);
|
||||||
}
|
}
|
||||||
public static Token operator(int line, int start, Operator val) {
|
public static Token operator(int line, int start, Operator val) {
|
||||||
return new Token(line, start, val);
|
return new Token(line, start, val, val.readable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.parsing;
|
package me.topchetoeu.jscript.compilation.parsing;
|
||||||
|
|
||||||
enum TokenType {
|
enum TokenType {
|
||||||
REGEX,
|
REGEX,
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadArr(statements.length));
|
||||||
|
|
||||||
|
for (var i = 0; i < statements.length; i++) {
|
||||||
|
var el = statements[i];
|
||||||
|
if (el != null) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushValue(i));
|
||||||
|
el.compile(target, true);
|
||||||
|
target.add(Instruction.storeMember());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayStatement(Location loc, Statement[] statements) {
|
||||||
|
super(loc);
|
||||||
|
this.statements = statements;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class CallStatement extends Statement {
|
||||||
|
public final Statement func;
|
||||||
|
public final Statement[] args;
|
||||||
|
public final boolean isNew;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
|
if (isNew) func.compile(target, true);
|
||||||
|
else if (func instanceof IndexStatement) {
|
||||||
|
((IndexStatement)func).compile(target, true, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
func.compile(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var arg : args) arg.compile(target, true);
|
||||||
|
|
||||||
|
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
|
||||||
|
else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type);
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
import java.util.List;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
|
||||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|
||||||
|
|
||||||
public class ChangeStatement extends Statement {
|
public class ChangeStatement extends Statement {
|
||||||
public final AssignableStatement value;
|
public final AssignableStatement value;
|
||||||
@@ -15,11 +13,13 @@ public class ChangeStatement extends Statement {
|
|||||||
public final boolean postfix;
|
public final boolean postfix;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean pollutesStack() { return true; }
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true);
|
||||||
@Override
|
if (!pollute) target.add(Instruction.discard());
|
||||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
else if (postfix) {
|
||||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix);
|
target.add(Instruction.pushValue(addAmount));
|
||||||
|
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class ConstantStatement extends Statement {
|
||||||
|
public final Object value;
|
||||||
|
public final boolean isNull;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return true; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (pollute) {
|
||||||
|
if (isNull) target.add(Instruction.pushNull());
|
||||||
|
else if (value instanceof Double) target.add(Instruction.pushValue((Double)value));
|
||||||
|
else if (value instanceof String) target.add(Instruction.pushValue((String)value));
|
||||||
|
else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value));
|
||||||
|
else target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConstantStatement(Location loc, Object val, boolean isNull) {
|
||||||
|
super(loc);
|
||||||
|
this.value = val;
|
||||||
|
this.isNull = isNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConstantStatement(Location loc, boolean val) {
|
||||||
|
this(loc, val, false);
|
||||||
|
}
|
||||||
|
public ConstantStatement(Location loc, String val) {
|
||||||
|
this(loc, val, false);
|
||||||
|
}
|
||||||
|
public ConstantStatement(Location loc, double val) {
|
||||||
|
this(loc, val, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConstantStatement ofUndefined(Location loc) {
|
||||||
|
return new ConstantStatement(loc, null, false);
|
||||||
|
}
|
||||||
|
public static ConstantStatement ofNull(Location loc) {
|
||||||
|
return new ConstantStatement(loc, null, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class DiscardStatement extends Statement {
|
||||||
|
public final Statement value;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return value.pure(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
value.compile(target, false);
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscardStatement(Location loc, Statement val) {
|
||||||
|
super(loc);
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.Type;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return varName == null && statement; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declare(CompileResult target) {
|
||||||
|
if (varName != null && statement) target.scope.define(varName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkBreakAndCont(CompileResult 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.map.toLocation(i), "Break was placed outside a loop.");
|
||||||
|
}
|
||||||
|
if (target.get(i).is(0, "cont")) {
|
||||||
|
throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompileResult compileBody(CompileResult target, boolean pollute, 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 subtarget = new CompileResult(target.scope.child());
|
||||||
|
|
||||||
|
subtarget.scope.define("this");
|
||||||
|
var argsVar = subtarget.scope.define("arguments");
|
||||||
|
|
||||||
|
if (args.length > 0) {
|
||||||
|
for (var i = 0; i < args.length; i++) {
|
||||||
|
subtarget.add(Instruction.loadVar(argsVar));
|
||||||
|
subtarget.add(Instruction.pushValue(i));
|
||||||
|
subtarget.add(Instruction.loadMember());
|
||||||
|
subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statement && this.varName != null) {
|
||||||
|
subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.declare(subtarget);
|
||||||
|
body.compile(subtarget, false);
|
||||||
|
subtarget.length = args.length;
|
||||||
|
subtarget.add(Instruction.ret()).setLocation(end);
|
||||||
|
checkBreakAndCont(subtarget, 0);
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures()));
|
||||||
|
return target.addChild(subtarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compile(CompileResult target, 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, pollute || hasVar || hasName, bp);
|
||||||
|
|
||||||
|
if (hasName) {
|
||||||
|
if (pollute || hasVar) target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushValue("name"));
|
||||||
|
target.add(Instruction.pushValue(name));
|
||||||
|
target.add(Instruction.storeMember());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasVar) {
|
||||||
|
var key = target.scope.getKey(this.varName);
|
||||||
|
|
||||||
|
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||||
|
target.add(Instruction.storeVar(target.scope.getKey(this.varName), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void compile(CompileResult target, boolean pollute, String name) {
|
||||||
|
compile(target, pollute, name, BreakpointType.NONE);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
|
||||||
|
compile(target, pollute, (String)null, bp);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, 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, CompileResult target, boolean pollute, String name) {
|
||||||
|
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name);
|
||||||
|
else stm.compile(target, pollute);
|
||||||
|
}
|
||||||
|
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp);
|
||||||
|
else stm.compile(target, pollute, bp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class GlobalThisStatement extends Statement {
|
||||||
|
@Override public boolean pure() { return true; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (pollute) target.add(Instruction.loadGlob());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalThisStatement(Location loc) {
|
||||||
|
super(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
if (operation != null) {
|
||||||
|
object.compile(target, true);
|
||||||
|
index.compile(target, true);
|
||||||
|
target.add(Instruction.dup(2));
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember());
|
||||||
|
value.compile(target, true);
|
||||||
|
target.add(Instruction.operation(operation));
|
||||||
|
|
||||||
|
target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
object.compile(target, true);
|
||||||
|
index.compile(target, true);
|
||||||
|
value.compile(target, true);
|
||||||
|
|
||||||
|
target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean dupObj, boolean pollute) {
|
||||||
|
object.compile(target, true);
|
||||||
|
if (dupObj) target.add(Instruction.dup());
|
||||||
|
|
||||||
|
index.compile(target, true);
|
||||||
|
target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, false, pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndexStatement(Location loc, Statement object, Statement index) {
|
||||||
|
super(loc);
|
||||||
|
this.object = object;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class LazyAndStatement extends Statement {
|
||||||
|
public final Statement first, second;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
first.compile(target, true);
|
||||||
|
if (pollute) target.add(Instruction.dup());
|
||||||
|
int start = target.temp();
|
||||||
|
if (pollute) target.add(Instruction.discard());
|
||||||
|
second.compile(target, pollute);
|
||||||
|
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
||||||
|
super(loc);
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class LazyOrStatement extends Statement {
|
||||||
|
public final Statement first, second;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
first.compile(target, true);
|
||||||
|
if (pollute) target.add(Instruction.dup());
|
||||||
|
int start = target.temp();
|
||||||
|
if (pollute) target.add(Instruction.discard());
|
||||||
|
second.compile(target, pollute);
|
||||||
|
target.set(start, Instruction.jmpIf(target.size() - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
||||||
|
super(loc);
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class ObjectStatement extends Statement {
|
||||||
|
public final Map<String, Statement> map;
|
||||||
|
public final Map<String, FunctionStatement> getters;
|
||||||
|
public final Map<String, FunctionStatement> setters;
|
||||||
|
|
||||||
|
@Override public boolean pure() {
|
||||||
|
for (var el : map.values()) {
|
||||||
|
if (!el.pure()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadObj());
|
||||||
|
|
||||||
|
for (var el : map.entrySet()) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushValue(el.getKey()));
|
||||||
|
var val = el.getValue();
|
||||||
|
FunctionStatement.compileWithName(val, target, true, el.getKey().toString());
|
||||||
|
target.add(Instruction.storeMember());
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = new ArrayList<Object>();
|
||||||
|
keys.addAll(getters.keySet());
|
||||||
|
keys.addAll(setters.keySet());
|
||||||
|
|
||||||
|
for (var key : keys) {
|
||||||
|
target.add(Instruction.pushValue((String)key));
|
||||||
|
|
||||||
|
if (getters.containsKey(key)) getters.get(key).compile(target, true);
|
||||||
|
else target.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
if (setters.containsKey(key)) setters.get(key).compile(target, true);
|
||||||
|
else target.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
target.add(Instruction.defProp());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectStatement(Location loc, Map<String, Statement> map, Map<String, FunctionStatement> getters, Map<String, FunctionStatement> setters) {
|
||||||
|
super(loc);
|
||||||
|
this.map = map;
|
||||||
|
this.getters = getters;
|
||||||
|
this.setters = setters;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
for (var arg : args) {
|
||||||
|
arg.compile(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.operation(operation));
|
||||||
|
else target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationStatement(Location loc, Operation operation, Statement ...args) {
|
||||||
|
super(loc);
|
||||||
|
this.operation = operation;
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadRegex(pattern, flags));
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegexStatement(Location loc, String pattern, String flags) {
|
||||||
|
super(loc);
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
if (value instanceof VariableStatement) {
|
||||||
|
var i = target.scope.getKey(((VariableStatement)value).name);
|
||||||
|
if (i instanceof String) {
|
||||||
|
target.add(Instruction.typeof((String)i));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.compile(target, pollute);
|
||||||
|
target.add(Instruction.typeof());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeofStatement(Location loc, Statement value) {
|
||||||
|
super(loc);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
var i = target.scope.getKey(name);
|
||||||
|
if (operation != null) {
|
||||||
|
target.add(Instruction.loadVar(i));
|
||||||
|
FunctionStatement.compileWithName(value, target, true, name);
|
||||||
|
target.add(Instruction.operation(operation));
|
||||||
|
target.add(Instruction.storeVar(i, pollute));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FunctionStatement.compileWithName(value, target, true, name);
|
||||||
|
target.add(Instruction.storeVar(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,22 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
public class VariableIndexStatement extends Statement {
|
||||||
|
public final int index;
|
||||||
|
|
||||||
|
@Override public boolean pure() { return true; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (pollute) target.add(Instruction.loadVar(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableIndexStatement(Location loc, int i) {
|
||||||
|
super(loc);
|
||||||
|
this.index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
|
|
||||||
|
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(CompileResult target, boolean pollute) {
|
||||||
|
var i = target.scope.getKey(name);
|
||||||
|
target.add(Instruction.loadVar(i));
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableStatement(Location loc, String name) {
|
||||||
|
super(loc);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/java/me/topchetoeu/jscript/core/Compiler.java
Normal file
23
src/java/me/topchetoeu/jscript/core/Compiler.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.core.scope.ValueVariable;
|
||||||
|
import me.topchetoeu.jscript.core.values.CodeFunction;
|
||||||
|
|
||||||
|
public interface Compiler {
|
||||||
|
public Key<Compiler> KEY = new Key<>();
|
||||||
|
|
||||||
|
public FunctionBody compile(Filename filename, String source);
|
||||||
|
|
||||||
|
public static Compiler get(Extensions ext) {
|
||||||
|
return ext.get(KEY, (filename, src) -> {
|
||||||
|
throw EngineException.ofError("No compiler attached to engine.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CodeFunction compile(Environment env, Filename filename, String raw) {
|
||||||
|
return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/java/me/topchetoeu/jscript/core/Context.java
Normal file
109
src/java/me/topchetoeu/jscript/core/Context.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.core.debug.DebugContext;
|
||||||
|
import me.topchetoeu.jscript.core.values.CodeFunction;
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.core.scope.ValueVariable;
|
||||||
|
|
||||||
|
public class Context implements Extensions {
|
||||||
|
public static final Context NULL = new Context();
|
||||||
|
|
||||||
|
public final Context parent;
|
||||||
|
public final Environment environment;
|
||||||
|
public final Frame frame;
|
||||||
|
// public final Engine engine;
|
||||||
|
public final int stackSize;
|
||||||
|
|
||||||
|
@Override public <T> void add(Key<T> key, T obj) {
|
||||||
|
if (environment != null) environment.add(key, obj);
|
||||||
|
// else if (engine != null) engine.add(key, obj);
|
||||||
|
}
|
||||||
|
@Override public <T> T get(Key<T> 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(Key<?> key) {
|
||||||
|
return
|
||||||
|
environment != null && environment.has(key);
|
||||||
|
// engine != null && engine.has(key);
|
||||||
|
}
|
||||||
|
@Override public boolean remove(Key<?> key) {
|
||||||
|
var res = false;
|
||||||
|
|
||||||
|
if (environment != null) res |= environment.remove(key);
|
||||||
|
// else if (engine != null) res |= engine.remove(key);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
@Override public Iterable<Key<?>> keys() {
|
||||||
|
if (environment == null) return List.of();
|
||||||
|
else return environment.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) {
|
||||||
|
DebugContext.get(this).onSource(filename, raw);
|
||||||
|
var result = new CodeFunction(environment, filename.toString(), Compiler.get(this).compile(filename, raw), new ValueVariable[0]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context pushFrame(Frame frame) {
|
||||||
|
var res = new Context(this, frame.function.environment, frame, stackSize + 1);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Frame> frames() {
|
||||||
|
var self = this;
|
||||||
|
return () -> new Iterator<Frame>() {
|
||||||
|
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 Frame next() {
|
||||||
|
update();
|
||||||
|
var res = curr.frame;
|
||||||
|
curr = curr.parent;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Context(Context parent, Environment environment, Frame frame, int stackSize) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.environment = environment;
|
||||||
|
this.frame = frame;
|
||||||
|
this.stackSize = stackSize;
|
||||||
|
|
||||||
|
if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) {
|
||||||
|
throw EngineException.ofRange("Stack overflow!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context() {
|
||||||
|
this(null, null, null, 0);
|
||||||
|
}
|
||||||
|
public Context(Environment env) {
|
||||||
|
this(null, env, null, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/java/me/topchetoeu/jscript/core/Engine.java
Normal file
95
src/java/me/topchetoeu/jscript/core/Engine.java
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.ResultRunnable;
|
||||||
|
import me.topchetoeu.jscript.common.events.DataNotifier;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.InterruptException;
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
|
||||||
|
public class Engine implements EventLoop {
|
||||||
|
private static class Task<T> implements Comparable<Task<?>> {
|
||||||
|
public final ResultRunnable<?> runnable;
|
||||||
|
public final DataNotifier<T> notifier = new DataNotifier<>();
|
||||||
|
public final boolean micro;
|
||||||
|
|
||||||
|
public Task(ResultRunnable<T> 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;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
|
||||||
|
var msg = new Task<T>(runnable, micro);
|
||||||
|
tasks.add(msg);
|
||||||
|
return msg.notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void run(boolean untilEmpty) {
|
||||||
|
while (!untilEmpty || !tasks.isEmpty()) {
|
||||||
|
try {
|
||||||
|
var task = tasks.take();
|
||||||
|
|
||||||
|
try {
|
||||||
|
((Task<Object>)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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataNotifier<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
|
||||||
|
return pushMsg(() -> {
|
||||||
|
return func.call(new Context(env), thisArg, args);
|
||||||
|
}, micro);
|
||||||
|
}
|
||||||
|
public DataNotifier<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||||
|
return pushMsg(() -> {
|
||||||
|
var ctx = new Context(env);
|
||||||
|
return ctx.compile(filename, raw).call(new Context(env), thisArg, args);
|
||||||
|
}, micro);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine() {
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/java/me/topchetoeu/jscript/core/Environment.java
Normal file
92
src/java/me/topchetoeu/jscript/core/Environment.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.core.scope.GlobalScope;
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.NativeFunction;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class Environment implements Extensions {
|
||||||
|
public static final Key<Compiler> COMPILE_FUNC = new Key<>();
|
||||||
|
|
||||||
|
public static final Key<FunctionValue> REGEX_CONSTR = new Key<>();
|
||||||
|
public static final Key<Integer> MAX_STACK_COUNT = new Key<>();
|
||||||
|
public static final Key<Boolean> HIDE_STACK = new Key<>();
|
||||||
|
|
||||||
|
public static final Key<ObjectValue> OBJECT_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> FUNCTION_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> ARRAY_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> BOOL_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> NUMBER_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> STRING_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> SYMBOL_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> ERROR_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> SYNTAX_ERR_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> TYPE_ERR_PROTO = new Key<>();
|
||||||
|
public static final Key<ObjectValue> RANGE_ERR_PROTO = new Key<>();
|
||||||
|
|
||||||
|
private HashMap<Key<?>, Object> data = new HashMap<>();
|
||||||
|
|
||||||
|
public GlobalScope global;
|
||||||
|
public WrapperProvider wrappers;
|
||||||
|
|
||||||
|
@Override public <T> void add(Key<T> key, T obj) {
|
||||||
|
data.put(key, obj);
|
||||||
|
}
|
||||||
|
@Override public <T> T get(Key<T> key) {
|
||||||
|
return (T)data.get(key);
|
||||||
|
}
|
||||||
|
@Override public boolean remove(Key<?> key) {
|
||||||
|
if (data.containsKey(key)) {
|
||||||
|
data.remove(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public boolean has(Key<?> key) {
|
||||||
|
return data.containsKey(key);
|
||||||
|
}
|
||||||
|
@Override public Iterable<Key<?>> keys() {
|
||||||
|
return data.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FunctionValue regexConstructor(Extensions ext) {
|
||||||
|
return ext.init(REGEX_CONSTR, new NativeFunction("RegExp", args -> {
|
||||||
|
throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return new Context(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/java/me/topchetoeu/jscript/core/EventLoop.java
Normal file
23
src/java/me/topchetoeu/jscript/core/EventLoop.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.ResultRunnable;
|
||||||
|
import me.topchetoeu.jscript.common.events.DataNotifier;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
|
||||||
|
public interface EventLoop {
|
||||||
|
public static final Key<EventLoop> KEY = new Key<>();
|
||||||
|
|
||||||
|
public static EventLoop get(Extensions ext) {
|
||||||
|
if (ext.hasNotNull(KEY)) return ext.get(KEY);
|
||||||
|
else return new EventLoop() {
|
||||||
|
@Override public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
|
||||||
|
throw EngineException.ofError("No event loop attached to environment.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro);
|
||||||
|
public default DataNotifier<Void> pushMsg(Runnable runnable, boolean micro) {
|
||||||
|
return pushMsg(() -> { runnable.run(); return null; }, micro);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/java/me/topchetoeu/jscript/core/Extensions.java
Normal file
38
src/java/me/topchetoeu/jscript/core/Extensions.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
public interface Extensions {
|
||||||
|
<T> T get(Key<T> key);
|
||||||
|
<T> void add(Key<T> key, T obj);
|
||||||
|
Iterable<Key<?>> keys();
|
||||||
|
|
||||||
|
boolean has(Key<?> key);
|
||||||
|
boolean remove(Key<?> key);
|
||||||
|
|
||||||
|
default void add(Key<Void> key) {
|
||||||
|
add(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean hasNotNull(Key<?> key) {
|
||||||
|
return has(key) && get(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> T get(Key<T> key, T defaultVal) {
|
||||||
|
if (has(key)) return get(key);
|
||||||
|
else return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> T init(Key<T> key, T val) {
|
||||||
|
if (has(key)) return get(key);
|
||||||
|
else {
|
||||||
|
add(key, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default void addAll(Extensions source) {
|
||||||
|
if (source == null) return;
|
||||||
|
for (var key : source.keys()) {
|
||||||
|
add((Key<Object>)key, (Object)source.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
329
src/java/me/topchetoeu/jscript/core/Frame.java
Normal file
329
src/java/me/topchetoeu/jscript/core/Frame.java
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.core.debug.DebugContext;
|
||||||
|
import me.topchetoeu.jscript.core.scope.LocalScope;
|
||||||
|
import me.topchetoeu.jscript.core.scope.ValueVariable;
|
||||||
|
import me.topchetoeu.jscript.core.values.ArrayValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.CodeFunction;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.ScopeValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.Values;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.InterruptException;
|
||||||
|
|
||||||
|
public class Frame {
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.instructions.length) instr = function.body.instructions[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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.jumpFlag = this.popTryFlag = false;
|
||||||
|
returnValue = InstructionRunner.exec(ctx, instr, this);
|
||||||
|
}
|
||||||
|
catch (EngineException e) {
|
||||||
|
error = e.add(ctx, function.name, DebugContext.get(ctx).getMapOrEmpty(function).toLocation(codePtr, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 ObjectValue getLocalScope() {
|
||||||
|
var names = new String[scope.locals.length];
|
||||||
|
var map = DebugContext.get(ctx).getMapOrEmpty(function);
|
||||||
|
|
||||||
|
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 < map.localNames.length) name = map.localNames[i];
|
||||||
|
|
||||||
|
names[i] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScopeValue(scope.locals, names);
|
||||||
|
}
|
||||||
|
public ObjectValue getCaptureScope() {
|
||||||
|
var names = new String[scope.captures.length];
|
||||||
|
var map = DebugContext.get(ctx).getMapOrEmpty(function);
|
||||||
|
|
||||||
|
for (int i = 0; i < scope.captures.length; i++) {
|
||||||
|
var name = "capture_" + (i - 2);
|
||||||
|
if (i < map.captureNames.length) name = map.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 Frame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
|
||||||
|
this.args = args.clone();
|
||||||
|
this.scope = new LocalScope(func.body.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
329
src/java/me/topchetoeu/jscript/core/InstructionRunner.java
Normal file
329
src/java/me/topchetoeu/jscript/core/InstructionRunner.java
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.core.scope.ValueVariable;
|
||||||
|
import me.topchetoeu.jscript.core.values.ArrayValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.CodeFunction;
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.Symbol;
|
||||||
|
import me.topchetoeu.jscript.core.values.Values;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
|
||||||
|
public class InstructionRunner {
|
||||||
|
private static Object execReturn(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
return frame.pop();
|
||||||
|
}
|
||||||
|
private static Object execThrow(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
throw new EngineException(frame.pop());
|
||||||
|
}
|
||||||
|
private static Object execThrowSyntax(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
throw EngineException.ofSyntax((String)instr.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execCall(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execCallNew(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execMakeVar(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
var name = (String)instr.get(0);
|
||||||
|
ctx.environment.global.define(name);
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execDefProp(Context ctx, Instruction instr, Frame 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.");
|
||||||
|
((ObjectValue)obj).defineProperty(ctx, name, (FunctionValue)getter, (FunctionValue)setter, false, false);
|
||||||
|
|
||||||
|
frame.push(obj);
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execKeys(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execTryStart(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execTryEnd(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.popTryFlag = true;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execDup(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execLoadValue(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
switch (instr.type) {
|
||||||
|
case PUSH_UNDEFINED: frame.push(null); break;
|
||||||
|
case PUSH_NULL: frame.push(Values.NULL); break;
|
||||||
|
default: frame.push(instr.get(0)); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execLoadVar(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execLoadObj(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.push(new ObjectValue());
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execLoadGlob(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.push(ctx.environment.global.obj);
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execLoadArr(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
var res = new ArrayValue();
|
||||||
|
res.setSize(instr.get(0));
|
||||||
|
frame.push(res);
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execLoadFunc(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
int id = 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, "", frame.function.body.children[id], captures);
|
||||||
|
|
||||||
|
frame.push(func);
|
||||||
|
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execLoadMember(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execLoadRegEx(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execDiscard(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.pop();
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execStoreMember(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execStoreVar(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execStoreSelfFunc(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execJmp(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.codePtr += (int)instr.get(0);
|
||||||
|
frame.jumpFlag = true;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execJmpIf(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
if (Values.toBoolean(frame.pop())) {
|
||||||
|
frame.codePtr += (int)instr.get(0);
|
||||||
|
frame.jumpFlag = true;
|
||||||
|
}
|
||||||
|
else frame.codePtr ++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
private static Object execJmpIfNot(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
if (Values.not(frame.pop())) {
|
||||||
|
frame.codePtr += (int)instr.get(0);
|
||||||
|
frame.jumpFlag = true;
|
||||||
|
}
|
||||||
|
else frame.codePtr ++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execTypeof(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
private static Object execNop(Context ctx, Instruction instr, Frame frame) {
|
||||||
|
frame.codePtr++;
|
||||||
|
return Values.NO_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execDelete(Context ctx, Instruction instr, Frame 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object execOperation(Context ctx, Instruction instr, Frame 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, Frame 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 PUSH_UNDEFINED:
|
||||||
|
case PUSH_NULL:
|
||||||
|
case PUSH_STRING:
|
||||||
|
case PUSH_NUMBER:
|
||||||
|
case PUSH_BOOL:
|
||||||
|
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_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() + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/java/me/topchetoeu/jscript/core/Key.java
Normal file
5
src/java/me/topchetoeu/jscript/core/Key.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
public class Key<T> {
|
||||||
|
|
||||||
|
}
|
||||||
12
src/java/me/topchetoeu/jscript/core/WrapperProvider.java
Normal file
12
src/java/me/topchetoeu/jscript/core/WrapperProvider.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package me.topchetoeu.jscript.core;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.core.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);
|
||||||
|
}
|
||||||
133
src/java/me/topchetoeu/jscript/core/debug/DebugContext.java
Normal file
133
src/java/me/topchetoeu/jscript/core/debug/DebugContext.java
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package me.topchetoeu.jscript.core.debug;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
||||||
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
import me.topchetoeu.jscript.core.Extensions;
|
||||||
|
import me.topchetoeu.jscript.core.Frame;
|
||||||
|
import me.topchetoeu.jscript.core.Key;
|
||||||
|
import me.topchetoeu.jscript.core.values.CodeFunction;
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
|
||||||
|
public class DebugContext {
|
||||||
|
public static final Key<DebugContext> KEY = new Key<>();
|
||||||
|
public static final Key<Void> IGNORE = new Key<>();
|
||||||
|
|
||||||
|
private HashMap<Filename, String> sources;
|
||||||
|
private WeakHashMap<FunctionBody, FunctionMap> maps;
|
||||||
|
private DebugHandler debugger;
|
||||||
|
|
||||||
|
public boolean attachDebugger(DebugHandler debugger) {
|
||||||
|
if (this.debugger != null) return false;
|
||||||
|
|
||||||
|
if (sources != null) {
|
||||||
|
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debugger = debugger;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public boolean detachDebugger() {
|
||||||
|
this.debugger = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugHandler debugger() {
|
||||||
|
if (debugger == null) return DebugHandler.empty();
|
||||||
|
else return debugger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionMap getMap(FunctionBody func) {
|
||||||
|
if (maps == null) return null;
|
||||||
|
return maps.get(func);
|
||||||
|
}
|
||||||
|
public FunctionMap getMap(FunctionValue func) {
|
||||||
|
if (maps == null || !(func instanceof CodeFunction)) return null;
|
||||||
|
return getMap(((CodeFunction)func).body);
|
||||||
|
}
|
||||||
|
public FunctionMap getMapOrEmpty(FunctionBody func) {
|
||||||
|
if (maps == null) return FunctionMap.EMPTY;
|
||||||
|
var res = maps.get(func);
|
||||||
|
if (res == null) return FunctionMap.EMPTY;
|
||||||
|
else return res;
|
||||||
|
}
|
||||||
|
public FunctionMap getMapOrEmpty(FunctionValue func) {
|
||||||
|
if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY;
|
||||||
|
return getMapOrEmpty(((CodeFunction)func).body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFramePop(Context ctx, Frame frame) {
|
||||||
|
if (debugger != null) debugger.onFramePop(ctx, frame);
|
||||||
|
}
|
||||||
|
public void onFramePush(Context ctx, Frame frame) {
|
||||||
|
if (debugger != null) debugger.onFramePush(ctx, frame);
|
||||||
|
}
|
||||||
|
public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||||
|
if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
public void onSource(Filename filename, String source) {
|
||||||
|
if (debugger != null) debugger.onSourceLoad(filename, source);
|
||||||
|
if (sources != null) sources.put(filename, source);
|
||||||
|
}
|
||||||
|
public void onFunctionLoad(FunctionBody func, FunctionMap map) {
|
||||||
|
if (maps != null) maps.put(func, map);
|
||||||
|
if (debugger != null) debugger.onFunctionLoad(func, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DebugContext(boolean enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
sources = new HashMap<>();
|
||||||
|
maps = new WeakHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugContext() {
|
||||||
|
this(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean enabled(Extensions exts) {
|
||||||
|
return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE);
|
||||||
|
}
|
||||||
|
public static DebugContext get(Extensions exts) {
|
||||||
|
if (enabled(exts)) return exts.get(KEY);
|
||||||
|
else return new DebugContext(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> stackTrace(Context ctx) {
|
||||||
|
var res = new ArrayList<String>();
|
||||||
|
var dbgCtx = get(ctx);
|
||||||
|
|
||||||
|
for (var el : ctx.frames()) {
|
||||||
|
var name = el.function.name;
|
||||||
|
|
||||||
|
var map = dbgCtx.getMapOrEmpty(el.function);
|
||||||
|
Location loc = null;
|
||||||
|
|
||||||
|
if (map != null) {
|
||||||
|
loc = map.toLocation(el.codePtr, true);
|
||||||
|
if (loc == null) loc = map.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/java/me/topchetoeu/jscript/core/debug/DebugHandler.java
Normal file
74
src/java/me/topchetoeu/jscript/core/debug/DebugHandler.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package me.topchetoeu.jscript.core.debug;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
||||||
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
import me.topchetoeu.jscript.core.Frame;
|
||||||
|
import me.topchetoeu.jscript.core.exceptions.EngineException;
|
||||||
|
|
||||||
|
public interface DebugHandler {
|
||||||
|
/**
|
||||||
|
* 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 onSourceLoad(Filename filename, String source);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a function body has been loaded
|
||||||
|
* @param body The body loaded
|
||||||
|
* @param map The map of the function
|
||||||
|
*/
|
||||||
|
void onFunctionLoad(FunctionBody body, FunctionMap map);
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Called when a function body has been loaded
|
||||||
|
// * @param body The body loaded
|
||||||
|
// * @param map The map of the function
|
||||||
|
// */
|
||||||
|
// void onFunctionUnload(FunctionBody body, FunctionMap 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 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 (currently does nothing)
|
||||||
|
*/
|
||||||
|
boolean onInstruction(Context ctx, Frame 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, Frame 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, Frame frame);
|
||||||
|
|
||||||
|
public static DebugHandler empty() {
|
||||||
|
return new DebugHandler () {
|
||||||
|
@Override public void onFramePop(Context ctx, Frame frame) { }
|
||||||
|
@Override public void onFramePush(Context ctx, Frame frame) { }
|
||||||
|
@Override public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public void onSourceLoad(Filename filename, String source) { }
|
||||||
|
@Override public void onFunctionLoad(FunctionBody body, FunctionMap map) { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.exceptions;
|
package me.topchetoeu.jscript.core.exceptions;
|
||||||
|
|
||||||
public class ConvertException extends RuntimeException {
|
public class ConvertException extends RuntimeException {
|
||||||
public final String source, target;
|
public final String source, target;
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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.Context;
|
||||||
|
import me.topchetoeu.jscript.core.Environment;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.Values;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue.PlaceholderProto;
|
||||||
|
|
||||||
|
public class EngineException extends RuntimeException {
|
||||||
|
public static class StackElement {
|
||||||
|
public final Location location;
|
||||||
|
public final String name;
|
||||||
|
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) res += "at " + loc.toString() + " ";
|
||||||
|
if (name != null && !name.equals("")) res += "in " + name + " ";
|
||||||
|
|
||||||
|
return res.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackElement(Context ctx, Location location, String name) {
|
||||||
|
if (name != null) name = name.trim();
|
||||||
|
if (name.equals("")) name = null;
|
||||||
|
|
||||||
|
if (ctx == null) this.ctx = null;
|
||||||
|
else this.ctx = new Context(ctx.environment);
|
||||||
|
this.location = location;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Object value;
|
||||||
|
public EngineException cause;
|
||||||
|
public Environment env = 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.name == null && el.location == null) return this;
|
||||||
|
setCtx(ctx);
|
||||||
|
stackTrace.add(el);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public EngineException setCause(EngineException cause) {
|
||||||
|
this.cause = cause;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public EngineException setCtx(Context ctx) {
|
||||||
|
if (this.env == null) this.env = ctx.environment;
|
||||||
|
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(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,6 +1,6 @@
|
|||||||
package me.topchetoeu.jscript.exceptions;
|
package me.topchetoeu.jscript.core.exceptions;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.common.Location;
|
||||||
|
|
||||||
public class SyntaxException extends RuntimeException {
|
public class SyntaxException extends RuntimeException {
|
||||||
public final Location loc;
|
public final Location loc;
|
||||||
80
src/java/me/topchetoeu/jscript/core/scope/GlobalScope.java
Normal file
80
src/java/me/topchetoeu/jscript/core/scope/GlobalScope.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package me.topchetoeu.jscript.core.scope;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.ScopeRecord;
|
||||||
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
import me.topchetoeu.jscript.core.values.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.core.values.NativeFunction;
|
||||||
|
import me.topchetoeu.jscript.core.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.core.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/java/me/topchetoeu/jscript/core/scope/LocalScope.java
Normal file
29
src/java/me/topchetoeu/jscript/core/scope/LocalScope.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package me.topchetoeu.jscript.core.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,25 +1,24 @@
|
|||||||
package me.topchetoeu.jscript.engine.scope;
|
package me.topchetoeu.jscript.core.scope;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.common.ScopeRecord;
|
||||||
|
|
||||||
public class LocalScopeRecord implements ScopeRecord {
|
public class LocalScopeRecord implements ScopeRecord {
|
||||||
public final LocalScopeRecord parent;
|
public final LocalScopeRecord parent;
|
||||||
public final GlobalScope global;
|
|
||||||
|
|
||||||
private final ArrayList<String> captures = new ArrayList<>();
|
private final ArrayList<String> captures = new ArrayList<>();
|
||||||
private final ArrayList<String> locals = new ArrayList<>();
|
private final ArrayList<String> locals = new ArrayList<>();
|
||||||
|
|
||||||
|
public String[] captures() {
|
||||||
|
return captures.toArray(String[]::new);
|
||||||
|
}
|
||||||
public String[] locals() {
|
public String[] locals() {
|
||||||
return locals.toArray(String[]::new);
|
return locals.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public LocalScopeRecord parent() { return parent; }
|
|
||||||
|
|
||||||
public LocalScopeRecord child() {
|
public LocalScopeRecord child() {
|
||||||
return new LocalScopeRecord(this, global);
|
return new LocalScopeRecord(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int localsCount() {
|
public int localsCount() {
|
||||||
@@ -59,12 +58,6 @@ public class LocalScopeRecord implements ScopeRecord {
|
|||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
public boolean has(Context ctx, String name) throws InterruptedException {
|
|
||||||
return
|
|
||||||
global.has(ctx, name) ||
|
|
||||||
locals.contains(name) ||
|
|
||||||
parent != null && parent.has(ctx, name);
|
|
||||||
}
|
|
||||||
public Object define(String name, boolean force) {
|
public Object define(String name, boolean force) {
|
||||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||||
locals.add(name);
|
locals.add(name);
|
||||||
@@ -77,12 +70,10 @@ public class LocalScopeRecord implements ScopeRecord {
|
|||||||
locals.remove(locals.size() - 1);
|
locals.remove(locals.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalScopeRecord(GlobalScope global) {
|
public LocalScopeRecord() {
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
this.global = global;
|
|
||||||
}
|
}
|
||||||
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
|
public LocalScopeRecord(LocalScopeRecord parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.global = global;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package me.topchetoeu.jscript.engine.scope;
|
package me.topchetoeu.jscript.core.scope;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.core.Context;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
import me.topchetoeu.jscript.core.values.Values;
|
||||||
|
|
||||||
public class ValueVariable implements Variable {
|
public class ValueVariable implements Variable {
|
||||||
public boolean readonly;
|
public boolean readonly;
|
||||||
9
src/java/me/topchetoeu/jscript/core/scope/Variable.java
Normal file
9
src/java/me/topchetoeu/jscript/core/scope/Variable.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package me.topchetoeu.jscript.core.scope;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
|
||||||
|
public interface Variable {
|
||||||
|
Object get(Context ctx);
|
||||||
|
default boolean readonly() { return true; }
|
||||||
|
default void set(Context ctx, Object val) { }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.engine.values;
|
package me.topchetoeu.jscript.core.values;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -6,7 +6,7 @@ import java.util.Comparator;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
|
||||||
// TODO: Make methods generic
|
// TODO: Make methods generic
|
||||||
public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||||
@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
private Object[] values;
|
private Object[] values;
|
||||||
private int size;
|
private int size;
|
||||||
|
|
||||||
private void alloc(int index) {
|
private Object[] alloc(int index) {
|
||||||
if (index < values.length) return;
|
index++;
|
||||||
|
if (index < values.length) return values;
|
||||||
if (index < values.length * 2) index = values.length * 2;
|
if (index < values.length * 2) index = values.length * 2;
|
||||||
|
|
||||||
var arr = new Object[index];
|
var arr = new Object[index];
|
||||||
System.arraycopy(values, 0, arr, 0, values.length);
|
System.arraycopy(values, 0, arr, 0, values.length);
|
||||||
values = arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() { return size; }
|
public int size() { return size; }
|
||||||
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
if (val < 0) return false;
|
if (val < 0) return false;
|
||||||
if (size > val) shrink(size - val);
|
if (size > val) shrink(size - val);
|
||||||
else {
|
else {
|
||||||
alloc(val);
|
values = alloc(val);
|
||||||
size = val;
|
size = val;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
public void set(Context ctx, int i, Object val) {
|
public void set(Context ctx, int i, Object val) {
|
||||||
if (i < 0) return;
|
if (i < 0) return;
|
||||||
|
|
||||||
alloc(i);
|
values = alloc(i);
|
||||||
|
|
||||||
val = Values.normalize(ctx, val);
|
val = Values.normalize(ctx, val);
|
||||||
if (val == null) val = UNDEFINED;
|
if (val == null) val = UNDEFINED;
|
||||||
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
if (i >= size) size = i + 1;
|
if (i >= size) size = i + 1;
|
||||||
}
|
}
|
||||||
public boolean has(int i) {
|
public boolean has(int i) {
|
||||||
return i >= 0 && i < values.length && values[i] != null;
|
return i >= 0 && i < size && values[i] != null;
|
||||||
}
|
}
|
||||||
public void remove(int i) {
|
public void remove(int i) {
|
||||||
if (i < 0 || i >= values.length) return;
|
if (i < 0 || i >= values.length) return;
|
||||||
@@ -81,12 +82,20 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
else arr[i + sourceStart] = values[i + destStart];
|
else arr[i + sourceStart] = values[i + destStart];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||||
|
if (arr == this) {
|
||||||
|
move(sourceStart, destStart, count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate in reverse to reallocate at most once
|
// 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--) {
|
for (var i = count - 1; i >= 0; i--) {
|
||||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
|
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
|
||||||
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
if (values[i + sourceStart] == UNDEFINED) arr.set(null, i + destStart, null);
|
||||||
else arr.set(ctx, i + destStart, values[i + sourceStart]);
|
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
|
||||||
|
else arr.set(null, i + destStart, values[i + sourceStart]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +106,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void move(int srcI, int dstI, int n) {
|
public void move(int srcI, int dstI, int n) {
|
||||||
alloc(dstI + n);
|
values = alloc(dstI + n);
|
||||||
|
|
||||||
System.arraycopy(values, srcI, values, dstI, n);
|
System.arraycopy(values, srcI, values, dstI, n);
|
||||||
|
|
||||||
@@ -122,7 +131,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
protected Object getField(Context ctx, Object key) {
|
||||||
if (key instanceof Number) {
|
if (key instanceof Number) {
|
||||||
var i = ((Number)key).doubleValue();
|
var i = ((Number)key).doubleValue();
|
||||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||||
@@ -133,7 +142,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
return super.getField(ctx, key);
|
return super.getField(ctx, key);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
protected boolean setField(Context ctx, Object key, Object val) {
|
||||||
if (key instanceof Number) {
|
if (key instanceof Number) {
|
||||||
var i = Values.number(key);
|
var i = Values.number(key);
|
||||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||||
@@ -145,7 +154,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
return super.setField(ctx, key, val);
|
return super.setField(ctx, key, val);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
protected boolean hasField(Context ctx, Object key) {
|
||||||
if (key instanceof Number) {
|
if (key instanceof Number) {
|
||||||
var i = Values.number(key);
|
var i = Values.number(key);
|
||||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||||
@@ -156,7 +165,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
return super.hasField(ctx, key);
|
return super.hasField(ctx, key);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected void deleteField(Context ctx, Object key) throws InterruptedException {
|
protected void deleteField(Context ctx, Object key) {
|
||||||
if (key instanceof Number) {
|
if (key instanceof Number) {
|
||||||
var i = Values.number(key);
|
var i = Values.number(key);
|
||||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||||
@@ -212,7 +221,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
|||||||
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
|
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayValue of(Context ctx, Collection<Object> values) {
|
public static ArrayValue of(Context ctx, Collection<?> values) {
|
||||||
return new ArrayValue(ctx, values.toArray(Object[]::new));
|
return new ArrayValue(ctx, values.toArray(Object[]::new));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
50
src/java/me/topchetoeu/jscript/core/values/CodeFunction.java
Normal file
50
src/java/me/topchetoeu/jscript/core/values/CodeFunction.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package me.topchetoeu.jscript.core.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
|
import me.topchetoeu.jscript.core.Context;
|
||||||
|
import me.topchetoeu.jscript.core.Environment;
|
||||||
|
import me.topchetoeu.jscript.core.Frame;
|
||||||
|
import me.topchetoeu.jscript.core.scope.ValueVariable;
|
||||||
|
|
||||||
|
public class CodeFunction extends FunctionValue {
|
||||||
|
public final FunctionBody body;
|
||||||
|
public final ValueVariable[] captures;
|
||||||
|
public Environment environment;
|
||||||
|
|
||||||
|
// public Location loc() {
|
||||||
|
// for (var instr : body.instructions) {
|
||||||
|
// 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 Frame(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.environment = environment;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.engine.frame;
|
package me.topchetoeu.jscript.core.values;
|
||||||
|
|
||||||
public enum ConvertHint {
|
public enum ConvertHint {
|
||||||
TOSTRING,
|
TOSTRING,
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user