Compare commits

..

179 Commits

Author SHA1 Message Date
48bd1e2015 bump 2024-01-12 09:54:32 +02:00
304665904f feat: extract log API to console 2024-01-12 09:53:56 +02:00
56ae3a85a6 build: improve build scripts 2024-01-12 09:48:20 +02:00
0178cb2194 build: specify java toolchain 2024-01-11 11:46:51 +02:00
a2cb5cd473 build: add gradle wrapper props 2024-01-11 11:46:41 +02:00
c123427e77 bump 2024-01-11 10:59:07 +02:00
7ac5ded185 build: set main class in jar manifest 2024-01-11 10:58:40 +02:00
769d6ae8fc version unbump 2024-01-11 10:55:43 +02:00
afb99ffc70 action attempt 1 2024-01-11 10:52:18 +02:00
46136e77e2 build: improve build scripts 2024-01-11 10:47:41 +02:00
b460b87318 refactor: remove old build script 2024-01-11 09:57:41 +02:00
e772f0b50d feat: change custom build script to gradle 2024-01-11 09:56:50 +02:00
187ad55291 fix: fully remove typescript 2024-01-10 17:02:45 +02:00
8156a1733f refactor: move debugging logic out of core 2024-01-10 17:01:24 +02:00
d1937fdb63 remove typescript 2024-01-10 16:51:40 +02:00
3f826cc85d remove old test 2024-01-10 16:51:17 +02:00
af35d7f20b fix: oops 2024-01-10 11:25:29 +02:00
cfa0e001b9 refactor: split up code in 4 modules 2024-01-10 11:21:49 +02:00
c10d071346 feat: some API improvements 2024-01-10 11:21:35 +02:00
89eea7d62b refactor: remove old useless exceptions 2024-01-06 19:51:48 +02:00
18d22a1282 Merge pull request #12 from TopchetoEU/TopchetoEU/cleanup
Major codebase cleanup
2024-01-06 18:28:11 +02:00
72a0d39d0b fix: make java 11 compatible 2024-01-06 18:27:36 +02:00
d8585a20bf refactor: don't require ctx in frame.push 2024-01-06 18:23:34 +02:00
e4c9a8756e fix: debugger hanging sometimes 2024-01-06 18:23:20 +02:00
c6e6425c7e fix: bring back ts minification 2024-01-06 17:53:42 +02:00
292ca64cb9 fix: wrong behavior in Number.toString (which somehow broke typescript) 2024-01-06 17:50:06 +02:00
4572db5c46 feat: some array tests 2024-01-06 17:49:36 +02:00
0251c4689d fix: use Values to access members in ObjectLib, instead of direct access 2024-01-06 17:49:27 +02:00
3173919b49 fix: implement proper parseInt logic 2024-01-06 17:48:35 +02:00
45f133c6b0 fix: use Values.call instead of direct calling 2024-01-06 17:48:10 +02:00
34276d720c fix: remove sparse call arguments 2024-01-06 17:47:38 +02:00
2c634778c3 fix: report proper function name in String.length errors 2024-01-06 17:47:07 +02:00
4aa757e625 fix: Function.bind now passess this argument, instead of the function itself 2024-01-06 17:46:39 +02:00
918f2623cd fix: small issue with sparse arrays 2024-01-06 17:46:13 +02:00
a321fc14bc fix: wrong signature of Map.forEach 2024-01-06 17:45:56 +02:00
07a6f18b16 refactor: some spring cleaning in array lib, fix small issue with join 2024-01-06 17:45:52 +02:00
5f4011aa0c refactor: move NO_RETURN to Values, remove some casters from Values 2024-01-06 17:45:44 +02:00
71f735b812 fix: some more libs fixes 2024-01-04 13:58:04 +02:00
e575b3287e fix: try-catch-finally fix #457846982 2024-01-04 13:57:41 +02:00
4fa5f5a815 feat: use new wrapper API in libs 2024-01-04 10:02:14 +02:00
a61c6a494e fix: some Argument and Engine API improvements, 2024-01-04 10:02:01 +02:00
978ee8db79 feat: make better native wrapper API 2023-12-28 16:55:57 +02:00
e372941e99 refactor: generalize Reading class 2023-12-27 20:18:41 +02:00
c36a0db860 refactor: remove more dead code 2023-12-27 20:18:23 +02:00
d6ee59363f refactor: remove unneeded event system 2023-12-27 20:10:11 +02:00
d5fd6e650e fix: clean up debugger API 2023-12-27 20:02:45 +02:00
c0b895e00a feat: greatly improve Context API 2023-12-27 14:22:18 +02:00
9ea5cd9277 refactor: remove old Data API 2023-12-27 14:21:52 +02:00
aaf9a6fa45 fix: pass environment to compiler via simple environment wrapper 2023-12-27 13:59:19 +02:00
579f09c837 fix: pass arguments to regex constructor in LOAD_REGEX 2023-12-27 13:28:17 +02:00
3343262e72 fix: main now uses new env API 2023-12-27 13:16:21 +02:00
153a1a9a49 feat: readd permissions API via new env API 2023-12-27 13:16:12 +02:00
bf38587271 feat: readd module API via env API 2023-12-27 13:15:46 +02:00
21534efd60 feat: readd FS API via new env API 2023-12-27 13:14:46 +02:00
802f2f3f52 fix: access env via context 2023-12-27 13:14:09 +02:00
38acc20a6f refactor: greatly improve Environment API 2023-12-26 17:12:20 +02:00
d7f6010319 fix: make code java 11 compatible 2023-12-26 14:22:35 +02:00
87f8975275 build: minify typescript code 2023-12-26 14:22:25 +02:00
09eb6507dc Merge pull request #11 from TopchetoEU/TopchetoEU/modules
Module support
2023-12-26 14:20:55 +02:00
2f58f6b245 feat: implement basic module system 2023-12-26 14:12:41 +02:00
4bc363485f fix: losts of FS API improvements 2023-12-26 14:02:32 +02:00
8e01db637b fix: improve path resolutions and FS API 2023-12-26 11:27:40 +02:00
1c64912786 feat: create memory-light directory list file 2023-12-26 11:27:09 +02:00
28265a8f44 fix: some bug fixes and improvements with File interface 2023-12-26 11:26:08 +02:00
e9e020512e fix: environment pushed when it shouldn't be 2023-12-24 15:17:38 +02:00
4b0bbf5190 fix: correctly convert virtual to real path 2023-12-24 14:40:27 +02:00
031f78ebf1 fix: don't include ts's code in repo anymore 2023-12-24 14:32:37 +02:00
562f1f9425 fix: remove major mistakes in README 2023-12-24 14:31:50 +02:00
82a09e8865 fix: some config files cleanup 2023-12-24 14:31:33 +02:00
90da2db1fb fix: error now is not null-prototyped 2023-12-24 14:30:48 +02:00
3d275c52c0 refactor: fix code to not result in compile warning 2023-12-24 14:30:31 +02:00
797585f539 feat: implement some stdlib functions 2023-12-24 14:28:12 +02:00
7a301eba8f fix: some stupid mistakes in FS 2023-12-24 14:27:27 +02:00
1b2068a274 fix: never-ending try-catch issues 2023-12-24 14:27:00 +02:00
078d7ed95f refactor: improve Engine API 2023-12-24 14:26:42 +02:00
93973c12b1 fix: change default return type to void for generators 2023-12-24 14:10:03 +02:00
cad4f34b51 fix: use correct env declarations in bootstrap.js 2023-12-24 14:08:20 +02:00
d3571d6ee2 fix: make .gitignore more restrictive 2023-12-22 10:46:30 +02:00
caf9131cde refactor: replace all CRLF with LF 2023-12-22 10:45:04 +02:00
8c6379eb24 Merge pull request #10 from TopchetoEU/TopchetoEU/mapping
Add support for source mappings
2023-12-18 22:42:38 +02:00
380a5c720a feat: make environment hidable from stack trace 2023-12-18 22:38:26 +02:00
76c3d377af feat: use mappings in stack traces 2023-12-18 22:30:14 +02:00
42f443572a fix: how the hell did i fix this (it was the cache all along) 2023-12-18 22:11:08 +02:00
773bc72f3e refactor: rename compileWithDebug to compile 2023-12-14 21:07:16 +02:00
0b5178e9fd fix: return minified typescript 2023-12-14 21:06:23 +02:00
8cffcff7db refactor: remove unused instructions 2023-12-14 17:02:24 +02:00
60bbaaccd4 fix: some issues with try-catch 2023-12-14 16:49:51 +02:00
60b1762462 refactor: remove printf 2023-12-14 16:49:35 +02:00
34434965d2 cant be fucked to split this one up 2023-12-14 12:39:01 +02:00
fe86123f0f refactor: improve function statement 2023-11-29 13:04:41 +02:00
d5e6edfa8b refactor: replace .locate with argument 2023-11-29 13:03:53 +02:00
73345062ca feat: implement new API with source maps 2023-11-28 21:40:37 +02:00
124341969c fix: simplify source map API 2023-11-28 21:39:25 +02:00
8defd93855 fix: use proper name for native constructors 2023-11-28 21:37:46 +02:00
6c57e0e9f2 fix: properly handle wrapper function 2023-11-28 21:37:13 +02:00
f1932914ee fix: move debugger assets to correct location 2023-11-27 20:28:02 +02:00
977701e601 feat: implement source maps 2023-11-26 16:50:07 +02:00
e8a7ac8da8 refactor: reorganize assets 2023-11-26 16:49:47 +02:00
6b1cb852c2 fix: arrays wrongly stringified in JSONLib 2023-11-26 13:51:56 +02:00
b59a003086 feat: implement VLQ parsing 2023-11-26 13:51:43 +02:00
1902e41f61 feat: make typescript output mappings 2023-11-26 11:52:47 +02:00
27162ef8ac feat: improve Bufffer API 2023-11-26 11:51:32 +02:00
4f22e76d2b fix: make code java 11 compatible 2023-11-25 20:22:16 +02:00
8924e7aadc build: fix build script to exit with code when error occurs 2023-11-25 20:16:06 +02:00
1d0e31a423 Merge pull request #9 from TopchetoEU/TopchetoEU/perms-and-fs
Permissions and filesystems
2023-11-25 20:10:59 +02:00
ab56908171 fix: faulty insufficient permissions error 2023-11-25 20:09:59 +02:00
eb14bb080c fix: debugger breakpoints not registered 2023-11-25 20:09:47 +02:00
f52f47cdb4 feat: properly implement filesystems 2023-11-25 19:36:18 +02:00
567eaa8514 fix: incorrect declarations 2023-11-25 19:13:20 +02:00
2cfdd8e335 feat: add utf8 encoding and decoding 2023-11-25 19:12:56 +02:00
4b1ec671e2 fix: micro tasks not handled properly 2023-11-25 18:58:35 +02:00
b127aadcf6 fix: promise was sending macro tasks instead of micro 2023-11-25 18:58:24 +02:00
b6eaff65ca style: remove unnececeary import 2023-11-25 18:48:46 +02:00
443dc0ffa1 feat: add await function to promise 2023-11-25 18:47:51 +02:00
e107dd3507 fix: promise doesn't use microtasks in some cases 2023-11-25 18:47:36 +02:00
6af3c70fce feat: add filesystem libs 2023-11-25 18:47:01 +02:00
8b743f49d1 feat: add toString, equals and hashCode overrides to wrappers and objects 2023-11-25 18:46:13 +02:00
e1ce384815 feat: add parse function to Filename 2023-11-25 18:44:27 +02:00
86d205e521 fix: parsing files won't modify last instruction to RET, instead will just append it 2023-11-25 18:44:11 +02:00
f0ad936e5b refactor: use new iterable names 2023-11-25 18:43:43 +02:00
4a1473c5be fix: sorting with no comparator now works 2023-11-25 18:42:54 +02:00
4111dbf5c4 fix: functions now print their name when stringified 2023-11-25 18:41:47 +02:00
1666682dc2 refactor: get rid of data parent hierarchy 2023-11-25 18:41:18 +02:00
f2b33d0233 feat: add support for async iterators
fix: comparasions had small behavioural issue
2023-11-25 18:40:49 +02:00
f5a0b6eaf7 fix: some improvements of compiled bytecode 2023-11-25 18:38:43 +02:00
829bea755d fix: CodeFrame didnt't handle out of bounds jumps well 2023-11-25 18:38:16 +02:00
4b0dcffd13 feat: add fs declarations in lib.d.ts 2023-11-25 18:37:40 +02:00
987f8b8f00 fix: handle native errors properly-ish 2023-11-25 14:18:46 +02:00
55e3d46bc2 feat: implement memory and root fs 2023-11-25 14:18:04 +02:00
3e25068219 feat: implement bulk of fs 2023-11-25 14:17:37 +02:00
7ecb8bfabb feat: implement permissions 2023-11-25 14:16:02 +02:00
488deea164 fix: improve performance of typescript by caching separate declarations 2023-11-14 09:24:39 +02:00
ed08041335 fix: internal error when trying to use key of "undefined" 2023-11-14 09:24:00 +02:00
0a4149ba81 fix: remove double space in "Uncaught ..." 2023-11-14 09:23:15 +02:00
30f5d619c3 fix: errors now have the right prototype and name 2023-11-14 09:22:56 +02:00
e7dbe91374 refactor: clean up protocol.json 2023-11-13 20:06:07 +02:00
455f5a613e feat: implement simple permission matching 2023-11-13 19:09:33 +02:00
1eeac3ae97 fix: replace templates in Metadata class with placeholder data 2023-11-13 19:08:56 +02:00
1acd78e119 refactor: clean up Main class 2023-11-13 18:56:59 +02:00
df9932874d feat: remove unnececeary @NativeInit directives 2023-11-06 14:03:15 +02:00
b47d1a7576 refactor: remove StackData and Data usage 2023-11-06 13:53:36 +02:00
fdfa8d7713 refactor: some minor fixes, rewrite README example 2023-11-06 10:55:38 +02:00
f5d1287948 fix: some annoying bugs, as well as splice 2023-11-06 00:55:30 +02:00
15f4278cb1 fix: build with java 17 2023-11-05 21:00:39 +02:00
df8465cb49 Merge pull request #8 from TopchetoEU/TopchetoEU/tests
Integrate typescript
2023-11-05 20:32:42 +02:00
e3104c223c fix: remove some unnececeary logs 2023-11-05 20:29:21 +02:00
1d0bae3de8 feat: include typescript code in source code 2023-11-05 20:27:23 +02:00
b66acd3089 fix: several more fixes 2023-11-05 19:44:44 +02:00
e326847287 feat: send value stack to debug client 2023-11-05 19:44:35 +02:00
26591d6631 fix: lazy operators incorrectly pop values from stack 2023-11-05 19:44:08 +02:00
af31b1ab79 fix: several small fixes 2023-11-05 19:43:53 +02:00
f885d4349f refactor: fix some bad code >:( 2023-11-05 19:43:28 +02:00
d57044acb7 fix: several bug fixes to help with typescript support 2023-11-05 12:44:29 +02:00
7df4e3b03f fix: oops 2023-11-04 11:43:57 +02:00
ed1009ab69 refactor: some code restructuring in the debugging 2023-11-04 11:42:06 +02:00
f856cdf37e fix: messages larger than 64KB are now fragmented properly 2023-11-04 11:41:31 +02:00
4f82574b8c fix: various small behavioural issues
fix: pesky try-catch logic
2023-11-04 11:40:50 +02:00
0ae24148d8 feat: write some tests 2023-11-04 11:38:48 +02:00
ac128d17f4 feat: implement Array.reduce
fix: native functions are now named
2023-11-04 11:38:29 +02:00
6508f15bb0 refactor: remove typescript source code from repo (for now) 2023-11-04 11:37:13 +02:00
69f93b4f87 refactor: make filenames more consistent 2023-11-04 11:36:36 +02:00
b675411925 fix: a lot of minor bugs 2023-10-29 23:47:48 +02:00
d1e93c2088 Merge branch 'master' of https://github.com/TopchetoEU/java-jscript 2023-10-29 12:33:18 +02:00
942db54546 feat: improve vscode debugging compatibility 2023-10-29 12:30:43 +02:00
d20df66982 feat: improve vscode debugging compatibility 2023-10-29 12:25:33 +02:00
16a9e5d761 Merge pull request #7 from TopchetoEU/TopcehtoEU/debugging
Debugging support
2023-10-28 17:11:55 +03:00
dc9d84a370 chore: nothing of use 2023-10-28 17:10:27 +03:00
edb71daef4 feat: fully implement local and capture scope object wrappers
feat: implement object sending and receiving
2023-10-28 16:58:33 +03:00
4b84309df6 feat: complete steptrough and breakpoints in debugger 2023-10-27 15:12:14 +03:00
d2d9fa9738 fix: exit now works 2023-10-07 14:08:47 +03:00
a4e5f7f471 refactor: clean up useless catch blocks 2023-10-07 13:55:44 +03:00
cc044374ba refactor: replace InterruptedException with unchecked alternative 2023-10-07 13:42:55 +03:00
517e3e6657 refactor: clean up context and stack data 2023-10-07 13:07:07 +03:00
926b9c17d8 feat: readd JSON to lib 2023-10-06 18:42:35 +03:00
fc705e7383 feat: readd date internals 2023-10-06 12:03:33 +03:00
a17ec737b7 refactor: rename polyfills to libs 2023-10-06 11:57:29 +03:00
952a4d631d fix: keep order of object fields 2023-10-04 22:06:49 +03:00
005610ca40 Merge pull request #6 from TopchetoEU/TopchetoEU/function-optimizations
Cache function bodies
2023-10-04 21:38:29 +03:00
9743a6c078 fix: cache function bodies 2023-10-04 21:34:33 +03:00
278 changed files with 16202 additions and 11965 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@@ -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
View File

@@ -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

View File

@@ -2,33 +2,28 @@
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
## Example
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
The following is going to execute a simple javascript statement:
```java
var engine = new PolyfillEngine(new File("."));
var in = new BufferedReader(new InputStreamReader(System.in));
engine.start();
var engine = new Engine();
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
var env = Internals.apply(new Environment());
while (true) {
try {
var raw = in.readLine();
// Queue code to load internal libraries and start engine
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
// Run the engine on the same thread, until the event loop runs empty
engine.run(true);
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
Values.printValue(engine.context(), res);
System.out.println();
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(engine.context()));
}
catch (InterruptedException _e) { return; }
}
catch (IOException | InterruptedException e) { return; }
}
// Get our result
System.out.println(awaitable.await());
```
## NOTE:
To setup the typescript bundle in your sources, run `node build.js init-ts`. This will download the latest version of typescript, minify it, and add it to your src folder. If you are going to work with the `node build.js debug|release` command, this is not a necessary step.

32
build.gradle Normal file
View 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

View File

@@ -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
View File

@@ -0,0 +1,4 @@
project_group = me.topchetoeu
project_name = jscript
project_version = 0.8.6-beta
main_class = me.topchetoeu.jscript.utils.JScriptRepl

View 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
View File

@@ -0,0 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = properties.project_name

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JScript Debugger</title>
</head>
<body>
<p>
This is the debugger of JScript. It implement the <a href="https://chromedevtools.github.io/devtools-protocol/1-2/">V8 Debugging protocol</a>,
so you can use the devtools in chrome. <br>
The debugger is still in early development, so please report any issues to
<a href="https://github.com/TopchetoEU/java-jscript/issues">the github repo</a>.
</p>
<p>
Here are the available entrypoints:
<ul>
<li><a href="json/version">/json/version</a> - version and other stuff about the JScript engine</li>
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
<li>/(any target) - websocket entrypoints for debugging</li>
</ul>
</p>
<p>
Running ${NAME} v${VERSION} by ${AUTHOR}
</p>
</body>
</html>

File diff suppressed because it is too large Load Diff

5
src/assets/metadata.json Normal file
View File

@@ -0,0 +1,5 @@
{
"version": "${version}",
"name": "${name}",
"author": "TopchetoEU"
}

View File

@@ -0,0 +1,53 @@
package me.topchetoeu.jscript.common;
public class Buffer {
private byte[] data;
private int length;
public void write(int i, byte[] val) {
if (i + val.length > data.length) {
var newCap = i + val.length + 1;
if (newCap < data.length * 2) newCap = data.length * 2;
var tmp = new byte[newCap];
System.arraycopy(this.data, 0, tmp, 0, length);
this.data = tmp;
}
System.arraycopy(val, 0, data, i, val.length);
if (i + val.length > length) length = i + val.length;
}
public int read(int i, byte[] buff) {
int n = buff.length;
if (i + n > length) n = length - i;
System.arraycopy(data, i, buff, 0, n);
return n;
}
public void append(byte b) {
write(length, new byte[] { b });
}
public byte[] data() {
var res = new byte[length];
System.arraycopy(this.data, 0, res, 0, length);
return res;
}
public int length() {
return length;
}
public Buffer(byte[] data) {
this.data = new byte[data.length];
this.length = data.length;
System.arraycopy(data, 0, this.data, 0, data.length);
}
public Buffer(int capacity) {
this.data = new byte[capacity];
this.length = 0;
}
public Buffer() {
this.data = new byte[128];
this.length = 0;
}
}

View File

@@ -0,0 +1,65 @@
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() {
return protocol + "://" + path;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
var other = (Filename) obj;
if (protocol == null) {
if (other.protocol != null) return false;
}
else if (!protocol.equals(other.protocol)) return false;
if (path == null) {
if (other.path != null) return false;
}
else if (!path.equals(other.path)) return false;
return true;
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
this.protocol = protocol;
this.path = path;
}
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
public static Path normalize(String path) {
return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
}

View File

@@ -1,18 +1,18 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
public class Location {
public static final Location INTERNAL = new Location(0, 0, "<internal>");
public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
private int line;
private int start;
private String filename;
private Filename filename;
public int line() { return line; }
public int start() { return start; }
public String filename() { return filename; }
public Filename filename() { return filename; }
@Override
public String toString() {
return filename + ":" + line + ":" + start;
return filename.toString() + ":" + line + ":" + start;
}
public Location add(int n, boolean clone) {
@@ -55,9 +55,39 @@ public class Location {
return true;
}
public Location(int line, int start, String filename) {
@Override
public int compareTo(Location other) {
int a = filename.toString().compareTo(other.filename.toString());
int b = Integer.compare(line, other.line);
int c = Integer.compare(start, other.start);
if (a != 0) return a;
if (b != 0) return b;
return c;
}
public Location(int line, int start, Filename filename) {
this.line = line;
this.start = start;
this.filename = filename;
}
public static Location parse(String raw) {
int i0 = -1, i1 = -1;
for (var i = raw.length() - 1; i >= 0; i--) {
if (raw.charAt(i) == ':') {
if (i1 == -1) i1 = i;
else if (i0 == -1) {
i0 = i;
break;
}
}
}
return new Location(
Integer.parseInt(raw.substring(i0 + 1, i1)),
Integer.parseInt(raw.substring(i1 + 1)),
Filename.parse(raw.substring(0, i0))
);
}
}

View 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;
}
}

View File

@@ -0,0 +1,33 @@
package me.topchetoeu.jscript.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
public class Reading {
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static synchronized String readline() throws IOException {
return reader.readLine();
}
/**
* Reads the given stream to a string
* @param in
* @return
*/
public static String streamToString(InputStream in) {
try {
return new String(in.readAllBytes());
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/" + name);
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));
}
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.common;
public interface ResultRunnable<T> {
T run();
}

View File

@@ -0,0 +1,95 @@
package me.topchetoeu.jscript.common;
import java.util.ArrayList;
import java.util.List;
public class VLQ {
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private static long[] toArray(List<Long> list) {
var arr = new long[list.size()];
for (var i = 0; i < list.size(); i++) arr[i] = list.get(i);
return arr;
}
public static String encode(long... arr) {
var raw = new StringBuilder();
for (var data : arr) {
var b = data < 0 ? 1 : 0;
data = Math.abs(data);
b |= (int)(data & 0b1111) << 1;
data >>= 4;
b |= data > 0 ? 0x20 : 0;;
raw.append(ALPHABET.charAt(b));
while (data > 0) {
b = (int)(data & 0b11111);
data >>= 5;
b |= data > 0 ? 0x20 : 0;
raw.append(ALPHABET.charAt(b));
}
}
return raw.toString();
}
public static long[] decode(String val) {
if (val.length() == 0) return new long[0];
var list = new ArrayList<Long>();
for (var i = 0; i < val.length();) {
var sign = 1;
var curr = ALPHABET.indexOf(val.charAt(i++));
var cont = (curr & 0x20) == 0x20;
if ((curr & 1) == 1) sign = -1;
long res = (curr & 0b11110) >> 1;
var n = 4;
for (; i < val.length() && cont;) {
curr = ALPHABET.indexOf(val.charAt(i++));
cont = (curr & 0x20) == 0x20;
res |= (curr & 0b11111) << n;
n += 5;
if (!cont) break;
}
list.add(res * sign);
}
return toArray(list);
}
public static String encodeMapping(long[][][] arr) {
var res = new StringBuilder();
var semicolon = false;
for (var line : arr) {
var comma = false;
if (semicolon) res.append(";");
semicolon = true;
for (var el : line) {
if (comma) res.append(",");
comma = true;
res.append(encode(el));
}
}
return res.toString();
}
public static long[][][] decodeMapping(String val) {
var lines = new ArrayList<long[][]>();
for (var line : val.split(";", -1)) {
var elements = new ArrayList<long[]>();
for (var el : line.split(",", -1)) {
elements.add(decode(el));
}
lines.add(elements.toArray(long[][]::new));
}
return lines.toArray(long[][][]::new);
}
}

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript.common.events;
public interface Awaitable<T> {
public static interface ResultHandler<T> {
public void onResult(T data);
}
public static interface ErrorHandler {
public void onError(RuntimeException error);
}
T await();
default void handle(ResultHandler<T> onResult, ErrorHandler onError) {
var thread = new Thread(() -> {
try {
onResult.onResult(await());
}
catch (RuntimeException e) {
onError.onError(e);
}
}, "Awaiter");
thread.start();
}
default void handle(ResultHandler<T> onResult) {
handle(onResult, err -> {});
}
}

View File

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

View File

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

View File

@@ -1,154 +1,243 @@
package me.topchetoeu.jscript.json;
import java.util.List;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.Operator;
import me.topchetoeu.jscript.parsing.ParseRes;
import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.parsing.Token;
public class JSON {
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}
public static ParseRes<String> parseString(String filename, List<Token> tokens, int i) {
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Double> parseNumber(String filename, List<Token> tokens, int i) {
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Boolean> parseBool(String filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed();
else if (id.result.equals("true")) return ParseRes.res(true, 1);
else if (id.result.equals("false")) return ParseRes.res(false, 1);
else return ParseRes.failed();
}
public static ParseRes<?> parseValue(String filename, List<Token> tokens, int i) {
return ParseRes.any(
parseString(filename, tokens, i),
parseNumber(filename, tokens, i),
parseBool(filename, tokens, i),
parseMap(filename, tokens, i),
parseList(filename, tokens, i)
);
}
public static ParseRes<JSONMap> parseMap(String filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
var values = new JSONMap();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
var name = ParseRes.any(
parseIdentifier(tokens, i + n),
parseString(filename, tokens, i + n),
parseNumber(filename, tokens, i + n)
);
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
else n += name.n;
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
}
n++;
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.put(name.result.toString(), JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static ParseRes<JSONList> parseList(String filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
var values = new JSONList();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.add(JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static JSONElement parse(String filename, String raw) {
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
else return JSONElement.of(res.result);
}
public static String stringify(JSONElement el) {
if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(el.list().get(i)));
}
res.append("]");
return res.toString();
}
if (el.isMap()) {
var res = new StringBuilder().append("{");
var entries = el.map().entrySet().stream().collect(Collectors.toList());
for (int i = 0; i < entries.size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
res.append(":");
res.append(stringify(entries.get(i).getValue()));
}
res.append("}");
return res.toString();
}
return null;
}
public static String stringify(JSONMap map) {
return stringify(JSONElement.of(map));
}
public static String stringify(JSONList list) {
return stringify(JSONElement.of(list));
}
}
package me.topchetoeu.jscript.common.json;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
import me.topchetoeu.jscript.core.parsing.Operator;
import me.topchetoeu.jscript.core.parsing.ParseRes;
import me.topchetoeu.jscript.core.parsing.Parsing;
import me.topchetoeu.jscript.core.parsing.Token;
public class JSON {
public static Object toJs(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : Values.getMembers(ctx, val, false, false)) {
var jsonEl = fromJs(ctx, Values.getMember(ctx, val, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
public static JSONElement fromJs(Context ctx, Object val) {
return fromJs(ctx, val, new HashSet<>());
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
if (minus) i++;
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
else return res.transform();
}
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed();
else if (id.result.equals("true")) return ParseRes.res(true, 1);
else if (id.result.equals("false")) return ParseRes.res(false, 1);
else return ParseRes.failed();
}
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
return ParseRes.any(
parseString(filename, tokens, i),
parseNumber(filename, tokens, i),
parseBool(filename, tokens, i),
parseMap(filename, tokens, i),
parseList(filename, tokens, i)
);
}
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
var values = new JSONMap();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
var name = ParseRes.any(
parseIdentifier(tokens, i + n),
parseString(filename, tokens, i + n),
parseNumber(filename, tokens, i + n)
);
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
else n += name.n;
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
}
n++;
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.put(name.result.toString(), JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
var values = new JSONList();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.add(JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
else return JSONElement.of(res.result);
}
public static String stringify(JSONElement el) {
if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(el.list().get(i)));
}
res.append("]");
return res.toString();
}
if (el.isMap()) {
var res = new StringBuilder().append("{");
var entries = el.map().entrySet().stream().collect(Collectors.toList());
for (int i = 0; i < entries.size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
res.append(":");
res.append(stringify(entries.get(i).getValue()));
}
res.append("}");
return res.toString();
}
return null;
}
public static String stringify(JSONMap map) {
return stringify(JSONElement.of(map));
}
public static String stringify(JSONList list) {
return stringify(JSONElement.of(list));
}
}

