Compare commits

...

271 Commits

Author SHA1 Message Date
45292990b1
some thesis fixes 2025-01-29 14:12:57 +02:00
2dcfff689a
add page numbers to thesis 2025-01-28 14:33:34 +02:00
2619e50e9b
fix typos in thesis 2025-01-28 13:25:09 +02:00
4bfda6b0a1
bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m38s
2025-01-28 13:11:29 +02:00
58d6110e1d
fix: stack overflow!! 2025-01-28 13:10:58 +02:00
120e59577d
bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m45s
2025-01-28 12:53:14 +02:00
efe123b658
wrong build files 2025-01-28 12:52:53 +02:00
b190367681
add thesis text 2025-01-26 18:07:13 +02:00
e601749866
refactor: make Value interface (again?) 2025-01-24 23:04:05 +02:00
1670b64aaf
bump
Some checks failed
tagged-release / Tagged Release (push) Failing after 3m54s
2025-01-24 22:48:34 +02:00
1548938537
small fixes
Some checks failed
tagged-release / Tagged Release (push) Has been cancelled
2025-01-24 22:46:51 +02:00
e14d85e7a8
whitespaces 2025-01-24 22:45:14 +02:00
4352550ae9
move debugging to lib 2025-01-24 22:42:33 +02:00
3c4d05abd4
restructuring of stdlibs 2025-01-24 22:37:52 +02:00
f16d088646
bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m44s
2025-01-24 05:57:33 +02:00
8b1c2a5e4e
separate more stuff into lib project 2025-01-24 05:57:15 +02:00
ee8268b144
fix: for-in not managing stack correctly 2025-01-24 05:47:44 +02:00
fffeac9bac
fix: name was always 'name'
All checks were successful
tagged-release / Tagged Release (push) Successful in 4m14s
2025-01-24 04:18:35 +02:00
166e9c0470
bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 3m56s
2025-01-24 03:26:14 +02:00
6125772038
add package uploading 2025-01-24 02:45:26 +02:00
1e982cd2ef
some minor restructuring 2025-01-24 00:22:15 +02:00
4389d115b6
use only babel 2025-01-24 00:22:03 +02:00
208444381e
move FunctionMap logic away from core 2025-01-24 00:21:51 +02:00
eff076f6fe
bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 3m56s
2025-01-22 20:34:46 +02:00
24a4a01d8e
fix warnings 2025-01-22 20:34:36 +02:00
b9a397a7b9
specify babel plugins separately 2025-01-22 20:34:31 +02:00
40f6cfe616
fix: make Function.compile do less stuff 2025-01-22 20:34:00 +02:00
f712fb09ae
refactor: merge TYPEOF instruction into OPERATION 2025-01-22 20:32:55 +02:00
6355a48c6b
fix: forEach is cringe 2025-01-22 20:21:32 +02:00
343684f9ce
move more instructions as intrinsics 2025-01-22 03:57:32 +02:00
582753440b
fix: remove unneeded LOAD_REGEX instruction 2025-01-22 01:54:29 +02:00
01e86b5e70 Merge pull request 'Write some tests' (#33) from topchetoeu/tests into master
Reviewed-on: #33
2025-01-15 18:23:24 +00:00
29b0c91c5d
fix: function mappings were registered incorrectly 2025-01-15 20:12:52 +02:00
961c8cefcc
make arguments null prototype, to follow spec closer 2025-01-15 19:54:12 +02:00
3b512b64eb
move buffers out of runtime 2025-01-15 19:52:53 +02:00
0118379d4e
simple func map tests 2025-01-15 19:51:35 +02:00
1be1cded9f
delete placeholder tests 2025-01-15 19:51:09 +02:00
c0b23d50e5
make common project classes hierarchy flat 2025-01-12 04:31:32 +02:00
cacffd01e8
move JSON and parsing logic to compiler project, decouple JSON and parser from engine logic 2025-01-12 04:27:43 +02:00
a115843351
parseRes tests 2025-01-12 03:23:35 +02:00
3623842827
sanitize Source.loc argument 2025-01-12 03:10:44 +02:00
24d0cb73b6
don't use JSON for parsing metadata 2025-01-11 15:21:14 +02:00
7a13b032f8
add environment tests 2025-01-11 14:19:19 +02:00
5a154c8a69
filename tests 2025-01-11 13:43:02 +02:00
ae77e3b55e
location tests 2025-01-11 13:42:53 +02:00
c8a89849ee
clearing up README 2025-01-10 04:53:11 +02:00
d563fc4919
build: split up into multiple projects, use kotlin DLS
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m23s
2025-01-10 04:14:40 +02:00
9668bccef1
fix: circular dependency on metadata when parsing
All checks were successful
tagged-release / Tagged Release (push) Successful in 2m19s
2025-01-10 00:53:29 +02:00
1d50ff14c5
bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 2m34s
2025-01-10 00:35:04 +02:00
a6c458cb23
rename project from jscript to j2s 2025-01-10 00:34:29 +02:00
31e2e95bc8
fix: prevent rollup from optimizing "void 0" to "undefined"
All checks were successful
tagged-release / Tagged Release (push) Successful in 2m29s
2025-01-09 00:13:14 +02:00
98dde69751
revert to coffeescript transpiler 2025-01-09 00:12:13 +02:00
ec1edb981e
fix: register sources before the next compiler gets invoked 2025-01-09 00:11:57 +02:00
36f9839485
fix: get rid of readonly for the Location type
(only caused headaches)
2025-01-09 00:10:15 +02:00
22f36267c0
feat: add prettier printing for arrays in debugger 2025-01-09 00:09:43 +02:00
f8b9776f28
fix: for-in loop doesn't declare its binding when it has to 2025-01-09 00:09:18 +02:00
12cff84666
fix: String.lastIndexOf's offset argument must default to the string's length 2025-01-09 00:08:14 +02:00
7058a689a2
fix: Object.defineProperty passes flags wrongly 2025-01-09 00:07:51 +02:00
fde8b42e36
fix: wrong return value from array.push and array.unshift 2025-01-09 00:07:17 +02:00
4ea14ca1f5
build: enable minification 2025-01-06 17:06:10 +02:00
f6ce261485
fix: add custom global functions to ts decls 2025-01-06 17:05:59 +02:00
51b347e0d7
fix: more stable engine exit 2025-01-06 17:05:37 +02:00
0b6484e0b4
fuck
All checks were successful
tagged-release / Tagged Release (push) Successful in 2m41s
2025-01-06 14:53:27 +02:00
07e0d0ba3b
fix script; bump version
Some checks failed
tagged-release / Tagged Release (push) Failing after 3m29s
2025-01-06 14:26:56 +02:00
93cae33bb0
fix: fix up script
Some checks failed
tagged-release / Tagged Release (push) Failing after 4m50s
2025-01-06 14:13:58 +02:00
b1e0db627c
fix typings so they are usable for building the project itself
Some checks failed
tagged-release / Tagged Release (push) Failing after 3m24s
2025-01-06 14:02:07 +02:00
4fd05e9e6f
fix up build scripts
Some checks failed
tagged-release / Tagged Release (push) Failing after 3m48s
2025-01-06 13:31:17 +02:00
de93adde8f
fix: some source mapping fixes 2025-01-06 13:24:58 +02:00
5c68c1717c
feat: implement some typed arrays 2025-01-06 13:24:44 +02:00
7883af7fff
fix: some array function fixes 2025-01-06 13:24:09 +02:00
3d5be60fc7
feat: honor passed set in toReadableLines 2025-01-06 13:22:58 +02:00
ba0b4e06ad
fix: some debugger issues 2025-01-06 13:22:26 +02:00
58e3546459
implement __proto__ field for all objects 2025-01-06 13:22:06 +02:00
57097e46ca
reorganize libs 2025-01-06 13:20:51 +02:00
28e72503a6 Update .github/workflows/tagged-release.yml
Some checks failed
tagged-release / Tagged Release (push) Failing after 2m14s
2025-01-01 22:33:57 +00:00
d0a0796e14 Merge pull request 'topchetoeu-bump-0-10' (#32) from topchetoeu-bump-0-10 into master
Some checks failed
tagged-release / Tagged Release (push) Failing after 11m3s
Reviewed-on: #32
2025-01-01 22:20:16 +00:00
0de54e5505 Update gradle.properties 2025-01-01 22:19:56 +00:00
96c9d29a6a Update .github/workflows/tagged-release.yml 2025-01-01 22:19:36 +00:00
37dc844cc4 fix: use typescript instead 2025-01-01 22:16:01 +00:00
b97e4bf163
improve: UserValue API 2024-12-28 13:20:04 +02:00
9ce0504948
fix: debugger concurrency issues 2024-12-28 13:19:53 +02:00
4c53689d9c
feat: some behavioral fixes 2024-12-28 13:19:35 +02:00
6c8c329992
feat: improve transpiler infrastructure 2024-12-28 13:19:02 +02:00
74f08b7483
small stdlib improvements
Some checks failed
tagged-release / Tagged Release (push) Failing after 7m8s
2024-12-28 13:17:45 +02:00
2bf920c681
remove old soruce map primordials and add next tick scheduler 2024-12-27 19:18:41 +02:00
1ac68425af
feat: implement basic promises 2024-12-27 19:18:18 +02:00
7423e3f283
feat: more sophisticated stdlib definitions 2024-12-27 19:16:55 +02:00
afe4542682
fix: concurrency issues with debug server 2024-12-27 19:16:26 +02:00
c971fde0e2
feat: implement binary number literals 2024-12-27 19:15:20 +02:00
8a34db833c
refactor: implement source mapping in javascript, instead of java 2024-12-27 19:15:09 +02:00
398d88c0a5
add more functions in stdlib, fix some issues 2024-12-27 19:10:56 +02:00
f80266618c
fix: throw copied return syntax too closely 2024-12-27 19:09:01 +02:00
f1997e4584
fix: incorrect continue jump location in for loops 2024-12-25 03:01:49 +02:00
e93498fed5
fix typescript compiler script 2024-12-25 02:56:16 +02:00
29bea68525
feat: add back debugger from old version 2024-12-25 02:55:54 +02:00
3a6401094f
fix: don't omit stack trace elements without locations 2024-12-25 02:54:22 +02:00
a705bf296e
fix: empty file yields an error 2024-12-25 02:53:53 +02:00
c3432306f7
fix: global object can be an arbitrary value 2024-12-25 02:53:44 +02:00
d8c18ccf17
more utility 2024-12-25 02:53:24 +02:00
277f59e54a
prepare codebase for source maps 2024-12-25 02:53:11 +02:00
a1bb5bdba6
separate the call stack from the debug context 2024-12-25 02:52:10 +02:00
c699102e56
try-catch AGAIN 2024-12-25 02:51:02 +02:00
b2f5068e12
some utility methods 2024-12-25 02:50:37 +02:00
05bafc7317
improve json parsing 2024-12-25 02:48:37 +02:00
c7c31c3859
feat: implement a lot of built-ins 2024-12-25 02:48:04 +02:00
c18015b9a0
fix: wrong precedences of && and ||
(how the fuck did i get this wrong)
2024-12-13 02:33:25 +02:00
5346b8917d
refactor: add semicolons and spaces -> tabs in gradle scripts 2024-12-13 02:33:01 +02:00
2ed7959088
refacor: remove pesky console.log 2024-12-13 02:29:55 +02:00
239d0ae8d7
feat: implement a lot of stdlibs 2024-12-13 02:29:41 +02:00
6fac295b99
fix: try-catch broken (again) 2024-12-13 02:29:09 +02:00
66440a9649
fix: a plethora of loop off by one and null issues 2024-12-13 02:28:36 +02:00
274a925ff8
fix: broken labeled jumps 2024-12-13 02:27:57 +02:00
130fe17441
missed you lil fucker 2024-12-13 02:27:32 +02:00
493c54ed39
fix: symbol was falsy 2024-12-13 02:27:21 +02:00
75786f39ad
fix: access of symbols in string 2024-12-13 02:27:12 +02:00
bce8b7293c
fix: parseInt was broken 2024-12-13 02:26:34 +02:00
00aeef5321
fix: make member algorithms correct 2024-12-13 02:26:12 +02:00
ff4aa3dcfd
fix: return null when loading an inexistent resource 2024-12-11 11:53:29 +02:00
45f52ff123
fix: catch variable causing issues 2024-12-11 11:53:19 +02:00
bed4014bef
feat: implement simple env + ts loader 2024-12-11 11:53:02 +02:00
17406c6b81
feat: create build process for environment 2024-12-11 11:51:03 +02:00
3abdd8d3c9
feat: add constructor target support 2024-12-10 15:37:39 +02:00
a329f615cf
fix: "e1" literal read as a number exponent 2024-12-10 01:21:44 +02:00
058d20b27f
feat: dead simple Map and RegExp implementations 2024-12-10 01:11:07 +02:00
ea158c1e60
fix: converts symbol key to string when assigning function to member 2024-12-10 01:10:43 +02:00
4e18c76bb1
some toStrings 2024-12-10 01:10:14 +02:00
8d7939d85a
fix: wrong index calculation 2024-12-10 01:09:50 +02:00
814e0d7b7e
feat: allow to invoke compiler with variable encapsulation enabled 2024-12-10 00:44:18 +02:00
138baebacb
fix: call regex constructor correctly 2024-12-10 00:43:56 +02:00
db241919f2
fix: when capturing, must check if already in captures 2024-12-10 00:43:15 +02:00
db45eb529d
change indentation to tabs 2024-12-09 23:58:43 +02:00
caf44a50e5
Merge pull request #31 from TopchetoEU:TopchetoEU/revert-ES5
TopchetoEU/revert-ES5
2024-12-09 23:39:57 +02:00
65f9debecc
fix: use default construct method 2024-12-09 23:39:05 +02:00
3f5e1a5fd8
feat: implement user values 2024-12-09 23:38:53 +02:00
b0d8a072aa
add hashCode to primitives 2024-12-09 23:38:39 +02:00
2e8e123ec4
small parser fixes 2024-12-09 23:37:08 +02:00
54d55814af
fix: errors with out of range arguments 2024-12-09 22:16:24 +02:00
28679f44d5
fix: symbols not stringified properly 2024-12-09 22:15:51 +02:00
611be55bbb
fix: should throw engine exceptions, not java exceptions 2024-12-09 22:15:38 +02:00
4992d0211b
fix: nasty issues with compilation 2024-12-09 22:15:15 +02:00
ba7505e148
fix: globalThis and for-in not parsed 2024-11-24 12:49:31 +02:00
3c13799c2f
feat: make function logging configurable 2024-11-24 12:49:04 +02:00
5c2fd00bfb
fix: add location data for LOAD_FUNCs 2024-11-24 12:48:49 +02:00
39eb6ffac5
fix: do variable inits properly 2024-11-24 12:48:30 +02:00
7f6df49fc5
fix: scope issues 2024-11-24 12:47:51 +02:00
61c5df5003
fix: gd damn it 2024-11-24 12:47:15 +02:00
41bb27e4dd
implement all changes in runtime 2024-11-23 20:15:42 +02:00
b4e7a42975
regress: remove ES6 stuff (except apply and construct constraints) from funcs 2024-11-23 20:11:57 +02:00
92fb0dbbfd
regress: simplify invoke model 2024-11-23 20:11:12 +02:00
fe8f65faf5
some final stuff in parsing 2024-11-23 20:10:47 +02:00
54fe16393a
regress: remove infrastructure for super references 2024-11-23 20:10:11 +02:00
14e4aade35
regress: remove infrastructure needed for ES6 stuff, simplify loops 2024-11-23 20:09:29 +02:00
754648fbf6
regress: remove ES6 instructions 2024-11-23 20:08:01 +02:00
20f2c3c5e9
regress: remove ES6 stuff from members 2024-11-23 20:07:10 +02:00
c5067cbfdd
regress: remove ES6 variables and simplify scope 2024-11-23 20:06:24 +02:00
5644966dd7
regress: remove ES6 nodes 2024-11-23 20:06:09 +02:00
50eb204da7
fix: remove unnecessary reference from core to compiler 2024-11-23 20:04:19 +02:00
45308e6d65
refactor: remove periods from ends of error msgs 2024-11-23 20:04:03 +02:00
0ebf189c95
fix: remove multi-key bullcrap 2024-11-23 20:01:00 +02:00
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
417 changed files with 23291 additions and 17731 deletions

View File

@ -11,27 +11,28 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '11'
java-version: '17'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main
with:
branch: 'master'
owner: 'TopchetoEU'
repository: 'java-jscript'
cache-disabled: true
gradle-version: "8.10"
- name: Build
run: |
cd java-jscript; gradle build
- uses: "marvinpinto/action-automatic-releases@latest"
run: gradle build
- name: Publish
run: gradle publish
env:
ACCESS_TOKEN: "${{secrets.PACKAGE_TOKEN}}"
REPO_URL: "${{github.server_url}}/api/packages/${{github.repository_owner}}/maven"
- name: Create release
uses: "https://gitea.com/actions/gitea-release-action@main"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
files: |
java-jscript/LICENSE
java-jscript/build/libs/*.jar
LICENSE
build/libs/*.jar

55
.gitignore vendored
View File

@ -1,24 +1,53 @@
*
/*
!/src
!/src/**/*
/buildSrc/*
!/buildSrc
!/buildSrc
!/buildSrc/src
!/buildSrc/build.gradle.kts
/common/*
!/common
!/common/src
!/common/build.gradle.kts
/runtime/*
!/runtime
!/runtime/src
!/runtime/build.gradle.kts
/compilation/*
!/compilation
!/compilation/src
!/compilation/build.gradle.kts
/repl/*
!/repl
!/repl/src
!/repl/build.gradle.kts
/lib/*
!/lib
!/lib/src
!/lib/build.gradle.kts
!/lib/package.json
!/lib/tsconfig.json
!/lib/rollup.config.js
# !/src
!/doc
!/doc/**/*
!/tests
!/tests/**/*
!/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/LICENSE
!/README.md
!/settings.gradle
!/build.gradle
!/settings.gradle.kts
!/build.gradle.kts
!/gradle.properties
!/gradle
!/gradle/wrapper
!/gradle/wrapper/gradle-wrapper.properties
!/package.json
!/rollup.config.js
!/tsconfig.json

View File

@ -1,25 +1,77 @@
# JScript
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
# J2S (Java-JavaScript or Java to JavaScript)
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
J2S 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. A small REPL (`me.topchetoeu.j2s.repl.SimpleRepl`) library with an included simple debugger (`me.topchetoeu.j2s.repl.debug.SimpleDebugger`). These are more or less reference implementations. In the future, most of the primordials logic of `SimpleRepl` will be moved in the "lib" project, but for now, it will stay there.
## Example
## How to use?
The following is going to execute a simple javascript statement:
Since this is mostly targeted for integration into other applications, here, examples for invoking JS code from Java will be shown. In the future, a more comprehensive wiki will be made.
### Setting up an event loop
First of all, you will want to create an event loop. While not required, 99% of the times you will want to have one.
```java
var engine = new Engine();
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
var env = Internals.apply(new Environment());
// Queue code to load internal libraries and start engine
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);
// Get our result
System.out.println(awaitable.await());
var thread = engine.start();
```
Hooray! Now you have an event loop. The thread that was automatically created is a daemon thread, so it will harakiri when the rest of the application ends. If you don't want to use the built-in thread, you can instead run it with `engine.run(untilEmpty)`. If you pass true (which you most likely need), the event loop will be run blocking-ly until it is empty. Otherwise, it will be run forever.
### Creating the execution environment
This is one of the other crucial parts of J2S's architecture - the environment. It contains the global scope, a reference to the event loop, the global scope, the debugger, source mappings and a lot more. To run JS code, you must create an environment:
```java
var env = Environment.empty();
env.add(EventLoop.KEY, engine); // Gives
env.add(DebugContext.KEY, new DebugContext()); // For source mappings
```
As you can see, the environment is nothing more than a simple map of objects that may be of interest to the JS code. Although you can do much more with the environment, we will leave it at that.
### Registering the compiler
Since the compiler is a part of the runtime, you need to register it in the environment. You can use the following boilerplate, although a nicer API will be exposed later on:
```java
env.add(Compiler.KEY, (_env, filename, raw, mapper) -> {
try {
// Invokes the compiler. Will return a CompilerResult, which, along other things,
// gives us all the compiled function bodies (aka function scaffoldings, that can be used to construct a function value)
var res = JavaScript.compile(env, filename, raw, true);
var body = res.body();
// We'll register the source and function source mappings for debugging
DebugContext.get(env).onSource(filename, raw);
for (var el : res.all()) {
DebugContext.get(env).onFunctionLoad(el.body(), el.map(mapper));
}
// Finally, we will construct the function
// Few things to note: we need to pass the environment, the name of the function (the filename),
// and the last thing: the captures. Since we are compiling the main function, we don't have
// any captures, so we pass an empty array
return new CodeFunction(env, filename.toString(), body, new Value[0][]);
}
catch (SyntaxException e) {
// Convert the exception to an engine exception
var res = EngineException.ofSyntax(e.msg);
// Add the location of the error to its stack trace
res.add(env, e.loc.filename() + "", e.loc);
throw res;
}
});
```
### Evaluating a piece of code on the event loop
This is what you really want to do: run code! You can do that in the following way:
```java
var result = engine.pushMsg(false, env, Filename.parse("my-app://test.js"), "return 10 + 5 / 3;", Value.UNDEFINED).get();
System.out.println(result.toReadable(env));
```
If all goes well, we will get "11.666..." as a result.

View File

@ -1,32 +0,0 @@
plugins {
id "application"
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
toolchain.languageVersion = JavaLanguageVersion.of(11)
withSourcesJar()
}
jar {
manifest.attributes["Main-class"] = project.main_class
}
sourceSets {
main.java.srcDirs = [ "src/java" ]
main.resources.srcDirs = [ "src/assets" ]
}
processResources {
filesMatching "metadata.json", {
expand(
version: project.project_version,
name: project.project_name
)
}
}
base.archivesName = project.project_name
version = project.project_version
group = project.project_group

23
build.gradle.kts Normal file
View File

@ -0,0 +1,23 @@
plugins {
id("base");
}
version = properties["project_version"].toString();
group = properties["project_group"].toString();
description = "ES5-compliant JavaScript interpreter";
tasks.wrapper {
gradleVersion = "8.10";
}
tasks.build {
for (proj in subprojects) {
dependsOn(proj.tasks.named("build"));
doLast {
copy {
from(proj.buildDir.resolve("libs"));
into("$buildDir/libs");
}
}
}
}

View File

@ -0,0 +1,7 @@
repositories {
mavenCentral();
}
plugins {
`kotlin-dsl`
}

View File

@ -0,0 +1,15 @@
plugins {
id("common");
}
java {
sourceCompatibility = JavaVersion.VERSION_17;
targetCompatibility = JavaVersion.VERSION_17;
toolchain {
languageVersion = JavaLanguageVersion.of(17);
}
withJavadocJar();
withSourcesJar();
}

View File

@ -0,0 +1,47 @@
plugins {
id("java");
id("maven-publish");
}
version = rootProject.version;
group = rootProject.group;
base.archivesName = "${properties["project_name"]}-${project.name}";
tasks.named<JavaCompile>("compileJava") {
options.release.set(8);
}
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");
}
publishing {
repositories {
maven {
name = "Gitea";
url = uri(System.getenv("REPO_URL") ?: "");
credentials(HttpHeaderCredentials::class) {
name = "Authorization";
value = "token ${System.getenv("ACCESS_TOKEN")}";
}
authentication {
create<HttpHeaderAuthentication>("header");
}
}
}
publications {
create<MavenPublication>("maven") {
from(components["java"]);
}
}
}

18
common/build.gradle.kts Normal file
View File

@ -0,0 +1,18 @@
plugins {
id("common-java");
}
description = "A collection of utils and structures for the rest of the project";
tasks.processResources {
filesMatching("metadata", {
expand(
"version" to properties["project_version"],
"name" to properties["project_name"],
);
});
}
tasks.test {
useJUnitPlatform();
}

View File

@ -0,0 +1,100 @@
package me.topchetoeu.j2s.common;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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<>();
@SuppressWarnings("unchecked")
public <T> T get(Key<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 (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) {
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) {
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);
hidden.add((Key<Object>)key);
return this;
}
public <T> T init(Key<T> key, T val) {
if (!has(key)) {
this.add(key, val);
return val;
}
else return get(key);
}
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();
}
}

