Compare commits

...

205 Commits

Author SHA1 Message Date
4e31766665 fix: add new vscode debugger functions 2024-03-29 21:53:15 +02:00
b5b63c4342 fix: make global cache of native wrappers 2024-03-28 16:08:07 +02:00
18f70a0d58 fix: i hate wrappers 2024-03-28 15:10:21 +02:00
d38b600366 fix: some more wrapper issues 2024-03-28 14:52:49 +02:00
0ac7af2ea3 fix: take into account empty classes 2024-03-28 14:21:23 +02:00
5185c93663 fix: don't include non-exposing wrappers in proto chain
feat: allow adding custom wrappers
2024-03-28 00:57:09 +02:00
510422cab7 feat: implement logic for exposing non-static fields 2024-03-27 23:39:33 +02:00
79e1d1cfaf Merge branch 'master' of https://github.com/TopchetoEU/java-jscript 2024-03-27 23:08:25 +02:00
e0f3274a95 feat: add simple for-of loop (not intended for production usage) 2024-03-27 23:08:21 +02:00
ef5d29105f Update README.md 2024-03-10 02:17:18 +02:00
d8ea6557df fix: buildline expects tag to start with 'v' 2024-03-09 00:45:00 +02:00
5ba858545a fix: defer handles of async functions 2024-03-09 00:28:30 +02:00
446ecd8f2b fix: promise defers callback twice 2024-03-08 17:23:50 +02:00
fbf103439a bump 2024-03-08 16:55:46 +02:00
b30f94de8f refactor: move function pushMsg signatures in EventLoop 2024-03-08 16:53:47 +02:00
47b4dd3c15 refactor: rename code to runtime 2024-03-06 23:23:01 +02:00
0fb336373a fix: make fs calls synchronized 2024-03-06 12:50:57 +02:00
b33325a98d fix: clear buffer of line writer file 2024-03-05 17:10:06 +02:00
ccf75d6066 fix: don't use Context.NULL in global scope 2024-03-05 16:51:50 +02:00
662dcc1ac1 bump 2024-03-05 16:30:13 +02:00
3e6214659b fix: use new global API 2024-03-05 15:54:51 +02:00
7c6622c53d fix: separate scope records from scopes 2024-03-05 15:45:02 +02:00
70d5871091 fix: properly check permissions 2024-03-03 20:47:54 +02:00
7b9bbe576b feat: add std streams as global variables 2024-03-03 20:31:20 +02:00
e6399c1546 refactor: remove unused var 2024-03-02 14:01:58 +02:00
c8253795b2 fix: make debugging work again 2024-03-02 13:56:48 +02:00
49dd725669 refactor: fully separate event loop from context 2024-02-29 00:23:14 +02:00
52489ad3a8 feat: separate compilation and runtime 2024-02-26 13:22:56 +02:00
c4d44547c8 fix: call move when passing same array to copyTo 2024-02-21 11:03:19 +02:00
c6dc031cfd fix: respect return value of constructors 2024-02-21 11:01:33 +02:00
285960bdd6 refactor: rework fs error system 2024-02-09 13:46:57 +02:00
cf99845f6b refactor: rework permission system 2024-01-13 11:05:43 +02:00
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
291 changed files with 16692 additions and 11596 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@@ -3,7 +3,7 @@ name: "tagged-release"
on:
push:
tags:
- "v*"
- "*"
jobs:
tagged-release:
@@ -11,15 +11,22 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '11'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main
with:
branch: 'master' # fuck this political bullshitshit, took me an hour to fix this
branch: 'master'
owner: 'TopchetoEU'
repository: 'java-jscript'
- name: "Build"
- name: Build
run: |
cd java-jscript; node ./build.js release ${{ github.ref }}
cd java-jscript; gradle build
- uses: "marvinpinto/action-automatic-releases@latest"
with:
@@ -27,4 +34,4 @@ jobs:
prerelease: false
files: |
java-jscript/LICENSE
java-jscript/dst/*.jar
java-jscript/build/libs/*.jar

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

32
build.gradle Normal file
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.9.12-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,58 @@
package me.topchetoeu.jscript.common;
public class Buffer {
private byte[] data;
private int length;
public void write(int i, byte[] val) {
if (i + val.length > data.length) {
var newCap = i + val.length + 1;
if (newCap < data.length * 2) newCap = data.length * 2;
var tmp = new byte[newCap];
System.arraycopy(this.data, 0, tmp, 0, length);
this.data = tmp;
}
System.arraycopy(val, 0, data, i, val.length);
if (i + val.length > length) length = i + val.length;
}
public int read(int i, byte[] buff) {
int n = buff.length;
if (i + n > length) n = length - i;
System.arraycopy(data, i, buff, 0, n);
return n;
}
public void clear() {
data = new byte[128];
length = 0;
}
public void append(byte b) {
write(length, new byte[] { b });
}
public byte[] data() {
var res = new byte[length];
System.arraycopy(this.data, 0, res, 0, length);
return res;
}
public int length() {
return length;
}
public Buffer(byte[] data) {
this.data = new byte[data.length];
this.length = data.length;
System.arraycopy(data, 0, this.data, 0, data.length);
}
public Buffer(int capacity) {
this.data = new byte[capacity];
this.length = 0;
}
public Buffer() {
this.data = new byte[128];
this.length = 0;
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.common;
import java.io.File;
import java.nio.file.Path;
public class Filename {
public final String protocol;
public final String path;
@Override public String toString() {
return protocol + "://" + path;
}
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
var other = (Filename) obj;
if (protocol == null) {
if (other.protocol != null) return false;
}
else if (!protocol.equals(other.protocol)) return false;
if (path == null) {
if (other.path != null) return false;
}
else if (!path.equals(other.path)) return false;
return true;
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
this.protocol = protocol;
this.path = path;
}
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
public static Path normalize(String path) {
return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
}

View File

@@ -0,0 +1,14 @@
package me.topchetoeu.jscript.common;
public class FunctionBody {
public final FunctionBody[] children;
public final Instruction[] instructions;
public final int localsN, argsN;
public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) {
this.children = children;
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions;
}
}

View File

@@ -0,0 +1,369 @@
package me.topchetoeu.jscript.common;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
NOP(0),
RETURN(1),
THROW(2),
THROW_SYNTAX(3),
DELETE(4),
TRY_START(5),
TRY_END(6),
CALL(7),
CALL_NEW(8),
JMP_IF(9),
JMP_IFN(10),
JMP(11),
PUSH_UNDEFINED(12),
PUSH_NULL(13),
PUSH_BOOL(14),
PUSH_NUMBER(15),
PUSH_STRING(16),
LOAD_VAR(17),
LOAD_MEMBER(18),
LOAD_GLOB(20),
LOAD_FUNC(21),
LOAD_ARR(22),
LOAD_OBJ(23),
STORE_SELF_FUNC(24),
LOAD_REGEX(25),
DUP(26),
STORE_VAR(27),
STORE_MEMBER(28),
DISCARD(29),
MAKE_VAR(30),
DEF_PROP(31),
KEYS(32),
TYPEOF(33),
OPERATION(34);
private static final HashMap<Integer, Type> types = new HashMap<>();
public final int numeric;
static {
for (var val : Type.values()) types.put(val.numeric, val);
}
private Type(int numeric) {
this.numeric = numeric;
}
public static Type fromNumeric(int i) {
return types.get(i);
}
}
public static enum BreakpointType {
NONE,
STEP_OVER,
STEP_IN;
public boolean shouldStepIn() {
return this != NONE;
}
public boolean shouldStepOver() {
return this == STEP_OVER;
}
}
public final Type type;
public final Object[] params;
@SuppressWarnings("unchecked")
public <T> T get(int i) {
if (i >= params.length || i < 0) return null;
return (T)params[i];
}
@SuppressWarnings("unchecked")
public <T> T get(int i, T defaultVal) {
if (i >= params.length || i < 0) return defaultVal;
return (T)params[i];
}
public boolean match(Object ...args) {
if (args.length != params.length) return false;
for (int i = 0; i < args.length; i++) {
var a = params[i];
var b = args[i];
if (a == null || b == null) {
if (!(a == null && b == null)) return false;
}
if (!a.equals(b)) return false;
}
return true;
}
public boolean is(int i, Object arg) {
if (params.length <= i) return false;
return params[i].equals(arg);
}
public void write(DataOutputStream writer) throws IOException {
var rawType = type.numeric;
switch (type) {
case KEYS:
case PUSH_BOOL:
case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break;
case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break;
case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
default:
}
writer.writeByte(rawType);
switch (type) {
case CALL: writer.writeInt(get(0)); break;
case CALL_NEW: writer.writeInt(get(0)); break;
case DUP: writer.writeInt(get(0)); break;
case JMP: writer.writeInt(get(0)); break;
case JMP_IF: writer.writeInt(get(0)); break;
case JMP_IFN: writer.writeInt(get(0)); break;
case LOAD_ARR: writer.writeInt(get(0)); break;
case LOAD_FUNC: {
writer.writeInt(params.length - 1);
for (var i = 0; i < params.length; i++) {
writer.writeInt(get(i + 1));
}
writer.writeInt(get(0));
break;
}
case LOAD_REGEX: writer.writeUTF(get(0)); break;
case LOAD_VAR: writer.writeInt(get(0)); break;
case MAKE_VAR: writer.writeUTF(get(0)); break;
case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
case PUSH_NUMBER: writer.writeDouble(get(0)); break;
case PUSH_STRING: writer.writeUTF(get(0)); break;
case STORE_SELF_FUNC: writer.writeInt(get(0)); break;
case STORE_VAR: writer.writeInt(get(0)); break;
case THROW_SYNTAX: writer.writeUTF(get(0));
case TRY_START:
writer.writeInt(get(0));
writer.writeInt(get(1));
writer.writeInt(get(2));
break;
case TYPEOF:
if (params.length > 0) writer.writeUTF(get(0));
break;
default:
}
}
private Instruction(Type type, Object ...params) {
this.type = type;
this.params = params;
}
public static Instruction read(DataInputStream stream) throws IOException {
var rawType = stream.readUnsignedByte();
var type = Type.fromNumeric(rawType & 127);
var flag = (rawType & 128) != 0;
switch (type) {
case CALL: return call(stream.readInt());
case CALL_NEW: return callNew(stream.readInt());
case DEF_PROP: return defProp();
case DELETE: return delete();
case DISCARD: return discard();
case DUP: return dup(stream.readInt());
case JMP: return jmp(stream.readInt());
case JMP_IF: return jmpIf(stream.readInt());
case JMP_IFN: return jmpIfNot(stream.readInt());
case KEYS: return keys(flag);
case LOAD_ARR: return loadArr(stream.readInt());
case LOAD_FUNC: {
var captures = new int[stream.readInt()];
for (var i = 0; i < captures.length; i++) {
captures[i] = stream.readInt();
}
return loadFunc(stream.readInt(), captures);
}
case LOAD_GLOB: return loadGlob();
case LOAD_MEMBER: return loadMember();
case LOAD_OBJ: return loadObj();
case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
case LOAD_VAR: return loadVar(stream.readInt());
case MAKE_VAR: return makeVar(stream.readUTF());
case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
case PUSH_NULL: return pushNull();
case PUSH_UNDEFINED: return pushUndefined();
case PUSH_BOOL: return pushValue(flag);
case PUSH_NUMBER: return pushValue(stream.readDouble());
case PUSH_STRING: return pushValue(stream.readUTF());
case RETURN: return ret();
case STORE_MEMBER: return storeMember(flag);
case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt());
case STORE_VAR: return storeVar(stream.readInt(), flag);
case THROW: return throwInstr();
case THROW_SYNTAX: return throwSyntax(stream.readUTF());
case TRY_END: return tryEnd();
case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt());
case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
case NOP:
if (flag) return null;
else return nop();
default: return null;
}
}
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
}
public static Instruction tryEnd() {
return new Instruction(Type.TRY_END);
}
public static Instruction throwInstr() {
return new Instruction(Type.THROW);
}
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
}
public static Instruction throwSyntax(String err) {
return new Instruction(Type.THROW_SYNTAX, err);
}
public static Instruction delete() {
return new Instruction(Type.DELETE);
}
public static Instruction ret() {
return new Instruction(Type.RETURN);
}
public static Instruction debug() {
return new Instruction(Type.NOP, "debug");
}
public static Instruction nop(Object ...params) {
return new Instruction(Type.NOP, params);
}
public static Instruction call(int argn) {
return new Instruction(Type.CALL, argn);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn);
}
public static Instruction jmp(int offset) {
return new Instruction(Type.JMP, offset);
}
public static Instruction jmpIf(int offset) {
return new Instruction(Type.JMP_IF, offset);
}
public static Instruction jmpIfNot(int offset) {
return new Instruction(Type.JMP_IFN, offset);
}
public static Instruction pushUndefined() {
return new Instruction(Type.PUSH_UNDEFINED);
}
public static Instruction pushNull() {
return new Instruction(Type.PUSH_NULL);
}
public static Instruction pushValue(boolean val) {
return new Instruction(Type.PUSH_BOOL, val);
}
public static Instruction pushValue(double val) {
return new Instruction(Type.PUSH_NUMBER, val);
}
public static Instruction pushValue(String val) {
return new Instruction(Type.PUSH_STRING, val);
}
public static Instruction makeVar(String name) {
return new Instruction(Type.MAKE_VAR, name);
}
public static Instruction loadVar(Object i) {
return new Instruction(Type.LOAD_VAR, i);
}
public static Instruction loadGlob() {
return new Instruction(Type.LOAD_GLOB);
}
public static Instruction loadMember() {
return new Instruction(Type.LOAD_MEMBER);
}
public static Instruction loadRegex(String pattern, String flags) {
return new Instruction(Type.LOAD_REGEX, pattern, flags);
}
public static Instruction loadFunc(int id, int[] captures) {
var args = new Object[1 + captures.length];
args[0] = id;
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
return new Instruction(Type.LOAD_FUNC, args);
}
public static Instruction loadObj() {
return new Instruction(Type.LOAD_OBJ);
}
public static Instruction loadArr(int count) {
return new Instruction(Type.LOAD_ARR, count);
}
public static Instruction dup() {
return new Instruction(Type.DUP, 1);
}
public static Instruction dup(int count) {
return new Instruction(Type.DUP, count);
}
public static Instruction storeSelfFunc(int i) {
return new Instruction(Type.STORE_SELF_FUNC, i);
}
public static Instruction storeVar(Object i) {
return new Instruction(Type.STORE_VAR, i, false);
}
public static Instruction storeVar(Object i, boolean keep) {
return new Instruction(Type.STORE_VAR, i, keep);
}
public static Instruction storeMember() {
return new Instruction(Type.STORE_MEMBER, false);
}
public static Instruction storeMember(boolean keep) {
return new Instruction(Type.STORE_MEMBER, keep);
}
public static Instruction discard() {
return new Instruction(Type.DISCARD);
}
public static Instruction typeof() {
return new Instruction(Type.TYPEOF);
}
public static Instruction typeof(String varName) {
return new Instruction(Type.TYPEOF, varName);
}
public static Instruction keys(boolean forInFormat) {
return new Instruction(Type.KEYS, forInFormat);
}
public static Instruction defProp() {
return new Instruction(Type.DEF_PROP);
}
public static Instruction operation(Operation op) {
return new Instruction(Type.OPERATION, op);
}
@Override
public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
}

View File

@@ -0,0 +1,101 @@
package me.topchetoeu.jscript.common;
import java.util.ArrayList;
public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native"));
private int line;
private int start;
private Filename filename;
public int line() { return line; }
public int start() { return start; }
public Filename filename() { return filename; }
@Override
public String toString() {
var res = new ArrayList<String>();
if (filename != null) res.add(filename.toString());
if (line >= 0) res.add(line + "");
if (start >= 0) res.add(start + "");
return String.join(":", res);
}
public Location add(int n, boolean clone) {
if (clone) return new Location(line, start + n, filename);
this.start += n;
return this;
}
public Location add(int n) {
return add(n, false);
}
public Location nextLine() {
line++;
start = 0;
return this;
}
public Location clone() {
return new Location(line, start, filename);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + line;
result = prime * result + start;
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Location other = (Location) obj;
if (line != other.line) return false;
if (start != other.start) return false;
if (filename == null && other.filename != null) return false;
else if (!filename.equals(other.filename)) return false;
return true;
}
@Override
public int compareTo(Location other) {
int a = filename.toString().compareTo(other.filename.toString());
int b = Integer.compare(line, other.line);
int c = Integer.compare(start, other.start);
if (a != 0) return a;
if (b != 0) return b;
return c;
}
public Location(int line, int start, Filename filename) {
this.line = line;
this.start = start;
this.filename = filename;
}
public static Location parse(String raw) {
int i0 = -1, i1 = -1;
for (var i = raw.length() - 1; i >= 0; i--) {
if (raw.charAt(i) == ':') {
if (i1 == -1) i1 = i;
else if (i0 == -1) {
i0 = i;
break;
}
}
}
return new Location(
Integer.parseInt(raw.substring(i0 + 1, i1)),
Integer.parseInt(raw.substring(i1 + 1)),
Filename.parse(raw.substring(0, i0))
);
}
}

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,54 @@
package me.topchetoeu.jscript.common;
import java.util.HashMap;
public enum Operation {
INSTANCEOF(1, 2),
IN(2, 2),
MULTIPLY(3, 2),
DIVIDE(4, 2),
MODULO(5, 2),
ADD(6, 2),
SUBTRACT(7, 2),
USHIFT_RIGHT(8, 2),
SHIFT_RIGHT(9, 2),
SHIFT_LEFT(10, 2),
GREATER(11, 2),
LESS(12, 2),
GREATER_EQUALS(13, 2),
LESS_EQUALS(14, 2),
LOOSE_EQUALS(15, 2),
LOOSE_NOT_EQUALS(16, 2),
EQUALS(17, 2),
NOT_EQUALS(18, 2),
AND(19, 2),
OR(20, 2),
XOR(21, 2),
NEG(23, 1),
POS(24, 1),
NOT(25, 1),
INVERSE(26, 1);
private static final HashMap<Integer, Operation> operations = new HashMap<>();
static {
for (var val : Operation.values()) operations.put(val.numeric, val);
}
public final int numeric;
public final int operands;
private Operation(int numeric, int n) {
this.numeric = numeric;
this.operands = n;
}
public static Operation fromNumeric(int i) {
return operations.get(i);
}
}

View File

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

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.common;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class RefTracker {
public static void onDestroy(Object obj, Runnable runnable) {
var queue = new ReferenceQueue<>();
var ref = new WeakReference<>(obj, queue);
obj = null;
var th = new Thread(() -> {
try {
queue.remove();
ref.get();
runnable.run();
}
catch (InterruptedException e) { return; }
});
th.setDaemon(true);
th.start();
}
}

View File

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

View File

@@ -1,6 +1,6 @@
package me.topchetoeu.jscript.events;
package me.topchetoeu.jscript.common.events;
public class DataNotifier<T> implements Awaitable<T> {
public class DataNotifier<T> {
private Notifier notifier = new Notifier();
private boolean isErr;
private T val;
@@ -11,15 +11,12 @@ public class DataNotifier<T> implements Awaitable<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 {
public T await() {
notifier.await();
try {

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.json;
package me.topchetoeu.jscript.common.json;
public class JSONElement {
public static enum Type {
@@ -65,10 +65,22 @@ public class JSONElement {
return (double)value;
}
public boolean bool() {
if (!isNumber()) throw new IllegalStateException("Element is not a boolean.");
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
@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,4 +1,4 @@
package me.topchetoeu.jscript.json;
package me.topchetoeu.jscript.common.json;
import java.util.ArrayList;
import java.util.Collection;
@@ -10,6 +10,9 @@ public class JSONList extends ArrayList<JSONElement> {
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; }
@@ -17,5 +20,7 @@ public class JSONList extends ArrayList<JSONElement> {
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(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,4 +1,4 @@
package me.topchetoeu.jscript.json;
package me.topchetoeu.jscript.common.json;
import java.util.Collection;
import java.util.HashMap;
@@ -51,7 +51,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONMap map(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
@@ -63,7 +63,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONList list(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
@@ -75,7 +75,7 @@ public class JSONMap implements Map<String, JSONElement> {
public String string(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
@@ -87,7 +87,7 @@ public class JSONMap implements Map<String, JSONElement> {
public double number(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
@@ -99,7 +99,7 @@ public class JSONMap implements Map<String, JSONElement> {
public boolean bool(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.common.mapping;
public enum ConvertType {
Exact,
Lower,
Upper,
Both,
}

View File

@@ -0,0 +1,190 @@
package me.topchetoeu.jscript.common.mapping;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.utils.mapping.SourceMap;
public class FunctionMap {
public static class FunctionMapBuilder {
private final TreeMap<Integer, Location> sourceMap = new TreeMap<>();
private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>();
public Location toLocation(int pc) {
return sourceMap.headMap(pc, true).firstEntry().getValue();
}
public FunctionMapBuilder setDebug(Location loc, BreakpointType type) {
if (loc == null || type == null || type == BreakpointType.NONE) return this;
breakpoints.put(loc, type);
return this;
}
public FunctionMapBuilder setLocation(int i, Location loc) {
if (loc == null || i < 0) return this;
sourceMap.put(i, loc);
return this;
}
public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) {
setDebug(loc, type);
setLocation(i, loc);
return this;
}
public Location first() {
if (sourceMap.size() == 0) return null;
return sourceMap.firstEntry().getValue();
}
public Location last() {
if (sourceMap.size() == 0) return null;
return sourceMap.lastEntry().getValue();
}
public FunctionMap build(String[] localNames, String[] captureNames) {
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
}
public FunctionMap build(LocalScopeRecord scope) {
return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures());
}
public FunctionMap build() {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
}
private FunctionMapBuilder() { }
}
public static final FunctionMap EMPTY = new FunctionMap();
private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
public final String[] localNames, captureNames;
public Location toLocation(int pc, boolean approxiamte) {
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
var res = pcToLoc.get(pc);
if (!approxiamte || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry();
if (entry == null) return null;
else return entry.getValue();
}
public Location toLocation(int pc) {
return toLocation(pc, false);
}
public BreakpointType getBreakpoint(int pc) {
return bps.getOrDefault(pc, BreakpointType.NONE);
}
public Location correctBreakpoint(Location loc) {
var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
}
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
var candidates = new HashMap<Filename, TreeSet<Location>>();
for (var name : bpLocs.keySet()) {
if (filename.matcher(name.toString()).matches()) {
candidates.put(name, bpLocs.get(name));
}
}
var res = new ArrayList<Location>(candidates.size());
for (var candidate : candidates.entrySet()) {
res.add(candidate.getValue().ceiling(new Location(line, column, candidate.getKey())));
}
return res;
}
public List<Location> breakpoints(Location start, Location end) {
if (!Objects.equals(start.filename(), end.filename())) return List.of();
NavigableSet<Location> set = bpLocs.get(start.filename());
if (set == null) return List.of();
if (start != null) set = set.tailSet(start, true);
if (end != null) set = set.headSet(end, true);
return set.stream().collect(Collectors.toList());
}
public Location start() {
if (pcToLoc.size() == 0) return null;
return pcToLoc.firstEntry().getValue();
}
public Location end() {
if (pcToLoc.size() == 0) return null;
return pcToLoc.lastEntry().getValue();
}
public FunctionMap apply(SourceMap map) {
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
for (var el : pcToLoc.entrySet()) {
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
}
res.bps.putAll(bps);
for (var el : bpLocs.entrySet()) {
for (var loc : el.getValue()) {
loc = map.toCompiled(loc);
if (loc == null) continue;
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
res.bpLocs.get(loc.filename()).add(loc);
}
}
return res;
}
public FunctionMap clone() {
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
res.pcToLoc.putAll(this.pcToLoc);
res.bps.putAll(bps);
res.bpLocs.putAll(bpLocs);
res.pcToLoc.putAll(pcToLoc);
return res;
}
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) {
var locToPc = new HashMap<Location, Integer>();
for (var el : map.entrySet()) {
pcToLoc.put(el.getKey(), el.getValue());
locToPc.putIfAbsent(el.getValue(), el.getKey());
}
for (var el : breakpoints.entrySet()) {
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
bps.put(locToPc.get(el.getKey()), el.getValue());
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
bpLocs.get(el.getKey().filename()).add(el.getKey());
}
this.localNames = localNames;
this.captureNames = captureNames;
}
private FunctionMap() {
localNames = new String[0];
captureNames = new String[0];
}
public static FunctionMapBuilder builder() {
return new FunctionMapBuilder();
}
}

View File

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

View File

@@ -0,0 +1,78 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.LinkedList;
import java.util.Vector;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
public class CompileResult {
public final Vector<Instruction> instructions = new Vector<>();
public final List<CompileResult> children = new LinkedList<>();
public final FunctionMapBuilder map = FunctionMap.builder();
public final LocalScopeRecord scope;
public int length = 0;
public int temp() {
instructions.add(null);
return instructions.size() - 1;
}
public CompileResult add(Instruction instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, instr);
return this;
}
public Instruction get(int i) {
return instructions.get(i);
}
public int size() { return instructions.size(); }
public void setDebug(Location loc, BreakpointType type) {
map.setDebug(loc, type);
}
public void setLocation(int i, Location loc) {
map.setLocation(i, loc);
}
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
map.setLocationAndDebug(i, loc, type);
}
public void setDebug(BreakpointType type) {
setDebug(map.last(), type);
}
public void setLocation(Location type) {
setLocation(instructions.size() - 1, type);
}
public void setLocationAndDebug(Location loc, BreakpointType type) {
setLocationAndDebug(instructions.size() - 1, loc, type);
}
public CompileResult addChild(CompileResult child) {
this.children.add(child);
return child;
}
public FunctionMap map() {
return map.build(scope);
}
public FunctionBody body() {
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren);
}
public CompileResult(LocalScopeRecord scope) {
this.scope = scope;
}
}

View File

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

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
public abstract class Statement {
private Location _loc;
public boolean pure() { return false; }
public void declare(CompileResult target) { }
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
int start = target.size();
compile(target, pollute);
if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
}
public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.NONE);
}
public Location loc() { return _loc; }
public void setLoc(Location loc) { _loc = loc; }
protected Statement(Location loc) {
this._loc = loc;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class DebugStatement extends Statement {
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.debug());
if (pollute) target.add(Instruction.pushUndefined());
}
public DebugStatement(Location loc) {
super(loc);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ForOfStatement extends Statement {
public final String varName;
public final boolean isDeclaration;
public final Statement iterable, body;
public final String label;
public final Location varLocation;
@Override
public void declare(CompileResult target) {
body.declare(target);
if (isDeclaration) target.scope.define(varName);
}
@Override
public void compile(CompileResult target, boolean pollute) {
var key = target.scope.getKey(varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
iterable.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.dup());
target.add(Instruction.loadVar("Symbol"));
target.add(Instruction.pushValue("iterator"));
target.add(Instruction.loadMember()).setLocation(iterable.loc());
target.add(Instruction.loadMember()).setLocation(iterable.loc());
target.add(Instruction.call(0)).setLocation(iterable.loc());
int start = target.size();
target.add(Instruction.dup());
target.add(Instruction.dup());
target.add(Instruction.pushValue("next"));
target.add(Instruction.loadMember()).setLocation(iterable.loc());
target.add(Instruction.call(0)).setLocation(iterable.loc());
target.add(Instruction.dup());
target.add(Instruction.pushValue("done"));
target.add(Instruction.loadMember()).setLocation(iterable.loc());
int mid = target.temp();
target.add(Instruction.pushValue("value"));
target.add(Instruction.loadMember()).setLocation(varLocation);
target.add(Instruction.storeVar(key)).setLocationAndDebug(iterable.loc(), BreakpointType.STEP_OVER);
body.compile(target, false, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end));
target.add(Instruction.discard());
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(end - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForOfStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement object, Statement body) {
super(loc);
this.varLocation = varLocation;
this.label = label;
this.isDeclaration = isDecl;
this.varName = varName;
this.iterable = object;
this.body = body;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class TryStatement extends Statement {
public final Statement tryBody;
public final Statement catchBody;
public final Statement finallyBody;
public final String name;
@Override
public void declare(CompileResult target) {
tryBody.declare(target);
if (catchBody != null) catchBody.declare(target);
if (finallyBody != null) finallyBody.declare(target);
}
@Override
public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
int replace = target.temp();
int start = replace + 1, catchStart = -1, finallyStart = -1;
tryBody.compile(target, false);
target.add(Instruction.tryEnd());
if (catchBody != null) {
catchStart = target.size() - start;
target.scope.define(name, true);
catchBody.compile(target, false);
target.scope.undefine();
target.add(Instruction.tryEnd());
}
if (finallyBody != null) {
finallyStart = target.size() - start;
finallyBody.compile(target, false);
target.add(Instruction.tryEnd());
}
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
if (pollute) target.add(Instruction.pushUndefined());
}
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
super(loc);
this.tryBody = tryBody;
this.catchBody = catchBody;
this.finallyBody = finallyBody;
this.name = name;
}
}

View File

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

View File

@@ -1,9 +1,9 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.common.Operation;
public enum Operator {
MULTIPLY("*", Operation.MULTIPLY, 13),
@@ -55,7 +55,7 @@ public enum Operator {
INCREASE("++"),
DECREASE("--");
public final String value;
public final String readable;
public final Operation operation;
public final int precedence;
public final boolean reverse;
@@ -63,7 +63,7 @@ public enum Operator {
static {
for (var el : Operator.values()) {
ops.put(el.value, el);
ops.put(el.readable, el);
}
}
@@ -74,38 +74,38 @@ public enum Operator {
}
private Operator() {
this.value = null;
this.readable = null;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value) {
this. value = value;
this.readable = value;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value, int precedence) {
this. value = value;
this.readable = value;
this.operation = null;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, int precedence, boolean reverse) {
this. value = value;
this.readable = value;
this.operation = null;
this.precedence = precedence;
this.reverse = reverse;
}
private Operator(String value, Operation funcName, int precedence) {
this. value = value;
this.readable = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
this.value = value;
this.readable = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = reverse;

View File

@@ -1,10 +1,10 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.parsing.Parsing.Parser;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser;
public class ParseRes<T> {
public static enum State {
@@ -69,6 +69,9 @@ public class ParseRes<T> {
@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();

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
import java.util.ArrayList;
import java.util.Collection;
@@ -7,33 +7,31 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.*;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.ParseRes.State;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
// TODO: this has to be rewritten
public class Parsing {
public static interface Parser<T> {
ParseRes<T> parse(String filename, List<Token> tokens, int i);
ParseRes<T> parse(Filename filename, List<Token> tokens, int i);
}
private static class ObjProp {
public final Object name;
public final String name;
public final String access;
public final FunctionStatement func;
public ObjProp(Object name, String access, FunctionStatement func) {
public ObjProp(String name, String access, FunctionStatement func) {
this.name = name;
this.access = access;
this.func = func;
@@ -69,7 +67,7 @@ public class Parsing {
reserved.add("delete");
reserved.add("break");
reserved.add("continue");
reserved.add("debug");
reserved.add("debugger");
reserved.add("implements");
reserved.add("interface");
reserved.add("package");
@@ -80,8 +78,6 @@ public class Parsing {
// Although ES5 allow these, we will comply to ES6 here
reserved.add("const");
reserved.add("let");
reserved.add("async");
reserved.add("super");
// These are allowed too, however our parser considers them keywords
reserved.add("undefined");
reserved.add("arguments");
@@ -91,7 +87,6 @@ public class Parsing {
// We allow yield and await, because they're part of the custom async and generator functions
}
public static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
@@ -122,7 +117,7 @@ public class Parsing {
private static final int CURR_MULTI_COMMENT = 8;
private static final int CURR_SINGLE_COMMENT = 9;
private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, String filename, List<RawToken> tokens) {
private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List<RawToken> tokens) {
var res = currToken.toString();
switch (currStage) {
@@ -143,7 +138,7 @@ public class Parsing {
// This method is so long because we're tokenizing the string using an iterative approach
// instead of a recursive descent parser. This is mainly done for performance reasons.
private static ArrayList<RawToken> splitTokens(String filename, String raw) {
private static ArrayList<RawToken> splitTokens(Filename filename, String raw) {
var tokens = new ArrayList<RawToken>();
var currToken = new StringBuilder(64);
@@ -219,8 +214,10 @@ public class Parsing {
currToken.append(c);
continue;
case CURR_SCIENTIFIC_NOT:
if (c == '-') currStage = CURR_NEG_SCIENTIFIC_NOT;
else if (!isDigit(c)) {
if (c == '-') {
if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT;
}
if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) {
i--; start--;
break;
}
@@ -257,7 +254,7 @@ public class Parsing {
}
break;
case CURR_LITERAL:
if (isAlphanumeric(c) || c == '_') {
if (isAlphanumeric(c) || c == '_' || c == '$') {
currToken.append(c);
continue;
}
@@ -397,13 +394,17 @@ public class Parsing {
return tokens;
}
private static int fromHex(char c) {
public static int fromHex(char c) {
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0';
return -1;
}
private static boolean inBounds(List<Token> tokens, int i) {
return i >= 0 && i < tokens.size();
}
private static String parseString(Location loc, String literal) {
var res = new StringBuilder();
@@ -568,20 +569,21 @@ public class Parsing {
}
private static double parseNumber(Location loc, String value) {
var res = parseNumber(false, value);
if (res == null) throw new SyntaxException(loc, "Invalid number format.");
if (res == null)
throw new SyntaxException(loc, "Invalid number format.");
else return res;
}
private static List<Token> parseTokens(String filename, Collection<RawToken> tokens) {
private static List<Token> parseTokens(Filename filename, Collection<RawToken> tokens) {
var res = new ArrayList<Token>();
for (var el : tokens) {
var loc = new Location(el.line, el.start, filename);
switch (el.type) {
case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break;
case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value))); break;
case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value))); break;
case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value))); break;
case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value), el.value)); break;
case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value), el.value)); break;
case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value), el.value)); break;
case OPERATOR:
Operator op = Operator.parse(el.value);
if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value));
@@ -593,11 +595,11 @@ public class Parsing {
return res;
}
public static List<Token> tokenize(String filename, String raw) {
public static List<Token> tokenize(Filename filename, String raw) {
return parseTokens(filename, splitTokens(filename, raw));
}
public static Location getLoc(String filename, List<Token> tokens, int i) {
public static Location getLoc(Filename filename, List<Token> tokens, int i) {
if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename);
if (i >= tokens.size()) i = tokens.size() - 1;
return new Location(tokens.get(i).line, tokens.get(i).start, filename);
@@ -608,49 +610,41 @@ public class Parsing {
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isIdentifier()) {
return ParseRes.res(tokens.get(i).identifier(), 1);
}
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
}
else return ParseRes.failed();
}
public static ParseRes<Operator> parseOperator(List<Token> tokens, int i) {
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isOperator()) {
return ParseRes.res(tokens.get(i).operator(), 1);
}
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
}
else return ParseRes.failed();
}
public static boolean isIdentifier(List<Token> tokens, int i, String lit) {
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isIdentifier(lit)) {
return true;
}
else return false;
}
catch (IndexOutOfBoundsException e) {
return false;
}
else return false;
}
public static boolean isOperator(List<Token> tokens, int i, Operator op) {
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isOperator(op)) {
return true;
}
else return false;
}
catch (IndexOutOfBoundsException e) {
return false;
}
else return false;
}
public static boolean isStatementEnd(List<Token> tokens, int i) {
if (isOperator(tokens, i, Operator.SEMICOLON)) return true;
@@ -663,34 +657,29 @@ public class Parsing {
return !reserved.contains(name);
}
public static ParseRes<ConstantStatement> parseString(String filename, List<Token> tokens, int i) {
public static ParseRes<ConstantStatement> parseString(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isString()) {
return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1);
}
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
else return ParseRes.failed();
}
}
public static ParseRes<ConstantStatement> parseNumber(String filename, List<Token> tokens, int i) {
public static ParseRes<ConstantStatement> parseNumber(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isNumber()) {
return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1);
}
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
else return ParseRes.failed();
}
}
public static ParseRes<RegexStatement> parseRegex(String filename, List<Token> tokens, int i) {
public static ParseRes<RegexStatement> parseRegex(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
try {
if (inBounds(tokens, i)) {
if (tokens.get(i).isRegex()) {
var val = tokens.get(i).regex();
var index = val.lastIndexOf('/');
@@ -700,12 +689,10 @@ public class Parsing {
}
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
}
}
public static ParseRes<ArrayStatement> parseArray(String filename, List<Token> tokens, int i) {
public static ParseRes<ArrayStatement> parseArray(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
@@ -744,7 +731,7 @@ public class Parsing {
return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n);
}
public static ParseRes<List<String>> parseParamList(String filename, List<Token> tokens, int i) {
public static ParseRes<List<String>> parseParamList(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -776,17 +763,18 @@ public class Parsing {
return ParseRes.res(args, n);
}
public static ParseRes<? extends Object> parsePropName(String filename, List<Token> tokens, int i) {
var idRes = parseIdentifier(tokens, i);
if (idRes.isSuccess()) return ParseRes.res(idRes.result, 1);
var strRes = parseString(null, tokens, i);
if (strRes.isSuccess()) return ParseRes.res(strRes.result.value, 1);
var numRes = parseNumber(null, tokens, i);
if (numRes.isSuccess()) return ParseRes.res(numRes.result.value, 1);
public static ParseRes<String> parsePropName(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
return ParseRes.failed();
if (inBounds(tokens, i)) {
var token = tokens.get(i);
if (token.isNumber() || token.isIdentifier() || token.isString()) return ParseRes.res(token.rawValue, 1);
else return ParseRes.error(loc, "Expected identifier, string or number literal.");
}
public static ParseRes<ObjProp> parseObjectProp(String filename, List<Token> tokens, int i) {
else return ParseRes.failed();
}
public static ParseRes<ObjProp> parseObjectProp(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -808,19 +796,21 @@ public class Parsing {
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res);
n += res.n;
var end = getLoc(filename, tokens, i + n - 1);
return ParseRes.res(new ObjProp(
name, access,
new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result)
new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result)
), n);
}
public static ParseRes<ObjectStatement> parseObject(String filename, List<Token> tokens, int i) {
public static ParseRes<ObjectStatement> parseObject(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
var values = new LinkedHashMap<Object, Statement>();
var getters = new LinkedHashMap<Object, FunctionStatement>();
var setters = new LinkedHashMap<Object, FunctionStatement>();
var values = new LinkedHashMap<String, Statement>();
var getters = new LinkedHashMap<String, FunctionStatement>();
var setters = new LinkedHashMap<String, FunctionStatement>();
if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
@@ -870,7 +860,7 @@ public class Parsing {
return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n);
}
public static ParseRes<NewStatement> parseNew(String filename, List<Token> tokens, int i) {
public static ParseRes<CallStatement> parseNew(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
var n = 0;
if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed();
@@ -881,12 +871,12 @@ public class Parsing {
var callRes = parseCall(filename, tokens, i + n, valRes.result, 0);
n += callRes.n;
if (callRes.isError()) return callRes.transform();
else if (callRes.isFailed()) return ParseRes.res(new NewStatement(loc, valRes.result), n);
else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n);
var call = (CallStatement)callRes.result;
return ParseRes.res(new NewStatement(loc, call.func, call.args), n);
return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n);
}
public static ParseRes<TypeofStatement> parseTypeof(String filename, List<Token> tokens, int i) {
public static ParseRes<TypeofStatement> parseTypeof(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
var n = 0;
if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed();
@@ -897,7 +887,7 @@ public class Parsing {
return ParseRes.res(new TypeofStatement(loc, valRes.result), n);
}
public static ParseRes<VoidStatement> parseVoid(String filename, List<Token> tokens, int i) {
public static ParseRes<DiscardStatement> parseVoid(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
var n = 0;
if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed();
@@ -906,9 +896,9 @@ public class Parsing {
if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes);
n += valRes.n;
return ParseRes.res(new VoidStatement(loc, valRes.result), n);
return ParseRes.res(new DiscardStatement(loc, valRes.result), n);
}
public static ParseRes<? extends Statement> parseDelete(String filename, List<Token> tokens, int i) {
public static ParseRes<? extends Statement> parseDelete(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed();
@@ -929,7 +919,7 @@ public class Parsing {
}
}
public static ParseRes<FunctionStatement> parseFunction(String filename, List<Token> tokens, int i, boolean statement) {
public static ParseRes<FunctionStatement> parseFunction(Filename filename, List<Token> tokens, int i, boolean statement) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -967,12 +957,13 @@ public class Parsing {
var res = parseCompound(filename, tokens, i + n);
n += res.n;
var end = getLoc(filename, tokens, i + n - 1);
if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, name, args.toArray(String[]::new), res.result), n);
if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n);
else return ParseRes.error(loc, "Expected a compound statement for function.", res);
}
public static ParseRes<OperationStatement> parseUnary(String filename, List<Token> tokens, int i) {
public static ParseRes<OperationStatement> parseUnary(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -991,9 +982,9 @@ public class Parsing {
var res = parseValue(filename, tokens, n + i, 14);
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.value), res);
else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.readable), res);
}
public static ParseRes<ChangeStatement> parsePrefixChange(String filename, List<Token> tokens, int i) {
public static ParseRes<ChangeStatement> parsePrefixChange(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1010,7 +1001,7 @@ public class Parsing {
if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator.");
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n);
}
public static ParseRes<? extends Statement> parseParens(String filename, List<Token> tokens, int i) {
public static ParseRes<? extends Statement> parseParens(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed();
@@ -1022,9 +1013,8 @@ public class Parsing {
return ParseRes.res(res.result, n);
}
@SuppressWarnings("all")
public static ParseRes<? extends Statement> parseSimple(String filename, List<Token> tokens, int i, boolean statement) {
var res = new ArrayList<>();
public static ParseRes<? extends Statement> parseSimple(Filename filename, List<Token> tokens, int i, boolean statement) {
var res = new ArrayList<ParseRes<? extends Statement>>();
if (!statement) {
res.add(parseObject(filename, tokens, i));
@@ -1047,10 +1037,10 @@ public class Parsing {
parseDelete(filename, tokens, i)
));
return ParseRes.any(res.toArray(ParseRes[]::new));
return ParseRes.any(res);
}
public static ParseRes<VariableStatement> parseVariable(String filename, List<Token> tokens, int i) {
public static ParseRes<VariableStatement> parseVariable(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
var literal = parseIdentifier(tokens, i);
@@ -1058,7 +1048,6 @@ public class Parsing {
if (!checkVarName(literal.result)) {
if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported.");
if (literal.result.equals("async")) return ParseRes.error(loc, "'async' is not supported.");
if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported.");
if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported.");
return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result));
@@ -1066,7 +1055,7 @@ public class Parsing {
return ParseRes.res(new VariableStatement(loc, literal.result), 1);
}
public static ParseRes<? extends Statement> parseLiteral(String filename, List<Token> tokens, int i) {
public static ParseRes<? extends Statement> parseLiteral(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return id.transform();
@@ -1078,10 +1067,10 @@ public class Parsing {
return ParseRes.res(new ConstantStatement(loc, false), 1);
}
if (id.result.equals("undefined")) {
return ParseRes.res(new ConstantStatement(loc, null), 1);
return ParseRes.res(ConstantStatement.ofUndefined(loc), 1);
}
if (id.result.equals("null")) {
return ParseRes.res(new ConstantStatement(loc, Values.NULL), 1);
return ParseRes.res(ConstantStatement.ofNull(loc), 1);
}
if (id.result.equals("this")) {
return ParseRes.res(new VariableIndexStatement(loc, 0), 1);
@@ -1094,7 +1083,7 @@ public class Parsing {
}
return ParseRes.failed();
}
public static ParseRes<IndexStatement> parseMember(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<IndexStatement> parseMember(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1107,7 +1096,7 @@ public class Parsing {
return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n);
}
public static ParseRes<IndexStatement> parseIndex(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<IndexStatement> parseIndex(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1123,7 +1112,7 @@ public class Parsing {
return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n);
}
public static ParseRes<? extends Statement> parseAssign(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<? extends Statement> parseAssign(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0 ;
@@ -1135,10 +1124,12 @@ public class Parsing {
var op = opRes.result;
if (!op.isAssign()) return ParseRes.failed();
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
if (!(prev instanceof AssignableStatement)) {
return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
}
var res = parseValue(filename, tokens, i + n, 2);
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.value), res);
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.readable), res);
n += res.n;
Operation operation = null;
@@ -1157,7 +1148,7 @@ public class Parsing {
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n);
}
public static ParseRes<CallStatement> parseCall(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<CallStatement> parseCall(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1175,8 +1166,7 @@ public class Parsing {
prevArg = true;
}
else if (argRes.isError()) return argRes.transform();
else if (isOperator(tokens, i + n, Operator.COMMA)) {
if (!prevArg) args.add(null);
else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) {
prevArg = false;
n++;
}
@@ -1184,12 +1174,12 @@ public class Parsing {
n++;
break;
}
else return ParseRes.failed();
else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren.");
}
return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n);
return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n);
}
public static ParseRes<ChangeStatement> parsePostfixChange(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<ChangeStatement> parsePostfixChange(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1207,7 +1197,7 @@ public class Parsing {
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator.");
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n);
}
public static ParseRes<OperationStatement> parseInstanceof(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<OperationStatement> parseInstanceof(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1220,7 +1210,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
}
public static ParseRes<OperationStatement> parseIn(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<OperationStatement> parseIn(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1233,7 +1223,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
}
public static ParseRes<CompoundStatement> parseComma(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<CompoundStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1244,9 +1234,9 @@ public class Parsing {
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res);
n += res.n;
return ParseRes.res(new CompoundStatement(loc, prev, res.result), n);
return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n);
}
public static ParseRes<IfStatement> parseTernary(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<IfStatement> parseTernary(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1265,7 +1255,7 @@ public class Parsing {
return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n);
}
public static ParseRes<? extends Statement> parseOperator(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
public static ParseRes<? extends Statement> parseOperator(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1277,7 +1267,7 @@ public class Parsing {
if (op.isAssign()) return parseAssign(filename, tokens, i + n - 1, prev, precedence);
var res = parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1));
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.value), res);
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.readable), res);
n += res.n;
if (op == Operator.LAZY_AND) {
@@ -1290,7 +1280,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n);
}
public static ParseRes<? extends Statement> parseValue(String filename, List<Token> tokens, int i, int precedence, boolean statement) {
public static ParseRes<? extends Statement> parseValue(Filename filename, List<Token> tokens, int i, int precedence, boolean statement) {
Statement prev = null;
int n = 0;
@@ -1331,16 +1321,15 @@ public class Parsing {
if (prev == null) return ParseRes.failed();
else return ParseRes.res(prev, n);
}
public static ParseRes<? extends Statement> parseValue(String filename, List<Token> tokens, int i, int precedence) {
public static ParseRes<? extends Statement> parseValue(Filename filename, List<Token> tokens, int i, int precedence) {
return parseValue(filename, tokens, i, precedence, false);
}
public static ParseRes<? extends Statement> parseValueStatement(String filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
public static ParseRes<? extends Statement> parseValueStatement(Filename filename, List<Token> tokens, int i) {
var valRes = parseValue(filename, tokens, i, 0, true);
if (!valRes.isSuccess()) return valRes.transform();
valRes.result.setLoc(loc);
// valRes.result.setLoc(loc);
var res = ParseRes.res(valRes.result, valRes.n);
if (isStatementEnd(tokens, i + res.n)) {
@@ -1352,7 +1341,7 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res);
}
public static ParseRes<VariableDeclareStatement> parseVariableDeclare(String filename, List<Token> tokens, int i) {
public static ParseRes<VariableDeclareStatement> parseVariableDeclare(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed();
@@ -1365,6 +1354,7 @@ public class Parsing {
}
while (true) {
var nameLoc = getLoc(filename, tokens, i + n);
var nameRes = parseIdentifier(tokens, i + n++);
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name.");
@@ -1382,7 +1372,7 @@ public class Parsing {
val = valRes.result;
}
res.add(new Pair(nameRes.result, val));
res.add(new Pair(nameRes.result, val, nameLoc));
if (isOperator(tokens, i + n, Operator.COMMA)) {
n++;
@@ -1396,7 +1386,7 @@ public class Parsing {
}
}
public static ParseRes<ReturnStatement> parseReturn(String filename, List<Token> tokens, int i) {
public static ParseRes<ReturnStatement> parseReturn(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed();
@@ -1408,8 +1398,7 @@ public class Parsing {
var valRes = parseValue(filename, tokens, i + n, 0);
n += valRes.n;
if (valRes.isError())
return ParseRes.error(loc, "Expected a return value.", valRes);
if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes);
var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n);
@@ -1420,7 +1409,7 @@ public class Parsing {
else
return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
}
public static ParseRes<ThrowStatement> parseThrow(String filename, List<Token> tokens, int i) {
public static ParseRes<ThrowStatement> parseThrow(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed();
@@ -1438,7 +1427,7 @@ public class Parsing {
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
}
public static ParseRes<BreakStatement> parseBreak(String filename, List<Token> tokens, int i) {
public static ParseRes<BreakStatement> parseBreak(Filename filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "break")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) {
@@ -1456,7 +1445,7 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
}
public static ParseRes<ContinueStatement> parseContinue(String filename, List<Token> tokens, int i) {
public static ParseRes<ContinueStatement> parseContinue(Filename filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) {
@@ -1474,8 +1463,8 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
}
public static ParseRes<DebugStatement> parseDebug(String filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "debug")) return ParseRes.failed();
public static ParseRes<DebugStatement> parseDebug(Filename filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) {
if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2);
@@ -1484,7 +1473,7 @@ public class Parsing {
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
}
public static ParseRes<CompoundStatement> parseCompound(String filename, List<Token> tokens, int i) {
public static ParseRes<CompoundStatement> parseCompound(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@@ -1510,7 +1499,7 @@ public class Parsing {
statements.add(res.result);
}
return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)), n);
return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n);
}
public static ParseRes<String> parseLabel(List<Token> tokens, int i) {
int n = 0;
@@ -1520,7 +1509,7 @@ public class Parsing {
return ParseRes.res(nameRes.result, n);
}
public static ParseRes<IfStatement> parseIf(String filename, List<Token> tokens, int i) {
public static ParseRes<IfStatement> parseIf(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1531,8 +1520,7 @@ public class Parsing {
if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes);
n += condRes.n;
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE))
return ParseRes.error(loc, "Expected a closing paren after if condition.");
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition.");
var res = parseStatement(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res);
@@ -1547,7 +1535,7 @@ public class Parsing {
return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n);
}
public static ParseRes<WhileStatement> parseWhile(String filename, List<Token> tokens, int i) {
public static ParseRes<WhileStatement> parseWhile(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1569,7 +1557,7 @@ public class Parsing {
return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n);
}
public static ParseRes<Statement> parseSwitchCase(String filename, List<Token> tokens, int i) {
public static ParseRes<Statement> parseSwitchCase(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1589,7 +1577,7 @@ public class Parsing {
return ParseRes.res(null, 2);
}
public static ParseRes<SwitchStatement> parseSwitch(String filename, List<Token> tokens, int i) {
public static ParseRes<SwitchStatement> parseSwitch(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1649,7 +1637,7 @@ public class Parsing {
statements.toArray(Statement[]::new)
), n);
}
public static ParseRes<DoWhileStatement> parseDoWhile(String filename, List<Token> tokens, int i) {
public static ParseRes<DoWhileStatement> parseDoWhile(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1678,7 +1666,7 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon.");
}
public static ParseRes<Statement> parseFor(String filename, List<Token> tokens, int i) {
public static ParseRes<Statement> parseFor(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1692,15 +1680,14 @@ public class Parsing {
if (isOperator(tokens, i + n, Operator.SEMICOLON)) {
n++;
decl = new CompoundStatement(loc);
decl = new CompoundStatement(loc, false);
}
else {
var declRes = ParseRes.any(
parseVariableDeclare(filename, tokens, i + n),
parseValueStatement(filename, tokens, i + n)
);
if (!declRes.isSuccess())
return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
n += declRes.n;
decl = declRes.result;
}
@@ -1719,7 +1706,7 @@ public class Parsing {
if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) {
n++;
inc = new CompoundStatement(loc);
inc = new CompoundStatement(loc, false);
}
else {
var incRes = parseValue(filename, tokens, i + n, 0);
@@ -1736,7 +1723,7 @@ public class Parsing {
return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n);
}
public static ParseRes<ForInStatement> parseForIn(String filename, List<Token> tokens, int i) {
public static ParseRes<ForInStatement> parseForIn(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1754,6 +1741,7 @@ public class Parsing {
var nameRes = parseIdentifier(tokens, i + n);
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop.");
var nameLoc = getLoc(filename, tokens, i + n);
n += nameRes.n;
Statement varVal = null;
@@ -1787,9 +1775,49 @@ public class Parsing {
if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes);
n += bodyRes.n;
return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
}
public static ParseRes<TryStatement> parseCatch(String filename, List<Token> tokens, int i) {
public static ParseRes<ForOfStatement> parseForOf(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
var labelRes = parseLabel(tokens, i + n);
var isDecl = false;
n += labelRes.n;
if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed();
if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'.");
if (isIdentifier(tokens, i + n, "var")) {
isDecl = true;
n++;
}
var nameRes = parseIdentifier(tokens, i + n);
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop.");
var nameLoc = getLoc(filename, tokens, i + n);
n += nameRes.n;
if (!isIdentifier(tokens, i + n++, "of")) {
if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported.");
else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported.");
else return ParseRes.error(loc, "Expected 'of' keyword after variable declaration.");
}
var objRes = parseValue(filename, tokens, i + n, 0);
if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes);
n += objRes.n;
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for.");
var bodyRes = parseStatement(filename, tokens, i + n);
if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes);
n += bodyRes.n;
return ParseRes.res(new ForOfStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, objRes.result, bodyRes.result), n);
}
public static ParseRes<TryStatement> parseCatch(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1830,8 +1858,8 @@ public class Parsing {
return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n);
}
public static ParseRes<? extends Statement> parseStatement(String filename, List<Token> tokens, int i) {
if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1);
public static ParseRes<? extends Statement> parseStatement(Filename filename, List<Token> tokens, int i) {
if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1);
if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed.");
return ParseRes.any(
parseVariableDeclare(filename, tokens, i),
@@ -1845,6 +1873,7 @@ public class Parsing {
parseSwitch(filename, tokens, i),
parseFor(filename, tokens, i),
parseForIn(filename, tokens, i),
parseForOf(filename, tokens, i),
parseDoWhile(filename, tokens, i),
parseCatch(filename, tokens, i),
parseCompound(filename, tokens, i),
@@ -1853,7 +1882,7 @@ public class Parsing {
);
}
public static Statement[] parse(String filename, String raw) {
public static Statement[] parse(Filename filename, String raw) {
var tokens = tokenize(filename, raw);
var list = new ArrayList<Statement>();
int i = 0;
@@ -1874,41 +1903,31 @@ public class Parsing {
return list.toArray(Statement[]::new);
}
public static CodeFunction compile(HashMap<Long, Instruction[]> funcs, Environment environment, Statement ...statements) {
var target = environment.global.globalChild();
var subscope = target.child();
var res = new CompileTarget(funcs);
var body = new CompoundStatement(null, statements);
if (body instanceof CompoundStatement) body = (CompoundStatement)body;
else body = new CompoundStatement(null, new Statement[] { body });
public static CompileResult compile(Statement ...statements) {
var target = new CompileResult(new LocalScopeRecord());
var stm = new CompoundStatement(null, true, statements);
subscope.define("this");
subscope.define("arguments");
body.declare(target);
target.scope.define("this");
target.scope.define("arguments");
try {
body.compile(res, subscope, true);
FunctionStatement.checkBreakAndCont(res, 0);
stm.compile(target, true);
FunctionStatement.checkBreakAndCont(target, 0);
}
catch (SyntaxException e) {
res.target.clear();
res.add(Instruction.throwSyntax(e));
target = new CompileResult(new LocalScopeRecord());
target.scope.define("this");
target.scope.define("arguments");
target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
}
if (res.size() != 0 && res.get(res.size() - 1).type == Type.DISCARD) {
res.set(res.size() - 1, Instruction.ret());
}
else res.add(Instruction.ret());
target.add(Instruction.ret()).setLocation(stm.loc());
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.array());
}
public static CodeFunction compile(HashMap<Long, Instruction[]> funcs, Environment environment, String filename, String raw) {
try {
return compile(funcs, environment, parse(filename, raw));
}
catch (SyntaxException e) {
return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) });
return target;
}
public static CompileResult compile(Filename filename, String raw) {
return compile(parse(filename, raw));
}
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
public class RawToken {
public final String value;

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.parsing.ParseRes.State;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
public class TestRes {
public final State state;

View File

@@ -1,21 +1,24 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
public class Token {
public final Object value;
public final String rawValue;
public final boolean isString;
public final boolean isRegex;
public final int line;
public final int start;
private Token(int line, int start, Object value, boolean isString, boolean isRegex) {
private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) {
this.value = value;
this.rawValue = rawValue;
this.line = line;
this.start = start;
this.isString = isString;
this.isRegex = isRegex;
}
private Token(int line, int start, Object value) {
private Token(int line, int start, Object value, String rawValue) {
this.value = value;
this.rawValue = rawValue;
this.line = line;
this.start = start;
this.isString = false;
@@ -37,19 +40,19 @@ public class Token {
public String identifier() { return (String)value; }
public Operator operator() { return (Operator)value; }
public static Token regex(int line, int start, String val) {
return new Token(line, start, val, false, true);
public static Token regex(int line, int start, String val, String rawValue) {
return new Token(line, start, val, rawValue, false, true);
}
public static Token string(int line, int start, String val) {
return new Token(line, start, val, true, false);
public static Token string(int line, int start, String val, String rawValue) {
return new Token(line, start, val, rawValue, true, false);
}
public static Token number(int line, int start, double val) {
return new Token(line, start, val);
public static Token number(int line, int start, double val, String rawValue) {
return new Token(line, start, val, rawValue);
}
public static Token identifier(int line, int start, String val) {
return new Token(line, start, val);
return new Token(line, start, val, val);
}
public static Token operator(int line, int start, Operator val) {
return new Token(line, start, val);
return new Token(line, start, val, val.readable);
}
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.parsing;
package me.topchetoeu.jscript.compilation.parsing;
enum TokenType {
REGEX,

View File

@@ -1,25 +1,22 @@
package me.topchetoeu.jscript.engine.scope;
package me.topchetoeu.jscript.compilation.scope;
import java.util.ArrayList;
import me.topchetoeu.jscript.engine.Context;
public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent;
public final GlobalScope global;
private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() {
return locals.toArray(String[]::new);
}
@Override
public LocalScopeRecord parent() { return parent; }
public LocalScopeRecord child() {
return new LocalScopeRecord(this, global);
return new LocalScopeRecord(this);
}
public int localsCount() {
@@ -59,12 +56,6 @@ public class LocalScopeRecord implements ScopeRecord {
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);
@@ -77,12 +68,10 @@ public class LocalScopeRecord implements ScopeRecord {
locals.remove(locals.size() - 1);
}
public LocalScopeRecord(GlobalScope global) {
public LocalScopeRecord() {
this.parent = null;
this.global = global;
}
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
public LocalScopeRecord(LocalScopeRecord parent) {
this.parent = parent;
this.global = global;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class CallStatement extends Statement {
public final Statement func;
public final Statement[] args;
public final boolean isNew;
@Override
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
if (isNew) func.compile(target, true);
else if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, true, true);
}
else {
target.add(Instruction.pushUndefined());
func.compile(target, true);
}
for (var arg : args) arg.compile(target, true);
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type);
if (!pollute) target.add(Instruction.discard());
}
@Override
public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc);
this.isNew = isNew;
this.func = func;
this.args = args;
}
}

View File

@@ -1,12 +1,11 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement {
public final AssignableStatement value;
@@ -14,9 +13,13 @@ public class ChangeStatement extends Statement {
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, postfix);
if (!pollute) target.add(Instruction.discard().locate(loc()));
public void compile(CompileResult target, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true);
if (!pollute) target.add(Instruction.discard());
else if (postfix) {
target.add(Instruction.pushValue(addAmount));
target.add(Instruction.operation(Operation.SUBTRACT));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

@@ -0,0 +1,47 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ConstantStatement extends Statement {
public final Object value;
public final boolean isNull;
@Override public boolean pure() { return true; }
@Override
public void compile(CompileResult target, boolean pollute) {
if (pollute) {
if (isNull) target.add(Instruction.pushNull());
else if (value instanceof Double) target.add(Instruction.pushValue((Double)value));
else if (value instanceof String) target.add(Instruction.pushValue((String)value));
else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value));
else target.add(Instruction.pushUndefined());
}
}
private ConstantStatement(Location loc, Object val, boolean isNull) {
super(loc);
this.value = val;
this.isNull = isNull;
}
public ConstantStatement(Location loc, boolean val) {
this(loc, val, false);
}
public ConstantStatement(Location loc, String val) {
this(loc, val, false);
}
public ConstantStatement(Location loc, double val) {
this(loc, val, false);
}
public static ConstantStatement ofUndefined(Location loc) {
return new ConstantStatement(loc, null, false);
}
public static ConstantStatement ofNull(Location loc) {
return new ConstantStatement(loc, null, true);
}
}

View File

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

View File

@@ -0,0 +1,127 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class FunctionStatement extends Statement {
public final CompoundStatement body;
public final String varName;
public final String[] args;
public final boolean statement;
public final Location end;
@Override public boolean pure() { return varName == null && statement; }
@Override
public void declare(CompileResult target) {
if (varName != null && statement) target.scope.define(varName);
}
public static void checkBreakAndCont(CompileResult target, int start) {
for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) {
if (target.get(i).is(0, "break") ) {
throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop.");
}
if (target.get(i).is(0, "cont")) {
throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop.");
}
}
}
}
private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) {
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])) {
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
}
}
}
var subtarget = new CompileResult(target.scope.child());
subtarget.scope.define("this");
var argsVar = subtarget.scope.define("arguments");
if (args.length > 0) {
for (var i = 0; i < args.length; i++) {
subtarget.add(Instruction.loadVar(argsVar));
subtarget.add(Instruction.pushValue(i));
subtarget.add(Instruction.loadMember());
subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i])));
}
}
if (!statement && this.varName != null) {
subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp);
}
body.declare(subtarget);
body.compile(subtarget, false);
subtarget.length = args.length;
subtarget.add(Instruction.ret()).setLocation(end);
checkBreakAndCont(subtarget, 0);
if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures()));
return target.addChild(subtarget);
}
public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (this.varName != null) name = this.varName;
var hasVar = this.varName != null && statement;
var hasName = name != null;
compileBody(target, pollute || hasVar || hasName, bp);
if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup());
target.add(Instruction.pushValue("name"));
target.add(Instruction.pushValue(name));
target.add(Instruction.storeMember());
}
if (hasVar) {
var key = target.scope.getKey(this.varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
target.add(Instruction.storeVar(target.scope.getKey(this.varName), false));
}
}
public void compile(CompileResult target, boolean pollute, String name) {
compile(target, pollute, name, BreakpointType.NONE);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
compile(target, pollute, (String)null, bp);
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, (String)null, BreakpointType.NONE);
}
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc);
this.end = end;
this.varName = varName;
this.statement = statement;
this.args = args;
this.body = body;
}
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name);
else stm.compile(target, pollute);
}
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp);
else stm.compile(target, pollute, bp);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class LazyAndStatement extends Statement {
public final Statement first, second;
@Override public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(CompileResult target, boolean pollute) {
first.compile(target, true);
if (pollute) target.add(Instruction.dup());
int start = target.temp();
if (pollute) target.add(Instruction.discard());
second.compile(target, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start));
}
public LazyAndStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class LazyOrStatement extends Statement {
public final Statement first, second;
@Override public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(CompileResult target, boolean pollute) {
first.compile(target, true);
if (pollute) target.add(Instruction.dup());
int start = target.temp();
if (pollute) target.add(Instruction.discard());
second.compile(target, pollute);
target.set(start, Instruction.jmpIf(target.size() - start));
}
public LazyOrStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,456 @@
package me.topchetoeu.jscript.lib;
import java.util.Iterator;
import java.util.Stack;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Array")
public class ArrayLib {
private static int normalizeI(int len, int i, boolean clamp) {
if (i < 0) i += len;
if (clamp) {
if (i < 0) i = 0;
if (i > len) i = len;
}
return i;
}
@Expose(value = "length", type = ExposeType.GETTER)
public static int __getLength(Arguments args) {
return args.self(ArrayValue.class).size();
}
@Expose(value = "length", type = ExposeType.SETTER)
public static void __setLength(Arguments args) {
args.self(ArrayValue.class).setSize(args.getInt(0));
}
@Expose public static ObjectValue __values(Arguments args) {
return __iterator(args);
}
@Expose public static ObjectValue __keys(Arguments args) {
return Values.toJSIterator(args.ctx, () -> new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < args.self(ArrayValue.class).size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return i++;
}
});
}
@Expose public static ObjectValue __entries(Arguments args) {
return Values.toJSIterator(args.ctx, () -> new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < args.self(ArrayValue.class).size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return new ArrayValue(args.ctx, i, args.self(ArrayValue.class).get(i++));
}
});
}
@Expose(value = "@@Symbol.iterator")
public static ObjectValue __iterator(Arguments args) {
return Values.toJSIterator(args.ctx, args.self(ArrayValue.class));
}
@Expose(value = "@@Symbol.asyncIterator")
public static ObjectValue __asyncIterator(Arguments args) {
return Values.toJSAsyncIterator(args.ctx, args.self(ArrayValue.class).iterator());
}
@Expose public static ArrayValue __concat(Arguments args) {
// TODO: Fully implement with non-array spreadable objects
var arrs = args.slice(-1);
var size = 0;
for (int i = 0; i < arrs.n(); i++) {
if (arrs.get(i) instanceof ArrayValue) size += arrs.convert(i, ArrayValue.class).size();
else i++;
}
var res = new ArrayValue(size);
for (int i = 0, j = 0; i < arrs.n(); i++) {
if (arrs.get(i) instanceof ArrayValue) {
var arrEl = arrs.convert(i, ArrayValue.class);
int n = arrEl.size();
arrEl.copyTo(res, 0, j, n);
j += n;
}
else {
res.set(args.ctx, j++, arrs.get(i));
}
}
return res;
}
@Expose public static ArrayValue __sort(Arguments args) {
var arr = args.self(ArrayValue.class);
var cmp = args.convert(0, FunctionValue.class);
var defaultCmp = new NativeFunction("", _args -> {
return _args.getString(0).compareTo(_args.getString(1));
});
arr.sort((a, b) -> {
var res = Values.toNumber(args.ctx, (cmp == null ? defaultCmp : cmp).call(args.ctx, null, a, b));
if (res < 0) return -1;
if (res > 0) return 1;
return 0;
});
return arr;
}
@Expose public static ArrayValue __fill(Arguments args) {
var arr = args.self(ArrayValue.class);
var val = args.get(0);
var start = normalizeI(arr.size(), args.getInt(1, 0), true);
var end = normalizeI(arr.size(), args.getInt(2, arr.size()), true);
for (; start < end; start++) arr.set(args.ctx, start, val);
return arr;
}
@Expose public static boolean __every(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = 0; i < arr.size(); i++) {
if (arr.has(i) && !Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, arr
))) return false;
}
return true;
}
@Expose public static boolean __some(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, arr
))) return true;
}
return false;
}
@Expose public static ArrayValue __filter(Arguments args) {
var arr = args.self(ArrayValue.class);
var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, arr
))) res.set(args.ctx, j++, arr.get(i));
}
return res;
}
@Expose public static ArrayValue __map(Arguments args) {
var arr = args.self(ArrayValue.class);
var res = new ArrayValue(arr.size());
res.setSize(arr.size());
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) res.set(args.ctx, i, Values.call(args.ctx, args.get(0), args.get(1), arr.get(i), i, arr));
}
return res;
}
@Expose public static void __forEach(Arguments args) {
var arr = args.self(ArrayValue.class);
var func = args.convert(0, FunctionValue.class);
var thisArg = args.get(1);
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) func.call(args.ctx, thisArg, arr.get(i), i, arr);
}
}
@Expose public static Object __reduce(Arguments args) {
var arr = args.self(ArrayValue.class);
var func = args.convert(0, FunctionValue.class);
var res = args.get(1);
var i = 0;
if (args.n() < 2) {
for (; i < arr.size(); i++) {
if (arr.has(i)){
res = arr.get(i++);
break;
}
}
}
for (; i < arr.size(); i++) {
if (arr.has(i)) {
res = func.call(args.ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Expose public static Object __reduceRight(Arguments args) {
var arr = args.self(ArrayValue.class);
var func = args.convert(0, FunctionValue.class);
var res = args.get(1);
var i = arr.size();
if (args.n() < 2) {
while (!arr.has(i--) && i >= 0) {
res = arr.get(i);
}
}
else i--;
for (; i >= 0; i--) {
if (arr.has(i)) {
res = func.call(args.ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Expose public static ArrayValue __flat(Arguments args) {
var arr = args.self(ArrayValue.class);
var depth = args.getInt(0, 1);
var res = new ArrayValue(arr.size());
var stack = new Stack<Object>();
var depths = new Stack<Integer>();
stack.push(arr);
depths.push(-1);
while (!stack.empty()) {
var el = stack.pop();
int d = depths.pop();
if ((d == -1 || d < depth) && el instanceof ArrayValue) {
var arrEl = (ArrayValue)el;
for (int i = arrEl.size() - 1; i >= 0; i--) {
if (!arrEl.has(i)) continue;
stack.push(arrEl.get(i));
depths.push(d + 1);
}
}
else res.set(args.ctx, res.size(), el);
}
return res;
}
@Expose public static ArrayValue __flatMap(Arguments args) {
return __flat(new Arguments(args.ctx, __map(args), 1));
}
@Expose public static Object __find(Arguments args) {
var arr = args.self(ArrayValue.class);
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return arr.get(i);
}
return null;
}
@Expose public static Object __findLast(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return arr.get(i);
}
return null;
}
@Expose public static int __findIndex(Arguments args) {
var arr = args.self(ArrayValue.class);
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return i;
}
return -1;
}
@Expose public static int __findLastIndex(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.ctx, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return i;
}
return -1;
}
@Expose public static int __indexOf(Arguments args) {
var arr = args.self(ArrayValue.class);
var val = args.get(0);
var start = normalizeI(arr.size(), args.getInt(1), true);
for (int i = start; i < arr.size(); i++) {
if (Values.strictEquals(args.ctx, arr.get(i), val)) return i;
}
return -1;
}
@Expose public static int __lastIndexOf(Arguments args) {
var arr = args.self(ArrayValue.class);
var val = args.get(0);
var start = normalizeI(arr.size(), args.getInt(1), true);
for (int i = arr.size(); i >= start; i--) {
if (Values.strictEquals(args.ctx, arr.get(i), val)) return i;
}
return -1;
}
@Expose public static boolean __includes(Arguments args) {
return __indexOf(args) >= 0;
}
@Expose public static Object __pop(Arguments args) {
var arr = args.self(ArrayValue.class);
if (arr.size() == 0) return null;
var val = arr.get(arr.size() - 1);
arr.shrink(1);
return val;
}
@Expose public static int __push(Arguments args) {
var arr = args.self(ArrayValue.class);
var values = args.args;
arr.copyFrom(args.ctx, values, 0, arr.size(), values.length);
return arr.size();
}
@Expose public static Object __shift(Arguments args) {
var arr = args.self(ArrayValue.class);
if (arr.size() == 0) return null;
var val = arr.get(0);
arr.move(1, 0, arr.size());
arr.shrink(1);
return val;
}
@Expose public static int __unshift(Arguments args) {
var arr = args.self(ArrayValue.class);
var values = args.slice(0).args;
arr.move(0, values.length, arr.size());
arr.copyFrom(args.ctx, values, 0, 0, values.length);
return arr.size();
}
@Expose public static ArrayValue __slice(Arguments args) {
var arr = args.self(ArrayValue.class);
var start = normalizeI(arr.size(), args.getInt(0), true);
var end = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
var res = new ArrayValue(end - start);
arr.copyTo(res, start, 0, end - start);
return res;
}
@Expose public static ArrayValue __splice(Arguments args) {
var arr = args.self(ArrayValue.class);
var start = normalizeI(arr.size(), args.getInt(0), true);
var deleteCount = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
var items = args.slice(2).args;
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
var size = arr.size() - deleteCount + items.length;
var res = new ArrayValue(deleteCount);
arr.copyTo(res, start, 0, deleteCount);
arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount);
arr.copyFrom(args.ctx, items, 0, start, items.length);
arr.setSize(size);
return res;
}
@Expose public static String __toString(Arguments args) {
return __join(new Arguments(args.ctx, args.self, ","));
}
@Expose public static String __join(Arguments args) {
var arr = args.self(ArrayValue.class);
var sep = args.getString(0, ", ");
var res = new StringBuilder();
var comma = false;
for (int i = 0; i < arr.size(); i++) {
if (!arr.has(i)) continue;
if (comma) res.append(sep);
comma = true;
var el = arr.get(i);
if (el == null || el == Values.NULL) continue;
res.append(Values.toString(args.ctx, el));
}
return res.toString();
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isArray(Arguments args) {
return args.get(0) instanceof ArrayValue;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __of(Arguments args) {
return new ArrayValue(args.ctx, args.slice(0).args);
}
@ExposeConstructor public static ArrayValue __constructor(Arguments args) {
ArrayValue res;
if (args.n() == 1 && args.get(0) instanceof Number) {
var len = args.getInt(0);
res = new ArrayValue(len);
res.setSize(len);
}
else {
var val = args.args;
res = new ArrayValue(val.length);
res.copyFrom(args.ctx, val, 0, 0, val.length);
}
return res;
}
}

View File

@@ -0,0 +1,85 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("AsyncFunction")
public class AsyncFunctionLib extends FunctionValue {
public final CodeFunction func;
private static class AsyncHelper {
public PromiseLib promise = new PromiseLib();
public Frame frame;
private boolean awaiting = false;
private void next(Context ctx, Object inducedValue, EngineException inducedError) {
Object res = null;
frame.onPush();
awaiting = false;
while (!awaiting) {
try {
res = frame.next(inducedValue, Values.NO_RETURN, inducedError);
inducedValue = Values.NO_RETURN;
inducedError = null;
if (res != Values.NO_RETURN) {
promise.fulfill(ctx, res);
break;
}
}
catch (EngineException e) {
promise.reject(ctx, e);
break;
}
}
frame.onPop();
if (awaiting) {
PromiseLib.handle(ctx, frame.pop(), new Handle() {
@Override
public void onFulfil(Object val) {
next(ctx, val, null);
}
@Override
public void onReject(EngineException err) {
next(ctx, Values.NO_RETURN, err);
}
}.defer(ctx));
}
}
public Object await(Arguments args) {
this.awaiting = true;
return args.get(0);
}
}
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncHelper();
var newArgs = new Object[args.length + 1];
newArgs[0] = new NativeFunction("await", handler::await);
System.arraycopy(args, 0, newArgs, 1, args.length);
handler.frame = new Frame(ctx, thisArg, newArgs, (CodeFunction)func);
handler.next(ctx, Values.NO_RETURN, null);
return handler.promise;
}
public AsyncFunctionLib(FunctionValue func) {
super(func.name, func.length);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
this.func = (CodeFunction)func;
}
}

View File

@@ -0,0 +1,33 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("AsyncGeneratorFunction")
public class AsyncGeneratorFunctionLib extends FunctionValue {
public final CodeFunction func;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncGeneratorLib();
var newArgs = new Object[args.length + 2];
newArgs[0] = new NativeFunction("await", handler::await);
newArgs[1] = new NativeFunction("yield", handler::yield);
System.arraycopy(args, 0, newArgs, 2, args.length);
handler.frame = new Frame(ctx, thisArg, newArgs, func);
return handler;
}
public AsyncGeneratorFunctionLib(CodeFunction func) {
super(func.name, func.length);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
this.func = func;
}
}

View File

@@ -0,0 +1,107 @@
package me.topchetoeu.jscript.lib;
import java.util.Map;
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("AsyncGenerator")
public class AsyncGeneratorLib {
private int state = 0;
private boolean done = false;
private PromiseLib currPromise;
public Frame frame;
private void next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) {
if (done) {
if (inducedError != null) throw inducedError;
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
"done", true,
"value", inducedReturn == Values.NO_RETURN ? null : inducedReturn
)));
return;
}
Object res = null;
state = 0;
frame.onPush();
while (state == 0) {
try {
res = frame.next(inducedValue, inducedReturn, inducedError);
inducedValue = inducedReturn = Values.NO_RETURN;
inducedError = null;
if (res != Values.NO_RETURN) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", true);
obj.defineProperty(ctx, "value", res);
currPromise.fulfill(ctx, obj);
break;
}
}
catch (EngineException e) {
currPromise.reject(ctx, e);
break;
}
}
frame.onPop();
if (state == 1) {
PromiseLib.handle(ctx, frame.pop(), new Handle() {
@Override public void onFulfil(Object val) {
next(ctx, val, Values.NO_RETURN, null);
}
@Override public void onReject(EngineException err) {
next(ctx, Values.NO_RETURN, Values.NO_RETURN, err);
}
}.defer(ctx));
}
else if (state == 2) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", false);
obj.defineProperty(ctx, "value", frame.pop());
currPromise.fulfill(ctx, obj);
}
}
@Override
public String toString() {
if (done) return "Generator [closed]";
if (state != 0) return "Generator [suspended]";
return "Generator [running]";
}
public Object await(Arguments args) {
this.state = 1;
return args.get(0);
}
public Object yield(Arguments args) {
this.state = 2;
return args.get(0);
}
@Expose public PromiseLib __next(Arguments args) {
this.currPromise = new PromiseLib();
if (args.has(0)) next(args.ctx, args.get(0), Values.NO_RETURN, null);
else next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null);
return this.currPromise;
}
@Expose public PromiseLib __return(Arguments args) {
this.currPromise = new PromiseLib();
next(args.ctx, Values.NO_RETURN, args.get(0), null);
return this.currPromise;
}
@Expose public PromiseLib __throw(Arguments args) {
this.currPromise = new PromiseLib();
next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx));
return this.currPromise;
}
}

View File

@@ -0,0 +1,37 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Boolean")
public class BooleanLib {
public static final BooleanLib TRUE = new BooleanLib(true);
public static final BooleanLib FALSE = new BooleanLib(false);
public final boolean value;
@Override public String toString() {
return value + "";
}
public BooleanLib(boolean val) {
this.value = val;
}
@ExposeConstructor public static Object __constructor(Arguments args) {
var val = args.getBoolean(0);
if (args.self instanceof ObjectValue) return val ? TRUE : FALSE;
else return val;
}
@Expose public static String __toString(Arguments args) {
return args.self(Boolean.class) ? "true" : "false";
}
@Expose public static boolean __valueOf(Arguments args) {
if (Values.isWrapper(args.self, BooleanLib.class)) return Values.wrapper(args.self, BooleanLib.class).value;
return args.self(Boolean.class);
}
}

View File

@@ -0,0 +1,38 @@
package me.topchetoeu.jscript.lib;
import java.io.IOException;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Console")
public class ConsoleLib {
public static interface Writer {
void writeLine(String val) throws IOException;
}
private File file;
@Expose
public void __log(Arguments args) {
var res = new StringBuilder();
var first = true;
for (var el : args.args) {
if (!first) res.append(" ");
first = false;
res.append(Values.toReadable(args.ctx, el).getBytes());
}
for (var line : res.toString().split("\n", -1)) {
file.write(line.getBytes());
}
}
public ConsoleLib(File file) {
this.file = file;
}
}

View File

@@ -1,11 +1,16 @@
package me.topchetoeu.jscript.lib;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Date")
public class DateLib {
private Calendar normal;
private Calendar utc;
@@ -22,244 +27,221 @@ public class DateLib {
normal = utc = null;
}
@Native
public static double now() {
return new DateLib().getTime();
}
@Native
public double getYear() {
@Expose public double __getYear() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.YEAR) - 1900;
}
@Native
public double setYear(Context ctx, double real) throws InterruptedException {
@Expose public double __setYeard(Arguments args) {
var real = args.getDouble(0);
if (real >= 0 && real <= 99) real = real + 1900;
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.YEAR, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double getFullYear() {
@Expose public double __getFullYear() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.YEAR);
}
@Native
public double getMonth() {
@Expose public double __getMonth() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.MONTH);
}
@Native
public double getDate() {
@Expose public double __getDate() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.DAY_OF_MONTH);
}
@Native
public double getDay() {
@Expose public double __getDay() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.DAY_OF_WEEK);
}
@Native
public double getHours() {
@Expose public double __getHours() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.HOUR_OF_DAY);
}
@Native
public double getMinutes() {
@Expose public double __getMinutes() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.MINUTE);
}
@Native
public double getSeconds() {
@Expose public double __getSeconds() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.SECOND);
}
@Native
public double getMilliseconds() {
@Expose public double __getMilliseconds() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.MILLISECOND);
}
@Native
public double getUTCFullYear() {
@Expose public double __getUTCFullYear() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.YEAR);
}
@Native
public double getUTCMonth() {
@Expose public double __getUTCMonth() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.MONTH);
}
@Native
public double getUTCDate() {
@Expose public double __getUTCDate() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.DAY_OF_MONTH);
}
@Native
public double getUTCDay() {
@Expose public double __getUTCDay() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.DAY_OF_WEEK);
}
@Native
public double getUTCHours() {
@Expose public double __getUTCHours() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.HOUR_OF_DAY);
}
@Native
public double getUTCMinutes() {
@Expose public double __getUTCMinutes() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.MINUTE);
}
@Native
public double getUTCSeconds() {
@Expose public double __getUTCSeconds() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.SECOND);
}
@Native
public double getUTCMilliseconds() {
@Expose public double __getUTCMilliseconds() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.MILLISECOND);
}
@Native
public double setFullYear(Context ctx, double real) throws InterruptedException {
@Expose public double __setFullYear(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.YEAR, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setMonth(Context ctx, double real) throws InterruptedException {
@Expose public double __setMonthd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MONTH, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setDate(Context ctx, double real) throws InterruptedException {
@Expose public double __setDated(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.DAY_OF_MONTH, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setDay(Context ctx, double real) throws InterruptedException {
@Expose public double __setDayd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.DAY_OF_WEEK, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setHours(Context ctx, double real) throws InterruptedException {
@Expose public double __setHoursd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.HOUR_OF_DAY, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setMinutes(Context ctx, double real) throws InterruptedException {
@Expose public double __setMinutesd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MINUTE, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setSeconds(Context ctx, double real) throws InterruptedException {
@Expose public double __setSecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.SECOND, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setMilliseconds(Context ctx, double real) throws InterruptedException {
@Expose public double __setMillisecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MILLISECOND, (int)real);
updateUTC();
return getTime();
return __getTime();
}
@Native
public double setUTCFullYear(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCFullYeard(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.YEAR, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCMonth(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCMonthd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MONTH, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCDate(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCDated(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.DAY_OF_MONTH, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCDay(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCDayd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.DAY_OF_WEEK, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCHours(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCHoursd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.HOUR_OF_DAY, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCMinutes(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCMinutesd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MINUTE, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCSeconds(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCSecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.SECOND, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double setUTCMilliseconds(Context ctx, double real) throws InterruptedException {
@Expose public double __setUTCMillisecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MILLISECOND, (int)real);
updateNormal();
return getTime();
return __getTime();
}
@Native
public double getTime() {
@Expose public double __getTime() {
if (utc == null) return Double.NaN;
return utc.getTimeInMillis();
}
@Native
public double getTimezoneOffset() {
@Expose public double __getTimezoneOffset() {
if (normal == null) return Double.NaN;
return normal.getTimeZone().getRawOffset() / 60000;
}
@Native
public double valueOf() {
@Expose public double __valueOf() {
if (normal == null) return Double.NaN;
else return normal.getTimeInMillis();
}
@Native
public String toString() {
@Expose public String __toString() {
return normal.getTime().toString();
}
@Native
@Override public String toString() {
return __toString();
}
public DateLib(long timestamp) {
normal = Calendar.getInstance();
utc = Calendar.getInstance();
@@ -268,8 +250,17 @@ public class DateLib {
utc.setTimeInMillis(timestamp);
}
@Native
public DateLib() {
this(new java.util.Date().getTime());
this(new Date().getTime());
}
@ExposeConstructor public static DateLib init(Arguments args) {
if (args.has(0)) return new DateLib(args.getLong(0));
else return new DateLib();
}
@Expose(target = ExposeTarget.STATIC)
public static double __now() {
return new DateLib().__getTime();
}
}

View File

@@ -0,0 +1,89 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Encoding")
public class EncodingLib {
private static final String HEX = "0123456789ABCDEF";
public static String encodeUriAny(String str, String keepAlphabet) {
if (str == null) str = "undefined";
var bytes = str.getBytes();
var sb = new StringBuilder(bytes.length);
for (byte c : bytes) {
if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
else {
sb.append('%');
sb.append(HEX.charAt(c / 16));
sb.append(HEX.charAt(c % 16));
}
}
return sb.toString();
}
public static String decodeUriAny(String str, String keepAlphabet) {
if (str == null) str = "undefined";
var res = new Buffer();
var bytes = str.getBytes();
for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
if (c == '%') {
if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
if (!Parsing.isAny((char)b, keepAlphabet)) {
i += 2;
res.append((byte)b);
continue;
}
}
res.append(c);
}
return new String(res.data());
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __encode(Arguments args) {
var res = new ArrayValue();
for (var el : args.getString(0).getBytes()) res.set(null, res.size(), (int)el);
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static String __decode(Arguments args) {
var raw = args.convert(0, ArrayList.class);
var res = new byte[raw.size()];
for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, raw.get(i));
return new String(res);
}
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURIComponent(Arguments args) {
return EncodingLib.encodeUriAny(args.getString(0), ".-_!~*'()");
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURIComponent(Arguments args) {
return decodeUriAny(args.getString(0), "");
}
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURI(Arguments args) {
return encodeUriAny(args.getString(0), ";,/?:@&=+$#.-_!~*'()");
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURI(Arguments args) {
return decodeUriAny(args.getString(0), ",/?:@&=+$#.");
}
}

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.exceptions.ConvertException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Error")
public class ErrorLib {
private static String toString(Context ctx, Object name, Object message) {
if (name == null) name = "";
else name = Values.toString(ctx, name).trim();
if (message == null) message = "";
else message = Values.toString(ctx, message).trim();
StringBuilder res = new StringBuilder();
if (!name.equals("")) res.append(name);
if (!message.equals("") && !name.equals("")) res.append(": ");
if (!message.equals("")) res.append(message);
return res.toString();
}
@ExposeField public static final String __name = "Error";
@Expose public static String __toString(Arguments args) {
if (args.self instanceof ObjectValue) return toString(args.ctx,
Values.getMember(args.ctx, args.self, "name"),
Values.getMember(args.ctx, args.self, "message")
);
else return "[Invalid error]";
}
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
var target = new ObjectValue();
var message = args.getString(0, "");
try {
target = args.self(ObjectValue.class);
}
catch (ConvertException e) {}
target.setPrototype(PlaceholderProto.ERROR);
target.defineProperty(args.ctx, "message", Values.toString(args.ctx, message));
return target;
}
}

View File

@@ -0,0 +1,84 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("File")
public class FileLib {
public final File fd;
@Expose public PromiseLib __pointer(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
try {
return fd.seek(0, 1);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __length(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
try {
long curr = fd.seek(0, 1);
long res = fd.seek(0, 2);
fd.seek(curr, 0);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __read(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
var n = args.getInt(0);
try {
var buff = new byte[n];
var res = new ArrayValue();
int resI = fd.read(buff);
for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __write(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
var val = args.convert(0, ArrayValue.class);
try {
var res = new byte[val.size()];
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
fd.write(res);
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __close(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
fd.close();
return null;
});
}
@Expose public PromiseLib __seek(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
var ptr = args.getLong(0);
var whence = args.getInt(1);
try {
return fd.seek(ptr, whence);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
public FileLib(File fd) {
this.fd = fd;
}
}

View File

@@ -0,0 +1,193 @@
package me.topchetoeu.jscript.lib;
import java.io.IOException;
import java.util.Iterator;
import java.util.Stack;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.ActionType;
import me.topchetoeu.jscript.utils.filesystem.EntryType;
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FileStat;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Filesystem")
public class FilesystemLib {
@ExposeField(target = ExposeTarget.STATIC)
public static final int __SEEK_SET = 0;
@ExposeField(target = ExposeTarget.STATIC)
public static final int __SEEK_CUR = 1;
@ExposeField(target = ExposeTarget.STATIC)
public static final int __SEEK_END = 2;
private static Filesystem fs(Context ctx) {
var fs = Filesystem.get(ctx);
if (fs != null) return fs;
throw EngineException.ofError("Current environment doesn't have a file system.");
}
@Expose(target = ExposeTarget.STATIC)
public static String __normalize(Arguments args) {
return fs(args.ctx).normalize(args.convert(String.class));
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __open(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
var fs = fs(args.ctx);
var path = fs.normalize(args.getString(0));
var _mode = Mode.parse(args.getString(1));
try {
if (fs.stat(path).type != EntryType.FILE) {
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN);
}
return new FileLib(fs.open(path, _mode));
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __ls(Arguments args) {
return Values.toJSAsyncIterator(args.ctx, new Iterator<>() {
private boolean failed, done;
private File file;
private String nextLine;
private void update() {
if (done) return;
if (!failed) {
if (file == null) {
var fs = fs(args.ctx);
var path = fs.normalize(args.getString(0));
if (fs.stat(path).type != EntryType.FOLDER) {
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN);
}
file = fs.open(path, Mode.READ);
}
if (nextLine == null) {
while (true) {
nextLine = file.readLine();
if (nextLine == null) {
done = true;
return;
}
nextLine = nextLine.trim();
if (!nextLine.equals("")) break;
}
}
}
}
@Override
public boolean hasNext() {
try {
update();
return !done && !failed;
}
catch (FilesystemException e) { throw e.toEngineException(); }
}
@Override
public String next() {
try {
update();
var res = nextLine;
nextLine = null;
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
}
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __mkdir(Arguments args) throws IOException {
return PromiseLib.await(args.ctx, () -> {
try {
fs(args.ctx).create(args.getString(0), EntryType.FOLDER);
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __mkfile(Arguments args) throws IOException {
return PromiseLib.await(args.ctx, () -> {
try {
fs(args.ctx).create(args.getString(0), EntryType.FILE);
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __rm(Arguments args) throws IOException {
return PromiseLib.await(args.ctx, () -> {
try {
var fs = fs(args.ctx);
var path = fs.normalize(args.getString(0));
var recursive = args.getBoolean(1);
if (!recursive) fs.create(path, EntryType.NONE);
else {
var stack = new Stack<String>();
stack.push(path);
while (!stack.empty()) {
var currPath = stack.pop();
FileStat stat;
try { stat = fs.stat(currPath); }
catch (FilesystemException e) { continue; }
if (stat.type == EntryType.FOLDER) {
for (var el : fs.open(currPath, Mode.READ).readToString().split("\n")) stack.push(el);
}
else fs.create(currPath, EntryType.NONE);
}
}
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __stat(Arguments args) throws IOException {
return PromiseLib.await(args.ctx, () -> {
try {
var fs = fs(args.ctx);
var path = fs.normalize(args.getString(0));
var stat = fs.stat(path);
var res = new ObjectValue();
res.defineProperty(args.ctx, "type", stat.type.name);
res.defineProperty(args.ctx, "mode", stat.mode.name);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __exists(Arguments args) throws IOException {
return PromiseLib.await(args.ctx, () -> {
try { fs(args.ctx).stat(args.getString(0)); return true; }
catch (FilesystemException e) { return false; }
});
}
}

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Function")
public class FunctionLib {
@Expose public static Object __apply(Arguments args) {
return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.convert(1, ArrayValue.class).toArray());
}
@Expose public static Object __call(Arguments args) {
return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.slice(1).args);
}
@Expose public static FunctionValue __bind(Arguments args) {
var self = args.self(FunctionValue.class);
var thisArg = args.get(0);
var bindArgs = args.slice(1).args;
return new NativeFunction(self.name + " (bound)", callArgs -> {
Object[] resArgs;
if (args.n() == 0) resArgs = bindArgs;
else {
resArgs = new Object[bindArgs.length + callArgs.n()];
System.arraycopy(bindArgs, 0, resArgs, 0, bindArgs.length);
System.arraycopy(callArgs.args, 0, resArgs, bindArgs.length, callArgs.n());
}
return self.call(callArgs.ctx, thisArg, resArgs);
});
}
@Expose public static String __toString(Arguments args) {
return args.self.toString();
}
@Expose(target = ExposeTarget.STATIC)
public static FunctionValue __async(Arguments args) {
return new AsyncFunctionLib(args.convert(0, FunctionValue.class));
}
@Expose(target = ExposeTarget.STATIC)
public static FunctionValue __asyncGenerator(Arguments args) {
return new AsyncGeneratorFunctionLib(args.convert(0, CodeFunction.class));
}
@Expose(target = ExposeTarget.STATIC)
public static FunctionValue __generator(Arguments args) {
return new GeneratorFunctionLib(args.convert(0, CodeFunction.class));
}
}

View File

@@ -0,0 +1,31 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("GeneratorFunction")
public class GeneratorFunctionLib extends FunctionValue {
public final CodeFunction func;
@Override public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new GeneratorLib();
var newArgs = new Object[args.length + 1];
newArgs[0] = new NativeFunction("yield", handler::yield);
System.arraycopy(args, 0, newArgs, 1, args.length);
handler.frame = new Frame(ctx, thisArg, newArgs, func);
return handler;
}
public GeneratorFunctionLib(CodeFunction func) {
super(func.name, func.length);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
this.func = func;
}
}

View File

@@ -0,0 +1,78 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Generator")
public class GeneratorLib {
private boolean yielding = true;
private boolean done = false;
public Frame frame;
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) {
if (done) {
if (inducedError != Values.NO_RETURN) throw inducedError;
var res = new ObjectValue();
res.defineProperty(ctx, "done", true);
res.defineProperty(ctx, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn);
return res;
}
Object res = null;
yielding = false;
frame.onPush();
while (!yielding) {
try {
res = frame.next(inducedValue, inducedReturn, inducedError);
inducedReturn = Values.NO_RETURN;
inducedError = null;
if (res != Values.NO_RETURN) {
done = true;
break;
}
}
catch (EngineException e) {
done = true;
throw e;
}
}
frame.onPop();
if (done) frame = null;
else res = frame.pop();
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", done);
obj.defineProperty(ctx, "value", res);
return obj;
}
@Expose public ObjectValue __next(Arguments args) {
if (args.n() == 0) return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null);
else return next(args.ctx, args.get(0), Values.NO_RETURN, null);
}
@Expose public ObjectValue __throw(Arguments args) {
return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx));
}
@Expose public ObjectValue __return(Arguments args) {
return next(args.ctx, Values.NO_RETURN, args.get(0), null);
}
@Override public String toString() {
if (done) return "Generator [closed]";
if (yielding) return "Generator [suspended]";
return "Generator [running]";
}
public Object yield(Arguments args) {
this.yielding = true;
return args.get(0);
}
}

View File

@@ -0,0 +1,217 @@
package me.topchetoeu.jscript.lib;
import java.util.HashMap;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.Environment;
import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
public class Internals {
private static final Key<HashMap<Integer, Thread>> THREADS = new Key<>();
private static final Key<Integer> I = new Key<>();
@Expose(target = ExposeTarget.STATIC)
public static Object __require(Arguments args) {
var repo = ModuleRepo.get(args.ctx);
if (repo != null) {
var res = repo.getModule(args.ctx, ModuleRepo.cwd(args.ctx), args.getString(0));
res.load(args.ctx);
return res.value();
}
else throw EngineException.ofError("Modules are not supported.");
}
@Expose(target = ExposeTarget.STATIC)
public static Thread __setTimeout(Arguments args) {
var func = args.convert(0, FunctionValue.class);
var delay = args.getDouble(1);
var arguments = args.slice(2).args;
if (!args.ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
var thread = new Thread(() -> {
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000);
try { Thread.sleep(ms, ns); }
catch (InterruptedException e) { return; }
args.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.environment), null, arguments), false);
});
thread.start();
var i = args.ctx.init(I, 1);
args.ctx.add(I, i + 1);
args.ctx.init(THREADS, new HashMap<Integer, Thread>()).put(i, thread);
return thread;
}
@Expose(target = ExposeTarget.STATIC)
public static Thread __setInterval(Arguments args) {
var func = args.convert(0, FunctionValue.class);
var delay = args.getDouble(1);
var arguments = args.slice(2).args;
if (!args.ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
var thread = new Thread(() -> {
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000);
while (true) {
try {
Thread.sleep(ms, ns);
}
catch (InterruptedException e) { return; }
args.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.environment), null, arguments), false);
}
});
thread.start();
var i = args.ctx.init(I, 1);
args.ctx.add(I, i + 1);
args.ctx.init(THREADS, new HashMap<Integer, Thread>()).put(i, thread);
return thread;
}
@Expose(target = ExposeTarget.STATIC)
public static void __clearTimeout(Arguments args) {
var i = args.getInt(0);
HashMap<Integer, Thread> map = args.ctx.get(THREADS);
if (map == null) return;
var thread = map.get(i);
if (thread == null) return;
thread.interrupt();
map.remove(i);
}
@Expose(target = ExposeTarget.STATIC)
public static void __clearInterval(Arguments args) {
__clearTimeout(args);
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseInt(Arguments args) {
return NumberLib.__parseInt(args);
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseFloat(Arguments args) {
return NumberLib.__parseFloat(args);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isNaN(Arguments args) {
return NumberLib.__isNaN(args);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isFinite(Arguments args) {
return NumberLib.__isFinite(args);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isInfinite(Arguments args) {
return NumberLib.__isInfinite(args);
}
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
public static FileLib __stdin(Arguments args) {
return new FileLib(Filesystem.get(args.ctx).open("std://in", Mode.READ));
}
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
public static FileLib __stdout(Arguments args) {
return new FileLib(Filesystem.get(args.ctx).open("std://out", Mode.READ));
}
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
public static FileLib __stderr(Arguments args) {
return new FileLib(Filesystem.get(args.ctx).open("std://err", Mode.READ));
}
@ExposeField(target = ExposeTarget.STATIC)
public static double __NaN = Double.NaN;
@ExposeField(target = ExposeTarget.STATIC)
public static double __Infinity = Double.POSITIVE_INFINITY;
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURIComponent(Arguments args) {
return EncodingLib.__encodeURIComponent(args);
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURIComponent(Arguments args) {
return EncodingLib.__decodeURIComponent(args);
}
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURI(Arguments args) {
return EncodingLib.__encodeURI(args);
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURI(Arguments args) {
return EncodingLib.__decodeURI(args);
}
public static Environment apply(Environment env) {
var wp = env.wrappers;
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class));
glob.define(null, "Math", false, wp.getNamespace(MathLib.class));
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class));
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
glob.define(null, false, wp.getConstr(FileLib.class));
glob.define(null, false, wp.getConstr(DateLib.class));
glob.define(null, false, wp.getConstr(ObjectLib.class));
glob.define(null, false, wp.getConstr(FunctionLib.class));
glob.define(null, false, wp.getConstr(ArrayLib.class));
glob.define(null, false, wp.getConstr(BooleanLib.class));
glob.define(null, false, wp.getConstr(NumberLib.class));
glob.define(null, false, wp.getConstr(StringLib.class));
glob.define(null, false, wp.getConstr(SymbolLib.class));
glob.define(null, false, wp.getConstr(PromiseLib.class));
glob.define(null, false, wp.getConstr(RegExpLib.class));
glob.define(null, false, wp.getConstr(MapLib.class));
glob.define(null, false, wp.getConstr(SetLib.class));
glob.define(null, false, wp.getConstr(ErrorLib.class));
glob.define(null, false, wp.getConstr(SyntaxErrorLib.class));
glob.define(null, false, wp.getConstr(TypeErrorLib.class));
glob.define(null, false, wp.getConstr(RangeErrorLib.class));
env.add(Environment.OBJECT_PROTO, wp.getProto(ObjectLib.class));
env.add(Environment.FUNCTION_PROTO, wp.getProto(FunctionLib.class));
env.add(Environment.ARRAY_PROTO, wp.getProto(ArrayLib.class));
env.add(Environment.BOOL_PROTO, wp.getProto(BooleanLib.class));
env.add(Environment.NUMBER_PROTO, wp.getProto(NumberLib.class));
env.add(Environment.STRING_PROTO, wp.getProto(StringLib.class));
env.add(Environment.SYMBOL_PROTO, wp.getProto(SymbolLib.class));
env.add(Environment.ERROR_PROTO, wp.getProto(ErrorLib.class));
env.add(Environment.SYNTAX_ERR_PROTO, wp.getProto(SyntaxErrorLib.class));
env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class));
env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class));
Values.setPrototype(Context.NULL, wp.getProto(ObjectLib.class), null);
env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class));
return env;
}
}

View File

@@ -0,0 +1,24 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("JSON")
public class JSONLib {
@Expose(target = ExposeTarget.STATIC)
public static Object __parse(Arguments args) {
try {
return JSON.toJs(JSON.parse(null, args.getString(0)));
}
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
}
@Expose(target = ExposeTarget.STATIC)
public static String __stringify(Arguments args) {
return JSON.stringify(JSON.fromJs(args.ctx, args.get(0)));
}
}

View File

@@ -0,0 +1,87 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Map")
public class MapLib {
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
@Expose("@@Symbol.iterator")
public ObjectValue __iterator(Arguments args) {
return this.__entries(args);
}
@Expose public void __clear() {
map.clear();
}
@Expose public boolean __delete(Arguments args) {
var key = args.get(0);
if (map.containsKey(key)) {
map.remove(key);
return true;
}
return false;
}
@Expose public ObjectValue __entries(Arguments args) {
return Values.toJSIterator(args.ctx, map
.entrySet()
.stream()
.map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue()))
.collect(Collectors.toList())
);
}
@Expose public ObjectValue __keys(Arguments args) {
return Values.toJSIterator(args.ctx, map.keySet());
}
@Expose public ObjectValue __values(Arguments args) {
return Values.toJSIterator(args.ctx, map.values());
}
@Expose public Object __get(Arguments args) {
return map.get(args.get(0));
}
@Expose public MapLib __set(Arguments args) {
map.put(args.get(0), args.get(1));
return this;
}
@Expose public boolean __has(Arguments args) {
return map.containsKey(args.get(0));
}
@Expose(type = ExposeType.GETTER)
public int __size() {
return map.size();
}
@Expose public void __forEach(Arguments args) {
var keys = new ArrayList<>(map.keySet());
for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), map.get(el), el, args.self);
}
public MapLib(Context ctx, Object iterable) {
for (var el : Values.fromJSIterator(ctx, iterable)) {
try {
map.put(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
}
catch (IllegalArgumentException e) { }
}
}
@ExposeConstructor public static MapLib __constructor(Arguments args) {
return new MapLib(args.ctx, args.get(0));
}
}

View File

@@ -0,0 +1,211 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Math")
public class MathLib {
@ExposeField(target = ExposeTarget.STATIC)
public static final double __E = Math.E;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __PI = Math.PI;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __SQRT2 = Math.sqrt(2);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __SQRT1_2 = Math.sqrt(.5);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LN2 = Math.log(2);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LN10 = Math.log(10);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LOG2E = Math.log(Math.E) / __LN2;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LOG10E = Math.log10(Math.E);
@Expose(target = ExposeTarget.STATIC)
public static double __asin(Arguments args) {
return Math.asin(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __acos(Arguments args) {
return Math.acos(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __atan(Arguments args) {
return Math.atan(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __atan2(Arguments args) {
var x = args.getDouble(1);
var y = args.getDouble(0);
if (x == 0) {
if (y == 0) return Double.NaN;
return Math.signum(y) * Math.PI / 2;
}
else {
var val = Math.atan(y / x);
if (x > 0) return val;
else if (y < 0) return val - Math.PI;
else return val + Math.PI;
}
}
@Expose(target = ExposeTarget.STATIC)
public static double __asinh(Arguments args) {
var x = args.getDouble(0);
return Math.log(x + Math.sqrt(x * x + 1));
}
@Expose(target = ExposeTarget.STATIC)
public static double __acosh(Arguments args) {
var x = args.getDouble(0);
return Math.log(x + Math.sqrt(x * x - 1));
}
@Expose(target = ExposeTarget.STATIC)
public static double __atanh(Arguments args) {
var x = args.getDouble(0);
if (x <= -1 || x >= 1) return Double.NaN;
return .5 * Math.log((1 + x) / (1 - x));
}
@Expose(target = ExposeTarget.STATIC)
public static double __sin(Arguments args) {
return Math.sin(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __cos(Arguments args) {
return Math.cos(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __tan(Arguments args) {
return Math.tan(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __sinh(Arguments args) {
return Math.sinh(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __cosh(Arguments args) {
return Math.cosh(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __tanh(Arguments args) {
return Math.tanh(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __sqrt(Arguments args) {
return Math.sqrt(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __cbrt(Arguments args) {
return Math.cbrt(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __hypot(Arguments args) {
var res = 0.;
for (var i = 0; i < args.n(); i++) {
var val = args.getDouble(i);
res += val * val;
}
return Math.sqrt(res);
}
@Expose(target = ExposeTarget.STATIC)
public static int __imul(Arguments args) { return args.getInt(0) * args.getInt(1); }
@Expose(target = ExposeTarget.STATIC)
public static double __exp(Arguments args) {
return Math.exp(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __expm1(Arguments args) {
return Math.expm1(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __pow(Arguments args) { return Math.pow(args.getDouble(0), args.getDouble(1)); }
@Expose(target = ExposeTarget.STATIC)
public static double __log(Arguments args) {
return Math.log(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __log10(Arguments args) {
return Math.log10(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __log1p(Arguments args) {
return Math.log1p(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __log2(Arguments args) {
return Math.log(args.getDouble(0)) / __LN2;
}
@Expose(target = ExposeTarget.STATIC)
public static double __ceil(Arguments args) {
return Math.ceil(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __floor(Arguments args) {
return Math.floor(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __round(Arguments args) {
return Math.round(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static float __fround(Arguments args) {
return (float)args.getDouble(0);
}
@Expose(target = ExposeTarget.STATIC)
public static double __trunc(Arguments args) {
var x = args.getDouble(0);
return Math.floor(Math.abs(x)) * Math.signum(x);
}
@Expose(target = ExposeTarget.STATIC)
public static double __abs(Arguments args) {
return Math.abs(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __max(Arguments args) {
var res = Double.NEGATIVE_INFINITY;
for (var i = 0; i < args.n(); i++) {
var el = args.getDouble(i);
if (el > res) res = el;
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static double __min(Arguments args) {
var res = Double.POSITIVE_INFINITY;
for (var i = 0; i < args.n(); i++) {
var el = args.getDouble(i);
if (el < res) res = el;
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static double __sign(Arguments args) {
return Math.signum(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __random() { return Math.random(); }
@Expose(target = ExposeTarget.STATIC)
public static int __clz32(Arguments args) {
return Integer.numberOfLeadingZeros(args.getInt(0));
}
}

View File

@@ -0,0 +1,92 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Number")
public class NumberLib {
@ExposeField(target = ExposeTarget.STATIC)
public static final double __EPSILON = Math.ulp(1.0);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MAX_SAFE_INTEGER = 9007199254740991.;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MIN_SAFE_INTEGER = -__MAX_SAFE_INTEGER;
// lmao big number go brrr
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MIN_VALUE = -__MAX_VALUE;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __NaN = 0. / 0;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __NEGATIVE_INFINITY = -1. / 0;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __POSITIVE_INFINITY = 1. / 0;
public final double value;
@Override public String toString() { return value + ""; }
public NumberLib(double val) {
this.value = val;
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); }
@Expose(target = ExposeTarget.STATIC)
public static boolean __isInfinite(Arguments args) { return Double.isInfinite(args.getDouble(0)); }
@Expose(target = ExposeTarget.STATIC)
public static boolean __isNaN(Arguments args) { return Double.isNaN(args.getDouble(0)); }
@Expose(target = ExposeTarget.STATIC)
public static boolean __isSafeInteger(Arguments args) {
return args.getDouble(0) > __MIN_SAFE_INTEGER && args.getDouble(0) < __MAX_SAFE_INTEGER;
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseFloat(Arguments args) {
return args.getDouble(0);
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseInt(Arguments args) {
var radix = args.getInt(1, 10);
if (radix < 2 || radix > 36) return Double.NaN;
else {
long res = 0;
for (var c : args.getString(0).toCharArray()) {
var digit = 0;
if (c >= '0' && c <= '9') digit = c - '0';
else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10;
else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10;
else break;
if (digit > radix) break;
res *= radix;
res += digit;
}
return res;
}
}
@ExposeConstructor public static Object __constructor(Arguments args) {
if (args.self instanceof ObjectValue) return new NumberLib(args.getDouble(0));
else return args.getDouble(0);
}
@Expose public static String __toString(Arguments args) {
return Values.toString(args.ctx, args.self);
}
@Expose public static double __valueOf(Arguments args) {
if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value;
else return Values.toNumber(args.ctx, args.self);
}
}

View File

@@ -0,0 +1,273 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Symbol;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Object")
public class ObjectLib {
@Expose(target = ExposeTarget.STATIC)
public static Object __assign(Arguments args) {
for (var obj : args.slice(1).args) {
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
Values.setMember(args.ctx, args.get(0), key, Values.getMember(args.ctx, obj, key));
}
}
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __create(Arguments args) {
var obj = new ObjectValue();
Values.setPrototype(args.ctx, obj, args.get(0));
if (args.n() >= 1) {
var newArgs = new Object[args.n()];
System.arraycopy(args.args, 1, args, 1, args.n() - 1);
newArgs[0] = obj;
__defineProperties(new Arguments(args.ctx, null, newArgs));
}
return obj;
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __defineProperty(Arguments args) {
var obj = args.convert(0, ObjectValue.class);
var key = args.get(1);
var attrib = args.convert(2, ObjectValue.class);
var hasVal = Values.hasMember(args.ctx, attrib, "value", false);
var hasGet = Values.hasMember(args.ctx, attrib, "get", false);
var hasSet = Values.hasMember(args.ctx, attrib, "set", false);
if (hasVal) {
if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property.");
if (!obj.defineProperty(
args.ctx, key,
Values.getMember(args.ctx, attrib, "value"),
Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")),
Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")),
Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))
)) throw EngineException.ofType("Can't define property '" + key + "'.");
}
else {
var get = Values.getMember(args.ctx, attrib, "get");
var set = Values.getMember(args.ctx, attrib, "set");
if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function.");
if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function.");
if (!obj.defineProperty(
args.ctx, key,
(FunctionValue)get, (FunctionValue)set,
Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")),
Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))
)) throw EngineException.ofType("Can't define property '" + key + "'.");
}
return obj;
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __defineProperties(Arguments args) {
var obj = args.convert(0, ObjectValue.class);
var attrib = args.get(1);
for (var key : Values.getMembers(null, attrib, false, false)) {
__defineProperty(new Arguments(args.ctx, null, obj, key, Values.getMember(args.ctx, attrib, key)));
}
return obj;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __keys(Arguments args) {
var obj = args.get(0);
var all = args.getBoolean(1);
var res = new ArrayValue();
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key);
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __entries(Arguments args) {
var res = new ArrayValue();
var obj = args.get(0);
var all = args.getBoolean(1);
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), new ArrayValue(args.ctx, key, Values.getMember(args.ctx, obj, key)));
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __values(Arguments args) {
var res = new ArrayValue();
var obj = args.get(0);
var all = args.getBoolean(1);
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key));
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __getOwnPropertyDescriptor(Arguments args) {
return Values.getMemberDescriptor(args.ctx, args.get(0), args.get(1));
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __getOwnPropertyDescriptors(Arguments args) {
var res = new ObjectValue();
var obj = args.get(0);
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
res.defineProperty(args.ctx, key, Values.getMemberDescriptor(args.ctx, obj, key));
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __getOwnPropertyNames(Arguments args) {
var res = new ArrayValue();
var obj = args.get(0);
var all = args.getBoolean(1);
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key);
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __getOwnPropertySymbols(Arguments args) {
var obj = args.get(0);
var res = new ArrayValue();
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
if (key instanceof Symbol) res.set(args.ctx, res.size(), key);
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __hasOwn(Arguments args) {
return Values.hasMember(args.ctx, args.get(0), args.get(1), true);
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __getPrototypeOf(Arguments args) {
return Values.getPrototype(args.ctx, args.get(0));
}
@Expose(target = ExposeTarget.STATIC)
public static Object __setPrototypeOf(Arguments args) {
Values.setPrototype(args.ctx, args.get(0), args.get(1));
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __fromEntries(Arguments args) {
var res = new ObjectValue();
for (var el : Values.fromJSIterator(args.ctx, args.get(0))) {
if (el instanceof ArrayValue) {
res.defineProperty(args.ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
}
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static Object __preventExtensions(Arguments args) {
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).preventExtensions();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static Object __seal(Arguments args) {
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).seal();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static Object __freeze(Arguments args) {
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).freeze();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isExtensible(Arguments args) {
var obj = args.get(0);
if (!(obj instanceof ObjectValue)) return false;
return ((ObjectValue)obj).extensible();
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isSealed(Arguments args) {
var obj = args.get(0);
if (!(obj instanceof ObjectValue)) return true;
var _obj = (ObjectValue)obj;
if (_obj.extensible()) return false;
for (var key : _obj.keys(true)) {
if (_obj.memberConfigurable(key)) return false;
}
return true;
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isFrozen(Arguments args) {
var obj = args.get(0);
if (!(obj instanceof ObjectValue)) return true;
var _obj = (ObjectValue)obj;
if (_obj.extensible()) return false;
for (var key : _obj.keys(true)) {
if (_obj.memberConfigurable(key)) return false;
if (_obj.memberWritable(key)) return false;
}
return true;
}
@Expose
public static Object __valueOf(Arguments args) {
return args.self;
}
@Expose
public static String __toString(Arguments args) {
var name = Values.getMember(args.ctx, args.self, Symbol.get("Symbol.typeName"));
if (name == null) name = "Unknown";
else name = Values.toString(args.ctx, name);
return "[object " + name + "]";
}
@Expose
public static boolean __hasOwnProperty(Arguments args) {
return Values.hasMember(args.ctx, args.self, args.get(0), true);
}
@ExposeConstructor
public static Object __constructor(Arguments args) {
var arg = args.get(0);
if (arg == null || arg == Values.NULL) return new ObjectValue();
else if (arg instanceof Boolean) return new BooleanLib((boolean)arg);
else if (arg instanceof Number) return new NumberLib(((Number)arg).doubleValue());
else if (arg instanceof String) return new StringLib((String)arg);
else if (arg instanceof Symbol) return new SymbolLib((Symbol)arg);
else return arg;
}
}

View File

@@ -0,0 +1,404 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.runtime.Context;
import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.Extensions;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Promise")
public class PromiseLib {
public static interface Handle {
void onFulfil(Object val);
void onReject(EngineException err);
default Handle defer(Extensions loop) {
var self = this;
return new Handle() {
@Override public void onFulfil(Object val) {
if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
loop.get(EventLoop.KEY).pushMsg(() -> self.onFulfil(val), true);
}
@Override public void onReject(EngineException val) {
if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
loop.get(EventLoop.KEY).pushMsg(() -> self.onReject(val), true);
}
};
}
}
private static final int STATE_PENDING = 0;
private static final int STATE_FULFILLED = 1;
private static final int STATE_REJECTED = 2;
private List<Handle> handles = new ArrayList<>();
private int state = STATE_PENDING;
private boolean handled = false;
private Object val;
private void resolveSynchronized(Context ctx, Object val, int newState) {
this.val = val;
this.state = newState;
for (var handle : handles) {
if (newState == STATE_FULFILLED) handle.onFulfil(val);
if (newState == STATE_REJECTED) {
handle.onReject((EngineException)val);
handled = true;
}
}
if (state == STATE_REJECTED && !handled) {
Values.printError(((EngineException)val).setCtx(ctx), "(in promise)");
}
handles = null;
// ctx.get(EventLoop.KEY).pushMsg(() -> {
// if (!ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
// handles = null;
// }, true);
}
private synchronized void resolve(Context ctx, Object val, int newState) {
if (this.state != STATE_PENDING || newState == STATE_PENDING) return;
handle(ctx, val, new Handle() {
@Override public void onFulfil(Object val) {
resolveSynchronized(ctx, val, newState);
}
@Override public void onReject(EngineException err) {
resolveSynchronized(ctx, val, STATE_REJECTED);
}
});
}
public synchronized void fulfill(Context ctx, Object val) {
resolve(ctx, val, STATE_FULFILLED);
}
public synchronized void reject(Context ctx, EngineException val) {
resolve(ctx, val, STATE_REJECTED);
}
private void handle(Handle handle) {
if (state == STATE_FULFILLED) handle.onFulfil(val);
else if (state == STATE_REJECTED) {
handle.onReject((EngineException)val);
handled = true;
}
else handles.add(handle);
}
@Override public String toString() {
if (state == STATE_PENDING) return "Promise (pending)";
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
else return "Promise (rejected)";
}
public PromiseLib() {
this.state = STATE_PENDING;
this.val = null;
}
public static PromiseLib await(Context ctx, ResultRunnable<Object> runner) {
var res = new PromiseLib();
new Thread(() -> {
try {
res.fulfill(ctx, runner.run());
}
catch (EngineException e) {
res.reject(ctx, e);
}
catch (Exception e) {
if (e instanceof InterruptException) throw e;
else {
res.reject(ctx, EngineException.ofError("Native code failed with " + e.getMessage()));
}
}
}, "Promisifier").start();
return res;
}
public static PromiseLib await(Context ctx, Runnable runner) {
return await(ctx, () -> {
runner.run();
return null;
});
}
public static void handle(Context ctx, Object obj, Handle handle) {
if (Values.isWrapper(obj, PromiseLib.class)) {
var promise = Values.wrapper(obj, PromiseLib.class);
handle(ctx, promise, handle);
return;
}
if (obj instanceof PromiseLib) {
((PromiseLib)obj).handle(handle);
return;
}
var rethrow = new boolean[1];
try {
var then = Values.getMember(ctx, obj, "then");
if (then instanceof FunctionValue) Values.call(ctx, then, obj,
new NativeFunction(args -> {
try { handle.onFulfil(args.get(0)); }
catch (Exception e) {
rethrow[0] = true;
throw e;
}
return null;
}),
new NativeFunction(args -> {
try { handle.onReject(new EngineException(args.get(0))); }
catch (Exception e) {
rethrow[0] = true;
throw e;
}
return null;
})
);
else handle.onFulfil(obj);
return;
}
catch (Exception e) {
if (rethrow[0]) throw e;
}
handle.onFulfil(obj);
}
public static PromiseLib ofResolved(Context ctx, Object value) {
var res = new PromiseLib();
res.fulfill(ctx, value);
return res;
}
public static PromiseLib ofRejected(Context ctx, EngineException value) {
var res = new PromiseLib();
res.reject(ctx, value);
return res;
}
@Expose(value = "resolve", target = ExposeTarget.STATIC)
public static PromiseLib __ofResolved(Arguments args) {
return ofResolved(args.ctx, args.get(0));
}
@Expose(value = "reject", target = ExposeTarget.STATIC)
public static PromiseLib __ofRejected(Arguments args) {
return ofRejected(args.ctx, new EngineException(args.get(0)).setCtx(args.ctx));
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __any(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setCtx(args.ctx));
var n = new int[] { promises.size() };
var res = new PromiseLib();
var errors = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
var index = i;
var val = promises.get(i);
if (res.state != STATE_PENDING) break;
handle(args.ctx, val, new Handle() {
public void onFulfil(Object val) { res.fulfill(args.ctx, val); }
public void onReject(EngineException err) {
errors.set(args.ctx, index, err.value);
n[0]--;
if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setCtx(args.ctx));
}
});
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __race(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
var res = new PromiseLib();
for (var i = 0; i < promises.size(); i++) {
var val = promises.get(i);
if (res.state != STATE_PENDING) break;
handle(args.ctx, val, new Handle() {
@Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); }
@Override public void onReject(EngineException err) { res.reject(args.ctx, err); }
});
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __all(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
var n = new int[] { promises.size() };
var res = new PromiseLib();
var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
if (res.state != STATE_PENDING) break;
var index = i;
var val = promises.get(i);
handle(args.ctx, val, new Handle() {
@Override public void onFulfil(Object val) {
result.set(args.ctx, index, val);
n[0]--;
if (n[0] <= 0) res.fulfill(args.ctx, result);
}
@Override public void onReject(EngineException err) {
res.reject(args.ctx, err);
}
});
}
if (n[0] <= 0) res.fulfill(args.ctx, result);
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __allSettled(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
var n = new int[] { promises.size() };
var res = new PromiseLib();
var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
if (res.state != STATE_PENDING) break;
var index = i;
handle(args.ctx, promises.get(i), new Handle() {
@Override public void onFulfil(Object val) {
var desc = new ObjectValue();
desc.defineProperty(args.ctx, "status", "fulfilled");
desc.defineProperty(args.ctx, "value", val);
result.set(args.ctx, index, desc);
n[0]--;
if (n[0] <= 0) res.fulfill(args.ctx, res);
}
@Override public void onReject(EngineException err) {
var desc = new ObjectValue();
desc.defineProperty(args.ctx, "status", "reject");
desc.defineProperty(args.ctx, "value", err.value);
result.set(args.ctx, index, desc);
n[0]--;
if (n[0] <= 0) res.fulfill(args.ctx, res);
}
});
}
if (n[0] <= 0) res.fulfill(args.ctx, result);
return res;
}
@Expose
public static Object __then(Arguments args) {
var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null;
var res = new PromiseLib();
handle(args.ctx, args.self, new Handle() {
@Override public void onFulfil(Object val) {
try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); }
catch (EngineException e) { res.reject(args.ctx, e); }
}
@Override public void onReject(EngineException err) {
try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); }
catch (EngineException e) { res.reject(args.ctx, e); }
}
}.defer(args.ctx));
return res;
}
@Expose
public static Object __catch(Arguments args) {
return __then(new Arguments(args.ctx, args.self, null, args.get(0)));
}
@Expose
public static Object __finally(Arguments args) {
var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
var res = new PromiseLib();
handle(args.ctx, args.self, new Handle() {
@Override public void onFulfil(Object val) {
try {
func.call(args.ctx);
res.fulfill(args.ctx, val);
}
catch (EngineException e) { res.reject(args.ctx, e); }
}
@Override public void onReject(EngineException err) {
try {
func.call(args.ctx);
res.reject(args.ctx, err);
}
catch (EngineException e) { res.reject(args.ctx, e); }
}
}.defer(args.ctx));
return res;
}
@ExposeConstructor
public static PromiseLib __constructor(Arguments args) {
var func = args.convert(0, FunctionValue.class);
var res = new PromiseLib();
try {
func.call(
args.ctx, null,
new NativeFunction(null, _args -> {
res.fulfill(_args.ctx, _args.get(0));
return null;
}),
new NativeFunction(null, _args -> {
res.reject(_args.ctx, new EngineException(_args.get(0)).setCtx(_args.ctx));
return null;
})
);
}
catch (EngineException e) {
res.reject(args.ctx, e);
}
return res;
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("RangeError")
public class RangeErrorLib extends ErrorLib {
@ExposeField public static final String __name = "RangeError";
@ExposeConstructor public static ObjectValue constructor(Arguments args) {
var target = ErrorLib.__constructor(args);
target.setPrototype(PlaceholderProto.RANGE_ERROR);
return target;
}
}

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