View File

@@ -1,76 +1,88 @@
package me.topchetoeu.jscript.json;
public class JSONElement {
public static enum Type {
STRING,
NUMBER,
BOOLEAN,
NULL,
LIST,
MAP,
}
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
public static JSONElement map(JSONMap val) {
return new JSONElement(Type.MAP, val);
}
public static JSONElement list(JSONList val) {
return new JSONElement(Type.LIST, val);
}
public static JSONElement string(String val) {
return new JSONElement(Type.STRING, val);
}
public static JSONElement number(double val) {
return new JSONElement(Type.NUMBER, val);
}
public static JSONElement bool(boolean val) {
return new JSONElement(Type.BOOLEAN, val);
}
public static JSONElement of(Object val) {
if (val instanceof JSONMap) return map((JSONMap)val);
else if (val instanceof JSONList) return list((JSONList)val);
else if (val instanceof String) return string((String)val);
else if (val instanceof Boolean) return bool((Boolean)val);
else if (val instanceof Number) return number(((Number)val).doubleValue());
else if (val == null) return NULL;
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
}
public final Type type;
private final Object value;
public boolean isMap() { return type == Type.MAP; }
public boolean isList() { return type == Type.LIST; }
public boolean isString() { return type == Type.STRING; }
public boolean isNumber() { return type == Type.NUMBER; }
public boolean isBoolean() { return type == Type.BOOLEAN; }
public boolean isNull() { return type == Type.NULL; }
public JSONMap map() {
if (!isMap()) throw new IllegalStateException("Element is not a map.");
return (JSONMap)value;
}
public JSONList list() {
if (!isList()) throw new IllegalStateException("Element is not a map.");
return (JSONList)value;
}
public String string() {
if (!isString()) throw new IllegalStateException("Element is not a string.");
return (String)value;
}
public double number() {
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
return (double)value;
}
public boolean bool() {
if (!isNumber()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
private JSONElement(Type type, Object val) {
this.type = type;
this.value = val;
}
}
package me.topchetoeu.jscript.common.json;
public class JSONElement {
public static enum Type {
STRING,
NUMBER,
BOOLEAN,
NULL,
LIST,
MAP,
}
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
public static JSONElement map(JSONMap val) {
return new JSONElement(Type.MAP, val);
}
public static JSONElement list(JSONList val) {
return new JSONElement(Type.LIST, val);
}
public static JSONElement string(String val) {
return new JSONElement(Type.STRING, val);
}
public static JSONElement number(double val) {
return new JSONElement(Type.NUMBER, val);
}
public static JSONElement bool(boolean val) {
return new JSONElement(Type.BOOLEAN, val);
}
public static JSONElement of(Object val) {
if (val instanceof JSONMap) return map((JSONMap)val);
else if (val instanceof JSONList) return list((JSONList)val);
else if (val instanceof String) return string((String)val);
else if (val instanceof Boolean) return bool((Boolean)val);
else if (val instanceof Number) return number(((Number)val).doubleValue());
else if (val == null) return NULL;
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
}
public final Type type;
private final Object value;
public boolean isMap() { return type == Type.MAP; }
public boolean isList() { return type == Type.LIST; }
public boolean isString() { return type == Type.STRING; }
public boolean isNumber() { return type == Type.NUMBER; }
public boolean isBoolean() { return type == Type.BOOLEAN; }
public boolean isNull() { return type == Type.NULL; }
public JSONMap map() {
if (!isMap()) throw new IllegalStateException("Element is not a map.");
return (JSONMap)value;
}
public JSONList list() {
if (!isList()) throw new IllegalStateException("Element is not a map.");
return (JSONList)value;
}
public String string() {
if (!isString()) throw new IllegalStateException("Element is not a string.");
return (String)value;
}
public double number() {
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
return (double)value;
}
public boolean bool() {
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
@Override
public String toString() {
if (isMap()) return "{...}";
if (isList()) return "[...]";
if (isString()) return (String)value;
if (isString()) return (String)value;
if (isNumber()) return (double)value + "";
if (isBoolean()) return (boolean)value + "";
if (isNull()) return "null";
return "";
}
private JSONElement(Type type, Object val) {
this.type = type;
this.value = val;
}
}

View File

@@ -1,21 +1,26 @@
package me.topchetoeu.jscript.json;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class JSONList extends ArrayList<JSONElement> {
public JSONList() {}
public JSONList(JSONElement ...els) {
super(List.of(els));
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
}
package me.topchetoeu.jscript.common.json;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class JSONList extends ArrayList<JSONElement> {
public JSONList() {}
public JSONList(JSONElement ...els) {
super(List.of(els));
}
public JSONList(Collection<JSONElement> els) {
super(els);
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
}

View File

@@ -1,150 +1,150 @@
package me.topchetoeu.jscript.json;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JSONMap implements Map<String, JSONElement> {
private Map<String, JSONElement> elements = new HashMap<>();
public JSONElement get(String path) {
var curr = this;
var segs = path.split("\\.");
var i = 0;
while (true) {
var tmp = curr.elements.get(segs[i++]);
if (i == segs.length) return tmp;
if (!tmp.isMap()) return null;
curr = tmp.map();
}
}
public boolean isMap(String path) {
var el = get(path);
return el != null && el.isMap();
}
public boolean isList(String path) {
var el = get(path);
return el != null && el.isList();
}
public boolean isString(String path) {
var el = get(path);
return el != null && el.isString();
}
public boolean isNumber(String path) {
var el = get(path);
return el != null && el.isNumber();
}
public boolean isBoolean(String path) {
var el = get(path);
return el != null && el.isBoolean();
}
public boolean isNull(String path) {
var el = get(path);
return el != null && el.isNull();
}
public boolean contains(String path) {
return get(path) != null;
}
public JSONMap map(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isMap()) return el.map();
return defaultVal;
}
public JSONList list(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isList()) return el.list();
return defaultVal;
}
public String string(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isString()) return el.string();
return defaultVal;
}
public double number(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isNumber()) return el.number();
return defaultVal;
}
public boolean bool(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isBoolean()) return el.bool();
return defaultVal;
}
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
@Override
public int size() { return elements.size(); }
@Override
public boolean isEmpty() { return elements.isEmpty(); }
@Override
public boolean containsKey(Object key) { return elements.containsKey(key); }
@Override
public boolean containsValue(Object value) { return elements.containsValue(value); }
@Override
public JSONElement get(Object key) { return elements.get(key); }
@Override
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
@Override
public JSONElement remove(Object key) { return elements.remove(key); }
@Override
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
@Override
public void clear() { elements.clear(); }
@Override
public Set<String> keySet() { return elements.keySet(); }
@Override
public Collection<JSONElement> values() { return elements.values(); }
@Override
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
public JSONMap() { }
public JSONMap(Map<String, JSONElement> els) {
this.elements = new HashMap<>(els);
}
}
package me.topchetoeu.jscript.common.json;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JSONMap implements Map<String, JSONElement> {
private Map<String, JSONElement> elements = new HashMap<>();
public JSONElement get(String path) {
var curr = this;
var segs = path.split("\\.");
var i = 0;
while (true) {
var tmp = curr.elements.get(segs[i++]);
if (i == segs.length) return tmp;
if (!tmp.isMap()) return null;
curr = tmp.map();
}
}
public boolean isMap(String path) {
var el = get(path);
return el != null && el.isMap();
}
public boolean isList(String path) {
var el = get(path);
return el != null && el.isList();
}
public boolean isString(String path) {
var el = get(path);
return el != null && el.isString();
}
public boolean isNumber(String path) {
var el = get(path);
return el != null && el.isNumber();
}
public boolean isBoolean(String path) {
var el = get(path);
return el != null && el.isBoolean();
}
public boolean isNull(String path) {
var el = get(path);
return el != null && el.isNull();
}
public boolean contains(String path) {
return get(path) != null;
}
public JSONMap map(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isMap()) return el.map();
return defaultVal;
}
public JSONList list(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isList()) return el.list();
return defaultVal;
}
public String string(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isString()) return el.string();
return defaultVal;
}
public double number(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isNumber()) return el.number();
return defaultVal;
}
public boolean bool(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isBoolean()) return el.bool();
return defaultVal;
}
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
@Override
public int size() { return elements.size(); }
@Override
public boolean isEmpty() { return elements.isEmpty(); }
@Override
public boolean containsKey(Object key) { return elements.containsKey(key); }
@Override
public boolean containsValue(Object value) { return elements.containsValue(value); }
@Override
public JSONElement get(Object key) { return elements.get(key); }
@Override
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
@Override
public JSONElement remove(Object key) { return elements.remove(key); }
@Override
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
@Override
public void clear() { elements.clear(); }
@Override
public Set<String> keySet() { return elements.keySet(); }
@Override
public Collection<JSONElement> values() { return elements.values(); }
@Override
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
public JSONMap() { }
public JSONMap(Map<String, JSONElement> els) {
this.elements = new HashMap<>(els);
}
}

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation;
package me.topchetoeu.jscript.core.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.engine.Operation;
public abstract class AssignableStatement extends Statement {
public abstract Statement toAssign(Statement val, Operation operation);

View File

@@ -0,0 +1,21 @@
package me.topchetoeu.jscript.core.compilation;
import me.topchetoeu.jscript.core.engine.values.Values;
public final class CalculateResult {
public final boolean exists;
public final Object value;
public final boolean isTruthy() {
return exists && Values.toBoolean(value);
}
public CalculateResult(Object value) {
this.exists = true;
this.value = value;
}
public CalculateResult() {
this.exists = false;
this.value = null;
}
}

View File

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

View File

@@ -0,0 +1,64 @@
package me.topchetoeu.jscript.core.compilation;
import java.util.List;
import java.util.Vector;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.core.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement {
public final Statement[] statements;
public final boolean separateFuncs;
public Location end;
@Override public boolean pure() {
for (var stm : statements) {
if (!stm.pure()) return false;
}
return true;
}
@Override
public void declare(ScopeRecord varsScope) {
for (var stm : statements) stm.declare(varsScope);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
List<Statement> statements = new Vector<Statement>();
if (separateFuncs) for (var stm : this.statements) {
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
stm.compile(target, scope, false);
}
else statements.add(stm);
}
else statements = List.of(this.statements);
var polluted = false;
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER);
else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
}
if (!polluted && pollute) {
target.add(Instruction.loadValue(loc(), null));
}
}
public CompoundStatement setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
super(loc);
this.separateFuncs = separateFuncs;
this.statements = statements;
}
}