View File

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

View File

@ -0,0 +1,16 @@
package me.topchetoeu.j2s.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,62 @@
package me.topchetoeu.j2s.common;
import java.util.Arrays;
import java.util.regex.Pattern;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
public interface FunctionMap {
public static final FunctionMap EMPTY = new FunctionMap() {
@Override public Location first() {
return null;
}
@Override public Location last() {
return null;
}
@Override public Location toLocation(int i, boolean approximate) {
return null;
}
@Override public BreakpointType getBreakpoint(int i) {
return BreakpointType.NONE;
}
@Override public Iterable<Location> breakpoints(Location start, Location end) {
return Arrays.asList();
}
@Override public Location correctBreakpoint(Location i) {
return null;
}
@Override public Iterable<Location> correctBreakpoint(Pattern filename, int line, int column) {
return Arrays.asList();
}
@Override public String[] localNames() {
return null;
}
@Override public String[] capturableNames() {
return null;
}
@Override public String[] captureNames() {
return null;
}
};
Location first();
Location last();
Location toLocation(int i, boolean approximate);
default Location toLocation(int i) {
return toLocation(i, false);
}
BreakpointType getBreakpoint(int i);
Location correctBreakpoint(Location i);
Iterable<Location> correctBreakpoint(Pattern filename, int line, int column);
Iterable<Location> breakpoints(Location start, Location end);
String[] localNames();
String[] capturableNames();
String[] captureNames();
}

