Compare commits

..

327 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
4f22e76d2b fix: make code java 11 compatible 2023-11-25 20:22:16 +02:00
8924e7aadc build: fix build script to exit with code when error occurs 2023-11-25 20:16:06 +02:00
1d0e31a423 Merge pull request #9 from TopchetoEU/TopchetoEU/perms-and-fs
Permissions and filesystems
2023-11-25 20:10:59 +02:00
ab56908171 fix: faulty insufficient permissions error 2023-11-25 20:09:59 +02:00
eb14bb080c fix: debugger breakpoints not registered 2023-11-25 20:09:47 +02:00
f52f47cdb4 feat: properly implement filesystems 2023-11-25 19:36:18 +02:00
567eaa8514 fix: incorrect declarations 2023-11-25 19:13:20 +02:00
2cfdd8e335 feat: add utf8 encoding and decoding 2023-11-25 19:12:56 +02:00
4b1ec671e2 fix: micro tasks not handled properly 2023-11-25 18:58:35 +02:00
b127aadcf6 fix: promise was sending macro tasks instead of micro 2023-11-25 18:58:24 +02:00
b6eaff65ca style: remove unnececeary import 2023-11-25 18:48:46 +02:00
443dc0ffa1 feat: add await function to promise 2023-11-25 18:47:51 +02:00
e107dd3507 fix: promise doesn't use microtasks in some cases 2023-11-25 18:47:36 +02:00
6af3c70fce feat: add filesystem libs 2023-11-25 18:47:01 +02:00
8b743f49d1 feat: add toString, equals and hashCode overrides to wrappers and objects 2023-11-25 18:46:13 +02:00
e1ce384815 feat: add parse function to Filename 2023-11-25 18:44:27 +02:00
86d205e521 fix: parsing files won't modify last instruction to RET, instead will just append it 2023-11-25 18:44:11 +02:00
f0ad936e5b refactor: use new iterable names 2023-11-25 18:43:43 +02:00
4a1473c5be fix: sorting with no comparator now works 2023-11-25 18:42:54 +02:00
4111dbf5c4 fix: functions now print their name when stringified 2023-11-25 18:41:47 +02:00
1666682dc2 refactor: get rid of data parent hierarchy 2023-11-25 18:41:18 +02:00
f2b33d0233 feat: add support for async iterators
fix: comparasions had small behavioural issue
2023-11-25 18:40:49 +02:00
f5a0b6eaf7 fix: some improvements of compiled bytecode 2023-11-25 18:38:43 +02:00
829bea755d fix: CodeFrame didnt't handle out of bounds jumps well 2023-11-25 18:38:16 +02:00
4b0dcffd13 feat: add fs declarations in lib.d.ts 2023-11-25 18:37:40 +02:00
987f8b8f00 fix: handle native errors properly-ish 2023-11-25 14:18:46 +02:00
55e3d46bc2 feat: implement memory and root fs 2023-11-25 14:18:04 +02:00
3e25068219 feat: implement bulk of fs 2023-11-25 14:17:37 +02:00
7ecb8bfabb feat: implement permissions 2023-11-25 14:16:02 +02:00
488deea164 fix: improve performance of typescript by caching separate declarations 2023-11-14 09:24:39 +02:00
ed08041335 fix: internal error when trying to use key of "undefined" 2023-11-14 09:24:00 +02:00
0a4149ba81 fix: remove double space in "Uncaught ..." 2023-11-14 09:23:15 +02:00
30f5d619c3 fix: errors now have the right prototype and name 2023-11-14 09:22:56 +02:00
e7dbe91374 refactor: clean up protocol.json 2023-11-13 20:06:07 +02:00
455f5a613e feat: implement simple permission matching 2023-11-13 19:09:33 +02:00
1eeac3ae97 fix: replace templates in Metadata class with placeholder data 2023-11-13 19:08:56 +02:00
1acd78e119 refactor: clean up Main class 2023-11-13 18:56:59 +02:00
df9932874d feat: remove unnececeary @NativeInit directives 2023-11-06 14:03:15 +02:00
b47d1a7576 refactor: remove StackData and Data usage 2023-11-06 13:53:36 +02:00
fdfa8d7713 refactor: some minor fixes, rewrite README example 2023-11-06 10:55:38 +02:00
f5d1287948 fix: some annoying bugs, as well as splice 2023-11-06 00:55:30 +02:00
15f4278cb1 fix: build with java 17 2023-11-05 21:00:39 +02:00
df8465cb49 Merge pull request #8 from TopchetoEU/TopchetoEU/tests
Integrate typescript
2023-11-05 20:32:42 +02:00
e3104c223c fix: remove some unnececeary logs 2023-11-05 20:29:21 +02:00
1d0bae3de8 feat: include typescript code in source code 2023-11-05 20:27:23 +02:00
b66acd3089 fix: several more fixes 2023-11-05 19:44:44 +02:00
e326847287 feat: send value stack to debug client 2023-11-05 19:44:35 +02:00
26591d6631 fix: lazy operators incorrectly pop values from stack 2023-11-05 19:44:08 +02:00
af31b1ab79 fix: several small fixes 2023-11-05 19:43:53 +02:00
f885d4349f refactor: fix some bad code >:( 2023-11-05 19:43:28 +02:00
d57044acb7 fix: several bug fixes to help with typescript support 2023-11-05 12:44:29 +02:00
7df4e3b03f fix: oops 2023-11-04 11:43:57 +02:00
ed1009ab69 refactor: some code restructuring in the debugging 2023-11-04 11:42:06 +02:00
f856cdf37e fix: messages larger than 64KB are now fragmented properly 2023-11-04 11:41:31 +02:00
4f82574b8c fix: various small behavioural issues
fix: pesky try-catch logic
2023-11-04 11:40:50 +02:00
0ae24148d8 feat: write some tests 2023-11-04 11:38:48 +02:00
ac128d17f4 feat: implement Array.reduce
fix: native functions are now named
2023-11-04 11:38:29 +02:00
6508f15bb0 refactor: remove typescript source code from repo (for now) 2023-11-04 11:37:13 +02:00
69f93b4f87 refactor: make filenames more consistent 2023-11-04 11:36:36 +02:00
b675411925 fix: a lot of minor bugs 2023-10-29 23:47:48 +02:00
d1e93c2088 Merge branch 'master' of https://github.com/TopchetoEU/java-jscript 2023-10-29 12:33:18 +02:00
942db54546 feat: improve vscode debugging compatibility 2023-10-29 12:30:43 +02:00
d20df66982 feat: improve vscode debugging compatibility 2023-10-29 12:25:33 +02:00
290 changed files with 14110 additions and 16277 deletions

1
.gitattributes vendored Normal file
View File

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

View File

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

35
.gitignore vendored
View File

@@ -1,11 +1,24 @@
.vscode
.gradle
.ignore
/out
/build
/bin
/dst
/*.js
!/build.js
/dead-code
/Metadata.java
*
!/src
!/src/**/*
!/doc
!/doc/**/*
!/tests
!/tests/**/*
!/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/LICENSE
!/README.md
!/settings.gradle
!/build.gradle
!/gradle.properties
!/gradle
!/gradle/wrapper
!/gradle/wrapper/gradle-wrapper.properties

View File

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

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,81 +0,0 @@
const { spawn } = require('child_process');
const fs = require('fs/promises');
const pt = require('path');
const { argv } = require('process');
const conf = {
name: "java-jscript",
author: "TopchetoEU",
javahome: "",
version: argv[3]
};
console.log(conf)
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
async function* find(src, dst, wildcard) {
const stat = await fs.stat(src);
if (stat.isDirectory()) {
for (const el of await fs.readdir(src)) {
for await (const res of find(pt.join(src, el), dst ? pt.join(dst, el) : undefined, wildcard)) yield res;
}
}
else if (stat.isFile() && wildcard(src)) yield dst ? { src, dst } : src;
}
async function copy(src, dst, wildcard) {
const promises = [];
for await (const el of find(src, dst, wildcard)) {
promises.push((async () => {
await fs.mkdir(pt.dirname(el.dst), { recursive: true });
await fs.copyFile(el.src, el.dst);
})());
}
await Promise.all(promises);
}
function run(cmd, ...args) {
return new Promise((res, rej) => {
const proc = spawn(cmd, args, { stdio: 'inherit' });
proc.once('exit', code => {
if (code === 0) res(code);
else rej(new Error(`Process ${cmd} exited with code ${code}.`));
});
})
}
async function compileJava() {
try {
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
.replace('${VERSION}', conf.version)
.replace('${NAME}', conf.name)
.replace('${AUTHOR}', conf.author)
);
const args = ['--release', '11', ];
if (argv[2] === 'debug') args.push('-g');
args.push('-d', 'dst/classes', 'Metadata.java');
for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path);
await run(conf.javahome + 'javac', ...args);
}
finally {
await fs.rm('Metadata.java');
}
}
(async () => {
try {
try { await fs.rm('dst', { recursive: true }); } catch {}
await copy('src', 'dst/classes', v => !v.endsWith('.java'));
await compileJava();
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
}
catch (e) {
if (argv[2] === 'debug') throw e;
else console.log(e.toString());
}
})();

4
gradle.properties Normal file
View File

@@ -0,0 +1,4 @@
project_group = me.topchetoeu
project_name = jscript
project_version = 0.9.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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,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

@@ -0,0 +1,29 @@
package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.json.JSON;
public class Metadata {
private static final String VERSION;
private static final String AUTHOR;
private static final String NAME;
static {
var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map();
VERSION = data.string("version");
AUTHOR = data.string("author");
NAME = data.string("name");
}
public static String version() {
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
else return VERSION;
}
public static String author() {
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
else return AUTHOR;
}
public static String name() {
if (NAME.equals("$" + "{NAME}")) return "some-product";
else return NAME;
}
}

View File

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

View File

@@ -0,0 +1,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,13 +35,25 @@ public class Filename {
return true;
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
this.protocol = protocol;
this.path = path;
}
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
// public static Path normalize(String path) {
// // 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);
}
}

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