View File

@@ -0,0 +1,29 @@
package me.topchetoeu.jscript.core.compilation;
public class FunctionBody {
public final Instruction[] instructions;
public final String[] captureNames, localNames;
public final int localsN, argsN;
public FunctionBody(int localsN, int argsN, Instruction[] instructions, String[] captureNames, String[] localNames) {
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions;
this.captureNames = captureNames;
this.localNames = localNames;
}
public FunctionBody(int localsN, int argsN, Instruction[] instructions) {
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions;
this.captureNames = new String[0];
this.localNames = new String[0];
}
public FunctionBody(Instruction... instructions) {
this.argsN = 0;
this.localsN = 2;
this.instructions = instructions;
this.captureNames = new String[0];
this.localNames = new String[0];
}
}

View File

@@ -0,0 +1,256 @@
package me.topchetoeu.jscript.core.compilation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.engine.Operation;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
RETURN,
THROW,
THROW_SYNTAX,
DELETE,
TRY_START,
TRY_END,
NOP,
CALL,
CALL_NEW,
JMP_IF,
JMP_IFN,
JMP,
LOAD_VALUE,
LOAD_VAR,
LOAD_MEMBER,
LOAD_VAL_MEMBER,
LOAD_GLOB,
LOAD_FUNC,
LOAD_ARR,
LOAD_OBJ,
STORE_SELF_FUNC,
LOAD_REGEX,
DUP,
STORE_VAR,
STORE_MEMBER,
DISCARD,
MAKE_VAR,
DEF_PROP,
KEYS,
TYPEOF,
OPERATION;
}
public static enum BreakpointType {
NONE,
STEP_OVER,
STEP_IN;
public boolean shouldStepIn() {
return this != NONE;
}
public boolean shouldStepOver() {
return this == STEP_OVER;
}
}
public final Type type;
public final Object[] params;
public Location location;
public BreakpointType breakpoint = BreakpointType.NONE;
public Instruction setDbgData(Instruction other) {
this.location = other.location;
this.breakpoint = other.breakpoint;
return this;
}
public Instruction locate(Location loc) {
this.location = loc;
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(int i) {
if (i >= params.length || i < 0) return null;
return (T)params[i];
}
@SuppressWarnings("unchecked")
public <T> T get(int i, T defaultVal) {
if (i >= params.length || i < 0) return defaultVal;
return (T)params[i];
}
public boolean match(Object ...args) {
if (args.length != params.length) return false;
for (int i = 0; i < args.length; i++) {
var a = params[i];
var b = args[i];
if (a == null || b == null) {
if (!(a == null && b == null)) return false;
}
if (!a.equals(b)) return false;
}
return true;
}
public boolean is(int i, Object arg) {
if (params.length <= i) return false;
return params[i].equals(arg);
}
private Instruction(Location location, Type type, Object ...params) {
this.location = location;
this.type = type;
this.params = params;
}
public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
}
public static Instruction tryEnd(Location loc) {
return new Instruction(loc, Type.TRY_END);
}
public static Instruction throwInstr(Location loc) {
return new Instruction(loc, Type.THROW);
}
public static Instruction throwSyntax(Location loc, SyntaxException err) {
return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage());
}
public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(loc, Type.THROW_SYNTAX, err);
}
public static Instruction delete(Location loc) {
return new Instruction(loc, Type.DELETE);
}
public static Instruction ret(Location loc) {
return new Instruction(loc, Type.RETURN);
}
public static Instruction debug(Location loc) {
return new Instruction(loc, Type.NOP, "debug");
}
public static Instruction nop(Location loc, Object ...params) {
for (var param : params) {
if (param instanceof String) continue;
if (param instanceof Boolean) continue;
if (param instanceof Double) continue;
if (param instanceof Integer) continue;
if (param == null) continue;
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
}
return new Instruction(loc, Type.NOP, params);
}
public static Instruction call(Location loc, int argn) {
return new Instruction(loc, Type.CALL, argn);
}
public static Instruction callNew(Location loc, int argn) {
return new Instruction(loc, Type.CALL_NEW, argn);
}
public static Instruction jmp(Location loc, int offset) {
return new Instruction(loc, Type.JMP, offset);
}
public static Instruction jmpIf(Location loc, int offset) {
return new Instruction(loc, Type.JMP_IF, offset);
}
public static Instruction jmpIfNot(Location loc, int offset) {
return new Instruction(loc, Type.JMP_IFN, offset);
}
public static Instruction loadValue(Location loc, Object val) {
return new Instruction(loc, Type.LOAD_VALUE, val);
}
public static Instruction makeVar(Location loc, String name) {
return new Instruction(loc, Type.MAKE_VAR, name);
}
public static Instruction loadVar(Location loc, Object i) {
return new Instruction(loc, Type.LOAD_VAR, i);
}
public static Instruction loadGlob(Location loc) {
return new Instruction(loc, Type.LOAD_GLOB);
}
public static Instruction loadMember(Location loc) {
return new Instruction(loc, Type.LOAD_MEMBER);
}
public static Instruction loadMember(Location loc, Object key) {
if (key instanceof Number) key = ((Number)key).doubleValue();
return new Instruction(loc, Type.LOAD_VAL_MEMBER, key);
}
public static Instruction loadRegex(Location loc, String pattern, String flags) {
return new Instruction(loc, Type.LOAD_REGEX, pattern, flags);
}
public static Instruction loadFunc(Location loc, long id, int[] captures) {
var args = new Object[1 + captures.length];
args[0] = id;
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
return new Instruction(loc, Type.LOAD_FUNC, args);
}
public static Instruction loadObj(Location loc) {
return new Instruction(loc, Type.LOAD_OBJ);
}
public static Instruction loadArr(Location loc, int count) {
return new Instruction(loc, Type.LOAD_ARR, count);
}
public static Instruction dup(Location loc) {
return new Instruction(loc, Type.DUP, 1);
}
public static Instruction dup(Location loc, int count) {
return new Instruction(loc, Type.DUP, count);
}
public static Instruction storeSelfFunc(Location loc, int i) {
return new Instruction(loc, Type.STORE_SELF_FUNC, i);
}
public static Instruction storeVar(Location loc, Object i) {
return new Instruction(loc, Type.STORE_VAR, i, false);
}
public static Instruction storeVar(Location loc, Object i, boolean keep) {
return new Instruction(loc, Type.STORE_VAR, i, keep);
}
public static Instruction storeMember(Location loc) {
return new Instruction(loc, Type.STORE_MEMBER, false);
}
public static Instruction storeMember(Location loc, boolean keep) {
return new Instruction(loc, Type.STORE_MEMBER, keep);
}
public static Instruction discard(Location loc) {
return new Instruction(loc, Type.DISCARD);
}
public static Instruction typeof(Location loc) {
return new Instruction(loc, Type.TYPEOF);
}
public static Instruction typeof(Location loc, Object varName) {
return new Instruction(loc, Type.TYPEOF, varName);
}
public static Instruction keys(Location loc, boolean forInFormat) {
return new Instruction(loc, Type.KEYS, forInFormat);
}
public static Instruction defProp(Location loc) {
return new Instruction(loc, Type.DEF_PROP);
}
public static Instruction operation(Location loc, Operation op) {
return new Instruction(loc, Type.OPERATION, op);
}
@Override
public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
}