View File

@ -0,0 +1,348 @@
package me.topchetoeu.j2s.common;
import java.util.HashMap;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
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),
CALL_NEW(0x12),
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_GLOB(0x38),
LOAD_INTRINSICS(0x39),
LOAD_ARG(0x3A),
LOAD_ARGS_N(0x3B),
LOAD_ARGS(0x3C),
LOAD_CALLED(0x3D),
LOAD_THIS(0x3E),
LOAD_ERROR(0x3F),
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),
GLOB_GET(0x50),
GLOB_SET(0x51),
GLOB_DEF(0x52),
OPERATION(0x56);
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 {
/**
* A debugger should never stop at such instruction, unless a breakpoint has been set on it
*/
NONE,
/**
* Debuggers should pause at instructions marked with this breakpoint type
* after any step command
*/
STEP_OVER,
/**
* Debuggers should pause at instructions marked with this breakpoint type
* only after a step-in command
*/
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];
}
private Instruction(Type type, Object ...params) {
this.type = type;
this.params = params;
}
/**
* Signals the start of a protected context
* @param catchStart The point to witch to jump if an error has been caught
* @param finallyStart The point to witch to jump after either the try or catch bodies have exited
* @param end The point to which to jump after exiting the whole protected context
*/
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
}
/**
* Signifies that the current protected section (try, catch or finally) has ended
*/
public static Instruction tryEnd() {
return new Instruction(Type.TRY_END);
}
/**
* Throws the top stack value
*/
public static Instruction throwInstr() {
return new Instruction(Type.THROW);
}
/**
* Converts the given exception to a runtime syntax error and throws it
*/
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
}
/**
* Converts the given exception to a runtime syntax error and throws it
*/
public static Instruction throwSyntax(String err) {
return new Instruction(Type.THROW_SYNTAX, err);
}
/**
* Converts the given exception to a runtime syntax error and throws it
*/
public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
}
/**
* Performs a JS object property deletion.
* Operands:
* 1. Object to manipulate
* 2. Key to delete
*/
public static Instruction delete() {
return new Instruction(Type.DELETE);
}
/**
* Returns the top stack value
*/
public static Instruction ret() {
return new Instruction(Type.RETURN);
}
/**
* A special NOP instruction telling any debugger to pause
*/
public static Instruction debug() {
return new Instruction(Type.NOP, "debug");
}
/**
* Does nothing. May be used for metadata or implementation-specific instructions that don't alter the behavior
*/
public static Instruction nop(Object ...params) {
return new Instruction(Type.NOP, params);
}
public static Instruction call(int argn, boolean hasSelf) {
return new Instruction(Type.CALL, argn, hasSelf);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn);
}
public static Instruction jmp(int offset) {
return new Instruction(Type.JMP, offset);
}
public static Instruction jmpIf(int offset) {
return new Instruction(Type.JMP_IF, offset);
}
public static Instruction jmpIfNot(int offset) {
return new Instruction(Type.JMP_IFN, offset);
}
public static 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);
}
/**
* Loads the given argument
* @param i The index of the argument to load. If -1, will get the index from the stack instead
*/
public static Instruction loadArg(int i) {
return new Instruction(Type.LOAD_ARG, i);
}
/**
* Pushes the amount of arguments to the stack
*/
public static Instruction loadArgsN() {
return new Instruction(Type.LOAD_ARGS_N);
}
/**
* Pushes the arguments object to the stack
*/
public static Instruction loadArgs() {
return new Instruction(Type.LOAD_ARGS);
}
/**
* Loads a reference to the function being called
*/
public static Instruction loadCalled() {
return new Instruction(Type.LOAD_CALLED);
}
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);
}
// TODO: make this capturing a concern of the compiler
public static Instruction loadFunc(int id, String name, int[] captures) {
var args = new Object[2 + captures.length];
args[0] = id;
args[1] = name;
for (var i = 0; i < captures.length; i++) args[i + 2] = 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, boolean keep) {
return new Instruction(Type.STORE_VAR, i, keep);
}
public static Instruction storeMember() {
return new Instruction(Type.STORE_MEMBER, false);
}
public static Instruction storeMember(boolean keep) {
return new Instruction(Type.STORE_MEMBER, keep);
}
public static Instruction 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 operation(Operation op) {
return new Instruction(Type.OPERATION, op);
}
@Override public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
}

View File

@ -0,0 +1,3 @@
package me.topchetoeu.j2s.common;
public final class Key<T> { }

View File

@ -0,0 +1,124 @@
package me.topchetoeu.j2s.common;
import java.util.ArrayList;
import java.util.Objects;
public abstract class Location implements Comparable<Location> {
public static final Location INTERNAL = Location.of(new Filename(Metadata.name(), "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() {
return nextLine(1);
}
public final Location nextLine(int offset) {
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() + offset; }
};
}
@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;
var filenameA = filename();
var filenameB = other.filename();
if (filenameB == null && filenameA == null) a = 0;
else if (filenameA == null) a = -1;
else if (filenameB == null) a = 1;
else a = filenameA.toString().compareTo(filenameB.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 i1 = raw.lastIndexOf(':');
if (i1 < 0) return Location.of(Filename.parse(raw), -1, -1);
var i0 = raw.substring(0, i1).lastIndexOf(':', i1);
if (i0 < 0) {
try {
return Location.of(null, Integer.parseInt(raw.substring(0, i1)) - 1, Integer.parseInt(raw.substring(i1 + 1)) - 1);
}
catch (NumberFormatException e) {}
try {
return Location.of(Filename.parse(raw.substring(0, i1)), Integer.parseInt(raw.substring(i1 + 1)) - 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(0, i1)), start - 1, -1);
}
return Location.of(Filename.parse(raw.substring(0, i0)), line - 1, start - 1);
}
}

View File

@ -0,0 +1,58 @@
package me.topchetoeu.j2s.common;
public class Metadata {
private static String VERSION;
private static String AUTHOR;
private static String NAME;
static {
var raw = Reading.resourceToString("metadata").split("\n");
var line = 0;
var file = "internal://metadata";
for (var el : raw) {
line++;
el = el.trim();
if (el.startsWith("#")) continue;
if (el.isEmpty()) continue;
var i = el.indexOf(":");
if (i < 0) throw new RuntimeException(String.format("%s:%s: Expected colon on line", file, line));
var name = el.substring(0, i).trim();
var value = el.substring(i + 1).trim();
switch (name) {
case "version":
VERSION = value;
break;
case "author":
AUTHOR = value;
break;
case "name":
NAME = value;
break;
default:
throw new RuntimeException(String.format("%s:%s: Unexpected metadata key '%s'", file, line, name));
}
}
if (VERSION == null) throw new RuntimeException(String.format("%s:%s: No version specified", file, line));
if (AUTHOR == null) throw new RuntimeException(String.format("%s:%s: No author specified", file, line));
if (NAME == null) throw new RuntimeException(String.format("%s:%s: No name specified", file, line));
}
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,55 @@
package me.topchetoeu.j2s.common;
import java.util.HashMap;
public enum Operation {
TYPEOF(0x10, 1),
INSTANCEOF(0x11, 2),
IN(0x12, 2),
MULTIPLY(0x20, 2),
DIVIDE(0x21, 2),
MODULO(0x22, 2),
ADD(0x23, 2),
SUBTRACT(0x24, 2),
USHIFT_RIGHT(0x30, 2),
SHIFT_RIGHT(0x31, 2),
SHIFT_LEFT(0x32, 2),
GREATER(0x40, 2),
LESS(0x41, 2),
GREATER_EQUALS(0x42, 2),
LESS_EQUALS(0x43, 2),
LOOSE_EQUALS(0x44, 2),
LOOSE_NOT_EQUALS(0x45, 2),
EQUALS(0x46, 2),
NOT_EQUALS(0x47, 2),
AND(0x50, 2),
OR(0x51, 2),
XOR(0x52, 2),
NEG(0x60, 1),
POS(0x61, 1),
NOT(0x62, 1),
INVERSE(0x63, 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,92 @@
package me.topchetoeu.j2s.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) {
if (in == null) return null;
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) {
var bytes = streamToBytes(in);
if (bytes == null) return null;
else return new String(bytes);
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/" + name);
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));
}
public static byte[] resourceToBytes(String name) {
return streamToBytes(resourceToStream(name));
}
}

View File

@ -0,0 +1,123 @@
package me.topchetoeu.j2s.common;
import java.math.BigDecimal;
public class StringifyUtils {
public static String quoteString(String raw) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : raw.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();
}
public static String quoteNumber(Double num) {
if (num == Double.NEGATIVE_INFINITY) return "-Infinity";
if (num == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(num)) return "NaN";
return BigDecimal.valueOf(num).stripTrailingZeros().toPlainString();
}
private 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 Double unqoteNumber(String src) {
var i = 0;
double whole = 0;
double fract = 0;
long exponent = 0;
boolean parsedAny = false;
boolean negative = false;
if (src.charAt(i) == '-') {
negative = true;
i++;
}
while (i < src.length()) {
var c = src.charAt(i);
if (c < '0' || c > '9') break;
parsedAny = true;
whole *= 10;
whole += src.charAt(i++) - '0';
}
if (i < src.length() && src.charAt(i) == '.') {
parsedAny = true;
i++;
while (i < src.length()) {
var c = src.charAt(i);
if (c < '0' || c > '9') break;
parsedAny = true;
fract += src.charAt(i++) - '0';
fract /= 10;
}
}
if (i < src.length() && (src.charAt(i) == 'e' || src.charAt(i) == 'E')) {
i++;
parsedAny = true;
boolean expNegative = false;
boolean parsedE = false;
if (i < src.length()) {
if (src.charAt(i) == '-') {
expNegative = true;
i++;
}
else if (src.charAt(i) == '+') {
i++;
}
}
while (i < src.length()) {
var c = src.charAt(i);
if (c < '0' || c > '9') break;
parsedE = true;
exponent *= 10;
if (expNegative) exponent -= src.charAt(i) - '0';
else exponent += src.charAt(i) - '0';
}
if (!parsedE) return Double.NaN;
}
if (i != src.length()) return Double.NaN;
if (!parsedAny) {
if (negative) return Double.NaN;
return 0.;
}
else if (negative) return -(whole + fract) * power(10, exponent);
else return (whole + fract) * power(10, exponent);
}
}

View File

@ -0,0 +1,12 @@
package me.topchetoeu.j2s.common;
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,3 @@
version: ${version}
name: ${name}
author: TopchetoEU

View File

@ -0,0 +1,69 @@
package me.topchetoeu.j2s.common;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.junit.jupiter.api.Test;
public class TestEnvironment {
private final Key<String> FOO = new Key<>();
private final Key<Void> MARKER = new Key<>();
@Test public void testShouldCreate() {
new Environment();
Environment.empty();
}
@Test public void testShouldNotExist() {
var env = new Environment();
assertEquals(null, env.get(FOO));
assertEquals(false, env.has(FOO));
}
@Test public void testShouldAdd() {
var env = new Environment();
env.add(FOO, "test");
assertEquals("test", env.get(FOO));
}
@Test public void testShouldGetFromParent() {
var parent = new Environment();
parent.add(FOO, "test");
var child = parent.child();
assertEquals("test", child.get(FOO));
assertEquals(true, child.has(FOO));
}
@Test public void testShouldHideParent() {
var parent = new Environment();
parent.add(FOO, "test");
var child = parent.child();
child.remove(FOO);
assertEquals(null, child.get(FOO));
assertEquals(false, child.has(FOO));
}
@Test public void testShouldAddMarker() {
var env = new Environment();
env.add(MARKER);
assertEquals(true, env.has(MARKER));
assertEquals(false, env.hasNotNull(MARKER));
}
@Test public void testShouldInitOnce() {
var env = new Environment();
assertEquals("a", env.init(FOO, "a"));
assertEquals("a", env.init(FOO, "b"));
assertEquals("a", env.get(FOO));
}
@Test public void testShouldInitOnceFrom() {
var env = new Environment();
assertEquals("a", env.initFrom(FOO, () -> "a"));
assertEquals("a", env.initFrom(FOO, () -> "b"));
assertEquals("a", env.get(FOO));
}
@Test public void testShouldWrap() {
var env = new Environment();
assertEquals(env, Environment.wrap(env));
assertInstanceOf(Environment.class, Environment.wrap(null));
}
}

View File

