Compare commits
312 Commits
v0.9.1-bet
...
master
Author | SHA1 | Date | |
---|---|---|---|
45292990b1 | |||
2dcfff689a | |||
2619e50e9b | |||
4bfda6b0a1 | |||
58d6110e1d | |||
120e59577d | |||
efe123b658 | |||
b190367681 | |||
e601749866 | |||
1670b64aaf | |||
1548938537 | |||
e14d85e7a8 | |||
4352550ae9 | |||
3c4d05abd4 | |||
f16d088646 | |||
8b1c2a5e4e | |||
ee8268b144 | |||
fffeac9bac | |||
166e9c0470 | |||
6125772038 | |||
1e982cd2ef | |||
4389d115b6 | |||
208444381e | |||
eff076f6fe | |||
24a4a01d8e | |||
b9a397a7b9 | |||
40f6cfe616 | |||
f712fb09ae | |||
6355a48c6b | |||
343684f9ce | |||
582753440b | |||
01e86b5e70 | |||
29b0c91c5d | |||
961c8cefcc | |||
3b512b64eb | |||
0118379d4e | |||
1be1cded9f | |||
c0b23d50e5 | |||
cacffd01e8 | |||
a115843351 | |||
3623842827 | |||
24d0cb73b6 | |||
7a13b032f8 | |||
5a154c8a69 | |||
ae77e3b55e | |||
c8a89849ee | |||
d563fc4919 | |||
9668bccef1 | |||
1d50ff14c5 | |||
a6c458cb23 | |||
31e2e95bc8 | |||
98dde69751 | |||
ec1edb981e | |||
36f9839485 | |||
22f36267c0 | |||
f8b9776f28 | |||
12cff84666 | |||
7058a689a2 | |||
fde8b42e36 | |||
4ea14ca1f5 | |||
f6ce261485 | |||
51b347e0d7 | |||
0b6484e0b4 | |||
07e0d0ba3b | |||
93cae33bb0 | |||
b1e0db627c | |||
4fd05e9e6f | |||
de93adde8f | |||
5c68c1717c | |||
7883af7fff | |||
3d5be60fc7 | |||
ba0b4e06ad | |||
58e3546459 | |||
57097e46ca | |||
28e72503a6 | |||
d0a0796e14 | |||
0de54e5505 | |||
96c9d29a6a | |||
37dc844cc4 | |||
b97e4bf163 | |||
9ce0504948 | |||
4c53689d9c | |||
6c8c329992 | |||
74f08b7483 | |||
2bf920c681 | |||
1ac68425af | |||
7423e3f283 | |||
afe4542682 | |||
c971fde0e2 | |||
8a34db833c | |||
398d88c0a5 | |||
f80266618c | |||
f1997e4584 | |||
e93498fed5 | |||
29bea68525 | |||
3a6401094f | |||
a705bf296e | |||
c3432306f7 | |||
d8c18ccf17 | |||
277f59e54a | |||
a1bb5bdba6 | |||
c699102e56 | |||
b2f5068e12 | |||
05bafc7317 | |||
c7c31c3859 | |||
c18015b9a0 | |||
5346b8917d | |||
2ed7959088 | |||
239d0ae8d7 | |||
6fac295b99 | |||
66440a9649 | |||
274a925ff8 | |||
130fe17441 | |||
493c54ed39 | |||
75786f39ad | |||
bce8b7293c | |||
00aeef5321 | |||
ff4aa3dcfd | |||
45f52ff123 | |||
bed4014bef | |||
17406c6b81 | |||
3abdd8d3c9 | |||
a329f615cf | |||
058d20b27f | |||
ea158c1e60 | |||
4e18c76bb1 | |||
8d7939d85a | |||
814e0d7b7e | |||
138baebacb | |||
db241919f2 | |||
db45eb529d | |||
caf44a50e5 | |||
65f9debecc | |||
3f5e1a5fd8 | |||
b0d8a072aa | |||
2e8e123ec4 | |||
54d55814af | |||
28679f44d5 | |||
611be55bbb | |||
4992d0211b | |||
ba7505e148 | |||
3c13799c2f | |||
5c2fd00bfb | |||
39eb6ffac5 | |||
7f6df49fc5 | |||
61c5df5003 | |||
41bb27e4dd | |||
b4e7a42975 | |||
92fb0dbbfd | |||
fe8f65faf5 | |||
54fe16393a | |||
14e4aade35 | |||
754648fbf6 | |||
20f2c3c5e9 | |||
c5067cbfdd | |||
5644966dd7 | |||
50eb204da7 | |||
45308e6d65 | |||
0ebf189c95 | |||
2fe5ce607a | |||
d821a3a89b | |||
0064c74ac8 | |||
bd548c813a | |||
78af69ec80 | |||
98e5299f9c | |||
797452c28f | |||
fee74dcba4 | |||
9845a39e84 | |||
ee78bdc1cb | |||
7fcb9ed19f | |||
8dee4353d4 | |||
59e6f34a01 | |||
fdac93bf4d | |||
06eae2eaf2 | |||
d7b50fa45b | |||
077e8afff7 | |||
631ef9db4a | |||
0258cc0a90 | |||
0b3dca8b13 | |||
6d56660136 | |||
8a21873631 | |||
fbbd26bf7d | |||
e2a8a382cc | |||
0670ffcdd1 | |||
9b957335bf | |||
e9f889576c | |||
e11d182631 | |||
30674ee463 | |||
fab3e59910 | |||
d7e4e7a024 | |||
e4166fe450 | |||
b5b7781136 | |||
f13bf584a5 | |||
4e8b110fc4 | |||
cb82f4cf32 | |||
23ae2b2e46 | |||
55613ef2c9 | |||
0b34c68139 | |||
d87e53264d | |||
1f42263051 | |||
3e6816cb2c | |||
2a01b3d86e | |||
8e64d13c87 | |||
5f88061ee7 | |||
b9268518f6 | |||
63ccd5757e | |||
515011b3ef | |||
b6f04aa177 | |||
6f548ce5ff | |||
7c74df4d36 | |||
641d4d1863 | |||
0004839f6f | |||
07411f62c8 | |||
4bfc062aaf | |||
9ec99def3f | |||
8f13ff3e0b | |||
d7353e19ed | |||
e509edc459 | |||
b6a90b108b | |||
55caf1e206 | |||
eac4a3af23 | |||
5b4adf5286 | |||
807b3918fa | |||
9265a7d813 | |||
1589ef51b0 | |||
![]() |
313b20a3b3 | ||
ce9b419757 | |||
6f8efe74c4 | |||
bd503ed943 | |||
5359c54694 | |||
93c246ad97 | |||
7c8efaf066 | |||
546d663466 | |||
f929015f55 | |||
2a5f6aa9aa | |||
78d233a6bd | |||
3f25868f19 | |||
e3f1bc0949 | |||
506726fd76 | |||
4cbc108686 | |||
c39c06b792 | |||
7ab78b9cea | |||
87e077d70d | |||
52f7c15ac8 | |||
6932bea677 | |||
82d6f52a26 | |||
1b87c2f5a6 | |||
163dfe7b6e | |||
2b6d4a87ca | |||
349d392269 | |||
6481e992fa | |||
4a5e5a71af | |||
89ba921b4a | |||
a45f4109d8 | |||
62aba62a41 | |||
4048d6ef1c | |||
d0ccf00f14 | |||
f09feae08f | |||
ef0fc5a61d | |||
bab59d454f | |||
3475e3a130 | |||
49b52d90a7 | |||
8a8de518a6 | |||
099201e4ad | |||
f8553b79f9 | |||
ba6462458c | |||
e33cdbb172 | |||
fc6ddf7d3c | |||
7f275095a2 | |||
90d019f92a | |||
6fb31be12c | |||
d6ede0b404 | |||
71b40240c0 | |||
a8775d212f | |||
71872a8d64 | |||
c707f880f7 | |||
0d629a6e82 | |||
6eea342d04 | |||
ece9cf68dc | |||
11ecd8c68f | |||
48bd304c6e | |||
d8e46c3149 | |||
5fc5eb08f8 | |||
8acbc003c4 | |||
fda33112a7 | |||
67b2413d7c | |||
3a05416510 | |||
c291328cc3 | |||
7cb267b0d9 | |||
4e31766665 | |||
b5b63c4342 | |||
18f70a0d58 | |||
d38b600366 | |||
0ac7af2ea3 | |||
5185c93663 | |||
510422cab7 | |||
79e1d1cfaf | |||
e0f3274a95 | |||
ef5d29105f | |||
d8ea6557df | |||
5ba858545a | |||
446ecd8f2b | |||
fbf103439a | |||
b30f94de8f | |||
47b4dd3c15 | |||
0fb336373a | |||
b33325a98d | |||
ccf75d6066 | |||
662dcc1ac1 | |||
3e6214659b | |||
7c6622c53d | |||
70d5871091 |
31
.github/workflows/tagged-release.yml
vendored
31
.github/workflows/tagged-release.yml
vendored
@ -3,7 +3,7 @@ name: "tagged-release"
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
tagged-release:
|
||||
@ -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
55
.gitignore
vendored
@ -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
|
||||
|
84
README.md
84
README.md
@ -1,29 +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();
|
||||
```
|
||||
|
||||
## NOTE:
|
||||
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.
|
||||
|
||||
To setup the typescript bundle in your sources, run `node build.js init-ts`. This will download the latest version of typescript, minify it, and add it to your src folder. If you are going to work with the `node build.js debug|release` command, this is not a necessary step.
|
||||
### 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.
|
||||
|
32
build.gradle
32
build.gradle
@ -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
23
build.gradle.kts
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
buildSrc/build.gradle.kts
Normal file
7
buildSrc/build.gradle.kts
Normal file
@ -0,0 +1,7 @@
|
||||
repositories {
|
||||
mavenCentral();
|
||||
}
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
15
buildSrc/src/main/kotlin/common-java.gradle.kts
Normal file
15
buildSrc/src/main/kotlin/common-java.gradle.kts
Normal file
@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
id("common");
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17;
|
||||
targetCompatibility = JavaVersion.VERSION_17;
|
||||
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17);
|
||||
}
|
||||
|
||||
withJavadocJar();
|
||||
withSourcesJar();
|
||||
}
|
47
buildSrc/src/main/kotlin/common.gradle.kts
Normal file
47
buildSrc/src/main/kotlin/common.gradle.kts
Normal 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
18
common/build.gradle.kts
Normal 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();
|
||||
}
|
100
common/src/main/java/me/topchetoeu/j2s/common/Environment.java
Normal file
100
common/src/main/java/me/topchetoeu/j2s/common/Environment.java
Normal 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();
|
||||
}
|
||||
}
|
53
common/src/main/java/me/topchetoeu/j2s/common/Filename.java
Normal file
53
common/src/main/java/me/topchetoeu/j2s/common/Filename.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
348
common/src/main/java/me/topchetoeu/j2s/common/Instruction.java
Normal file
348
common/src/main/java/me/topchetoeu/j2s/common/Instruction.java
Normal 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;
|
||||
}
|
||||
}
|
3
common/src/main/java/me/topchetoeu/j2s/common/Key.java
Normal file
3
common/src/main/java/me/topchetoeu/j2s/common/Key.java
Normal file
@ -0,0 +1,3 @@
|
||||
package me.topchetoeu.j2s.common;
|
||||
|
||||
public final class Key<T> { }
|
124
common/src/main/java/me/topchetoeu/j2s/common/Location.java
Normal file
124
common/src/main/java/me/topchetoeu/j2s/common/Location.java
Normal 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);
|
||||
}
|
||||
}
|
58
common/src/main/java/me/topchetoeu/j2s/common/Metadata.java
Normal file
58
common/src/main/java/me/topchetoeu/j2s/common/Metadata.java
Normal 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;
|
||||
}
|
||||
}
|
55
common/src/main/java/me/topchetoeu/j2s/common/Operation.java
Normal file
55
common/src/main/java/me/topchetoeu/j2s/common/Operation.java
Normal 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);
|
||||
}
|
||||
}
|
92
common/src/main/java/me/topchetoeu/j2s/common/Reading.java
Normal file
92
common/src/main/java/me/topchetoeu/j2s/common/Reading.java
Normal 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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
3
common/src/main/resources/metadata
Normal file
3
common/src/main/resources/metadata
Normal file
@ -0,0 +1,3 @@
|
||||
version: ${version}
|
||||
name: ${name}
|
||||
author: TopchetoEU
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
159
common/src/test/java/me/topchetoeu/j2s/common/TestLocation.java
Normal file
159
common/src/test/java/me/topchetoeu/j2s/common/TestLocation.java
Normal 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());
|
||||
}
|
||||
}
|
13
compilation/build.gradle.kts
Normal file
13
compilation/build.gradle.kts
Normal 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"));
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.j2s.compilation.parsing;
|
||||
|
||||
public interface Parser<T> {
|
||||
public ParseRes<T> parse(Source src, int i);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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); }
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
9
doc/text/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
!/img
|
||||
!/src
|
||||
!/.gitignore
|
||||
!/build
|
||||
!/document.md
|
||||
!/README.md
|
||||
!/requirements.md
|
||||
!/template.html
|
1
doc/text/README.md
Normal file
1
doc/text/README.md
Normal 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
42
doc/text/build
Executable 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
Loading…
Reference in New Issue
Block a user