View File

@@ -0,0 +1,32 @@
package me.topchetoeu.jscript.core.compilation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public abstract class Statement {
private Location _loc;
public boolean pure() { return false; }
public void declare(ScopeRecord varsScope) { }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
int start = target.size();
compile(target, scope, pollute);
if (target.size() != start) {
target.get(start).locate(loc());
target.setDebug(start, type);
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.NONE);
}
public Location loc() { return _loc; }
public void setLoc(Location loc) { _loc = loc; }
protected Statement(Location loc) {
this._loc = loc;
}
}

View File

@@ -0,0 +1,18 @@
package me.topchetoeu.jscript.core.compilation;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
public class ThrowSyntaxStatement extends Statement {
public final String name;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.throwSyntax(loc(), name));
}
public ThrowSyntaxStatement(SyntaxException e) {
super(e.loc);
this.name = e.msg;
}
}

View File

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

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class BreakStatement extends Statement {
public final String label;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "break", label));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public BreakStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ContinueStatement extends Statement {
public final String label;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public ContinueStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class DebugStatement extends Statement {
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.debug(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public DebugStatement(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class DeleteStatement extends Statement {
public final Statement key;
public final Statement value;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true);
key.compile(target, scope, true);
target.add(Instruction.delete(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), true));
}
public DeleteStatement(Location loc, Statement key, Statement value) {
super(loc);
this.key = key;
this.value = value;
}
}

View File

@@ -0,0 +1,37 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class DoWhileStatement extends Statement {
public final Statement condition, body;
public final String label;
@Override
public void declare(ScopeRecord globScope) {
body.declare(globScope);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size();
body.compile(target, scope, false, BreakpointType.STEP_OVER);
int mid = target.size();
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(loc(), start - end));
}
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
}

View File

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

View File

@@ -0,0 +1,47 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body;
public final String label;
@Override
public void declare(ScopeRecord globScope) {
declaration.declare(globScope);
body.declare(globScope);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
int start = target.size();
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int mid = target.size();
target.add(Instruction.nop(null));
body.compile(target, scope, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size();
assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
super(loc);
this.label = label;
this.declaration = declaration;
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
}

View File

@@ -0,0 +1,52 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class IfStatement extends Statement {
public final Statement condition, body, elseBody;
@Override
public void declare(ScopeRecord globScope) {
body.declare(globScope);
if (elseBody != null) elseBody.declare(globScope);
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
condition.compile(target, scope, true, breakpoint);
if (elseBody == null) {
int i = target.size();
target.add(Instruction.nop(null));
body.compile(target, scope, pollute, breakpoint);
int endI = target.size();
target.set(i, Instruction.jmpIfNot(loc(), endI - i));
}
else {
int start = target.size();
target.add(Instruction.nop(null));
body.compile(target, scope, pollute, breakpoint);
target.add(Instruction.nop(null));
int mid = target.size();
elseBody.compile(target, scope, pollute, breakpoint);
int end = target.size();
target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
}
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.STEP_IN);
}
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
super(loc);
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
}
}

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ReturnStatement extends Statement {
public final Statement value;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value == null) target.add(Instruction.loadValue(loc(), null));
else value.compile(target, scope, true);
target.add(Instruction.ret(loc()));
}
public ReturnStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

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

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.core.compilation.control;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ThrowStatement extends Statement {
public final Statement value;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true);
target.add(Instruction.throwInstr(loc()));
}
public ThrowStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ArrayStatement extends Statement {
public final Statement[] statements;
@Override public boolean pure() {
for (var stm : statements) {
if (!stm.pure()) return false;
}
return true;
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadArr(loc(), statements.length));
for (var i = 0; i < statements.length; i++) {
var el = statements[i];
if (el != null) {
target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), i));
el.compile(target, scope, true);
target.add(Instruction.storeMember(loc()));
}
}
if (!pollute) target.add(Instruction.discard(loc()));
}
public ArrayStatement(Location loc, Statement[] statements) {
super(loc);
this.statements = statements;
}
}

View File

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

View File

@@ -0,0 +1,32 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.AssignableStatement;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.Operation;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement {
public final AssignableStatement value;
public final double addAmount;
public final boolean postfix;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
if (!pollute) target.add(Instruction.discard(loc()));
else if (postfix) {
target.add(Instruction.loadValue(loc(), addAmount));
target.add(Instruction.operation(loc(), Operation.SUBTRACT));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
super(loc);
this.value = value;
this.addAmount = addAmount;
this.postfix = postfix;
}
}

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class ConstantStatement extends Statement {
public final Object value;
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadValue(loc(), value));
}
public ConstantStatement(Location loc, Object val) {
super(loc);
this.value = val;
}
}

View File

@@ -0,0 +1,24 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class DiscardStatement extends Statement {
public final Statement value;
@Override public boolean pure() { return value.pure(); }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false);
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public DiscardStatement(Location loc, Statement val) {
super(loc);
this.value = val;
}
}

View File

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

View File

@@ -0,0 +1,20 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class GlobalThisStatement extends Statement {
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadGlob(loc()));
}
public GlobalThisStatement(Location loc) {
super(loc);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.core.engine.values.Values;
public class LazyAndStatement extends Statement {
public final Statement first, second;
@Override public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) {
first.compile(target, scope, pollute);
}
else second.compile(target, scope, pollute);
return;
}
first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup(loc()));
int start = target.size();
target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
}
public LazyAndStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@@ -0,0 +1,39 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.core.engine.values.Values;
public class LazyOrStatement extends Statement {
public final Statement first, second;
@Override public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) {
second.compile(target, scope, pollute);
}
else first.compile(target, scope, pollute);
return;
}
first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup(loc()));
int start = target.size();
target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(loc(), target.size() - start));
}
public LazyOrStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

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

View File

@@ -0,0 +1,37 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.Operation;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) {
arg.compile(target, scope, true);
}
if (pollute) target.add(Instruction.operation(loc(), operation));
else target.add(Instruction.discard(loc()));
}
public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
}

View File

@@ -0,0 +1,26 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class RegexStatement extends Statement {
public final String pattern, flags;
// Not really pure, since a function is called, but can be ignored.
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadRegex(loc(), pattern, flags));
if (!pollute) target.add(Instruction.discard(loc()));
}
public RegexStatement(Location loc, String pattern, String flags) {
super(loc);
this.pattern = pattern;
this.flags = flags;
}
}

View File

@@ -0,0 +1,33 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class TypeofStatement extends Statement {
public final Statement value;
// Not really pure, since a variable from the global scope could be accessed,
// which could lead to code execution, that would get omitted
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) {
target.add(Instruction.typeof(loc(), (String)i));
return;
}
}
value.compile(target, scope, pollute);
target.add(Instruction.typeof(loc()));
}
public TypeofStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

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

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class VariableIndexStatement extends Statement {
public final int index;
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadVar(loc(), index));
}
public VariableIndexStatement(Location loc, int i) {
super(loc);
this.index = i;
}
}

View File

@@ -0,0 +1,32 @@
package me.topchetoeu.jscript.core.compilation.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.AssignableStatement;
import me.topchetoeu.jscript.core.compilation.CompileTarget;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.compilation.Statement;
import me.topchetoeu.jscript.core.engine.Operation;
import me.topchetoeu.jscript.core.engine.scope.ScopeRecord;
public class VariableStatement extends AssignableStatement {
public final String name;
@Override public boolean pure() { return false; }
@Override
public Statement toAssign(Statement val, Operation operation) {
return new VariableAssignStatement(loc(), name, val, operation);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name);
target.add(Instruction.loadVar(loc(), i));
if (!pollute) target.add(Instruction.discard(loc()));
}
public VariableStatement(Location loc, String name) {
super(loc);
this.name = name;
}
}

View File

@@ -0,0 +1,166 @@
package me.topchetoeu.jscript.core.engine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.engine.debug.DebugContext;
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
import me.topchetoeu.jscript.core.engine.values.Symbol;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.lib.EnvironmentLib;
import me.topchetoeu.jscript.utils.mapping.SourceMap;
public class Context implements Extensions {
public static final Context NULL = new Context(null);
public final Context parent;
public final Environment environment;
public final CodeFrame frame;
public final Engine engine;
public final int stackSize;
@Override public <T> void add(Symbol key, T obj) {
if (environment != null) environment.add(key, obj);
else if (engine != null) engine.add(key, obj);
}
@Override public <T> T get(Symbol key) {
if (environment != null && environment.has(key)) return environment.get(key);
else if (engine != null && engine.has(key)) return engine.get(key);
return null;
}
@Override public boolean has(Symbol key) {
return
environment != null && environment.has(key) ||
engine != null && engine.has(key);
}
@Override public boolean remove(Symbol key) {
var res = false;
if (environment != null) res |= environment.remove(key);
else if (engine != null) res |= engine.remove(key);
return res;
}
@Override public Iterable<Symbol> keys() {
if (engine == null && environment == null) return List.of();
if (engine == null) return environment.keys();
if (environment == null) return engine.keys();
return () -> Stream.concat(
StreamSupport.stream(engine.keys().spliterator(), false),
StreamSupport.stream(environment.keys().spliterator(), false)
).distinct().iterator();
}
public FunctionValue compile(Filename filename, String raw) {
var env = environment;
var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), new EnvironmentLib(env));
var function = (FunctionValue)Values.getMember(this, result, "function");
if (!DebugContext.enabled(this)) return function;
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
var breakpoints = new TreeSet<>(
Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
.map(v -> Location.parse(Values.toString(this, v)))
.collect(Collectors.toList())
);
var maps = new SourceMap[rawMapChain.length];
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
var map = SourceMap.chain(maps);
if (map != null) {
var newBreakpoints = new TreeSet<Location>();
for (var bp : breakpoints) {
bp = map.toCompiled(bp);
if (bp != null) newBreakpoints.add(bp);
}
breakpoints = newBreakpoints;
}
DebugContext.get(this).onSource(filename, raw, breakpoints, map);
return function;
}
public Context pushFrame(CodeFrame frame) {
var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1);
return res;
}
public Iterable<CodeFrame> frames() {
var self = this;
return () -> new Iterator<CodeFrame>() {
private Context curr = self;
private void update() {
while (curr != null && curr.frame == null) curr = curr.parent;
}
@Override public boolean hasNext() {
update();
return curr != null;
}
@Override public CodeFrame next() {
update();
var res = curr.frame;
curr = curr.parent;
return res;
}
};
}
public List<String> stackTrace() {
var res = new ArrayList<String>();
for (var el : frames()) {
var name = el.function.name;
Location loc = null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!trace.equals("")) res.add(trace);
}
return res;
}
private Context(Context parent, Environment environment, CodeFrame frame, Engine engine, int stackSize) {
this.parent = parent;
this.environment = environment;
this.frame = frame;
this.engine = engine;
this.stackSize = stackSize;
if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) {
throw EngineException.ofRange("Stack overflow!");
}
}
public Context(Engine engine) {
this(null, null, null, engine, 0);
}
public Context(Engine engine, Environment env) {
this(null, env, null, engine, 0);
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.core.engine;
import java.util.HashMap;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.events.Awaitable;
import me.topchetoeu.jscript.core.compilation.FunctionBody;
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
import me.topchetoeu.jscript.core.engine.values.Symbol;
public class Engine extends EventLoop implements Extensions {
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private final Environment env = new Environment();
@Override
public <T> void add(Symbol key, T obj) {
this.env.add(key, obj);
}
@Override
public <T> T get(Symbol key) {
return this.env.get(key);
}
@Override
public boolean has(Symbol key) {
return this.env.has(key);
}
@Override
public boolean remove(Symbol key) {
return this.env.remove(key);
}
@Override
public Iterable<Symbol> keys() {
return env.keys();
}
public Engine copy() {
var res = new Engine();
res.env.addAll(env);
return res;
}
public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
return pushMsg(() -> {
return func.call(new Context(this, env), thisArg, args);
}, micro);
}
public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(() -> {
var ctx = new Context(this, env);
return ctx.compile(filename, raw).call(new Context(this, env), thisArg, args);
}, micro);
}
public Engine() {
}
}

View File