@ -0,0 +1,37 @@
package me.topchetoeu.j2s.common;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestFilename {
@Test public void testShouldParseFilePath() {
var filename = Filename.parse("file://hello.world");
assertEquals("file", filename.protocol);
assertEquals("hello.world", filename.path);
}
@Test public void testShouldParseNoProtocolFilename() {
var filename = Filename.parse("hello.world");
assertEquals("file", filename.protocol);
assertEquals("hello.world", filename.path);
}
@Test public void testShouldParseAdditionalSlashFilename() {
var filename = Filename.parse("test:///hello.world");
assertEquals("test", filename.protocol);
assertEquals("/hello.world", filename.path);
}
@Test public void testShouldParseOneSlashFilename() {
var filename = Filename.parse("test:/hello.world");
assertEquals("file", filename.protocol);
assertEquals("test:/hello.world", filename.path);
}
@Test public void testShouldParseMatroshkaFilename() {
var a = Filename.parse("a://b://hello.world");
assertEquals("a", a.protocol);
assertEquals("b://hello.world", a.path);
var b = Filename.parse(a.path);
assertEquals("b", b.protocol);
assertEquals("hello.world", b.path);
}
}

View File

@ -0,0 +1,159 @@
package me.topchetoeu.j2s.common;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestLocation {
@Test public void testShouldCreateLocation() {
var loc = Location.of(new Filename("file", "test.txt"), 10, 5);
assertEquals("test.txt", loc.filename().path);
assertEquals(10, loc.line());
assertEquals(5, loc.start());
}
@Test public void testShouldGetNextLineLocation() {
var loc = Location.of("test.txt:10:5");
var next = loc.nextLine();
assertEquals(new Filename("file", "test.txt"), next.filename());
assertEquals(10, next.line());
assertEquals(0, next.start());
assertEquals(new Filename("file", "test.txt"), loc.filename());
assertEquals(9, loc.line());
assertEquals(4, loc.start());
}
@Test public void testShouldGetNextNthLineLocation() {
var loc = Location.of(new Filename("file", "test.txt"), 10, 5);
var next = loc.nextLine(5);
assertEquals(15, next.line());
assertEquals(0, next.start());
assertEquals(10, loc.line());
assertEquals(5, loc.start());
}
@Test public void testShouldGetNextLocation() {
var loc = Location.of("test:10:5");
var next = loc.add(10);
assertEquals(new Filename("file", "test"), next.filename());
assertEquals(9, next.line());
assertEquals(14, next.start());
assertEquals(new Filename("file", "test"), loc.filename());
assertEquals(9, loc.line());
assertEquals(4, loc.start());
}
@Test public void testShouldParseLocation() {
var loc = Location.of("test.txt:10:5");
assertEquals(new Filename("file", "test.txt"), loc.filename());
assertEquals(9, loc.line());
assertEquals(4, loc.start());
}
@Test public void testShouldParseComplexFilenameLocation() {
var loc = Location.of("testificate://test.txt:10:5");
assertEquals(new Filename("testificate", "test.txt"), loc.filename());
assertEquals(9, loc.line());
assertEquals(4, loc.start());
}
@Test public void testShouldParseNoFilenameLocation() {
var loc = Location.of("10:5");
assertEquals(null, loc.filename());
assertEquals(9, loc.line());
assertEquals(4, loc.start());
}
@Test public void testShouldParseNoStartLocationA() {
var loc = Location.of("file://10:5");
assertEquals(new Filename("file", "10"), loc.filename());
assertEquals(4, loc.line());
assertEquals(-1, loc.start());
}
@Test public void testShouldParseNoStartLocationB() {
var loc = Location.of("file:5");
assertEquals(new Filename("file", "file"), loc.filename());
assertEquals(4, loc.line());
assertEquals(-1, loc.start());
}
@Test public void testShouldParseOnlyFilenameLocationA() {
var loc = Location.of("http://example.org/test.txt");
assertEquals(new Filename("http", "example.org/test.txt"), loc.filename());
assertEquals(-1, loc.line());
assertEquals(-1, loc.start());
}
@Test public void testShouldParseOnlyFilenameLocationB() {
var loc = Location.of("test.txt");
assertEquals(new Filename("file", "test.txt"), loc.filename());
assertEquals(-1, loc.line());
assertEquals(-1, loc.start());
}
@Test public void testShouldParseOnlyFilenameWithColonLocation() {
var loc = Location.of("my-file:bad-file");
assertEquals(new Filename("file", "my-file:bad-file"), loc.filename());
assertEquals(-1, loc.line());
assertEquals(-1, loc.start());
}
@Test public void testShouldParseOnlyFilenameWithTripleColonLocation() {
var loc = Location.of("a:my-file:bad-file");
assertEquals(new Filename("file", "a:my-file:bad-file"), loc.filename());
assertEquals(-1, loc.line());
assertEquals(-1, loc.start());
}
@Test public void testCompareEqualLoc() {
var locA = Location.of("test:10:5");
var locB = Location.of("test:10:5");
assertEquals(0, locA.compareTo(locB));
assertEquals(0, locB.compareTo(locA));
}
@Test public void testCompareNoFileLoc() {
var locA = Location.of("10:5");
var locB = Location.of("11:5");
assertEquals(-1, locA.compareTo(locB));
assertEquals(1, locB.compareTo(locA));
}
@Test public void testCompareOneNoFileLoc() {
var locA = Location.of("10:5");
var locB = Location.of("test:10:5");
assertEquals(-1, locA.compareTo(locB));
assertEquals(1, locB.compareTo(locA));
}
@Test public void testCompareDiffFileLoc() {
var locA = Location.of("a:10:5");
var locB = Location.of("b:10:5");
assertEquals(-1, locA.compareTo(locB));
assertEquals(1, locB.compareTo(locA));
}
@Test public void testCompareDiffLineLoc() {
var locA = Location.of("test:10:5");
var locB = Location.of("test:11:5");
assertEquals(-1, locA.compareTo(locB));
assertEquals(1, locB.compareTo(locA));
}
@Test public void testCompareDiffStartLoc() {
var locA = Location.of("test:10:5");
var locB = Location.of("test:10:10");
assertEquals(-1, locA.compareTo(locB));
assertEquals(1, locB.compareTo(locA));
}
@Test public void testToStringAll() {
var locA = Location.of("test:10:5");
assertEquals("file://test:10:5", locA.toString());
}
@Test public void testToStringNoFilename() {
var locA = Location.of("10:5");
assertEquals("10:5", locA.toString());
}
@Test public void testToStringNoStart() {
var locA = Location.of("file:5");
assertEquals("file://file:5", locA.toString());
}
@Test public void testToStringNoLoc() {
var locA = Location.of("file");
assertEquals("file://file", locA.toString());
}
}

View File

@ -0,0 +1,13 @@
plugins {
id("common-java");
}
description = "A compiler of EcmaScript 5 code to J2S bytecode";
tasks.test {
useJUnitPlatform();
}
dependencies {
implementation(project(":common"));
}

View File

@ -0,0 +1,219 @@
package me.topchetoeu.j2s.compilation;
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.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.FunctionMap;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
public final class CompilationFunctionMap implements FunctionMap {
public static class FunctionMapBuilder {
private Location first, last;
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;
if (first == null || first.compareTo(loc) > 0) first = loc;
if (last == null || last.compareTo(loc) < 0) last = loc;
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() {
return first;
}
public Location last() {
return last;
}
public FunctionMapBuilder map(Function<Location, Location> mapper) {
var newSourceMaps = new HashMap<Integer, Location>();
var newBreakpoints = new HashMap<Location, BreakpointType>();
for (var key : sourceMap.keySet()) {
var mapped = mapper.apply(sourceMap.get(key));
if (mapped == null) continue;
newSourceMaps.put(key, mapped);
}
for (var key : breakpoints.keySet()) {
var mapped = mapper.apply(key);
if (mapped == null) continue;
newBreakpoints.put(mapped, breakpoints.get(key));
}
sourceMap.clear();
sourceMap.putAll(newSourceMaps);
breakpoints.clear();
breakpoints.putAll(newBreakpoints);
return this;
}
public CompilationFunctionMap build(String[] localNames, String[] capturableNames, String[] captureNames) {
return new CompilationFunctionMap(sourceMap, breakpoints, first, last, localNames, capturableNames, captureNames);
}
public CompilationFunctionMap build() {
return new CompilationFunctionMap(sourceMap, breakpoints, first, last, new String[0], new String[0], new String[0]);
}
private FunctionMapBuilder() { }
}
public static final CompilationFunctionMap EMPTY = new CompilationFunctionMap();
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, capturableNames, captureNames;
public final Location first, last;
@Override public Location toLocation(int pc, boolean approximate) {
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
var res = pcToLoc.get(pc);
if (!approximate || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry();
if (entry == null) return null;
else return entry.getValue();
}
@Override public Location toLocation(int pc) {
return toLocation(pc, false);
}
@Override public BreakpointType getBreakpoint(int pc) {
return bps.getOrDefault(pc, BreakpointType.NONE);
}
@Override public Location correctBreakpoint(Location loc) {
var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
}
@Override 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;
}
@Override 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();
}
@Override public Location first() {
return first;
}
@Override public Location last() {
return last;
}
@Override public String[] capturableNames() {
return capturableNames;
}
@Override public String[] captureNames() {
return captureNames;
}
@Override public String[] localNames() {
return localNames;
}
public CompilationFunctionMap clone() {
var res = new CompilationFunctionMap(new HashMap<>(), new HashMap<>(), first, last, localNames, capturableNames, captureNames);
res.pcToLoc.putAll(this.pcToLoc);
res.bps.putAll(bps);
res.bpLocs.putAll(bpLocs);
res.pcToLoc.putAll(pcToLoc);
return res;
}
public CompilationFunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, Location first, Location last, String[] localNames, String[] capturableNames, 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;
this.capturableNames = capturableNames;
this.first = first;
this.last = last;
}
private CompilationFunctionMap() {
localNames = new String[0];
captureNames = new String[0];
capturableNames = new String[0];
first = null;
last = null;
}
public static FunctionMapBuilder builder() {
return new FunctionMapBuilder();
}
}

View File

@ -0,0 +1,168 @@
package me.topchetoeu.j2s.compilation;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.function.Function;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompilationFunctionMap.FunctionMapBuilder;
import me.topchetoeu.j2s.compilation.control.TryNode;
import me.topchetoeu.j2s.compilation.scope.FunctionScope;
import me.topchetoeu.j2s.compilation.scope.Variable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
public final class CompileResult {
public static final Key<Void> DEBUG_LOG = new Key<>();
private FunctionBody body;
public final List<Instruction> instructions;
public final List<CompileResult> children;
public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
public final FunctionMapBuilder map;
public final Environment env;
public int length;
public final FunctionScope scope;
public final Map<TryNode, Variable> catchBindings = new HashMap<>();
public int temp() {
instructions.add(null);
return instructions.size() - 1;
}
public CompileResult add(Instruction instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, instr);
return this;
}
public 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 Iterable<CompileResult> all() {
var stack = new Stack<CompileResult>();
stack.push(this);
return () -> new Iterator<CompileResult>() {
@Override public CompileResult next() {
if (stack.empty()) return null;
else {
var res = stack.pop();
for (var child : res.children) {
stack.push(child);
}
return res;
}
}
@Override public boolean hasNext() {
return !stack.empty();
}
};
}
public CompileResult addChild(FunctionNode node, CompileResult res) {
this.children.add(res);
this.childrenMap.put(node, res);
this.childrenIndices.put(node, this.children.size() - 1);
return res;
}
public Instruction[] instructions() {
return instructions.toArray(new Instruction[0]);
}
public CompilationFunctionMap map(Function<Location, Location> mapper) {
return map.map(mapper).build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public CompilationFunctionMap map() {
return map.build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public FunctionBody body() {
if (body != null) return body;
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
var instrRes = instructions();
if (env.has(DEBUG_LOG)) {
System.out.println("================= BODY =================");
System.out.println("LOCALS: " + scope.localsCount());
System.out.println("CAPTURABLES: " + scope.capturablesCount());
System.out.println("CAPTURES: " + scope.capturesCount());
for (var instr : instrRes) System.out.println(instr);
}
return body = new FunctionBody(
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren
);
}
public CompileResult subtarget() {
return new CompileResult(env, new FunctionScope(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, FunctionScope scope, int length) {
this.scope = scope;
this.instructions = new ArrayList<>();
this.children = new LinkedList<>();
this.map = CompilationFunctionMap.builder();
this.env = env;
this.length = length;
}
private CompileResult(Environment env, FunctionScope 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,112 @@
package me.topchetoeu.j2s.compilation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class CompoundNode extends Node {
public final Node[] statements;
public Location end;
@Override public void resolve(CompileResult target) {
for (var stm : statements) stm.resolve(target);
}
@Override public void compileFunctions(CompileResult target) {
for (var stm : statements) stm.compileFunctions(target);
}
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
List<Node> statements = new ArrayList<Node>();
for (var stm : this.statements) {
if (stm instanceof FunctionStatementNode func) {
func.compile(target, 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(target, false, BreakpointType.STEP_OVER);
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
}
if (!polluted && pollute) {
target.add(Instruction.pushUndefined());
}
}
public CompoundNode setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundNode(Location loc, Node ...statements) {
super(loc);
this.statements = statements;
}
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, children.toArray(new Node[0])), n);
}
else return ParseRes.res(new CompoundNode(loc, 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, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
}
}

View File

@ -0,0 +1,19 @@
package me.topchetoeu.j2s.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,125 @@
package me.topchetoeu.j2s.compilation;
import java.util.List;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.scope.FunctionScope;
import me.topchetoeu.j2s.compilation.values.VariableNode;
public abstract class FunctionNode extends Node {
public final CompoundNode body;
public final List<VariableNode> params;
public final Location end;
public abstract String name();
public final String name(String fallback) {
return this.name() != null ? this.name() : fallback;
}
protected final int[] captures(CompileResult target) {
return target.childrenMap.get(this).scope.getCaptureIndices();
}
protected final Environment rootEnv(Environment env) {
return env.get(JavaScript.COMPILE_ROOT);
}
@Override public void resolve(CompileResult target) { }
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String selfName) {
var target = new CompileResult(env, scope, params.size());
var i = 0;
body.resolve(target);
for (var param : params) scope.define(param.name);
var hasSelf = false;
if (selfName != null && !scope.has(selfName, false)) {
hasSelf = true;
scope.define(selfName);
}
body.compileFunctions(target);
for (var param : params) {
target.add(Instruction.loadArg(i++)).setLocation(param.loc());
target.add(scope.define(param.name).index().toSet(false)).setLocation(param.loc());
}
if (hasSelf) {
target.add(Instruction.loadCalled());
target.add(scope.define(selfName).index().toSet(false));
}
body.compile(target, lastReturn, BreakpointType.NONE);
return target;
}
public final CompileResult compileBody(CompileResult parent, String selfName) {
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, 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, List<VariableNode> params, CompoundNode body) {
super(loc);
this.end = end;
this.params = params;
this.body = body;
}
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 = JavaScript.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,33 @@
package me.topchetoeu.j2s.compilation;
import java.util.List;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.scope.Variable;
import me.topchetoeu.j2s.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));
}
@Override public void compileFunctions(CompileResult target) {
target.addChild(this, compileBody(target, name()));
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
target.add(VariableNode.toSet(target, end, this.name, false, true)).setLocation(loc());
if (pollute) target.add(Instruction.pushUndefined());
}
public FunctionStatementNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
}

