Compare commits
5 Commits
v0.10.3-be
...
7a13b032f8
| Author | SHA1 | Date | |
|---|---|---|---|
|
7a13b032f8
|
|||
|
5a154c8a69
|
|||
|
ae77e3b55e
|
|||
|
c8a89849ee
|
|||
|
d563fc4919
|
40
.gitignore
vendored
40
.gitignore
vendored
@@ -1,6 +1,40 @@
|
||||
/*
|
||||
|
||||
!/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
|
||||
!/tests
|
||||
!/.github
|
||||
@@ -10,8 +44,8 @@
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
|
||||
!/settings.gradle
|
||||
!/build.gradle
|
||||
!/settings.gradle.kts
|
||||
!/build.gradle.kts
|
||||
!/gradle.properties
|
||||
|
||||
!/package.json
|
||||
|
||||
80
README.md
80
README.md
@@ -2,22 +2,76 @@
|
||||
|
||||
**WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
|
||||
|
||||
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. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||
J2S is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. A small REPL (`me.topchetoeu.j2s.repl.SimpleRepl`) library with an included simple debugger (`me.topchetoeu.j2s.repl.debug.SimpleDebugger`). These are more or less reference implementations. In the future, most of the primordials logic of `SimpleRepl` will be moved in the "lib" project, but for now, it will stay there.
|
||||
|
||||
## Example
|
||||
## How to use?
|
||||
|
||||
The following is going to execute a simple javascript statement:
|
||||
Since this is mostly targeted for integration into other applications, here, examples for invoking JS code from Java will be shown. In the future, a more comprehensive wiki will be made.
|
||||
|
||||
### Setting up an event loop
|
||||
|
||||
First of all, you will want to create an event loop. While not required, 99% of the times you will want to have one.
|
||||
|
||||
```java
|
||||
var engine = new Engine();
|
||||
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
|
||||
var env = Internals.apply(new Environment());
|
||||
|
||||
// Queue code to load internal libraries and start engine
|
||||
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
|
||||
// Run the engine on the same thread, until the event loop runs empty
|
||||
engine.run(true);
|
||||
|
||||
// Get our result
|
||||
System.out.println(awaitable.await());
|
||||
var thread = engine.start();
|
||||
```
|
||||
|
||||
Hooray! Now you have an event loop. The thread that was automatically created is a daemon thread, so it will harakiri when the rest of the application ends. If you don't want to use the built-in thread, you can instead run it with `engine.run(untilEmpty)`. If you pass true (which you most likely need), the event loop will be run blocking-ly until it is empty. Otherwise, it will be run forever.
|
||||
|
||||
### Creating the execution environment
|
||||
|
||||
This is one of the other crucial parts of J2S's architecture - the environment. It contains the global scope, a reference to the event loop, the global scope, the debugger, source mappings and a lot more. To run JS code, you must create an environment:
|
||||
|
||||
```java
|
||||
var env = Environment.empty();
|
||||
env.add(EventLoop.KEY, engine); // Gives
|
||||
env.add(DebugContext.KEY, new DebugContext()); // For source mappings
|
||||
```
|
||||
|
||||
As you can see, the environment is nothing more than a simple map of objects that may be of interest to the JS code. Although you can do much more with the environment, we will leave it at that.
|
||||
|
||||
### Registering the compiler
|
||||
|
||||
Since the compiler is a part of the runtime, you need to register it in the environment. You can use the following boilerplate, although a nicer API will be exposed later on:
|
||||
|
||||
```java
|
||||
env.add(Compiler.KEY, (_env, filename, raw, mapper) -> {
|
||||
try {
|
||||
// Invokes the compiler. Will return a CompilerResult, which, along other things,
|
||||
// gives us all the compiled function bodies (aka function scaffoldings, that can be used to construct a function value)
|
||||
var res = JavaScript.compile(env, filename, raw, true);
|
||||
var body = res.body();
|
||||
|
||||
// We'll register the source and function source mappings for debugging
|
||||
DebugContext.get(env).onSource(filename, raw);
|
||||
for (var el : res.bodies()) {
|
||||
DebugContext.get(env).onFunctionLoad(el, res.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.
|
||||
|
||||
128
build.gradle
128
build.gradle
@@ -1,128 +0,0 @@
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
plugins {
|
||||
id 'application';
|
||||
id 'com.github.node-gradle.node' version '5.0.0';
|
||||
id 'net.nemerosa.versioning' version '2.15.0';
|
||||
id 'org.ajoberstar.grgit' version '5.0.0-rc.3'; // required by gradle
|
||||
|
||||
// TODO: figure out how to integrate proguard
|
||||
// id "com.github.xaverkapeller.proguard-annotations"
|
||||
}
|
||||
|
||||
base.archivesName = project.project_name;
|
||||
version = project.project_version;
|
||||
group = project.project_group;
|
||||
description = 'ES5-compliant JavaScript interpreter';
|
||||
|
||||
node {
|
||||
version = '20.0.0';
|
||||
npmVersion = '8.0.0';
|
||||
download = true;
|
||||
}
|
||||
|
||||
task compileEnv(type: NpmTask) {
|
||||
dependsOn npmInstall;
|
||||
|
||||
inputs.files('rollup.config.js');
|
||||
inputs.dir('src/lib/libs');
|
||||
outputs.files("build/js/env.js");
|
||||
|
||||
// group = 'build'
|
||||
args = ['run', 'build-env'];
|
||||
}
|
||||
task compileTypescript(type: NpmTask) {
|
||||
dependsOn npmInstall;
|
||||
|
||||
inputs.files('rollup.config.js');
|
||||
inputs.dir('src/lib/transpiler');
|
||||
outputs.files("build/js/ts.js");
|
||||
// nom nom tasty ram
|
||||
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");
|
||||
|
||||
// group = 'build'
|
||||
args = ['run', 'build-ts'];
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenCentral();
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2';
|
||||
compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2';
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2';
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher';
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17;
|
||||
targetCompatibility = JavaVersion.VERSION_17;
|
||||
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17);
|
||||
}
|
||||
}
|
||||
|
||||
configure([tasks.compileJava]) {
|
||||
options.release = 8;
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': project.main_class,
|
||||
'Build-Timestamp': new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||
'Build-Branch': versioning.info.branch,
|
||||
'Build-Revision': versioning.info.commit,
|
||||
'Build-Jdk': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})",
|
||||
'Build-Author': 'TopchetoEU',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = project.main_class;
|
||||
applicationDefaultJvmArgs = ['-Xmx2G', '-Xms2G', '-server', '-Dfile.encoding=UTF-8'];
|
||||
}
|
||||
|
||||
distZip {
|
||||
eachFile { file ->
|
||||
if (file.path.contains('bin')) {
|
||||
file.exclude();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distTar {
|
||||
eachFile { file ->
|
||||
if (file.path.contains('bin')) {
|
||||
file.exclude();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
dependsOn compileEnv;
|
||||
dependsOn compileTypescript;
|
||||
|
||||
from("build/js") {
|
||||
into "lib";
|
||||
}
|
||||
|
||||
filesMatching "metadata.json", {
|
||||
expand(
|
||||
version: project.project_version,
|
||||
name: project.project_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform();
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '8.10';
|
||||
}
|
||||
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 {
|
||||
subprojects.forEach { proj ->
|
||||
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`
|
||||
}
|
||||
12
buildSrc/src/main/kotlin/common-java.gradle.kts
Normal file
12
buildSrc/src/main/kotlin/common-java.gradle.kts
Normal file
@@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
id("common");
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17;
|
||||
targetCompatibility = JavaVersion.VERSION_17;
|
||||
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17);
|
||||
}
|
||||
}
|
||||
22
buildSrc/src/main/kotlin/common.gradle.kts
Normal file
22
buildSrc/src/main/kotlin/common.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id("java");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
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.json", {
|
||||
expand(
|
||||
"version" to properties["project_version"],
|
||||
"name" to properties["project_name"],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform();
|
||||
}
|
||||
@@ -63,8 +63,11 @@ public class Environment {
|
||||
}
|
||||
|
||||
public <T> T init(Key<T> key, T val) {
|
||||
if (!has(key)) this.add(key, val);
|
||||
return 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)) {
|
||||
@@ -32,9 +32,9 @@ public abstract class Location implements Comparable<Location> {
|
||||
};
|
||||
}
|
||||
public final Location nextLine() {
|
||||
return changeLine(1);
|
||||
return nextLine(1);
|
||||
}
|
||||
public final Location changeLine(int offset) {
|
||||
public final Location nextLine(int offset) {
|
||||
var self = this;
|
||||
|
||||
return new Location() {
|
||||
@@ -60,7 +60,14 @@ public abstract class Location implements Comparable<Location> {
|
||||
}
|
||||
|
||||
@Override public int compareTo(Location other) {
|
||||
int a = filename().toString().compareTo(other.filename().toString());
|
||||
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());
|
||||
|
||||
@@ -79,21 +86,27 @@ public abstract class Location implements Comparable<Location> {
|
||||
}
|
||||
|
||||
public static Location of(String raw) {
|
||||
var i0 = raw.lastIndexOf(':');
|
||||
if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1);
|
||||
var i1 = raw.lastIndexOf(':');
|
||||
if (i1 < 0) return Location.of(Filename.parse(raw), -1, -1);
|
||||
|
||||
var i1 = raw.lastIndexOf(':', i0);
|
||||
var i0 = raw.substring(0, i1).lastIndexOf(':', i1);
|
||||
if (i0 < 0) {
|
||||
try {
|
||||
return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1);
|
||||
return Location.of(null, Integer.parseInt(raw.substring(0, i1)) - 1, Integer.parseInt(raw.substring(i1 + 1)) - 1);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Location.of(Filename.parse(raw), -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));
|
||||
}
|
||||
@@ -105,9 +118,9 @@ public abstract class Location implements Comparable<Location> {
|
||||
line = Integer.parseInt(raw.substring(i0 + 1, i1));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1);
|
||||
return Location.of(Filename.parse(raw.substring(0, i1)), start - 1, -1);
|
||||
}
|
||||
|
||||
return Location.of(Filename.parse(raw.substring(0, i0)), start, line);
|
||||
return Location.of(Filename.parse(raw.substring(0, i0)), line - 1, start - 1);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package me.topchetoeu.j2s.common.parsing;
|
||||
|
||||
import me.topchetoeu.j2s.common.SyntaxException;
|
||||
import me.topchetoeu.j2s.compilation.values.constants.NumberNode;
|
||||
|
||||
public class Parsing {
|
||||
public static boolean isDigit(Character c) {
|
||||
@@ -346,8 +345,8 @@ public class Parsing {
|
||||
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||
return ParseRes.failed();
|
||||
}
|
||||
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
||||
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
||||
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);
|
||||
@@ -406,9 +405,10 @@ public class Parsing {
|
||||
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||
return ParseRes.failed();
|
||||
}
|
||||
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
||||
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
||||
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);
|
||||
|
||||
@@ -425,16 +425,20 @@ public class Parsing {
|
||||
n += 2;
|
||||
|
||||
var res = parseHex(src, i);
|
||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
||||
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);
|
||||
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;
|
||||
if (digit < 0)
|
||||
break;
|
||||
|
||||
parsedAny = true;
|
||||
result *= alphabet.length();
|
||||
@@ -443,10 +447,21 @@ public class Parsing {
|
||||
}
|
||||
|
||||
if (!parsedAny) {
|
||||
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||
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);
|
||||
} 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,72 @@
|
||||
package me.topchetoeu.j2s.common;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import me.topchetoeu.j2s.common.environment.Environment;
|
||||
import me.topchetoeu.j2s.common.environment.Key;
|
||||
|
||||
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(env.get(FOO), null);
|
||||
assertEquals(env.has(FOO), false);
|
||||
}
|
||||
|
||||
@Test public void testShouldAdd() {
|
||||
var env = new Environment();
|
||||
env.add(FOO, "test");
|
||||
assertEquals(env.get(FOO), "test");
|
||||
}
|
||||
|
||||
@Test public void testShouldGetFromParent() {
|
||||
var parent = new Environment();
|
||||
parent.add(FOO, "test");
|
||||
var child = parent.child();
|
||||
assertEquals(child.get(FOO), "test");
|
||||
assertEquals(child.has(FOO), true);
|
||||
}
|
||||
@Test public void testShouldHideParent() {
|
||||
var parent = new Environment();
|
||||
parent.add(FOO, "test");
|
||||
var child = parent.child();
|
||||
child.remove(FOO);
|
||||
assertEquals(child.get(FOO), null);
|
||||
assertEquals(child.has(FOO), false);
|
||||
}
|
||||
|
||||
@Test public void testShouldAddMarker() {
|
||||
var env = new Environment();
|
||||
env.add(MARKER);
|
||||
assertEquals(env.has(MARKER), true);
|
||||
assertEquals(env.hasNotNull(MARKER), false);
|
||||
}
|
||||
|
||||
@Test public void testShouldInitOnce() {
|
||||
var env = new Environment();
|
||||
assertEquals(env.init(FOO, "a"), "a");
|
||||
assertEquals(env.init(FOO, "b"), "a");
|
||||
assertEquals(env.get(FOO), "a");
|
||||
}
|
||||
@Test public void testShouldInitOnceFrom() {
|
||||
var env = new Environment();
|
||||
assertEquals(env.initFrom(FOO, () -> "a"), "a");
|
||||
assertEquals(env.initFrom(FOO, () -> "b"), "a");
|
||||
assertEquals(env.get(FOO), "a");
|
||||
}
|
||||
|
||||
@Test public void testShouldWrap() {
|
||||
var env = new Environment();
|
||||
assertEquals(Environment.wrap(env), env);
|
||||
assertNotEquals(Environment.wrap(null), null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package me.topchetoeu.j2s.common;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import me.topchetoeu.j2s.common.parsing.Filename;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
162
common/src/test/java/me/topchetoeu/j2s/common/TestLocation.java
Normal file
162
common/src/test/java/me/topchetoeu/j2s/common/TestLocation.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package me.topchetoeu.j2s.common;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import me.topchetoeu.j2s.common.parsing.Filename;
|
||||
import me.topchetoeu.j2s.common.parsing.Location;
|
||||
|
||||
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(next.filename(), new Filename("file", "test.txt"));
|
||||
assertEquals(next.line(), 10);
|
||||
assertEquals(next.start(), 0);
|
||||
assertEquals(loc.filename(), new Filename("file", "test.txt"));
|
||||
assertEquals(loc.line(), 9);
|
||||
assertEquals(loc.start(), 4);
|
||||
}
|
||||
@Test public void testShouldGetNextNthLineLocation() {
|
||||
var loc = Location.of(new Filename("file", "test.txt"), 10, 5);
|
||||
var next = loc.nextLine(5);
|
||||
assertEquals(next.line(), 15);
|
||||
assertEquals(next.start(), 0);
|
||||
assertEquals(loc.line(), 10);
|
||||
assertEquals(loc.start(), 5);
|
||||
}
|
||||
|
||||
@Test public void testShouldGetNextLocation() {
|
||||
var loc = Location.of("test:10:5");
|
||||
var next = loc.add(10);
|
||||
assertEquals(next.filename(), new Filename("file", "test"));
|
||||
assertEquals(next.line(), 9);
|
||||
assertEquals(next.start(), 14);
|
||||
assertEquals(loc.filename(), new Filename("file", "test"));
|
||||
assertEquals(loc.line(), 9);
|
||||
assertEquals(loc.start(), 4);
|
||||
}
|
||||
|
||||
@Test public void testShouldParseLocation() {
|
||||
var loc = Location.of("test.txt:10:5");
|
||||
assertEquals(loc.filename(), new Filename("file", "test.txt"));
|
||||
assertEquals(loc.line(), 9);
|
||||
assertEquals(loc.start(), 4);
|
||||
}
|
||||
@Test public void testShouldParseComplexFilenameLocation() {
|
||||
var loc = Location.of("testificate://test.txt:10:5");
|
||||
assertEquals(loc.filename(), new Filename("testificate", "test.txt"));
|
||||
assertEquals(loc.line(), 9);
|
||||
assertEquals(loc.start(), 4);
|
||||
}
|
||||
@Test public void testShouldParseNoFilenameLocation() {
|
||||
var loc = Location.of("10:5");
|
||||
assertEquals(loc.filename(), null);
|
||||
assertEquals(loc.line(), 9);
|
||||
assertEquals(loc.start(), 4);
|
||||
}
|
||||
@Test public void testShouldParseNoStartLocationA() {
|
||||
var loc = Location.of("file://10:5");
|
||||
assertEquals(loc.filename(), new Filename("file", "10"));
|
||||
assertEquals(loc.line(), 4);
|
||||
assertEquals(loc.start(), -1);
|
||||
}
|
||||
@Test public void testShouldParseNoStartLocationB() {
|
||||
var loc = Location.of("file:5");
|
||||
assertEquals(loc.filename(), new Filename("file", "file"));
|
||||
assertEquals(loc.line(), 4);
|
||||
assertEquals(loc.start(), -1);
|
||||
}
|
||||
@Test public void testShouldParseOnlyFilenameLocationA() {
|
||||
var loc = Location.of("http://example.org/test.txt");
|
||||
assertEquals(loc.filename(), new Filename("http", "example.org/test.txt"));
|
||||
assertEquals(loc.line(), -1);
|
||||
assertEquals(loc.start(), -1);
|
||||
}
|
||||
@Test public void testShouldParseOnlyFilenameLocationB() {
|
||||
var loc = Location.of("test.txt");
|
||||
assertEquals(loc.filename(), new Filename("file", "test.txt"));
|
||||
assertEquals(loc.line(), -1);
|
||||
assertEquals(loc.start(), -1);
|
||||
}
|
||||
@Test public void testShouldParseOnlyFilenameWithColonLocation() {
|
||||
var loc = Location.of("my-file:bad-file");
|
||||
assertEquals(loc.filename(), new Filename("file", "my-file:bad-file"));
|
||||
assertEquals(loc.line(), -1);
|
||||
assertEquals(loc.start(), -1);
|
||||
}
|
||||
@Test public void testShouldParseOnlyFilenameWithTripleColonLocation() {
|
||||
var loc = Location.of("a:my-file:bad-file");
|
||||
assertEquals(loc.filename(), new Filename("file", "a:my-file:bad-file"));
|
||||
assertEquals(loc.line(), -1);
|
||||
assertEquals(loc.start(), -1);
|
||||
}
|
||||
|
||||
@Test public void testCompareEqualLoc() {
|
||||
var locA = Location.of("test:10:5");
|
||||
var locB = Location.of("test:10:5");
|
||||
|
||||
assertEquals(locA.compareTo(locB), 0);
|
||||
assertEquals(locB.compareTo(locA), 0);
|
||||
}
|
||||
@Test public void testCompareNoFileLoc() {
|
||||
var locA = Location.of("10:5");
|
||||
var locB = Location.of("11:5");
|
||||
|
||||
assertEquals(locA.compareTo(locB), -1);
|
||||
assertEquals(locB.compareTo(locA), 1);
|
||||
}
|
||||
@Test public void testCompareOneNoFileLoc() {
|
||||
var locA = Location.of("10:5");
|
||||
var locB = Location.of("test:10:5");
|
||||
|
||||
assertEquals(locA.compareTo(locB), -1);
|
||||
assertEquals(locB.compareTo(locA), 1);
|
||||
}
|
||||
@Test public void testCompareDiffFileLoc() {
|
||||
var locA = Location.of("a:10:5");
|
||||
var locB = Location.of("b:10:5");
|
||||
|
||||
assertEquals(locA.compareTo(locB), -1);
|
||||
assertEquals(locB.compareTo(locA), 1);
|
||||
}
|
||||
@Test public void testCompareDiffLineLoc() {
|
||||
var locA = Location.of("test:10:5");
|
||||
var locB = Location.of("test:11:5");
|
||||
|
||||
assertEquals(locA.compareTo(locB), -1);
|
||||
assertEquals(locB.compareTo(locA), 1);
|
||||
}
|
||||
@Test public void testCompareDiffStartLoc() {
|
||||
var locA = Location.of("test:10:5");
|
||||
var locB = Location.of("test:10:10");
|
||||
|
||||
assertEquals(locA.compareTo(locB), -1);
|
||||
assertEquals(locB.compareTo(locA), 1);
|
||||
}
|
||||
|
||||
@Test public void testToStringAll() {
|
||||
var locA = Location.of("test:10:5");
|
||||
assertEquals(locA.toString(), "file://test:10:5");
|
||||
}
|
||||
@Test public void testToStringNoFilename() {
|
||||
var locA = Location.of("10:5");
|
||||
assertEquals(locA.toString(), "10:5");
|
||||
}
|
||||
@Test public void testToStringNoStart() {
|
||||
var locA = Location.of("file:5");
|
||||
assertEquals(locA.toString(), "file://file:5");
|
||||
}
|
||||
@Test public void testToStringNoLoc() {
|
||||
var locA = Location.of("file");
|
||||
assertEquals(locA.toString(), "file://file");
|
||||
}
|
||||
}
|
||||
22
compilation/build.gradle.kts
Normal file
22
compilation/build.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id("common-java");
|
||||
}
|
||||
|
||||
description = "A compiler of EcmaScript 5 code to J2S bytecode";
|
||||
|
||||
tasks.processResources {
|
||||
filesMatching("metadata.json", {
|
||||
expand(
|
||||
"version" to properties["project_version"],
|
||||
"name" to properties["project_name"],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform();
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"));
|
||||
}
|
||||
@@ -2,6 +2,7 @@ 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.FunctionBody;
|
||||
@@ -18,6 +19,7 @@ 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 {
|
||||
@@ -64,9 +66,31 @@ public final class CompileResult {
|
||||
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<FunctionBody> bodies() {
|
||||
var stack = new Stack<FunctionBody>();
|
||||
stack.push(body());
|
||||
|
||||
return () -> new Iterator<FunctionBody>() {
|
||||
@Override public FunctionBody next() {
|
||||
if (stack.empty()) return null;
|
||||
else {
|
||||
var res = stack.pop();
|
||||
for (var el : res.children) {
|
||||
stack.push(el);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@Override public boolean hasNext() {
|
||||
return !stack.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public CompileResult addChild(FunctionNode node, CompileResult res) {
|
||||
this.children.add(res);
|
||||
@@ -23,15 +23,6 @@ public class NumberNode extends Node {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static double power(double a, long b) {
|
||||
if (b == 0) return 1;
|
||||
if (b == 1) return a;
|
||||
if (b < 0) return 1 / power(a, -b);
|
||||
|
||||
if ((b & 1) == 0) return power(a * a, b / 2);
|
||||
else return a * power(a * a, b / 2);
|
||||
}
|
||||
|
||||
public static ParseRes<NumberNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.topchetoeu.j2s;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestHelloWorld {
|
||||
|
||||
@Test
|
||||
public void testHelloWorld() {
|
||||
final String message = "Hello World!";
|
||||
assertEquals("Hello World!", message);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
project_group = me.topchetoeu
|
||||
project_name = j2s
|
||||
project_version = 0.10.3-beta
|
||||
project_version = 0.10.4-beta
|
||||
main_class = me.topchetoeu.j2s.repl.SimpleRepl
|
||||
|
||||
68
lib/build.gradle.kts
Normal file
68
lib/build.gradle.kts
Normal file
@@ -0,0 +1,68 @@
|
||||
import com.github.gradle.node.npm.task.NpmTask;
|
||||
|
||||
plugins {
|
||||
id("common");
|
||||
id("com.github.node-gradle.node") version "5.0.0";
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
enabled = false;
|
||||
}
|
||||
tasks.classes {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
node {
|
||||
version = "20.0.0";
|
||||
npmVersion = "8.0.0";
|
||||
download = true;
|
||||
}
|
||||
|
||||
tasks.register<NpmTask>("compileStdlib") {
|
||||
dependsOn("npmInstall");
|
||||
|
||||
inputs.files("rollup.config.js");
|
||||
inputs.dir("src/stdlib");
|
||||
outputs.files("build/js/stdlib.js");
|
||||
|
||||
args.set(listOf("run", "build-env"));
|
||||
}
|
||||
tasks.register<NpmTask>("compileTranspiler") {
|
||||
dependsOn("npmInstall");
|
||||
|
||||
inputs.files("rollup.config.js");
|
||||
inputs.dir("src/transpiler");
|
||||
outputs.files("build/js/transpiler.js");
|
||||
// nom nom tasty ram
|
||||
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");
|
||||
|
||||
args.set(listOf("run", "build-ts"));
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes(
|
||||
"Main-Class" to properties["main_class"].toString(),
|
||||
"Build-Author" to "TopchetoEU",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
dependsOn("compileStdlib");
|
||||
dependsOn("compileTranspiler");
|
||||
|
||||
from("build/js") {
|
||||
into("lib");
|
||||
}
|
||||
from("src/lib") {
|
||||
into("lib");
|
||||
}
|
||||
|
||||
filesMatching("metadata.json", {
|
||||
expand(
|
||||
"version" to properties["project_version"].toString(),
|
||||
"name" to properties["project_name"].toString(),
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build-env": "rollup -c --environment INPUT:src/lib/libs/_entry.ts,OUTPUT:build/js/index.js,POLYFILLS:src/lib/libs/polyfills",
|
||||
"build-ts": "rollup -c --environment INPUT:src/lib/transpiler/_entry.ts,OUTPUT:build/js/ts.js"
|
||||
"build-env": "rollup -c --environment INPUT:src/stdlib/_entry.ts,OUTPUT:build/js/stdlib.js,POLYFILLS:src/polyfills",
|
||||
"build-ts": "rollup -c --environment INPUT:src/transpiler/_entry.ts,OUTPUT:build/js/transpiler.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user