@@ -0,0 +1,128 @@
package me.topchetoeu.jscript.core.engine;
import java.util.HashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.engine.debug.DebugContext;
import me.topchetoeu.jscript.core.engine.scope.GlobalScope;
import me.topchetoeu.jscript.core.engine.values.ArrayValue;
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
import me.topchetoeu.jscript.core.engine.values.NativeFunction;
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
import me.topchetoeu.jscript.core.engine.values.Symbol;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.parsing.Parsing;
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
@SuppressWarnings("unchecked")
public class Environment implements Extensions {
public static final HashMap<String, Symbol> symbols = new HashMap<>();
public static final Symbol WRAPPERS = Symbol.get("Environment.wrappers");
public static final Symbol COMPILE_FUNC = Symbol.get("Environment.compile");
public static final Symbol REGEX_CONSTR = Symbol.get("Environment.regexConstructor");
public static final Symbol STACK = Symbol.get("Environment.stack");
public static final Symbol MAX_STACK_COUNT = Symbol.get("Environment.maxStackCount");
public static final Symbol HIDE_STACK = Symbol.get("Environment.hideStack");
public static final Symbol OBJECT_PROTO = Symbol.get("Environment.objectPrototype");
public static final Symbol FUNCTION_PROTO = Symbol.get("Environment.functionPrototype");
public static final Symbol ARRAY_PROTO = Symbol.get("Environment.arrayPrototype");
public static final Symbol BOOL_PROTO = Symbol.get("Environment.boolPrototype");
public static final Symbol NUMBER_PROTO = Symbol.get("Environment.numberPrototype");
public static final Symbol STRING_PROTO = Symbol.get("Environment.stringPrototype");
public static final Symbol SYMBOL_PROTO = Symbol.get("Environment.symbolPrototype");
public static final Symbol ERROR_PROTO = Symbol.get("Environment.errorPrototype");
public static final Symbol SYNTAX_ERR_PROTO = Symbol.get("Environment.syntaxErrorPrototype");
public static final Symbol TYPE_ERR_PROTO = Symbol.get("Environment.typeErrorPrototype");
public static final Symbol RANGE_ERR_PROTO = Symbol.get("Environment.rangeErrorPrototype");
private HashMap<Symbol, Object> data = new HashMap<>();
public GlobalScope global;
public WrapperProvider wrappers;
@Override public <T> void add(Symbol key, T obj) {
data.put(key, obj);
}
@Override public <T> T get(Symbol key) {
return (T)data.get(key);
}
@Override public boolean remove(Symbol key) {
if (data.containsKey(key)) {
data.remove(key);
return true;
}
return false;
}
@Override public boolean has(Symbol key) {
return data.containsKey(key);
}
@Override public Iterable<Symbol> keys() {
return data.keySet();
}
public static FunctionValue compileFunc(Extensions ext) {
return ext.init(COMPILE_FUNC, new NativeFunction("compile", args -> {
var source = args.getString(0);
var filename = args.getString(1);
var env = Values.wrapper(Values.getMember(args.ctx, args.get(2), Symbol.get("env")), Environment.class);
var isDebug = DebugContext.enabled(args.ctx);
var res = new ObjectValue();
var target = Parsing.compile(env, Filename.parse(filename), source);
Engine.functions.putAll(target.functions);
Engine.functions.remove(0l);
res.defineProperty(args.ctx, "function", target.func(env));
res.defineProperty(args.ctx, "mapChain", new ArrayValue());
if (isDebug) res.defineProperty(
args.ctx, "breakpoints",
ArrayValue.of(args.ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))
);
return res;
}));
}
public static FunctionValue regexConstructor(Extensions ext) {
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", args -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx.environment, args.ctx.engine);
}));
}
public Environment copy() {
var res = new Environment(null, global);
res.wrappers = wrappers.fork(res);
res.global = global;
res.data.putAll(data);
return res;
}
public Environment child() {
var res = copy();
res.global = res.global.globalChild();
return res;
}
public Context context(Engine engine) {
return new Context(engine, this);
}
public Environment(WrapperProvider nativeConverter, GlobalScope global) {
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope();
this.wrappers = nativeConverter;
this.global = global;
}
public Environment() {
this(null, null);
}
}

View File

@@ -0,0 +1,81 @@
package me.topchetoeu.jscript.core.engine;
import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.common.events.Awaitable;
import me.topchetoeu.jscript.common.events.DataNotifier;
import me.topchetoeu.jscript.core.exceptions.InterruptException;
public class EventLoop {
private static class Task implements Comparable<Task> {
public final ResultRunnable<?> runnable;
public final DataNotifier<Object> notifier = new DataNotifier<>();
public final boolean micro;
public Task(ResultRunnable<?> runnable, boolean micro) {
this.runnable = runnable;
this.micro = micro;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
}
}
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
private Thread thread;
@SuppressWarnings("unchecked")
public <T> Awaitable<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
var msg = new Task(runnable, micro);
tasks.add(msg);
return (Awaitable<T>)msg.notifier;
}
public Awaitable<Object> pushMsg(Runnable runnable, boolean micro) {
return pushMsg(() -> { runnable.run(); return null; }, micro);
}
public void run(boolean untilEmpty) {
while (!untilEmpty || !tasks.isEmpty()) {
try {
var task = tasks.take();
try {
task.notifier.next(task.runnable.run());
}
catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e);
}
}
catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
break;
}
}
}
public Thread thread() {
return thread;
}
public Thread start() {
if (thread == null) {
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
thread.start();
}
return thread;
}
public void stop() {
if (thread != null) thread.interrupt();
thread = null;
}
public boolean inLoopThread() {
return Thread.currentThread() == thread;
}
public boolean isRunning() {
return this.thread != null;
}
}

View File

@@ -0,0 +1,34 @@
package me.topchetoeu.jscript.core.engine;
import me.topchetoeu.jscript.core.engine.values.Symbol;
public interface Extensions {
<T> T get(Symbol key);
<T> void add(Symbol key, T obj);
Iterable<Symbol> keys();
boolean has(Symbol key);
boolean remove(Symbol key);
default boolean hasNotNull(Symbol key) {
return has(key) && get(key) != null;
}
default <T> T get(Symbol key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
}
default <T> T init(Symbol key, T val) {
if (has(key)) return get(key);
else {
add(key, val);
return val;
}
}
default void addAll(Extensions source) {
for (var key : source.keys()) {
add(key, source.get(key));
}
}
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.engine;
package me.topchetoeu.jscript.core.engine;
public enum Operation {
INSTANCEOF(2, false),

View File

@@ -0,0 +1,12 @@
package me.topchetoeu.jscript.core.engine;
import me.topchetoeu.jscript.core.engine.values.FunctionValue;
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
public interface WrapperProvider {
public ObjectValue getProto(Class<?> obj);
public ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj);
public WrapperProvider fork(Environment env);
}

View File

@@ -0,0 +1,100 @@
package me.topchetoeu.jscript.core.engine.debug;
import java.util.HashMap;
import java.util.TreeSet;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.Extensions;
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
import me.topchetoeu.jscript.core.engine.values.Symbol;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.utils.mapping.SourceMap;
public class DebugContext implements DebugController {
public static final Symbol ENV_KEY = Symbol.get("Engine.debug");
public static final Symbol IGNORE = Symbol.get("Engine.ignoreDebug");
private HashMap<Filename, String> sources;
private HashMap<Filename, TreeSet<Location>> bpts;
private HashMap<Filename, SourceMap> maps;
private DebugController debugger;
public boolean attachDebugger(DebugController debugger) {
if (this.debugger != null) return false;
if (sources != null) {
for (var source : sources.entrySet()) debugger.onSource(
source.getKey(), source.getValue(),
bpts.get(source.getKey()),
maps.get(source.getKey())
);
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger() {
this.debugger = null;
return true;
}
public DebugController debugger() {
if (debugger == null) return DebugController.empty();
else return debugger;
}
@Override public void onFramePop(Context ctx, CodeFrame frame) {
if (debugger != null) debugger.onFramePop(ctx, frame);
}
@Override public void onFramePush(Context ctx, CodeFrame frame) {
if (debugger != null) debugger.onFramePush(ctx, frame);
}
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
if (sources != null) sources.put(filename, source);
if (bpts != null) bpts.put(filename, breakpoints);
if (maps != null) maps.put(filename, map);
}
public Location mapToCompiled(Location location) {
if (maps == null) return location;
var map = maps.get(location.filename());
if (map == null) return location;
return map.toCompiled(location);
}
public Location mapToOriginal(Location location) {
if (maps == null) return location;
var map = maps.get(location.filename());
if (map == null) return location;
return map.toOriginal(location);
}
private DebugContext(boolean enabled) {
if (enabled) {
sources = new HashMap<>();
bpts = new HashMap<>();
maps = new HashMap<>();
}
}
public DebugContext() {
this(true);
}
public static boolean enabled(Extensions exts) {
return exts.hasNotNull(ENV_KEY) && !exts.has(IGNORE);
}
public static DebugContext get(Extensions exts) {
if (enabled(exts)) return exts.get(ENV_KEY);
else return new DebugContext(false);
}
}

View File

@@ -0,0 +1,62 @@
package me.topchetoeu.jscript.core.engine.debug;
import java.util.TreeSet;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.utils.mapping.SourceMap;
public interface DebugController {
/**
* Called when a script has been loaded
* @param filename The name of the source
* @param source The name of the source
* @param breakpoints A set of all the breakpointable locations in this source
* @param map The source map associated with this file. null if this source map isn't mapped
*/
void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
/**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @param loc The most recent location the code frame has been at
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
* @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
* @return Whether or not the frame should restart
*/
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
/**
* Called immediatly before a frame has been pushed on the frame stack.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was pushed
*/
void onFramePush(Context ctx, CodeFrame frame);
/**
* Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was popped out
*/
void onFramePop(Context ctx, CodeFrame frame);
public static DebugController empty() {
return new DebugController () {
@Override public void onFramePop(Context ctx, CodeFrame frame) { }
@Override public void onFramePush(Context ctx, CodeFrame frame) { }
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) { }
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord {
public Object getKey(String name);
public Object define(String name);
public ScopeRecord parent();
public LocalScopeRecord child();
}
package me.topchetoeu.jscript.core.engine.scope;
public interface ScopeRecord {
public Object getKey(String name);
public Object define(String name);
public LocalScopeRecord child();
}

View File

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

View File

@@ -0,0 +1,9 @@
package me.topchetoeu.jscript.core.engine.scope;
import me.topchetoeu.jscript.core.engine.Context;
public interface Variable {
Object get(Context ctx);
default boolean readonly() { return true; }
default void set(Context ctx, Object val) { }
}

View File

@@ -1,218 +1,222 @@
package me.topchetoeu.jscript.engine.values;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object();
private Object[] values;
private int size;
private void alloc(int index) {
if (index < values.length) return;
if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length);
values = arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
alloc(val);
size = val;
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == UNDEFINED) return null;
else return res;
}
public void set(Context ctx, int i, Object val) {
if (i < 0) return;
alloc(i);
val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED;
values[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < values.length && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Object[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) {
values[--size] = null;
}
}
}
public Object[] toArray() {
Object[] res = new Object[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart];
}
}
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once
for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else arr.set(ctx, i + destStart, values[i + sourceStart]);
}
}
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
set(ctx, i + destStart, arr[i + sourceStart]);
}
}
public void move(int srcI, int dstI, int n) {
alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Object> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2;
if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override
protected Object getField(Context ctx, Object key) throws InterruptedException {
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
return get((int)i);
}
}
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
set(ctx, (int)i, val);
return true;
}
}
return super.setField(ctx, key, val);
}
@Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
return has((int)i);
}
}
return super.hasField(ctx, key);
}
@Override
protected void deleteField(Context ctx, Object key) throws InterruptedException {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
remove((int)i);
return;
}
}
super.deleteField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
return res;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
values = new Object[16];
size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
}
public ArrayValue(Context ctx, Object ...values) {
this();
this.values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
}
public static ArrayValue of(Context ctx, Collection<Object> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new));
}
}
package me.topchetoeu.jscript.core.engine.values;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.core.engine.Context;
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object();
private Object[] values;
private int size;
private Object[] alloc(int index) {
index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length);
return arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
values = alloc(val);
size = val;
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == UNDEFINED) return null;
else return res;
}
public void set(Context ctx, int i, Object val) {
if (i < 0) return;
values = alloc(i);
val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED;
values[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < size && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Object[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) {
values[--size] = null;
}
}
}
public Object[] toArray() {
Object[] res = new Object[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart];
}
}
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once
if (destStart + count > arr.size) arr.size = destStart + count;
for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(ctx, i + destStart, values[i + sourceStart]);
}
}
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
set(ctx, i + destStart, arr[i + sourceStart]);
}
}
public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Object> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2;
if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override
protected Object getField(Context ctx, Object key) {
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
return get((int)i);
}
}
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
set(ctx, (int)i, val);
return true;
}
}
return super.setField(ctx, key, val);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
return has((int)i);
}
}
return super.hasField(ctx, key);
}
@Override
protected void deleteField(Context ctx, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
remove((int)i);
return;
}
}
super.deleteField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
return res;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
values = new Object[16];
size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
}
public ArrayValue(Context ctx, Object ...values) {
this();
this.values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
}
public static ArrayValue of(Context ctx, Collection<?> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new));
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.core.engine.values;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.compilation.FunctionBody;
import me.topchetoeu.jscript.core.compilation.Instruction;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.Environment;
import me.topchetoeu.jscript.core.engine.frame.CodeFrame;
import me.topchetoeu.jscript.core.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue {
public final int localsN;
public final Instruction[] body;
public final String[] captureNames, localNames;
public final ValueVariable[] captures;
public Environment environment;
public Location loc() {
for (var instr : body) {
if (instr.location != null) return instr.location;
}
return null;
}
public String readable() {
var loc = loc();
if (loc == null) return name;
else if (name.equals("")) return loc.toString();
else return name + "@" + loc;
}
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var frame = new CodeFrame(ctx, thisArg, args, this);
frame.onPush();
try {
while (true) {
var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null);
if (res != Values.NO_RETURN) return res;
}
}
finally {
frame.onPop();
}
}
public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
super(name, body.argsN);
this.captures = captures;
this.captureNames = body.captureNames;
this.localNames = body.localNames;
this.environment = environment;
this.localsN = body.localsN;
this.body = body.instructions;
}
}

View File

@@ -1,70 +1,70 @@
package me.topchetoeu.jscript.engine.values;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
public abstract class FunctionValue extends ObjectValue {
public String name = "";
public boolean special = false;
public int length;
@Override
public String toString() {
return "function(...) { ...}";
}
public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException;
public Object call(Context ctx) throws InterruptedException {
return call(ctx, null);
}
@Override
protected Object getField(Context ctx, Object key) throws InterruptedException {
if (key.equals("name")) return name;
if (key.equals("length")) return length;
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
if (key.equals("name")) name = Values.toString(ctx, val);
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val);
return true;
}
@Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
if (key.equals("name")) return true;
if (key.equals("length")) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
if (includeNonEnumerable) {
res.add("name");
res.add("length");
}
return res;
}
public FunctionValue(String name, int length) {
super(PlaceholderProto.FUNCTION);
if (name == null) name = "";
this.length = length;
this.name = name;
nonConfigurableSet.add("name");
nonEnumerableSet.add("name");
nonWritableSet.add("length");
nonConfigurableSet.add("length");
nonEnumerableSet.add("length");
var proto = new ObjectValue();
proto.defineProperty(null, "constructor", this, true, false, false);
this.defineProperty(null, "prototype", proto, true, false, false);
}
}
package me.topchetoeu.jscript.core.engine.values;
import java.util.List;
import me.topchetoeu.jscript.core.engine.Context;
public abstract class FunctionValue extends ObjectValue {
public String name = "";
public boolean special = false;
public int length;
@Override
public String toString() {
return String.format("function %s(...)", name);
}
public abstract Object call(Context ctx, Object thisArg, Object ...args);
public Object call(Context ctx) {
return call(ctx, null);
}
@Override
protected Object getField(Context ctx, Object key) {
if ("name".equals(key)) return name;
if ("length".equals(key)) return length;
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if ("name".equals(key)) name = Values.toString(ctx, val);
else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val);
return true;
}
@Override
protected boolean hasField(Context ctx, Object key) {
if ("name".equals(key)) return true;
if ("length".equals(key)) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
if (includeNonEnumerable) {
res.add("name");
res.add("length");
}
return res;
}
public FunctionValue(String name, int length) {
super(PlaceholderProto.FUNCTION);
if (name == null) name = "";
this.length = length;
this.name = name;
nonConfigurableSet.add("name");
nonEnumerableSet.add("name");
nonWritableSet.add("length");
nonConfigurableSet.add("length");
nonEnumerableSet.add("length");
var proto = new ObjectValue();
proto.defineProperty(null, "constructor", this, true, false, false);
this.defineProperty(null, "prototype", proto, true, false, false);
}
}