View File

@ -0,0 +1,28 @@
package me.topchetoeu.j2s.compilation;
import java.util.List;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.values.VariableNode;
public class FunctionValueNode extends FunctionNode {
public final String name;
@Override public String name() { return name; }
@Override public void compileFunctions(CompileResult target) {
target.addChild(this, compileBody(target, name()));
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
if (!pollute) target.add(Instruction.discard());
}
public FunctionValueNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
}

View File

@ -0,0 +1,325 @@
package me.topchetoeu.j2s.compilation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.control.BreakNode;
import me.topchetoeu.j2s.compilation.control.ContinueNode;
import me.topchetoeu.j2s.compilation.control.DebugNode;
import me.topchetoeu.j2s.compilation.control.DeleteNode;
import me.topchetoeu.j2s.compilation.control.DoWhileNode;
import me.topchetoeu.j2s.compilation.control.ForInNode;
import me.topchetoeu.j2s.compilation.control.ForNode;
import me.topchetoeu.j2s.compilation.control.IfNode;
import me.topchetoeu.j2s.compilation.control.ReturnNode;
import me.topchetoeu.j2s.compilation.control.SwitchNode;
import me.topchetoeu.j2s.compilation.control.ThrowNode;
import me.topchetoeu.j2s.compilation.control.TryNode;
import me.topchetoeu.j2s.compilation.control.WhileNode;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.scope.FunctionScope;
import me.topchetoeu.j2s.compilation.values.ArgumentsNode;
import me.topchetoeu.j2s.compilation.values.ArrayNode;
import me.topchetoeu.j2s.compilation.values.GlobalThisNode;
import me.topchetoeu.j2s.compilation.values.ObjectNode;
import me.topchetoeu.j2s.compilation.values.RegexNode;
import me.topchetoeu.j2s.compilation.values.ThisNode;
import me.topchetoeu.j2s.compilation.values.VariableNode;
import me.topchetoeu.j2s.compilation.values.constants.BoolNode;
import me.topchetoeu.j2s.compilation.values.constants.NullNode;
import me.topchetoeu.j2s.compilation.values.constants.NumberNode;
import me.topchetoeu.j2s.compilation.values.constants.StringNode;
import me.topchetoeu.j2s.compilation.values.operations.CallNode;
import me.topchetoeu.j2s.compilation.values.operations.ChangeNode;
import me.topchetoeu.j2s.compilation.values.operations.DiscardNode;
import me.topchetoeu.j2s.compilation.values.operations.IndexNode;
import me.topchetoeu.j2s.compilation.values.operations.OperationNode;
import me.topchetoeu.j2s.compilation.values.operations.PostfixNode;
import me.topchetoeu.j2s.compilation.values.operations.TypeofNode;
public final class JavaScript {
public static enum DeclarationType {
@Deprecated
VAR;
}
public static final Key<Environment> COMPILE_ROOT = new Key<>();
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),
JavaScript::parseLiteral,
StringNode::parse,
RegexNode::parse,
NumberNode::parse,
ChangeNode::parsePrefixDecrease,
ChangeNode::parsePrefixIncrease,
OperationNode::parsePrefix,
ArrayNode::parse,
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("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(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<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<Node> res = ParseRes.first(src, i + n,
VariableDeclareNode::parse,
ReturnNode::parse,
ThrowNode::parse,
ContinueNode::parse,
BreakNode::parse,
DebugNode::parse,
IfNode::parse,
WhileNode::parse,
SwitchNode::parse,
ForNode::parse,
ForInNode::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<Boolean> 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(true, 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) {
i += Parsing.skipEmpty(src, i);
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, boolean passthrough, Node ...statements) {
env = env.child();
env.add(COMPILE_ROOT, env);
var func = new FunctionValueNode(null, null, Arrays.asList(), new CompoundNode(null, statements), null);
var res = func.compileBody(env, new FunctionScope(passthrough), true, null);
return res;
}
public static CompileResult compile(Environment env, Filename filename, String raw, boolean passthrough) {
return JavaScript.compile(env, passthrough, JavaScript.parse(env, filename, raw));
}
public static CompileResult compile(Filename filename, String raw, boolean passthrough) {
var env = new Environment();
return JavaScript.compile(env, passthrough, 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);
}
public static ParseRes<List<VariableNode>> 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<VariableNode>();
var closeParen = Parsing.parseOperator(src, i + n, ")");
n += closeParen.n;
if (!closeParen.isSuccess()) {
while (true) {
n += Parsing.skipEmpty(src, i + n);
var param = VariableNode.parse(src, i + n);
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(params, n);
}
}

View File

@ -0,0 +1,120 @@
package me.topchetoeu.j2s.compilation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.function.IntSupplier;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.SyntaxException;
public class LabelContext {
public static final Key<LabelContext> BREAK_CTX = new Key<>();
public static final Key<LabelContext> CONTINUE_CTX = new Key<>();
private final LinkedList<IntSupplier> list = new LinkedList<>();
private final HashMap<String, IntSupplier> map = new HashMap<>();
private final LinkedList<ArrayList<Runnable>> deferredList = new LinkedList<>();
private final HashMap<String, ArrayList<Runnable>> deferredMap = new HashMap<>();
public IntSupplier get() {
return list.peekLast();
}
public IntSupplier get(String name) {
return map.get(name);
}
public void flushAdders(String name) {
for (var adder : deferredList.peek()) {
adder.run();
}
deferredList.pop();
if (name != null) {
var adders = deferredMap.remove(name);
if (adders != null) {
for (var adder : adders) adder.run();
}
}
}
public boolean jump(CompileResult target) {
var res = get();
if (res != null) {
var tmp = target.temp();
this.deferredList.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
return true;
}
else return false;
}
public boolean jump(CompileResult target, String name) {
var res = name == null ? get() : get(name);
if (res != null) {
var tmp = target.temp();
Runnable task = () -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp));
if (name == null) this.deferredList.peekLast().add(task);
else if (deferredMap.containsKey(name)) this.deferredMap.get(name).add(task);
else return false;
return true;
}
else return false;
}
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);
deferredList.push(new ArrayList<>());
if (name != null) deferredMap.put(name, new ArrayList<>());
}
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);
flushAdders(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,28 @@
package me.topchetoeu.j2s.compilation;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
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 abstract void compileFunctions(CompileResult target);
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.j2s.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.j2s.compilation;
import me.topchetoeu.j2s.common.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,115 @@
package me.topchetoeu.j2s.compilation;
import java.util.ArrayList;
import java.util.List;
import com.github.bsideup.jabel.Desugar;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.VariableNode;
public class VariableDeclareNode extends Node {
@Desugar
public static record Pair(VariableNode var, Node value) { }
public final List<Pair> values;
@Override public void resolve(CompileResult target) {
for (var entry : values) {
target.scope.define(entry.var.name);
}
}
@Override public void compileFunctions(CompileResult target) {
for (var pair : values) {
if (pair.value != null) pair.value.compileFunctions(target);
}
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
var index = target.scope.get(entry.var.name, false);
if (entry.value != null) {
entry.value.compile(target, true);
}
if (index == null) {
if (entry.value == null) {
target.add(Instruction.globDef(entry.var.name));
}
else {
target.add(Instruction.globSet(entry.var.name, false, true));
}
}
else if (entry.value != null) {
target.add(index.index().toSet(false));
}
}
if (pollute) target.add(Instruction.pushUndefined());
}
public VariableDeclareNode(Location loc, List<Pair> values) {
super(loc);
this.values = values;
}
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, res), n);
}
while (true) {
var nameLoc = src.loc(i + n);
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
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(new VariableNode(nameLoc, name.result), val));
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, res), n);
}
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
}
}
}

View File

@ -0,0 +1,58 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class BreakNode extends Node {
public final String label;
@Override public void compileFunctions(CompileResult target) {
}
@Override public void compile(CompileResult target, boolean pollute) {
if (!LabelContext.getBreak(target.env).jump(target, label)) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal break statement");
}
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,58 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class ContinueNode extends Node {
public final String label;
@Override public void compileFunctions(CompileResult target) {
}
@Override public void compile(CompileResult target, boolean pollute) {
if (!LabelContext.getCont(target.env).jump(target)) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal continue statement");
}
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,40 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class DebugNode extends Node {
@Override public void compileFunctions(CompileResult target) {
}
@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,57 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.VariableNode;
import me.topchetoeu.j2s.compilation.values.constants.BoolNode;
import me.topchetoeu.j2s.compilation.values.operations.IndexNode;
public class DeleteNode extends Node {
public final Node key;
public final Node value;
@Override public void compileFunctions(CompileResult target) {
key.compileFunctions(target);
value.compileFunctions(target);
}
@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,88 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class DoWhileNode extends Node {
public final Node condition, body;
public final String label;
@Override public void compileFunctions(CompileResult target) {
condition.compileFunctions(target);
body.compileFunctions(target);
}
@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();
LabelContext.pushLoop(target.env, loc(), label, end, start);
body.compile(target, false, BreakpointType.STEP_OVER);
condition.compile(target, true, BreakpointType.STEP_OVER);
int endI = target.size();
end.set(endI + 1);
LabelContext.popLoop(target.env, label);
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;
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 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,122 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.VariableNode;
public class ForInNode extends Node {
public final boolean isDecl;
public final VariableNode binding;
public final Node object, body;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
if (isDecl) {
target.scope.define(binding.name);
}
}
@Override public void compileFunctions(CompileResult target) {
object.compileFunctions(target);
body.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadIntrinsics("keys"));
object.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.pushValue(false));
target.add(Instruction.pushValue(true));
target.add(Instruction.call(3, false));
int start = target.size();
target.add(Instruction.dup());
target.add(Instruction.call(0, false));
target.add(Instruction.dup());
target.add(Instruction.loadMember("done"));
int mid = target.temp();
target.add(Instruction.loadMember("value")).setLocation(binding.loc());
target.add(VariableNode.toSet(target, loc(), binding.name, false, true)).setLocation(binding.loc());
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start);
body.compile(target, false, BreakpointType.STEP_OVER);
int endI = target.size();
target.add(Instruction.jmp(start - endI));
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(endI - mid + 1));
end.set(endI + 1);
LabelContext.popLoop(target.env, label);
target.add(Instruction.discard());
if (pollute) target.add(Instruction.pushUndefined());
}
public ForInNode(Location loc, String label, VariableNode binding, boolean isDecl, Node object, Node body) {
super(loc);
this.label = label;
this.binding = binding;
this.isDecl = isDecl;
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 varKw = JavaScript.parseDeclarationType(src, i + n);
n += varKw.n;
n += Parsing.skipEmpty(src, i + n);
var bindingLoc = src.loc(i + n);
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a variable name");
n += name.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, new VariableNode(bindingLoc, name.result), varKw.isSuccess(), obj.result, bodyRes.result), n);
}
}

