Compare commits
182 Commits
v0.4.0-alp
...
0.9.26-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
8acbc003c4
|
|||
|
fda33112a7
|
|||
|
67b2413d7c
|
|||
|
3a05416510
|
|||
|
c291328cc3
|
|||
|
7cb267b0d9
|
|||
|
4e31766665
|
|||
|
b5b63c4342
|
|||
|
18f70a0d58
|
|||
|
d38b600366
|
|||
|
0ac7af2ea3
|
|||
|
5185c93663
|
|||
|
510422cab7
|
|||
|
79e1d1cfaf
|
|||
|
e0f3274a95
|
|||
| ef5d29105f | |||
|
d8ea6557df
|
|||
|
5ba858545a
|
|||
|
446ecd8f2b
|
|||
|
fbf103439a
|
|||
|
b30f94de8f
|
|||
|
47b4dd3c15
|
|||
|
0fb336373a
|
|||
|
b33325a98d
|
|||
|
ccf75d6066
|
|||
|
662dcc1ac1
|
|||
|
3e6214659b
|
|||
|
7c6622c53d
|
|||
|
70d5871091
|
|||
|
7b9bbe576b
|
|||
|
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
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* -text
|
||||
17
.github/workflows/tagged-release.yml
vendored
17
.github/workflows/tagged-release.yml
vendored
@@ -3,7 +3,7 @@ name: "tagged-release"
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
tagged-release:
|
||||
@@ -11,15 +11,22 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Clone repository
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||
with:
|
||||
branch: 'master' # fuck this political bullshitshit, took me an hour to fix this
|
||||
branch: 'master'
|
||||
owner: 'TopchetoEU'
|
||||
repository: 'java-jscript'
|
||||
- name: "Build"
|
||||
- name: Build
|
||||
run: |
|
||||
cd java-jscript; node ./build.js release ${{ github.ref }}
|
||||
cd java-jscript; gradle build
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
@@ -27,4 +34,4 @@ jobs:
|
||||
prerelease: false
|
||||
files: |
|
||||
java-jscript/LICENSE
|
||||
java-jscript/dst/*.jar
|
||||
java-jscript/build/libs/*.jar
|
||||
35
.gitignore
vendored
35
.gitignore
vendored
@@ -1,11 +1,24 @@
|
||||
.vscode
|
||||
.gradle
|
||||
.ignore
|
||||
/out
|
||||
/build
|
||||
/bin
|
||||
/dst
|
||||
/*.js
|
||||
!/build.js
|
||||
/dead-code
|
||||
/Metadata.java
|
||||
*
|
||||
|
||||
!/src
|
||||
!/src/**/*
|
||||
|
||||
!/doc
|
||||
!/doc/**/*
|
||||
|
||||
!/tests
|
||||
!/tests/**/*
|
||||
|
||||
!/.github
|
||||
!/.github/**/*
|
||||
|
||||
!/.gitignore
|
||||
!/.gitattributes
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
!/settings.gradle
|
||||
!/build.gradle
|
||||
!/gradle.properties
|
||||
!/gradle
|
||||
!/gradle/wrapper
|
||||
!/gradle/wrapper/gradle-wrapper.properties
|
||||
33
README.md
33
README.md
@@ -2,33 +2,24 @@
|
||||
|
||||
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
|
||||
|
||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
||||
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
|
||||
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||
|
||||
## Example
|
||||
|
||||
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
|
||||
The following is going to execute a simple javascript statement:
|
||||
|
||||
```java
|
||||
var engine = new PolyfillEngine(new File("."));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine.start();
|
||||
var engine = new Engine();
|
||||
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
|
||||
var env = Internals.apply(new Environment());
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
// Queue code to load internal libraries and start engine
|
||||
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
|
||||
// Run the engine on the same thread, until the event loop runs empty
|
||||
engine.run(true);
|
||||
|
||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
||||
Values.printValue(engine.context(), res);
|
||||
System.out.println();
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
}
|
||||
catch (InterruptedException _e) { return; }
|
||||
}
|
||||
catch (IOException | InterruptedException e) { return; }
|
||||
}
|
||||
// Get our result
|
||||
System.out.println(awaitable.await());
|
||||
```
|
||||
|
||||
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.9.26-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
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
1004
src/assets/debugger/protocol.json
Normal file
1004
src/assets/debugger/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
5
src/assets/metadata.json
Normal file
5
src/assets/metadata.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "${version}",
|
||||
"name": "${name}",
|
||||
"author": "TopchetoEU"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
58
src/java/me/topchetoeu/jscript/common/Buffer.java
Normal file
58
src/java/me/topchetoeu/jscript/common/Buffer.java
Normal file
@@ -0,0 +1,58 @@
|
||||
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 clear() {
|
||||
data = new byte[128];
|
||||
length = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,23 @@
|
||||
package me.topchetoeu.jscript;
|
||||
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;
|
||||
|
||||
public String toString() {
|
||||
@Override public String toString() {
|
||||
return protocol + "://" + path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
@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) {
|
||||
@Override public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
@@ -40,15 +36,22 @@ public class Filename {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Filename fromFile(File file) {
|
||||
return new Filename("file", file.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
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.runtime.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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package me.topchetoeu.jscript;
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Location implements Comparable<Location> {
|
||||
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
|
||||
public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native"));
|
||||
private int line;
|
||||
private int start;
|
||||
private Filename filename;
|
||||
@@ -12,7 +14,13 @@ public class Location implements Comparable<Location> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return filename.toString() + ":" + line + ":" + start;
|
||||
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) {
|
||||
@@ -71,4 +79,23 @@ public class Location implements Comparable<Location> {
|
||||
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,34 +1,31 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public class DataNotifier<T> implements Awaitable<T> {
|
||||
private Notifier notifier = new Notifier();
|
||||
private boolean isErr;
|
||||
private T val;
|
||||
private RuntimeException err;
|
||||
|
||||
public void error(RuntimeException t) {
|
||||
err = t;
|
||||
isErr = true;
|
||||
notifier.next();
|
||||
}
|
||||
public void error(Throwable t) {
|
||||
error(new RuntimeException(t));
|
||||
}
|
||||
public void next(T val) {
|
||||
this.val = val;
|
||||
isErr = false;
|
||||
notifier.next();
|
||||
}
|
||||
public T await() {
|
||||
notifier.await();
|
||||
|
||||
try {
|
||||
if (isErr) throw err;
|
||||
else return val;
|
||||
}
|
||||
finally {
|
||||
this.err = null;
|
||||
this.val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.events;
|
||||
|
||||
public class DataNotifier<T> {
|
||||
private Notifier notifier = new Notifier();
|
||||
private boolean isErr;
|
||||
private T val;
|
||||
private RuntimeException err;
|
||||
|
||||
public void error(RuntimeException t) {
|
||||
err = t;
|
||||
isErr = true;
|
||||
notifier.next();
|
||||
}
|
||||
public void next(T val) {
|
||||
this.val = val;
|
||||
isErr = false;
|
||||
notifier.next();
|
||||
}
|
||||
public T await() {
|
||||
notifier.await();
|
||||
|
||||
try {
|
||||
if (isErr) throw err;
|
||||
else return val;
|
||||
}
|
||||
finally {
|
||||
this.err = null;
|
||||
this.val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import me.topchetoeu.jscript.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); }
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.events;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.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,243 +1,243 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.parsing.Operator;
|
||||
import me.topchetoeu.jscript.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.parsing.Token;
|
||||
|
||||
public class JSON {
|
||||
public static 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 ObjectValue) {
|
||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
||||
prev.add(val);
|
||||
|
||||
var res = new JSONMap();
|
||||
|
||||
for (var el : ((ObjectValue)val).keys(false)) {
|
||||
var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, 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 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 == null) return null;
|
||||
return null;
|
||||
}
|
||||
public static JSONElement fromJs(Context ctx, Object val) {
|
||||
return fromJs(ctx, val, new HashSet<>());
|
||||
}
|
||||
|
||||
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||
return Parsing.parseIdentifier(tokens, i);
|
||||
}
|
||||
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
|
||||
var res = Parsing.parseString(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
|
||||
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
|
||||
if (minus) i++;
|
||||
|
||||
var res = Parsing.parseNumber(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
|
||||
var id = parseIdentifier(tokens, i);
|
||||
|
||||
if (!id.isSuccess()) return ParseRes.failed();
|
||||
else if (id.result.equals("true")) return ParseRes.res(true, 1);
|
||||
else if (id.result.equals("false")) return ParseRes.res(false, 1);
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
|
||||
return ParseRes.any(
|
||||
parseString(filename, tokens, i),
|
||||
parseNumber(filename, tokens, i),
|
||||
parseBool(filename, tokens, i),
|
||||
parseMap(filename, tokens, i),
|
||||
parseList(filename, tokens, i)
|
||||
);
|
||||
}
|
||||
|
||||
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONMap();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var name = ParseRes.any(
|
||||
parseIdentifier(tokens, i + n),
|
||||
parseString(filename, tokens, i + n),
|
||||
parseNumber(filename, tokens, i + n)
|
||||
);
|
||||
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
|
||||
else n += name.n;
|
||||
|
||||
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
|
||||
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
|
||||
}
|
||||
n++;
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.put(name.result.toString(), JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONList();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.add(JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static JSONElement parse(Filename filename, String raw) {
|
||||
if (filename == null) filename = new Filename("jscript", "json");
|
||||
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
|
||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||
else return JSONElement.of(res.result);
|
||||
}
|
||||
|
||||
public static String stringify(JSONElement el) {
|
||||
if (el.isNumber()) return Double.toString(el.number());
|
||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||
if (el.isNull()) return "null";
|
||||
if (el.isString()) {
|
||||
var res = new StringBuilder("\"");
|
||||
var alphabet = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
for (var c : el.string().toCharArray()) {
|
||||
if (c < 32 || c >= 127) {
|
||||
res
|
||||
.append("\\u")
|
||||
.append(alphabet[(c >> 12) & 0xF])
|
||||
.append(alphabet[(c >> 8) & 0xF])
|
||||
.append(alphabet[(c >> 4) & 0xF])
|
||||
.append(alphabet[(c >> 0) & 0xF]);
|
||||
}
|
||||
else if (c == '\\')
|
||||
res.append("\\\\");
|
||||
else if (c == '"')
|
||||
res.append("\\\"");
|
||||
else res.append(c);
|
||||
}
|
||||
|
||||
return res.append('"').toString();
|
||||
}
|
||||
if (el.isList()) {
|
||||
var res = new StringBuilder().append("[");
|
||||
for (int i = 0; i < el.list().size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(el.list().get(i)));
|
||||
}
|
||||
res.append("]");
|
||||
return res.toString();
|
||||
}
|
||||
if (el.isMap()) {
|
||||
var res = new StringBuilder().append("{");
|
||||
var entries = el.map().entrySet().stream().collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
|
||||
res.append(":");
|
||||
res.append(stringify(entries.get(i).getValue()));
|
||||
}
|
||||
res.append("}");
|
||||
return res.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static String stringify(JSONMap map) {
|
||||
return stringify(JSONElement.of(map));
|
||||
}
|
||||
public static String stringify(JSONList list) {
|
||||
return stringify(JSONElement.of(list));
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.compilation.parsing.Operator;
|
||||
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.compilation.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.compilation.parsing.Token;
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
|
||||
public class JSON {
|
||||
public static Object toJs(JSONElement val) {
|
||||
if (val.isBoolean()) return val.bool();
|
||||
if (val.isString()) return val.string();
|
||||
if (val.isNumber()) return val.number();
|
||||
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
|
||||
if (val.isMap()) {
|
||||
var res = new ObjectValue();
|
||||
for (var el : val.map().entrySet()) {
|
||||
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
if (val.isNull()) return Values.NULL;
|
||||
return null;
|
||||
}
|
||||
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
|
||||
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
|
||||
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
|
||||
if (val instanceof String) return JSONElement.string((String)val);
|
||||
if (val == Values.NULL) return JSONElement.NULL;
|
||||
if (val instanceof ArrayValue) {
|
||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
||||
prev.add(val);
|
||||
|
||||
var res = new JSONList();
|
||||
|
||||
for (var el : ((ArrayValue)val).toArray()) {
|
||||
var jsonEl = fromJs(ctx, el, prev);
|
||||
if (jsonEl == null) jsonEl = JSONElement.NULL;
|
||||
res.add(jsonEl);
|
||||
}
|
||||
|
||||
prev.remove(val);
|
||||
return JSONElement.of(res);
|
||||
}
|
||||
if (val instanceof ObjectValue) {
|
||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
||||
prev.add(val);
|
||||
|
||||
var res = new JSONMap();
|
||||
|
||||
for (var el : Values.getMembers(ctx, val, false, false)) {
|
||||
var jsonEl = fromJs(ctx, Values.getMember(ctx, val, el), prev);
|
||||
if (jsonEl == null) continue;
|
||||
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
|
||||
}
|
||||
|
||||
prev.remove(val);
|
||||
return JSONElement.of(res);
|
||||
}
|
||||
if (val == null) return null;
|
||||
return null;
|
||||
}
|
||||
public static JSONElement fromJs(Context ctx, Object val) {
|
||||
return fromJs(ctx, val, new HashSet<>());
|
||||
}
|
||||
|
||||
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||
return Parsing.parseIdentifier(tokens, i);
|
||||
}
|
||||
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
|
||||
var res = Parsing.parseString(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
|
||||
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
|
||||
if (minus) i++;
|
||||
|
||||
var res = Parsing.parseNumber(filename, tokens, i);
|
||||
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
|
||||
else return res.transform();
|
||||
}
|
||||
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
|
||||
var id = parseIdentifier(tokens, i);
|
||||
|
||||
if (!id.isSuccess()) return ParseRes.failed();
|
||||
else if (id.result.equals("true")) return ParseRes.res(true, 1);
|
||||
else if (id.result.equals("false")) return ParseRes.res(false, 1);
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
|
||||
return ParseRes.any(
|
||||
parseString(filename, tokens, i),
|
||||
parseNumber(filename, tokens, i),
|
||||
parseBool(filename, tokens, i),
|
||||
parseMap(filename, tokens, i),
|
||||
parseList(filename, tokens, i)
|
||||
);
|
||||
}
|
||||
|
||||
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONMap();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var name = ParseRes.any(
|
||||
parseIdentifier(tokens, i + n),
|
||||
parseString(filename, tokens, i + n),
|
||||
parseNumber(filename, tokens, i + n)
|
||||
);
|
||||
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
|
||||
else n += name.n;
|
||||
|
||||
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
|
||||
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
|
||||
}
|
||||
n++;
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.put(name.result.toString(), JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
|
||||
int n = 0;
|
||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
|
||||
|
||||
var values = new JSONList();
|
||||
|
||||
while (true) {
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var res = parseValue(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
||||
else n += res.n;
|
||||
|
||||
values.add(JSONElement.of(res.result));
|
||||
|
||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
public static JSONElement parse(Filename filename, String raw) {
|
||||
if (filename == null) filename = new Filename("jscript", "json");
|
||||
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
|
||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||
else return JSONElement.of(res.result);
|
||||
}
|
||||
|
||||
public static String stringify(JSONElement el) {
|
||||
if (el.isNumber()) return Double.toString(el.number());
|
||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||
if (el.isNull()) return "null";
|
||||
if (el.isString()) {
|
||||
var res = new StringBuilder("\"");
|
||||
var alphabet = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
for (var c : el.string().toCharArray()) {
|
||||
if (c < 32 || c >= 127) {
|
||||
res
|
||||
.append("\\u")
|
||||
.append(alphabet[(c >> 12) & 0xF])
|
||||
.append(alphabet[(c >> 8) & 0xF])
|
||||
.append(alphabet[(c >> 4) & 0xF])
|
||||
.append(alphabet[(c >> 0) & 0xF]);
|
||||
}
|
||||
else if (c == '\\')
|
||||
res.append("\\\\");
|
||||
else if (c == '"')
|
||||
res.append("\\\"");
|
||||
else res.append(c);
|
||||
}
|
||||
|
||||
return res.append('"').toString();
|
||||
}
|
||||
if (el.isList()) {
|
||||
var res = new StringBuilder().append("[");
|
||||
for (int i = 0; i < el.list().size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(el.list().get(i)));
|
||||
}
|
||||
res.append("]");
|
||||
return res.toString();
|
||||
}
|
||||
if (el.isMap()) {
|
||||
var res = new StringBuilder().append("{");
|
||||
var entries = el.map().entrySet().stream().collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (i != 0) res.append(",");
|
||||
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
|
||||
res.append(":");
|
||||
res.append(stringify(entries.get(i).getValue()));
|
||||
}
|
||||
res.append("}");
|
||||
return res.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static String stringify(JSONMap map) {
|
||||
return stringify(JSONElement.of(map));
|
||||
}
|
||||
public static String stringify(JSONList list) {
|
||||
return stringify(JSONElement.of(list));
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,88 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
public class JSONElement {
|
||||
public static enum Type {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
NULL,
|
||||
LIST,
|
||||
MAP,
|
||||
}
|
||||
|
||||
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
|
||||
|
||||
public static JSONElement map(JSONMap val) {
|
||||
return new JSONElement(Type.MAP, val);
|
||||
}
|
||||
public static JSONElement list(JSONList val) {
|
||||
return new JSONElement(Type.LIST, val);
|
||||
}
|
||||
public static JSONElement string(String val) {
|
||||
return new JSONElement(Type.STRING, val);
|
||||
}
|
||||
public static JSONElement number(double val) {
|
||||
return new JSONElement(Type.NUMBER, val);
|
||||
}
|
||||
public static JSONElement bool(boolean val) {
|
||||
return new JSONElement(Type.BOOLEAN, val);
|
||||
}
|
||||
|
||||
public static JSONElement of(Object val) {
|
||||
if (val instanceof JSONMap) return map((JSONMap)val);
|
||||
else if (val instanceof JSONList) return list((JSONList)val);
|
||||
else if (val instanceof String) return string((String)val);
|
||||
else if (val instanceof Boolean) return bool((Boolean)val);
|
||||
else if (val instanceof Number) return number(((Number)val).doubleValue());
|
||||
else if (val == null) return NULL;
|
||||
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
private final Object value;
|
||||
|
||||
public boolean isMap() { return type == Type.MAP; }
|
||||
public boolean isList() { return type == Type.LIST; }
|
||||
public boolean isString() { return type == Type.STRING; }
|
||||
public boolean isNumber() { return type == Type.NUMBER; }
|
||||
public boolean isBoolean() { return type == Type.BOOLEAN; }
|
||||
public boolean isNull() { return type == Type.NULL; }
|
||||
|
||||
public JSONMap map() {
|
||||
if (!isMap()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONMap)value;
|
||||
}
|
||||
public JSONList list() {
|
||||
if (!isList()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONList)value;
|
||||
}
|
||||
public String string() {
|
||||
if (!isString()) throw new IllegalStateException("Element is not a string.");
|
||||
return (String)value;
|
||||
}
|
||||
public double number() {
|
||||
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
|
||||
return (double)value;
|
||||
}
|
||||
public boolean bool() {
|
||||
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
|
||||
return (boolean)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isMap()) return "{...}";
|
||||
if (isList()) return "[...]";
|
||||
if (isString()) return (String)value;
|
||||
if (isString()) return (String)value;
|
||||
if (isNumber()) return (double)value + "";
|
||||
if (isBoolean()) return (boolean)value + "";
|
||||
if (isNull()) return "null";
|
||||
return "";
|
||||
}
|
||||
|
||||
private JSONElement(Type type, Object val) {
|
||||
this.type = type;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
public class JSONElement {
|
||||
public static enum Type {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
NULL,
|
||||
LIST,
|
||||
MAP,
|
||||
}
|
||||
|
||||
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
|
||||
|
||||
public static JSONElement map(JSONMap val) {
|
||||
return new JSONElement(Type.MAP, val);
|
||||
}
|
||||
public static JSONElement list(JSONList val) {
|
||||
return new JSONElement(Type.LIST, val);
|
||||
}
|
||||
public static JSONElement string(String val) {
|
||||
return new JSONElement(Type.STRING, val);
|
||||
}
|
||||
public static JSONElement number(double val) {
|
||||
return new JSONElement(Type.NUMBER, val);
|
||||
}
|
||||
public static JSONElement bool(boolean val) {
|
||||
return new JSONElement(Type.BOOLEAN, val);
|
||||
}
|
||||
|
||||
public static JSONElement of(Object val) {
|
||||
if (val instanceof JSONMap) return map((JSONMap)val);
|
||||
else if (val instanceof JSONList) return list((JSONList)val);
|
||||
else if (val instanceof String) return string((String)val);
|
||||
else if (val instanceof Boolean) return bool((Boolean)val);
|
||||
else if (val instanceof Number) return number(((Number)val).doubleValue());
|
||||
else if (val == null) return NULL;
|
||||
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
private final Object value;
|
||||
|
||||
public boolean isMap() { return type == Type.MAP; }
|
||||
public boolean isList() { return type == Type.LIST; }
|
||||
public boolean isString() { return type == Type.STRING; }
|
||||
public boolean isNumber() { return type == Type.NUMBER; }
|
||||
public boolean isBoolean() { return type == Type.BOOLEAN; }
|
||||
public boolean isNull() { return type == Type.NULL; }
|
||||
|
||||
public JSONMap map() {
|
||||
if (!isMap()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONMap)value;
|
||||
}
|
||||
public JSONList list() {
|
||||
if (!isList()) throw new IllegalStateException("Element is not a map.");
|
||||
return (JSONList)value;
|
||||
}
|
||||
public String string() {
|
||||
if (!isString()) throw new IllegalStateException("Element is not a string.");
|
||||
return (String)value;
|
||||
}
|
||||
public double number() {
|
||||
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
|
||||
return (double)value;
|
||||
}
|
||||
public boolean bool() {
|
||||
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
|
||||
return (boolean)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isMap()) return "{...}";
|
||||
if (isList()) return "[...]";
|
||||
if (isString()) return (String)value;
|
||||
if (isString()) return (String)value;
|
||||
if (isNumber()) return (double)value + "";
|
||||
if (isBoolean()) return (boolean)value + "";
|
||||
if (isNull()) return "null";
|
||||
return "";
|
||||
}
|
||||
|
||||
private JSONElement(Type type, Object val) {
|
||||
this.type = type;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONList extends ArrayList<JSONElement> {
|
||||
public JSONList() {}
|
||||
public JSONList(JSONElement ...els) {
|
||||
super(List.of(els));
|
||||
}
|
||||
public JSONList(Collection<JSONElement> els) {
|
||||
super(els);
|
||||
}
|
||||
|
||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
|
||||
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONList extends ArrayList<JSONElement> {
|
||||
public JSONList() {}
|
||||
public JSONList(JSONElement ...els) {
|
||||
super(List.of(els));
|
||||
}
|
||||
public JSONList(Collection<JSONElement> els) {
|
||||
super(els);
|
||||
}
|
||||
|
||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
|
||||
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
|
||||
|
||||
}
|
||||
@@ -1,150 +1,150 @@
|
||||
package me.topchetoeu.jscript.json;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class JSONMap implements Map<String, JSONElement> {
|
||||
private Map<String, JSONElement> elements = new HashMap<>();
|
||||
|
||||
public JSONElement get(String path) {
|
||||
var curr = this;
|
||||
var segs = path.split("\\.");
|
||||
var i = 0;
|
||||
|
||||
while (true) {
|
||||
var tmp = curr.elements.get(segs[i++]);
|
||||
if (i == segs.length) return tmp;
|
||||
if (!tmp.isMap()) return null;
|
||||
curr = tmp.map();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMap(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isMap();
|
||||
}
|
||||
public boolean isList(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isList();
|
||||
}
|
||||
public boolean isString(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isString();
|
||||
}
|
||||
public boolean isNumber(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNumber();
|
||||
}
|
||||
public boolean isBoolean(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isBoolean();
|
||||
}
|
||||
public boolean isNull(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNull();
|
||||
}
|
||||
public boolean contains(String path) {
|
||||
return get(path) != null;
|
||||
}
|
||||
|
||||
public JSONMap map(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.map();
|
||||
}
|
||||
public JSONMap map(String path, JSONMap defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isMap()) return el.map();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONList list(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.list();
|
||||
}
|
||||
public JSONList list(String path, JSONList defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isList()) return el.list();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public String string(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.string();
|
||||
}
|
||||
public String string(String path, String defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isString()) return el.string();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public double number(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.number();
|
||||
}
|
||||
public double number(String path, double defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isNumber()) return el.number();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public boolean bool(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.bool();
|
||||
}
|
||||
public boolean bool(String path, boolean defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isBoolean()) return el.bool();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
|
||||
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
|
||||
@Override
|
||||
public int size() { return elements.size(); }
|
||||
@Override
|
||||
public boolean isEmpty() { return elements.isEmpty(); }
|
||||
@Override
|
||||
public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||
@Override
|
||||
public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||
@Override
|
||||
public JSONElement get(Object key) { return elements.get(key); }
|
||||
@Override
|
||||
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||
@Override
|
||||
public JSONElement remove(Object key) { return elements.remove(key); }
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||
|
||||
@Override
|
||||
public void clear() { elements.clear(); }
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() { return elements.keySet(); }
|
||||
@Override
|
||||
public Collection<JSONElement> values() { return elements.values(); }
|
||||
@Override
|
||||
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||
|
||||
public JSONMap() { }
|
||||
public JSONMap(Map<String, JSONElement> els) {
|
||||
this.elements = new HashMap<>(els);
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class JSONMap implements Map<String, JSONElement> {
|
||||
private Map<String, JSONElement> elements = new HashMap<>();
|
||||
|
||||
public JSONElement get(String path) {
|
||||
var curr = this;
|
||||
var segs = path.split("\\.");
|
||||
var i = 0;
|
||||
|
||||
while (true) {
|
||||
var tmp = curr.elements.get(segs[i++]);
|
||||
if (i == segs.length) return tmp;
|
||||
if (!tmp.isMap()) return null;
|
||||
curr = tmp.map();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMap(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isMap();
|
||||
}
|
||||
public boolean isList(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isList();
|
||||
}
|
||||
public boolean isString(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isString();
|
||||
}
|
||||
public boolean isNumber(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNumber();
|
||||
}
|
||||
public boolean isBoolean(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isBoolean();
|
||||
}
|
||||
public boolean isNull(String path) {
|
||||
var el = get(path);
|
||||
return el != null && el.isNull();
|
||||
}
|
||||
public boolean contains(String path) {
|
||||
return get(path) != null;
|
||||
}
|
||||
|
||||
public JSONMap map(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.map();
|
||||
}
|
||||
public JSONMap map(String path, JSONMap defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isMap()) return el.map();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONList list(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.list();
|
||||
}
|
||||
public JSONList list(String path, JSONList defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isList()) return el.list();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public String string(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.string();
|
||||
}
|
||||
public String string(String path, String defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isString()) return el.string();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public double number(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.number();
|
||||
}
|
||||
public double number(String path, double defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isNumber()) return el.number();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public boolean bool(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
return el.bool();
|
||||
}
|
||||
public boolean bool(String path, boolean defaultVal) {
|
||||
var el = get(path);
|
||||
if (el == null) return defaultVal;
|
||||
if (el.isBoolean()) return el.bool();
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
|
||||
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
|
||||
@Override
|
||||
public int size() { return elements.size(); }
|
||||
@Override
|
||||
public boolean isEmpty() { return elements.isEmpty(); }
|
||||
@Override
|
||||
public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||
@Override
|
||||
public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||
@Override
|
||||
public JSONElement get(Object key) { return elements.get(key); }
|
||||
@Override
|
||||
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||
@Override
|
||||
public JSONElement remove(Object key) { return elements.remove(key); }
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||
|
||||
@Override
|
||||
public void clear() { elements.clear(); }
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() { return elements.keySet(); }
|
||||
@Override
|
||||
public Collection<JSONElement> values() { return elements.values(); }
|
||||
@Override
|
||||
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||
|
||||
public JSONMap() { }
|
||||
public JSONMap(Map<String, JSONElement> els) {
|
||||
this.elements = new HashMap<>(els);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.common.mapping;
|
||||
|
||||
public enum ConvertType {
|
||||
Exact,
|
||||
Lower,
|
||||
Upper,
|
||||
Both,
|
||||
}
|
||||
192
src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java
Normal file
192
src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java
Normal file
@@ -0,0 +1,192 @@
|
||||
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.compilation.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()) {
|
||||
var val = correctBreakpoint(new Location(line, column, candidate.getKey()));
|
||||
if (val == null) continue;
|
||||
res.add(val);
|
||||
}
|
||||
|
||||
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,7 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
|
||||
public abstract class AssignableStatement extends Statement {
|
||||
public abstract Statement toAssign(Statement val, Operation operation);
|
||||
@@ -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.compilation.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.runtime.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;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,69 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ForInStatement extends Statement {
|
||||
public final String varName;
|
||||
public final boolean isDeclaration;
|
||||
public final Statement varValue, object, body;
|
||||
public final String label;
|
||||
public final Location varLocation;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
if (isDeclaration) globScope.define(varName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var key = scope.getKey(varName);
|
||||
|
||||
int first = target.size();
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
varValue.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compileWithDebug(target, scope, true);
|
||||
target.add(Instruction.keys(true));
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadValue(null));
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
|
||||
target.add(Instruction.loadMember("value").locate(varLocation));
|
||||
target.setDebug();
|
||||
target.add(Instruction.storeVar(key));
|
||||
|
||||
body.compileWithDebug(target, scope, false);
|
||||
|
||||
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.loadValue(null));
|
||||
target.get(first).locate(loc());
|
||||
target.setDebug(first);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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,73 @@
|
||||
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 ForOfStatement extends Statement {
|
||||
public final String varName;
|
||||
public final boolean isDeclaration;
|
||||
public final Statement iterable, 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));
|
||||
|
||||
iterable.compile(target, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadVar("Symbol"));
|
||||
target.add(Instruction.pushValue("iterator"));
|
||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
||||
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.pushValue("next"));
|
||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
||||
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.pushValue("done"));
|
||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
||||
int mid = target.temp();
|
||||
|
||||
target.add(Instruction.pushValue("value"));
|
||||
target.add(Instruction.loadMember()).setLocation(varLocation);
|
||||
target.add(Instruction.storeVar(key)).setLocationAndDebug(iterable.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.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public ForOfStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement object, Statement body) {
|
||||
super(loc);
|
||||
this.varLocation = varLocation;
|
||||
this.label = label;
|
||||
this.isDeclaration = isDecl;
|
||||
this.varName = varName;
|
||||
this.iterable = 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,113 +1,113 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
|
||||
public enum Operator {
|
||||
MULTIPLY("*", Operation.MULTIPLY, 13),
|
||||
DIVIDE("/", Operation.DIVIDE, 12),
|
||||
MODULO("%", Operation.MODULO, 12),
|
||||
SUBTRACT("-", Operation.SUBTRACT, 11),
|
||||
ADD("+", Operation.ADD, 11),
|
||||
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
|
||||
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
|
||||
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
|
||||
GREATER(">", Operation.GREATER, 9),
|
||||
LESS("<", Operation.LESS, 9),
|
||||
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
|
||||
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
|
||||
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
|
||||
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
|
||||
EQUALS("==", Operation.LOOSE_EQUALS, 8),
|
||||
LOOSE_EQUALS("===", Operation.EQUALS, 8),
|
||||
AND("&", Operation.AND, 7),
|
||||
XOR("^", Operation.XOR, 6),
|
||||
OR("|", Operation.OR, 5),
|
||||
LAZY_AND("&&", 4),
|
||||
LAZY_OR("||", 3),
|
||||
ASSIGN_SHIFT_LEFT("<<=", 2, true),
|
||||
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
|
||||
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
|
||||
ASSIGN_AND("&=", 2, true),
|
||||
ASSIGN_OR("|=", 2, true),
|
||||
ASSIGN_XOR("^=", 2, true),
|
||||
ASSIGN_MODULO("%=", 2, true),
|
||||
ASSIGN_DIVIDE("/=", 2, true),
|
||||
ASSIGN_MULTIPLY("*=", 2, true),
|
||||
ASSIGN_SUBTRACT("-=", 2, true),
|
||||
ASSIGN_ADD("+=", 2, true),
|
||||
ASSIGN("=", 2, true),
|
||||
SEMICOLON(";"),
|
||||
COLON(":"),
|
||||
PAREN_OPEN("("),
|
||||
PAREN_CLOSE(")"),
|
||||
BRACKET_OPEN("["),
|
||||
BRACKET_CLOSE("]"),
|
||||
BRACE_OPEN("{"),
|
||||
BRACE_CLOSE("}"),
|
||||
DOT("."),
|
||||
COMMA(","),
|
||||
NOT("!"),
|
||||
QUESTION("?"),
|
||||
INVERSE("~"),
|
||||
INCREASE("++"),
|
||||
DECREASE("--");
|
||||
|
||||
public final String value;
|
||||
public final Operation operation;
|
||||
public final int precedence;
|
||||
public final boolean reverse;
|
||||
private static final Map<String, Operator> ops = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (var el : Operator.values()) {
|
||||
ops.put(el.value, el);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssign() { return precedence == 2; }
|
||||
|
||||
public static Operator parse(String val) {
|
||||
return ops.get(val);
|
||||
}
|
||||
|
||||
private Operator() {
|
||||
this.value = null;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence, boolean reverse) {
|
||||
this. value = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
|
||||
private Operator(String value, Operation funcName, int precedence) {
|
||||
this. value = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
||||
this.value = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
|
||||
public enum Operator {
|
||||
MULTIPLY("*", Operation.MULTIPLY, 13),
|
||||
DIVIDE("/", Operation.DIVIDE, 12),
|
||||
MODULO("%", Operation.MODULO, 12),
|
||||
SUBTRACT("-", Operation.SUBTRACT, 11),
|
||||
ADD("+", Operation.ADD, 11),
|
||||
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
|
||||
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
|
||||
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
|
||||
GREATER(">", Operation.GREATER, 9),
|
||||
LESS("<", Operation.LESS, 9),
|
||||
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
|
||||
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
|
||||
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
|
||||
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
|
||||
EQUALS("==", Operation.LOOSE_EQUALS, 8),
|
||||
LOOSE_EQUALS("===", Operation.EQUALS, 8),
|
||||
AND("&", Operation.AND, 7),
|
||||
XOR("^", Operation.XOR, 6),
|
||||
OR("|", Operation.OR, 5),
|
||||
LAZY_AND("&&", 4),
|
||||
LAZY_OR("||", 3),
|
||||
ASSIGN_SHIFT_LEFT("<<=", 2, true),
|
||||
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
|
||||
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
|
||||
ASSIGN_AND("&=", 2, true),
|
||||
ASSIGN_OR("|=", 2, true),
|
||||
ASSIGN_XOR("^=", 2, true),
|
||||
ASSIGN_MODULO("%=", 2, true),
|
||||
ASSIGN_DIVIDE("/=", 2, true),
|
||||
ASSIGN_MULTIPLY("*=", 2, true),
|
||||
ASSIGN_SUBTRACT("-=", 2, true),
|
||||
ASSIGN_ADD("+=", 2, true),
|
||||
ASSIGN("=", 2, true),
|
||||
SEMICOLON(";"),
|
||||
COLON(":"),
|
||||
PAREN_OPEN("("),
|
||||
PAREN_CLOSE(")"),
|
||||
BRACKET_OPEN("["),
|
||||
BRACKET_CLOSE("]"),
|
||||
BRACE_OPEN("{"),
|
||||
BRACE_CLOSE("}"),
|
||||
DOT("."),
|
||||
COMMA(","),
|
||||
NOT("!"),
|
||||
QUESTION("?"),
|
||||
INVERSE("~"),
|
||||
INCREASE("++"),
|
||||
DECREASE("--");
|
||||
|
||||
public final String readable;
|
||||
public final Operation operation;
|
||||
public final int precedence;
|
||||
public final boolean reverse;
|
||||
private static final Map<String, Operator> ops = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (var el : Operator.values()) {
|
||||
ops.put(el.readable, el);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssign() { return precedence == 2; }
|
||||
|
||||
public static Operator parse(String val) {
|
||||
return ops.get(val);
|
||||
}
|
||||
|
||||
private Operator() {
|
||||
this.readable = null;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value) {
|
||||
this.readable = value;
|
||||
this.operation = null;
|
||||
this.precedence = -1;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence) {
|
||||
this.readable = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, int precedence, boolean reverse) {
|
||||
this.readable = value;
|
||||
this.operation = null;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
|
||||
private Operator(String value, Operation funcName, int precedence) {
|
||||
this.readable = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = false;
|
||||
}
|
||||
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
||||
this.readable = value;
|
||||
this.operation = funcName;
|
||||
this.precedence = precedence;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +1,100 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.parsing.Parsing.Parser;
|
||||
|
||||
public class ParseRes<T> {
|
||||
public static enum State {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
ERROR;
|
||||
|
||||
public boolean isSuccess() { return this == SUCCESS; }
|
||||
public boolean isFailed() { return this == FAILED; }
|
||||
public boolean isError() { return this == ERROR; }
|
||||
}
|
||||
|
||||
public final ParseRes.State state;
|
||||
public final String error;
|
||||
public final T result;
|
||||
public final int n;
|
||||
|
||||
private ParseRes(ParseRes.State state, String error, T result, int readN) {
|
||||
this.result = result;
|
||||
this.n = readN;
|
||||
this.state = state;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public ParseRes<T> setN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, i);
|
||||
}
|
||||
public ParseRes<T> addN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, this.n + i);
|
||||
}
|
||||
public <T2> ParseRes<T2> transform() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
||||
return new ParseRes<>(state, error, null, 0);
|
||||
}
|
||||
public TestRes toTest() {
|
||||
if (isSuccess()) return TestRes.res(n);
|
||||
else if (isError()) return TestRes.error(null, error);
|
||||
else return TestRes.failed();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return state.isSuccess(); }
|
||||
public boolean isFailed() { return state.isFailed(); }
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static <T> ParseRes<T> failed() {
|
||||
return new ParseRes<T>(State.FAILED, null, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
return new ParseRes<>(State.ERROR, other.error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> res(T val, int i) {
|
||||
return new ParseRes<>(State.SUCCESS, null, val, i);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
|
||||
ParseRes<? extends T> best = null;
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
if (parser.isSuccess()) {
|
||||
if (best == null || best.n < parser.n) best = parser;
|
||||
}
|
||||
else if (parser.isError() && error.isFailed()) error = parser.transform();
|
||||
}
|
||||
|
||||
if (best != null) return best;
|
||||
else return error;
|
||||
}
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
var res = parser.parse(null, tokens, 0);
|
||||
if (res.isSuccess()) return res;
|
||||
else if (res.isError() && error.isFailed()) error = res.transform();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser;
|
||||
|
||||
public class ParseRes<T> {
|
||||
public static enum State {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
ERROR;
|
||||
|
||||
public boolean isSuccess() { return this == SUCCESS; }
|
||||
public boolean isFailed() { return this == FAILED; }
|
||||
public boolean isError() { return this == ERROR; }
|
||||
}
|
||||
|
||||
public final ParseRes.State state;
|
||||
public final String error;
|
||||
public final T result;
|
||||
public final int n;
|
||||
|
||||
private ParseRes(ParseRes.State state, String error, T result, int readN) {
|
||||
this.result = result;
|
||||
this.n = readN;
|
||||
this.state = state;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public ParseRes<T> setN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, i);
|
||||
}
|
||||
public ParseRes<T> addN(int i) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, this.n + i);
|
||||
}
|
||||
public <T2> ParseRes<T2> transform() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
||||
return new ParseRes<>(state, error, null, 0);
|
||||
}
|
||||
public TestRes toTest() {
|
||||
if (isSuccess()) return TestRes.res(n);
|
||||
else if (isError()) return TestRes.error(null, error);
|
||||
else return TestRes.failed();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return state.isSuccess(); }
|
||||
public boolean isFailed() { return state.isFailed(); }
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static <T> ParseRes<T> failed() {
|
||||
return new ParseRes<T>(State.FAILED, null, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
return new ParseRes<>(State.ERROR, other.error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> res(T val, int i) {
|
||||
return new ParseRes<>(State.SUCCESS, null, val, i);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
|
||||
return any(List.of(parsers));
|
||||
}
|
||||
public static <T> ParseRes<? extends T> any(List<ParseRes<? extends T>> parsers) {
|
||||
ParseRes<? extends T> best = null;
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
if (parser.isSuccess()) {
|
||||
if (best == null || best.n < parser.n) best = parser;
|
||||
}
|
||||
else if (parser.isError() && error.isFailed()) error = parser.transform();
|
||||
}
|
||||
|
||||
if (best != null) return best;
|
||||
else return error;
|
||||
}
|
||||
@SafeVarargs
|
||||
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
|
||||
ParseRes<? extends T> error = ParseRes.failed();
|
||||
|
||||
for (var parser : parsers) {
|
||||
var res = parser.parse(null, tokens, 0);
|
||||
if (res.isSuccess()) return res;
|
||||
else if (res.isError() && error.isFailed()) error = res.transform();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
public class RawToken {
|
||||
public final String value;
|
||||
public final TokenType type;
|
||||
public final int line;
|
||||
public final int start;
|
||||
|
||||
public RawToken(String value, TokenType type, int line, int start) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
public class RawToken {
|
||||
public final String value;
|
||||
public final TokenType type;
|
||||
public final int line;
|
||||
public final int start;
|
||||
|
||||
public RawToken(String value, TokenType type, int line, int start) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,45 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.parsing.ParseRes.State;
|
||||
|
||||
public class TestRes {
|
||||
public final State state;
|
||||
public final String error;
|
||||
public final int i;
|
||||
|
||||
private TestRes(ParseRes.State state, String error, int i) {
|
||||
this.i = i;
|
||||
this.state = state;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public TestRes add(int n) {
|
||||
return new TestRes(state, null, this.i + n);
|
||||
}
|
||||
public <T> ParseRes<T> transform() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed.");
|
||||
else if (isError()) return ParseRes.error(null, error);
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return state.isSuccess(); }
|
||||
public boolean isFailed() { return state.isFailed(); }
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static TestRes failed() {
|
||||
return new TestRes(State.FAILED, null, 0);
|
||||
}
|
||||
public static TestRes error(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
return new TestRes(State.ERROR, error, 0);
|
||||
}
|
||||
public static TestRes error(Location loc, String error, TestRes other) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!other.isError()) return new TestRes(State.ERROR, error, 0);
|
||||
return new TestRes(State.ERROR, other.error, 0);
|
||||
}
|
||||
public static TestRes res(int i) {
|
||||
return new TestRes(State.SUCCESS, null, i);
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
|
||||
|
||||
public class TestRes {
|
||||
public final State state;
|
||||
public final String error;
|
||||
public final int i;
|
||||
|
||||
private TestRes(ParseRes.State state, String error, int i) {
|
||||
this.i = i;
|
||||
this.state = state;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public TestRes add(int n) {
|
||||
return new TestRes(state, null, this.i + n);
|
||||
}
|
||||
public <T> ParseRes<T> transform() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed.");
|
||||
else if (isError()) return ParseRes.error(null, error);
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return state.isSuccess(); }
|
||||
public boolean isFailed() { return state.isFailed(); }
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static TestRes failed() {
|
||||
return new TestRes(State.FAILED, null, 0);
|
||||
}
|
||||
public static TestRes error(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
return new TestRes(State.ERROR, error, 0);
|
||||
}
|
||||
public static TestRes error(Location loc, String error, TestRes other) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!other.isError()) return new TestRes(State.ERROR, error, 0);
|
||||
return new TestRes(State.ERROR, other.error, 0);
|
||||
}
|
||||
public static TestRes res(int i) {
|
||||
return new TestRes(State.SUCCESS, null, i);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,58 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
public class Token {
|
||||
public final Object value;
|
||||
public final boolean isString;
|
||||
public final boolean isRegex;
|
||||
public final int line;
|
||||
public final int start;
|
||||
|
||||
private Token(int line, int start, Object value, boolean isString, boolean isRegex) {
|
||||
this.value = value;
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
this.isString = isString;
|
||||
this.isRegex = isRegex;
|
||||
}
|
||||
private Token(int line, int start, Object value) {
|
||||
this.value = value;
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
this.isString = false;
|
||||
this.isRegex = false;
|
||||
}
|
||||
|
||||
public boolean isString() { return isString; }
|
||||
public boolean isRegex() { return isRegex; }
|
||||
public boolean isNumber() { return value instanceof Number; }
|
||||
public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; }
|
||||
public boolean isOperator() { return value instanceof Operator; }
|
||||
|
||||
public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); }
|
||||
public boolean isOperator(Operator op) { return value.equals(op); }
|
||||
|
||||
public String string() { return (String)value; }
|
||||
public String regex() { return (String)value; }
|
||||
public double number() { return (double)value; }
|
||||
public String identifier() { return (String)value; }
|
||||
public Operator operator() { return (Operator)value; }
|
||||
|
||||
public static Token regex(int line, int start, String val) {
|
||||
return new Token(line, start, val, false, true);
|
||||
}
|
||||
public static Token string(int line, int start, String val) {
|
||||
return new Token(line, start, val, true, false);
|
||||
}
|
||||
public static Token number(int line, int start, double val) {
|
||||
return new Token(line, start, val);
|
||||
}
|
||||
public static Token identifier(int line, int start, String val) {
|
||||
return new Token(line, start, val);
|
||||
}
|
||||
public static Token operator(int line, int start, Operator val) {
|
||||
return new Token(line, start, val);
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
public class Token {
|
||||
public final Object value;
|
||||
public final String rawValue;
|
||||
public final boolean isString;
|
||||
public final boolean isRegex;
|
||||
public final int line;
|
||||
public final int start;
|
||||
|
||||
private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) {
|
||||
this.value = value;
|
||||
this.rawValue = rawValue;
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
this.isString = isString;
|
||||
this.isRegex = isRegex;
|
||||
}
|
||||
private Token(int line, int start, Object value, String rawValue) {
|
||||
this.value = value;
|
||||
this.rawValue = rawValue;
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
this.isString = false;
|
||||
this.isRegex = false;
|
||||
}
|
||||
|
||||
public boolean isString() { return isString; }
|
||||
public boolean isRegex() { return isRegex; }
|
||||
public boolean isNumber() { return value instanceof Number; }
|
||||
public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; }
|
||||
public boolean isOperator() { return value instanceof Operator; }
|
||||
|
||||
public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); }
|
||||
public boolean isOperator(Operator op) { return value.equals(op); }
|
||||
|
||||
public String string() { return (String)value; }
|
||||
public String regex() { return (String)value; }
|
||||
public double number() { return (double)value; }
|
||||
public String identifier() { return (String)value; }
|
||||
public Operator operator() { return (Operator)value; }
|
||||
|
||||
public static Token regex(int line, int start, String val, String rawValue) {
|
||||
return new Token(line, start, val, rawValue, false, true);
|
||||
}
|
||||
public static Token string(int line, int start, String val, String rawValue) {
|
||||
return new Token(line, start, val, rawValue, true, false);
|
||||
}
|
||||
public static Token number(int line, int start, double val, String rawValue) {
|
||||
return new Token(line, start, val, rawValue);
|
||||
}
|
||||
public static Token identifier(int line, int start, String val) {
|
||||
return new Token(line, start, val, val);
|
||||
}
|
||||
public static Token operator(int line, int start, Operator val) {
|
||||
return new Token(line, start, val, val.readable);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package me.topchetoeu.jscript.parsing;
|
||||
|
||||
enum TokenType {
|
||||
REGEX,
|
||||
STRING,
|
||||
NUMBER,
|
||||
LITERAL,
|
||||
OPERATOR,
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
enum TokenType {
|
||||
REGEX,
|
||||
STRING,
|
||||
NUMBER,
|
||||
LITERAL,
|
||||
OPERATOR,
|
||||
}
|
||||
@@ -1,91 +1,77 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
public final GlobalScope global;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] captures() {
|
||||
return captures.toArray(String[]::new);
|
||||
}
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalScopeRecord parent() { return parent; }
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this, global);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
|
||||
public int[] getCaptures() {
|
||||
var buff = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var name : captures) {
|
||||
var index = parent.getKey(name);
|
||||
if (index instanceof Integer) buff[i++] = (int)index;
|
||||
}
|
||||
|
||||
var res = new int[i];
|
||||
System.arraycopy(buff, 0, res, 0, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object getKey(String name) {
|
||||
var capI = captures.indexOf(name);
|
||||
var locI = locals.lastIndexOf(name);
|
||||
if (locI >= 0) return locI;
|
||||
if (capI >= 0) return ~capI;
|
||||
if (parent != null) {
|
||||
var res = parent.getKey(name);
|
||||
if (res != null && res instanceof Integer) {
|
||||
captures.add(name);
|
||||
return -captures.size();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
public boolean has(Context ctx, String name) {
|
||||
return
|
||||
global.has(ctx, name) ||
|
||||
locals.contains(name) ||
|
||||
parent != null && parent.has(ctx, name);
|
||||
}
|
||||
public Object define(String name, boolean force) {
|
||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
public Object define(String name) {
|
||||
return define(name, false);
|
||||
}
|
||||
public void undefine() {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord(GlobalScope global) {
|
||||
this.parent = null;
|
||||
this.global = global;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
|
||||
this.parent = parent;
|
||||
this.global = global;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] captures() {
|
||||
return captures.toArray(String[]::new);
|
||||
}
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
|
||||
public int[] getCaptures() {
|
||||
var buff = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var name : captures) {
|
||||
var index = parent.getKey(name);
|
||||
if (index instanceof Integer) buff[i++] = (int)index;
|
||||
}
|
||||
|
||||
var res = new int[i];
|
||||
System.arraycopy(buff, 0, res, 0, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object getKey(String name) {
|
||||
var capI = captures.indexOf(name);
|
||||
var locI = locals.lastIndexOf(name);
|
||||
if (locI >= 0) return locI;
|
||||
if (capI >= 0) return ~capI;
|
||||
if (parent != null) {
|
||||
var res = parent.getKey(name);
|
||||
if (res != null && res instanceof Integer) {
|
||||
captures.add(name);
|
||||
return -captures.size();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
public Object define(String name, boolean force) {
|
||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
public Object define(String name) {
|
||||
return define(name, false);
|
||||
}
|
||||
public void undefine() {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord() {
|
||||
this.parent = null;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public ScopeRecord parent();
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
@@ -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,32 +1,31 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ChangeStatement extends Statement {
|
||||
public final AssignableStatement value;
|
||||
public final double addAmount;
|
||||
public final boolean postfix;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
else if (postfix) {
|
||||
target.add(Instruction.loadValue(addAmount));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
this.addAmount = addAmount;
|
||||
this.postfix = postfix;
|
||||
}
|
||||
}
|
||||
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 ChangeStatement extends Statement {
|
||||
public final AssignableStatement value;
|
||||
public final double addAmount;
|
||||
public final boolean postfix;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true);
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
else if (postfix) {
|
||||
target.add(Instruction.pushValue(addAmount));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
this.addAmount = addAmount;
|
||||
this.postfix = postfix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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.runtime.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;
|
||||
}
|
||||
}
|
||||
456
src/java/me/topchetoeu/jscript/lib/ArrayLib.java
Normal file
456
src/java/me/topchetoeu/jscript/lib/ArrayLib.java
Normal file
@@ -0,0 +1,456 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Array")
|
||||
public class ArrayLib {
|
||||
private static int normalizeI(int len, int i, boolean clamp) {
|
||||
if (i < 0) i += len;
|
||||
if (clamp) {
|
||||
if (i < 0) i = 0;
|
||||
if (i > len) i = len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Expose(value = "length", type = ExposeType.GETTER)
|
||||
public static int __getLength(Arguments args) {
|
||||
return args.self(ArrayValue.class).size();
|
||||
}
|
||||
@Expose(value = "length", type = ExposeType.SETTER)
|
||||
public static void __setLength(Arguments args) {
|
||||
args.self(ArrayValue.class).setSize(args.getInt(0));
|
||||
}
|
||||
|
||||
@Expose public static ObjectValue __values(Arguments args) {
|
||||
return __iterator(args);
|
||||
}
|
||||
@Expose public static ObjectValue __keys(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, () -> new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < args.self(ArrayValue.class).size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
if (!hasNext()) return null;
|
||||
return i++;
|
||||
}
|
||||
});
|
||||
}
|
||||
@Expose public static ObjectValue __entries(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, () -> new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < args.self(ArrayValue.class).size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
if (!hasNext()) return null;
|
||||
return new ArrayValue(args.ctx, i, args.self(ArrayValue.class).get(i++));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Expose(value = "@@Symbol.iterator")
|
||||
public static ObjectValue __iterator(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, args.self(ArrayValue.class));
|
||||
}
|
||||
@Expose(value = "@@Symbol.asyncIterator")
|
||||
public static ObjectValue __asyncIterator(Arguments args) {
|
||||
return Values.toJSAsyncIterator(args.ctx, args.self(ArrayValue.class).iterator());
|
||||
}
|
||||
|
||||
@Expose public static ArrayValue __concat(Arguments args) {
|
||||
// TODO: Fully implement with non-array spreadable objects
|
||||
var arrs = args.slice(-1);
|
||||
var size = 0;
|
||||
|
||||
for (int i = 0; i < arrs.n(); i++) {
|
||||
if (arrs.get(i) instanceof ArrayValue) size += arrs.convert(i, ArrayValue.class).size();
|
||||
else i++;
|
||||
}
|
||||
|
||||
var res = new ArrayValue(size);
|
||||
|
||||
for (int i = 0, j = 0; i < arrs.n(); i++) {
|
||||
if (arrs.get(i) instanceof ArrayValue) {
|
||||
var arrEl = arrs.convert(i, ArrayValue.class);
|
||||
int n = arrEl.size();
|
||||
arrEl.copyTo(res, 0, j, n);
|
||||
j += n;
|
||||
}
|
||||
else {
|
||||
res.set(args.ctx, j++, arrs.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose public static ArrayValue __sort(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var cmp = args.convert(0, FunctionValue.class);
|
||||
|
||||
var defaultCmp = new NativeFunction("", _args -> {
|
||||
return _args.getString(0).compareTo(_args.getString(1));
|
||||
});
|
||||
|
||||
arr.sort((a, b) -> {
|
||||
var res = Values.toNumber(args.ctx, (cmp == null ? defaultCmp : cmp).call(args.ctx, null, a, b));
|
||||
if (res < 0) return -1;
|
||||
if (res > 0) return 1;
|
||||
return 0;
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
@Expose public static ArrayValue __fill(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var val = args.get(0);
|
||||
var start = normalizeI(arr.size(), args.getInt(1, 0), true);
|
||||
var end = normalizeI(arr.size(), args.getInt(2, arr.size()), true);
|
||||
|
||||
for (; start < end; start++) arr.set(args.ctx, start, val);
|
||||
|
||||
return arr;
|
||||
}
|
||||
@Expose public static boolean __every(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && !Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, arr
|
||||
))) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@Expose public static boolean __some(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, arr
|
||||
))) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@Expose public static ArrayValue __filter(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var res = new ArrayValue(arr.size());
|
||||
|
||||
for (int i = 0, j = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, arr
|
||||
))) res.set(args.ctx, j++, arr.get(i));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose public static ArrayValue __map(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var res = new ArrayValue(arr.size());
|
||||
res.setSize(arr.size());
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i)) res.set(args.ctx, i, Values.call(args.ctx, args.get(0), args.get(1), arr.get(i), i, arr));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@Expose public static void __forEach(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var thisArg = args.get(1);
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i)) func.call(args.ctx, thisArg, arr.get(i), i, arr);
|
||||
}
|
||||
}
|
||||
|
||||
@Expose public static Object __reduce(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var res = args.get(1);
|
||||
var i = 0;
|
||||
|
||||
if (args.n() < 2) {
|
||||
for (; i < arr.size(); i++) {
|
||||
if (arr.has(i)){
|
||||
res = arr.get(i++);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < arr.size(); i++) {
|
||||
if (arr.has(i)) {
|
||||
res = func.call(args.ctx, null, res, arr.get(i), i, arr);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose public static Object __reduceRight(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var res = args.get(1);
|
||||
var i = arr.size();
|
||||
|
||||
if (args.n() < 2) {
|
||||
while (!arr.has(i--) && i >= 0) {
|
||||
res = arr.get(i);
|
||||
}
|
||||
}
|
||||
else i--;
|
||||
|
||||
for (; i >= 0; i--) {
|
||||
if (arr.has(i)) {
|
||||
res = func.call(args.ctx, null, res, arr.get(i), i, arr);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose public static ArrayValue __flat(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var depth = args.getInt(0, 1);
|
||||
var res = new ArrayValue(arr.size());
|
||||
var stack = new Stack<Object>();
|
||||
var depths = new Stack<Integer>();
|
||||
|
||||
stack.push(arr);
|
||||
depths.push(-1);
|
||||
|
||||
while (!stack.empty()) {
|
||||
var el = stack.pop();
|
||||
int d = depths.pop();
|
||||
|
||||
if ((d == -1 || d < depth) && el instanceof ArrayValue) {
|
||||
var arrEl = (ArrayValue)el;
|
||||
for (int i = arrEl.size() - 1; i >= 0; i--) {
|
||||
if (!arrEl.has(i)) continue;
|
||||
stack.push(arrEl.get(i));
|
||||
depths.push(d + 1);
|
||||
}
|
||||
}
|
||||
else res.set(args.ctx, res.size(), el);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose public static ArrayValue __flatMap(Arguments args) {
|
||||
return __flat(new Arguments(args.ctx, __map(args), 1));
|
||||
}
|
||||
|
||||
@Expose public static Object __find(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return arr.get(i);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@Expose public static Object __findLast(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = arr.size() - 1; i >= 0; i--) {
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return arr.get(i);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Expose public static int __findIndex(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@Expose public static int __findLastIndex(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = arr.size() - 1; i >= 0; i--) {
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Expose public static int __indexOf(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var val = args.get(0);
|
||||
var start = normalizeI(arr.size(), args.getInt(1), true);
|
||||
|
||||
for (int i = start; i < arr.size(); i++) {
|
||||
if (Values.strictEquals(args.ctx, arr.get(i), val)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@Expose public static int __lastIndexOf(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var val = args.get(0);
|
||||
var start = normalizeI(arr.size(), args.getInt(1), true);
|
||||
|
||||
for (int i = arr.size(); i >= start; i--) {
|
||||
if (Values.strictEquals(args.ctx, arr.get(i), val)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Expose public static boolean __includes(Arguments args) {
|
||||
return __indexOf(args) >= 0;
|
||||
}
|
||||
|
||||
@Expose public static Object __pop(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
if (arr.size() == 0) return null;
|
||||
|
||||
var val = arr.get(arr.size() - 1);
|
||||
arr.shrink(1);
|
||||
return val;
|
||||
}
|
||||
@Expose public static int __push(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var values = args.args;
|
||||
|
||||
arr.copyFrom(args.ctx, values, 0, arr.size(), values.length);
|
||||
return arr.size();
|
||||
}
|
||||
|
||||
@Expose public static Object __shift(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
if (arr.size() == 0) return null;
|
||||
var val = arr.get(0);
|
||||
|
||||
arr.move(1, 0, arr.size());
|
||||
arr.shrink(1);
|
||||
return val;
|
||||
}
|
||||
@Expose public static int __unshift(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var values = args.slice(0).args;
|
||||
|
||||
arr.move(0, values.length, arr.size());
|
||||
arr.copyFrom(args.ctx, values, 0, 0, values.length);
|
||||
return arr.size();
|
||||
}
|
||||
|
||||
@Expose public static ArrayValue __slice(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var start = normalizeI(arr.size(), args.getInt(0), true);
|
||||
var end = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
|
||||
|
||||
var res = new ArrayValue(end - start);
|
||||
arr.copyTo(res, start, 0, end - start);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose public static ArrayValue __splice(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var start = normalizeI(arr.size(), args.getInt(0), true);
|
||||
var deleteCount = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
|
||||
var items = args.slice(2).args;
|
||||
|
||||
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
|
||||
|
||||
var size = arr.size() - deleteCount + items.length;
|
||||
var res = new ArrayValue(deleteCount);
|
||||
arr.copyTo(res, start, 0, deleteCount);
|
||||
arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount);
|
||||
arr.copyFrom(args.ctx, items, 0, start, items.length);
|
||||
arr.setSize(size);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return __join(new Arguments(args.ctx, args.self, ","));
|
||||
}
|
||||
|
||||
@Expose public static String __join(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var sep = args.getString(0, ", ");
|
||||
var res = new StringBuilder();
|
||||
var comma = false;
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (!arr.has(i)) continue;
|
||||
|
||||
if (comma) res.append(sep);
|
||||
comma = true;
|
||||
|
||||
var el = arr.get(i);
|
||||
if (el == null || el == Values.NULL) continue;
|
||||
|
||||
res.append(Values.toString(args.ctx, el));
|
||||
}
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isArray(Arguments args) {
|
||||
return args.get(0) instanceof ArrayValue;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __of(Arguments args) {
|
||||
return new ArrayValue(args.ctx, args.slice(0).args);
|
||||
}
|
||||
|
||||
@ExposeConstructor public static ArrayValue __constructor(Arguments args) {
|
||||
ArrayValue res;
|
||||
|
||||
if (args.n() == 1 && args.get(0) instanceof Number) {
|
||||
var len = args.getInt(0);
|
||||
res = new ArrayValue(len);
|
||||
res.setSize(len);
|
||||
}
|
||||
else {
|
||||
var val = args.args;
|
||||
res = new ArrayValue(val.length);
|
||||
res.copyFrom(args.ctx, val, 0, 0, val.length);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
85
src/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java
Normal file
85
src/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java
Normal file
@@ -0,0 +1,85 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("AsyncFunction")
|
||||
public class AsyncFunctionLib extends FunctionValue {
|
||||
public final CodeFunction func;
|
||||
|
||||
private static class AsyncHelper {
|
||||
public PromiseLib promise = new PromiseLib();
|
||||
public Frame frame;
|
||||
|
||||
private boolean awaiting = false;
|
||||
|
||||
private void next(Context ctx, Object inducedValue, EngineException inducedError) {
|
||||
Object res = null;
|
||||
|
||||
frame.onPush();
|
||||
awaiting = false;
|
||||
while (!awaiting) {
|
||||
try {
|
||||
res = frame.next(inducedValue, Values.NO_RETURN, inducedError);
|
||||
inducedValue = Values.NO_RETURN;
|
||||
inducedError = null;
|
||||
|
||||
if (res != Values.NO_RETURN) {
|
||||
promise.fulfill(ctx, res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
promise.reject(ctx, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
frame.onPop();
|
||||
|
||||
if (awaiting) {
|
||||
PromiseLib.handle(ctx, frame.pop(), new Handle() {
|
||||
@Override
|
||||
public void onFulfil(Object val) {
|
||||
next(ctx, val, null);
|
||||
}
|
||||
@Override
|
||||
public void onReject(EngineException err) {
|
||||
next(ctx, Values.NO_RETURN, err);
|
||||
}
|
||||
}.defer(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
public Object await(Arguments args) {
|
||||
this.awaiting = true;
|
||||
return args.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new AsyncHelper();
|
||||
|
||||
var newArgs = new Object[args.length + 1];
|
||||
newArgs[0] = new NativeFunction("await", handler::await);
|
||||
System.arraycopy(args, 0, newArgs, 1, args.length);
|
||||
|
||||
handler.frame = new Frame(ctx, thisArg, newArgs, (CodeFunction)func);
|
||||
handler.next(ctx, Values.NO_RETURN, null);
|
||||
return handler.promise;
|
||||
}
|
||||
|
||||
public AsyncFunctionLib(FunctionValue func) {
|
||||
super(func.name, func.length);
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
this.func = (CodeFunction)func;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("AsyncGeneratorFunction")
|
||||
public class AsyncGeneratorFunctionLib extends FunctionValue {
|
||||
public final CodeFunction func;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new AsyncGeneratorLib();
|
||||
|
||||
var newArgs = new Object[args.length + 2];
|
||||
newArgs[0] = new NativeFunction("await", handler::await);
|
||||
newArgs[1] = new NativeFunction("yield", handler::yield);
|
||||
System.arraycopy(args, 0, newArgs, 2, args.length);
|
||||
|
||||
handler.frame = new Frame(ctx, thisArg, newArgs, func);
|
||||
return handler;
|
||||
}
|
||||
|
||||
public AsyncGeneratorFunctionLib(CodeFunction func) {
|
||||
super(func.name, func.length);
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
this.func = func;
|
||||
}
|
||||
}
|
||||
107
src/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java
Normal file
107
src/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("AsyncGenerator")
|
||||
public class AsyncGeneratorLib {
|
||||
private int state = 0;
|
||||
private boolean done = false;
|
||||
private PromiseLib currPromise;
|
||||
public Frame frame;
|
||||
|
||||
private void next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) {
|
||||
if (done) {
|
||||
if (inducedError != null) throw inducedError;
|
||||
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
|
||||
"done", true,
|
||||
"value", inducedReturn == Values.NO_RETURN ? null : inducedReturn
|
||||
)));
|
||||
return;
|
||||
}
|
||||
|
||||
Object res = null;
|
||||
state = 0;
|
||||
|
||||
frame.onPush();
|
||||
while (state == 0) {
|
||||
try {
|
||||
res = frame.next(inducedValue, inducedReturn, inducedError);
|
||||
inducedValue = inducedReturn = Values.NO_RETURN;
|
||||
inducedError = null;
|
||||
|
||||
if (res != Values.NO_RETURN) {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", true);
|
||||
obj.defineProperty(ctx, "value", res);
|
||||
currPromise.fulfill(ctx, obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
currPromise.reject(ctx, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
frame.onPop();
|
||||
|
||||
if (state == 1) {
|
||||
PromiseLib.handle(ctx, frame.pop(), new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
next(ctx, val, Values.NO_RETURN, null);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
next(ctx, Values.NO_RETURN, Values.NO_RETURN, err);
|
||||
}
|
||||
}.defer(ctx));
|
||||
}
|
||||
else if (state == 2) {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", false);
|
||||
obj.defineProperty(ctx, "value", frame.pop());
|
||||
currPromise.fulfill(ctx, obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (state != 0) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public Object await(Arguments args) {
|
||||
this.state = 1;
|
||||
return args.get(0);
|
||||
}
|
||||
public Object yield(Arguments args) {
|
||||
this.state = 2;
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
@Expose public PromiseLib __next(Arguments args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
if (args.has(0)) next(args.ctx, args.get(0), Values.NO_RETURN, null);
|
||||
else next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Expose public PromiseLib __return(Arguments args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
next(args.ctx, Values.NO_RETURN, args.get(0), null);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Expose public PromiseLib __throw(Arguments args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx));
|
||||
return this.currPromise;
|
||||
}
|
||||
}
|
||||
37
src/java/me/topchetoeu/jscript/lib/BooleanLib.java
Normal file
37
src/java/me/topchetoeu/jscript/lib/BooleanLib.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Boolean")
|
||||
public class BooleanLib {
|
||||
public static final BooleanLib TRUE = new BooleanLib(true);
|
||||
public static final BooleanLib FALSE = new BooleanLib(false);
|
||||
|
||||
public final boolean value;
|
||||
|
||||
@Override public String toString() {
|
||||
return value + "";
|
||||
}
|
||||
|
||||
public BooleanLib(boolean val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@ExposeConstructor public static Object __constructor(Arguments args) {
|
||||
var val = args.getBoolean(0);
|
||||
if (args.self instanceof ObjectValue) return val ? TRUE : FALSE;
|
||||
else return val;
|
||||
}
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return args.self(Boolean.class) ? "true" : "false";
|
||||
}
|
||||
@Expose public static boolean __valueOf(Arguments args) {
|
||||
if (Values.isWrapper(args.self, BooleanLib.class)) return Values.wrapper(args.self, BooleanLib.class).value;
|
||||
return args.self(Boolean.class);
|
||||
}
|
||||
}
|
||||
38
src/java/me/topchetoeu/jscript/lib/ConsoleLib.java
Normal file
38
src/java/me/topchetoeu/jscript/lib/ConsoleLib.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.filesystem.File;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Console")
|
||||
public class ConsoleLib {
|
||||
public static interface Writer {
|
||||
void writeLine(String val) throws IOException;
|
||||
}
|
||||
|
||||
private File file;
|
||||
|
||||
@Expose
|
||||
public void __log(Arguments args) {
|
||||
var res = new StringBuilder();
|
||||
var first = true;
|
||||
|
||||
for (var el : args.args) {
|
||||
if (!first) res.append(" ");
|
||||
first = false;
|
||||
res.append(Values.toReadable(args.ctx, el).getBytes());
|
||||
}
|
||||
|
||||
for (var line : res.toString().split("\n", -1)) {
|
||||
file.write(line.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public ConsoleLib(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@Native("Date") public class DateLib {
|
||||
@WrapperName("Date")
|
||||
public class DateLib {
|
||||
private Calendar normal;
|
||||
private Calendar utc;
|
||||
|
||||
@@ -22,244 +27,221 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
normal = utc = null;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static double now() {
|
||||
return new DateLib().getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getYear() {
|
||||
@Expose public double __getYear() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.YEAR) - 1900;
|
||||
}
|
||||
@Native
|
||||
public double setYear(Context ctx, double real) {
|
||||
@Expose public double __setYeard(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (real >= 0 && real <= 99) real = real + 1900;
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.YEAR, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getFullYear() {
|
||||
@Expose public double __getFullYear() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.YEAR);
|
||||
}
|
||||
@Native
|
||||
public double getMonth() {
|
||||
@Expose public double __getMonth() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getDate() {
|
||||
@Expose public double __getDate() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.DAY_OF_MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getDay() {
|
||||
@Expose public double __getDay() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.DAY_OF_WEEK);
|
||||
}
|
||||
@Native
|
||||
public double getHours() {
|
||||
@Expose public double __getHours() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.HOUR_OF_DAY);
|
||||
}
|
||||
@Native
|
||||
public double getMinutes() {
|
||||
@Expose public double __getMinutes() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.MINUTE);
|
||||
}
|
||||
@Native
|
||||
public double getSeconds() {
|
||||
@Expose public double __getSeconds() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.SECOND);
|
||||
}
|
||||
@Native
|
||||
public double getMilliseconds() {
|
||||
@Expose public double __getMilliseconds() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.MILLISECOND);
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getUTCFullYear() {
|
||||
@Expose public double __getUTCFullYear() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.YEAR);
|
||||
}
|
||||
@Native
|
||||
public double getUTCMonth() {
|
||||
@Expose public double __getUTCMonth() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getUTCDate() {
|
||||
@Expose public double __getUTCDate() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.DAY_OF_MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getUTCDay() {
|
||||
@Expose public double __getUTCDay() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.DAY_OF_WEEK);
|
||||
}
|
||||
@Native
|
||||
public double getUTCHours() {
|
||||
@Expose public double __getUTCHours() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.HOUR_OF_DAY);
|
||||
}
|
||||
@Native
|
||||
public double getUTCMinutes() {
|
||||
@Expose public double __getUTCMinutes() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.MINUTE);
|
||||
}
|
||||
@Native
|
||||
public double getUTCSeconds() {
|
||||
@Expose public double __getUTCSeconds() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.SECOND);
|
||||
}
|
||||
@Native
|
||||
public double getUTCMilliseconds() {
|
||||
@Expose public double __getUTCMilliseconds() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.MILLISECOND);
|
||||
}
|
||||
|
||||
@Native
|
||||
public double setFullYear(Context ctx, double real) {
|
||||
@Expose public double __setFullYear(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.YEAR, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setMonth(Context ctx, double real) {
|
||||
@Expose public double __setMonthd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.MONTH, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setDate(Context ctx, double real) {
|
||||
@Expose public double __setDated(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.DAY_OF_MONTH, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setDay(Context ctx, double real) {
|
||||
@Expose public double __setDayd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.DAY_OF_WEEK, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setHours(Context ctx, double real) {
|
||||
@Expose public double __setHoursd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.HOUR_OF_DAY, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setMinutes(Context ctx, double real) {
|
||||
@Expose public double __setMinutesd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.MINUTE, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setSeconds(Context ctx, double real) {
|
||||
@Expose public double __setSecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.SECOND, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setMilliseconds(Context ctx, double real) {
|
||||
@Expose public double __setMillisecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.MILLISECOND, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double setUTCFullYear(Context ctx, double real) {
|
||||
@Expose public double __setUTCFullYeard(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.YEAR, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCMonth(Context ctx, double real) {
|
||||
@Expose public double __setUTCMonthd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.MONTH, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCDate(Context ctx, double real) {
|
||||
@Expose public double __setUTCDated(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.DAY_OF_MONTH, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCDay(Context ctx, double real) {
|
||||
@Expose public double __setUTCDayd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.DAY_OF_WEEK, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCHours(Context ctx, double real) {
|
||||
@Expose public double __setUTCHoursd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.HOUR_OF_DAY, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCMinutes(Context ctx, double real) {
|
||||
@Expose public double __setUTCMinutesd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.MINUTE, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCSeconds(Context ctx, double real) {
|
||||
@Expose public double __setUTCSecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.SECOND, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCMilliseconds(Context ctx, double real) {
|
||||
@Expose public double __setUTCMillisecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.MILLISECOND, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getTime() {
|
||||
@Expose public double __getTime() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.getTimeInMillis();
|
||||
}
|
||||
@Native
|
||||
public double getTimezoneOffset() {
|
||||
@Expose public double __getTimezoneOffset() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.getTimeZone().getRawOffset() / 60000;
|
||||
}
|
||||
|
||||
@Native
|
||||
public double valueOf() {
|
||||
@Expose public double __valueOf() {
|
||||
if (normal == null) return Double.NaN;
|
||||
else return normal.getTimeInMillis();
|
||||
}
|
||||
|
||||
@Native
|
||||
public String toString() {
|
||||
@Expose public String __toString() {
|
||||
return normal.getTime().toString();
|
||||
}
|
||||
|
||||
@Native
|
||||
@Override public String toString() {
|
||||
return __toString();
|
||||
}
|
||||
|
||||
public DateLib(long timestamp) {
|
||||
normal = Calendar.getInstance();
|
||||
utc = Calendar.getInstance();
|
||||
@@ -268,8 +250,17 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
utc.setTimeInMillis(timestamp);
|
||||
}
|
||||
|
||||
@Native
|
||||
public DateLib() {
|
||||
this(new java.util.Date().getTime());
|
||||
this(new Date().getTime());
|
||||
}
|
||||
|
||||
@ExposeConstructor public static DateLib init(Arguments args) {
|
||||
if (args.has(0)) return new DateLib(args.getLong(0));
|
||||
else return new DateLib();
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __now() {
|
||||
return new DateLib().__getTime();
|
||||
}
|
||||
}
|
||||
89
src/java/me/topchetoeu/jscript/lib/EncodingLib.java
Normal file
89
src/java/me/topchetoeu/jscript/lib/EncodingLib.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.common.Buffer;
|
||||
import me.topchetoeu.jscript.compilation.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Encoding")
|
||||
public class EncodingLib {
|
||||
private static final String HEX = "0123456789ABCDEF";
|
||||
|
||||
public static String encodeUriAny(String str, String keepAlphabet) {
|
||||
if (str == null) str = "undefined";
|
||||
|
||||
var bytes = str.getBytes();
|
||||
var sb = new StringBuilder(bytes.length);
|
||||
|
||||
for (byte c : bytes) {
|
||||
if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
|
||||
else {
|
||||
sb.append('%');
|
||||
sb.append(HEX.charAt(c / 16));
|
||||
sb.append(HEX.charAt(c % 16));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
public static String decodeUriAny(String str, String keepAlphabet) {
|
||||
if (str == null) str = "undefined";
|
||||
|
||||
var res = new Buffer();
|
||||
var bytes = str.getBytes();
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
var c = bytes[i];
|
||||
if (c == '%') {
|
||||
if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
|
||||
var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
|
||||
if (!Parsing.isAny((char)b, keepAlphabet)) {
|
||||
i += 2;
|
||||
res.append((byte)b);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
res.append(c);
|
||||
}
|
||||
|
||||
return new String(res.data());
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __encode(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
for (var el : args.getString(0).getBytes()) res.set(null, res.size(), (int)el);
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decode(Arguments args) {
|
||||
var raw = args.convert(0, ArrayList.class);
|
||||
var res = new byte[raw.size()];
|
||||
for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, raw.get(i));
|
||||
return new String(res);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURIComponent(Arguments args) {
|
||||
return EncodingLib.encodeUriAny(args.getString(0), ".-_!~*'()");
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURIComponent(Arguments args) {
|
||||
return decodeUriAny(args.getString(0), "");
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURI(Arguments args) {
|
||||
return encodeUriAny(args.getString(0), ";,/?:@&=+$#.-_!~*'()");
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURI(Arguments args) {
|
||||
return decodeUriAny(args.getString(0), ",/?:@&=+$#.");
|
||||
}
|
||||
}
|
||||
54
src/java/me/topchetoeu/jscript/lib/ErrorLib.java
Normal file
54
src/java/me/topchetoeu/jscript/lib/ErrorLib.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Error")
|
||||
public class ErrorLib {
|
||||
private static String toString(Context ctx, Object name, Object message) {
|
||||
if (name == null) name = "";
|
||||
else name = Values.toString(ctx, name).trim();
|
||||
if (message == null) message = "";
|
||||
else message = Values.toString(ctx, message).trim();
|
||||
StringBuilder res = new StringBuilder();
|
||||
|
||||
if (!name.equals("")) res.append(name);
|
||||
if (!message.equals("") && !name.equals("")) res.append(": ");
|
||||
if (!message.equals("")) res.append(message);
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
@ExposeField public static final String __name = "Error";
|
||||
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
if (args.self instanceof ObjectValue) return toString(args.ctx,
|
||||
Values.getMember(args.ctx, args.self, "name"),
|
||||
Values.getMember(args.ctx, args.self, "message")
|
||||
);
|
||||
else return "[Invalid error]";
|
||||
}
|
||||
|
||||
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
|
||||
var target = new ObjectValue();
|
||||
var message = args.getString(0, "");
|
||||
|
||||
try {
|
||||
target = args.self(ObjectValue.class);
|
||||
}
|
||||
catch (ConvertException e) {}
|
||||
|
||||
target.setPrototype(PlaceholderProto.ERROR);
|
||||
target.defineProperty(args.ctx, "message", Values.toString(args.ctx, message));
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
84
src/java/me/topchetoeu/jscript/lib/FileLib.java
Normal file
84
src/java/me/topchetoeu/jscript/lib/FileLib.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.filesystem.File;
|
||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("File")
|
||||
public class FileLib {
|
||||
public final File fd;
|
||||
|
||||
@Expose public PromiseLib __pointer(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
return fd.seek(0, 1);
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose public PromiseLib __length(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
long curr = fd.seek(0, 1);
|
||||
long res = fd.seek(0, 2);
|
||||
fd.seek(curr, 0);
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
|
||||
@Expose public PromiseLib __read(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var n = args.getInt(0);
|
||||
try {
|
||||
var buff = new byte[n];
|
||||
var res = new ArrayValue();
|
||||
int resI = fd.read(buff);
|
||||
|
||||
for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose public PromiseLib __write(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var val = args.convert(0, ArrayValue.class);
|
||||
try {
|
||||
var res = new byte[val.size()];
|
||||
|
||||
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
|
||||
fd.write(res);
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose public PromiseLib __close(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
fd.close();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@Expose public PromiseLib __seek(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var ptr = args.getLong(0);
|
||||
var whence = args.getInt(1);
|
||||
|
||||
try {
|
||||
return fd.seek(ptr, whence);
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
|
||||
public FileLib(File fd) {
|
||||
this.fd = fd;
|
||||
}
|
||||
}
|
||||
193
src/java/me/topchetoeu/jscript/lib/FilesystemLib.java
Normal file
193
src/java/me/topchetoeu/jscript/lib/FilesystemLib.java
Normal file
@@ -0,0 +1,193 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.filesystem.ActionType;
|
||||
import me.topchetoeu.jscript.utils.filesystem.EntryType;
|
||||
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
|
||||
import me.topchetoeu.jscript.utils.filesystem.File;
|
||||
import me.topchetoeu.jscript.utils.filesystem.FileStat;
|
||||
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
|
||||
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
|
||||
import me.topchetoeu.jscript.utils.filesystem.Mode;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Filesystem")
|
||||
public class FilesystemLib {
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final int __SEEK_SET = 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final int __SEEK_CUR = 1;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final int __SEEK_END = 2;
|
||||
|
||||
private static Filesystem fs(Context ctx) {
|
||||
var fs = Filesystem.get(ctx);
|
||||
if (fs != null) return fs;
|
||||
throw EngineException.ofError("Current environment doesn't have a file system.");
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __normalize(Arguments args) {
|
||||
return fs(args.ctx).normalize(args.convert(String.class));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __open(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
var _mode = Mode.parse(args.getString(1));
|
||||
|
||||
try {
|
||||
if (fs.stat(path).type != EntryType.FILE) {
|
||||
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN);
|
||||
}
|
||||
|
||||
return new FileLib(fs.open(path, _mode));
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __ls(Arguments args) {
|
||||
|
||||
return Values.toJSAsyncIterator(args.ctx, new Iterator<>() {
|
||||
private boolean failed, done;
|
||||
private File file;
|
||||
private String nextLine;
|
||||
|
||||
private void update() {
|
||||
if (done) return;
|
||||
if (!failed) {
|
||||
if (file == null) {
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
|
||||
if (fs.stat(path).type != EntryType.FOLDER) {
|
||||
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN);
|
||||
}
|
||||
|
||||
file = fs.open(path, Mode.READ);
|
||||
}
|
||||
|
||||
if (nextLine == null) {
|
||||
while (true) {
|
||||
nextLine = file.readLine();
|
||||
if (nextLine == null) {
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
nextLine = nextLine.trim();
|
||||
if (!nextLine.equals("")) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
update();
|
||||
return !done && !failed;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
}
|
||||
@Override
|
||||
public String next() {
|
||||
try {
|
||||
update();
|
||||
var res = nextLine;
|
||||
nextLine = null;
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __mkdir(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
fs(args.ctx).create(args.getString(0), EntryType.FOLDER);
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __mkfile(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
fs(args.ctx).create(args.getString(0), EntryType.FILE);
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __rm(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
var recursive = args.getBoolean(1);
|
||||
|
||||
if (!recursive) fs.create(path, EntryType.NONE);
|
||||
else {
|
||||
var stack = new Stack<String>();
|
||||
stack.push(path);
|
||||
|
||||
while (!stack.empty()) {
|
||||
var currPath = stack.pop();
|
||||
FileStat stat;
|
||||
|
||||
try { stat = fs.stat(currPath); }
|
||||
catch (FilesystemException e) { continue; }
|
||||
|
||||
if (stat.type == EntryType.FOLDER) {
|
||||
for (var el : fs.open(currPath, Mode.READ).readToString().split("\n")) stack.push(el);
|
||||
}
|
||||
else fs.create(currPath, EntryType.NONE);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __stat(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
var stat = fs.stat(path);
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(args.ctx, "type", stat.type.name);
|
||||
res.defineProperty(args.ctx, "mode", stat.mode.name);
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __exists(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try { fs(args.ctx).stat(args.getString(0)); return true; }
|
||||
catch (FilesystemException e) { return false; }
|
||||
});
|
||||
}
|
||||
}
|
||||
54
src/java/me/topchetoeu/jscript/lib/FunctionLib.java
Normal file
54
src/java/me/topchetoeu/jscript/lib/FunctionLib.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Function")
|
||||
public class FunctionLib {
|
||||
@Expose public static Object __apply(Arguments args) {
|
||||
return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.convert(1, ArrayValue.class).toArray());
|
||||
}
|
||||
@Expose public static Object __call(Arguments args) {
|
||||
return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.slice(1).args);
|
||||
}
|
||||
@Expose public static FunctionValue __bind(Arguments args) {
|
||||
var self = args.self(FunctionValue.class);
|
||||
var thisArg = args.get(0);
|
||||
var bindArgs = args.slice(1).args;
|
||||
|
||||
return new NativeFunction(self.name + " (bound)", callArgs -> {
|
||||
Object[] resArgs;
|
||||
|
||||
if (args.n() == 0) resArgs = bindArgs;
|
||||
else {
|
||||
resArgs = new Object[bindArgs.length + callArgs.n()];
|
||||
System.arraycopy(bindArgs, 0, resArgs, 0, bindArgs.length);
|
||||
System.arraycopy(callArgs.args, 0, resArgs, bindArgs.length, callArgs.n());
|
||||
}
|
||||
|
||||
return self.call(callArgs.ctx, thisArg, resArgs);
|
||||
});
|
||||
}
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return args.self.toString();
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static FunctionValue __async(Arguments args) {
|
||||
return new AsyncFunctionLib(args.convert(0, FunctionValue.class));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static FunctionValue __asyncGenerator(Arguments args) {
|
||||
return new AsyncGeneratorFunctionLib(args.convert(0, CodeFunction.class));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static FunctionValue __generator(Arguments args) {
|
||||
return new GeneratorFunctionLib(args.convert(0, CodeFunction.class));
|
||||
}
|
||||
}
|
||||
31
src/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java
Normal file
31
src/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("GeneratorFunction")
|
||||
public class GeneratorFunctionLib extends FunctionValue {
|
||||
public final CodeFunction func;
|
||||
|
||||
@Override public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new GeneratorLib();
|
||||
|
||||
var newArgs = new Object[args.length + 1];
|
||||
newArgs[0] = new NativeFunction("yield", handler::yield);
|
||||
System.arraycopy(args, 0, newArgs, 1, args.length);
|
||||
|
||||
handler.frame = new Frame(ctx, thisArg, newArgs, func);
|
||||
return handler;
|
||||
}
|
||||
|
||||
public GeneratorFunctionLib(CodeFunction func) {
|
||||
super(func.name, func.length);
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
this.func = func;
|
||||
}
|
||||
}
|
||||
78
src/java/me/topchetoeu/jscript/lib/GeneratorLib.java
Normal file
78
src/java/me/topchetoeu/jscript/lib/GeneratorLib.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Generator")
|
||||
public class GeneratorLib {
|
||||
private boolean yielding = true;
|
||||
private boolean done = false;
|
||||
public Frame frame;
|
||||
|
||||
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) {
|
||||
if (done) {
|
||||
if (inducedError != Values.NO_RETURN) throw inducedError;
|
||||
var res = new ObjectValue();
|
||||
res.defineProperty(ctx, "done", true);
|
||||
res.defineProperty(ctx, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn);
|
||||
return res;
|
||||
}
|
||||
|
||||
Object res = null;
|
||||
yielding = false;
|
||||
|
||||
frame.onPush();
|
||||
while (!yielding) {
|
||||
try {
|
||||
res = frame.next(inducedValue, inducedReturn, inducedError);
|
||||
inducedReturn = Values.NO_RETURN;
|
||||
inducedError = null;
|
||||
if (res != Values.NO_RETURN) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
done = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
frame.onPop();
|
||||
|
||||
if (done) frame = null;
|
||||
else res = frame.pop();
|
||||
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", done);
|
||||
obj.defineProperty(ctx, "value", res);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Expose public ObjectValue __next(Arguments args) {
|
||||
if (args.n() == 0) return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
else return next(args.ctx, args.get(0), Values.NO_RETURN, null);
|
||||
}
|
||||
@Expose public ObjectValue __throw(Arguments args) {
|
||||
return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx));
|
||||
}
|
||||
@Expose public ObjectValue __return(Arguments args) {
|
||||
return next(args.ctx, Values.NO_RETURN, args.get(0), null);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (yielding) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public Object yield(Arguments args) {
|
||||
this.yielding = true;
|
||||
return args.get(0);
|
||||
}
|
||||
}
|
||||
217
src/java/me/topchetoeu/jscript/lib/Internals.java
Normal file
217
src/java/me/topchetoeu/jscript/lib/Internals.java
Normal file
@@ -0,0 +1,217 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.Environment;
|
||||
import me.topchetoeu.jscript.runtime.EventLoop;
|
||||
import me.topchetoeu.jscript.runtime.Key;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
|
||||
import me.topchetoeu.jscript.utils.filesystem.Mode;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
|
||||
|
||||
public class Internals {
|
||||
private static final Key<HashMap<Integer, Thread>> THREADS = new Key<>();
|
||||
private static final Key<Integer> I = new Key<>();
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __require(Arguments args) {
|
||||
var repo = ModuleRepo.get(args.ctx);
|
||||
|
||||
if (repo != null) {
|
||||
var res = repo.getModule(args.ctx, ModuleRepo.cwd(args.ctx), args.getString(0));
|
||||
res.load(args.ctx);
|
||||
return res.value();
|
||||
}
|
||||
|
||||
else throw EngineException.ofError("Modules are not supported.");
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Thread __setTimeout(Arguments args) {
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var delay = args.getDouble(1);
|
||||
var arguments = args.slice(2).args;
|
||||
|
||||
if (!args.ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
|
||||
|
||||
var thread = new Thread(() -> {
|
||||
var ms = (long)delay;
|
||||
var ns = (int)((delay - ms) * 10000000);
|
||||
|
||||
try { Thread.sleep(ms, ns); }
|
||||
catch (InterruptedException e) { return; }
|
||||
|
||||
args.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.environment), null, arguments), false);
|
||||
});
|
||||
|
||||
thread.start();
|
||||
var i = args.ctx.init(I, 1);
|
||||
args.ctx.add(I, i + 1);
|
||||
args.ctx.init(THREADS, new HashMap<Integer, Thread>()).put(i, thread);
|
||||
|
||||
return thread;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Thread __setInterval(Arguments args) {
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var delay = args.getDouble(1);
|
||||
var arguments = args.slice(2).args;
|
||||
|
||||
if (!args.ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
|
||||
|
||||
var thread = new Thread(() -> {
|
||||
var ms = (long)delay;
|
||||
var ns = (int)((delay - ms) * 10000000);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(ms, ns);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
|
||||
args.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.environment), null, arguments), false);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
var i = args.ctx.init(I, 1);
|
||||
args.ctx.add(I, i + 1);
|
||||
args.ctx.init(THREADS, new HashMap<Integer, Thread>()).put(i, thread);
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static void __clearTimeout(Arguments args) {
|
||||
var i = args.getInt(0);
|
||||
HashMap<Integer, Thread> map = args.ctx.get(THREADS);
|
||||
if (map == null) return;
|
||||
|
||||
var thread = map.get(i);
|
||||
if (thread == null) return;
|
||||
|
||||
thread.interrupt();
|
||||
map.remove(i);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static void __clearInterval(Arguments args) {
|
||||
__clearTimeout(args);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseInt(Arguments args) {
|
||||
return NumberLib.__parseInt(args);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseFloat(Arguments args) {
|
||||
return NumberLib.__parseFloat(args);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isNaN(Arguments args) {
|
||||
return NumberLib.__isNaN(args);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isFinite(Arguments args) {
|
||||
return NumberLib.__isFinite(args);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isInfinite(Arguments args) {
|
||||
return NumberLib.__isInfinite(args);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
|
||||
public static FileLib __stdin(Arguments args) {
|
||||
return new FileLib(Filesystem.get(args.ctx).open("std://in", Mode.READ));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
|
||||
public static FileLib __stdout(Arguments args) {
|
||||
return new FileLib(Filesystem.get(args.ctx).open("std://out", Mode.READ));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
|
||||
public static FileLib __stderr(Arguments args) {
|
||||
return new FileLib(Filesystem.get(args.ctx).open("std://err", Mode.READ));
|
||||
}
|
||||
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static double __NaN = Double.NaN;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static double __Infinity = Double.POSITIVE_INFINITY;
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURIComponent(Arguments args) {
|
||||
return EncodingLib.__encodeURIComponent(args);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURIComponent(Arguments args) {
|
||||
return EncodingLib.__decodeURIComponent(args);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURI(Arguments args) {
|
||||
return EncodingLib.__encodeURI(args);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURI(Arguments args) {
|
||||
return EncodingLib.__decodeURI(args);
|
||||
}
|
||||
|
||||
public static Environment apply(Environment env) {
|
||||
var wp = env.wrappers;
|
||||
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class));
|
||||
|
||||
glob.define(null, "Math", false, wp.getNamespace(MathLib.class));
|
||||
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class));
|
||||
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
|
||||
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
|
||||
|
||||
glob.define(null, false, wp.getConstr(FileLib.class));
|
||||
|
||||
glob.define(null, false, wp.getConstr(DateLib.class));
|
||||
glob.define(null, false, wp.getConstr(ObjectLib.class));
|
||||
glob.define(null, false, wp.getConstr(FunctionLib.class));
|
||||
glob.define(null, false, wp.getConstr(ArrayLib.class));
|
||||
|
||||
glob.define(null, false, wp.getConstr(BooleanLib.class));
|
||||
glob.define(null, false, wp.getConstr(NumberLib.class));
|
||||
glob.define(null, false, wp.getConstr(StringLib.class));
|
||||
glob.define(null, false, wp.getConstr(SymbolLib.class));
|
||||
|
||||
glob.define(null, false, wp.getConstr(PromiseLib.class));
|
||||
glob.define(null, false, wp.getConstr(RegExpLib.class));
|
||||
glob.define(null, false, wp.getConstr(MapLib.class));
|
||||
glob.define(null, false, wp.getConstr(SetLib.class));
|
||||
|
||||
glob.define(null, false, wp.getConstr(ErrorLib.class));
|
||||
glob.define(null, false, wp.getConstr(SyntaxErrorLib.class));
|
||||
glob.define(null, false, wp.getConstr(TypeErrorLib.class));
|
||||
glob.define(null, false, wp.getConstr(RangeErrorLib.class));
|
||||
|
||||
env.add(Environment.OBJECT_PROTO, wp.getProto(ObjectLib.class));
|
||||
env.add(Environment.FUNCTION_PROTO, wp.getProto(FunctionLib.class));
|
||||
env.add(Environment.ARRAY_PROTO, wp.getProto(ArrayLib.class));
|
||||
|
||||
env.add(Environment.BOOL_PROTO, wp.getProto(BooleanLib.class));
|
||||
env.add(Environment.NUMBER_PROTO, wp.getProto(NumberLib.class));
|
||||
env.add(Environment.STRING_PROTO, wp.getProto(StringLib.class));
|
||||
env.add(Environment.SYMBOL_PROTO, wp.getProto(SymbolLib.class));
|
||||
|
||||
env.add(Environment.ERROR_PROTO, wp.getProto(ErrorLib.class));
|
||||
env.add(Environment.SYNTAX_ERR_PROTO, wp.getProto(SyntaxErrorLib.class));
|
||||
env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class));
|
||||
env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class));
|
||||
|
||||
Values.setPrototype(Context.NULL, wp.getProto(ObjectLib.class), null);
|
||||
env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class));
|
||||
|
||||
return env;
|
||||
}
|
||||
}
|
||||
24
src/java/me/topchetoeu/jscript/lib/JSONLib.java
Normal file
24
src/java/me/topchetoeu/jscript/lib/JSONLib.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.common.json.JSON;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("JSON")
|
||||
public class JSONLib {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __parse(Arguments args) {
|
||||
try {
|
||||
return JSON.toJs(JSON.parse(null, args.getString(0)));
|
||||
}
|
||||
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __stringify(Arguments args) {
|
||||
return JSON.stringify(JSON.fromJs(args.ctx, args.get(0)));
|
||||
}
|
||||
}
|
||||
87
src/java/me/topchetoeu/jscript/lib/MapLib.java
Normal file
87
src/java/me/topchetoeu/jscript/lib/MapLib.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Map")
|
||||
public class MapLib {
|
||||
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
|
||||
|
||||
@Expose("@@Symbol.iterator")
|
||||
public ObjectValue __iterator(Arguments args) {
|
||||
return this.__entries(args);
|
||||
}
|
||||
|
||||
@Expose public void __clear() {
|
||||
map.clear();
|
||||
}
|
||||
@Expose public boolean __delete(Arguments args) {
|
||||
var key = args.get(0);
|
||||
if (map.containsKey(key)) {
|
||||
map.remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Expose public ObjectValue __entries(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, map
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
@Expose public ObjectValue __keys(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, map.keySet());
|
||||
}
|
||||
@Expose public ObjectValue __values(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, map.values());
|
||||
}
|
||||
|
||||
@Expose public Object __get(Arguments args) {
|
||||
return map.get(args.get(0));
|
||||
}
|
||||
@Expose public MapLib __set(Arguments args) {
|
||||
map.put(args.get(0), args.get(1));
|
||||
return this;
|
||||
}
|
||||
@Expose public boolean __has(Arguments args) {
|
||||
return map.containsKey(args.get(0));
|
||||
}
|
||||
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public int __size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Expose public void __forEach(Arguments args) {
|
||||
var keys = new ArrayList<>(map.keySet());
|
||||
|
||||
for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), map.get(el), el, args.self);
|
||||
}
|
||||
|
||||
public MapLib(Context ctx, Object iterable) {
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) {
|
||||
try {
|
||||
map.put(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
|
||||
}
|
||||
catch (IllegalArgumentException e) { }
|
||||
}
|
||||
}
|
||||
|
||||
@ExposeConstructor public static MapLib __constructor(Arguments args) {
|
||||
return new MapLib(args.ctx, args.get(0));
|
||||
}
|
||||
}
|
||||
211
src/java/me/topchetoeu/jscript/lib/MathLib.java
Normal file
211
src/java/me/topchetoeu/jscript/lib/MathLib.java
Normal file
@@ -0,0 +1,211 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Math")
|
||||
public class MathLib {
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __E = Math.E;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __PI = Math.PI;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __SQRT2 = Math.sqrt(2);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __SQRT1_2 = Math.sqrt(.5);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LN2 = Math.log(2);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LN10 = Math.log(10);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LOG2E = Math.log(Math.E) / __LN2;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LOG10E = Math.log10(Math.E);
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __asin(Arguments args) {
|
||||
return Math.asin(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __acos(Arguments args) {
|
||||
return Math.acos(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __atan(Arguments args) {
|
||||
return Math.atan(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __atan2(Arguments args) {
|
||||
var x = args.getDouble(1);
|
||||
var y = args.getDouble(0);
|
||||
|
||||
if (x == 0) {
|
||||
if (y == 0) return Double.NaN;
|
||||
return Math.signum(y) * Math.PI / 2;
|
||||
}
|
||||
else {
|
||||
var val = Math.atan(y / x);
|
||||
if (x > 0) return val;
|
||||
else if (y < 0) return val - Math.PI;
|
||||
else return val + Math.PI;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __asinh(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
return Math.log(x + Math.sqrt(x * x + 1));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __acosh(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
return Math.log(x + Math.sqrt(x * x - 1));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __atanh(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
|
||||
if (x <= -1 || x >= 1) return Double.NaN;
|
||||
return .5 * Math.log((1 + x) / (1 - x));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sin(Arguments args) {
|
||||
return Math.sin(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __cos(Arguments args) {
|
||||
return Math.cos(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __tan(Arguments args) {
|
||||
return Math.tan(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sinh(Arguments args) {
|
||||
return Math.sinh(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __cosh(Arguments args) {
|
||||
return Math.cosh(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __tanh(Arguments args) {
|
||||
return Math.tanh(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sqrt(Arguments args) {
|
||||
return Math.sqrt(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __cbrt(Arguments args) {
|
||||
return Math.cbrt(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __hypot(Arguments args) {
|
||||
var res = 0.;
|
||||
for (var i = 0; i < args.n(); i++) {
|
||||
var val = args.getDouble(i);
|
||||
res += val * val;
|
||||
}
|
||||
return Math.sqrt(res);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static int __imul(Arguments args) { return args.getInt(0) * args.getInt(1); }
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __exp(Arguments args) {
|
||||
return Math.exp(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __expm1(Arguments args) {
|
||||
return Math.expm1(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __pow(Arguments args) { return Math.pow(args.getDouble(0), args.getDouble(1)); }
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log(Arguments args) {
|
||||
return Math.log(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log10(Arguments args) {
|
||||
return Math.log10(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log1p(Arguments args) {
|
||||
return Math.log1p(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log2(Arguments args) {
|
||||
return Math.log(args.getDouble(0)) / __LN2;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __ceil(Arguments args) {
|
||||
return Math.ceil(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __floor(Arguments args) {
|
||||
return Math.floor(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __round(Arguments args) {
|
||||
return Math.round(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static float __fround(Arguments args) {
|
||||
return (float)args.getDouble(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __trunc(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
return Math.floor(Math.abs(x)) * Math.signum(x);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __abs(Arguments args) {
|
||||
return Math.abs(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __max(Arguments args) {
|
||||
var res = Double.NEGATIVE_INFINITY;
|
||||
|
||||
for (var i = 0; i < args.n(); i++) {
|
||||
var el = args.getDouble(i);
|
||||
if (el > res) res = el;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __min(Arguments args) {
|
||||
var res = Double.POSITIVE_INFINITY;
|
||||
|
||||
for (var i = 0; i < args.n(); i++) {
|
||||
var el = args.getDouble(i);
|
||||
if (el < res) res = el;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sign(Arguments args) {
|
||||
return Math.signum(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __random() { return Math.random(); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static int __clz32(Arguments args) {
|
||||
return Integer.numberOfLeadingZeros(args.getInt(0));
|
||||
}
|
||||
}
|
||||
92
src/java/me/topchetoeu/jscript/lib/NumberLib.java
Normal file
92
src/java/me/topchetoeu/jscript/lib/NumberLib.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Number")
|
||||
public class NumberLib {
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __EPSILON = Math.ulp(1.0);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MAX_SAFE_INTEGER = 9007199254740991.;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MIN_SAFE_INTEGER = -__MAX_SAFE_INTEGER;
|
||||
// lmao big number go brrr
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MIN_VALUE = -__MAX_VALUE;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __NaN = 0. / 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __NEGATIVE_INFINITY = -1. / 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __POSITIVE_INFINITY = 1. / 0;
|
||||
|
||||
public final double value;
|
||||
|
||||
@Override public String toString() { return value + ""; }
|
||||
|
||||
public NumberLib(double val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isInfinite(Arguments args) { return Double.isInfinite(args.getDouble(0)); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isNaN(Arguments args) { return Double.isNaN(args.getDouble(0)); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isSafeInteger(Arguments args) {
|
||||
return args.getDouble(0) > __MIN_SAFE_INTEGER && args.getDouble(0) < __MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseFloat(Arguments args) {
|
||||
return args.getDouble(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseInt(Arguments args) {
|
||||
var radix = args.getInt(1, 10);
|
||||
|
||||
if (radix < 2 || radix > 36) return Double.NaN;
|
||||
else {
|
||||
long res = 0;
|
||||
|
||||
for (var c : args.getString(0).toCharArray()) {
|
||||
var digit = 0;
|
||||
|
||||
if (c >= '0' && c <= '9') digit = c - '0';
|
||||
else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10;
|
||||
else break;
|
||||
|
||||
if (digit > radix) break;
|
||||
|
||||
res *= radix;
|
||||
res += digit;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@ExposeConstructor public static Object __constructor(Arguments args) {
|
||||
if (args.self instanceof ObjectValue) return new NumberLib(args.getDouble(0));
|
||||
else return args.getDouble(0);
|
||||
}
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return Values.toString(args.ctx, args.self);
|
||||
}
|
||||
@Expose public static double __valueOf(Arguments args) {
|
||||
if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value;
|
||||
else return Values.toNumber(args.ctx, args.self);
|
||||
}
|
||||
}
|
||||
273
src/java/me/topchetoeu/jscript/lib/ObjectLib.java
Normal file
273
src/java/me/topchetoeu/jscript/lib/ObjectLib.java
Normal file
@@ -0,0 +1,273 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Symbol;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Object")
|
||||
public class ObjectLib {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __assign(Arguments args) {
|
||||
for (var obj : args.slice(1).args) {
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
Values.setMember(args.ctx, args.get(0), key, Values.getMember(args.ctx, obj, key));
|
||||
}
|
||||
}
|
||||
return args.get(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __create(Arguments args) {
|
||||
var obj = new ObjectValue();
|
||||
Values.setPrototype(args.ctx, obj, args.get(0));
|
||||
|
||||
if (args.n() >= 1) {
|
||||
var newArgs = new Object[args.n()];
|
||||
System.arraycopy(args.args, 1, args, 1, args.n() - 1);
|
||||
newArgs[0] = obj;
|
||||
|
||||
__defineProperties(new Arguments(args.ctx, null, newArgs));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __defineProperty(Arguments args) {
|
||||
var obj = args.convert(0, ObjectValue.class);
|
||||
var key = args.get(1);
|
||||
var attrib = args.convert(2, ObjectValue.class);
|
||||
|
||||
var hasVal = Values.hasMember(args.ctx, attrib, "value", false);
|
||||
var hasGet = Values.hasMember(args.ctx, attrib, "get", false);
|
||||
var hasSet = Values.hasMember(args.ctx, attrib, "set", false);
|
||||
|
||||
if (hasVal) {
|
||||
if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property.");
|
||||
if (!obj.defineProperty(
|
||||
args.ctx, key,
|
||||
Values.getMember(args.ctx, attrib, "value"),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))
|
||||
)) throw EngineException.ofType("Can't define property '" + key + "'.");
|
||||
}
|
||||
else {
|
||||
var get = Values.getMember(args.ctx, attrib, "get");
|
||||
var set = Values.getMember(args.ctx, attrib, "set");
|
||||
if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function.");
|
||||
if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function.");
|
||||
|
||||
if (!obj.defineProperty(
|
||||
args.ctx, key,
|
||||
(FunctionValue)get, (FunctionValue)set,
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))
|
||||
)) throw EngineException.ofType("Can't define property '" + key + "'.");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __defineProperties(Arguments args) {
|
||||
var obj = args.convert(0, ObjectValue.class);
|
||||
var attrib = args.get(1);
|
||||
|
||||
for (var key : Values.getMembers(null, attrib, false, false)) {
|
||||
__defineProperty(new Arguments(args.ctx, null, obj, key, Values.getMember(args.ctx, attrib, key)));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __keys(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
var res = new ArrayValue();
|
||||
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __entries(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), new ArrayValue(args.ctx, key, Values.getMember(args.ctx, obj, key)));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __values(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __getOwnPropertyDescriptor(Arguments args) {
|
||||
return Values.getMemberDescriptor(args.ctx, args.get(0), args.get(1));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __getOwnPropertyDescriptors(Arguments args) {
|
||||
var res = new ObjectValue();
|
||||
var obj = args.get(0);
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
res.defineProperty(args.ctx, key, Values.getMemberDescriptor(args.ctx, obj, key));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __getOwnPropertyNames(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __getOwnPropertySymbols(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
var res = new ArrayValue();
|
||||
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
if (key instanceof Symbol) res.set(args.ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __hasOwn(Arguments args) {
|
||||
return Values.hasMember(args.ctx, args.get(0), args.get(1), true);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __getPrototypeOf(Arguments args) {
|
||||
return Values.getPrototype(args.ctx, args.get(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __setPrototypeOf(Arguments args) {
|
||||
Values.setPrototype(args.ctx, args.get(0), args.get(1));
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __fromEntries(Arguments args) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
for (var el : Values.fromJSIterator(args.ctx, args.get(0))) {
|
||||
if (el instanceof ArrayValue) {
|
||||
res.defineProperty(args.ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __preventExtensions(Arguments args) {
|
||||
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).preventExtensions();
|
||||
return args.get(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __seal(Arguments args) {
|
||||
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).seal();
|
||||
return args.get(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __freeze(Arguments args) {
|
||||
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).freeze();
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isExtensible(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
if (!(obj instanceof ObjectValue)) return false;
|
||||
return ((ObjectValue)obj).extensible();
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isSealed(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
|
||||
if (!(obj instanceof ObjectValue)) return true;
|
||||
var _obj = (ObjectValue)obj;
|
||||
|
||||
if (_obj.extensible()) return false;
|
||||
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isFrozen(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
|
||||
if (!(obj instanceof ObjectValue)) return true;
|
||||
var _obj = (ObjectValue)obj;
|
||||
|
||||
if (_obj.extensible()) return false;
|
||||
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
if (_obj.memberWritable(key)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Expose
|
||||
public static Object __valueOf(Arguments args) {
|
||||
return args.self;
|
||||
}
|
||||
@Expose
|
||||
public static String __toString(Arguments args) {
|
||||
var name = Values.getMember(args.ctx, args.self, Symbol.get("Symbol.typeName"));
|
||||
if (name == null) name = "Unknown";
|
||||
else name = Values.toString(args.ctx, name);
|
||||
|
||||
return "[object " + name + "]";
|
||||
}
|
||||
@Expose
|
||||
public static boolean __hasOwnProperty(Arguments args) {
|
||||
return Values.hasMember(args.ctx, args.self, args.get(0), true);
|
||||
}
|
||||
|
||||
@ExposeConstructor
|
||||
public static Object __constructor(Arguments args) {
|
||||
var arg = args.get(0);
|
||||
if (arg == null || arg == Values.NULL) return new ObjectValue();
|
||||
else if (arg instanceof Boolean) return new BooleanLib((boolean)arg);
|
||||
else if (arg instanceof Number) return new NumberLib(((Number)arg).doubleValue());
|
||||
else if (arg instanceof String) return new StringLib((String)arg);
|
||||
else if (arg instanceof Symbol) return new SymbolLib((Symbol)arg);
|
||||
else return arg;
|
||||
}
|
||||
}
|
||||
404
src/java/me/topchetoeu/jscript/lib/PromiseLib.java
Normal file
404
src/java/me/topchetoeu/jscript/lib/PromiseLib.java
Normal file
@@ -0,0 +1,404 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.ResultRunnable;
|
||||
import me.topchetoeu.jscript.runtime.Context;
|
||||
import me.topchetoeu.jscript.runtime.EventLoop;
|
||||
import me.topchetoeu.jscript.runtime.Extensions;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.utils.interop.Expose;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.utils.interop.WrapperName;
|
||||
|
||||
@WrapperName("Promise")
|
||||
public class PromiseLib {
|
||||
public static interface Handle {
|
||||
void onFulfil(Object val);
|
||||
void onReject(EngineException err);
|
||||
|
||||
default Handle defer(Extensions loop) {
|
||||
var self = this;
|
||||
|
||||
return new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
|
||||
loop.get(EventLoop.KEY).pushMsg(() -> self.onFulfil(val), true);
|
||||
}
|
||||
@Override public void onReject(EngineException val) {
|
||||
if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
|
||||
loop.get(EventLoop.KEY).pushMsg(() -> self.onReject(val), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static final int STATE_PENDING = 0;
|
||||
private static final int STATE_FULFILLED = 1;
|
||||
private static final int STATE_REJECTED = 2;
|
||||
|
||||
private List<Handle> handles = new ArrayList<>();
|
||||
|
||||
private int state = STATE_PENDING;
|
||||
private boolean handled = false;
|
||||
private Object val;
|
||||
|
||||
private void resolveSynchronized(Context ctx, Object val, int newState) {
|
||||
this.val = val;
|
||||
this.state = newState;
|
||||
|
||||
for (var handle : handles) {
|
||||
if (newState == STATE_FULFILLED) handle.onFulfil(val);
|
||||
if (newState == STATE_REJECTED) {
|
||||
handle.onReject((EngineException)val);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == STATE_REJECTED && !handled) {
|
||||
Values.printError(((EngineException)val).setCtx(ctx), "(in promise)");
|
||||
}
|
||||
|
||||
handles = null;
|
||||
|
||||
// ctx.get(EventLoop.KEY).pushMsg(() -> {
|
||||
// if (!ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
|
||||
|
||||
|
||||
// handles = null;
|
||||
// }, true);
|
||||
|
||||
}
|
||||
private synchronized void resolve(Context ctx, Object val, int newState) {
|
||||
if (this.state != STATE_PENDING || newState == STATE_PENDING) return;
|
||||
|
||||
handle(ctx, val, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
resolveSynchronized(ctx, val, newState);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
resolveSynchronized(ctx, val, STATE_REJECTED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void fulfill(Context ctx, Object val) {
|
||||
resolve(ctx, val, STATE_FULFILLED);
|
||||
}
|
||||
public synchronized void reject(Context ctx, EngineException val) {
|
||||
resolve(ctx, val, STATE_REJECTED);
|
||||
}
|
||||
|
||||
private void handle(Handle handle) {
|
||||
if (state == STATE_FULFILLED) handle.onFulfil(val);
|
||||
else if (state == STATE_REJECTED) {
|
||||
handle.onReject((EngineException)val);
|
||||
handled = true;
|
||||
}
|
||||
else handles.add(handle);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
if (state == STATE_PENDING) return "Promise (pending)";
|
||||
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
|
||||
else return "Promise (rejected)";
|
||||
}
|
||||
|
||||
public PromiseLib() {
|
||||
this.state = STATE_PENDING;
|
||||
this.val = null;
|
||||
}
|
||||
|
||||
public static PromiseLib await(Context ctx, ResultRunnable<Object> runner) {
|
||||
var res = new PromiseLib();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
res.fulfill(ctx, runner.run());
|
||||
}
|
||||
catch (EngineException e) {
|
||||
res.reject(ctx, e);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptException) throw e;
|
||||
else {
|
||||
res.reject(ctx, EngineException.ofError("Native code failed with " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}, "Promisifier").start();
|
||||
|
||||
return res;
|
||||
}
|
||||
public static PromiseLib await(Context ctx, Runnable runner) {
|
||||
return await(ctx, () -> {
|
||||
runner.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static void handle(Context ctx, Object obj, Handle handle) {
|
||||
if (Values.isWrapper(obj, PromiseLib.class)) {
|
||||
var promise = Values.wrapper(obj, PromiseLib.class);
|
||||
handle(ctx, promise, handle);
|
||||
return;
|
||||
}
|
||||
if (obj instanceof PromiseLib) {
|
||||
((PromiseLib)obj).handle(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
var rethrow = new boolean[1];
|
||||
|
||||
try {
|
||||
var then = Values.getMember(ctx, obj, "then");
|
||||
|
||||
if (then instanceof FunctionValue) Values.call(ctx, then, obj,
|
||||
new NativeFunction(args -> {
|
||||
try { handle.onFulfil(args.get(0)); }
|
||||
catch (Exception e) {
|
||||
rethrow[0] = true;
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(args -> {
|
||||
try { handle.onReject(new EngineException(args.get(0))); }
|
||||
catch (Exception e) {
|
||||
rethrow[0] = true;
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else handle.onFulfil(obj);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (rethrow[0]) throw e;
|
||||
}
|
||||
|
||||
handle.onFulfil(obj);
|
||||
}
|
||||
|
||||
public static PromiseLib ofResolved(Context ctx, Object value) {
|
||||
var res = new PromiseLib();
|
||||
res.fulfill(ctx, value);
|
||||
return res;
|
||||
}
|
||||
public static PromiseLib ofRejected(Context ctx, EngineException value) {
|
||||
var res = new PromiseLib();
|
||||
res.reject(ctx, value);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose(value = "resolve", target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __ofResolved(Arguments args) {
|
||||
return ofResolved(args.ctx, args.get(0));
|
||||
}
|
||||
@Expose(value = "reject", target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __ofRejected(Arguments args) {
|
||||
return ofRejected(args.ctx, new EngineException(args.get(0)).setCtx(args.ctx));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __any(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
|
||||
if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setCtx(args.ctx));
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
var errors = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
handle(args.ctx, val, new Handle() {
|
||||
public void onFulfil(Object val) { res.fulfill(args.ctx, val); }
|
||||
public void onReject(EngineException err) {
|
||||
errors.set(args.ctx, index, err.value);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setCtx(args.ctx));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __race(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
var res = new PromiseLib();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var val = promises.get(i);
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
handle(args.ctx, val, new Handle() {
|
||||
@Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); }
|
||||
@Override public void onReject(EngineException err) { res.reject(args.ctx, err); }
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __all(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
|
||||
handle(args.ctx, val, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
result.set(args.ctx, index, val);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, result);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
res.reject(args.ctx, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __allSettled(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
var index = i;
|
||||
|
||||
handle(args.ctx, promises.get(i), new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
var desc = new ObjectValue();
|
||||
desc.defineProperty(args.ctx, "status", "fulfilled");
|
||||
desc.defineProperty(args.ctx, "value", val);
|
||||
|
||||
result.set(args.ctx, index, desc);
|
||||
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, res);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
var desc = new ObjectValue();
|
||||
desc.defineProperty(args.ctx, "status", "reject");
|
||||
desc.defineProperty(args.ctx, "value", err.value);
|
||||
|
||||
result.set(args.ctx, index, desc);
|
||||
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose
|
||||
public static Object __then(Arguments args) {
|
||||
var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
|
||||
var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null;
|
||||
|
||||
var res = new PromiseLib();
|
||||
|
||||
handle(args.ctx, args.self, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); }
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); }
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
}.defer(args.ctx));
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose
|
||||
public static Object __catch(Arguments args) {
|
||||
return __then(new Arguments(args.ctx, args.self, null, args.get(0)));
|
||||
}
|
||||
@Expose
|
||||
public static Object __finally(Arguments args) {
|
||||
var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
|
||||
|
||||
var res = new PromiseLib();
|
||||
|
||||
handle(args.ctx, args.self, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
try {
|
||||
func.call(args.ctx);
|
||||
res.fulfill(args.ctx, val);
|
||||
}
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
try {
|
||||
func.call(args.ctx);
|
||||
res.reject(args.ctx, err);
|
||||
}
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
}.defer(args.ctx));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ExposeConstructor
|
||||
public static PromiseLib __constructor(Arguments args) {
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var res = new PromiseLib();
|
||||
|
||||
try {
|
||||
func.call(
|
||||
args.ctx, null,
|
||||
new NativeFunction(null, _args -> {
|
||||
res.fulfill(_args.ctx, _args.get(0));
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, _args -> {
|
||||
res.reject(_args.ctx, new EngineException(_args.get(0)).setCtx(_args.ctx));
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
res.reject(args.ctx, e);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user