View File

@@ -1,25 +1,26 @@
package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.Context;
public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner {
Object run(Context ctx, Object thisArg, Object[] args) throws InterruptedException;
}
public final NativeFunctionRunner action;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
return action.run(ctx, thisArg, args);
}
public NativeFunction(String name, NativeFunctionRunner action) {
super(name, 0);
this.action = action;
}
public NativeFunction(NativeFunctionRunner action) {
super("", 0);
this.action = action;
}
}
package me.topchetoeu.jscript.core.engine.values;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.utils.interop.Arguments;
public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner {
Object run(Arguments args);
}
public final NativeFunctionRunner action;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
return action.run(new Arguments(ctx, thisArg, args));
}
public NativeFunction(String name, NativeFunctionRunner action) {
super(name, 0);
this.action = action;
}
public NativeFunction(NativeFunctionRunner action) {
super("", 0);
this.action = action;
}
}

View File

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

View File

@@ -1,347 +1,354 @@
package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.engine.Context;
public class ObjectValue {
public static enum PlaceholderProto {
NONE,
OBJECT,
ARRAY,
FUNCTION,
ERROR,
SYNTAX_ERROR,
TYPE_ERROR,
RANGE_ERROR,
}
public static enum State {
NORMAL,
NO_EXTENSIONS,
SEALED,
FROZEN,
}
public static class Property {
public final FunctionValue getter;
public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter;
this.setter = setter;
}
}
private static final Object OBJ_PROTO = new Object();
private static final Object ARR_PROTO = new Object();
private static final Object FUNC_PROTO = new Object();
private static final Object ERR_PROTO = new Object();
private static final Object SYNTAX_ERR_PROTO = new Object();
private static final Object TYPE_ERR_PROTO = new Object();
private static final Object RANGE_ERR_PROTO = new Object();
protected Object prototype;
public State state = State.NORMAL;
public HashMap<Object, Object> values = new HashMap<>();
public HashMap<Object, Property> properties = new HashMap<>();
public HashSet<Object> nonWritableSet = new HashSet<>();
public HashSet<Object> nonConfigurableSet = new HashSet<>();
public HashSet<Object> nonEnumerableSet = new HashSet<>();
public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false;
return !values.containsKey(key) || !nonWritableSet.contains(key);
}
public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false;
return !nonConfigurableSet.contains(key);
}
public final boolean memberEnumerable(Object key) {
return !nonEnumerableSet.contains(key);
}
public final boolean extensible() {
return state == State.NORMAL;
}
public final void preventExtensions() {
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
}
public final void seal() {
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
}
public final void freeze() {
state = State.FROZEN;
}
public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
boolean reconfigured =
writable != memberWritable(key) ||
configurable != memberConfigurable(key) ||
enumerable != memberEnumerable(key);
if (!reconfigured) {
if (!memberWritable(key)) {
var a = values.get(key);
var b = val;
if (a == null || b == null) return a == null && b == null;
return a == b || a.equals(b);
}
values.put(key, val);
return true;
}
if (
properties.containsKey(key) &&
values.get(key) == val &&
!reconfigured
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key))
return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!writable) nonWritableSet.add(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
values.put(key, val);
return true;
}
public final boolean defineProperty(Context ctx, Object key, Object val) {
return defineProperty(ctx, key, val, true, true, true);
}
public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key);
if (
properties.containsKey(key) &&
properties.get(key).getter == getter &&
properties.get(key).setter == setter &&
!configurable == nonConfigurableSet.contains(key) &&
!enumerable == nonEnumerableSet.contains(key)
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
properties.put(key, new Property(getter, setter));
return true;
}
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
try {
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
if (prototype == ARR_PROTO) return ctx.env.proto("array");
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
if (prototype == ERR_PROTO) return ctx.env.proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
}
catch (NullPointerException e) {
return null;
}
return (ObjectValue)prototype;
}
public final boolean setPrototype(Context ctx, Object val) {
val = Values.normalize(ctx, val);
if (!extensible()) return false;
if (val == null || val == Values.NULL) {
prototype = null;
return true;
}
else if (Values.isObject(val)) {
var obj = Values.object(val);
if (ctx != null && ctx.env != null) {
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj;
}
else prototype = obj;
return true;
}
return false;
}
public final boolean setPrototype(PlaceholderProto val) {
if (!extensible()) return false;
switch (val) {
case OBJECT: prototype = OBJ_PROTO; break;
case FUNCTION: prototype = FUNC_PROTO; break;
case ARRAY: prototype = ARR_PROTO; break;
case ERROR: prototype = ERR_PROTO; break;
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
case NONE: prototype = null; break;
}
return true;
}
protected Property getProperty(Context ctx, Object key) throws InterruptedException {
if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key);
else return null;
}
protected Object getField(Context ctx, Object key) throws InterruptedException {
if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key);
else return null;
}
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(ctx, key);
}
values.put(key, val);
return true;
}
protected void deleteField(Context ctx, Object key) throws InterruptedException {
values.remove(key);
}
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
return values.containsKey(key);
}
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException {
key = Values.normalize(ctx, key);
if ("__proto__".equals(key)) {
var res = getPrototype(ctx);
return res == null ? Values.NULL : res;
}
var prop = getProperty(ctx, key);
if (prop != null) {
if (prop.getter == null) return null;
else return prop.getter.call(ctx, Values.normalize(ctx, thisArg));
}
else return getField(ctx, key);
}
public final Object getMember(Context ctx, Object key) throws InterruptedException {
return getMember(ctx, key, this);
}
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
var prop = getProperty(ctx, key);
if (prop != null) {
if (prop.setter == null) return false;
prop.setter.call(ctx, Values.normalize(ctx, thisArg), val);
return true;
}
else if (onlyProps) return false;
else if (!extensible() && !values.containsKey(key)) return false;
else if (key == null) {
values.put(key, val);
return true;
}
else if (key.equals("__proto__")) return setPrototype(ctx, val);
else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val);
}
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) throws InterruptedException {
return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps);
}
public final boolean hasMember(Context ctx, Object key, boolean own) throws InterruptedException {
key = Values.normalize(ctx, key);
if (key != null && key.equals("__proto__")) return true;
if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true;
if (own) return false;
var proto = getPrototype(ctx);
return proto != null && proto.hasMember(ctx, key, own);
}
public final boolean deleteMember(Context ctx, Object key) throws InterruptedException {
key = Values.normalize(ctx, key);
if (!memberConfigurable(key)) return false;
properties.remove(key);
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
deleteField(ctx, key);
return true;
}
public final ObjectValue getMemberDescriptor(Context ctx, Object key) throws InterruptedException {
key = Values.normalize(ctx, key);
var prop = properties.get(key);
var res = new ObjectValue();
res.defineProperty(ctx, "configurable", memberConfigurable(key));
res.defineProperty(ctx, "enumerable", memberEnumerable(key));
if (prop != null) {
res.defineProperty(ctx, "get", prop.getter);
res.defineProperty(ctx, "set", prop.setter);
}
else if (hasField(ctx, key)) {
res.defineProperty(ctx, "value", values.get(key));
res.defineProperty(ctx, "writable", memberWritable(key));
}
else return null;
return res;
}
public List<Object> keys(boolean includeNonEnumerable) {
var res = new ArrayList<Object>();
for (var key : values.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
for (var key : properties.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
return res;
}
public ObjectValue(Context ctx, Map<?, ?> values) {
this(PlaceholderProto.OBJECT);
for (var el : values.entrySet()) {
defineProperty(ctx, el.getKey(), el.getValue());
}
}
public ObjectValue(PlaceholderProto proto) {
nonConfigurableSet.add("__proto__");
nonEnumerableSet.add("__proto__");
setPrototype(proto);
}
public ObjectValue() {
this(PlaceholderProto.OBJECT);
}
}
package me.topchetoeu.jscript.core.engine.values;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.Environment;
public class ObjectValue {
public static enum PlaceholderProto {
NONE,
OBJECT,
ARRAY,
FUNCTION,
ERROR,
SYNTAX_ERROR,
TYPE_ERROR,
RANGE_ERROR,
}
public static enum State {
NORMAL,
NO_EXTENSIONS,
SEALED,
FROZEN,
}
public static class Property {
public final FunctionValue getter;
public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter;
this.setter = setter;
}
}
private static final Object OBJ_PROTO = new Object();
private static final Object ARR_PROTO = new Object();
private static final Object FUNC_PROTO = new Object();
private static final Object ERR_PROTO = new Object();
private static final Object SYNTAX_ERR_PROTO = new Object();
private static final Object TYPE_ERR_PROTO = new Object();
private static final Object RANGE_ERR_PROTO = new Object();
protected Object prototype;
public State state = State.NORMAL;
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
private Property getProperty(Context ctx, Object key) {
if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key);
else return null;
}
public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false;
return !values.containsKey(key) || !nonWritableSet.contains(key);
}
public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false;
return !nonConfigurableSet.contains(key);
}
public final boolean memberEnumerable(Object key) {
return !nonEnumerableSet.contains(key);
}
public final boolean extensible() {
return state == State.NORMAL;
}
public final void preventExtensions() {
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
}
public final void seal() {
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
}
public final void freeze() {
state = State.FROZEN;
}
public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
boolean reconfigured =
writable != memberWritable(key) ||
configurable != memberConfigurable(key) ||
enumerable != memberEnumerable(key);
if (!reconfigured) {
if (!memberWritable(key)) {
var a = values.get(key);
var b = val;
if (a == null || b == null) return a == null && b == null;
return a == b || a.equals(b);
}
values.put(key, val);
return true;
}
if (
properties.containsKey(key) &&
values.get(key) == val &&
!reconfigured
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!writable) nonWritableSet.add(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
values.put(key, val);
return true;
}
public final boolean defineProperty(Context ctx, Object key, Object val) {
return defineProperty(ctx, key, val, true, true, true);
}
public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key);
if (
properties.containsKey(key) &&
properties.get(key).getter == getter &&
properties.get(key).setter == setter &&
!configurable == nonConfigurableSet.contains(key) &&
!enumerable == nonEnumerableSet.contains(key)
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
properties.put(key, new Property(getter, setter));
return true;
}
public ObjectValue getPrototype(Context ctx) {
try {
if (prototype == OBJ_PROTO) return ctx.get(Environment.OBJECT_PROTO);
if (prototype == ARR_PROTO) return ctx.get(Environment.ARRAY_PROTO);
if (prototype == FUNC_PROTO) return ctx.get(Environment.FUNCTION_PROTO);
if (prototype == ERR_PROTO) return ctx.get(Environment.ERROR_PROTO);
if (prototype == RANGE_ERR_PROTO) return ctx.get(Environment.RANGE_ERR_PROTO);
if (prototype == SYNTAX_ERR_PROTO) return ctx.get(Environment.SYNTAX_ERR_PROTO);
if (prototype == TYPE_ERR_PROTO) return ctx.get(Environment.TYPE_ERR_PROTO);
}
catch (NullPointerException e) { return null; }
return (ObjectValue)prototype;
}
public final boolean setPrototype(PlaceholderProto val) {
if (!extensible()) return false;
switch (val) {
case OBJECT: prototype = OBJ_PROTO; break;
case FUNCTION: prototype = FUNC_PROTO; break;
case ARRAY: prototype = ARR_PROTO; break;
case ERROR: prototype = ERR_PROTO; break;
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
case NONE: prototype = null; break;
}
return true;
}
/**
* A method, used to get the value of a field. If a property is bound to
* this key, but not a field, this method should return null.
*/
protected Object getField(Context ctx, Object key) {
if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key);
else return null;
}
/**
* Changes the value of a field, that is bound to the given key. If no field is
* bound to this key, a new field should be created with the given value
* @return Whether or not the operation was successful
*/
protected boolean setField(Context ctx, Object key, Object val) {
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(ctx, key);
}
values.put(key, val);
return true;
}
/**
* Deletes the field bound to the given key.
*/
protected void deleteField(Context ctx, Object key) {
values.remove(key);
}
/**
* Returns whether or not there is a field bound to the given key.
* This must ignore properties
*/
protected boolean hasField(Context ctx, Object key) {
return values.containsKey(key);
}
public final Object getMember(Context ctx, Object key, Object thisArg) {
key = Values.normalize(ctx, key);
if ("__proto__".equals(key)) {
var res = getPrototype(ctx);
return res == null ? Values.NULL : res;
}
var prop = getProperty(ctx, key);
if (prop != null) {
if (prop.getter == null) return null;
else return prop.getter.call(ctx, Values.normalize(ctx, thisArg));
}
else return getField(ctx, key);
}
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
var prop = getProperty(ctx, key);
if (prop != null) {
if (prop.setter == null) return false;
prop.setter.call(ctx, Values.normalize(ctx, thisArg), val);
return true;
}
else if (onlyProps) return false;
else if (!extensible() && !values.containsKey(key)) return false;
else if (key == null) {
values.put(key, val);
return true;
}
else if ("__proto__".equals(key)) return setPrototype(ctx, val);
else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val);
}
public final boolean hasMember(Context ctx, Object key, boolean own) {
key = Values.normalize(ctx, key);
if (key != null && "__proto__".equals(key)) return true;
if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true;
if (own) return false;
var proto = getPrototype(ctx);
return proto != null && proto.hasMember(ctx, key, own);
}
public final boolean deleteMember(Context ctx, Object key) {
key = Values.normalize(ctx, key);
if (!memberConfigurable(key)) return false;
properties.remove(key);
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
deleteField(ctx, key);
return true;
}
public final boolean setPrototype(Context ctx, Object val) {
val = Values.normalize(ctx, val);
if (!extensible()) return false;
if (val == null || val == Values.NULL) {
prototype = null;
return true;
}
else if (val instanceof ObjectValue) {
var obj = Values.object(val);
if (ctx != null) {
if (obj == ctx.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO;
else if (obj == ctx.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO;
else if (obj == ctx.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO;
else if (obj == ctx.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO;
else if (obj == ctx.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO;
else prototype = obj;
}
else prototype = obj;
return true;
}
return false;
}
public final ObjectValue getMemberDescriptor(Context ctx, Object key) {
key = Values.normalize(ctx, key);
var prop = properties.get(key);
var res = new ObjectValue();
res.defineProperty(ctx, "configurable", memberConfigurable(key));
res.defineProperty(ctx, "enumerable", memberEnumerable(key));
if (prop != null) {
res.defineProperty(ctx, "get", prop.getter);
res.defineProperty(ctx, "set", prop.setter);
}
else if (hasField(ctx, key)) {
res.defineProperty(ctx, "value", values.get(key));
res.defineProperty(ctx, "writable", memberWritable(key));
}
else return null;
return res;
}
public List<Object> keys(boolean includeNonEnumerable) {
var res = new ArrayList<Object>();
for (var key : values.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
for (var key : properties.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
return res;
}
public ObjectValue(Context ctx, Map<?, ?> values) {
this(PlaceholderProto.OBJECT);
for (var el : values.entrySet()) {
defineProperty(ctx, el.getKey(), el.getValue());
}
}
public ObjectValue(PlaceholderProto proto) {
nonConfigurableSet.add("__proto__");
nonEnumerableSet.add("__proto__");
setPrototype(proto);
}
public ObjectValue() {
this(PlaceholderProto.OBJECT);
}
}

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.jscript.core.engine.values;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.scope.ValueVariable;
public class ScopeValue extends ObjectValue {
public final ValueVariable[] variables;
public final HashMap<String, Integer> names = new HashMap<>();
@Override
protected Object getField(Context ctx, Object key) {
if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (names.containsKey(key)) {
variables[names.get(key)].set(ctx, val);
return true;
}
var proto = getPrototype(ctx);
if (proto != null && proto.hasMember(ctx, key, false) && proto.setField(ctx, key, val)) return true;
return super.setField(ctx, key, val);
}
@Override
protected void deleteField(Context ctx, Object key) {
if (names.containsKey(key)) return;
super.deleteField(ctx, key);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (names.containsKey(key)) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet());
return res;
}
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i);
this.nonConfigurableSet.add(names[i]);
}
}
}

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.jscript.core.engine.values;
import java.util.HashMap;
public final class Symbol {
private static final HashMap<String, Symbol> registry = new HashMap<>();
public final String value;
public Symbol(String value) {
this.value = value;
}
@Override
public String toString() {
if (value == null) return "Symbol";
else return "@@" + value;
}
public static Symbol get(String name) {
if (registry.containsKey(name)) return registry.get(name);
else {
var res = new Symbol(name);
registry.put(name, res);
return res;
}
}
}

View File

@@ -1,5 +1,7 @@
package me.topchetoeu.jscript.engine.values;
package me.topchetoeu.jscript.core.engine.values;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -10,19 +12,37 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.frame.ConvertHint;
import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.Environment;
import me.topchetoeu.jscript.core.engine.Operation;
import me.topchetoeu.jscript.core.engine.frame.ConvertHint;
import me.topchetoeu.jscript.core.exceptions.ConvertException;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
import me.topchetoeu.jscript.lib.PromiseLib;
public class Values {
public static final Object NULL = new Object();
public static enum CompareResult {
NOT_EQUAL,
EQUAL,
LESS,
GREATER;
public boolean less() { return this == LESS; }
public boolean greater() { return this == GREATER; }
public boolean lessOrEqual() { return this == LESS || this == EQUAL; }
public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; }
public static CompareResult from(int cmp) {
if (cmp < 0) return LESS;
if (cmp > 0) return GREATER;
return EQUAL;
}
}
public static final Object NULL = new Object();
public static final Object NO_RETURN = new Object();
public static boolean isObject(Object val) { return val instanceof ObjectValue; }
public static boolean isFunction(Object val) { return val instanceof FunctionValue; }
public static boolean isArray(Object val) { return val instanceof ArrayValue; }
public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; }
public static boolean isWrapper(Object val, Class<?> clazz) {
if (!isWrapper(val)) return false;
@@ -50,8 +70,7 @@ public class Values {
@SuppressWarnings("unchecked")
public static <T> T wrapper(Object val, Class<T> clazz) {
if (!isWrapper(val)) return null;
if (!isWrapper(val)) val = new NativeWrapper(val);
var res = (NativeWrapper)val;
if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped;
else return null;
@@ -67,11 +86,11 @@ public class Values {
return "object";
}
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) throws InterruptedException {
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) {
var func = getMember(ctx, obj, name);
if (func != null) {
var res = ((FunctionValue)func).call(ctx, obj);
if (func instanceof FunctionValue) {
var res = Values.call(ctx, func, obj);
if (isPrimitive(res)) return res;
}
@@ -88,7 +107,7 @@ public class Values {
obj == NULL;
}
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException {
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) {
obj = normalize(ctx, obj);
if (isPrimitive(obj)) return obj;
@@ -96,37 +115,31 @@ public class Values {
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
if (ctx != null) {
try {
return tryCallConvertFunc(ctx, obj, first);
}
catch (EngineException unused) {
return tryCallConvertFunc(ctx, obj, second);
}
try { return tryCallConvertFunc(ctx, obj, first); }
catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); }
}
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
public static boolean toBoolean(Object obj) {
if (obj == NULL || obj == null) return false;
if (obj instanceof Number && number(obj) == 0) return false;
if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false;
if (obj instanceof String && ((String)obj).equals("")) return false;
if (obj instanceof Boolean) return (Boolean)obj;
return true;
}
public static double toNumber(Context ctx, Object obj) throws InterruptedException {
public static double toNumber(Context ctx, Object obj) {
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val instanceof Number) return number(val);
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
if (val instanceof String) {
try {
return Double.parseDouble((String)val);
}
catch (NumberFormatException e) { }
try { return Double.parseDouble((String)val); }
catch (NumberFormatException e) { return Double.NaN; }
}
return Double.NaN;
}
public static String toString(Context ctx, Object obj) throws InterruptedException {
public static String toString(Context ctx, Object obj) {
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val == null) return "undefined";
@@ -141,52 +154,52 @@ public class Values {
}
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
if (val instanceof String) return (String)val;
if (val instanceof Symbol) return ((Symbol)val).toString();
if (val instanceof Symbol) return val.toString();
return "Unknown value";
}
public static Object add(Context ctx, Object a, Object b) throws InterruptedException {
public static Object add(Context ctx, Object a, Object b) {
if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
else return toNumber(ctx, a) + toNumber(ctx, b);
}
public static double subtract(Context ctx, Object a, Object b) throws InterruptedException {
public static double subtract(Context ctx, Object a, Object b) {
return toNumber(ctx, a) - toNumber(ctx, b);
}
public static double multiply(Context ctx, Object a, Object b) throws InterruptedException {
public static double multiply(Context ctx, Object a, Object b) {
return toNumber(ctx, a) * toNumber(ctx, b);
}
public static double divide(Context ctx, Object a, Object b) throws InterruptedException {
public static double divide(Context ctx, Object a, Object b) {
return toNumber(ctx, a) / toNumber(ctx, b);
}
public static double modulo(Context ctx, Object a, Object b) throws InterruptedException {
public static double modulo(Context ctx, Object a, Object b) {
return toNumber(ctx, a) % toNumber(ctx, b);
}
public static double negative(Context ctx, Object obj) throws InterruptedException {
public static double negative(Context ctx, Object obj) {
return -toNumber(ctx, obj);
}
public static int and(Context ctx, Object a, Object b) throws InterruptedException {
public static int and(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) & (int)toNumber(ctx, b);
}
public static int or(Context ctx, Object a, Object b) throws InterruptedException {
public static int or(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) | (int)toNumber(ctx, b);
}
public static int xor(Context ctx, Object a, Object b) throws InterruptedException {
public static int xor(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b);
}
public static int bitwiseNot(Context ctx, Object obj) throws InterruptedException {
public static int bitwiseNot(Context ctx, Object obj) {
return ~(int)toNumber(ctx, obj);
}
public static int shiftLeft(Context ctx, Object a, Object b) throws InterruptedException {
public static int shiftLeft(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) << (int)toNumber(ctx, b);
}
public static int shiftRight(Context ctx, Object a, Object b) throws InterruptedException {
public static int shiftRight(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b);
}
public static long unsignedShiftRight(Context ctx, Object a, Object b) throws InterruptedException {
public static long unsignedShiftRight(Context ctx, Object a, Object b) {
long _a = (long)toNumber(ctx, a);
long _b = (long)toNumber(ctx, b);
@@ -195,19 +208,25 @@ public class Values {
return _a >>> _b;
}
public static int compare(Context ctx, Object a, Object b) throws InterruptedException {
public static CompareResult compare(Context ctx, Object a, Object b) {
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b);
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
var _a = toNumber(ctx, a);
var _b = toNumber(ctx, b);
if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL;
return CompareResult.from(Double.compare(_a, _b));
}
public static boolean not(Object obj) {
return !toBoolean(obj);
}
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) throws InterruptedException {
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) {
if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
var val = getPrototype(ctx, obj);
@@ -219,7 +238,7 @@ public class Values {
return false;
}
public static Object operation(Context ctx, Operation op, Object ...args) throws InterruptedException {
public static Object operation(Context ctx, Operation op, Object ...args) {
switch (op) {
case ADD: return add(ctx, args[0], args[1]);
case SUBTRACT: return subtract(ctx, args[0], args[1]);
@@ -236,10 +255,10 @@ public class Values {
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]);
case GREATER: return compare(ctx, args[0], args[1]) > 0;
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0;
case LESS: return compare(ctx, args[0], args[1]) < 0;
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0;
case GREATER: return compare(ctx, args[0], args[1]).greater();
case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual();
case LESS: return compare(ctx, args[0], args[1]).less();
case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
case INVERSE: return bitwiseNot(ctx, args[0]);
case NOT: return not(args[0]);
@@ -260,11 +279,11 @@ public class Values {
}
}
public static Object getMember(Context ctx, Object obj, Object key) throws InterruptedException {
public static Object getMember(Context ctx, Object obj, Object key) {
obj = normalize(ctx, obj); key = normalize(ctx, key);
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
if (isObject(obj)) return object(obj).getMember(ctx, key);
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj);
if (obj instanceof String && key instanceof Number) {
var i = number(key);
@@ -276,26 +295,31 @@ public class Values {
var proto = getPrototype(ctx, obj);
if (proto == null) return key.equals("__proto__") ? NULL : null;
else if (key != null && key.equals("__proto__")) return proto;
if (proto == null) return "__proto__".equals(key) ? NULL : null;
else if (key != null && "__proto__".equals(key)) return proto;
else return proto.getMember(ctx, key, obj);
}
public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException {
public static Object getMemberPath(Context ctx, Object obj, Object ...path) {
var res = obj;
for (var key : path) res = getMember(ctx, res, key);
return res;
}
public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
if (key.equals("__proto__")) return setPrototype(ctx, obj, val);
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false);
var proto = getPrototype(ctx, obj);
return proto.setMember(ctx, key, val, obj, true);
}
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) throws InterruptedException {
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) {
if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key);
if (key.equals("__proto__")) return true;
if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
if ("__proto__".equals(key)) return true;
if (obj instanceof ObjectValue) return object(obj).hasMember(ctx, key, own);
if (obj instanceof String && key instanceof Number) {
var i = number(key);
@@ -308,34 +332,39 @@ public class Values {
var proto = getPrototype(ctx, obj);
return proto != null && proto.hasMember(ctx, key, own);
}
public static boolean deleteMember(Context ctx, Object obj, Object key) throws InterruptedException {
public static boolean deleteMember(Context ctx, Object obj, Object key) {
if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key);
if (isObject(obj)) return object(obj).deleteMember(ctx, key);
if (obj instanceof ObjectValue) return object(obj).deleteMember(ctx, key);
else return false;
}
public static ObjectValue getPrototype(Context ctx, Object obj) throws InterruptedException {
public static ObjectValue getPrototype(Context ctx, Object obj) {
if (obj == null || obj == NULL) return null;
obj = normalize(ctx, obj);
if (isObject(obj)) return object(obj).getPrototype(ctx);
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ctx);
if (ctx == null) return null;
if (obj instanceof String) return ctx.env.proto("string");
else if (obj instanceof Number) return ctx.env.proto("number");
else if (obj instanceof Boolean) return ctx.env.proto("bool");
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
if (obj instanceof String) return ctx.get(Environment.STRING_PROTO);
else if (obj instanceof Number) return ctx.get(Environment.NUMBER_PROTO);
else if (obj instanceof Boolean) return ctx.get(Environment.BOOL_PROTO);
else if (obj instanceof Symbol) return ctx.get(Environment.SYMBOL_PROTO);
return null;
}
public static boolean setPrototype(Context ctx, Object obj, Object proto) throws InterruptedException {
public static boolean setPrototype(Context ctx, Object obj, Object proto) {
obj = normalize(ctx, obj);
return isObject(obj) && object(obj).setPrototype(ctx, proto);
return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ctx, proto);
}
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException {
public static void makePrototypeChain(Context ctx, Object... chain) {
for(var i = 1; i < chain.length; i++) {
setPrototype(ctx, chain[i], chain[i - 1]);
}
}
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) {
List<Object> res = new ArrayList<>();
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
if (obj instanceof ObjectValue) res = object(obj).keys(includeNonEnumerable);
if (obj instanceof String) {
for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i);
}
@@ -345,14 +374,14 @@ public class Values {
while (proto != null) {
res.addAll(proto.keys(includeNonEnumerable));
proto = proto.getPrototype(ctx);
proto = getPrototype(ctx, proto);
}
}
return res;
}
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) {
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
else if (obj instanceof String && key instanceof Number) {
var i = ((Number)key).intValue();
@@ -370,19 +399,24 @@ public class Values {
else return null;
}
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args);
}
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException {
public static Object callNew(Context ctx, Object func, Object ...args) {
var res = new ObjectValue();
var proto = Values.getMember(ctx, func, "prototype");
res.setPrototype(ctx, proto);
try {
var proto = Values.getMember(ctx, func, "prototype");
setPrototype(ctx, res, proto);
var ret = call(ctx, func, res, args);
var ret = call(ctx, func, res, args);
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res;
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res;
}
catch (IllegalArgumentException e) {
throw EngineException.ofType("Tried to call new on an invalid constructor.");
}
}
public static boolean strictEquals(Context ctx, Object a, Object b) {
@@ -395,7 +429,7 @@ public class Values {
return a == b || a.equals(b);
}
public static boolean looseEqual(Context ctx, Object a, Object b) throws InterruptedException {
public static boolean looseEqual(Context ctx, Object a, Object b) {
a = normalize(ctx, a); b = normalize(ctx, b);
// In loose equality, null is equivalent to undefined
@@ -446,14 +480,14 @@ public class Values {
if (val instanceof Class) {
if (ctx == null) return null;
else return ctx.env.wrappersProvider.getConstr((Class<?>)val);
else return ctx.environment.wrappers.getConstr((Class<?>)val);
}
return new NativeWrapper(val);
}
@SuppressWarnings("unchecked")
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) {
if (clazz == Void.class) return null;
if (obj instanceof NativeWrapper) {
@@ -513,37 +547,40 @@ public class Values {
if (obj == null) return null;
if (clazz.isInstance(obj)) return (T)obj;
if (clazz.isAssignableFrom(NativeWrapper.class)) {
return (T)new NativeWrapper(obj);
}
throw new ConvertException(type(obj), clazz.getSimpleName());
}
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
public static Iterable<Object> fromJSIterator(Context ctx, Object obj) {
return () -> {
try {
var symbol = ctx.env.symbol("Symbol.iterator");
var symbol = Symbol.get("Symbol.iterator");
var iteratorFunc = getMember(ctx, obj, symbol);
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator();
var iterator = iteratorFunc instanceof FunctionValue ?
((FunctionValue)iteratorFunc).call(ctx, obj, obj) :
iteratorFunc;
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
if (!isFunction(nextFunc)) return Collections.emptyIterator();
if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator();
return new Iterator<Object>() {
private Object value = null;
public boolean consumed = true;
private FunctionValue next = (FunctionValue)nextFunc;
private void loadNext() throws InterruptedException {
private void loadNext() {
if (next == null) value = null;
else if (consumed) {
var curr = object(next.call(ctx, iterator));
var curr = next.call(ctx, iterator);
if (curr == null) { next = null; value = null; }
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
if (toBoolean(Values.getMember(ctx, curr, "done"))) { next = null; value = null; }
else {
this.value = curr.getMember(ctx, "value");
this.value = Values.getMember(ctx, curr, "value");
consumed = false;
}
}
@@ -551,156 +588,184 @@ public class Values {
@Override
public boolean hasNext() {
try {
loadNext();
return next != null;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
loadNext();
return next != null;
}
@Override
public Object next() {
try {
loadNext();
var res = value;
value = null;
consumed = true;
return res;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
loadNext();
var res = value;
value = null;
consumed = true;
return res;
}
};
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
catch (IllegalArgumentException | NullPointerException e) {
return Collections.emptyIterator();
}
};
}
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException {
public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue();
try {
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
var key = getMember(ctx, getMember(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else return new ObjectValue(ctx, Map.of("value", it.next()));
else {
var obj = new ObjectValue();
obj.defineProperty(args.ctx, "value", it.next());
return obj;
}
}));
return res;
}
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException {
return fromJavaIterator(ctx, it.iterator());
public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
return toJSIterator(ctx, it.iterator());
}
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException {
if (passed.contains(val)) {
System.out.print("[circular]");
return;
public static ObjectValue toJSAsyncIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue();
try {
var key = getMemberPath(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
return PromiseLib.await(args.ctx, () -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else {
var obj = new ObjectValue();
obj.defineProperty(args.ctx, "value", it.next());
return obj;
}
});
}));
return res;
}
private static boolean isEmptyFunc(ObjectValue val) {
if (!(val instanceof FunctionValue)) return false;
if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false;
var proto = val.values.get("prototype");
if (!(proto instanceof ObjectValue)) return false;
var protoObj = (ObjectValue)proto;
if (protoObj.values.get("constructor") != val) return false;
if (protoObj.values.size() + protoObj.properties.size() != 1) return false;
return true;
}
private static String toReadable(Context ctx, Object val, HashSet<Object> passed, int tab) {
if (tab == 0 && val instanceof String) return (String)val;
if (passed.contains(val)) return "[circular]";
var printed = true;
var res = new StringBuilder();
if (val instanceof FunctionValue) {
System.out.print("function ");
var name = Values.getMember(ctx, val, "name");
if (name != null) System.out.print(Values.toString(ctx, name));
System.out.print("(...)");
res.append(val.toString());
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
if (loc != null) System.out.print(" @ " + loc);
if (loc != null) res.append(" @ " + loc);
}
else if (val instanceof ArrayValue) {
System.out.print("[");
res.append("[");
var obj = ((ArrayValue)val);
for (int i = 0; i < obj.size(); i++) {
if (i != 0) System.out.print(", ");
else System.out.print(" ");
if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab);
else System.out.print("<empty>");
if (i != 0) res.append(", ");
else res.append(" ");
if (obj.has(i)) res.append(toReadable(ctx, obj.get(i), passed, tab));
else res.append("<empty>");
}
System.out.print(" ] ");
res.append(" ] ");
}
else if (val instanceof NativeWrapper) {
var obj = ((NativeWrapper)val).wrapped;
System.out.print("Native " + obj.toString() + " ");
res.append("Native " + obj.toString() + " ");
}
else printed = false;
if (val instanceof ObjectValue) {
if (tab > 3) {
System.out.print("{...}");
return;
return "{...}";
}
passed.add(val);
var obj = (ObjectValue)val;
if (obj.values.size() + obj.properties.size() == 0) {
if (!printed) System.out.println("{}");
if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) {
if (!printed) res.append("{}\n");
}
else {
System.out.println("{");
res.append("{\n");
for (var el : obj.values.entrySet()) {
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
printValue(ctx, el.getKey(), passed, tab + 1);
System.out.print(": ");
printValue(ctx, el.getValue(), passed, tab + 1);
System.out.println(",");
for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(toReadable(ctx, el.getKey(), passed, tab + 1));
res.append(": ");
res.append(toReadable(ctx, el.getValue(), passed, tab + 1));
res.append(",\n");
}
for (var el : obj.properties.entrySet()) {
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
printValue(ctx, el.getKey(), passed, tab + 1);
System.out.println(": [prop],");
for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(toReadable(ctx, el.getKey(), passed, tab + 1));
res.append(": [prop],\n");
}
for (int i = 0; i < tab; i++) System.out.print(" ");
System.out.print("}");
passed.remove(val);
for (int i = 0; i < tab; i++) res.append(" ");
res.append("}");
}
passed.remove(val);
}
else if (val == null) System.out.print("undefined");
else if (val == Values.NULL) System.out.print("null");
else if (val instanceof String) System.out.print("'" + val + "'");
else System.out.print(Values.toString(ctx, val));
else if (val == null) return "undefined";
else if (val == Values.NULL) return "null";
else if (val instanceof String) return "'" + val + "'";
else return Values.toString(ctx, val);
return res.toString();
}
public static void printValue(Context ctx, Object val) throws InterruptedException {
printValue(ctx, val, new HashSet<>(), 0);
public static String toReadable(Context ctx, Object val) {
return toReadable(ctx, val, new HashSet<>(), 0);
}
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
public static String errorToReadable(RuntimeException err, String prefix) {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
try {
if (err instanceof EngineException) {
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
if (err instanceof EngineException) {
var ee = ((EngineException)err);
try {
return prefix + " " + ee.toString(new Context(ee.engine, ee.env));
}
else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
}
else if (err.getCause() instanceof InterruptedException) return;
else {
System.out.println("Internal error ocurred:");
err.printStackTrace();
catch (EngineException ex) {
return prefix + " " + toReadable(new Context(ee.engine, ee.env), ee.value);
}
}
catch (EngineException ex) {
System.out.println("Uncaught ");
Values.printValue(null, ((EngineException)err).value);
System.out.println();
else if (err instanceof SyntaxException) {
return prefix + " SyntaxError " + ((SyntaxException)err).msg;
}
else if (err.getCause() instanceof InterruptedException) return "";
else {
var str = new ByteArrayOutputStream();
err.printStackTrace(new PrintStream(str));
return prefix + " internal error " + str.toString();
}
}
public static void printValue(Context ctx, Object val) {
System.out.print(toReadable(ctx, val));
}
public static void printError(RuntimeException err, String prefix) {
System.out.println(errorToReadable(err, prefix));
}
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.exceptions;
package me.topchetoeu.jscript.core.exceptions;
public class ConvertException extends RuntimeException {
public final String source, target;

View File

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

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.core.exceptions;
public class InterruptException extends RuntimeException {
public InterruptException() { }
public InterruptException(Throwable e) {
super(e);
}
}

View File

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

View File

@@ -1,113 +1,113 @@
package me.topchetoeu.jscript.parsing;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.engine.Operation;
public enum Operator {
MULTIPLY("*", Operation.MULTIPLY, 13),
DIVIDE("/", Operation.DIVIDE, 12),
MODULO("%", Operation.MODULO, 12),
SUBTRACT("-", Operation.SUBTRACT, 11),
ADD("+", Operation.ADD, 11),
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
GREATER(">", Operation.GREATER, 9),
LESS("<", Operation.LESS, 9),
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
EQUALS("==", Operation.LOOSE_EQUALS, 8),
LOOSE_EQUALS("===", Operation.EQUALS, 8),
AND("&", Operation.AND, 7),
XOR("^", Operation.XOR, 6),
OR("|", Operation.OR, 5),
LAZY_AND("&&", 4),
LAZY_OR("||", 3),
ASSIGN_SHIFT_LEFT("<<=", 2, true),
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
ASSIGN_AND("&=", 2, true),
ASSIGN_OR("|=", 2, true),
ASSIGN_XOR("^=", 2, true),
ASSIGN_MODULO("%=", 2, true),
ASSIGN_DIVIDE("/=", 2, true),
ASSIGN_MULTIPLY("*=", 2, true),
ASSIGN_SUBTRACT("-=", 2, true),
ASSIGN_ADD("+=", 2, true),
ASSIGN("=", 2, true),
SEMICOLON(";"),
COLON(":"),
PAREN_OPEN("("),
PAREN_CLOSE(")"),
BRACKET_OPEN("["),
BRACKET_CLOSE("]"),
BRACE_OPEN("{"),
BRACE_CLOSE("}"),
DOT("."),
COMMA(","),
NOT("!"),
QUESTION("?"),
INVERSE("~"),
INCREASE("++"),
DECREASE("--");
public final String value;
public final Operation operation;
public final int precedence;
public final boolean reverse;
private static final Map<String, Operator> ops = new HashMap<>();
static {
for (var el : Operator.values()) {
ops.put(el.value, el);
}
}
public boolean isAssign() { return precedence == 2; }
public static Operator parse(String val) {
return ops.get(val);
}
private Operator() {
this.value = null;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value) {
this. value = value;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value, int precedence) {
this. value = value;
this.operation = null;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, int precedence, boolean reverse) {
this. value = value;
this.operation = null;
this.precedence = precedence;
this.reverse = reverse;
}
private Operator(String value, Operation funcName, int precedence) {
this. value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
this.value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = reverse;
}
package me.topchetoeu.jscript.core.parsing;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.core.engine.Operation;
public enum Operator {
MULTIPLY("*", Operation.MULTIPLY, 13),
DIVIDE("/", Operation.DIVIDE, 12),
MODULO("%", Operation.MODULO, 12),
SUBTRACT("-", Operation.SUBTRACT, 11),
ADD("+", Operation.ADD, 11),
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
GREATER(">", Operation.GREATER, 9),
LESS("<", Operation.LESS, 9),
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
EQUALS("==", Operation.LOOSE_EQUALS, 8),
LOOSE_EQUALS("===", Operation.EQUALS, 8),
AND("&", Operation.AND, 7),
XOR("^", Operation.XOR, 6),
OR("|", Operation.OR, 5),
LAZY_AND("&&", 4),
LAZY_OR("||", 3),
ASSIGN_SHIFT_LEFT("<<=", 2, true),
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
ASSIGN_AND("&=", 2, true),
ASSIGN_OR("|=", 2, true),
ASSIGN_XOR("^=", 2, true),
ASSIGN_MODULO("%=", 2, true),
ASSIGN_DIVIDE("/=", 2, true),
ASSIGN_MULTIPLY("*=", 2, true),
ASSIGN_SUBTRACT("-=", 2, true),
ASSIGN_ADD("+=", 2, true),
ASSIGN("=", 2, true),
SEMICOLON(";"),
COLON(":"),
PAREN_OPEN("("),
PAREN_CLOSE(")"),
BRACKET_OPEN("["),
BRACKET_CLOSE("]"),
BRACE_OPEN("{"),
BRACE_CLOSE("}"),
DOT("."),
COMMA(","),
NOT("!"),
QUESTION("?"),
INVERSE("~"),
INCREASE("++"),
DECREASE("--");
public final String value;
public final Operation operation;
public final int precedence;
public final boolean reverse;
private static final Map<String, Operator> ops = new HashMap<>();
static {
for (var el : Operator.values()) {
ops.put(el.value, el);
}
}
public boolean isAssign() { return precedence == 2; }
public static Operator parse(String val) {
return ops.get(val);
}
private Operator() {
this.value = null;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value) {
this. value = value;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value, int precedence) {
this. value = value;
this.operation = null;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, int precedence, boolean reverse) {
this. value = value;
this.operation = null;
this.precedence = precedence;
this.reverse = reverse;
}
private Operator(String value, Operation funcName, int precedence) {
this. value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
this.value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = reverse;
}
}

View File

@@ -1,97 +1,100 @@
package me.topchetoeu.jscript.parsing;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.parsing.Parsing.Parser;
public class ParseRes<T> {
public static enum State {
SUCCESS,
FAILED,
ERROR;
public boolean isSuccess() { return this == SUCCESS; }
public boolean isFailed() { return this == FAILED; }
public boolean isError() { return this == ERROR; }
}
public final ParseRes.State state;
public final String error;
public final T result;
public final int n;
private ParseRes(ParseRes.State state, String error, T result, int readN) {
this.result = result;
this.n = readN;
this.state = state;
this.error = error;
}
public ParseRes<T> setN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, i);
}
public ParseRes<T> addN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, this.n + i);
}
public <T2> ParseRes<T2> transform() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
return new ParseRes<>(state, error, null, 0);
}
public TestRes toTest() {
if (isSuccess()) return TestRes.res(n);
else if (isError()) return TestRes.error(null, error);
else return TestRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static <T> ParseRes<T> failed() {
return new ParseRes<T>(State.FAILED, null, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error) {
if (loc != null) error = loc + ": " + error;
return new ParseRes<>(State.ERROR, error, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
if (loc != null) error = loc + ": " + error;
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
return new ParseRes<>(State.ERROR, other.error, null, 0);
}
public static <T> ParseRes<T> res(T val, int i) {
return new ParseRes<>(State.SUCCESS, null, val, i);
}
@SafeVarargs
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
ParseRes<? extends T> best = null;
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
if (parser.isSuccess()) {
if (best == null || best.n < parser.n) best = parser;
}
else if (parser.isError() && error.isFailed()) error = parser.transform();
}
if (best != null) return best;
else return error;
}
@SafeVarargs
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
var res = parser.parse(null, tokens, 0);
if (res.isSuccess()) return res;
else if (res.isError() && error.isFailed()) error = res.transform();
}
return error;
}
package me.topchetoeu.jscript.core.parsing;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.parsing.Parsing.Parser;
public class ParseRes<T> {
public static enum State {
SUCCESS,
FAILED,
ERROR;
public boolean isSuccess() { return this == SUCCESS; }
public boolean isFailed() { return this == FAILED; }
public boolean isError() { return this == ERROR; }
}
public final ParseRes.State state;
public final String error;
public final T result;
public final int n;
private ParseRes(ParseRes.State state, String error, T result, int readN) {
this.result = result;
this.n = readN;
this.state = state;
this.error = error;
}
public ParseRes<T> setN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, i);
}
public ParseRes<T> addN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, this.n + i);
}
public <T2> ParseRes<T2> transform() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
return new ParseRes<>(state, error, null, 0);
}
public TestRes toTest() {
if (isSuccess()) return TestRes.res(n);
else if (isError()) return TestRes.error(null, error);
else return TestRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static <T> ParseRes<T> failed() {
return new ParseRes<T>(State.FAILED, null, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error) {
if (loc != null) error = loc + ": " + error;
return new ParseRes<>(State.ERROR, error, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
if (loc != null) error = loc + ": " + error;
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
return new ParseRes<>(State.ERROR, other.error, null, 0);
}
public static <T> ParseRes<T> res(T val, int i) {
return new ParseRes<>(State.SUCCESS, null, val, i);
}
@SafeVarargs
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
return any(List.of(parsers));
}
public static <T> ParseRes<? extends T> any(List<ParseRes<? extends T>> parsers) {
ParseRes<? extends T> best = null;
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
if (parser.isSuccess()) {
if (best == null || best.n < parser.n) best = parser;
}
else if (parser.isError() && error.isFailed()) error = parser.transform();
}
if (best != null) return best;
else return error;
}
@SafeVarargs
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
var res = parser.parse(null, tokens, 0);
if (res.isSuccess()) return res;
else if (res.isError() && error.isFailed()) error = res.transform();
}
return error;
}
}

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