View File

@ -0,0 +1,132 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.VariableDeclareNode;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class ForNode extends Node {
public final Node declaration, assignment, condition, body;
public final String label;
@Override public void resolve(CompileResult target) {
if (declaration != null) declaration.resolve(target);
body.resolve(target);
}
@Override public void compileFunctions(CompileResult target) {
if (declaration != null) declaration.compileFunctions(target);
if (assignment != null) assignment.compileFunctions(target);
if (condition != null) condition.compileFunctions(target);
body.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
if (declaration != null) declaration.compile(target, false, BreakpointType.STEP_OVER);
var continueTarget = new DeferredIntSupplier();
int start = target.size();
int mid = -1;
if (condition != null) {
condition.compile(target, true, BreakpointType.STEP_OVER);
mid = target.temp();
}
var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, continueTarget);
body.compile(target, false, BreakpointType.STEP_OVER);
continueTarget.set(target.size());
if (assignment != null) assignment.compile(target, false, BreakpointType.STEP_OVER);
int endI = target.size();
end.set(endI + 1);
LabelContext.popLoop(target.env, label);
target.add(Instruction.jmp(start - endI));
if (condition != null) target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
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(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,135 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
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 compileFunctions(CompileResult target) {
condition.compileFunctions(target);
body.compileFunctions(target);
if (elseBody != null) elseBody.compileFunctions(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, pollute, 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, pollute, BreakpointType.STEP_OVER);
int mid = target.temp();
elseBody.compile(target, pollute, 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 + n);
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,53 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class ReturnNode extends Node {
public final Node value;
@Override public void compileFunctions(CompileResult target) {
if (value != null) value.compileFunctions(target);
}
@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,198 @@
package me.topchetoeu.j2s.compilation.control;
import java.util.ArrayList;
import java.util.HashMap;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
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 compileFunctions(CompileResult target) {
value.compileFunctions(target);
for (var _case : cases) {
_case.value.compileFunctions(target);
}
for (var stm : body) {
stm.compileFunctions(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);
// TODO: create a jump map
for (var ccase : cases) {
target.add(Instruction.dup());
ccase.value.compile(target, true);
target.add(Instruction.operation(Operation.EQUALS));
caseToStatement.put(target.temp(), ccase.statementI);
}
int start = target.temp();
var end = new DeferredIntSupplier();
LabelContext.getBreak(target.env).pushLoop(loc(), label, end);
for (var stm : body) {
statementToIndex.put(statementToIndex.size(), target.size());
stm.compile(target, false, BreakpointType.STEP_OVER);
}
int endI = target.size();
end.set(endI);
LabelContext.getBreak(target.env).popLoop(label);
target.add(Instruction.discard());
if (pollute) target.add(Instruction.pushUndefined());
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(endI - start));
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
for (var el : caseToStatement.entrySet()) {
var i = statementToIndex.get(el.getValue());
if (i == null) i = endI;
target.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;
n += Parsing.skipEmpty(src, i + 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,46 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class ThrowNode extends Node {
public final Node value;
@Override public void compileFunctions(CompileResult target) {
value.compileFunctions(target);
}
@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 val = JavaScript.parseExpression(src, i + n, 0);
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a throw value");
n += val.n;
var 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,148 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.CompoundNode;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
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 compileFunctions(CompileResult target) {
tryBody.compileFunctions(target);
if (catchBody != null) {
if (captureName != null) {
var index = target.scope.defineCatch(captureName);
target.catchBindings.put(this, index);
}
catchBody.compileFunctions(target);
if (captureName != null) target.scope.undefineCatch();
}
if (finallyBody != null) finallyBody.compileFunctions(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 catchVar = target.catchBindings.get(this);
target.scope.defineCatch(captureName, catchVar);
target.add(Instruction.loadError()).setLocation(catchBody.loc());
target.add(catchVar.index().toSet(false)).setLocation(catchBody.loc());
catchBody.compile(target, false);
target.scope.undefineCatch();
}
else catchBody.compile(target, false);
target.add(Instruction.tryEnd());
}
if (finallyBody != null) {
finallyStart = target.size() - start;
finallyBody.compile(target, false);
target.add(Instruction.tryEnd());
}
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,82 @@
package me.topchetoeu.j2s.compilation.control;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.DeferredIntSupplier;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.LabelContext;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
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 compileFunctions(CompileResult target) {
condition.compileFunctions(target);
body.compileFunctions(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);
body.compile(target, false, BreakpointType.STEP_OVER);
var endI = target.size();
end.set(endI + 1);
LabelContext.popLoop(target.env, label);
target.add(Instruction.jmp(start - endI));
target.set(mid, Instruction.jmpIfNot(endI - 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,147 @@
package me.topchetoeu.j2s.compilation.json;
import java.util.HashMap;
import java.util.stream.Collectors;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Metadata;
import me.topchetoeu.j2s.common.StringifyUtils;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.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<JSONElement> 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(JSONElement.map(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(JSONElement.map(values), n);
}
public static ParseRes<JSONElement> 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(JSONElement.list(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(JSONElement.list(values), n);
}
public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename(Metadata.name(), "json");
var res = parseValue(new Source(null, filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(Location.of(filename, 0, 0), "Invalid JSON given");
else if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
else return JSONElement.of(res.result);
}
public static String stringify(JSONElement el) {
if (el.isNumber()) return StringifyUtils.quoteNumber(el.number());
if (el.isString()) return StringifyUtils.quoteString(el.string());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
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(StringifyUtils.quoteString(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

@ -0,0 +1,88 @@
package me.topchetoeu.j2s.compilation.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 JSONElement el) return el;
else if (val instanceof JSONMap map) return map(map);
else if (val instanceof JSONList list) return list(list);
else if (val instanceof String str) return string(str);
else if (val instanceof Boolean bool) return bool(bool);
else if (val instanceof Number num) return number(num.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

@ -0,0 +1,26 @@
package me.topchetoeu.j2s.compilation.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

@ -0,0 +1,136 @@
package me.topchetoeu.j2s.compilation.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) {
JSONElement curr = JSONElement.map(this);
for (var seg : path.split("\\.")) {
if (!curr.isMap()) return null;
curr = curr.map().elements.get(seg);
}
return curr;
}
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,59 @@
package me.topchetoeu.j2s.compilation.members;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.ObjectNode;
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 compileFunctions(CompileResult target) {
key.compileFunctions(target);
value.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
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.storeMember());
}
public FieldMemberNode(Location loc, Node key, Node value) {
this.loc = loc;
this.key = key;
this.value = value;
}
public static ParseRes<FieldMemberNode> 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;
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);
}
}

View File

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

View File

@ -0,0 +1,92 @@
package me.topchetoeu.j2s.compilation.members;
import java.util.Arrays;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.CompoundNode;
import me.topchetoeu.j2s.compilation.FunctionNode;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.ObjectNode;
import me.topchetoeu.j2s.compilation.values.VariableNode;
import me.topchetoeu.j2s.compilation.values.constants.StringNode;
public final class PropertyMemberNode extends FunctionNode implements Member {
public final Node key;
public final VariableNode argument;
@Override public String name() {
if (key instanceof StringNode str) {
if (isGetter()) return "get " + str.value;
else return "set " + str.value;
}
else return null;
}
public boolean isGetter() { return argument == null; }
public boolean isSetter() { return argument != null; }
@Override public void compileFunctions(CompileResult target) {
key.compileFunctions(target);
target.addChild(this, compileBody(target, null));
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (isGetter()) {
target.add(Instruction.loadIntrinsics("defGetter"));
}
else {
target.add(Instruction.loadIntrinsics("defSetter"));
}
target.add(Instruction.dup(1, 1));
key.compile(target, true);
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
target.add(Instruction.call(3, false));
target.add(Instruction.discard());
if (!pollute) target.add(Instruction.discard());
}
public PropertyMemberNode(Location loc, Location end, Node key, VariableNode argument, CompoundNode body) {
super(loc, end, argument == null ? Arrays.asList() : 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 = JavaScript.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.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
if (access.result.equals("set") && params.result.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
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.get(0), body.result
), n);
}
}

View File

@ -0,0 +1,82 @@
package me.topchetoeu.j2s.compilation.parsing;
import me.topchetoeu.j2s.common.Location;
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);
}
@SuppressWarnings("unchecked")
public <T2> ParseRes<T2> chainError() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed");
return (ParseRes<T2>)this;
}
@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);
}
@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.j2s.compilation.parsing;
public interface Parser<T> {
public ParseRes<T> parse(Source src, int i);
}

View File

@ -0,0 +1,464 @@
package me.topchetoeu.j2s.compilation.parsing;
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) {
int n = 0;
if (i == 0 && src.is(0, "#!")) {
while (!src.is(n, '\n')) {
if (n >= src.size()) return src.size();
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) return ParseRes.error(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) return ParseRes.error(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);
else 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);
}
private static ParseRes<Double> parseBin(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 > 1) return ParseRes.error(src.loc(i + n), "Digits in binary literals must be from 0 to 1, encountered " + digit);
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 2;
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, "0b") || src.is(i + n, "0B")) {
n += 2;
var res = parseBin(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete binary 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 (parsedAny && src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
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) * power(10, exponent), n);
else return ParseRes.res((whole + fract) * 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) * power(10, exponent), n);
else return ParseRes.res((whole + fract) * 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, '\0')));
if (digit < 0)
break;
parsedAny = true;
result *= alphabet.length();
result += digit;
n++;
}
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);
}
private 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);
}
}

View File

@ -0,0 +1,79 @@
package me.topchetoeu.j2s.compilation.parsing;
import java.util.function.Predicate;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Location;
public class Source {
public final Environment env;
public final Filename filename;
public final String src;
private int[] lineStarts;
public Location loc(int offset) {
if (offset < 0) offset = 0;
if (offset > src.length()) offset = src.length();
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,69 @@
package me.topchetoeu.j2s.compilation.parsing;
import java.util.Objects;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Location;
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,27 @@
package me.topchetoeu.j2s.compilation.patterns;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.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.j2s.compilation.patterns;
/**
* Represents all nodes that can be converted to assign targets
*/
public interface AssignTargetLike {
AssignTarget toAssignTarget();
}

View File

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

View File

@ -0,0 +1,221 @@
package me.topchetoeu.j2s.compilation.scope;
import java.util.ArrayList;
import java.util.HashMap;
public final class FunctionScope {
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS);
protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this.locals);
private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
private final HashMap<String, Variable> localsMap = new HashMap<>();
private final HashMap<String, Variable> capturesMap = new HashMap<>();
private final ArrayList<Variable> catchesMap = new ArrayList<>();
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
private final HashMap<Variable, Variable> parentToChild = new HashMap<>();
public final FunctionScope parent;
public final boolean passthrough;
private Variable addCaptured(Variable var, boolean captured) {
if (captured && !this.capturables.has(var) && !this.captures.has(var)) this.capturables.add(var);
return var;
}
private Variable getCatchVar(String name) {
for (var el : catchesMap) {
if (el.name.equals(name)) return el;
}
return null;
}
/**
* @return If a variable with the same name exists, the old variable. Otherwise, the given variable
*/
public Variable define(Variable var) {
if (passthrough) return null;
else {
var catchVar = getCatchVar(var.name);
if (catchVar != null) return catchVar;
if (localsMap.containsKey(var.name)) return localsMap.get(var.name);
if (capturesMap.containsKey(var.name)) throw new RuntimeException("HEY!");
localsMap.put(var.name, var);
return locals.add(var);
}
}
/**
* @return A variable with the given name, or null if a global variable
*/
public Variable define(String name) {
return define(new Variable(name, false));
}
/**
* Creates a catch variable and returns it
*/
public Variable defineCatch(String name) {
var var = new Variable(name, false);
this.locals.add(var);
this.catchesMap.add(var);
return var;
}
/**
* Creates a catch variable, using a specific variable instance
* Used in the second pass
*/
public Variable defineCatch(String name, Variable var) {
this.locals.add(var);
this.catchesMap.add(var);
return var;
}
/**
* Removes the last catch variable.
* NOTE: the variable is still in the internal list. It just won't be findable by its name
*/
public void undefineCatch() {
this.catchesMap.remove(this.catchesMap.size() - 1);
}
/**
* 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 catchVar = getCatchVar(name);
if (catchVar != null) return addCaptured(catchVar, capture);
if (localsMap.containsKey(name)) return addCaptured(localsMap.get(name), capture);
if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture);
if (parent == null) return null;
var parentVar = parent.get(name, true);
if (parentVar == null) return null;
var childVar = captures.add(parentVar.clone().setIndexSupplier(null));
capturesMap.put(childVar.name, childVar);
childToParent.put(childVar, parentVar);
parentToChild.put(parentVar, childVar);
return childVar;
}
/**
* If the variable given is contained in this function, just returns the variable itself.
* However, this function is important to handle cases in which you might want to access
* a captured variable. In such cases, this function will return a capture to the given variable.
*
* @param capture Whether or not to execute this capturing logic
*/
public Variable get(Variable var, boolean capture) {
if (captures.has(var)) return addCaptured(var, capture);
if (locals.has(var)) return addCaptured(var, capture);
if (capture) {
if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture);
if (parent == null) return null;
var parentVar = parent.get(var, true);
if (parentVar == null) return null;
var childVar = captures.add(parentVar.clone());
childToParent.put(childVar, parentVar);
parentToChild.put(parentVar, childVar);
return childVar;
}
else 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 (localsMap.containsKey(name)) return true;
if (capture) {
if (capturesMap.containsKey(name)) return true;
if (parent != null) return parent.has(name, true);
}
return false;
}
public int localsCount() {
return locals.size();
}
public int capturesCount() {
return captures.size();
}
public int capturablesCount() {
return capturables.size();
}
public int[] getCaptureIndices() {
var res = new int[captures.size()];
var i = 0;
for (var el : captures) {
assert childToParent.containsKey(el);
res[i] = childToParent.get(el).index().toCaptureIndex();
i++;
}
return res;
}
public Iterable<Variable> capturables() {
return capturables;
}
public Iterable<Variable> locals() {
return locals;
}
public String[] captureNames() {
var res = new String[this.captures.size()];
var i = 0;
for (var el : this.captures) {
res[i++] = el.name;
}
return res;
}
public String[] localNames() {
var res = new String[this.locals.size()];
var i = 0;
for (var el : this.locals) {
res[i++] = el.name;
}
return res;
}
public String[] capturableNames() {
var res = new String[this.capturables.size()];
var i = 0;
for (var el : this.capturables) {
res[i++] = el.name;
}
return res;
}
public FunctionScope(FunctionScope parent) {
this.parent = parent;
this.passthrough = false;
}
public FunctionScope(boolean passthrough) {
this.parent = null;
this.passthrough = passthrough;
}
}

