Compare commits

..

264 Commits

Author SHA1 Message Date
2fe5ce607a fix: multiply acting as subtract 2024-09-21 19:01:05 +03:00
d821a3a89b refactor: utilize inheritence in index.js 2024-09-21 18:46:22 +03:00
0064c74ac8 fix: don't allow execution of CALL_SUPER twice or in non-construct call 2024-09-21 18:46:02 +03:00
bd548c813a fix: null out thisArg only when constructing 2024-09-21 18:45:38 +03:00
78af69ec80 fix: parseStatementEnd behaving incorrectly when EOF 2024-09-21 18:44:08 +03:00
98e5299f9c fix: derived classes use the scope API incorrectly 2024-09-21 18:43:32 +03:00
797452c28f fix: tmp variables captured incorrectly 2024-09-21 18:43:15 +03:00
fee74dcba4 fix: infinite loop in class parser 2024-09-21 18:42:51 +03:00
9845a39e84 fix: operations polluting stack 2024-09-21 18:42:34 +03:00
ee78bdc1cb feat: implement derived classes 2024-09-21 18:06:03 +03:00
7fcb9ed19f fix: member field initializers should be iterable 2024-09-20 11:39:48 +03:00
8dee4353d4 feat: implement non-enumerable members in classes 2024-09-20 11:39:46 +03:00
59e6f34a01 refactor: clean up REPL stringification code 2024-09-20 11:39:40 +03:00
fdac93bf4d fix: scope offsets calculated incorrectly 2024-09-20 11:39:34 +03:00
06eae2eaf2 Merge pull request #29 from TopchetoEU/TopchetoEU/classes
Classes
2024-09-19 15:21:01 +00:00
d7b50fa45b refactor: use classes in index.js 2024-09-19 18:11:42 +03:00
077e8afff7 fix: some behavioral issues 2024-09-19 18:11:35 +03:00
631ef9db4a fix: differenciate between non-functions and non-invokables in messages 2024-09-19 18:10:50 +03:00
0258cc0a90 feat: implement classes (without inheritence) 2024-09-19 18:09:28 +03:00
0b3dca8b13 refactor: extract members into own classes 2024-09-19 18:08:11 +03:00
6d56660136 fix: stupid mistake with variable capturing 2024-09-19 14:22:21 +03:00
8a21873631 fix: retrofit patterns for bindings and check if var is init in runtime 2024-09-19 11:02:02 +03:00
fbbd26bf7d fix: remove unneeded comments 2024-09-14 22:08:33 +03:00
e2a8a382cc refactoring 2024-09-14 21:33:33 +03:00
0670ffcdd1 fix: int value not correctly recognized 2024-09-14 19:54:42 +03:00
9b957335bf optimization: keep StringValue instances tied to one String instance 2024-09-14 19:45:05 +03:00
e9f889576c feat: implement hidden integers 2024-09-14 19:38:30 +03:00
e11d182631 refactor: remove dead code 2024-09-14 18:52:07 +03:00
30674ee463 refactor: get rid of InterruptException 2024-09-14 18:46:47 +03:00
fab3e59910 feat: implement a byte array 2024-09-14 18:46:28 +03:00
d7e4e7a024 refactor: oops 2024-09-14 18:45:55 +03:00
e4166fe450 refactor: rewrite some code for java 8 compatibility 2024-09-14 18:45:20 +03:00
b5b7781136 Merge pull request #28 from TopchetoEU:TopchetoEU/destructing
TopchetoEU/destructing
2024-09-14 15:38:02 +03:00
f13bf584a5 feat: add some missing features in the polyfills 2024-09-14 15:25:34 +03:00
4e8b110fc4 feat: add assign shorthands 2024-09-14 14:33:09 +03:00
cb82f4cf32 feat: implement patterns 2024-09-14 14:23:46 +03:00
23ae2b2e46 todo 2024-09-14 14:23:35 +03:00
55613ef2c9 feat: extend the instruction set 2024-09-14 14:23:28 +03:00
0b34c68139 fix: unnecessary new line in toReadable 2024-09-14 14:22:51 +03:00
d87e53264d refactor: split array logic 2024-09-14 14:22:31 +03:00
1f42263051 clean up member logic 2024-09-14 13:53:58 +03:00
3e6816cb2c fix: properly hande variable collisions 2024-09-12 20:25:11 +03:00
2a01b3d86e some work losl 2024-09-07 21:06:08 +03:00
8e64d13c87 refactor: clean up assigning 2024-09-06 15:48:22 +03:00
5f88061ee7 refactor: rename callNew -> construct and call -> invoke 2024-09-06 15:48:07 +03:00
b9268518f6 fix: array statements broken when empty elements 2024-09-06 15:46:10 +03:00
63ccd5757e feat: implement spread_obj intrinsic 2024-09-06 10:03:55 +03:00
515011b3ef refactor: improve meber listing system 2024-09-06 10:03:37 +03:00
b6f04aa177 Merge pull request #27 from TopchetoEU/TopchetoEU/optimize-var-flatten
Optimize var flattening
2024-09-05 22:32:48 +03:00
6f548ce5ff feat: reflect scope optimizations in runtime 2024-09-05 22:30:28 +03:00
7c74df4d36 refactor: use new system to reorder variables that overlaps neighboring scopes 2024-09-05 21:25:39 +03:00
641d4d1863 Merge pull request #26 from TopchetoEU/ES6
ES6 Support Groundwork + Fixes
2024-09-05 17:26:07 +03:00
0004839f6f fix: realloc for declarations after each iteration 2024-09-05 17:15:26 +03:00
07411f62c8 feat: implement capturable locals realloc 2024-09-05 17:14:59 +03:00
4bfc062aaf fix: correctly flatten locals in control flow statements 2024-09-05 17:13:34 +03:00
9ec99def3f fix: variable declarations shouldn't collide with defined name of named function exp 2024-09-05 13:29:42 +03:00
8f13ff3e0b fix: scope gets polluted by arguments with named function expressions 2024-09-05 13:29:20 +03:00
d7353e19ed fix: treat "arguments" as a keyword (as per strict soecifications) 2024-09-05 13:18:53 +03:00
e509edc459 fix: wrong arguments when compilling function bodies 2024-09-05 13:03:49 +03:00
b6a90b108b fix: wrong check for var decl collisions 2024-09-05 13:03:36 +03:00
55caf1e206 refactor: remove unneeded old comments 2024-09-05 13:03:04 +03:00
eac4a3af23 fix: throw access before decl for const and let in runtime 2024-09-05 00:31:03 +03:00
5b4adf5286 a clusterfuck of fixes with let and const 2024-09-05 00:28:13 +03:00
807b3918fa motherfucker 2024-09-04 15:55:59 +03:00
9265a7d813 Merge branch 'master' into ES6 2024-09-04 15:52:03 +03:00
1589ef51b0 refactor: move src and resources to standard places 2024-09-04 15:32:54 +03:00
marregui
313b20a3b3 Add first test (#23)
* Add the first test and upgrade build.gradle to modern standards

1. gradle wrapper
2. ./gradlew run
3. manifest will look like
    Manifest-Version: 1.0
    Main-Class: me.topchetoeu.jscript.runtime.SimpleRepl
    Build-Timestamp: 2024-09-04T10:44:35.990+0200
    Build-Branch: ma/add-first-tests
    Build-Revision: 412edc0ebc
    Build-Jdk: 21.0.3 (Oracle Corporation 21.0.3+7-LTS-152)
    Build-Author: TopchetoEU
4. build/distributions contains a zip and a jar which contain jscript-0.9.41-beta.jar
5. unnecessary libs have been removed
6. gradle has been updated to 8.10
7. first test has been added

* fix: revert removal of Jabel (for support of Java 11)

---------

Co-authored-by: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com>
2024-09-04 15:29:17 +03:00
ce9b419757 reafactor: make globals initializer use ES6 features 2024-09-04 10:45:34 +03:00
6f8efe74c4 fix: print was returning java null, not JS undefined 2024-09-04 10:45:14 +03:00
bd503ed943 feat: impl new instructions 2024-09-04 10:44:58 +03:00
5359c54694 fix: rethrow SyntaxException from compilation as EngineException 2024-09-04 10:44:44 +03:00
93c246ad97 refactor: remove arg loading from frame 2024-09-04 10:44:24 +03:00
7c8efaf066 fix: incorrect printing of object-like values 2024-09-04 10:43:40 +03:00
546d663466 feat: add this arg capture 2024-09-04 10:42:21 +03:00
f929015f55 ammend to prev commit 2024-09-04 10:41:52 +03:00
2a5f6aa9aa fix: use _STR and _INT variants of member instructions 2024-09-04 10:41:17 +03:00
78d233a6bd refactor: remove ArgumentsNode 2024-09-04 10:39:11 +03:00
3f25868f19 fix: some scope bug fixes 2024-09-04 10:38:16 +03:00
e3f1bc0949 fix: incorrect compilation of if-else 2024-09-04 10:36:48 +03:00
506726fd76 feat: implement ES6 variables and rest args 2024-09-04 10:36:25 +03:00
4cbc108686 feat: implement optional arguments 2024-09-04 10:00:48 +03:00
c39c06b792 refactor: move away intrinsic logic to final methods for performance 2024-09-04 10:00:45 +03:00
7ab78b9cea fix: control flow nodes were making scopes instead of compound nodes 2024-09-04 10:00:43 +03:00
87e077d70d oops 2024-09-04 10:00:40 +03:00
52f7c15ac8 refactor: change how function scope keeps track of arguments 2024-09-04 10:00:38 +03:00
6932bea677 refactor: remove unused class 2024-09-04 10:00:36 +03:00
82d6f52a26 refactor: make some classes final for performance 2024-09-04 10:00:33 +03:00
1b87c2f5a6 fix: add for-of to statement list 2024-09-04 10:00:30 +03:00
163dfe7b6e feat: implement access to intrinsics 2024-09-04 10:00:25 +03:00
2b6d4a87ca fix: for in and for of not reading open paren 2024-09-04 10:00:22 +03:00
349d392269 major rewrite: clean up a lot of code and lay ground for ES6 support 2024-09-04 10:00:15 +03:00
6481e992fa feat: implement "has" function for scopes 2024-09-04 10:00:11 +03:00
4a5e5a71af feat: Create new scope system for ES6+ support 2024-09-04 10:00:02 +03:00
89ba921b4a refactor: rename statements to nodes 2024-09-04 10:00:00 +03:00
a45f4109d8 feat: add environment in Source 2024-09-04 09:59:57 +03:00
62aba62a41 refactor: make Environment more reusable 2024-09-04 09:59:54 +03:00
4048d6ef1c refactor: rename ES5 to JavaScript 2024-09-04 09:59:42 +03:00
d0ccf00f14 everything all at once 2024-09-04 09:59:35 +03:00
f09feae08f refactor: clean up parsing 2024-09-04 09:59:32 +03:00
ef0fc5a61d refactor: distribute parse functions in node classes 2024-09-04 09:59:28 +03:00
bab59d454f refactor: Transition to a Value class 2024-09-04 09:59:26 +03:00
3475e3a130 refactor: Remove environment-related bloat 2024-09-04 09:59:15 +03:00
49b52d90a7 fix: wrappers cache compare objects with .equals and not == 2024-04-21 11:03:00 +03:00
8a8de518a6 feat: make Function constructor 2024-04-20 23:44:02 +03:00
099201e4ad refactor: remove testing junk in REPL 2024-04-20 22:23:45 +03:00
f8553b79f9 fix: run module in an isolated context 2024-04-20 22:22:55 +03:00
ba6462458c fix: some fixes in the filesystem 2024-04-20 22:18:47 +03:00
e33cdbb172 fix: properties not applied to wrappers without constructor method 2024-04-13 01:03:34 +03:00
fc6ddf7d3c feat: allow interface proxy wrappers 2024-04-12 16:37:06 +03:00
7f275095a2 fix: continue statement compiled incorrectly 2024-04-07 12:50:58 +03:00
90d019f92a bump 2024-04-07 12:33:48 +03:00
6fb31be12c fix(debugger): handle all errors when generating description 2024-04-07 12:33:26 +03:00
d6ede0b404 fix: incorrect toFixed behavior 2024-04-03 15:52:01 +03:00
71b40240c0 feat: add Number.toFixed 2024-04-03 15:09:01 +03:00
a8775d212f fix: clean up extensions at some points 2024-04-03 14:52:29 +03:00
71872a8d64 fix 2024-04-03 14:25:14 +03:00
c707f880f7 fix: use Extensions instead of Environment 2024-04-03 14:21:23 +03:00
0d629a6e82 fix: use correct class instead of proxy 2024-04-03 12:27:15 +03:00
6eea342d04 fix: fuck 2024-04-02 18:24:43 +03:00
ece9cf68dc fix: correctly update proto chain 2024-04-02 18:19:05 +03:00
11ecd8c68f fix: exec debugger close logic on application exit 2024-04-02 18:05:49 +03:00
48bd304c6e fix: environment forks fixes 2024-04-02 18:05:20 +03:00
d8e46c3149 fix: clone environment correctly 2024-03-31 16:11:32 +03:00
5fc5eb08f8 fix: update breakpoints when removing bp 2024-03-30 12:52:44 +02:00
8acbc003c4 fix: properly resolve breakpoints 2024-03-30 12:13:04 +02:00
fda33112a7 fix: load maps when attaching debugger 2024-03-30 11:13:45 +02:00
67b2413d7c bump2 2024-03-30 10:36:55 +02:00
3a05416510 bump 2024-03-30 10:30:26 +02:00
c291328cc3 fix: detach debugger after close 2024-03-30 10:22:12 +02:00
7cb267b0d9 fix: some issues with debugger 2024-03-30 09:55:20 +02:00
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
309 changed files with 13075 additions and 15654 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:
@@ -16,15 +16,17 @@ jobs:
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:
@@ -32,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,44 +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. 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 Engine(true /* false if you dont want debugging */);
var env = new Environment(null, null, null);
var debugger = new DebugServer();
// Create one target for the engine and start debugging server
debugger.targets.put("target", (socket, req) -> new SimpleDebugger(socket, engine));
debugger.start(new InetSocketAddress("127.0.0.1", 9229), true);
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());
// Queue code to load internal libraries and start engine
engine.pushMsg(false, null, new Internals().getApplier(env));
engine.start();
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);
while (true) {
try {
var raw = Reading.read();
if (raw == null) break;
// Push a message to the engine with the raw REPL code
var res = engine.pushMsg(
false, new Context(engine).pushEnv(env),
new Filename("jscript", "repl.js"), raw, null
).await();
Values.printValue(null, res);
}
catch (EngineException e) { Values.printError(e, ""); }
catch (SyntaxException ex) {
System.out.println("Syntax error:" + ex.msg);
}
catch (IOException e) { }
}
// Get our result
System.out.println(awaitable.await());
```

90
build.gradle Normal file
View File

@@ -0,0 +1,90 @@
import java.text.SimpleDateFormat
plugins {
id 'application'
id 'net.nemerosa.versioning' version '2.15.0'
id 'org.ajoberstar.grgit' version '5.0.0-rc.3' // required by gradle
// TODO: figure out how to integrate proguard
// id "com.github.xaverkapeller.proguard-annotations"
}
base.archivesName = project.project_name
version = project.project_version
group = project.project_group
description = 'ES5-compliant JavaScript interpreter'
repositories {
mavenCentral()
}
dependencies {
annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2'
compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configure([tasks.compileJava]) {
options.release = 8
}
jar {
manifest {
attributes(
'Main-Class': project.main_class,
'Build-Timestamp': new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-Branch': versioning.info.branch,
'Build-Revision': versioning.info.commit,
'Build-Jdk': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})",
'Build-Author': 'TopchetoEU'
)
}
}
application {
mainClass = project.main_class
applicationDefaultJvmArgs = ['-Xmx2G', '-Xms2G', '-server', '-Dfile.encoding=UTF-8']
}
distZip {
eachFile { file ->
if (file.path.contains('bin')) {
file.exclude()
}
}
}
distTar {
eachFile { file ->
if (file.path.contains('bin')) {
file.exclude()
}
}
}
processResources {
filesMatching "metadata.json", {
expand(
version: project.project_version,
name: project.project_name
)
}
}
test {
useJUnitPlatform()
}
wrapper {
gradleVersion = '8.10'
}

View File

@@ -1,80 +0,0 @@
const { spawn } = require('child_process');
const fs = require('fs/promises');
const pt = require('path');
const { argv, exit } = 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;
console.log(e.toString());
exit(-1);
}
})();

4
gradle.properties Normal file
View File

@@ -0,0 +1,4 @@
project_group = me.topchetoeu
project_name = jscript
project_version = 0.9.41-beta
main_class = me.topchetoeu.jscript.runtime.SimpleRepl

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

13
settings.gradle Normal file
View File

@@ -0,0 +1,13 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = properties.project_name

View File

@@ -1,41 +1,58 @@
package me.topchetoeu.jscript.filesystem;
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 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);
}
}
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,16 @@
package me.topchetoeu.jscript.common;
public class FunctionBody {
public final FunctionBody[] children;
public final Instruction[] instructions;
public final int localsN, capturablesN, capturesN, length;
public FunctionBody(int localsN, int capturablesN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) {
this.children = children;
this.length = length;
this.localsN = localsN;
this.capturablesN = capturablesN;
this.capturesN = capturesN;
this.instructions = instructions;
}
}

View File

@@ -0,0 +1,505 @@
package me.topchetoeu.jscript.common;
import java.util.HashMap;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
import me.topchetoeu.jscript.common.parsing.Location;
public class Instruction {
public static enum Type {
RETURN(0x00),
NOP(0x01),
THROW(0x02),
THROW_SYNTAX(0x03),
DELETE(0x04),
TRY_START(0x05),
TRY_END(0x06),
CALL(0x10),
@Deprecated
CALL_MEMBER(0x11),
CALL_NEW(0x12),
CALL_SUPER(0x13),
JMP_IF(0x18),
JMP_IFN(0x19),
JMP(0x1A),
PUSH_UNDEFINED(0x20),
PUSH_NULL(0x21),
PUSH_BOOL(0x22),
PUSH_NUMBER(0x23),
PUSH_STRING(0x24),
DUP(0x25),
DISCARD(0x26),
LOAD_FUNC(0x30),
LOAD_ARR(0x31),
LOAD_OBJ(0x32),
LOAD_REGEX(0x33),
LOAD_GLOB(0x38),
LOAD_INTRINSICS(0x39),
LOAD_ARGS(0x3A),
LOAD_REST_ARGS(0x3B),
LOAD_CALLEE(0x3C),
LOAD_THIS(0x3D),
LOAD_ERROR(0x3E),
LOAD_VAR(0x40),
LOAD_MEMBER(0x41),
LOAD_MEMBER_INT(0x42),
LOAD_MEMBER_STR(0x43),
STORE_VAR(0x48),
STORE_MEMBER(0x49),
STORE_MEMBER_INT(0x4A),
STORE_MEMBER_STR(0x4B),
DEF_PROP(0x50),
DEF_FIELD(0x51),
KEYS(0x52),
TYPEOF(0x53),
OPERATION(0x54),
EXTEND(0x55),
GLOB_GET(0x60),
GLOB_SET(0x61),
GLOB_DEF(0x62),
// CAP_INIT(0x70),
VAR_INIT(0x71),
CAP_FREE(0x72),
VAR_FREE(0x73);
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:
// case GLOB_SET:
// rawType |= (boolean)get(0) ? 128 : 0; break;
// case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
// default:
// }
// writer.writeByte(rawType);
// switch (type) {
// case CALL:
// case CALL_NEW:
// case CALL_MEMBER:
// writer.writeInt(get(0));
// writer.writeUTF(get(1));
// 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));
// writer.writeUTF(get(0));
// break;
// }
// case LOAD_REGEX: writer.writeUTF(get(0)); break;
// case LOAD_VAR: writer.writeInt(get(0)); break;
// case GLOB_DEF: writer.writeUTF(get(0)); break;
// case GLOB_GET: writer.writeUTF(get(0)); break;
// case GLOB_SET:
// 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_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(), stream.readUTF());
// case CALL_NEW: return callNew(stream.readInt(), stream.readUTF());
// case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF());
// 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(), stream.readUTF(), 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 GLOB_DEF: return globDef(stream.readUTF());
// case GLOB_GET: return globGet(stream.readUTF());
// case GLOB_SET: return globSet(stream.readUTF(), flag);
// 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_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(), 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 throwSyntax(Location loc, String err) {
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
}
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, boolean hasSelf, String name) {
return new Instruction(Type.CALL, argn, hasSelf, name);
}
public static Instruction call(int argn, boolean hasSelf) {
return call(argn, hasSelf, "");
}
@Deprecated
public static Instruction callMember(int argn, String name) {
return new Instruction(Type.CALL_MEMBER, argn, name);
}
@Deprecated
public static Instruction callMember(int argn) {
return new Instruction(Type.CALL_MEMBER, argn, "");
}
public static Instruction callNew(int argn, String name) {
return new Instruction(Type.CALL_NEW, argn, name);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn, "");
}
public static Instruction callSuper(int argn) {
return new Instruction(Type.CALL_SUPER, 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 IntFunction<Instruction> jmp(IntSupplier pos) {
return i -> new Instruction(Type.JMP, pos.getAsInt() - i);
}
public static IntFunction<Instruction> jmpIf(IntSupplier pos) {
return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i);
}
public static IntFunction<Instruction> jmpIfNot(IntSupplier pos) {
return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i);
}
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 globDef(String name) {
return new Instruction(Type.GLOB_DEF, name);
}
public static Instruction globGet(String name, boolean force) {
return new Instruction(Type.GLOB_GET, name, force);
}
public static Instruction globSet(String name, boolean keep, boolean define) {
return new Instruction(Type.GLOB_SET, name, keep, define);
}
public static Instruction loadVar(int i) {
return new Instruction(Type.LOAD_VAR, i);
}
public static Instruction loadThis() {
return new Instruction(Type.LOAD_THIS);
}
public static Instruction loadArgs(boolean real) {
return new Instruction(Type.LOAD_ARGS, real);
}
public static Instruction loadRestArgs(int offset) {
return new Instruction(Type.LOAD_REST_ARGS, offset);
}
public static Instruction loadCallee() {
return new Instruction(Type.LOAD_CALLEE);
}
public static Instruction loadGlob() {
return new Instruction(Type.LOAD_GLOB);
}
public static Instruction loadIntrinsics(String key) {
return new Instruction(Type.LOAD_INTRINSICS, key);
}
public static Instruction loadError() {
return new Instruction(Type.LOAD_ERROR);
}
public static Instruction loadMember() {
return new Instruction(Type.LOAD_MEMBER);
}
public static Instruction loadMember(int member) {
return new Instruction(Type.LOAD_MEMBER_INT, member);
}
public static Instruction loadMember(String member) {
return new Instruction(Type.LOAD_MEMBER_STR, member);
}
public static Instruction loadRegex(String pattern, String flags) {
return new Instruction(Type.LOAD_REGEX, pattern, flags);
}
// TODO: make this capturing a concern of the compiler
public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, boolean noThis, String name, int[] captures) {
if (name == null) name = "";
var args = new Object[6 + captures.length];
args[0] = id;
args[1] = name;
args[2] = callable;
args[3] = constructible;
args[4] = captureThis;
args[5] = noThis;
for (var i = 0; i < captures.length; i++) args[i + 6] = 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, 0);
}
public static Instruction dup(int count, int offset) {
return new Instruction(Type.DUP, count, offset);
}
// public static Instruction storeVar(int i) {
// return new Instruction(Type.STORE_VAR, i, false);
// }
public static Instruction storeVar(int i, boolean keep, boolean initialize) {
return new Instruction(Type.STORE_VAR, i, keep, initialize);
}
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 storeMember(String key) {
return new Instruction(Type.STORE_MEMBER_STR, key, false);
}
public static Instruction storeMember(String key, boolean keep) {
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
}
public static Instruction storeMember(int key) {
return new Instruction(Type.STORE_MEMBER_INT, key, false);
}
public static Instruction storeMember(int key, boolean keep) {
return new Instruction(Type.STORE_MEMBER_INT, key, 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 own, boolean onlyEnumerable) {
return new Instruction(Type.KEYS, own, onlyEnumerable);
}
public static Instruction defProp(boolean setter, boolean enumerable) {
return new Instruction(Type.DEF_PROP, setter, enumerable);
}
public static Instruction defField(boolean enumerable) {
return new Instruction(Type.DEF_FIELD, enumerable);
}
public static Instruction extend() {
return new Instruction(Type.EXTEND);
}
public static Instruction operation(Operation op) {
return new Instruction(Type.OPERATION, op);
}
public static Instruction capFree(int i) {
return new Instruction(Type.CAP_FREE, i);
}
public static Instruction varFree(int i) {
return new Instruction(Type.VAR_FREE, i);
}
public static Instruction varInit(int i, boolean force) {
return new Instruction(Type.VAR_INIT, i, force);
}
// public static Instruction stackAlloc(int start, int n) {
// return new Instruction(Type.STACK_ALLOC, start, start + n);
// }
// public static Instruction stackRealloc(int start, int n) {
// return new Instruction(Type.STACK_REALLOC, start, start + n);
// }
@Override public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
}

View File

@@ -1,9 +1,18 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.json.JSON;
public class Metadata {
private static final String VERSION = "${VERSION}";
private static final String AUTHOR = "${AUTHOR}";
private static final String NAME = "${NAME}";
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";

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,85 @@
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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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 byte[] streamToBytes(InputStream in) {
try {
List<byte[]> bufs = null;
byte[] result = null;
int total = 0;
int n;
do {
var buf = new byte[8192];
int nread = 0;
// read to EOF which may read more or less than buffer size
while ((n = in.read(buf, nread, buf.length - nread)) > 0) {
nread += n;
}
if (nread > 0) {
if (Integer.MAX_VALUE - 8 - total < nread) throw new OutOfMemoryError("Required array size too large");
if (nread < buf.length) buf = Arrays.copyOfRange(buf, 0, nread);
total += nread;
if (result == null) result = buf;
else {
if (bufs == null) {
bufs = new ArrayList<>();
bufs.add(result);
}
bufs.add(buf);
}
}
// if the last call to read returned -1 or the number of bytes
// requested have been read then break
} while (n >= 0);
if (bufs == null) {
if (result == null) return new byte[0];
return result.length == total ? result : Arrays.copyOf(result, total);
}
result = new byte[total];
int offset = 0;
int remaining = total;
for (byte[] b : bufs) {
int count = Math.min(b.length, remaining);
System.arraycopy(b, 0, result, offset, count);
offset += count;
remaining -= count;
}
return result;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public static String streamToString(InputStream in) {
return new String(streamToBytes(in));
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/" + name);
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));
}
}

View File

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

View File

@@ -0,0 +1,187 @@
package me.topchetoeu.jscript.common.environment;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.Supplier;
public class Environment {
public final Environment parent;
private final Map<Key<Object>, Object> map = new HashMap<>();
private final Set<Key<Object>> hidden = new HashSet<>();
private final Map<MultiKey<Object>, Set<Object>> multi = new HashMap<>();
private final Map<MultiKey<Object>, Set<Object>> multiHidden = new HashMap<>();
@SuppressWarnings("unchecked")
private <T> Set<T> getAll(MultiKey<T> key, boolean forceClone) {
Set<T> parent = null, child = null;
boolean cloned = false;
if (this.parent != null && !hidden.contains(key)) {
parent = this.parent.getAll(key, false);
if (parent.size() == 0) parent = null;
else if (multiHidden.containsKey(key)) {
parent = new HashSet<>(parent);
parent.removeAll(multiHidden.get(key));
cloned = true;
}
}
if (multi.containsKey(key)) {
child = (Set<T>)multi.get(key);
if (child.size() == 0) child = null;
}
if (!forceClone) {
if (parent == null && child == null) return new HashSet<>();
if (parent == null && child != null) return child;
if (parent != null && child == null) return parent;
}
if (!cloned) parent = new HashSet<>();
parent.addAll(child);
return parent;
}
private <T> T getMulti(MultiKey<T> key) {
return key.of(getAll(key, false));
}
private boolean hasMulti(MultiKey<?> key) {
return getAll(key, false).size() > 0;
}
@SuppressWarnings("all")
private <T> Environment addMulti(MultiKey<T> key, T value) {
if (!multi.containsKey(key)) {
if (hidden.contains(key)) {
multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true));
hidden.remove(key);
}
multi.put((MultiKey)key, new HashSet<>());
}
multi.get(key).add(value);
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(Key<T> key) {
if (key instanceof MultiKey) return getMulti((MultiKey<T>)key);
if (map.containsKey(key)) return (T)map.get(key);
else if (!hidden.contains(key) && parent != null) return parent.get(key);
else return null;
}
public boolean has(Key<?> key) {
if (key instanceof MultiKey) return hasMulti((MultiKey<?>)key);
if (map.containsKey(key)) return true;
else if (!hidden.contains(key) && parent != null) return parent.has(key);
else return false;
}
public boolean hasNotNull(Key<?> key) {
return get(key) != null;
}
public <T> T get(Key<T> key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
}
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
if (has(key)) return get(key);
else return defaultVal.get();
}
@SuppressWarnings("unchecked")
public <T> Environment add(Key<T> key, T val) {
if (key instanceof MultiKey) return add(key, val);
map.put((Key<Object>)key, val);
hidden.remove(key);
return this;
}
public Environment add(Key<Void> key) {
return add(key, null);
}
@SuppressWarnings("all")
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
for (var pair : map.entrySet()) {
if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) {
for (var val : (Iterable<?>)pair.getValue()) {
addMulti((MultiKey<Object>)pair.getKey(), val);
}
}
else add((Key<Object>)pair.getKey(), pair.getValue());
}
map.putAll((Map)map);
hidden.removeAll(map.keySet());
return this;
}
public Environment addAll(Map<Key<?>, ?> map) {
return addAll(map, true);
}
@SuppressWarnings("unchecked")
public Environment remove(Key<?> key) {
map.remove(key);
multi.remove(key);
multiHidden.remove(key);
hidden.add((Key<Object>)key);
return this;
}
@SuppressWarnings("all")
public <T> Environment remove(MultiKey<T> key, T val) {
if (multi.containsKey(key)) {
multi.get(key).remove(val);
multiHidden.get(key).add(val);
if (multi.get(key).size() == 0) {
multi.remove(key);
multiHidden.remove(key);
hidden.add((Key)key);
}
}
return this;
}
public <T> T init(Key<T> key, T val) {
if (!has(key)) this.add(key, val);
return val;
}
public <T> T initFrom(Key<T> key, Supplier<T> val) {
if (!has(key)) {
var res = val.get();
this.add(key, res);
return res;
}
else return get(key);
}
public Environment child() {
return new Environment(this);
}
public Environment(Environment parent) {
this.parent = parent;
}
public Environment() {
this.parent = null;
}
public static Environment wrap(Environment env) {
if (env == null) return empty();
else return env;
}
public static Environment empty() {
return new Environment();
}
public static int nextId() {
return new Random().nextInt();
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.common.environment;
public interface Key<T> {
public static <T> Key<T> of() {
return new Key<>() { };
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.common.environment;
import java.util.Set;
public interface MultiKey<T> extends Key<T> {
public T of(Set<T> values);
}

View File

@@ -0,0 +1,172 @@
package me.topchetoeu.jscript.common.json;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
public class JSON {
public static ParseRes<JSONElement> parseString(Source src, int i) {
var res = Parsing.parseString(src, i);
if (!res.isSuccess()) return res.chainError();
return ParseRes.res(JSONElement.string(res.result), res.n);
}
public static ParseRes<JSONElement> parseNumber(Source src, int i) {
var res = Parsing.parseNumber(src, i, true);
if (!res.isSuccess()) return res.chainError();
else return ParseRes.res(JSONElement.number(res.result), res.n);
}
public static ParseRes<JSONElement> parseLiteral(Source src, int i) {
var id = Parsing.parseIdentifier(src, i);
if (!id.isSuccess()) return ParseRes.failed();
else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n);
else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n);
else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n);
else return ParseRes.failed();
}
public static ParseRes<JSONElement> parseValue(Source src, int i) {
return ParseRes.first(src, i,
JSON::parseString,
JSON::parseNumber,
JSON::parseLiteral,
JSON::parseMap,
JSON::parseList
);
}
public static ParseRes<JSONMap> parseMap(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "{")) return ParseRes.failed();
n++;
var values = new JSONMap();
if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(new HashMap<>()), n + 1);
while (true) {
var name = parseString(src, i + n);
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index");
n += name.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon");
n++;
var res = parseValue(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
values.put(name.result.toString(), res.result);
n += res.n;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) n++;
else if (src.is(i + n, "}")) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static ParseRes<JSONList> parseList(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n++, "[]")) return ParseRes.failed();
var values = new JSONList();
if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1);
while (true) {
var res = parseValue(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
values.add(res.result);
n += res.n;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) n++;
else if (src.is(i + n, "]")) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(new Source(null, filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
else return JSONElement.of(res.result);
}
public static String stringify(JSONElement el) {
if (el.isNumber()) {
var d = el.number();
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
if (d == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(d)) return "NaN";
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
}
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(el.list().get(i)));
}
res.append("]");
return res.toString();
}
if (el.isMap()) {
var res = new StringBuilder().append("{");
var entries = el.map().entrySet().stream().collect(Collectors.toList());
for (int i = 0; i < entries.size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
res.append(":");
res.append(stringify(entries.get(i).getValue()));
}
res.append("}");
return res.toString();
}
return null;
}
public static String stringify(JSONMap map) {
return stringify(JSONElement.of(map));
}
public static String stringify(JSONList list) {
return stringify(JSONElement.of(list));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,192 @@
package me.topchetoeu.jscript.common.mapping;
import java.util.ArrayList;
import java.util.Arrays;
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.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.Scope;
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(Scope scope) {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
}
public FunctionMap build() {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
}
private FunctionMapBuilder() { }
}
public static final FunctionMap EMPTY = new FunctionMap();
private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
public final String[] localNames, captureNames;
public Location toLocation(int pc, boolean approxiamte) {
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
var res = pcToLoc.get(pc);
if (!approxiamte || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry();
if (entry == null) return null;
else return entry.getValue();
}
public Location toLocation(int pc) {
return toLocation(pc, false);
}
public BreakpointType getBreakpoint(int pc) {
return bps.getOrDefault(pc, BreakpointType.NONE);
}
public Location correctBreakpoint(Location loc) {
var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
}
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
var candidates = new HashMap<Filename, TreeSet<Location>>();
for (var name : bpLocs.keySet()) {
if (filename.matcher(name.toString()).matches()) {
candidates.put(name, bpLocs.get(name));
}
}
var res = new ArrayList<Location>(candidates.size());
for (var candidate : candidates.entrySet()) {
var val = correctBreakpoint(Location.of(candidate.getKey(), line, column));
if (val == null) continue;
res.add(val);
}
return res;
}
public List<Location> breakpoints(Location start, Location end) {
if (!Objects.equals(start.filename(), end.filename())) return Arrays.asList();
NavigableSet<Location> set = bpLocs.get(start.filename());
if (set == null) return Arrays.asList();
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 static FunctionMap apply(FunctionMap funcMap, SourceMap map) {
// var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames);
// for (var el : funcMap.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(new HashMap<>(), new HashMap<>(), 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,4 +1,4 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common.parsing;
import java.io.File;
@@ -6,22 +6,17 @@ public class Filename {
public final String protocol;
public final String path;
public String toString() {
@Override public String toString() {
return protocol + "://" + path;
}
@Override
public int hashCode() {
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
@Override public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
@@ -40,11 +35,6 @@ public class Filename {
return true;
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
@@ -57,4 +47,13 @@ public class Filename {
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) {
// // File file = new File("/" + path.trim().replace("\\", "/"));
// // String normalizedPath = new File("/" + path.trim().replace("\\", "/")).getAbsolutePath().replaceFirst("^/", "").replace("\\", "/");
// // return normalizedPath;
// 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,108 @@
package me.topchetoeu.jscript.common.parsing;
import java.util.ArrayList;
import java.util.Objects;
public abstract class Location implements Comparable<Location> {
public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1);
public abstract int line();
public abstract int start();
public abstract Filename filename();
public final String toString() {
var res = new ArrayList<String>();
if (filename() != null) res.add(filename().toString());
if (line() >= 0) res.add(line() + 1 + "");
if (start() >= 0) res.add(start() + 1 + "");
return String.join(":", res);
}
public final Location add(int n) {
var self = this;
return new Location() {
@Override public Filename filename() { return self.filename(); }
@Override public int start() { return self.start() + n; }
@Override public int line() { return self.line(); }
};
}
public final Location nextLine() {
var self = this;
return new Location() {
@Override public Filename filename() { return self.filename(); }
@Override public int start() { return 0; }
@Override public int line() { return self.line() + 1; }
};
}
@Override public int hashCode() {
return Objects.hash(line(), start(), filename());
}
@Override public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Location)) return false;
var other = (Location)obj;
if (!Objects.equals(this.start(), other.start())) return false;
if (!Objects.equals(this.line(), other.line())) return false;
if (!Objects.equals(this.filename(), 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 static Location of(Filename filename, int line, int start) {
return new Location() {
@Override public Filename filename() { return filename; }
@Override public int start() { return start; }
@Override public int line() { return line; }
};
}
public static Location of(String raw) {
var i0 = raw.lastIndexOf(':');
if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1);
var i1 = raw.lastIndexOf(':', i0);
if (i0 < 0) {
try {
return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1);
}
catch (NumberFormatException e) {
return Location.of(Filename.parse(raw), -1, -1);
}
}
int start, line;
try {
start = Integer.parseInt(raw.substring(i1 + 1));
}
catch (NumberFormatException e) {
return Location.of(Filename.parse(raw), -1, -1);
}
try {
line = Integer.parseInt(raw.substring(i0 + 1, i1));
}
catch (NumberFormatException e) {
return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1);
}
return Location.of(Filename.parse(raw.substring(0, i0)), start, line);
}
}

View File

@@ -0,0 +1,80 @@
package me.topchetoeu.jscript.common.parsing;
public class ParseRes<T> {
public static enum State {
SUCCESS,
FAILED,
ERROR;
public boolean isSuccess() { return this == SUCCESS; }
public boolean isFailed() { return this == FAILED; }
public boolean isError() { return this == ERROR; }
}
public final ParseRes.State state;
public final Location errorLocation;
public final String error;
public final T result;
public final int n;
private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) {
this.result = result;
this.n = readN;
this.state = state;
this.error = error;
this.errorLocation = errorLocation;
}
public ParseRes<T> setN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, null, result, i);
}
public ParseRes<T> addN(int n) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, null, result, this.n + n);
}
public <T2> ParseRes<T2> chainError() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
return new ParseRes<>(state, errorLocation, error, null, 0);
}
@SuppressWarnings("unchecked")
public <T2> ParseRes<T2> chainError(ParseRes<?> other) {
if (!this.isError()) return other.chainError();
return (ParseRes<T2>) this;
}
@SuppressWarnings("unchecked")
public <T2> ParseRes<T2> chainError(Location loc, String error) {
if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0);
return (ParseRes<T2>) this;
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static <T> ParseRes<T> failed() {
return new ParseRes<T>(State.FAILED, null, null, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error) {
// TODO: differentiate definitive and probable errors
return new ParseRes<>(State.ERROR, loc, error, null, 0);
}
public static <T> ParseRes<T> res(T val, int i) {
return new ParseRes<>(State.SUCCESS, null, null, val, i);
}
@SafeVarargs
@SuppressWarnings("all")
public static <T> ParseRes<T> first(Source src, int i, Parser ...parsers) {
int n = Parsing.skipEmpty(src, i);
ParseRes<T> error = ParseRes.failed();
for (var parser : parsers) {
var res = parser.parse(src, i + n);
if (res.isSuccess()) return res.addN(n);
if (res.isError() && error.isFailed()) error = res.chainError();
}
return error;
}
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.common.parsing;
public interface Parser<T> {
public ParseRes<T> parse(Source src, int i);
}

View File

@@ -0,0 +1,420 @@
package me.topchetoeu.jscript.common.parsing;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
public class Parsing {
public static boolean isDigit(Character c) {
return c != null && c >= '0' && c <= '9';
}
public static boolean isAny(char c, String alphabet) {
return alphabet.contains(Character.toString(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;
}
public static int skipEmpty(Source src, int i) {
return skipEmpty(src, i, true);
}
public static int skipEmpty(Source src, int i, boolean noComments) {
int n = 0;
if (i == 0 && src.is(0, "#!")) {
while (!src.is(n, '\n')) n++;
n++;
}
var isSingle = false;
var isMulti = false;
while (i + n < src.size()) {
if (isSingle) {
if (src.is(i + n, '\n')) {
n++;
isSingle = false;
}
else n++;
}
else if (isMulti) {
if (src.is(i + n, "*/")) {
n += 2;
isMulti = false;
}
else n++;
}
else if (src.is(i + n, "//")) {
n += 2;
isSingle = true;
}
else if (src.is(i + n, "/*")) {
n += 2;
isMulti = true;
}
else if (src.is(i + n, Character::isWhitespace)) {
n++;
}
else break;
}
return n;
}
public static ParseRes<Character> parseChar(Source src, int i) {
int n = 0;
if (src.is(i + n, '\\')) {
n++;
char c = src.at(i + n++);
if (c == 'b') return ParseRes.res('\b', n);
else if (c == 't') return ParseRes.res('\t', n);
else if (c == 'n') return ParseRes.res('\n', n);
else if (c == 'f') return ParseRes.res('\f', n);
else if (c == 'r') return ParseRes.res('\r', n);
else if (c == '0') {
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else return ParseRes.res('\0', n);
}
else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else if (c == 'x') {
var newC = 0;
for (var j = 0; j < 2; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence.");
int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence.");
n++;
newC = (newC << 4) | val;
}
return ParseRes.res((char)newC, n);
}
else if (c == 'u') {
var newC = 0;
for (var j = 0; j < 4; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
n++;
newC = (newC << 4) | val;
}
return ParseRes.res((char)newC, n);
}
else if (c == '\n') return ParseRes.res(null, n);
}
return ParseRes.res(src.at(i + n), n + 1);
}
public static ParseRes<String> parseIdentifier(Source src, int i) {
var n = skipEmpty(src, i);
var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && Parsing.isDigit(c)) break;
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
res.append(c);
n++;
first = false;
}
if (res.length() <= 0) return ParseRes.failed();
else return ParseRes.res(res.toString(), n);
}
public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
var n = skipEmpty(src, i);
var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && Parsing.isDigit(c)) break;
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
res.append(c);
n++;
first = false;
}
if (res.length() <= 0) return ParseRes.failed();
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
else return ParseRes.failed();
}
public static boolean isIdentifier(Source src, int i, String test) {
return parseIdentifier(src, i, test).isSuccess();
}
public static ParseRes<String> parseOperator(Source src, int i, String op) {
var n = skipEmpty(src, i);
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
else return ParseRes.failed();
}
private static ParseRes<Double> parseHex(Source src, int i) {
int n = 0;
double res = 0;
while (true) {
int digit = Parsing.fromHex(src.at(i + n, '\0'));
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 16;
res += digit;
}
}
private static ParseRes<Double> parseOct(Source src, int i) {
int n = 0;
double res = 0;
while (true) {
int digit = src.at(i + n, '\0') - '0';
if (digit < 0 || digit > 9) break;
if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit);
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 8;
res += digit;
}
return ParseRes.res(res, n);
}
public static ParseRes<String> parseString(Source src, int i) {
var n = skipEmpty(src, i);
char quote;
if (src.is(i + n, '\'')) quote = '\'';
else if (src.is(i + n, '"')) quote = '"';
else return ParseRes.failed();
n++;
var res = new StringBuilder();
while (true) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
if (src.is(i + n, quote)) {
n++;
return ParseRes.res(res.toString(), n);
}
var charRes = parseChar(src, i + n);
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
n += charRes.n;
if (charRes.result != null) res.append(charRes.result);
}
}
public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
var n = skipEmpty(src, i);
double whole = 0;
double fract = 0;
long exponent = 0;
boolean parsedAny = false;
boolean negative = false;
if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2;
var res = parseHex(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
n += 2;
var res = parseOct(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
else if (src.is(i + n, '0')) {
n++;
parsedAny = true;
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed");
}
while (src.is(i + n, Parsing::isDigit)) {
parsedAny = true;
whole *= 10;
whole += src.at(i + n++) - '0';
}
if (src.is(i + n, '.')) {
parsedAny = true;
n++;
while (src.is(i + n, Parsing::isDigit)) {
fract += src.at(i + n++) - '0';
fract /= 10;
}
}
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
parsedAny = true;
boolean expNegative = false;
boolean parsedE = false;
if (src.is(i + n, '-')) {
expNegative = true;
n++;
}
else if (src.is(i + n, '+')) n++;
while (src.is(i + n, Parsing::isDigit)) {
parsedE = true;
exponent *= 10;
if (expNegative) exponent -= src.at(i + n++) - '0';
else exponent += src.at(i + n++) - '0';
}
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
}
public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) {
var n = skipEmpty(src, i);
double whole = 0;
double fract = 0;
long exponent = 0;
boolean parsedAny = false;
boolean negative = false;
if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
while (src.is(i + n, Parsing::isDigit)) {
parsedAny = true;
whole *= 10;
whole += src.at(i + n++) - '0';
}
if (src.is(i + n, '.')) {
parsedAny = true;
n++;
while (src.is(i + n, Parsing::isDigit)) {
fract += src.at(i + n++) - '0';
fract /= 10;
}
}
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
parsedAny = true;
boolean expNegative = false;
boolean parsedE = false;
if (src.is(i + n, '-')) {
expNegative = true;
n++;
}
else if (src.is(i + n, '+')) n++;
while (src.is(i + n, Parsing::isDigit)) {
parsedE = true;
exponent *= 10;
if (expNegative) exponent -= src.at(i + n++) - '0';
else exponent += src.at(i + n++) - '0';
}
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
}
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
var n = skipEmpty(src, i);
double result = 0;
boolean parsedAny = false;
boolean negative = false;
if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2;
var res = parseHex(src, i);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
while (true) {
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n)));
if (digit < 0) break;
parsedAny = true;
result += digit;
result *= alphabet.length();
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-result, n);
else return ParseRes.res(-result, n);
}
}

View File

@@ -0,0 +1,75 @@
package me.topchetoeu.jscript.common.parsing;
import java.util.function.Predicate;
import me.topchetoeu.jscript.common.environment.Environment;
public class Source {
public final Environment env;
public final Filename filename;
public final String src;
private int[] lineStarts;
public Location loc(int offset) {
return new SourceLocation(filename, lineStarts, offset);
}
public boolean is(int i, char c) {
return i >= 0 && i < src.length() && src.charAt(i) == c;
}
public boolean is(int i, String src) {
if (i < 0 || i + src.length() > size()) return false;
for (int j = 0; j < src.length(); j++) {
if (at(i + j) != src.charAt(j)) return false;
}
return true;
}
public boolean is(int i, Predicate<Character> predicate) {
if (i < 0 || i >= src.length()) return false;
return predicate.test(at(i));
}
public char at(int i) {
return src.charAt(i);
}
public char at(int i, char defaultVal) {
if (i < 0 || i >= src.length()) return defaultVal;
else return src.charAt(i);
}
public int size() {
return src.length();
}
public String slice(int start, int end) {
return src.substring(start, end);
}
public Source(Environment env, Filename filename, String src) {
if (env == null) this.env = new Environment();
else this.env = env;
this.filename = filename;
this.src = src;
int n = 1;
lineStarts = new int[16];
lineStarts[0] = 0;
for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) {
if (n >= lineStarts.length) {
var newArr = new int[lineStarts.length * 2];
System.arraycopy(lineStarts, 0, newArr, 0, n);
lineStarts = newArr;
}
lineStarts[n++] = i + 1;
}
var newArr = new int[n];
System.arraycopy(lineStarts, 0, newArr, 0, n);
lineStarts = newArr;
}
public Source(String src) {
this(null, null, src);
}
}

View File

@@ -0,0 +1,66 @@
package me.topchetoeu.jscript.common.parsing;
import java.util.Objects;
public class SourceLocation extends Location {
private int[] lineStarts;
private int line;
private int start;
private final Filename filename;
private final int offset;
private void update() {
if (lineStarts == null) return;
int a = 0;
int b = lineStarts.length;
while (true) {
if (a + 1 >= b) break;
var mid = -((-a - b) >> 1);
var el = lineStarts[mid];
if (el < offset) a = mid;
else if (el > offset) b = mid;
else {
this.line = mid;
this.start = 0;
this.lineStarts = null;
return;
}
}
this.line = a;
this.start = offset - lineStarts[a];
this.lineStarts = null;
return;
}
@Override public Filename filename() { return filename; }
@Override public int line() {
update();
return line;
}
@Override public int start() {
update();
return start;
}
@Override public int hashCode() {
return Objects.hash(offset);
}
@Override public int compareTo(Location other) {
if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset);
else return super.compareTo(other);
}
@Override public boolean equals(Object obj) {
if (obj instanceof SourceLocation other) return this.offset == other.offset;
else return super.equals(obj);
}
public SourceLocation(Filename filename, int[] lineStarts, int offset) {
this.filename = filename;
this.lineStarts = lineStarts;
this.offset = offset;
}
}

View File

@@ -0,0 +1,244 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.members.FieldMemberNode;
import me.topchetoeu.jscript.compilation.members.Member;
import me.topchetoeu.jscript.compilation.members.MethodMemberNode;
import me.topchetoeu.jscript.compilation.members.PropertyMemberNode;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
public abstract class ClassNode extends FunctionNode {
public static final class ClassBody {
public final List<Member> staticMembers;
public final List<FieldMemberNode> protoFields;
public final List<Member> protoMembers;
public final Parameters constructorParameters;
public final CompoundNode constructorBody;
public final Node superExpr;
public final boolean hasConstr;
public ClassBody(
List<Member> staticMembers, List<FieldMemberNode> protoFields, List<Member> protoMembers,
Parameters constructorParameters, CompoundNode constructorBody,
Node superExpr, boolean hasConstr
) {
this.staticMembers = staticMembers;
this.protoFields = protoFields;
this.protoMembers = protoMembers;
this.constructorParameters = constructorParameters;
this.constructorBody = constructorBody;
this.superExpr = superExpr;
this.hasConstr = hasConstr;
}
}
public static final Key<Environment> CLASS_ROOT = Key.of();
public static final Key<Consumer<CompileResult>> SUPER = Key.of();
public static final Key<Consumer<CompileResult>> SUPER_PROTO = Key.of();
public static final Key<Consumer<CompileResult>> SUPER_CONSTR = Key.of();
public static final Key<Consumer<CompileResult>> ON_SUPER_CALL = Key.of();
public final ClassBody body;
public final String name;
@Override public String name() { return name; }
public void compileStatic(CompileResult target) {
for (var member : body.staticMembers) {
member.compile(target, true, false);
}
}
public void compilePrototype(CompileResult target) {
if (body.protoMembers.size() > 0) {
target.add(Instruction.dup());
target.add(Instruction.loadMember("prototype"));
for (var i = 0; i < body.protoMembers.size() - 1; i++) {
body.protoMembers.get(i).compile(target, true, false);
}
body.protoMembers.get(body.protoMembers.size() - 1).compile(target, false, false);
}
}
private void compileFieldInits(CompileResult target) {
for (var member : body.protoFields) {
target.add(Instruction.loadThis());
member.compile(target, false, true);
}
}
@Override protected void compilePreBody(CompileResult target) {
if (target.env.hasNotNull(SUPER_PROTO)) {
if (!body.hasConstr) {
throw new SyntaxException(loc(), "Default constructors in derived classes not supported");
// compileFieldInits(target);
}
}
else compileFieldInits(target);
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (body.superExpr == null) {
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, false, true, false, false, name, captures(id, target)));
compileStatic(target);
compilePrototype(target);
}
else {
var subtarget = target.subtarget().rootEnvironment(JavaScript.COMPILE_ROOT);
subtarget.scope.singleEntry = true;
subtarget.beginScope();
var protoVar = target.scope.defineTemp();
var constrVar = target.scope.defineTemp();
subtarget.env.add(SUPER_PROTO, t -> {
var i = t.scope.get(protoVar, false);
t.add(_i -> i.index().toGet());
});
subtarget.env.add(SUPER_CONSTR, t -> {
var i = t.scope.get(constrVar, false);
t.add(_i -> i.index().toGet());
});
var staticTarget = subtarget.subEnvironment();
staticTarget.env.add(SUPER, subtarget.env.get(SUPER_CONSTR));
staticTarget.env.add(CLASS_ROOT, staticTarget.env);
var protoTarget = subtarget.subEnvironment();
protoTarget.env.add(SUPER, subtarget.env.get(SUPER_PROTO));
protoTarget.env.add(CLASS_ROOT, protoTarget.env);
var constrEnv = subtarget.env.child();
constrEnv.add(SUPER, subtarget.env.get(SUPER_PROTO));
constrEnv.add(ON_SUPER_CALL, this::compileFieldInits);
constrEnv.add(CLASS_ROOT, constrEnv);
var id = target.addChild(compileBody(constrEnv, new FunctionScope(subtarget.scope), false, name, null));
target.add(_i -> Instruction.loadFunc(id, false, true, false, true, name, captures(id, target)));
body.superExpr.compile(target, true);
target.add(Instruction.extend());
target.add(Instruction.dup(1, 0));
target.add(Instruction.loadMember("prototype"));
target.add(_i -> protoVar.index().toInit());
target.add(_i -> constrVar.index().toInit());
compileStatic(staticTarget);
compilePrototype(protoTarget);
subtarget.endScope();
}
}
public ClassNode(Location loc, Location end, String name, ClassBody body) {
super(loc, end, body.constructorParameters, body.constructorBody);
this.name = name;
this.body = body;
}
public static ParseRes<Member> parseMember(Source src, int i) {
return ParseRes.first(src, i,
PropertyMemberNode::parse,
FieldMemberNode::parseClass,
MethodMemberNode::parse
);
}
public static ParseRes<ClassBody> parseBody(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
ParseRes<Node> superExpr = ParseRes.failed();
if (Parsing.isIdentifier(src, i + n, "extends")) {
n += 7;
superExpr = JavaScript.parseExpression(src, i + n, 14);
if (!superExpr.isSuccess()) return superExpr.chainError(src.loc(i + n), "Expected an expression after 'extends'");
n += superExpr.n;
n += Parsing.skipEmpty(src, i + n);
}
if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected a class body");
n++;
n += Parsing.skipEmpty(src, i + n);
var fields = new LinkedList<FieldMemberNode>();
var members = new LinkedList<Member>();
var statics = new LinkedList<Member>();
var params = new Parameters(new ArrayList<>());
var body = new CompoundNode(loc, false);
var hasConstr = false;
if (src.is(i + n, "}")) {
n++;
return ParseRes.res(new ClassBody(statics, fields, members, params, body, superExpr.result, false), n);
}
while (true) {
ParseRes<Member> prop = parseMember(src, i + n);
if (prop.isSuccess()) {
n += prop.n;
if (prop.result instanceof FieldMemberNode field) fields.add(field);
else if (prop.result instanceof MethodMemberNode method && method.name().equals("constructor")) {
if (hasConstr) return ParseRes.error(loc, "A class may only have one constructor");
params = method.params;
body = method.body;
hasConstr = true;
}
else members.add(prop.result);
}
else if (Parsing.isIdentifier(src, i + n, "static")) {
n += 6;
var staticProp = parseMember(src, i + n);
if (!staticProp.isSuccess()) {
if (prop.isError()) return prop.chainError();
else return staticProp.chainError(src.loc(i + n), "Expected a member after 'static' keyword");
}
n += staticProp.n;
statics.add(staticProp.result);
}
else {
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) n += end.n;
else return ParseRes.error(src.loc(i + n), "Expected a member, end of statement or a closing colon");
}
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
// else return ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
}
return ParseRes.res(new ClassBody(statics, fields, members, params, body, superExpr.result, hasConstr), n);
}
// public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
// super(loc, end, params, body);
// this.name = name;
// }
}

View File

@@ -0,0 +1,40 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
public class ClassStatementNode extends ClassNode {
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
super.compile(target, pollute, name, bp);
var i = target.scope.define(DeclarationType.LET, name(), loc());
target.add(_i -> i.index().toInit());
if (pollute) target.add(Instruction.pushUndefined());
}
public ClassStatementNode(Location loc, Location end, String name, ClassBody body) {
super(loc, end, name, body);
}
public static ParseRes<ClassStatementNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
n += 5;
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a class name");
n += name.n;
var body = parseBody(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body");
n += body.n;
return ParseRes.res(new ClassStatementNode(loc, src.loc(i + n), name.result, body.result), n);
}
}

View File

@@ -0,0 +1,179 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.Scope;
public final class CompileResult {
public static final class ChildData {
public final int id;
public final CompileResult result;
public ChildData(int id, CompileResult result) {
this.result = result;
this.id = id;
}
}
public final List<IntFunction<Instruction>> instructions;
public final List<CompileResult> children;
public final FunctionMapBuilder map;
public final Environment env;
public int length;
public Runnable buildTask = () -> {
throw new IllegalStateException("Compile result is not ready to be built");
};
public final Scope scope;
public int temp() {
instructions.add(null);
return instructions.size() - 1;
}
public CompileResult add(Instruction instr) {
instructions.add(i -> instr);
return this;
}
public CompileResult add(IntFunction<Instruction> instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, _i -> instr);
return this;
}
public CompileResult set(int i, IntFunction<Instruction>instr) {
instructions.set(i, instr);
return this;
}
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 void beginScope() {
// for (var cap : scope.capturables()) {
// add(_i -> Instruction.capInit(cap.index().index));
// }
}
public void reallocScope() {
for (var cap : scope.capturables()) {
add(_i -> cap.index().toGet());
add(_i -> Instruction.capFree(cap.index().index));
add(_i -> cap.index().toInit());
}
scope.end();
}
public void endScope() {
for (var cap : scope.capturables()) {
add(_i -> Instruction.capFree(cap.index().index));
}
for (var var : scope.locals()) {
add(_i -> Instruction.varFree(var.index().index));
}
scope.end();
}
public int addChild(CompileResult res) {
this.children.add(res);
return this.children.size() - 1;
}
public Instruction[] instructions() {
var res = new Instruction[instructions.size()];
var i = 0;
for (var suppl : instructions) {
res[i] = suppl.apply(i);
i++;
}
return res;
}
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();
var instrRes = new Instruction[instructions.size()];
var i = 0;
for (var suppl : instructions) {
instrRes[i] = suppl.apply(i);
// System.out.println(instrRes[i]);
i++;
}
return new FunctionBody(
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren
);
}
public CompileResult subtarget() {
return new CompileResult(env, new Scope(scope), this);
}
public CompileResult setEnvironment(Environment env) {
return new CompileResult(env, scope, this);
}
/**
* Returns a compile result with a child of the environment that relates to the given key.
* In essence, this is used to create a compile result which is back at the root environment of the compilation
*/
public CompileResult rootEnvironment(Key<Environment> env) {
return new CompileResult(this.env.get(env).child(), scope, this);
}
public CompileResult subEnvironment() {
return new CompileResult(env.child(), scope, this);
}
public CompileResult(Environment env, Scope scope, int length, Consumer<CompileResult> task) {
this.scope = scope;
this.instructions = new ArrayList<>();
this.children = new LinkedList<>();
this.map = FunctionMap.builder();
this.env = env;
this.length = length;
this.buildTask = () -> task.accept(this);
}
private CompileResult(Environment env, Scope scope, CompileResult parent) {
this.scope = scope;
this.instructions = parent.instructions;
this.children = parent.children;
this.map = parent.map;
this.env = env;
}
}

View File

@@ -0,0 +1,129 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
public class CompoundNode extends Node {
public final Node[] statements;
public boolean hasScope;
public Location end;
@Override public void resolve(CompileResult target) {
for (var stm : statements) stm.resolve(target);
}
public void compile(CompileResult target, boolean pollute, boolean singleEntry, BreakpointType type) {
List<Node> statements = new ArrayList<Node>();
var subtarget = hasScope ? target.subtarget() : target;
if (hasScope) subtarget.beginScope();
for (var stm : this.statements) {
if (stm instanceof FunctionStatementNode func) {
func.compile(subtarget, false);
}
else statements.add(stm);
}
var polluted = false;
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER);
else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER);
}
if (hasScope) subtarget.endScope();
if (!polluted && pollute) {
target.add(Instruction.pushUndefined());
}
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
compile(target, pollute, true, type);
}
public CompoundNode setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundNode(Location loc, boolean hasScope, Node ...statements) {
super(loc);
this.hasScope = hasScope;
this.statements = statements;
}
public static void compileMultiEntry(Node node, CompileResult target, boolean pollute, BreakpointType type) {
if (node instanceof CompoundNode comp) {
comp.compile(target, pollute, false, type);
}
else {
node.compile(target, pollute, type);
}
}
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
if (precedence > 1) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, ",")) return ParseRes.failed();
n++;
var curr = JavaScript.parseExpression(src, i + n, 2);
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
n += curr.n;
if (prev instanceof CompoundNode comp) {
var children = new ArrayList<Node>();
children.addAll(Arrays.asList(comp.statements));
children.add(curr.result);
return ParseRes.res(new CompoundNode(loc, comp.hasScope, children.toArray(new Node[0])), n);
}
else return ParseRes.res(new CompoundNode(loc, false, prev, curr.result), n);
}
public static ParseRes<CompoundNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "{")) return ParseRes.failed();
n++;
var statements = new ArrayList<Node>();
while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
var res = JavaScript.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
n += res.n;
statements.add(res.result);
}
return ParseRes.res(new CompoundNode(loc, true, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.jscript.compilation;
import java.util.function.IntSupplier;
public final class DeferredIntSupplier implements IntSupplier {
private int value;
private boolean set;
public void set(int val) {
if (set) throw new RuntimeException("A deferred int supplier may be set only once");
value = val;
set = true;
}
@Override public int getAsInt() {
if (!set) throw new RuntimeException("Deferred int supplier accessed too early");
return value;
}
}

View File

@@ -0,0 +1,78 @@
package me.topchetoeu.jscript.compilation;
import java.util.Arrays;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.control.ReturnNode;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
public class FunctionArrowNode extends FunctionNode {
@Override public String name() { return null; }
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, true, false, true, false, null, captures(id, target)));
}
@Override protected Environment rootEnv(Environment env) {
return env.getWith(ClassNode.CLASS_ROOT, () -> super.rootEnv(env));
}
public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) {
super(loc, end, params, expToBody(body));
}
private static final CompoundNode expToBody(Node node) {
if (node instanceof CompoundNode res) return res;
else return new CompoundNode(node.loc(), false, new ReturnNode(node.loc(), node));
}
public static ParseRes<FunctionArrowNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
Parameters params;
if (src.is(i + n, "(")) {
var paramsRes = Parameters.parseParameters(src, i + n);
if (!paramsRes.isSuccess()) return paramsRes.chainError();
n += paramsRes.n;
n += Parsing.skipEmpty(src, i + n);
params = paramsRes.result;
}
else {
var singleParam = Pattern.parse(src, i + n, true);
if (!singleParam.isSuccess()) return ParseRes.failed();
n += singleParam.n;
n += Parsing.skipEmpty(src, i + n);
params = new Parameters(Arrays.asList(singleParam.result));
}
if (!src.is(i + n, "=>")) return ParseRes.failed();
n += 2;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "{")) {
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
n += body.n;
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
}
else {
var body = JavaScript.parseExpression(src, i + n, 2);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
n += body.n;
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
}
}
}

View File

@@ -0,0 +1,132 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.scope.Variable;
public abstract class FunctionNode extends Node {
public final CompoundNode body;
public final Parameters params;
public final Location end;
public abstract String name();
protected final int[] captures(int id, CompileResult target) {
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
}
protected void compilePreBody(CompileResult target) { }
protected Environment rootEnv(Environment env) {
return env.get(JavaScript.COMPILE_ROOT);
}
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) {
var name = this.name() != null ? this.name() : _name;
return new CompileResult(env, scope, params.params.size(), target -> {
compilePreBody(target);
if (params.params.size() > 0) {
target.add(Instruction.loadArgs(true));
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0));
var i = 0;
for (var param : params.params) {
target.add(Instruction.loadMember(i++));
param.destruct(target, DeclarationType.VAR, true);
}
}
if (params.rest != null) {
target.add(Instruction.loadRestArgs(params.params.size()));
params.rest.destruct(target, DeclarationType.VAR, true);
}
if (selfName != null && !scope.has(name, false)) {
var i = scope.defineSpecial(new Variable(selfName, true), end);
target.add(Instruction.loadCallee());
target.add(_i -> i.index().toInit());
}
body.resolve(target);
body.compile(target, lastReturn, BreakpointType.NONE);
scope.end();
for (var child : target.children) child.buildTask.run();
scope.finish();
});
}
public final CompileResult compileBody(CompileResult parent, String name, String selfName) {
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, name, selfName);
}
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
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 FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) {
super(loc);
this.end = end;
this.params = params;
this.body = body;
this.body.hasScope = false;
}
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name);
else stm.compile(target, pollute);
}
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp);
else stm.compile(target, pollute, bp);
}
public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed();
n += 8;
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
n += name.n;
n += Parsing.skipEmpty(src, i + n);
var params = Parameters.parseParameters(src, i + n);
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
n += params.n;
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
n += body.n;
if (statement) return ParseRes.res(new FunctionStatementNode(
loc, src.loc(i + n - 1),
params.result, body.result, name.result
), n);
else return ParseRes.res(new FunctionValueNode(
loc, src.loc(i + n - 1),
params.result, body.result, name.result
), n);
}
}

View File

@@ -0,0 +1,29 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class FunctionStatementNode extends FunctionNode {
public final String name;
@Override public String name() { return name; }
@Override public void resolve(CompileResult target) {
target.scope.define(new Variable(name, false), end);
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, true, true, false, false, name, captures(id, target)));
target.add(VariableNode.toInit(target, end, this.name));
if (pollute) target.add(Instruction.pushUndefined());
}
public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
}

View File

@@ -0,0 +1,21 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
public class FunctionValueNode extends FunctionNode {
public final String name;
@Override public String name() { return name; }
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, true, true, false, false, name, captures(id, target)));
}
public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
}

View File

@@ -0,0 +1,302 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.control.BreakNode;
import me.topchetoeu.jscript.compilation.control.ContinueNode;
import me.topchetoeu.jscript.compilation.control.DebugNode;
import me.topchetoeu.jscript.compilation.control.DeleteNode;
import me.topchetoeu.jscript.compilation.control.DoWhileNode;
import me.topchetoeu.jscript.compilation.control.ForInNode;
import me.topchetoeu.jscript.compilation.control.ForNode;
import me.topchetoeu.jscript.compilation.control.ForOfNode;
import me.topchetoeu.jscript.compilation.control.IfNode;
import me.topchetoeu.jscript.compilation.control.ReturnNode;
import me.topchetoeu.jscript.compilation.control.SwitchNode;
import me.topchetoeu.jscript.compilation.control.ThrowNode;
import me.topchetoeu.jscript.compilation.control.TryNode;
import me.topchetoeu.jscript.compilation.control.WhileNode;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
import me.topchetoeu.jscript.compilation.values.ArrayNode;
import me.topchetoeu.jscript.compilation.values.ClassValueNode;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.RegexNode;
import me.topchetoeu.jscript.compilation.values.SuperNode;
import me.topchetoeu.jscript.compilation.values.ThisNode;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
import me.topchetoeu.jscript.compilation.values.constants.NullNode;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
import me.topchetoeu.jscript.compilation.values.operations.CallNode;
import me.topchetoeu.jscript.compilation.values.operations.ChangeNode;
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
import me.topchetoeu.jscript.compilation.values.operations.OperationNode;
import me.topchetoeu.jscript.compilation.values.operations.PostfixNode;
import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
public final class JavaScript {
public static enum DeclarationType {
VAR(false, false),
CONST(true, true),
LET(true, false);
public final boolean strict, readonly;
private DeclarationType(boolean strict, boolean readonly) {
this.strict = strict;
this.readonly = readonly;
}
}
public static final Key<Environment> COMPILE_ROOT = Key.of();
static final Set<String> reserved = new HashSet<>(Arrays.asList(
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
"finally", "for", "do", "while", "switch", "case", "default", "new",
"function", "var", "return", "throw", "typeof", "delete", "break",
"continue", "debugger", "implements", "interface", "package", "private",
"protected", "public", "static", "arguments", "class", "extends"
));
public static ParseRes<? extends Node> parseParens(Source src, int i) {
int n = 0;
var openParen = Parsing.parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError();
n += openParen.n;
var res = JavaScript.parseExpression(src, i + n, 0);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
n += res.n;
var closeParen = Parsing.parseOperator(src, i + n, ")");
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
n += closeParen.n;
return ParseRes.res(res.result, n);
}
public static ParseRes<? extends Node> parseSimple(Source src, int i, boolean statement) {
return ParseRes.first(src, i,
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
(s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j),
JavaScript::parseLiteral,
StringNode::parse,
RegexNode::parse,
NumberNode::parse,
ChangeNode::parsePrefixDecrease,
ChangeNode::parsePrefixIncrease,
OperationNode::parsePrefix,
ArrayNode::parse,
(s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j),
JavaScript::parseParens,
CallNode::parseNew,
TypeofNode::parse,
DiscardNode::parse,
DeleteNode::parse,
VariableNode::parse
);
}
public static ParseRes<? extends Node> parseLiteral(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var id = Parsing.parseIdentifier(src, i);
if (!id.isSuccess()) return id.chainError();
n += id.n;
if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n);
if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n);
if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n);
if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n);
if (id.result.equals("super")) return ParseRes.res(new SuperNode(loc), n);
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
return ParseRes.failed();
}
public static ParseRes<Node> parseExpression(Source src, int i, int precedence, boolean statement) {
var n = Parsing.skipEmpty(src, i);
Node prev = null;
while (true) {
if (prev == null) {
var res = parseSimple(src, i + n, statement);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
}
else if (res.isError()) return res.chainError();
else break;
}
else {
var _prev = prev;
ParseRes<Node> res = ParseRes.first(src, i + n,
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
(s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
(s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
(s, j) -> IndexNode.parseIndex(s, j, _prev, precedence),
(s, j) -> CallNode.parseCall(s, j, _prev, precedence),
(s, j) -> CompoundNode.parseComma(s, j, _prev, precedence)
);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
continue;
}
else if (res.isError()) return res.chainError();
break;
}
}
if (prev == null) return ParseRes.failed();
else return ParseRes.res(prev, n);
}
public static ParseRes<Node> parseExpression(Source src, int i, int precedence) {
return parseExpression(src, i, precedence, false);
}
public static ParseRes<Node> parseExpressionStatement(Source src, int i) {
var res = parseExpression(src, i, 0, true);
if (!res.isSuccess()) return res.chainError();
var end = JavaScript.parseStatementEnd(src, i + res.n);
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
return res.addN(end.n);
}
public static ParseRes<? extends Node> parseStatement(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1);
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
ParseRes<? extends Node> res = ParseRes.first(src, i + n,
ClassStatementNode::parse,
VariableDeclareNode::parse,
ReturnNode::parse,
ThrowNode::parse,
ContinueNode::parse,
BreakNode::parse,
DebugNode::parse,
IfNode::parse,
WhileNode::parse,
SwitchNode::parse,
ForNode::parse,
ForInNode::parse,
ForOfNode::parse,
DoWhileNode::parse,
TryNode::parse,
CompoundNode::parse,
(s, j) -> FunctionNode.parseFunction(s, j, true),
JavaScript::parseExpressionStatement
);
return res.addN(n);
}
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (i + n >= src.size()) return ParseRes.res(true, n);
for (var j = i; j < i + n; j++) {
if (src.is(j, '\n')) return ParseRes.res(true, n);
}
if (src.is(i + n, ';')) return ParseRes.res(true, n + 1);
if (src.is(i + n, '}')) return ParseRes.res(true, n);
return ParseRes.failed();
}
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
var res = Parsing.parseIdentifier(src, i);
if (!res.isSuccess()) return res.chainError();
if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n);
if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n);
if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n);
return ParseRes.failed();
}
public static Node[] parse(Environment env, Filename filename, String raw) {
var src = new Source(env, filename, raw);
var list = new ArrayList<Node>();
int i = 0;
while (true) {
if (i >= src.size()) break;
var res = parseStatement(src, i);
if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
i += res.n;
i += Parsing.skipEmpty(src, i);
list.add(res.result);
}
return list.toArray(new Node[0]);
}
public static boolean checkVarName(String name) {
return !JavaScript.reserved.contains(name);
}
public static CompileResult compile(Environment env, Node ...statements) {
env = env.child();
env.add(COMPILE_ROOT, env);
var func = new FunctionValueNode(null, null, new Parameters(Arrays.asList()), new CompoundNode(null, true, statements), null);
var res = func.compileBody(env, new FunctionScope(true), true, null, null);
res.buildTask.run();
return res;
}
public static CompileResult compile(Environment env, Filename filename, String raw) {
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
}
public static CompileResult compile(Filename filename, String raw) {
var env = new Environment();
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
}
public static ParseRes<String> parseLabel(Source src, int i) {
int n = Parsing.skipEmpty(src, i);
var nameRes = Parsing.parseIdentifier(src, i + n);
if (!nameRes.isSuccess()) return nameRes.chainError();
n += nameRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.failed();
n++;
return ParseRes.res(nameRes.result, n);
}
}

View File

@@ -0,0 +1,85 @@
package me.topchetoeu.jscript.compilation;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.parsing.Location;
public class LabelContext {
public static final Key<LabelContext> BREAK_CTX = Key.of();
public static final Key<LabelContext> CONTINUE_CTX = Key.of();
private final LinkedList<IntSupplier> list = new LinkedList<>();
private final HashMap<String, IntSupplier> map = new HashMap<>();
public IntSupplier get() {
return list.peekLast();
}
public IntSupplier get(String name) {
return map.get(name);
}
public IntFunction<Instruction> getJump() {
var res = get();
if (res == null) return null;
else return i -> Instruction.jmp(res.getAsInt() - i);
}
public IntFunction<Instruction> getJump(String name) {
var res = get(name);
if (res == null) return null;
else return i -> Instruction.jmp(res.getAsInt() - i);
}
public void push(IntSupplier jumpTarget) {
list.add(jumpTarget);
}
public void push(Location loc, String name, IntSupplier jumpTarget) {
if (name == null) return;
if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name));
map.put(name, jumpTarget);
}
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) {
push(jumpTarget);
push(loc, name, jumpTarget);
}
public void pop() {
list.removeLast();
}
public void pop(String name) {
if (name == null) return;
map.remove(name);
}
public void popLoop(String name) {
pop();
pop(name);
}
public static LabelContext getBreak(Environment env) {
return env.initFrom(BREAK_CTX, () -> new LabelContext());
}
public static LabelContext getCont(Environment env) {
return env.initFrom(CONTINUE_CTX, () -> new LabelContext());
}
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) {
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget);
}
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) {
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
LabelContext.getCont(env).pushLoop(loc, name, contTarget);
}
public static void popLoop(Environment env, String name) {
LabelContext.getBreak(env).popLoop(name);
LabelContext.getCont(env).popLoop(name);
}
}

View File

@@ -0,0 +1,26 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
public abstract class Node {
private Location loc;
public void resolve(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) { this.loc = loc; }
protected Node(Location loc) {
this.loc = loc;
}
}

View File

@@ -0,0 +1,93 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Function;
public final class NodeChildren implements Iterable<Node> {
public static final class Slot {
private Node node;
private final Function<Node, Node> replacer;
public final void replace(Node node) {
this.node = this.replacer.apply(node);
}
public Slot(Node nodes, Function<Node, Node> replacer) {
this.node = nodes;
this.replacer = replacer;
}
}
private final Slot[] slots;
private NodeChildren(Slot[] slots) {
this.slots = slots;
}
@Override public Iterator<Node> iterator() {
return new Iterator<Node>() {
private int i = 0;
private Slot[] arr = slots;
@Override public boolean hasNext() {
if (arr == null) return false;
else if (i >= arr.length) {
arr = null;
return false;
}
else return true;
}
@Override public Node next() {
if (!hasNext()) return null;
return arr[i++].node;
}
};
}
public Iterable<Slot> slots() {
return () -> new Iterator<Slot>() {
private int i = 0;
private Slot[] arr = slots;
@Override public boolean hasNext() {
if (arr == null) return false;
else if (i >= arr.length) {
arr = null;
return false;
}
else return true;
}
@Override public Slot next() {
if (!hasNext()) return null;
return arr[i++];
}
};
}
public static final class Builder {
private final ArrayList<Slot> slots = new ArrayList<>();
public final Builder add(Slot ...children) {
for (var child : children) {
this.slots.add(child);
}
return this;
}
public final Builder add(Iterable<Slot> children) {
for (var child : children) {
this.slots.add(child);
}
return this;
}
public final Builder add(Node child, Function<Node, Node> replacer) {
slots.add(new Slot(child, replacer));
return this;
}
public final NodeChildren build() {
return new NodeChildren(slots.toArray(new Slot[0]));
}
}
}

View File

@@ -0,0 +1,15 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.parsing.Location;
public final class Parameter {
public final Location loc;
public final String name;
public final Node node;
public Parameter(Location loc, String name, Node node) {
this.name = name;
this.node = node;
this.loc = loc;
}
}

View File

@@ -0,0 +1,84 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
public final class Parameters {
public final int length;
public final List<Pattern> params;
public final Pattern rest;
public Parameters(List<Pattern> params, Pattern rest) {
var len = params.size();
for (var i = params.size() - 1; i >= 0; i--) {
if (!(params.get(i) instanceof AssignNode)) break;
len--;
}
this.params = params;
this.length = len;
this.rest = rest;
}
public Parameters(List<Pattern> params) {
this(params, null);
}
public static ParseRes<Parameters> parseParameters(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var openParen = Parsing.parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
n += openParen.n;
var params = new ArrayList<Pattern>();
var closeParen = Parsing.parseOperator(src, i + n, ")");
n += closeParen.n;
if (!closeParen.isSuccess()) {
while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "...")) {
n += 3;
var rest = Pattern.parse(src, i + n, true);
if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
n += rest.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
n++;
return ParseRes.res(new Parameters(params, rest.result), n);
}
var param = Pattern.parse(src, i + n, true);
if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
n += param.n;
n += Parsing.skipEmpty(src, i + n);
params.add(param.result);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
}
}
return ParseRes.res(new Parameters(params), n);
}
}

View File

@@ -0,0 +1,115 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
public class VariableDeclareNode extends Node {
public static class Pair {
public final Pattern destructor;
public final Node value;
public final Location location;
public Pair(Pattern destr, Node value, Location location) {
this.destructor = destr;
this.value = value;
this.location = location;
}
}
public final List<Pair> values;
public final DeclarationType declType;
@Override public void resolve(CompileResult target) {
if (!declType.strict) {
for (var entry : values) {
entry.destructor.destructDeclResolve(target);
}
}
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
if (entry.value == null) {
if (declType == DeclarationType.VAR) entry.destructor.declare(target, null, false);
else entry.destructor.declare(target, declType, false);
}
else {
entry.value.compile(target, true);
if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true);
else entry.destructor.destruct(target, declType, true);
}
}
if (pollute) target.add(Instruction.pushUndefined());
}
public VariableDeclareNode(Location loc, DeclarationType declType, List<Pair> values) {
super(loc);
this.values = values;
this.declType = declType;
}
public static ParseRes<VariableDeclareNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var declType = JavaScript.parseDeclarationType(src, i + n);
if (!declType.isSuccess()) return declType.chainError();
n += declType.n;
var res = new ArrayList<Pair>();
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n);
}
while (true) {
var nameLoc = src.loc(i + n);
var name = Pattern.parse(src, i + n, false);
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor");
n += name.n;
Node val = null;
var endN = n;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "=")) {
n++;
var valRes = JavaScript.parseExpression(src, i + n, 2);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='");
n += valRes.n;
endN = n;
n += Parsing.skipEmpty(src, i + n);
val = valRes.result;
}
res.add(new Pair(name.result, val, nameLoc));
if (src.is(i + n, ",")) {
n++;
continue;
}
end = JavaScript.parseStatementEnd(src, i + endN);
if (end.isSuccess()) {
n += end.n + endN - n;
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n);
}
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
}
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
public class BreakNode extends Node {
public final String label;
@Override public void compile(CompileResult target, boolean pollute) {
var res = LabelContext.getBreak(target.env).getJump();
if (res == null) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal break statement");
}
target.add(res);
if (pollute) target.add(Instruction.pushUndefined());
}
public BreakNode(Location loc, String label) {
super(loc);
this.label = label;
}
public static ParseRes<BreakNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed();
n += 5;
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new BreakNode(loc, null), n);
}
var label = Parsing.parseIdentifier(src, i + n);
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
n += label.n;
end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new BreakNode(loc, label.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
public class ContinueNode extends Node {
public final String label;
@Override public void compile(CompileResult target, boolean pollute) {
var res = LabelContext.getCont(target.env).getJump();
if (res == null) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal continue statement");
}
target.add(res);
if (pollute) target.add(Instruction.pushUndefined());
}
public ContinueNode(Location loc, String label) {
super(loc);
this.label = label;
}
public static ParseRes<ContinueNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed();
n += 8;
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ContinueNode(loc, null), n);
}
var label = Parsing.parseIdentifier(src, i + n);
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
n += label.n;
end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ContinueNode(loc, label.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
}

View File

@@ -0,0 +1,37 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class DebugNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.debug());
if (pollute) target.add(Instruction.pushUndefined());
}
public DebugNode(Location loc) {
super(loc);
}
public static ParseRes<DebugNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed();
n += 8;
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new DebugNode(loc), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
}

View File

@@ -0,0 +1,53 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
public class DeleteNode extends Node {
public final Node key;
public final Node 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 static ParseRes<? extends Node> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed();
n += 6;
var valRes = JavaScript.parseExpression(src, i + n, 15);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'");
n += valRes.n;
if (valRes.result instanceof IndexNode) {
var index = (IndexNode)valRes.result;
return ParseRes.res(new DeleteNode(loc, index.index, index.object), n);
}
else if (valRes.result instanceof VariableNode) {
return ParseRes.error(src.loc(i + n), "A variable may not be deleted");
}
else return ParseRes.res(new BoolNode(loc, true), n);
}
public DeleteNode(Location loc, Node key, Node value) {
super(loc);
this.key = key;
this.value = value;
}
}

View File

@@ -0,0 +1,84 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
public class DoWhileNode extends Node {
public final Node condition, body;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
int start = target.size();
var end = new DeferredIntSupplier();
var mid = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start);
body.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
mid.set(target.size());
condition.compile(target, true, BreakpointType.STEP_OVER);
int endI = target.size();
end.set(endI + 1);
target.add(Instruction.jmpIf(start - endI));
}
public DoWhileNode(Location loc, String label, Node condition, Node body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
public static ParseRes<DoWhileNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var labelRes = JavaScript.parseLabel(src, i + n);
n += labelRes.n;
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
n += 2;
var bodyRes = JavaScript.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
n += bodyRes.n;
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
n += 5;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
n++;
var condRes = JavaScript.parseExpression(src, i + n, 0);
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition.");
n++;
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
}

View File

@@ -0,0 +1,101 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.Binding;
public class ForInNode extends Node {
public final Binding binding;
public final Node object, body;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
binding.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
binding.declareLateInit(target);
object.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(false, true));
int start = target.size();
target.add(Instruction.dup());
int mid = target.temp();
target.add(Instruction.loadMember("value")).setLocation(binding.loc());
binding.assign(target, false);
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
int endI = target.size();
target.add(Instruction.jmp(start - endI));
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForInNode(Location loc, String label, Binding binding, Node object, Node body) {
super(loc);
this.label = label;
this.binding = binding;
this.object = object;
this.body = body;
}
public static ParseRes<ForInNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n);
n += label.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
n += 3;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
n++;
n += Parsing.skipEmpty(src, i + n);
var binding = Binding.parse(src, i + n);
if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-in loop");
n += binding.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration");
n += 2;
var obj = JavaScript.parseExpression(src, i + n, 0);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
n += obj.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
n++;
var bodyRes = JavaScript.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
n += bodyRes.n;
return ParseRes.res(new ForInNode(loc, label.result, binding.result, obj.result, bodyRes.result), n);
}
}

View File

@@ -0,0 +1,131 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.VariableDeclareNode;
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
public class ForNode extends Node {
public final Node declaration, assignment, condition, body;
public final String label;
@Override public void resolve(CompileResult target) {
declaration.resolve(target);
body.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
var subtarget = target.subtarget();
subtarget.scope.singleEntry = false;
subtarget.beginScope();
declaration.compile(subtarget, false, BreakpointType.STEP_OVER);
int start = subtarget.size();
CompoundNode.compileMultiEntry(condition, subtarget, true, BreakpointType.STEP_OVER);
int mid = subtarget.temp();
var end = new DeferredIntSupplier();
LabelContext.pushLoop(subtarget.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(subtarget.env, label);
subtarget.reallocScope();
CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER);
int endI = subtarget.size();
end.set(endI);
subtarget.add(Instruction.jmp(start - endI));
subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1));
if (pollute) subtarget.add(Instruction.pushUndefined());
subtarget.endScope();
}
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {
super(loc);
this.label = label;
this.declaration = declaration;
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
private static ParseRes<Node> parseSemicolon(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, ";")) return ParseRes.failed();
else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1);
}
private static ParseRes<Node> parseCondition(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var res = JavaScript.parseExpression(src, i + n, 0);
if (!res.isSuccess()) return res.chainError();
n += res.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon");
else return ParseRes.res(res.result, n + 1);
}
private static ParseRes<? extends Node> parseUpdater(Source src, int i) {
return JavaScript.parseExpression(src, i, 0);
}
public static ParseRes<ForNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var labelRes = JavaScript.parseLabel(src, i + n);
n += labelRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
n += 3;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'");
n++;
ParseRes<Node> decl = ParseRes.first(src, i + n,
ForNode::parseSemicolon,
VariableDeclareNode::parse,
ForNode::parseCondition
);
if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression");
n += decl.n;
ParseRes<Node> cond = ParseRes.first(src, i + n,
ForNode::parseSemicolon,
ForNode::parseCondition
);
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition");
n += cond.n;
var update = parseUpdater(src, i + n);
if (update.isError()) return update.chainError();
n += update.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater");
n++;
var body = JavaScript.parseStatement(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
n += body.n;
return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n);
}
}

View File

@@ -0,0 +1,109 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.Binding;
public class ForOfNode extends Node {
public final Binding binding;
public final Node iterable, body;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
binding.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
binding.declareLateInit(target);
iterable.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.dup());
target.add(Instruction.loadIntrinsics("it_key"));
target.add(Instruction.loadMember()).setLocation(iterable.loc());
target.add(Instruction.call(0, true)).setLocation(iterable.loc());
int start = target.size();
target.add(Instruction.dup(2, 0));
target.add(Instruction.loadMember("next")).setLocation(iterable.loc());
target.add(Instruction.call(0, true)).setLocation(iterable.loc());
target.add(Instruction.dup());
target.add(Instruction.loadMember("done")).setLocation(iterable.loc());
int mid = target.temp();
target.add(Instruction.loadMember("value")).setLocation(binding.loc);
binding.assign(target, false);
var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
int endI = target.size();
end.set(endI);
target.add(Instruction.jmp(start - endI));
target.add(Instruction.discard());
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForOfNode(Location loc, String label, Binding binding, Node object, Node body) {
super(loc);
this.label = label;
this.binding = binding;
this.iterable = object;
this.body = body;
}
public static ParseRes<ForOfNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n);
n += label.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
n += 3;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
n++;
n += Parsing.skipEmpty(src, i + n);
var binding = Binding.parse(src, i + n);
if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-of loop");
n += binding.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration");
n += 2;
var obj = JavaScript.parseExpression(src, i + n, 0);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
n += obj.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
n++;
var bodyRes = JavaScript.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body");
n += bodyRes.n;
return ParseRes.res(new ForOfNode(loc, label.result, binding.result, obj.result, bodyRes.result), n);
}
}

View File

@@ -0,0 +1,131 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
public class IfNode extends Node {
public final Node condition, body, elseBody;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
if (elseBody != null) elseBody.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
condition.compile(target, true, breakpoint);
if (elseBody == null) {
int start = target.temp();
var end = new DeferredIntSupplier();
LabelContext.getBreak(target.env).push(loc(), label, end);
body.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.getBreak(target.env).pop(label);
int endI = target.size();
end.set(endI);
target.set(start, Instruction.jmpIfNot(endI - start));
}
else {
int start = target.temp();
var end = new DeferredIntSupplier();
LabelContext.getBreak(target.env).push(loc(), label, end);
body.compile(target, false, BreakpointType.STEP_OVER);
int mid = target.temp();
elseBody.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.getBreak(target.env).pop(label);
int endI = target.size();
end.set(endI);
target.set(start, Instruction.jmpIfNot(mid - start + 1));
target.set(mid, Instruction.jmp(endI - mid));
}
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) {
super(loc);
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
this.label = label;
}
public static ParseRes<IfNode> parseTernary(Source src, int i, Node prev, int precedence) {
if (precedence > 2) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "?")) return ParseRes.failed();
var loc = src.loc(i + n);
n++;
var a = JavaScript.parseExpression(src, i + n, 2);
if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator.");
n += a.n;
n += Parsing.skipEmpty(src, i);
if (!src.is(i + n, ":")) return ParseRes.failed();
n++;
var b = JavaScript.parseExpression(src, i + n, 2);
if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator.");
n += b.n;
return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n);
}
public static ParseRes<IfNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n);
n += label.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed();
n += 2;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'.");
n++;
var condRes = JavaScript.parseExpression(src, i + n, 0);
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition.");
n++;
var res = JavaScript.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body.");
n += res.n;
var elseKw = Parsing.parseIdentifier(src, i + n, "else");
if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n);
n += elseKw.n;
var elseRes = JavaScript.parseStatement(src, i + n);
if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body.");
n += elseRes.n;
return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
}
}

View File

@@ -0,0 +1,50 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class ReturnNode extends Node {
public final Node 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 ReturnNode(Location loc, Node value) {
super(loc);
this.value = value;
}
public static ParseRes<ReturnNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed();
n += 6;
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ReturnNode(loc, null), n);
}
var val = JavaScript.parseExpression(src, i + n, 0);
if (val.isError()) return val.chainError();
n += val.n;
end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ReturnNode(loc, val.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement or a return value");
}
}

View File

@@ -0,0 +1,193 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.ArrayList;
import java.util.HashMap;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
public class SwitchNode extends Node {
public static class SwitchCase {
public final Node value;
public final int statementI;
public SwitchCase(Node value, int statementI) {
this.value = value;
this.statementI = statementI;
}
}
public final Node value;
public final SwitchCase[] cases;
public final Node[] body;
public final int defaultI;
public final String label;
@Override public void resolve(CompileResult target) {
for (var stm : body) stm.resolve(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);
var subtarget = target.subtarget();
subtarget.beginScope();
// TODO: create a jump map
for (var ccase : cases) {
subtarget.add(Instruction.dup());
ccase.value.compile(subtarget, true);
subtarget.add(Instruction.operation(Operation.EQUALS));
caseToStatement.put(subtarget.temp(), ccase.statementI);
}
int start = subtarget.temp();
var end = new DeferredIntSupplier();
LabelContext.getBreak(target.env).push(loc(), label, end);
for (var stm : body) {
statementToIndex.put(statementToIndex.size(), subtarget.size());
stm.compile(subtarget, false, BreakpointType.STEP_OVER);
}
LabelContext.getBreak(target.env).pop(label);
subtarget.endScope();
int endI = subtarget.size();
end.set(endI);
subtarget.add(Instruction.discard());
if (pollute) subtarget.add(Instruction.pushUndefined());
if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start));
else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
for (var el : caseToStatement.entrySet()) {
var i = statementToIndex.get(el.getValue());
if (i == null) i = endI;
subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
}
}
public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) {
super(loc);
this.label = label;
this.value = value;
this.defaultI = defaultI;
this.cases = cases;
this.body = body;
}
private static ParseRes<Node> parseSwitchCase(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
n += 4;
var val = JavaScript.parseExpression(src, i + n, 0);
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'");
n += val.n;
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value");
n++;
return ParseRes.res(val.result, n);
}
private static ParseRes<Void> parseDefaultCase(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed();
n += 7;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'");
n++;
return ParseRes.res(null, n);
}
public static ParseRes<SwitchNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n);
n += label.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed();
n += 6;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
n++;
var val = JavaScript.parseExpression(src, i + n, 0);
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value");
n += val.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value");
n++;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value");
n++;
n += Parsing.skipEmpty(src, i + n);
var statements = new ArrayList<Node>();
var cases = new ArrayList<SwitchCase>();
var defaultI = -1;
while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
ParseRes<Node> caseRes = ParseRes.first(src, i + n,
SwitchNode::parseDefaultCase,
SwitchNode::parseSwitchCase
);
if (caseRes.isSuccess()) {
n += caseRes.n;
if (caseRes.result == null) defaultI = statements.size();
else cases.add(new SwitchCase(caseRes.result, statements.size()));
continue;
}
if (caseRes.isError()) return caseRes.chainError();
var stm = JavaScript.parseStatement(src, i + n);
if (stm.isSuccess()) {
n += stm.n;
statements.add(stm.result);
continue;
}
else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'");
}
return ParseRes.res(new SwitchNode(
loc, label.result, val.result, defaultI,
cases.toArray(new SwitchCase[0]),
statements.toArray(new Node[0])
), n);
}
}

View File

@@ -0,0 +1,49 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class ThrowNode extends Node {
public final Node value;
@Override public void compile(CompileResult target, boolean pollute) {
value.compile(target, true);
target.add(Instruction.throwInstr()).setLocation(loc());
}
public ThrowNode(Location loc, Node value) {
super(loc);
this.value = value;
}
public static ParseRes<ThrowNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed();
n += 5;
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ThrowNode(loc, null), n);
}
var val = JavaScript.parseExpression(src, i + n, 0);
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value");
n += val.n;
end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ThrowNode(loc, val.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
}

View File

@@ -0,0 +1,142 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.scope.Variable;
public class TryNode extends Node {
public final CompoundNode tryBody;
public final CompoundNode catchBody;
public final CompoundNode finallyBody;
public final String captureName;
public final String label;
@Override public void resolve(CompileResult target) {
tryBody.resolve(target);
if (catchBody != null) catchBody.resolve(target);
if (finallyBody != null) finallyBody.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
int replace = target.temp();
var endSuppl = new DeferredIntSupplier();
int start = replace + 1, catchStart = -1, finallyStart = -1;
LabelContext.getBreak(target.env).push(loc(), label, endSuppl);
tryBody.compile(target, false);
target.add(Instruction.tryEnd());
if (catchBody != null) {
catchStart = target.size() - start;
if (captureName != null) {
var subtarget = target.subtarget();
subtarget.beginScope();
subtarget.scope.singleEntry = true;
var catchVar = subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc());
subtarget.add(Instruction.loadError());
subtarget.add(catchVar.index().toInit());
catchBody.compile(subtarget, false);
subtarget.endScope();
subtarget.scope.end();
}
else catchBody.compile(target, false);
target.add(Instruction.tryEnd());
}
if (finallyBody != null) {
finallyStart = target.size() - start;
finallyBody.compile(target, false);
}
LabelContext.getBreak(target.env).pop(label);
endSuppl.set(target.size());
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
if (pollute) target.add(Instruction.pushUndefined());
}
public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) {
super(loc);
this.tryBody = tryBody;
this.catchBody = catchBody;
this.finallyBody = finallyBody;
this.captureName = captureName;
this.label = label;
}
public static ParseRes<TryNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var labelRes = JavaScript.parseLabel(src, i + n);
n += labelRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed();
n += 3;
var tryBody = CompoundNode.parse(src, i + n);
if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body");
n += tryBody.n;
n += Parsing.skipEmpty(src, i + n);
String capture = null;
CompoundNode catchBody = null, finallyBody = null;
if (Parsing.isIdentifier(src, i + n, "catch")) {
n += 5;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "(")) {
n++;
var nameRes = Parsing.parseIdentifier(src, i + n);
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
capture = nameRes.result;
n += nameRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name");
n++;
}
var bodyRes = CompoundNode.parse(src, i + n);
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body");
n += bodyRes.n;
n += Parsing.skipEmpty(src, i + n);
catchBody = bodyRes.result;
}
if (Parsing.isIdentifier(src, i + n, "finally")) {
n += 7;
var bodyRes = CompoundNode.parse(src, i + n);
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body");
n += bodyRes.n;
n += Parsing.skipEmpty(src, i + n);
finallyBody = bodyRes.result;
}
if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally");
return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n);
}
}

View File

@@ -0,0 +1,79 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
public class WhileNode extends Node {
public final Node condition, body;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
int start = target.size();
condition.compile(target, true);
int mid = target.temp();
var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
var endI = target.size();
end.set(endI + 1);
target.add(Instruction.jmp(start - end.getAsInt()));
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public WhileNode(Location loc, String label, Node condition, Node body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
public static ParseRes<WhileNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n);
n += label.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
n += 5;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
n++;
var cond = JavaScript.parseExpression(src, i + n, 0);
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition.");
n += cond.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition.");
n++;
var body = JavaScript.parseStatement(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body.");
n += body.n;
return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n);
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.compilation.members;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
public class AssignShorthandNode implements Member {
public final Location loc;
public final Node key;
public final AssignTarget target;
public final Node value;
@Override public Location loc() { return loc; }
@Override public void compile(CompileResult target, boolean pollute, boolean enumerable) {
throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context");
}
public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) {
this.loc = loc;
this.key = key;
this.target = target;
this.value = value;
}
public AssignTarget target() {
return new AssignNode(loc(), target, value);
}
public static ParseRes<AssignShorthandNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var var = VariableNode.parse(src, i + n);
if (!var.isSuccess()) return var.chainError();
n += var.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "=")) return ParseRes.failed();
n++;
var value = JavaScript.parseExpression(src, i + n, 2);
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer");
n += value.n;
return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n);
}
}

View File

@@ -0,0 +1,92 @@
package me.topchetoeu.jscript.compilation.members;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class FieldMemberNode implements Member {
public final Location loc;
public final Node key;
public final Node value;
@Override public Location loc() { return loc; }
@Override public void compile(CompileResult target, boolean pollute, boolean enumerable) {
if (pollute) target.add(Instruction.dup());
key.compile(target, true);
if (value == null) target.add(Instruction.pushUndefined());
else value.compile(target, true);
target.add(Instruction.defField(enumerable));
}
public FieldMemberNode(Location loc, Node key, Node value) {
this.loc = loc;
this.key = key;
this.value = value;
}
public static ParseRes<FieldMemberNode> parseObject(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var name = ObjectNode.parsePropName(src, i + n);
if (!name.isSuccess()) return name.chainError();
n += name.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.failed();
n++;
var value = JavaScript.parseExpression(src, i + n, 2);
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
n += value.n;
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
}
public static ParseRes<FieldMemberNode> parseShorthand(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var var = VariableNode.parse(src, i + n);
if (!var.isSuccess()) return var.chainError();
n += var.n;
return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n);
}
public static ParseRes<FieldMemberNode> parseClass(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var name = ObjectNode.parsePropName(src, i + n);
if (!name.isSuccess()) return name.chainError();
n += name.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "=")) {
var end = JavaScript.parseStatementEnd(src, i + n);
if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer");
n += end.n;
return ParseRes.res(new FieldMemberNode(loc, name.result, null), n);
}
n++;
var value = JavaScript.parseExpression(src, i + n, 2);
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
n += value.n;
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
}
}

View File

@@ -0,0 +1,10 @@
package me.topchetoeu.jscript.compilation.members;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
public interface Member {
Location loc();
void compile(CompileResult target, boolean pollute, boolean enumerable);
}

View File

@@ -0,0 +1,70 @@
package me.topchetoeu.jscript.compilation.members;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.FunctionNode;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.Parameters;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class MethodMemberNode extends FunctionNode implements Member {
public final Node key;
@Override public String name() {
if (key instanceof StringNode str) return str.value;
else return null;
}
@Override protected Environment rootEnv(Environment env) {
return env;
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (pollute) target.add(Instruction.dup());
key.compile(target, true);
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, true, false, false, false, null, captures(id, target)));
}
@Override public void compile(CompileResult target, boolean pollute, boolean enumerable) {
compile(target, pollute);
target.add(Instruction.defField(enumerable));
}
public MethodMemberNode(Location loc, Location end, Node key, Parameters params, CompoundNode body) {
super(loc, end, params, body);
this.key = key;
}
public static ParseRes<MethodMemberNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var name = ObjectNode.parsePropName(src, i + n);
if (!name.isSuccess()) return name.chainError();
n += name.n;
var params = Parameters.parseParameters(src, i + n);
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
n += params.n;
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
n += body.n;
var end = src.loc(i + n - 1);
return ParseRes.res(new MethodMemberNode(
loc, end, name.result, params.result, body.result
), n);
}
}

View File

@@ -0,0 +1,90 @@
package me.topchetoeu.jscript.compilation.members;
import java.util.Arrays;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.FunctionNode;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.Parameters;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class PropertyMemberNode extends FunctionNode implements Member{
public final Node key;
public final Pattern argument;
@Override public String name() {
if (key instanceof StringNode str) {
if (isGetter()) return "get " + str.value;
else return "set " + str.value;
}
else return null;
}
@Override protected Environment rootEnv(Environment env) {
return env;
}
public boolean isGetter() { return argument == null; }
public boolean isSetter() { return argument != null; }
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (pollute) target.add(Instruction.dup());
key.compile(target, true);
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, true, false, false, false, null, captures(id, target)));
}
@Override public void compile(CompileResult target, boolean pollute, boolean enumerable) {
compile(target, pollute);
target.add(Instruction.defProp(isSetter(), enumerable));
}
public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) {
super(loc, end, argument == null ? new Parameters(Arrays.asList()) : new Parameters(Arrays.asList(argument)), body);
this.key = key;
this.argument = argument;
}
public static ParseRes<PropertyMemberNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var access = Parsing.parseIdentifier(src, i + n);
if (!access.isSuccess()) return ParseRes.failed();
if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed();
n += access.n;
var name = ObjectNode.parsePropName(src, i + n);
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
n += name.n;
var params = Parameters.parseParameters(src, i + n);
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
if (access.result.equals("get") && params.result.params.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
if (access.result.equals("set") && params.result.params.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
if (params.result.rest != null) return ParseRes.error(params.result.rest.loc(), "Property members may not have rest arguments");
n += params.n;
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
n += body.n;
var end = src.loc(i + n - 1);
return ParseRes.res(new PropertyMemberNode(
loc, end, name.result, access.result.equals("get") ? null : params.result.params.get(0), body.result
), n);
}
}

View File

@@ -0,0 +1,84 @@
package me.topchetoeu.jscript.compilation.patterns;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
public class AssignPattern implements Pattern {
public final Location loc;
public final AssignTarget assignable;
public final Node value;
@Override public Location loc() { return loc; }
@Override public void destructDeclResolve(CompileResult target) {
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
p.destructDeclResolve(target);
}
private void common(CompileResult target) {
target.add(Instruction.dup());
target.add(Instruction.pushUndefined());
target.add(Instruction.operation(Operation.EQUALS));
var start = target.temp();
target.add(Instruction.discard());
value.compile(target, true);
target.set(start, Instruction.jmpIfNot(target.size() - start));
}
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
if (lateInitializer) {
if (assignable instanceof Pattern p) p.declare(target, decl, lateInitializer);
else throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
}
else throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration");
}
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
common(target);
p.destruct(target, decl, shouldDeclare);
}
@Override public void beforeAssign(CompileResult target) {
assignable.beforeAssign(target);
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
common(target);
assignable.afterAssign(target, false);
}
public AssignPattern(Location loc, Pattern assignable, Node value) {
this.loc = loc;
this.assignable = assignable;
this.value = value;
}
public static ParseRes<AssignPattern> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var pattern = Pattern.parse(src, i + n, false);
if (!pattern.isSuccess()) return pattern.chainError();
n += pattern.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "=")) return ParseRes.failed();
n++;
var value = JavaScript.parseExpression(src, i + n, 2);
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a default value");
n += value.n;
return ParseRes.res(new AssignPattern(loc, pattern.result, value.result), n);
}
}

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript.compilation.patterns;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
/**
* Represents all nodes that can be assign targets
*/
public interface AssignTarget extends AssignTargetLike {
Location loc();
/**
* Called to perform calculations before the assigned value is calculated
*/
default void beforeAssign(CompileResult target) {}
/**
* Called to perform the actual assignemnt. Between the `beforeAssign` and this call a single value will have been pushed to the stack
* @param pollute Whether or not to leave the original value on the stack
*/
void afterAssign(CompileResult target, boolean pollute);
default void assign(CompileResult target, boolean pollute) {
afterAssign(target, pollute);
}
@Override default AssignTarget toAssignTarget() { return this; }
}

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.compilation.patterns;
/**
* Represents all nodes that can be converted to assign targets
*/
public interface AssignTargetLike {
AssignTarget toAssignTarget();
}

View File

@@ -0,0 +1,80 @@
package me.topchetoeu.jscript.compilation.patterns;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
public class Binding implements Pattern {
public final Location loc;
public final DeclarationType type;
public final AssignTarget assignable;
@Override public Location loc() { return loc; }
@Override public void destructDeclResolve(CompileResult target) {
if (type != null && !type.strict) {
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
p.destructDeclResolve(target);
}
}
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
p.destruct(target, decl, shouldDeclare);
}
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
p.declare(target, decl, lateInitializer);
}
public void resolve(CompileResult target) {
if (type != null) destructDeclResolve(target);
}
public void declare(CompileResult target, boolean hasInit) {
if (type != null) destructVar(target, type, hasInit);
}
public void declareLateInit(CompileResult target) {
if (type != null) declare(target, type, true);
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
assignable.assign(target, pollute);
}
public Binding(Location loc, DeclarationType type, AssignTarget assignable) {
this.loc = loc;
this.type = type;
this.assignable = assignable;
}
public static ParseRes<Binding> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var declType = JavaScript.parseDeclarationType(src, i + n);
if (!declType.isSuccess()) {
var res = JavaScript.parseExpression(src, i + n, 13);
if (res.isSuccess() && res.result instanceof AssignTargetLike target) {
n += res.n;
return ParseRes.res(new Binding(loc, null, target.toAssignTarget()), n);
}
else return ParseRes.failed();
}
else {
n += declType.n;
n += Parsing.skipEmpty(src, i + n);
var res = Pattern.parse(src, i + n, false);
if (!res.isSuccess()) return ParseRes.failed();
n += res.n;
return ParseRes.res(new Binding(loc, declType.result, res.result), n);
}
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.compilation.patterns;
import me.topchetoeu.jscript.compilation.CompileResult;
public interface ChangeTarget extends AssignTarget {
void beforeChange(CompileResult target);
}

View File

@@ -0,0 +1,161 @@
package me.topchetoeu.jscript.compilation.patterns;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
public class ObjectPattern extends Node implements Pattern {
public static final class Member {
public final Node key;
public final AssignTarget consumable;
public Member(Node key, AssignTarget consumer) {
this.key = key;
this.consumable = consumer;
}
}
public final List<Member> members;
public void compile(CompileResult target, Consumer<AssignTarget> consumer, boolean pollute) {
for (var el : members) {
target.add(Instruction.dup());
IndexNode.indexLoad(target, el.key, true);
consumer.accept(el.consumable);
}
if (!pollute) target.add(Instruction.discard());
}
@Override public void destructDeclResolve(CompileResult target) {
for (var t : members) {
if (t.consumable instanceof Pattern p) p.destructDeclResolve(target);
else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context");
}
}
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
compile(target, t -> {
if (t instanceof Pattern p) p.destruct(target, decl, shouldDeclare);
else throw new SyntaxException(t.loc(), "Unexpected non-pattern in destruct context");
}, false);
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
compile(target, t -> t.assign(target, false), pollute);
}
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
if (lateInitializer) {
for (var t : members) {
if (t.consumable instanceof Pattern p) p.declare(target, decl, lateInitializer);
else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context");
}
}
else throw new SyntaxException(loc(), "Object pattern must be initialized");
}
public ObjectPattern(Location loc, List<Member> members) {
super(loc);
this.members = members;
}
private static ParseRes<Member> parseShorthand(Source src, int i) {
ParseRes<Pattern> res = ParseRes.first(src, i,
AssignPattern::parse,
VariableNode::parse
);
if (res.isSuccess()) {
if (res.result instanceof AssignPattern assign) {
if (assign.assignable instanceof VariableNode var) {
return ParseRes.res(new Member(new StringNode(var.loc(), var.name), res.result), res.n);
}
}
else if (res.result instanceof VariableNode var) {
return ParseRes.res(new Member(new StringNode(var.loc(), var.name), res.result), res.n);
}
}
return res.chainError();
}
private static ParseRes<Member> parseKeyed(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var key = ObjectNode.parsePropName(src, i + n);
if (!key.isSuccess()) return key.chainError();
n += key.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n , ":")) return ParseRes.failed();
n++;
ParseRes<Pattern> res = Pattern.parse(src, i + n, true);
if (!res.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a pattern after colon");
n += res.n;
return ParseRes.res(new Member(key.result, res.result), n);
}
public static ParseRes<ObjectPattern> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "{")) return ParseRes.failed();
n++;
n += Parsing.skipEmpty(src, i + n);
var members = new LinkedList<Member>();
if (src.is(i + n, "}")) {
n++;
return ParseRes.res(new ObjectPattern(loc, members), n);
}
while (true) {
ParseRes<Member> prop = ParseRes.first(src, i + n,
ObjectPattern::parseKeyed,
ObjectPattern::parseShorthand
);
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object pattern");
n += prop.n;
members.add(prop.result);
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
continue;
}
else if (src.is(i + n, "}")) {
n++;
break;
}
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
}
return ParseRes.res(new ObjectPattern(loc, members), n);
}
}

View File

@@ -0,0 +1,59 @@
package me.topchetoeu.jscript.compilation.patterns;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.values.VariableNode;
/**
* Represents all nodes that can be a destructors (note that all destructors are assign targets, too)
*/
public interface Pattern extends AssignTarget {
Location loc();
/**
* Called when the destructor has to declare
* @param target
*/
void destructDeclResolve(CompileResult target);
/**
* Called when a declaration-like is being destructed
* @param decl The variable type the destructor must declare, if it is a named pne
*/
void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare);
/**
* Run when destructing a declaration without an initializer
*/
void declare(CompileResult target, DeclarationType decl, boolean lateInitializer);
public default void destructArg(CompileResult target, DeclarationType decl) {
destruct(target, decl, false);
}
public default void destructVar(CompileResult target, DeclarationType decl, boolean hasInitializer) {
if (hasInitializer) {
if (decl == null || !decl.strict) destruct(target, null, true);
else destruct(target, decl, true);
}
else {
if (decl == null || !decl.strict) declare(target, null, false);
else declare(target, decl, false);
}
}
public static ParseRes<Pattern> parse(Source src, int i, boolean withDefault) {
return withDefault ?
ParseRes.first(src, i,
AssignPattern::parse,
ObjectPattern::parse,
VariableNode::parse
) :
ParseRes.first(src, i,
ObjectPattern::parse,
VariableNode::parse
);
}
}

View File

@@ -0,0 +1,136 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.HashMap;
import java.util.HashSet;
import me.topchetoeu.jscript.common.parsing.Location;
public class FunctionScope extends Scope {
private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
private final HashMap<String, Variable> specialVarMap = new HashMap<>();
private final HashMap<String, Variable> functionVarMap = new HashMap<>();
private final HashMap<String, Variable> capturesMap = new HashMap<>();
private final HashSet<String> blacklistNames = new HashSet<>();
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
private final HashMap<Variable, Variable> parentToChild = new HashMap<>();
private final Scope captureParent;
public final boolean passtrough;
@Override public boolean hasNonStrict(String name) {
if (functionVarMap.containsKey(name)) return true;
if (blacklistNames.contains(name)) return true;
return false;
}
@Override public Variable define(Variable var, Location loc) {
checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (passtrough) {
blacklistNames.add(var.name);
return null;
}
else {
functionVarMap.put(var.name, var);
return locals.add(var);
}
}
public Variable defineSpecial(Variable var, Location loc) {
checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
specialVarMap.put(var.name, var);
return locals.add(var);
}
@Override public Variable get(String name, boolean capture) {
var superRes = super.get(name, capture);
if (superRes != null) return superRes;
if (specialVarMap.containsKey(name)) return addCaptured(specialVarMap.get(name), capture);
if (functionVarMap.containsKey(name)) return addCaptured(functionVarMap.get(name), capture);
if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture);
if (captureParent == null) return null;
var parentVar = captureParent.get(name, true);
if (parentVar == null) return null;
var childVar = captures.add(parentVar.clone());
capturesMap.put(childVar.name, childVar);
childToParent.put(childVar, parentVar);
parentToChild.put(parentVar, childVar);
return childVar;
}
@Override public Variable get(Variable var, boolean capture) {
if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture);
if (captures.has(var)) return addCaptured(var, capture);
if (locals.has(var)) return addCaptured(var, capture);
if (captureParent == null) return null;
var parentVar = captureParent.get(var, true);
if (parentVar == null) return null;
var childVar = captures.add(parentVar.clone());
childToParent.put(childVar, parentVar);
parentToChild.put(parentVar, childVar);
return childVar;
}
@Override public boolean has(String name, boolean capture) {
if (functionVarMap.containsKey(name)) return true;
if (specialVarMap.containsKey(name)) return true;
if (capture) {
if (capturesMap.containsKey(name)) return true;
if (captureParent != null) return captureParent.has(name, true);
}
return false;
}
@Override protected void onFinish() {
captures.freeze();
super.onFinish();
}
@Override public int capturesCount() {
return captures.size();
}
public int[] getCaptureIndices() {
var res = new int[captures.size()];
var i = 0;
for (var el : captures.all()) {
assert childToParent.containsKey(el);
res[i] = childToParent.get(el).index().toCaptureIndex();
i++;
}
return res;
}
public FunctionScope(Scope parent) {
super();
if (parent.finished()) throw new RuntimeException("Parent is finished");
this.captureParent = parent;
this.passtrough = false;
this.singleEntry = false;
}
public FunctionScope(boolean passtrough) {
super();
this.captureParent = null;
this.passtrough = passtrough;
this.singleEntry = false;
}
}

View File

@@ -0,0 +1,249 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.HashMap;
import java.util.LinkedList;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
public class Scope {
protected final HashMap<String, Variable> strictVarMap = new HashMap<>();
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset);
protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset);
private boolean ended = false;
private boolean finished = false;
private Scope child;
private LinkedList<Scope> children = new LinkedList<>();
public final Scope parent;
/**
* Wether or not the scope is going to be entered multiple times.
* If set to true, captured variables will be kept as allocations, otherwise will be converted to locals
*/
public boolean singleEntry = true;
protected void transferCaptured(Variable var) {
if (!singleEntry) {
if (!this.capturables.has(var)) this.capturables.add(var);
}
else if (parent != null) {
parent.transferCaptured(var);
}
else throw new IllegalStateException("Couldn't transfer captured variable");
}
protected final Variable addCaptured(Variable var, boolean captured) {
if (captured) transferCaptured(var);
return var;
}
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
}
/**
* Throws if the scope is ended
*/
protected final void checkNotEnded() {
if (ended) throw new IllegalStateException("Cannot define in an ended scope");
}
/**
* Defines a nameless variable for holding intermediate temporary values
*
* @throws RuntimeException If the scope is finalized or has an active child
*/
public Variable defineTemp() {
checkNotEnded();
return this.locals.add(new Variable("<temp>", false));
}
/**
* Defines an ES5-style variable
*
* @returns The index supplier of the variable if it is a local, or null if it is a global
* @throws SyntaxException If an ES2015-style variable with the same name exists anywhere from the current function to the current scope
* @throws RuntimeException If the scope is finalized or has an active child
*/
public Variable define(Variable var, Location loc) {
checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (parent != null) return parent.define(var, loc);
return null;
}
/**
* Checks if this scope's function parent has a non-strict variable of the given name
*/
public boolean hasNonStrict(String name) { return false; }
/**
* Defines an ES2015-style variable
* @param readonly True if const, false if let
* @return The index supplier of the variable
* @throws SyntaxException If any variable with the same name exists in the current scope
* @throws RuntimeException If the scope is finalized or has an active child
*/
public Variable defineStrict(Variable var, Location loc) {
checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name);
strictVarMap.put(var.name, var);
return locals.add(var);
}
/**
* Gets the index supplier of the given variable name, or null if it is a global
*
* @param capture If true, the variable is being captured by a function
*/
public Variable get(String name, boolean capture) {
var res = strictVarMap.get(name);
if (res != null) return addCaptured(res, capture);
if (parent != null) return parent.get(name, capture);
return null;
}
/**
* Gets the index supplier of the given variable, or null if it isn't in this scope chain.
* This will also execute capturing logic.
*
* @param capture If true, the variable is being captured by a function
*/
public Variable get(Variable var, boolean capture) {
if (locals.has(var)) return addCaptured(var, capture);
if (parent != null) return parent.get(var, capture);
return null;
}
/**
* Checks if the given variable name is accessible
*
* @param capture If true, will check beyond this function's scope
*/
public boolean has(String name, boolean capture) {
if (strictVarMap.containsKey(name)) return true;
if (parent != null) return parent.has(name, capture);
return false;
}
/**
* Gets the index offset from this scope to its children
*/
public final int variableOffset() {
var res = 0;
for (var curr = parent; curr != null; curr = curr.parent) {
res += curr.locals.size();
}
return res;
}
public final int capturablesOffset() {
var res = 0;
for (var curr = this; curr != null; curr = curr.parent) {
if (curr != this) res += curr.capturables.size();
if (curr.parent == null) res += curr.localsCount();
}
return res;
}
public final Variable define(DeclarationType type, String name, Location loc) {
if (type.strict) return defineStrict(new Variable(name, type.readonly), loc);
else return define(new Variable(name, type.readonly), loc);
}
public int localsCount() {
var res = 0;
for (var child : children) {
var childN = child.localsCount();
if (res < childN) res = childN;
}
return res + locals.size();
}
public int capturesCount() { return 0; }
public int allocCount() {
var res = capturables.size();
return res;
}
public int capturablesCount() {
var res = capturables.size();
for (var child : children) res += child.capturablesCount();
return res;
}
public Iterable<Variable> capturables() {
return capturables.all();
}
public Iterable<Variable> locals() {
return locals.all();
}
/**
* Ends this scope. This will make it possible for another child to take its place
*/
public boolean end() {
if (ended) return false;
this.ended = true;
if (this.parent != null) {
assert this.parent.child == this;
this.parent.child = null;
}
return true;
}
protected void onFinish() {
this.locals.freeze();
this.capturables.freeze();
}
/**
* Finalizes this scope. The scope will become immutable after this call
* @return
*/
public final boolean finish() {
if (finished) return false;
if (parent != null && parent.finished) throw new IllegalStateException("Tried to finish a child after the parent was finished");
this.onFinish();
for (var child : children) child.finish();
this.finished = true;
return true;
}
public final boolean ended() { return ended; }
public final boolean finished() { return finished; }
public final Scope child() { return child; }
public Scope() {
this(null);
}
public Scope(Scope parent) {
if (parent != null) {
if (parent.ended) throw new RuntimeException("Parent is not active");
if (parent.finished) throw new RuntimeException("Parent is finished");
if (parent.child != null) throw new RuntimeException("Parent has an active child");
this.parent = parent;
this.parent.child = this;
this.parent.children.add(this);
}
else this.parent = null;
}
}

View File

@@ -0,0 +1,41 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.function.Supplier;
public final class Variable {
private Supplier<VariableIndex> indexSupplier;
private boolean frozen;
public final boolean readonly;
public final String name;
public final VariableIndex index() {
if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized");
return indexSupplier.get();
}
public final void freeze() {
this.frozen = true;
}
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
this.indexSupplier = index;
return this;
}
public final Supplier<VariableIndex> indexSupplier() {
return indexSupplier;
}
public final Variable clone() {
return new Variable(name, readonly).setIndexSupplier(indexSupplier);
}
public Variable(String name, boolean readonly) {
this.name = name;
this.readonly = readonly;
}
public static Variable of(String name, boolean readonly, VariableIndex index) {
return new Variable(name, readonly).setIndexSupplier(() -> index);
}
}

View File

@@ -0,0 +1,60 @@
package me.topchetoeu.jscript.compilation.scope;
import me.topchetoeu.jscript.common.Instruction;
public final class VariableIndex {
public static enum IndexType {
LOCALS,
CAPTURABLES,
CAPTURES,
}
public final VariableIndex.IndexType type;
public final int index;
public final int toCaptureIndex() {
switch (type) {
case CAPTURES: return ~index;
case CAPTURABLES: return index;
default: throw new UnsupportedOperationException("Index type " + type + " may not be captured");
}
}
public final Instruction toGet() {
switch (type) {
case CAPTURES: return Instruction.loadVar(~index);
case CAPTURABLES: return Instruction.loadVar(index);
case LOCALS: return Instruction.loadVar(index);
default: throw new UnsupportedOperationException("Unknown index type " + type);
}
}
public final Instruction toSet(boolean keep) {
switch (type) {
case CAPTURES: return Instruction.storeVar(index, keep, false);
case CAPTURABLES: return Instruction.storeVar(index, keep, false);
case LOCALS: return Instruction.storeVar(index, keep, false);
default: throw new UnsupportedOperationException("Unknown index type " + type);
}
}
public final Instruction toInit() {
switch (type) {
case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type);
case CAPTURABLES: return Instruction.storeVar(index, false, true);
case LOCALS: return Instruction.storeVar(index, false, true);
default: throw new UnsupportedOperationException("Unknown index type " + type);
}
}
public final Instruction toUndefinedInit(boolean force) {
switch (type) {
case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type);
case CAPTURABLES: return Instruction.varInit(index, force);
case LOCALS: return Instruction.varInit(index, force);
default: throw new UnsupportedOperationException("Unknown index type " + type);
}
}
public VariableIndex(VariableIndex.IndexType type, int index) {
this.type = type;
this.index = index;
}
}

View File

@@ -0,0 +1,205 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
public final class VariableList {
private final class VariableNode implements Supplier<VariableIndex> {
public Variable var;
public VariableNode next;
public VariableNode prev;
public boolean frozen;
public int index;
public VariableList list() { return VariableList.this; }
@Override public VariableIndex get() {
if (frozen) {
if (offset == null) return new VariableIndex(indexType, index);
else new VariableIndex(indexType, index + offset.getAsInt());
}
var res = 0;
if (offset != null) res = offset.getAsInt();
for (var it = prev; it != null; it = it.prev) {
res++;
}
return new VariableIndex(indexType, res);
}
public void freeze() {
if (frozen) return;
this.frozen = true;
this.next = null;
this.var.freeze();
if (prev == null) return;
this.index = prev.index + 1;
this.next = null;
return;
}
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
this.var = var;
this.next = next;
this.prev = prev;
}
}
private VariableNode first, last;
private ArrayList<VariableNode> frozenList = null;
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
private final IntSupplier offset;
public final VariableIndex.IndexType indexType;
public boolean frozen() {
if (frozenList != null) {
assert frozenList != null;
assert varMap != null;
assert first == null;
assert last == null;
return true;
}
else {
assert frozenList == null;
assert varMap != null;
return false;
}
}
public Variable add(Variable val) {
if (frozen()) throw new RuntimeException("The scope has been frozen");
if (val.indexSupplier() instanceof VariableNode prevNode) {
prevNode.list().remove(val);
}
var node = new VariableNode(val, null, last);
if (last != null) {
assert first != null;
last.next = node;
node.prev = last;
last = node;
}
else {
first = last = node;
}
varMap.put(val, node);
val.setIndexSupplier(node);
return val;
}
public Variable remove(Variable var) {
if (frozen()) throw new RuntimeException("The scope has been frozen");
if (var == null) return null;
var node = varMap.get(var);
if (node == null) return null;
if (node.prev != null) {
assert node != first;
node.prev.next = node.next;
}
else {
assert node == first;
first = first.next;
}
if (node.next != null) {
assert node != last;
node.next.prev = node.prev;
}
else {
assert node == last;
last = last.prev;
}
node.next = null;
node.prev = null;
varMap.remove(node.var);
return node.var;
}
public boolean has(Variable var) {
return varMap.containsKey(var);
}
public Supplier<VariableIndex> indexer(Variable var) {
return varMap.get(var);
}
public int size() {
if (frozen()) return frozenList.size();
else return varMap.size();
}
public void freeze() {
if (frozen()) return;
frozenList = new ArrayList<>();
for (var node = first; node != null; ) {
frozenList.add(node);
var tmp = node;
node = node.next;
tmp.freeze();
}
first = last = null;
}
public Iterable<Variable> all() {
if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator();
else return () -> new Iterator<Variable>() {
private VariableNode curr = first;
@Override public boolean hasNext() {
return curr != null;
}
@Override public Variable next() {
if (curr == null) return null;
var res = curr;
curr = curr.next;
return res.var;
}
};
}
public VariableList(VariableIndex.IndexType type, IntSupplier offset) {
this.indexType = type;
this.offset = offset;
}
public VariableList(VariableIndex.IndexType type, int offset) {
this.indexType = type;
this.offset = () -> offset;
}
public VariableList(VariableIndex.IndexType type, VariableList prev) {
this.indexType = type;
this.offset = prev::size;
}
public VariableList(VariableIndex.IndexType type) {
this.indexType = type;
this.offset = null;
}
}

View File

@@ -0,0 +1,17 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class ArgumentsNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.loadArgs(false));
}
public ArgumentsNode(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,81 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class ArrayNode extends Node {
public final Node[] statements;
@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());
el.compile(target, true);
target.add(Instruction.storeMember(i));
}
}
if (!pollute) target.add(Instruction.discard());
}
public ArrayNode(Location loc, Node[] statements) {
super(loc);
this.statements = statements;
}
public static ParseRes<ArrayNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "[")) return ParseRes.failed();
n++;
var values = new ArrayList<Node>();
loop: while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "]")) {
n++;
break;
}
while (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
values.add(null);
if (src.is(i + n, "]")) {
n++;
break loop;
}
}
var res = JavaScript.parseExpression(src, i + n, 2);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element.");
n += res.n;
n += Parsing.skipEmpty(src, i + n);
values.add(res.result);
if (src.is(i + n, ",")) n++;
else if (src.is(i + n, "]")) {
n++;
break;
}
}
return ParseRes.res(new ArrayNode(loc, values.toArray(new Node[0])), n);
}
}

View File

@@ -0,0 +1,34 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.ClassNode;
import me.topchetoeu.jscript.compilation.JavaScript;
public class ClassValueNode extends ClassNode {
public ClassValueNode(Location loc, Location end, String name, ClassBody body) {
super(loc, end, name, body);
}
public static ParseRes<ClassValueNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
n += 5;
var name = Parsing.parseIdentifier(src, i + n);
if (name.isSuccess() && !JavaScript.checkVarName(name.result)) {
name = ParseRes.error(src.loc(i + n), "Unexpected keyword '" + name.result + "'");
}
n += name.n;
var body = parseBody(src, i + n);
if (!body.isSuccess()) return body.chainError(name).chainError(src.loc(i + n), "Expected a class body");
n += body.n;
return ParseRes.res(new ClassValueNode(loc, src.loc(i + n), name.result, body.result), n);
}
}

View File

@@ -0,0 +1,17 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class GlobalThisNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.loadGlob());
}
public GlobalThisNode(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,171 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.LinkedList;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.members.AssignShorthandNode;
import me.topchetoeu.jscript.compilation.members.FieldMemberNode;
import me.topchetoeu.jscript.compilation.members.Member;
import me.topchetoeu.jscript.compilation.members.MethodMemberNode;
import me.topchetoeu.jscript.compilation.members.PropertyMemberNode;
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
import me.topchetoeu.jscript.compilation.patterns.ObjectPattern;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class ObjectNode extends Node implements AssignTargetLike {
public final List<Member> members;
// TODO: Implement spreading into object
// private void compileRestObjBuilder(CompileResult target, int srcDupN) {
// var subtarget = target.subtarget();
// var src = subtarget.scope.defineTemp();
// var dst = subtarget.scope.defineTemp();
// target.add(Instruction.loadObj());
// target.add(_i -> src.index().toSet(true));
// target.add(_i -> dst.index().toSet(destructors.size() > 0));
// target.add(Instruction.keys(true, true));
// var start = target.size();
// target.add(Instruction.dup());
// var mid = target.temp();
// target.add(_i -> src.index().toGet());
// target.add(Instruction.dup(1, 1));
// target.add(Instruction.loadMember());
// target.add(_i -> dst.index().toGet());
// target.add(Instruction.dup(1, 1));
// target.add(Instruction.storeMember());
// target.add(Instruction.discard());
// var end = target.size();
// target.add(Instruction.jmp(start - end));
// target.set(mid, Instruction.jmpIfNot(end - mid + 1));
// target.add(Instruction.discard());
// target.add(Instruction.dup(srcDupN, 1));
// target.scope.end();
// }
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadObj());
for (var el : members) el.compile(target, true, true);
}
@Override public AssignTarget toAssignTarget() {
var newMembers = new LinkedList<ObjectPattern.Member>();
for (var el : members) {
if (el instanceof FieldMemberNode field) {
if (field.value instanceof AssignTargetLike target) newMembers.add(new ObjectPattern.Member(field.key, target.toAssignTarget()));
else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor");
}
else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new ObjectPattern.Member(shorthand.key, shorthand.target()));
else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor");
}
return new ObjectPattern(loc(), newMembers);
}
public ObjectNode(Location loc, List<Member> map) {
super(loc);
this.members = map;
}
private static ParseRes<Node> parseComputePropName(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "[")) return ParseRes.failed();
n++;
var val = JavaScript.parseExpression(src, i, 0);
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property");
n += val.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket after compute property");
n++;
return ParseRes.res(val.result, n);
}
public static ParseRes<Node> parsePropName(Source src, int i) {
return ParseRes.first(src, i,
(s, j) -> {
var m = Parsing.skipEmpty(s, j);
var l = s.loc(j + m);
var r = Parsing.parseIdentifier(s, j + m);
if (r.isSuccess()) return ParseRes.res(new StringNode(l, r.result), r.n);
else return r.chainError();
},
StringNode::parse,
NumberNode::parse,
ObjectNode::parseComputePropName
);
}
public static ParseRes<ObjectNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "{")) return ParseRes.failed();
n++;
n += Parsing.skipEmpty(src, i + n);
var members = new LinkedList<Member>();
if (src.is(i + n, "}")) {
n++;
return ParseRes.res(new ObjectNode(loc, members), n);
}
while (true) {
ParseRes<Member> prop = ParseRes.first(src, i + n,
MethodMemberNode::parse,
PropertyMemberNode::parse,
FieldMemberNode::parseObject,
AssignShorthandNode::parse,
FieldMemberNode::parseShorthand
);
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal");
n += prop.n;
members.add(prop.result);
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
continue;
}
else if (src.is(i + n, "}")) {
n++;
break;
}
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
}
return ParseRes.res(new ObjectNode(loc, members), n);
}
}

View File

@@ -0,0 +1,76 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class RegexNode extends Node {
public final String pattern, flags;
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadRegex(pattern, flags));
if (!pollute) target.add(Instruction.discard());
}
public static ParseRes<RegexNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, '/')) return ParseRes.failed();
var loc = src.loc(i + n);
n++;
var source = new StringBuilder();
var flags = new StringBuilder();
var inBrackets = false;
while (true) {
if (src.is(i + n, '[')) {
n++;
inBrackets = true;
source.append(src.at(i + n));
continue;
}
else if (src.is(i + n, ']')) {
n++;
inBrackets = false;
source.append(src.at(i + n));
continue;
}
else if (src.is(i + n, '/') && !inBrackets) {
n++;
break;
}
var charRes = Parsing.parseChar(src, i + n);
if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed");
source.append(charRes.result);
n++;
}
while (true) {
char c = src.at(i + n, '\0');
if (src.is(i + n, v -> Parsing.isAny(c, "dgimsuy"))) {
if (flags.indexOf(c + "") >= 0) return ParseRes.error(src.loc(i + n), "The flags of a regular expression may not be repeated");
flags.append(c);
}
else break;
n++;
}
return ParseRes.res(new RegexNode(loc, source.toString(), flags.toString()), n);
}
public RegexNode(Location loc, String pattern, String flags) {
super(loc);
this.pattern = pattern;
this.flags = flags;
}
}

View File

@@ -0,0 +1,17 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class SuperNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
throw new SyntaxException(loc(), "Unexpected 'super' reference here");
}
public SuperNode(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,17 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class ThisNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.loadThis());
}
public ThisNode(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,132 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.function.IntFunction;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
import me.topchetoeu.jscript.compilation.scope.Variable;
public class VariableNode extends Node implements Pattern, ChangeTarget {
public final String name;
public String assignName() { return name; }
@Override public void beforeChange(CompileResult target) {
target.add(VariableNode.toGet(target, loc(), name));
}
@Override public void destructDeclResolve(CompileResult target) {
var i = target.scope.define(new Variable(name, false), loc());
if (i != null) target.add(_i -> i.index().toUndefinedInit(false));
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
target.add(VariableNode.toSet(target, loc(), name, pollute));
}
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
if (decl != null) {
var i = target.scope.define(decl, name, loc());
target.add(_i -> i.index().toUndefinedInit(decl.strict));
}
else target.add(_i -> {
var i = target.scope.get(name, false);
if (i == null) return Instruction.globDef(name);
else return Instruction.nop();
});
target.setLocation(loc());
}
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
if (!shouldDeclare || decl == null) {
if (shouldDeclare) target.add(VariableNode.toInit(target, loc(), name));
else target.add(VariableNode.toInit(target, loc(), name));
}
else {
if (decl == DeclarationType.VAR && target.scope.has(name, false)) throw new SyntaxException(loc(), "Duplicate parameter name not allowed");
var v = target.scope.define(decl, name, loc());
target.add(_i -> v.index().toInit());
}
target.setLocation(loc());
}
@Override public void compile(CompileResult target, boolean pollute) {
target.add(toGet(target, loc(), name, true, false)).setLocation(loc());
}
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) {
var oldI = target.scope.get(name, false);
if (oldI != null) {
if (keep) return _i -> oldI.index().toGet();
else return _i -> Instruction.nop();
}
else return _i -> {
var newI = target.scope.get(name, false);
if (newI == null) return Instruction.globGet(name, forceGet);
else if (keep) return newI.index().toGet();
else return Instruction.nop();
};
}
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name) {
return toGet(target, loc, name, true, false);
}
public static IntFunction<Instruction> toInit(CompileResult target, Location loc, String name) {
var oldI = target.scope.get(name, false);
if (oldI != null) return _i -> oldI.index().toInit();
else return _i -> {
var i = target.scope.get(name, false);
if (i == null) return Instruction.globSet(name, false, true);
else return i.index().toInit();
};
}
public static IntFunction<Instruction> toSet(CompileResult target, Location loc, String name, boolean keep) {
var oldI = target.scope.get(name, false);
if (oldI != null) return _i -> oldI.index().toSet(keep);
else return _i -> {
var i = target.scope.get(name, false);
if (i == null) return Instruction.globSet(name, keep, false);
else return i.index().toSet(keep);
};
}
public VariableNode(Location loc, String name) {
super(loc);
this.name = name;
}
public static ParseRes<VariableNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var literal = Parsing.parseIdentifier(src, i);
if (!literal.isSuccess()) return literal.chainError();
n += literal.n;
if (!JavaScript.checkVarName(literal.result)) {
if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported");
return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result));
}
return ParseRes.res(new VariableNode(loc, literal.result), n);
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class BoolNode extends Node {
public final boolean value;
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.pushValue(value));
}
public BoolNode(Location loc, boolean value) {
super(loc);
this.value = value;
}
}

View File

@@ -0,0 +1,14 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class NullNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.pushNull());
}
public NullNode(Location loc) { super(loc); }
}

View File

@@ -0,0 +1,40 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class NumberNode extends Node {
public final double value;
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.pushValue(value));
}
public NumberNode(Location loc, double value) {
super(loc);
this.value = value;
}
public static double power(double a, long b) {
if (b == 0) return 1;
if (b == 1) return a;
if (b < 0) return 1 / power(a, -b);
if ((b & 1) == 0) return power(a * a, b / 2);
else return a * power(a * a, b / 2);
}
public static ParseRes<NumberNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var res = Parsing.parseNumber(src, i + n, false);
if (res.isSuccess()) return ParseRes.res(new NumberNode(loc, res.result), n + res.n);
else return res.chainError();
}
}

View File

@@ -0,0 +1,31 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
public class StringNode extends Node {
public final String value;
@Override public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.pushValue(value));
}
public StringNode(Location loc, String value) {
super(loc);
this.value = value;
}
public static ParseRes<StringNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var res = Parsing.parseString(src, i + n);
if (res.isSuccess()) return ParseRes.res(new StringNode(loc, res.result), n + res.n);
else return res.chainError();
}
}

View File

@@ -0,0 +1,46 @@
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
public class AssignNode extends Node implements AssignTarget {
public final AssignTarget assignable;
public final Node value;
@Override public void compile(CompileResult target, boolean pollute) {
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here");
assignable.beforeAssign(target);
value.compile(target, true);
assignable.afterAssign(target, pollute);
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed");
if (pollute) target.add(Instruction.dup(2, 0));
else target.add(Instruction.dup());
target.add(Instruction.pushUndefined());
target.add(Instruction.operation(Operation.EQUALS));
var start = target.temp();
target.add(Instruction.discard());
value.compile(target, true);
target.set(start, Instruction.jmpIfNot(target.size() - start));
assignable.assign(target, false);
if (!pollute) target.add(Instruction.discard());
}
public AssignNode(Location loc, AssignTarget assignable, Node value) {
super(loc);
this.assignable = assignable;
this.value = value;
}
}

View File

@@ -0,0 +1,203 @@
package me.topchetoeu.jscript.compilation.values.operations;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.ClassNode;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
import me.topchetoeu.jscript.compilation.values.ArrayNode;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.SuperNode;
import me.topchetoeu.jscript.compilation.values.ThisNode;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class CallNode extends Node {
public static boolean ATTACH_NAME = true;
public final Node func;
public final Node[] args;
public final boolean isNew;
private String generateName(Node func, Node index) {
String res = "(intermediate value)";
boolean shouldParen = false;
if (func instanceof ObjectNode) {
var obj = (ObjectNode)func;
shouldParen = true;
if (obj.members.size() > 0) res = "{}";
else res = "{(intermediate value)}";
}
else if (func instanceof StringNode) {
res = JSON.stringify(JSONElement.string(((StringNode)func).value));
}
else if (func instanceof NumberNode) {
res = JSON.stringify(JSONElement.number(((NumberNode)func).value));
}
else if (func instanceof BoolNode) {
res = ((BoolNode)func).value ? "true" : "false";
}
else if (func instanceof VariableNode) {
res = ((VariableNode)func).name;
}
else if (func instanceof ThisNode) {
res = "this";
}
else if (func instanceof ArgumentsNode) {
res = "arguments";
}
else if (func instanceof ArrayNode) {
var els = new ArrayList<String>();
for (var el : ((ArrayNode)func).statements) {
if (el != null) els.add(generateName(el, null));
else els.add("(intermediate value)");
}
res = "[" + String.join(",", els) + "]";
}
if (index == null) return res;
if (shouldParen) res = "(" + res + ")";
if (index instanceof StringNode) {
var val = ((StringNode)index).value;
var bracket = JSON.stringify(JSONElement.string(val));
if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]";
if (Parsing.parseIdentifier(new Source(val), 0).n != val.length()) return res + "[" + bracket + "]";
return res + "." + val;
}
return res + "[" + generateName(index, null) + "]";
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
var superInstr = target.env.get(ClassNode.SUPER);
if (!isNew && func instanceof SuperNode && superInstr != null && target.env.hasNotNull(ClassNode.ON_SUPER_CALL)) {
target.env.get(ClassNode.SUPER_CONSTR).accept(target);
for (var arg : args) arg.compile(target, true);
target.add(Instruction.callSuper(args.length));
target.env.get(ClassNode.ON_SUPER_CALL).accept(target);
}
else if (!isNew && func instanceof IndexNode indexn) {
var obj = indexn.object;
var index = indexn.index;
String name = "";
if (superInstr != null && obj instanceof SuperNode) {
new ThisNode(null).compile(target, true);
target.env.get(ClassNode.SUPER).accept(target);
IndexNode.indexLoad(target, index, true);
}
else {
obj.compile(target, true);
target.add(Instruction.dup());
IndexNode.indexLoad(target, index, true);
}
for (var arg : args) arg.compile(target, true);
if (ATTACH_NAME) name = generateName(obj, index);
target.add(Instruction.call(args.length, true, name));
target.setLocationAndDebug(loc(), type);
}
else {
String name = "";
func.compile(target, true);
for (var arg : args) arg.compile(target, true);
if (ATTACH_NAME) name = generateName(func, null);
if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type);
else target.add(Instruction.call(args.length, false, name)).setLocationAndDebug(loc(), type);
}
if (!pollute) target.add(Instruction.discard());
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public CallNode(Location loc, boolean isNew, Node func, Node ...args) {
super(loc);
this.isNew = isNew;
this.func = func;
this.args = args;
}
public static ParseRes<CallNode> parseCall(Source src, int i, Node prev, int precedence) {
if (precedence > 17) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "(")) return ParseRes.failed();
n++;
var args = new ArrayList<Node>();
boolean prevArg = false;
while (true) {
var argRes = JavaScript.parseExpression(src, i + n, 2);
n += argRes.n;
n += Parsing.skipEmpty(src, i + n);
if (argRes.isSuccess()) {
args.add(argRes.result);
prevArg = true;
}
else if (argRes.isError()) return argRes.chainError();
else if (prevArg && src.is(i + n, ",")) {
prevArg = false;
n++;
}
else if (src.is(i + n, ")")) {
n++;
break;
}
else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren");
else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren");
}
return ParseRes.res(new CallNode(loc, false, prev, args.toArray(new Node[0])), n);
}
public static ParseRes<CallNode> parseNew(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed();
n += 3;
var valRes = JavaScript.parseExpression(src, i + n, 18);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword.");
n += valRes.n;
var callRes = CallNode.parseCall(src, i + n, valRes.result, 0);
if (callRes.isFailed()) return ParseRes.res(new CallNode(loc, true, valRes.result), n);
if (callRes.isError()) return callRes.chainError();
n += callRes.n;
return ParseRes.res(new CallNode(loc, true, callRes.result.func, callRes.result.args), n);
}
}

View File

@@ -0,0 +1,60 @@
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
public class ChangeNode extends Node {
public final ChangeTarget changable;
public final Node value;
public final Operation op;
@Override public void compile(CompileResult target, boolean pollute) {
changable.beforeChange(target);
value.compile(target, true);
target.add(Instruction.operation(op));
changable.afterAssign(target, pollute);
}
public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) {
super(loc);
this.changable = changable;
this.value = value;
this.op = op;
}
public static ParseRes<ChangeNode> parsePrefixIncrease(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "++")) return ParseRes.failed();
n += 2;
var res = JavaScript.parseExpression(src, i + n, 15);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n);
}
public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "--")) return ParseRes.failed();
n += 2;
var res = JavaScript.parseExpression(src, i + n, 15);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n);
}
}

View File

@@ -0,0 +1,39 @@
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class DiscardNode extends Node {
public final Node value;
@Override public void compile(CompileResult target, boolean pollute) {
if (value != null) value.compile(target, false);
if (pollute) target.add(Instruction.pushUndefined());
}
public DiscardNode(Location loc, Node val) {
super(loc);
this.value = val;
}
public static ParseRes<DiscardNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed();
n += 4;
var valRes = JavaScript.parseExpression(src, i + n, 14);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword.");
n += valRes.n;
return ParseRes.res(new DiscardNode(loc, valRes.result), n);
}
}

View File

@@ -0,0 +1,158 @@
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.ClassNode;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
import me.topchetoeu.jscript.compilation.values.SuperNode;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class IndexNode extends Node implements ChangeTarget {
public final Node object;
public final Node index;
@Override public void beforeAssign(CompileResult target) {
compileObj(target, object);
indexStorePushKey(target, index);
}
@Override public void beforeChange(CompileResult target) {
compileObj(target, object);
if (index instanceof NumberNode num && (int)num.value == num.value) {
target.add(Instruction.dup());
target.add(Instruction.loadMember((int)num.value));
}
else if (index instanceof StringNode str) {
target.add(Instruction.dup());
target.add(Instruction.loadMember(str.value));
}
else {
index.compile(target, true);
target.add(Instruction.dup(1, 1));
target.add(Instruction.dup(1, 1));
target.add(Instruction.loadMember());
}
}
@Override public void assign(CompileResult target, boolean pollute) {
compileObj(target, object);
target.add(Instruction.dup(1, 1));
indexStorePushKey(target, index);
indexStore(target, index, pollute);
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
indexStore(target, index, pollute);
}
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
compileObj(target, object);
if (dupObj) target.add(Instruction.dup());
if (index instanceof NumberNode num && (int)num.value == num.value) {
target.add(Instruction.loadMember((int)num.value));
}
else if (index instanceof StringNode str) {
target.add(Instruction.loadMember(str.value));
}
else {
index.compile(target, true);
target.add(Instruction.loadMember());
}
target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard());
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, false, pollute);
}
public IndexNode(Location loc, Node object, Node index) {
super(loc);
this.object = object;
this.index = index;
}
private static void compileObj(CompileResult target, Node obj) {
if (obj instanceof SuperNode && target.env.hasNotNull(ClassNode.SUPER)) {
target.env.get(ClassNode.SUPER).accept(target);
}
else obj.compile(target, true);
}
public static ParseRes<IndexNode> parseIndex(Source src, int i, Node prev, int precedence) {
if (precedence > 18) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "[")) return ParseRes.failed();
n++;
var valRes = JavaScript.parseExpression(src, i + n, 0);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression");
n += valRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket");
n++;
return ParseRes.res(new IndexNode(loc, prev, valRes.result), n);
}
public static ParseRes<IndexNode> parseMember(Source src, int i, Node prev, int precedence) {
if (precedence > 18) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, ".")) return ParseRes.failed();
n++;
var literal = Parsing.parseIdentifier(src, i + n);
if (!literal.isSuccess()) return literal.chainError(src.loc(i + n), "Expected an identifier after member access.");
n += literal.n;
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
}
public static void indexStorePushKey(CompileResult target, Node index) {
if (index instanceof NumberNode num && (int)num.value == num.value) return;
if (index instanceof StringNode) return;
index.compile(target, true);
}
public static void indexStore(CompileResult target, Node index, boolean pollute) {
if (index instanceof NumberNode num && (int)num.value == num.value) {
target.add(Instruction.storeMember((int)num.value, pollute));
}
else if (index instanceof StringNode str) {
target.add(Instruction.storeMember(str.value, pollute));
}
else {
target.add(Instruction.storeMember(pollute));
}
}
public static void indexLoad(CompileResult target, Node index, boolean pollute) {
if (index instanceof NumberNode num && (int)num.value == num.value) {
target.add(Instruction.loadMember((int)num.value));
}
else if (index instanceof StringNode str) {
target.add(Instruction.loadMember(str.value));
}
else {
index.compile(target, true);
target.add(Instruction.loadMember());
}
if (!pollute) target.add(Instruction.discard());
}
}

View File

@@ -0,0 +1,45 @@
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class LazyAndNode extends Node {
public final Node first, second;
@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 LazyAndNode(Location loc, Node first, Node second) {
super(loc);
this.first = first;
this.second = second;
}
public static ParseRes<LazyAndNode> parse(Source src, int i, Node prev, int precedence) {
if (precedence < 4) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "&&")) return ParseRes.failed();
var loc = src.loc(i + n);
n += 2;
var res = JavaScript.parseExpression(src, i + n, 4);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator.");
n += res.n;
return ParseRes.res(new LazyAndNode(loc, prev, res.result), n);
}
}

View File

@@ -0,0 +1,46 @@
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
public class LazyOrNode extends Node {
public final Node first, second;
@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 LazyOrNode(Location loc, Node first, Node second) {
super(loc);
this.first = first;
this.second = second;
}
public static ParseRes<LazyOrNode> parse(Source src, int i, Node prev, int precedence) {
if (precedence < 3) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "||")) return ParseRes.failed();
var loc = src.loc(i + n);
n += 2;
var res = JavaScript.parseExpression(src, i + n, 4);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator.");
n += res.n;
return ParseRes.res(new LazyOrNode(loc, prev, res.result), n);
}
}

View File

@@ -0,0 +1,233 @@
package me.topchetoeu.jscript.compilation.values.operations;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
public class OperationNode extends Node {
private static interface OperatorFactory {
String token();
int precedence();
ParseRes<Node> construct(Source src, int i, Node prev);
}
private static class NormalOperatorFactory implements OperatorFactory {
public final String token;
public final int precedence;
public final Operation operation;
@Override public int precedence() { return precedence; }
@Override public String token() { return token; }
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
var loc = src.loc(i);
var other = JavaScript.parseExpression(src, i, precedence + 1);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
return ParseRes.res(new OperationNode(loc, operation, prev, (Node)other.result), other.n);
}
public NormalOperatorFactory(String token, int precedence, Operation operation) {
this.token = token;
this.precedence = precedence;
this.operation = operation;
}
}
private static class AssignmentOperatorFactory implements OperatorFactory {
public final String token;
public final int precedence;
public final Operation operation;
@Override public int precedence() { return precedence; }
@Override public String token() { return token; }
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
var loc = src.loc(i);
if (operation == null) {
if (!(prev instanceof AssignTargetLike target)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
var other = JavaScript.parseExpression(src, i, precedence);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), other.n);
}
else {
if (!(prev instanceof ChangeTarget target)) return ParseRes.error(loc, String.format("Expected a changeable expression before '%s'", token));
var other = JavaScript.parseExpression(src, i, precedence);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
return ParseRes.res(new ChangeNode(loc, target, other.result, operation), other.n);
}
}
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
this.token = token;
this.precedence = precedence;
this.operation = operation;
}
}
private static class LazyAndFactory implements OperatorFactory {
@Override public int precedence() { return 4; }
@Override public String token() { return "&&"; }
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
var loc = src.loc(i);
var other = JavaScript.parseExpression(src, i, 5);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '&&'");
return ParseRes.res(new LazyAndNode(loc, prev, (Node)other.result), other.n);
}
}
private static class LazyOrFactory implements OperatorFactory {
@Override public int precedence() { return 5; }
@Override public String token() { return "||"; }
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
var loc = src.loc(i);
var other = JavaScript.parseExpression(src, i, 6);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '||'");
return ParseRes.res(new LazyOrNode(loc, prev, (Node)other.result), other.n);
}
}
public final Node[] args;
public final Operation operation;
@Override public void compile(CompileResult target, boolean pollute) {
for (var arg : args) {
arg.compile(target, true);
}
target.add(Instruction.operation(operation));
if (!pollute) target.add(Instruction.discard());
}
public OperationNode(Location loc, Operation operation, Node ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
private static final Map<String, OperatorFactory> factories = Arrays.asList(
new NormalOperatorFactory("*", 13, Operation.MULTIPLY),
new NormalOperatorFactory("/", 12, Operation.DIVIDE),
new NormalOperatorFactory("%", 12, Operation.MODULO),
new NormalOperatorFactory("-", 11, Operation.SUBTRACT),
new NormalOperatorFactory("+", 11, Operation.ADD),
new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT),
new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT),
new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT),
new NormalOperatorFactory(">", 9, Operation.GREATER),
new NormalOperatorFactory("<", 9, Operation.LESS),
new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS),
new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS),
new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS),
new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS),
new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS),
new NormalOperatorFactory("===", 8, Operation.EQUALS),
new NormalOperatorFactory("&", 7, Operation.AND),
new NormalOperatorFactory("^", 6, Operation.XOR),
new NormalOperatorFactory("|", 5, Operation.OR),
new AssignmentOperatorFactory("=", 2, null),
new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY),
new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE),
new AssignmentOperatorFactory("%=", 2, Operation.MODULO),
new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT),
new AssignmentOperatorFactory("+=", 2, Operation.ADD),
new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT),
new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT),
new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT),
new AssignmentOperatorFactory("&=", 2, Operation.AND),
new AssignmentOperatorFactory("^=", 2, Operation.XOR),
new AssignmentOperatorFactory("|=", 2, Operation.OR),
new LazyAndFactory(),
new LazyOrFactory()
).stream().collect(Collectors.toMap(v -> v.token(), v -> v));
private static final List<String> operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList());
public static ParseRes<OperationNode> parsePrefix(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
Operation operation = null;
String op;
if (src.is(i + n, op = "+")) operation = Operation.POS;
else if (src.is(i + n, op = "-")) operation = Operation.NEG;
else if (src.is(i + n, op = "~")) operation = Operation.INVERSE;
else if (src.is(i + n, op = "!")) operation = Operation.NOT;
else return ParseRes.failed();
n++;
var res = JavaScript.parseExpression(src, i + n, 14);
if (res.isSuccess()) return ParseRes.res(new OperationNode(loc, operation, res.result), n + res.n);
else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op));
}
public static ParseRes<OperationNode> parseInstanceof(Source src, int i, Node prev, int precedence) {
if (precedence > 9) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var kw = Parsing.parseIdentifier(src, i + n, "instanceof");
if (!kw.isSuccess()) return kw.chainError();
n += kw.n;
var valRes = JavaScript.parseExpression(src, i + n, 10);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'.");
n += valRes.n;
return ParseRes.res(new OperationNode(loc, Operation.INSTANCEOF, prev, valRes.result), n);
}
public static ParseRes<OperationNode> parseIn(Source src, int i, Node prev, int precedence) {
if (precedence > 9) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var kw = Parsing.parseIdentifier(src, i + n, "in");
if (!kw.isSuccess()) return kw.chainError();
n += kw.n;
var valRes = JavaScript.parseExpression(src, i + n, 10);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'.");
n += valRes.n;
return ParseRes.res(new OperationNode(loc, Operation.IN, valRes.result, prev), n);
}
public static ParseRes<? extends Node> parseOperator(Source src, int i, Node prev, int precedence) {
var n = Parsing.skipEmpty(src, i);
for (var token : operatorsByLength) {
var factory = factories.get(token);
if (!src.is(i + n, token)) continue;
if (factory.precedence() < precedence) return ParseRes.failed();
n += token.length();
n += Parsing.skipEmpty(src, i + n);
var res = factory.construct(src, i + n, prev);
return res.addN(n);
}
return ParseRes.failed();
}
}

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