View File

@ -0,0 +1,35 @@
package me.topchetoeu.j2s.compilation.scope;
import java.util.function.Supplier;
public final class Variable {
private Supplier<VariableIndex> indexSupplier;
public final boolean readonly;
public final String name;
public final VariableIndex index() {
return indexSupplier.get();
}
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,53 @@
package me.topchetoeu.j2s.compilation.scope;
import me.topchetoeu.j2s.common.Instruction;
public final class VariableIndex {
public static enum IndexType {
/**
* A simple variable that is only ever used within the function
*/
LOCALS,
/**
* A variable that has the ability to be captured by children functions
*/
CAPTURABLES,
/**
* A variable that has been captured from the parent function
*/
CAPTURES,
}
public final 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);
case CAPTURABLES: return Instruction.storeVar(index, keep);
case LOCALS: return Instruction.storeVar(index, keep);
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,191 @@
package me.topchetoeu.j2s.compilation.scope;
import java.util.HashMap;
import java.util.Iterator;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import me.topchetoeu.j2s.compilation.scope.VariableIndex.IndexType;
public final class VariableList implements Iterable<Variable> {
private final class VariableNode implements Supplier<VariableIndex> {
public Variable var;
public VariableNode next;
public VariableNode prev;
public int index;
public int indexIteration = -1;
public VariableList list() { return VariableList.this; }
private int getIndex() {
if (this.indexIteration != VariableList.this.indexIteration) {
this.indexIteration = VariableList.this.indexIteration;
if (prev == null) this.index = 0;
else this.index = prev.getIndex() + 1;
}
return this.index;
}
@Override public VariableIndex get() {
if (offset == null) return new VariableIndex(indexType, this.getIndex());
else return new VariableIndex(indexType, offset.getAsInt() + this.getIndex());
}
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
this.var = var;
this.next = next;
this.prev = prev;
}
}
private VariableNode first, last;
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
private final IntSupplier offset;
/**
* Increased when indices need recalculation. VariableNode will check if
* its internal indexIteration is up to date with this, and if not, will
* recalculate its index
*/
private int indexIteration = 0;
public final VariableIndex.IndexType indexType;
/**
* Adds the given variable to this list. If it already exists, does nothing
* @return val
*/
public Variable add(Variable val) {
if (this.varMap.containsKey(val)) return val;
this.indexIteration++;
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;
}
/**
* If the variable is not in the list, does nothing. Otherwise, removes the variable from the list
* @return null if nothing was done, else the deleted variable (should be var)
*/
public Variable remove(Variable var) {
if (var == null) return null;
var node = varMap.get(var);
if (node == null) return null;
this.indexIteration++;
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);
node.var.setIndexSupplier(null);
return node.var;
}
/**
* Checks if the list has the given variable
*/
public boolean has(Variable var) {
return varMap.containsKey(var);
}
/**
* Returns an indexer for the given variable
*/
public Supplier<VariableIndex> indexer(Variable var) {
return varMap.get(var);
}
public int size() {
return varMap.size();
}
public Iterator<Variable> iterator() {
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;
}
};
}
/**
* @param offset Will offset the indices by the given amount from the supplier
*/
public VariableList(IndexType type, IntSupplier offset) {
this.indexType = type;
this.offset = offset;
}
/**
* @param offset Will offset the indices by the given amount
*/
public VariableList(IndexType type, int offset) {
this.indexType = type;
this.offset = () -> offset;
}
/**
* @param prev Will offset the indices by the size of the given list
*/
public VariableList(IndexType type, VariableList prev) {
this.indexType = type;
this.offset = () -> {
if (prev.offset != null) return prev.offset.getAsInt() + prev.size();
else return prev.size();
};
}
public VariableList(IndexType type) {
this.indexType = type;
this.offset = null;
}
}

View File

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

View File

@ -0,0 +1,87 @@
package me.topchetoeu.j2s.compilation.values;
import java.util.ArrayList;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class ArrayNode extends Node {
public final Node[] statements;
@Override public void compileFunctions(CompileResult target) {
for (var stm : statements) {
if (stm != null) stm.compileFunctions(target);
}
}
@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,20 @@
package me.topchetoeu.j2s.compilation.values;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
public class GlobalThisNode extends Node {
@Override public void compileFunctions(CompileResult target) {
}
@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,114 @@
package me.topchetoeu.j2s.compilation.values;
import java.util.LinkedList;
import java.util.List;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.members.FieldMemberNode;
import me.topchetoeu.j2s.compilation.members.Member;
import me.topchetoeu.j2s.compilation.members.PropertyMemberNode;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.constants.NumberNode;
import me.topchetoeu.j2s.compilation.values.constants.StringNode;
public class ObjectNode extends Node {
public final List<Member> members;
@Override public void compileFunctions(CompileResult target) {
for (var member : members) member.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadObj());
for (var el : members) el.compile(target, true);
}
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,
PropertyMemberNode::parse,
FieldMemberNode::parse
);
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,89 @@
package me.topchetoeu.j2s.compilation.values;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class RegexNode extends Node {
public final String pattern, flags;
@Override public void compileFunctions(CompileResult target) {
}
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadIntrinsics("regex"));
target.add(Instruction.pushValue(pattern));
target.add(Instruction.pushValue(flags));
target.add(Instruction.call(2, false));
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;
loop: while (true) {
if (i + n >= src.size()) break;
switch (src.at(i + n)) {
case '[':
inBrackets = true;
source.append('[');
n++;
continue;
case ']':
inBrackets = false;
source.append(']');
n++;
continue;
case '/':
n++;
if (inBrackets) {
source.append('/');
continue;
}
else break loop;
case '\\':
source.append('\\');
source.append(src.at(i + n + 1));
n += 2;
break;
default:
source.append(src.at(i + n));
n++;
break;
}
}
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,20 @@
package me.topchetoeu.j2s.compilation.values;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
public class ThisNode extends Node {
@Override public void compileFunctions(CompileResult target) {
}
@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,73 @@
package me.topchetoeu.j2s.compilation.values;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.patterns.ChangeTarget;
public class VariableNode extends Node implements ChangeTarget {
public final String name;
@Override public void compileFunctions(CompileResult target) {
}
public String assignName() { return name; }
@Override public void beforeChange(CompileResult target) {
target.add(VariableNode.toGet(target, loc(), name));
}
@Override public void afterAssign(CompileResult target, boolean pollute) {
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
}
@Override public void compile(CompileResult target, boolean pollute) {
target.add(toGet(target, loc(), name, true, false)).setLocation(loc());
}
public static Instruction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) {
var i = target.scope.get(name, false);
if (i != null) {
if (keep) return i.index().toGet();
else return Instruction.nop();
}
else return Instruction.globGet(name, forceGet);
}
public static Instruction toGet(CompileResult target, Location loc, String name) {
return toGet(target, loc, name, true, false);
}
public static Instruction toSet(CompileResult target, Location loc, String name, boolean keep, boolean init) {
var i = target.scope.get(name, false);
if (i != null) return i.index().toSet(keep);
else return Instruction.globSet(name, keep, init);
}
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,22 @@
package me.topchetoeu.j2s.compilation.values.constants;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
public class BoolNode extends Node {
public final boolean value;
@Override public void compileFunctions(CompileResult target) {
}
@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,17 @@
package me.topchetoeu.j2s.compilation.values.constants;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
public class NullNode extends Node {
@Override public void compileFunctions(CompileResult target) {
}
@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,34 @@
package me.topchetoeu.j2s.compilation.values.constants;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class NumberNode extends Node {
public final double value;
@Override public void compileFunctions(CompileResult target) {
}
@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 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,34 @@
package me.topchetoeu.j2s.compilation.values.constants;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class StringNode extends Node {
public final String value;
@Override public void compileFunctions(CompileResult target) {
}
@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,51 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.patterns.AssignTarget;
public class AssignNode extends Node implements AssignTarget {
public final AssignTarget assignable;
public final Node value;
@Override public void compileFunctions(CompileResult target) {
((Node)assignable).compileFunctions(target);
value.compileFunctions(target);
}
@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,115 @@
package me.topchetoeu.j2s.compilation.values.operations;
import java.util.ArrayList;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class CallNode extends Node {
public final Node func;
public final Node[] args;
public final boolean isNew;
@Override public void compileFunctions(CompileResult target) {
func.compileFunctions(target);
for (var arg : args) arg.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
if (!isNew && func instanceof IndexNode indexNode) {
var obj = indexNode.object;
var index = indexNode.index;
obj.compile(target, true);
target.add(Instruction.dup());
IndexNode.indexLoad(target, index, true);
for (var arg : args) arg.compile(target, true);
target.add(Instruction.call(args.length, true));
target.setLocationAndDebug(loc(), type);
}
else {
func.compile(target, true);
for (var arg : args) arg.compile(target, true);
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
else target.add(Instruction.call(args.length, false)).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();
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,65 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.patterns.ChangeTarget;
import me.topchetoeu.j2s.compilation.values.constants.NumberNode;
public class ChangeNode extends Node {
public final ChangeTarget changable;
public final Node value;
public final Operation op;
@Override public void compileFunctions(CompileResult target) {
((Node)changable).compileFunctions(target);
value.compileFunctions(target);
}
@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,43 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class DiscardNode extends Node {
public final Node value;
@Override public void compileFunctions(CompileResult target) {
if (value != null) value.compileFunctions(target);
}
@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,154 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.patterns.ChangeTarget;
import me.topchetoeu.j2s.compilation.values.constants.NumberNode;
import me.topchetoeu.j2s.compilation.values.constants.StringNode;
public class IndexNode extends Node implements ChangeTarget {
public final Node object;
public final Node index;
@Override public void compileFunctions(CompileResult target) {
object.compileFunctions(target);
index.compileFunctions(target);
}
@Override public void beforeAssign(CompileResult target) {
object.compile(target, true);
indexStorePushKey(target, index);
}
@Override public void beforeChange(CompileResult target) {
object.compile(target, true);
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) {
object.compile(target, true);
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) {
object.compile(target, true);
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;
}
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,50 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class LazyAndNode extends Node {
public final Node first, second;
@Override public void compileFunctions(CompileResult target) {
first.compileFunctions(target);
second.compileFunctions(target);
}
@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,51 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class LazyOrNode extends Node {
public final Node first, second;
@Override public void compileFunctions(CompileResult target) {
first.compileFunctions(target);
second.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
first.compile(target, true);
if (pollute) target.add(Instruction.dup());
int start = target.temp();
if (pollute) target.add(Instruction.discard());
second.compile(target, pollute);
target.set(start, Instruction.jmpIf(target.size() - start));
}
public LazyOrNode(Location loc, Node first, Node second) {
super(loc);
this.first = first;
this.second = second;
}
public static ParseRes<LazyOrNode> parse(Source src, int i, Node prev, int precedence) {
if (precedence < 3) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "||")) return ParseRes.failed();
var loc = src.loc(i + n);
n += 2;
var res = JavaScript.parseExpression(src, i + n, 4);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator.");
n += res.n;
return ParseRes.res(new LazyOrNode(loc, prev, res.result), n);
}
}

View File

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

View File

@ -0,0 +1,56 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.patterns.ChangeTarget;
import me.topchetoeu.j2s.compilation.values.constants.NumberNode;
public class PostfixNode extends ChangeNode {
@Override public void compileFunctions(CompileResult target) {
((Node)changable).compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
super.compile(target, pollute);
if (pollute) {
value.compile(target, true);
target.add(Instruction.operation(Operation.ADD));
}
}
public PostfixNode(Location loc, ChangeTarget value, double addAmount) {
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
}
public static ParseRes<ChangeNode> parsePostfixIncrease(Source src, int i, Node prev, int precedence) {
if (precedence > 15) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "++")) return ParseRes.failed();
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
n += 2;
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n);
}
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
if (precedence > 15) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "--")) return ParseRes.failed();
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
n += 2;
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n);
}
}

View File

@ -0,0 +1,52 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
import me.topchetoeu.j2s.compilation.values.VariableNode;
public class TypeofNode extends Node {
public final Node value;
@Override public void compileFunctions(CompileResult target) {
value.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
if (value instanceof VariableNode varNode) {
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true, true));
if (pollute) target.add(Instruction.operation(Operation.TYPEOF));
else target.add(Instruction.discard());
return;
}
value.compile(target, pollute);
if (pollute) target.add(Instruction.operation(Operation.TYPEOF));
}
public TypeofNode(Location loc, Node value) {
super(loc);
this.value = value;
}
public static ParseRes<TypeofNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "typeof")) 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 'typeof' keyword.");
n += valRes.n;
return ParseRes.res(new TypeofNode(loc, valRes.result), n);
}
}

View File

@ -0,0 +1,39 @@
package me.topchetoeu.j2s.compilation.values.operations;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.FunctionNode;
import me.topchetoeu.j2s.compilation.Node;
import me.topchetoeu.j2s.compilation.values.VariableNode;
public class VariableAssignNode extends Node {
public final String name;
public final Node value;
public final Operation operation;
@Override public void compileFunctions(CompileResult target) {
value.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
if (operation != null) {
target.add(VariableNode.toGet(target, loc(), name));
FunctionNode.compileWithName(value, target, true, name);
target.add(Instruction.operation(operation));
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
}
else {
FunctionNode.compileWithName(value, target, true, name);
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
}
}
public VariableAssignNode(Location loc, String name, Node val, Operation operation) {
super(loc);
this.name = name;
this.value = val;
this.operation = operation;
}
}

View File

@ -0,0 +1,98 @@
package me.topchetoeu.j2s.compilation;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
public class TestParseRes {
@Test public void testCreateFailed() {
var res = ParseRes.failed();
assertEquals(false, res.isSuccess());
assertEquals(true, res.isFailed());
assertEquals(false, res.isError());
assertEquals(0, res.n);
assertEquals(null, res.result);
assertEquals(null, res.errorLocation);
assertEquals(null, res.error);
}
@Test public void testCreateError() {
var res = ParseRes.error(Location.of("test:10:5"), "test");
assertEquals(false, res.isSuccess());
assertEquals(false, res.isFailed());
assertEquals(true, res.isError());
assertEquals(0, res.n);
assertEquals(null, res.result);
assertEquals(Location.of("test:10:5"), res.errorLocation);
assertEquals("test", res.error);
}
@Test public void testCreateResult() {
var res = ParseRes.res("test", 10);
assertEquals(true, res.isSuccess());
assertEquals(false, res.isFailed());
assertEquals(false, res.isError());
assertEquals(10, res.n);
assertEquals("test", res.result);
assertEquals(null, res.errorLocation);
assertEquals(null, res.error);
}
@Test public void testChainFailed() {
var a = ParseRes.<Integer>failed();
var b1 = a.<String>chainError();
assertEquals(a, b1);
var b2 = a.<String>chainError(Location.of("test:1:2"), "test");
assertEquals(true, b2.isError());
assertEquals("test", b2.error);
}
@Test public void testChainError() {
var a = ParseRes.<Integer>error(Location.of("test:1:2"), "test");
var b1 = a.<String>chainError();
assertEquals(a, b1);
var b2 = a.<String>chainError(Location.of("test:1:2"), "test");
assertEquals(a, b2);
}
@Test public void testChainResult() {
var a = ParseRes.<Integer>res(10, 6);
assertThrows(RuntimeException.class, () -> a.<String>chainError());
var b1 = a.<String>chainError(Location.of("test:1:2"), "test");
assertEquals(true, b1.isError());
assertEquals("test", b1.error);
}
@Test public void testShouldAdd5() {
var a = ParseRes.res("test", 6);
var b = a.addN(5);
assertEquals(11, b.n);
}
@Test public void testShouldSet5() {
var a = ParseRes.res("test", 6);
var b = a.setN(5);
assertEquals(5, b.n);
}
@Test public void testShouldNotAdd() {
var a = ParseRes.failed();
var b = a.addN(5);
assertEquals(0, b.n);
}
@Test public void testShouldNotSet() {
var a = ParseRes.failed();
var b = a.setN(5);
assertEquals(0, b.n);
}
}

View File

@ -0,0 +1,65 @@
package me.topchetoeu.j2s.compilation;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class TestSource {
private Source mkSource(String src) {
return new Source(new Environment(), Filename.parse("test"), src);
}
@Test public void testShouldCreate() {
new Source(new Environment(), Filename.parse("test"), "my little source :)");
new Source(null, Filename.parse("test"), "my little source :)");
new Source("my little source :)");
}
@Test public void testShouldGet() {
var src = mkSource("1234567890");
assertEquals('1', src.at(0));
assertEquals('6', src.at(5));
}
@Test public void testShouldThrowOutOfRange() {
var src = mkSource("1234567890");
assertThrows(IndexOutOfBoundsException.class, () -> src.at(-1));
assertThrows(IndexOutOfBoundsException.class, () -> src.at(10));
}
@Test public void testImmutableSrcLoc() {
var src = mkSource("1234567890");
var loc = src.loc(5);
// kinda stupid
for (var i = 0; i < 1000; i++) {
assertEquals(5, loc.start());
assertEquals(0, loc.line());
assertEquals(Filename.parse("test"), loc.filename());
}
}
@Test public void testSingleLineSourceLocation() {
var src = mkSource("1234567890");
assertEquals(Location.of("test:1:1"), src.loc(-5));
assertEquals(Location.of("test:1:1"), src.loc(0));
assertEquals(Location.of("test:1:10"), src.loc(9));
assertEquals(Location.of("test:1:11"), src.loc(14));
}
@Test public void testMultilineSourceLocation() {
var src = mkSource("123\n456\n\n789\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
assertEquals(Location.of("test:1:1"), src.loc(-5));
assertEquals(Location.of("test:1:1"), src.loc(0));
assertEquals(Location.of("test:1:4"), src.loc(3));
assertEquals(Location.of("test:2:1"), src.loc(4));
assertEquals(Location.of("test:2:4"), src.loc(7));
assertEquals(Location.of("test:3:1"), src.loc(8));
assertEquals(Location.of("test:4:1"), src.loc(9));
assertEquals(Location.of("test:4:2"), src.loc(10));
assertEquals(Location.of("test:4:4"), src.loc(12));
assertEquals(Location.of("test:5:1"), src.loc(13));
assertEquals(Location.of("test:19:1"), src.loc(50));
}
}

View File

@ -0,0 +1,65 @@
package me.topchetoeu.j2s.compilation.parsing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class TestParseString {
@Test public void notAString() {
var res = Parsing.parseString(new Source("var a = 10"), 0);
assertEquals(true, res.isFailed());
}
@Test public void simple() {
var src = new Source("\"this is a test\"");
var res = Parsing.parseString(src, 0);
assertEquals(true, res.isSuccess());
assertEquals("this is a test", res.result);
assertEquals(16, res.n);
}
@Test public void simpleEscaped() {
var src = new Source("\'this\\\\ is \\n some \\'\\\"\\\n escapes for you :\\)\'");
var res = Parsing.parseString(src, 0);
assertEquals(true, res.isSuccess());
assertEquals("this\\ is \n some '\" escapes for you :)", res.result);
assertEquals(46, res.n);
}
@Test public void allEscaped() {
var src = new Source("'\\b\\t\\n\\f\\r\\0\\'\\x01\\u0123'");
var res = Parsing.parseString(src, 0);
assertEquals(true, res.isSuccess());
assertEquals("\b\t\n\f\r\0'\u0001\u0123", res.result);
assertEquals(26, res.n);
}
@Test public void shouldFailOctal() {
var res1 = Parsing.parseString(new Source("'\\012'"), 0);
assertEquals(true, res1.isError());
var res2 = Parsing.parseString(new Source("'\\123'"), 0);
assertEquals(true, res2.isError());
}
@Test public void shouldFailIncompleteHex() {
var res1 = Parsing.parseString(new Source("'\\x"), 0);
assertEquals(true, res1.isError());
var res2 = Parsing.parseString(new Source("'\\x1turd"), 0);
assertEquals(true, res2.isError());
var res3 = Parsing.parseString(new Source("'\\xturd"), 0);
assertEquals(true, res3.isError());
}
@Test public void shouldFailIncompleteUnicode() {
var res1 = Parsing.parseString(new Source("'\\u"), 0);
assertEquals(true, res1.isError());
var res2 = Parsing.parseString(new Source("'\\u123turd"), 0);
assertEquals(true, res2.isError());
var res3 = Parsing.parseString(new Source("'\\uturd"), 0);
assertEquals(true, res3.isError());
}
@Test public void unterminated() {
var src = new Source("\"this is a test");
var res = Parsing.parseString(src, 0);
assertEquals(true, res.isError());
}
}

View File

@ -0,0 +1,48 @@
package me.topchetoeu.j2s.compilation.parsing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class TestSkipWhite {
@Test public void shBang() {
var res1 = Parsing.skipEmpty(new Source("#!my-shbang\n10"), 0);
assertEquals(12, res1);
var res2 = Parsing.skipEmpty(new Source("#!fin"), 0);
assertEquals(5, res2);
}
@Test public void simple() {
var res1 = Parsing.skipEmpty(new Source("2134 45324"), 4);
assertEquals(3, res1);
var res2 = Parsing.skipEmpty(new Source("2134 "), 4);
assertEquals(3, res2);
}
@Test public void nothing() {
var res1 = Parsing.skipEmpty(new Source("12345678"), 4);
assertEquals(0, res1);
var res2 = Parsing.skipEmpty(new Source("1234"), 4);
assertEquals(0, res2);
}
@Test public void singleLineComment() {
var res1 = Parsing.skipEmpty(new Source("123// test\n54314214"), 3);
assertEquals(8, res1);
var res2 = Parsing.skipEmpty(new Source("123// test"), 3);
assertEquals(7, res2);
}
@Test public void multilineComment() {
var res1 = Parsing.skipEmpty(new Source("123/*test*/54314214"), 3);
assertEquals(8, res1);
var res2 = Parsing.skipEmpty(new Source("123/*test*/"), 3);
assertEquals(8, res2);
var res3 = Parsing.skipEmpty(new Source("123/*test"), 3);
assertEquals(6, res3);
}
}

9
doc/text/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/*
!/img
!/src
!/.gitignore
!/build
!/document.md
!/README.md
!/requirements.md
!/template.html

1
doc/text/README.md Normal file
View File

@ -0,0 +1 @@
This is the main body of my thesis. **BE WARNED!** It is quite lengthy and written in Bulgarian.

42
doc/text/build Executable file
View File

@ -0,0 +1,42 @@
#!/bin/luajit -lsrc.build
local config = require "config";
function profession(val)
if val == "sys" then
return {
profession = "481020 „Системен програмист“",
specialty = "4810201 „Системно програмиране“",
};
elseif val == "net" then
return combine {
profession = "523050 „Техник на компютърни системи“",
specialty = "5230502 „Компютърни мрежи“",
};
end
end
emit {
target = "res.html",
template {
template = "template.html",
year = os.date "%Y",
prev_year = os.date "%Y" - 1,
config,
profession(config.profession),
build {
"requirements.md",
content = "requirements",
},
build {
"document.md",
content = "content",
toc = "toc",
ctx = {
chapter_format = "Глава %s<br/>",
chapter_format_plain = "Глава %s: ",
},
},
},
}

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