Compare commits

..

No commits in common. "master" and "v0.10.1-beta" have entirely different histories.

265 changed files with 3994 additions and 8594 deletions

View File

@ -25,14 +25,10 @@ jobs:
gradle-version: "8.10"
- name: Build
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:
# api_key: "${{secrets.TOKEN}}"
files: |
LICENSE
build/libs/*.jar

40
.gitignore vendored
View File

@ -1,40 +1,6 @@
/*
/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
!/src
!/doc
!/tests
!/.github
@ -44,8 +10,8 @@
!/LICENSE
!/README.md
!/settings.gradle.kts
!/build.gradle.kts
!/settings.gradle
!/build.gradle
!/gradle.properties
!/package.json

View File

@ -1,77 +1,25 @@
# J2S (Java-JavaScript or Java to JavaScript)
# JScript
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
**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. 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.
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.
## How to use?
## Example
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.
The following is going to execute a simple javascript statement:
```java
var engine = new Engine();
var thread = engine.start();
// 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());
```
Hooray! Now you have an event loop. The thread that was automatically created is a daemon thread, so it will harakiri when the rest of the application ends. If you don't want to use the built-in thread, you can instead run it with `engine.run(untilEmpty)`. If you pass true (which you most likely need), the event loop will be run blocking-ly until it is empty. Otherwise, it will be run forever.
### Creating the execution environment
This is one of the other crucial parts of J2S's architecture - the environment. It contains the global scope, a reference to the event loop, the global scope, the debugger, source mappings and a lot more. To run JS code, you must create an environment:
```java
var env = Environment.empty();
env.add(EventLoop.KEY, engine); // Gives
env.add(DebugContext.KEY, new DebugContext()); // For source mappings
```
As you can see, the environment is nothing more than a simple map of objects that may be of interest to the JS code. Although you can do much more with the environment, we will leave it at that.
### Registering the compiler
Since the compiler is a part of the runtime, you need to register it in the environment. You can use the following boilerplate, although a nicer API will be exposed later on:
```java
env.add(Compiler.KEY, (_env, filename, raw, mapper) -> {
try {
// Invokes the compiler. Will return a CompilerResult, which, along other things,
// gives us all the compiled function bodies (aka function scaffoldings, that can be used to construct a function value)
var res = JavaScript.compile(env, filename, raw, true);
var body = res.body();
// We'll register the source and function source mappings for debugging
DebugContext.get(env).onSource(filename, raw);
for (var el : res.all()) {
DebugContext.get(env).onFunctionLoad(el.body(), el.map(mapper));
}
// Finally, we will construct the function
// Few things to note: we need to pass the environment, the name of the function (the filename),
// and the last thing: the captures. Since we are compiling the main function, we don't have
// any captures, so we pass an empty array
return new CodeFunction(env, filename.toString(), body, new Value[0][]);
}
catch (SyntaxException e) {
// Convert the exception to an engine exception
var res = EngineException.ofSyntax(e.msg);
// Add the location of the error to its stack trace
res.add(env, e.loc.filename() + "", e.loc);
throw res;
}
});
```
### Evaluating a piece of code on the event loop
This is what you really want to do: run code! You can do that in the following way:
```java
var result = engine.pushMsg(false, env, Filename.parse("my-app://test.js"), "return 10 + 5 / 3;", Value.UNDEFINED).get();
System.out.println(result.toReadable(env));
```
If all goes well, we will get "11.666..." as a result.

128
build.gradle Normal file
View File

@ -0,0 +1,128 @@
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';
}

View File

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

View File

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

View File

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

View File

@ -1,47 +0,0 @@
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"]);
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,58 +0,0 @@
package me.topchetoeu.j2s.common;
public class Metadata {
private static String VERSION;
private static String AUTHOR;
private static String NAME;
static {
var raw = Reading.resourceToString("metadata").split("\n");
var line = 0;
var file = "internal://metadata";
for (var el : raw) {
line++;
el = el.trim();
if (el.startsWith("#")) continue;
if (el.isEmpty()) continue;
var i = el.indexOf(":");
if (i < 0) throw new RuntimeException(String.format("%s:%s: Expected colon on line", file, line));
var name = el.substring(0, i).trim();
var value = el.substring(i + 1).trim();
switch (name) {
case "version":
VERSION = value;
break;
case "author":
AUTHOR = value;
break;
case "name":
NAME = value;
break;
default:
throw new RuntimeException(String.format("%s:%s: Unexpected metadata key '%s'", file, line, name));
}
}
if (VERSION == null) throw new RuntimeException(String.format("%s:%s: No version specified", file, line));
if (AUTHOR == null) throw new RuntimeException(String.format("%s:%s: No author specified", file, line));
if (NAME == null) throw new RuntimeException(String.format("%s:%s: No name specified", file, line));
}
public static String version() {
if (VERSION.equals("${VERSION}")) return "1337-devel";
else return VERSION;
}
public static String author() {
if (AUTHOR.equals("${AUTHOR}")) return "anonymous";
else return AUTHOR;
}
public static String name() {
if (NAME.equals("${NAME}")) return "some-product";
else return NAME;
}
}

View File

@ -1,55 +0,0 @@
package me.topchetoeu.j2s.common;
import java.util.HashMap;
public enum Operation {
TYPEOF(0x10, 1),
INSTANCEOF(0x11, 2),
IN(0x12, 2),
MULTIPLY(0x20, 2),
DIVIDE(0x21, 2),
MODULO(0x22, 2),
ADD(0x23, 2),
SUBTRACT(0x24, 2),
USHIFT_RIGHT(0x30, 2),
SHIFT_RIGHT(0x31, 2),
SHIFT_LEFT(0x32, 2),
GREATER(0x40, 2),
LESS(0x41, 2),
GREATER_EQUALS(0x42, 2),
LESS_EQUALS(0x43, 2),
LOOSE_EQUALS(0x44, 2),
LOOSE_NOT_EQUALS(0x45, 2),
EQUALS(0x46, 2),
NOT_EQUALS(0x47, 2),
AND(0x50, 2),
OR(0x51, 2),
XOR(0x52, 2),
NEG(0x60, 1),
POS(0x61, 1),
NOT(0x62, 1),
INVERSE(0x63, 1);
private static final HashMap<Integer, Operation> operations = new HashMap<>();
static {
for (var val : Operation.values()) operations.put(val.numeric, val);
}
public final int numeric;
public final int operands;
private Operation(int numeric, int n) {
this.numeric = numeric;
this.operands = n;
}
public static Operation fromNumeric(int i) {
return operations.get(i);
}
}

View File

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

View File

@ -1,3 +0,0 @@
version: ${version}
name: ${name}
author: TopchetoEU

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -1,42 +0,0 @@
#!/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: ",
},
},
},
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -1,14 +0,0 @@
---
title: Requirements
---
1. Тема: Среда за изпълнение на „EcmaScript 5“ код за виртуалната машина на „Java“
2. Изисквания
- Покритие на „ECMA-262 5.1 Edition“ стандарта
- Задоволително бързодействие
- Възможност за лесно вграждане в други софтуерни продукти
- Добри тестове, подробна документация и удобен и интуитивен публичен интерфейс за разработчици
3. Съдържание
1. Теоретична част
2. Практическа част
3. Приложение

View File

@ -1,728 +0,0 @@
---@diagnostic disable: undefined-field
require "src.utils";
local json = require "src.json";
local function reader(fd, ...)
if type(fd) == "string" then
fd = assert(io.open(fd, ... or "r"));
elseif type(fd) == "function" then
return fd;
else
assert(fd, ...);
end
return function ()
if fd == nil then error "File is closed" end
local res = fd:read(1024);
if res == nil then
fd:close();
fd = nil;
return nil;
else
return res;
end
end;
end
local function read_to_str(fd, ...)
local res = {};
for val in reader(fd, ...) do
res[#res + 1] = val;
end
return table.concat(res);
end
local flag = {};
function flag.has(name, ...)
if name == ... then
return true;
elseif ... == nil then
return false;
else
return flag.has(name, select(2, ...));
end
end
function flag.add(name, ...)
if flag.has(name, ...) then
return ...;
else
return name, ...;
end
end
local converters = {};
local function sanitize(str)
return (str
:gsub("<", "&lt")
:gsub(">", "&gt")
);
end
local function get_indent(ctx, n)
return ("\t"):rep((ctx.indent or 0) + (n or 0));
end
local function indent(ctx)
ctx.indent = (ctx.indent or 0) + 1;
end
local function undent(ctx)
ctx.indent = (ctx.indent or 1) - 1;
end
local function convert(data, ctx, ...)
local func = converters[data.t];
if func == nil then
error(table.concat { "Unknown node '", data.t, "': ", to_readable(data) });
else
return func(data.c, ctx, ...);
end
end
local function convert_all(arr, ctx, ...)
local res = array {};
local plain = array {};
local inline = true;
for i = 1, #arr do
local a, b, c = convert(arr[i], ctx, ...);
res:append(a);
plain:append(b);
if not c then
inline = false;
res:append("\n", get_indent(ctx));
end
end
return res, plain, inline;
end
local function make_id(id, readable, ctx)
ctx.ids = ctx.ids or {};
local res;
if ctx.ids[id] ~= nil then
ctx.ids[id] = ctx.ids[id] + 1;
res = id .. "-" .. ctx.ids[id];
else
ctx.ids[id] = 0;
res = id;
end
if readable then
ctx.citables = ctx.citables or {};
ctx.citables[id] = readable;
end
return res;
end
local function last_id(id, ctx)
ctx.ids = ctx.ids or {};
if ctx.ids[id] ~= nil and ctx.ids[id] > 0 then
return id .. "-" .. ctx.ids[id];
else
return id;
end
end
local function count_headers(array)
local res = 0;
for i = 1, #array do
if not array[i].ignored then
res = res + 1;
end
end
return res;
end
local function read_attributes(data, readable, ctx, no_id)
local res = {};
if data[1] ~= "" then
res.id = data[1];
end
for i = 1, #data[2] do
res[data[2][i]] = true;
end
for i = 1, #data[3] do
local curr = data[3][i];
res[curr[1]] = curr[2];
end
if not no_id and res.id then
res.id = make_id(res.id, readable, ctx);
end
return res;
end
function converters.Table(data, ctx)
local options = data[3];
local header = data[4];
local body = data[5][1];
local text = array {};
local function convert_cell(cell, tag, options)
local align = options[1].t;
indent(ctx);
text:push("<", tag);
if align == "AlignRight" then
text:push [[ class="right"]];
elseif align == "AlignLeft" then
text:push [[ class="left"]];
elseif align == "AlignCenter" then
text:push [[ class="center"]];
end
text:push(">\n", get_indent(ctx));
text:append((convert_all(cell[5], ctx)));
undent(ctx);
text:push("\n", get_indent(ctx), "</" .. tag .. ">");
end
local function convert_row(row, tag)
text:push("<tr>");
for i = 1, #row[2] do
local cell = row[2][i];
indent(ctx);
text:push("\n", get_indent(ctx));
convert_cell(cell, tag, options[i]);
undent(ctx);
end
text:push("\n", get_indent(ctx), "</tr>");
end
text:push [[<table class="graphic-table">]];
indent(ctx);
indent(ctx);
text:push("\n", get_indent(ctx, -1), "<thead>");
text:push("\n", get_indent(ctx));
convert_row(header[2][1], "th");
text:push("\n", get_indent(ctx, -1), "</thead>");
text:push("\n", get_indent(ctx, -1), "<tbody>");
for i = 1, #body[4] do
text:push("\n", get_indent(ctx));
convert_row(body[4][i], "td");
end
text:push("\n", get_indent(ctx, -1), "</tbody>");
undent(ctx);
undent(ctx);
text:push("\n", get_indent(ctx), "</table>");
return text, array { "[table]" };
end
function converters.BulletList(data, ctx)
local text = array { "<ul class=\"bullet\">" };
local plain = array {};
indent(ctx);
for i = 1, #data do
local el_text, el_plain = convert_all(data[i], ctx);
text
:push("\n", get_indent(ctx), "<li>")
:append(el_text)
:push("</li>");
plain:push("* "):append(el_plain):push("\n");
end
undent(ctx);
text:push("\n", get_indent(ctx), "</ul>");
return text, plain;
end
function converters.OrderedList(data, ctx)
local text = array { "<ol>" };
local plain = array {};
indent(ctx);
for i = 1, #data[2] do
local el_text, el_plain = convert_all(data[2][i], ctx);
text
:push("\n", get_indent(ctx), "<li value=\"", i, "\">")
:append(el_text)
:push("</li>");
plain:push(i .. ") "):append(el_plain):push("\n");
end
undent(ctx);
text:push("\n", get_indent(ctx), "</ol>");
return text, plain;
end
function converters.Code(data, ctx)
local content = data[2];
return array { "<code>", sanitize(content), "</code>" }, array { content };
end
function converters.CodeBlock(data, ctx)
local attribs = read_attributes(data[1], nil, ctx);
local content = data[2];
local text = array { "<pre><code" };
local classes = array {};
for k, v in next, attribs do
if v == true then
classes:push("language-" .. k);
end
end
if #classes > 0 then
text:push(" class=\"", classes:join " ", "\"");
end
text:push(">", sanitize(content), "</code></pre>");
return text, array { content };
end
function converters.Image(data, ctx)
local alt, alt_plain = convert_all(data[2], ctx);
local url = data[3][1];
local title = data[3][2];
local attribs = read_attributes(data[1], title or alt_plain:join " ", ctx);
local classes = array {};
if attribs.small then classes:push("small") end
if attribs.big then classes:push("big") end
local text = array {}
:push("<img title=\"", title, "\" src=\"", url, "\" alt=\"")
:append(alt)
:push("\"");
if #classes > 0 then
text:push(" class=\"", classes:join " ", "\"");
end
text:push("/>");
return text, title and { title } or alt_plain or url or "[picture]";
end
function converters.Figure(data, ctx)
local chapter_i = count_headers(ctx.headers or {});
ctx.figures_indices = ctx.figures_indices or {};
ctx.figures_indices[chapter_i] = (ctx.figures_indices[chapter_i] or 0) + 1;
local figure_i = ctx.figures_indices[chapter_i];
local prefix = ("%s.%s."):format(chapter_i, figure_i);
local attribs = read_attributes(data[1], prefix, ctx);
local id = attribs.id;
indent(ctx);
indent(ctx);
local name, name_plain = convert_all(data[2][1], ctx);
local content, plain = convert_all(data[3], ctx);
undent(ctx);
undent(ctx);
local text = array { "<figure" };
if id then text:push(" id=\"", id, "\"") end
text:push(">");
text
:push("\n", get_indent(ctx, 1), "<div class=\"fig-content\">", "\n", get_indent(ctx, 2))
:append(content)
:push("\n", get_indent(ctx, 1), "</div>", "\n", get_indent(ctx, 1), "<figcaption>", "\n", get_indent(ctx, 2))
:push(prefix, " ")
:append(name)
:push("\n", get_indent(ctx, 1), "</figcaption>")
:push("\n", get_indent(ctx), "</figure>");
plain
:push(" (", prefix, " ")
:append(name_plain)
:push(")");
ctx.citables = ctx.citables or {};
if id then ctx.citables[id] = prefix end
return text, plain;
end
function converters.Div(data, ctx)
local attribs = read_attributes(data[1], nil, ctx, true);
if attribs.figure then
local separator_i = data[2]:find_i(function (v) return v.t == "HorizontalRule" end);
local content_data, title_data;
if separator_i == nil then
content_data = array { data[2][1] };
title_data = data[2]:slice(2);
else
content_data = data[2]:slice(1, separator_i - 1);
title_data = data[2]:slice(separator_i + 1);
end
if #title_data == 1 and title_data[1].t == "Para" then
title_data = title_data[1].c
end
return converters.Figure(array {
data[1],
array { title_data },
content_data,
}, ctx);
else
error "Divs are not supported";
end
end
function converters.Cite(data, ctx)
local citation = data[1][1];
local id = last_id(citation.citationId, ctx);
local function target()
local res = (ctx.citables or {})[id];
if res == nil then
io.stderr:write("WARNING! Citation '" .. id .. "' doesn't exist!\n");
res = id;
end
return res;
end
return array { "<a href=\"#", id, "\">", target, "</a>" }, array { target }, true;
end
function converters.Header(data, ctx)
local level = data[1];
local attribs = read_attributes(data[2], nil, ctx);
ctx.headers = ctx.headers or array {};
ctx.header_stack = ctx.header_stack or array {};
local parent = ctx.header_stack[level - 1];
if level > 1 and parent == nil then error "Heading hierarchy inconsistent" end
local text, plain = convert_all(data[3], ctx);
local header = {
id = attribs.id,
level = level,
children = array {},
ignored = attribs.nonum or false,
};
local text_prefix, plain_prefix = "", "";
if level == 1 then
ctx.headers:push(header);
else
parent.children:push(header);
end
if not header.ignored then
local prefix = array { count_headers(ctx.headers) };
local text_format, plain_format;
for i = 1, level - 1 do
--- @diagnostic disable-next-line: undefined-field
prefix:push(count_headers(ctx.header_stack[i].children));
end
header.prefix = prefix:join ".";
if level == 1 then
text_format = ctx.chapter_format or "Chapter %s<br>";
plain_format = ctx.chapter_format_plain or "Chapter %s: ";
else
text_format = "%s. ";
plain_format = "%s. ";
end
text_prefix = text_format:format(header.prefix);
plain_prefix = plain_format:format(header.prefix);
end
ctx.header_stack = ctx.header_stack:fill(nil, level + 1, #ctx.header_stack);
ctx.header_stack[level] = header;
header.text = text_prefix .. text:join "";
header.plain = plain_prefix .. plain:join "";
if attribs.id then
ctx.citables = ctx.citables or {};
ctx.citables[attribs.id] = header.plain;
end
return
arrays.concat({ ("<h%d id=%q>"):format(level, attribs.id), text_prefix }, text, { ("</h%d>"):format(level) }),
array { plain_prefix }:append(plain);
end
function converters.Link(data, ctx)
local content, content_plain = convert_all(data[2], ctx);
local url = data[3][1];
local attribs = read_attributes(data[1], nil, ctx);
local id = attribs.id or data[3][2];
if id == "" then id = nil end
if id then
ctx.link_i = (ctx.link_i or 0) + 1;
local i = ctx.link_i;
ctx.citables = ctx.citables or {};
ctx.citables[id] = "[" .. i .. "]";
end
local plain = array {};
plain:push("<a href=\"", url, "\"");
if id ~= nil then
plain:push(" id=\"", id, "\"");
end
plain:push(">");
plain:append(content);
plain:push("</a>");
return plain, content_plain:push(" (", url, ")"), true;
end
function converters.Para(data, ctx)
indent(ctx);
local text, plain = convert_all(data, ctx);
undent(ctx);
return
array {}
:push "<p>"
:append(text)
:append "</p>",
plain;
end
function converters.Emph(data, ctx)
local text, plain = convert_all(data, ctx);
return arrays.concat({ "<i>" }, text, { "</i>" }), plain, true;
end
function converters.Strong(data, ctx)
local text, plain = convert_all(data, ctx);
return arrays.concat({ "<strong>" }, text, { "</strong>" }), plain, true;
end
function converters.Str(data)
return array { sanitize(data) }, array { data }, true;
end
function converters.Plain(data, ctx)
return convert_all(data, ctx);
end
function converters.RawBlock(data)
return array {}, array {}, true;
end
function converters.MetaInlines(data, ctx)
return convert_all(data, ctx);
end
function converters.LineBreak()
return array { "<br>" }, array { " " }, true;
end
function converters.SoftBreak()
return array { "&shy;" }, array { " " }, true;
end
function converters.HorizontalRule()
return array { "<br class=\"pg-break\"/>" }, array { " " };
end
function converters.Space()
return array { " " }, array { " " }, true;
end
local function parse_meta(meta)
local res = {};
for k, v in next, meta do
local text, plain = convert(v, {});
res[k] = text:concat "";
end
return res;
end
local function convert_headers(headers, ctx)
ctx = ctx or { indent = 0 };
indent(ctx);
local res = arrays.concat(
{ "<ul>" },
headers:flat_map(function (child)
return arrays.concat(
{
"\n", get_indent(ctx),
"<li><a href=\"#",
child.id,
"\"><span class=\"name\">",
child.plain,
"</span><span class=\"page\">Page</span></a>"
},
#child.children > 0 and convert_headers(child.children, ctx) or { "" },
{ "</li>" }
);
end),
{
"\n", get_indent(ctx, -1),
"</ul>"
}
);
undent(ctx);
return res;
end
local function parse(...)
local stream, err = io.popen("pandoc --highlight-style kate --from markdown-raw_html --to json -o - " .. array { ... }:join " ");
local str = read_to_str(stream, err);
local raw = json.parse(str);
return {
metadata = parse_meta(raw.meta),
content = raw.blocks or array {},
};
end
local function convert_page(data, ctx)
ctx = ctx or {};
ctx.headers = ctx.headers or array {};
local curr_page = array {};
local pages = array {};
for i = 1, #data.content do
local curr = data.content[i];
if curr.t == "Header" then
if curr.c[1] == 1 then
if #pages > 0 or #curr_page > 0 then
pages:append(curr_page);
end
curr_page = array {};
end
end
local text, _, inline = convert(curr, ctx);
curr_page:append(text);
if not inline then
curr_page:push("\n");
end
end
pages:append(curr_page);
local function mapper(v)
if type(v) == "function" then
return v();
else
return v;
end
end
return {
content = pages:map(mapper, true):join "",
toc = convert_headers(ctx.headers):map(mapper, true):join "",
};
end
local function subst(template, variables)
local replaces = array {};
for k, v in next, variables do
local i = 1;
local search = "{{" .. k .. "}}";
while true do
local b, e = string.find(template, search, i, true);
if b == nil then break end
replaces:push { b = b, e = e, v = v };
i = e + 1;
end
end
table.sort(replaces, function (a, b) return a.b < b.b end);
local parts = array {};
local prev = 1;
for i = 1, #replaces do
local curr = replaces[i];
parts:push(template:sub(prev, curr.b - 1), curr.v);
prev = curr.e + 1;
end
parts:push(template:sub(prev));
return parts:join "";
end
function build(options)
local toc = options.toc;
local content = options.content;
local ctx = options.ctx or {};
print(table.concat { "Building ", array(options):join ", ", "..." });
local res = convert_page(parse(unpack(options)), ctx);
if toc == nil and content == nil then
return res.content;
end
local obj = {};
if toc ~= nil then obj[toc] = res.toc end
if content ~= nil then obj[content] = res.content end
return obj;
end
function combine(objects)
local res = {};
for i = 1, #objects do
for k, v in next, objects[i] do
res[k] = v;
end
end
return res;
end
function template(options)
print("Templating into " .. options.template .. "...");
options = combine(array { options }:append(options));
return subst(read_to_str(io.open(options.template, "r")), options);
end
function emit(values)
local f = io.stdout;
if values.target then
f = assert(io.open(values.target, "w"));
print("Emitting to " .. values.target .. "...");
else
print("Emitting to stdout...");
end
for i = 1, #values do
assert(f:write(values[i]));
end
if f ~= io.stdout then
f:close();
end
end
-- return main;

View File

@ -1,244 +0,0 @@
local json = { null = {} };
-- Internal functions.
local function kind_of(obj)
if type(obj) ~= "table" then return type(obj) end
local i = 1;
for _ in pairs(obj) do
if obj[i] ~= nil then
i = i + 1;
else
return "table";
end
end
if i == 1 then
return "table";
else
return "array";
end
end
local function escape_str(s)
local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" };
local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" };
for i, c in ipairs(in_char) do
s = s:gsub(c, "\\" .. out_char[i]);
end
return s
end
-- Returns pos, did_find; there are two cases:
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
-- 2. Delimiter not found: pos = pos after leading space; did_find = false.
-- This throws an error if err_if_missing is true and the delim is not found.
local function skip_delim(str, pos, delim, err_if_missing)
pos = string.find(str, "%S", pos) or pos;
if string.sub(str, pos, pos) ~= delim then
if err_if_missing then
error(table.concat { "Expected ", delim, " near position ", pos });
end
return pos, false;
end
return pos + 1, true;
end
local esc_map = { b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", v = "\v" };
-- Expects the given pos to be the first character after the opening quote.
-- Returns val, pos; the returned pos is after the closing quote character.
local function parse_str_val(str, pos, check)
if pos > #str then error("End of input found while parsing string") end
if check then
if string.sub(str, pos, pos) ~= "\"" then
return nil, pos;
else
pos = pos + 1;
end
else
pos = pos + 1;
end
local res = {};
while true do
local c = string.sub(str, pos, pos);
if c == "\"" then
return table.concat(res), pos + 1;
elseif c == "\\" then
c = string.sub(str, pos + 1, pos + 1);
res[#res + 1] = esc_map[c] or c;
pos = pos + 2;
else
res[#res + 1] = c;
pos = pos + 1;
end
end
end
-- Returns val, pos; the returned pos is after the number's final character.
local function parse_num_val(str, pos)
local num_str = string.match(str, "^-?%d+%.?%d*[eE]?[+-]?%d*", pos);
local val = tonumber(num_str);
if not val then error(table.concat { "Error parsing number at position ", pos, "." }) end
return val, pos + #num_str;
end
local json_end = { "eof" };
local function parse_impl(str, pos, end_delim)
pos = pos or 1;
if pos > #str then error("Reached unexpected end of input") end
pos = str:find("%S", pos) or pos;
local c = str:sub(pos, pos);
local delim_found;
if c == "{" then
pos = pos + 1;
local key;
local obj = {};
c = string.sub(str, pos, pos);
if c == "}" then
return obj, pos
else
while true do
key, pos = parse_str_val(str, pos, true);
if key == nil then error("Expected a string key") end
pos = skip_delim(str, pos, ":", true); -- true -> error if missing.
obj[key], pos = parse_impl(str, pos);
pos, delim_found = skip_delim(str, pos, "}");
if delim_found then return obj, pos end
pos, delim_found = skip_delim(str, pos, ",");
if not delim_found then error("Expected semicolon or comma") end
end
end
elseif c == "[" then
pos = pos + 1
local arr = array {};
local val;
local delim_found = true;
while true do
val, pos = parse_impl(str, pos, "]");
if val == json_end then return arr, pos end
if not delim_found then error("Comma missing between array items: " .. str:sub(pos, pos + 25)) end
arr[#arr + 1] = val;
pos, delim_found = skip_delim(str, pos, ",");
end
elseif c == "\"" then -- Parse a string.
return parse_str_val(str, pos, false);
elseif c == "-" or c:find("%d") then -- Parse a number.
return parse_num_val(str, pos);
elseif c == end_delim then -- End of an object or array.
return json_end, pos + 1, true;
elseif str:sub(pos, pos + 3) == "null" then
return nil, pos + 4;
elseif str:sub(pos, pos + 3) == "true" then
return true, pos + 4;
elseif str:sub(pos, pos + 4) == "false" then
return true, pos + 5;
else
error(table.concat { "Invalid json syntax starting at position ", pos, ": ", str:sub(pos, pos + 10) });
end
end
local function stringify_impl(obj, all, indent_str, n)
local s = {}; -- We'll build the string as an array of strings to be concatenated.
local kind = kind_of(obj); -- This is 'array' if it's an array or type(obj) otherwise.
if kind == "array" then
for i, val in ipairs(obj) do
s[i] = stringify_impl(val, all, indent_str, n + 1);
end
if not indent_str then
return "[" .. table.concat(s, ",") .. "]";
elseif #s == 0 then
return "[]";
else
local indent = "\n" .. string.rep(indent_str, n + 1);
return table.concat {
"[",
indent, table.concat(s, "," .. indent),
"\n", string.rep(indent_str, n), "]"
};
end
elseif kind == "table" then
for k, v in pairs(obj) do
local sep = indent_str and ": " or ":";
local val = stringify_impl(v, all, indent_str, n + 1);
if val ~= nil then
if type(k) == "string" then
s[#s + 1] = stringify_impl(k) .. sep .. val;
elseif type(k) == "number" then
s[#s + 1] = "\"" .. k .. "\"" .. sep .. val;
elseif type(k) == "boolean" then
s[#s + 1] = "\"" .. k .. "\"" .. sep .. val;
end
end
end
if not indent_str then
return "{" .. table.concat(s, ",") .. "}";
elseif #s == 0 then
return "{}";
else
local indent = "\n" .. string.rep(indent_str, n + 1);
return table.concat {
"{",
indent, table.concat(s, "," .. indent),
"\n", string.rep(indent_str, n), "}"
};
end
return "{" .. table.concat(s, ",") .. "}";
elseif kind == "string" then
return "\"" .. escape_str(obj) .. "\"";
elseif kind == "number" then
return tostring(obj);
elseif kind == 'boolean' then
return tostring(obj);
elseif kind == "nil" then
return "null";
elseif all then
return tostring(obj);
else
return nil;
end
end
-- local indent_str = " ";
function json.stringify(obj, indent_str)
if indent_str == true then
indent_str = " ";
end
return stringify_impl(obj, false, indent_str, 0);
end
function json.pretty(obj)
return stringify_impl(obj, true, " ", 0);
end
json.null = {}; -- This is a one-off table to represent the null value.
---@param str string
---@return unknown
function json.parse(str)
local obj = parse_impl(str, 1);
return obj;
end
return json

View File

@ -1,30 +0,0 @@
local ffi = require "ffi";
ffi.cdef [[
typedef struct timeval {
long tv_sec;
long tv_usec;
} timeval;
int gettimeofday(struct timeval* t, void* tzp);
]];
local function now()
local target = ffi.new "timeval";
ffi.C.gettimeofday(target, nil);
return tonumber(target.tv_sec) + tonumber(target.tv_usec) / 1000000;
end
local function measure(func, ...)
local start = now();
return (function(...)
io.stderr:write(("Took %s seconds\n"):format(now() - start));
return ...;
end)(func(...));
end
return {
now = now,
measure = measure,
}

View File

@ -1,429 +0,0 @@
-- TILL - TopchetoEU's "immaculate" lua libs
-- Some useful utilities every lua-er should use.
-- Might break shit, don't use in production for crying out loud
-- Reimplement this for lua <5.1
if table.move == nil then
--- @diagnostic disable: duplicate-set-field
function table.move(src, src_b, src_e, dst_b, dst)
if dst == nil then dst = src end
local offset = dst_b - src_b;
if dst_b < src_b then
for i = src_e, src_b, -1 do
dst[i + offset] = src[i];
end
else
for i = src_b, src_e do
dst[i + offset] = src[i];
end
end
return dst;
end
end
-- Why did we *remove* this from the spec again? Classical PUC
unpack = table.unpack or unpack;
table.unpack = unpack;
--- Prepares the object to be a class - puts an __index member in it pointing to the object itself
--- @generic T
--- @param obj T
--- @return T | { __index: T }
function class(obj)
--- @diagnostic disable-next-line: inject-field
obj.__index = obj;
return obj;
end
-- arrays
--- @alias array<T> T[] | arraylib
--- Converts the object to an array by putting "arrays" as its metatable
--- @generic T: unknown
--- @param obj T[]
--- @return array<T>
--- @overload fun(val: string): array<string>
function array(obj)
if type(obj) == "string" then
return obj:split "";
else
return arrays.mk(obj);
end
end
--- @class arraylib
arrays = class {};
--- Creates an array
function arrays.mk(obj)
return setmetatable(obj, arrays);
end
--- Adds every element of every passed array to the end of this array
function arrays:append(...)
local res = self;
local n = #res + 1;
for i = 1, select("#", ...) do
local curr = select(i, ...);
for j = 1, #curr do
res[n] = curr[j];
n = n + 1;
end
end
return res;
end
--- Returns all the given arrays, concatenated to one
function arrays.concat(...)
--- @diagnostic disable-next-line: missing-fields
return arrays.append(array {}, ...);
end
--- Adds all the given elements to the end of this array
function arrays:push(...)
return self:append({ ... });
end
--- Removes the last element of the array and returns it
--- @generic T
--- @param self array<T>
--- @return T val? The removed element, or nil if none
function arrays:pop()
local res = self[#self];
self[#self] = nil;
return res;
end
--- @generic T
--- @param self array<T>
--- @return T val? The last element of this array, or nil of none
function arrays:peek()
return self[#self];
end
--- Returns the result of mapping the values in table t through the function f
--- @param mutate boolean? If true, will operate directly on the given array
function arrays:map(f, mutate)
local out;
if mutate then
out = self;
else
out = array {};
end
for i = 1, #self do
out[i] = f(self[i], i, self);
end
return out;
end
--- Finds the index of the given element, or nil if it doesn't exist
function arrays:find_i(f)
for i = 1, #self do
if f(self[i], i, self) then
return i;
end
end
return nil;
end
--- Like arrays:map, but will expect the function to return arrays, and will :append them to the result array instead
function arrays:flat_map(f)
local out = array {};
for i = 1, #self do
out:append(f(self[i], i, self));
end
return out;
end
--- Sets each value from b to e to val in the given array
function arrays:fill(val, b, e)
if b == nil then b = 1 end
if e == nil then e = self end
if b < 0 then b = #self + 1 - b end
if e < 0 then e = #self + 1 - e end
for i = b, e do
self[i] = val;
end
return self;
end
--- Every element from start to stop is removed from this array and are replaced with the given elements
function arrays:splice(start, stop, ...)
-- TODO: optimize
if select("#") > 0 then
local n = stop - start + 1;
while n > 0 do
table.remove(self, start);
n = n - 1;
end
for i = 1, select("#") do
table.insert(self, start, (select(i, ...)));
end
return self;
else
local res = {};
for i = start, stop do
table.insert(res, self[i]);
end
return res;
end
end
--- Every element from start to stop is removed from this array and are replaced with the given elements
function arrays:slice(start, stop)
start = start or 1;
stop = stop or #self;
local res = array {};
for i = start, stop do
res:push(self[i]);
end
return res;
end
--- Equivalent of table.concat { table.unpack(self) }
--- @param sep string? Separator (defaults to empty)
--- @param b number? First element to take (defaults to beginning)
--- @param e number? Last element to take (defaults to end)
function arrays:join(sep, b, e)
return table.concat(self, sep, b, e);
end
function arrays:__concat(other)
return arrays.concat(self, other);
end
-- strings
--- Splits the text into an array of separate lines.
--- @param self string
function string:split(sep)
sep = sep or "";
local lines = arrays {};
local pos = 1;
if sep == "" then
for i = 1, #self do
lines:push(self:sub(1, 1));
end
else
while true do
local b, e = self:find(sep, pos);
if not b then
table.insert(lines, self:sub(pos));
break;
else
table.insert(lines, self:sub(pos, b - 1));
pos = e + 1;
end
end
end
return lines;
end
--- Gets the nth character
--- @param self string
--- @param i number
function string:at(i)
return self:sub(i, i);
end
--- Performs a plain string replace
--- @param self string
--- @param old string
--- @param new string
function string:replace(old, new)
local b, e = self:find(old, 1, true);
if b == nil then
return self;
else
return self:sub(1, b - 1) .. new .. self:sub(e + 1);
end
end
local function escape_str(s)
local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" };
local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" };
for i, c in ipairs(in_char) do
s = s:gsub(c, "\\" .. out_char[i]);
end
return s
end
local function stringify_impl(obj, indent_str, n, passed)
local s = {}; -- We'll build the string as an array of strings to be concatenated.
local kind = type(obj); -- This is 'array' if it's an array or type(obj) otherwise.
if kind == "table" then
if passed[obj] then return "<circular>" end
passed[obj] = true;
local len = #obj;
for i = 1, len do
s[i] = stringify_impl(obj[i], indent_str, n + 1, passed) .. ",";
end
local keys = {};
for k, v in pairs(obj) do
if type(k) ~= "number" or k > len then
keys[#keys + 1] = { k, v };
end
end
table.sort(keys, function (a, b)
if type(a[1]) == "number" and type(b[1]) == "number" then
return a[1] < b[1];
elseif type(a[1]) == "string" and type(b[1]) == "string" then
return a[1] < b[1];
else
return type(a[1]) < type(b[1]) or tostring(a[1]) < tostring(b[1]);
end
end);
for i = 1, #keys do
local k = keys[i][1];
local v = keys[i][2];
local val = stringify_impl(v, indent_str, n + 1, passed);
if val ~= nil then
if type(k) == "string" then
s[#s + 1] = table.concat { k, ": ", val, "," };
else
s[#s + 1] = table.concat { "[", stringify_impl(k, indent_str, n + 1, passed), "]: ", val, "," };
end
end
end
local meta = getmetatable(obj);
if meta ~= nil and meta ~= arrays then
s[#s + 1] = "<meta> = " .. stringify_impl(meta, indent_str, n + 1, passed) .. ",";
end
passed[obj] = false;
if #s == 0 then
if meta == arrays then
return "[]";
else
return "{}";
end
end
local contents = table.concat(s, " "):sub(1, -2);
if #contents > 80 then
local indent = "\n" .. string.rep(indent_str, n + 1);
contents = table.concat {
indent,
table.concat(s, indent),
"\n", string.rep(indent_str, n)
};
else
contents = " " .. contents .. " ";
end
if meta == arrays then
return table.concat { "[", contents, "]" };
else
return table.concat { "{", contents, "}" };
end
elseif kind == "string" then
return "\"" .. escape_str(obj) .. "\"";
elseif kind == "function" then
local data = debug.getinfo(obj, "S");
return table.concat { tostring(obj), " @ ", data.short_src, ":", data.linedefined };
elseif kind == "nil" then
return "null";
else
return tostring(obj);
end
end
--- Turns the given value to a human-readable string.
--- Should be used only for debugging and display purposes
function to_readable(obj, indent_str)
return stringify_impl(obj, indent_str or " ", 0, {});
end
function print(...)
for i = 1, select("#", ...) do
if i > 1 then
io.stderr:write("\t");
end
io.stderr:write(tostring((select(i, ...))));
end
io.stderr:write("\n");
end
--- Prints the given values in a human-readable manner to stderr
--- Should be used only for debugging
function pprint(...)
for i = 1, select("#", ...) do
if i > 1 then
io.stderr:write("\t");
end
io.stderr:write(to_readable((select(i, ...))));
end
io.stderr:write("\n");
end
-- functions
--- @class functions
functions = class {};
--- Constructs a function, such that it calls the first function with the passed arguments,
--- the second function with the return of the first, and so on. The return value of the last function is returned
---
--- In short, does the following, if the passed functions are a, b and c: return c(b(a(...)))
---
--- Sometimes less cumbersome to write (a | b | c | d)(args...) than d(c(b(a(args...))))
--- @param self function
function functions:pipe(...)
if ... == nil then
return self;
else
local next = ...;
return functions.pipe(function (...)
return next(self(...));
end, select(2, ...));
end
end
--- Calls pipe with a and b; Alternative syntax for older Lua installation
function functions.__sub(a, b)
return functions.pipe(a, b);
end
--- Calls pipe with a and b
function functions.__bor(a, b)
return functions.pipe(a, b);
end
-- It's not vital to have this metatable, so we will just try our very best
if debug then
debug.setmetatable(load, functions);
end

View File

@ -1,577 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Дипломна работа {{author}}</title>
<style>
@page {
size: A4;
}
@page {
@bottom-right-corner {
text-align: center;
content: counter(page);
}
}
h1 {
break-before: page;
}
.pg-break {
break-after: page;
}
figure {
break-inside: avoid-page;
}
.page h1, .page h2 {
break-before: unset !important;
}
body {
font-family: Georgia, 'Times New Roman', Times, serif;
font-size: 14pt;
line-height: 1.75;
}
p::before {
width: 3em;
display: inline-block;
content: ' ';
}
.page {
height: 100%;
line-height: 1.25;
h1, h2, h3, h4, h5, h6 {
margin: 0;
}
}
ul {
padding: 0;
}
li > ul {
padding-left: 2em;
}
nav {
display: contents;
width: 100%;
overflow-x: hidden;
}
nav ul {
list-style-type: none;
}
nav span {
background-color: white;
}
nav a {
line-height: 1.25;
position: relative;
overflow: hidden;
width: 100%;
display: flex;
align-items: baseline;
justify-content: space-between;
text-decoration: none;
}
nav a::after {
z-index: -1;
position: absolute;
right: 0;
float: right;
overflow: hidden;
width: max-content;
content:
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . '
'. . . . . . . . . . . . . . . . . . ';
}
nav a span {
z-index: 10000;
}
a {
color: rgb(207, 37, 37);
}
figure {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
gap: .5em;
box-sizing: border-box;
margin: 0;
padding: 1em 0;
}
figure .fig-content {
display: flex;
flex-direction: row;
gap: 1em;
justify-content: center;
flex-wrap: wrap;
}
figcaption {
font-style: italic;
text-align: center;
}
img.small {
max-width: 50%;
max-height: 25%;
}
img {
width: 100%;
}
pre {
font-size: .65em;
margin: .5em 0;
}
pre > code {
font-size: inherit;
line-height: 1.25;
padding: 0 !important;
}
.hljs-ln-code {
padding-left: .5em !important;
white-space: pre-wrap;
}
.title-content {
display: flex;
flex-direction: column;
gap: .5em;
}
.hljs-ln-numbers {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
color: #ccc;
border-right: 1px solid #CCC;
vertical-align: top;
padding-right: .5em !important;
}
p {
margin: 0;
text-align: justify;
}
.page {
height: 100%;
max-height: 100%;
font-size: 12pt;
}
.school-header {
display: flex;
flex-direction: row;
width: 100%;
gap: 2em;
font-size: 1.25em;
text-align: right;
margin-bottom: 1em;
}
.school-img {
height: 6em;
width: unset;
}
.pg-break {
break-after: page;
}
.title-page {
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: center;
}
.title-authors {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 3em;
width: 100%;
}
.title-author {
display: flex;
flex-direction: column;
}
.asm-header {
display: flex;
justify-content: space-between;
}
.asm-signature {
display: block;
text-align: right;
/* flex-direction: column;
text-align: right; */
}
.asm-title {
display: flex;
flex-direction: column;
gap: 1em;
text-align: center;
}
.asm-content {
display: grid;
padding-top: 5em;
grid-template-rows: auto auto min-content;
/* flex-direction: column;
gap: .5em;
justify-content: space-between; */
width: 100%;
height: 100%;
}
.asm-page {
display: flex;
flex-direction: column;
}
ul {
margin: 0;
}
.graphic-table {
font-size: .9em;
line-height: 1.25;
border-collapse: collapse;
width: 100%;
}
.graphic-table tr:nth-child() {
line-height: 1.25;
border-collapse: collapse;
}
.graphic-table .left {
text-align: left;
}
.graphic-table .right {
text-align: right;
}
.graphic-table .center {
text-align: center;
}
.graphic-table td, .graphic-table th {
border: 1px solid black;
padding: .25em;
margin: 0;
/* border */
}
.graphic-table thead tr {
background-color: #dadada;
}
.graphic-table tbody tr {
background-color: #ffffff;
}
.graphic-table tbody tr:nth-child(even) {
background-color: #ececec;
}
</style>
<style keep>
@media print {
nav a {
color: black;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<link rel="stylesheet" href="https://unpkg.com/highlightjs/styles/vs.css">
<script src="https://unpkg.com/@highlightjs/cdn-assets/highlight.min.js"></script>
<script src="https://unpkg.com/pagedjs/dist/paged.js"></script>
<script src="https://unpkg.com/highlightjs-line-numbers.js/src/highlightjs-line-numbers.js"></script>
<script>
// This godless solution works, so it is fine
class RepeatingTableHeadersHandler extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
this.splitTablesRefs = [];
}
afterPageLayout(pageElement, page, breakToken, chunker) {
this.chunker = chunker;
this.splitTablesRefs = [];
if (breakToken) {
const node = breakToken.node;
const tables = this.findAllAncestors(node, "table");
if (node.tagName === "TABLE") {
tables.push(node);
}
if (tables.length > 0) {
this.splitTablesRefs = tables.map(t => t.dataset.ref);
//checks if split inside thead and if so, set breakToken to next sibling element
let thead = node.tagName === "THEAD" ? node : this.findFirstAncestor(node, "thead");
if (thead) {
let lastTheadNode = thead.hasChildNodes() ? thead.lastChild : thead;
breakToken.node = this.nodeAfter(lastTheadNode, chunker.source);
}
this.hideEmptyTables(pageElement, node);
}
}
}
hideEmptyTables(pageElement, breakTokenNode) {
this.splitTablesRefs.forEach(ref => {
let table = pageElement.querySelector("[data-ref='" + ref + "']");
if (table) {
let sourceBody = table.querySelector("tbody > tr");
if (!sourceBody || this.refEquals(sourceBody.firstElementChild, breakTokenNode)) {
table.style.visibility = "hidden";
table.style.position = "absolute";
let lineSpacer = table.nextSibling;
if (lineSpacer) {
lineSpacer.style.visibility = "hidden";
lineSpacer.style.position = "absolute";
}
}
}
});
}
refEquals(a, b) {
return a && a.dataset && b && b.dataset && a.dataset.ref === b.dataset.ref;
}
findFirstAncestor(element, selector) {
while (element.parentNode && element.parentNode.nodeType === 1) {
if (element.parentNode.matches(selector)) {
return element.parentNode;
}
element = element.parentNode;
}
return null;
}
findAllAncestors(element, selector) {
const ancestors = [];
while (element.parentNode && element.parentNode.nodeType === 1) {
if (element.parentNode.matches(selector)) {
ancestors.unshift(element.parentNode);
}
element = element.parentNode;
}
return ancestors;
}
// The addition of repeating Table Headers is done here because this hook is triggered before overflow handling
layout(rendered, layout) {
this.splitTablesRefs.forEach(ref => {
const renderedTable = rendered.querySelector("[data-ref='" + ref + "']");
if (renderedTable) {
// this event can be triggered multiple times
// added a flag repeated-headers to control when table headers already repeated in current page.
if (!renderedTable.getAttribute("repeated-headers")) {
const sourceTable = this.chunker.source.querySelector("[data-ref='" + ref + "']");
this.repeatColgroup(sourceTable, renderedTable);
this.repeatTHead(sourceTable, renderedTable);
renderedTable.setAttribute("repeated-headers", true);
}
}
});
}
repeatColgroup(sourceTable, renderedTable) {
let colgroup = sourceTable.querySelectorAll("colgroup");
let firstChild = renderedTable.firstChild;
colgroup.forEach((colgroup) => {
let clonedColgroup = colgroup.cloneNode(true);
renderedTable.insertBefore(clonedColgroup, firstChild);
});
}
repeatTHead(sourceTable, renderedTable) {
let thead = sourceTable.querySelector("thead");
if (thead) {
let clonedThead = thead.cloneNode(true);
renderedTable.insertBefore(clonedThead, renderedTable.firstChild);
}
}
// the functions below are from pagedjs utils/dom.js
nodeAfter(node, limiter) {
if (limiter && node === limiter) {
return;
}
let significantNode = this.nextSignificantNode(node);
if (significantNode) {
return significantNode;
}
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) {
return;
}
significantNode = this.nextSignificantNode(node);
if (significantNode) {
return significantNode;
}
}
}
}
nextSignificantNode(sib) {
while ((sib = sib.nextSibling)) {
if (!this.isIgnorable(sib)) return sib;
}
return null;
}
isIgnorable(node) {
return (node.nodeType === 8) || // A comment node
((node.nodeType === 3) && this.isAllWhitespace(node)); // a text node, all whitespace
}
isAllWhitespace(node) {
return !(/[^\t\n\r ]/.test(node.textContent));
}
}
</script>
<script>
Paged.registerHandlers(RepeatingTableHeadersHandler);
window.onload = async evn => {
hljs.highlightAll();
hljs.initLineNumbersOnLoad();
await new Promise(res => setTimeout(res, 50));
const { Previewer, DOMContent } = Paged;
evn.preventDefault();
const p = new Previewer();
const styles = [...document.head.getElementsByTagName("style")]
.filter(v => !v.hasAttribute("keep"))
.map(v => {
v.remove();
return { [window.location.href]: v.textContent };
});
const flow = await p.preview(DOMContent, styles, document.body);
const pages = flow.pages.map(v => v.element);
for (const nav of document.getElementsByTagName("nav")) {
for (let el of nav.getElementsByTagName("li")) {
el = el.children[0];
if (!(el instanceof HTMLAnchorElement)) continue;
const name = [...el.children].find(v => v.classList.contains("name"));
const page = [...el.children].find(v => v.classList.contains("page"));
const href = decodeURIComponent(/#(.+)$/.exec(el.href)?.[1]);
const target = document.getElementById(href);
const i = pages.findIndex(v => v.contains(target));
page.innerText = i + 1;
page.href = "#" + href;
// console.log(target, name, i);
}
}
for (let i = 0; i < 3; i++) {
pages[i].getElementsByClassName("pagedjs_margin-bottom-right-corner-holder")[0].innerText = "";
console.log(pages[i].getElementsByClassName("pagedjs_margin-bottom-right-corner-holder")[0]);
}
};
</script>
</head>
<body>
<div class="page title-page">
<div class="school-header">
<img class="school-img" src="{{school_img}}"/>
<h4>{{school_name}}</h4>
</div>
<div class="title-content">
<h2>ДИПЛОМНА РАБОТА</h2>
<div>по професия код {{profession}}</div>
<div>специалност код {{specialty}}</div>
</div>
<div class="title-content">Тема: {{topic}}</div>
<div class="title-authors">
<div class="title-author">
<div class="author-type">Дипломант:</div>
<div class="author-name">{{author}}</div>
</div>
<div class="title-author">
<div class="author-type">Научен ръководител:</div>
<div class="author-name">{{supervisor}}</div>
</div>
</div>
<div class="title-end">СОФИЯ - {{year}}</div>
</div>
<div class="page asm-page">
<div class="school-header">
<img class="school-img" src="{{school_img}}"/>
<h4>{{school_name}}</h4>
</div>
<div class="asm-header">
<div class="asm-header-dates">
<div>Дата на заданието: 28.10.{{prev_year}} г.</div>
<div>Дата на предаване: 28.01.{{year}} г.</div>
</div>
<div class="asm-signature">
<div class="asm-signature-place">Утвърждавам: ..............................</div>
<div class="asm-signature-person">/{{ensurer_name}}/ </div>
</div>
</div>
<div class="asm-content">
<div class="asm-title">
<h2>ЗАДАНИЕ<br/>за дипломна работа</h2>
<h4>ДЪРЖАВЕН ИЗПИТ ЗА ПРИДОБИВАНЕ НА ТРЕТА СТЕПЕН НА ПРОФЕСИОНАЛНА КВАЛИФИКАЦИЯ</h4>
<div>
<div>по професия код {{profession}}</div>
<div>специалност код {{specialty}}</div>
</div>
</div>
<div class="asm-requirements">
на ученика {{author}} от {{class}} клас<br/>
{{requirements}}
</div>
<div class="asm-authors">
<div class="asm-signature">
<div class="author-type">Дипломант: ...........................................</div>
<div class="author-name">/{{author}}/</div>
</div>
<div class="asm-signature">
<div class="author-type">Ръководител: ...........................................</div>
<div class="author-name">/{{supervisor}}/</div>
</div>
<div class="asm-signature">
<div class="author-type">{{head_teacher_title}}: ...........................................</div>
<div class="author-name">/{{head_teacher_name}}/</div>
</div>
</div>
</div>
</div>
<div class="page">
<div>prazna str</div>
</div>
{{content}}
<nav>
{{toc}}
</nav>
</body>
</html>

View File

@ -1,4 +1,4 @@
project_group = me.topchetoeu.j2s
project_name = j2s
project_version = 0.10.11-beta
main_class = me.topchetoeu.j2s.repl.SimpleRepl
project_group = me.topchetoeu
project_name = jscript
project_version = 0.10.0-beta
main_class = me.topchetoeu.jscript.repl.SimpleRepl

View File

@ -1,84 +0,0 @@
import com.github.gradle.node.npm.task.NpmTask;
plugins {
id("common-java");
id("com.github.node-gradle.node") version "5.0.0";
}
dependencies {
implementation(project(":common"));
implementation(project(":compilation"));
implementation(project(":runtime"));
}
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>("compileBabel") {
dependsOn("npmInstall");
inputs.files("rollup.config.js");
inputs.dir("src/transpiler");
outputs.files("build/js/babel.js");
// nom nom tasty ram
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");
args.set(listOf("run", "build-babel"));
}
tasks.register<NpmTask>("compileTypescript") {
dependsOn("npmInstall");
inputs.files("rollup.config.js");
inputs.dir("src/transpiler");
outputs.files("build/js/typescript.js");
// nom nom tasty ram
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");
args.set(listOf("run", "build-typescript"));
}
tasks.register<NpmTask>("compileCoffee") {
dependsOn("npmInstall");
inputs.files("rollup.config.js");
inputs.dir("src/transpiler");
outputs.files("build/js/coffee.js");
// nom nom tasty ram
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");
args.set(listOf("run", "build-coffee"));
}
tasks.jar {
manifest {
attributes(
"Main-Class" to properties["main_class"].toString(),
"Build-Author" to "TopchetoEU",
);
}
}
tasks.processResources {
dependsOn("compileStdlib");
dependsOn("compileTypescript");
dependsOn("compileBabel");
dependsOn("compileCoffee");
from("build/js") {
into("lib");
}
from("src/lib") {
into("lib");
}
}

View File

@ -1,135 +0,0 @@
package me.topchetoeu.j2s.lib;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Metadata;
import me.topchetoeu.j2s.common.Reading;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.runtime.Compiler;
import me.topchetoeu.j2s.runtime.debug.DebugHandler;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.CodeFunction;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.functions.NativeFunction;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
public class Compilers {
public static Compiler jsCompiler() {
return (env, filename, raw, mapper) -> {
try {
var res = JavaScript.compile(env, filename, raw, true);
var body = res.body();
DebugHandler.get(env).onSourceLoad(filename, raw);
for (var el : res.all()) {
DebugHandler.get(env).onFunctionLoad(el.body(), el.map(mapper));
}
return new CodeFunction(env, filename.toString(), body, new Value[0][]);
}
catch (SyntaxException e) {
var res = EngineException.ofSyntax(e.msg);
res.add(env, e.loc.filename() + "", e.loc);
throw res;
}
};
}
public static Compiler wrap(Compiler first, Environment compilerEnv, Environment targetEnv, FunctionValue factory) {
var curr = new NativeFunction(args -> {
var filename = Filename.parse(args.get(0).toString(args.env));
var src = args.get(1).toString(args.env);
var mapper = (FunctionValue)args.get(2);
return first.compile(targetEnv, filename, src, NativeMapper.unwrap(args.env, mapper));
});
var next = (FunctionValue)factory.apply(compilerEnv, Value.UNDEFINED, curr);
return (env, filename, source, map) -> {
return (FunctionValue)next.apply(
compilerEnv, Value.UNDEFINED,
StringValue.of(filename.toString()),
StringValue.of(source),
new NativeMapper(map)
);
};
}
public static Compiler transpilerFromSource(Compiler prev, Environment target, Filename compilerName, String compilerSrc) {
var env = StdLib.apply(null);
// var handler = new SimpleDebugHandler();
// env.add(DebugHandler.KEY, handler);
var glob = Value.global(env);
var compilerFactory = new FunctionValue[1];
glob.defineOwnField(env, "getResource", new NativeFunction(args -> {
var name = args.get(0).toString(args.env);
var src = Reading.resourceToString("lib/" + name);
if (src == null) return Value.UNDEFINED;
else return StringValue.of(src);
}));
glob.defineOwnField(env, "register", new NativeFunction(args -> {
var func = (FunctionValue)args.get(0);
compilerFactory[0] = func;
return Value.UNDEFINED;
}));
glob.defineOwnField(env, "registerSource", new NativeFunction(args -> {
var filename = Filename.parse(args.get(0).toString(args.env));
var src = args.get(1).toString(args.env);
DebugHandler.get(target).onSourceLoad(filename, src);
return Value.UNDEFINED;
}));
var compiled = JavaScript.compile(compilerName, compilerSrc, false);
// for (var el : compiled.all()) {
// handler.onFunctionLoad(el.body(), el.map());
// }
try {
new CodeFunction(env, "intializer", compiled.body(), new Value[0][]).apply(env, Value.UNDEFINED);
return wrap(prev, env, target, compilerFactory[0]);
}
catch (EngineException e) {
System.out.println(Value.errorToReadable(env, e, "in transpiler initializer"));
return prev;
}
}
public static Compiler babelCompiler(Compiler prev, Environment target) {
return transpilerFromSource(prev, target,
new Filename(Metadata.name(), "babel.js"),
Reading.resourceToString("lib/babel.js")
);
}
public static Compiler typescriptCompiler(Compiler prev, Environment target) {
return transpilerFromSource(prev, target,
new Filename(Metadata.name(), "typescript.js"),
Reading.resourceToString("lib/typescript.js")
);
}
public static Compiler coffeescriptCompiler(Compiler prev, Environment target) {
return transpilerFromSource(prev, target,
new Filename(Metadata.name(), "coffee.js"),
Reading.resourceToString("lib/coffee.js")
);
}
public static interface TranspilerFactory {
Compiler create(Compiler prev, Environment target);
}
public static Compiler chainTranspilers(Compiler base, Environment target, TranspilerFactory ...factories) {
var res = base;
for (var el : factories) {
res = el.create(res, target);
}
return res;
}
}

View File

@ -1,41 +0,0 @@
package me.topchetoeu.j2s.lib;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Metadata;
import me.topchetoeu.j2s.common.Reading;
import me.topchetoeu.j2s.compilation.CompileResult;
import me.topchetoeu.j2s.compilation.JavaScript;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.CodeFunction;
public class StdLib {
private static final CompileResult RUNNER = JavaScript.compile(
new Filename(Metadata.name(), "init.js"),
Reading.resourceToString("lib/stdlib.js"), false
);
public static Environment apply(Environment env, CompileResult body) {
env = new Environment();
var stubEnv = new Environment();
Value.global(stubEnv).defineOwnField(stubEnv, "target", Value.global(env));
Value.global(stubEnv).defineOwnField(stubEnv, "primordials", Primordials.create(env));
var func = new CodeFunction(stubEnv, "intializer", body.body(), new Value[0][]);
try {
func.apply(stubEnv, Value.UNDEFINED);
}
catch (EngineException e) {
System.out.println(Value.errorToReadable(env, e, "in environment initializer"));
}
return env;
}
public static Environment apply(Environment env) {
if (env == null) env = new Environment();
return apply(env, RUNNER);
}
}

View File

@ -1,37 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import java.io.IOException;
import me.topchetoeu.j2s.runtime.debug.DebugHandler;
public interface Debugger extends DebugHandler {
void close();
void enable(V8Message msg) throws IOException;
void disable(V8Message msg) throws IOException;
void setBreakpointByUrl(V8Message msg) throws IOException;
void removeBreakpoint(V8Message msg) throws IOException;
void continueToLocation(V8Message msg) throws IOException;
void getScriptSource(V8Message msg) throws IOException;
void getPossibleBreakpoints(V8Message msg) throws IOException;
void resume(V8Message msg) throws IOException;
void pause(V8Message msg) throws IOException;
void stepInto(V8Message msg) throws IOException;
void stepOut(V8Message msg) throws IOException;
void stepOver(V8Message msg) throws IOException;
void setPauseOnExceptions(V8Message msg) throws IOException;
void evaluateOnCallFrame(V8Message msg) throws IOException;
void getProperties(V8Message msg) throws IOException;
void releaseObjectGroup(V8Message msg) throws IOException;
void releaseObject(V8Message msg) throws IOException;
void callFunctionOn(V8Message msg) throws IOException;
void runtimeEnable(V8Message msg) throws IOException;
}

View File

@ -1,5 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
public interface DebuggerProvider {
Debugger getDebugger(WebSocket socket, HttpRequest req);
}

View File

@ -1,100 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Map;
import me.topchetoeu.j2s.common.Reading;
public class HttpRequest {
public final String method;
public final String path;
public final Map<String, String> headers;
public final OutputStream out;
public void writeCode(int code, String name) {
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeLastHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeadersEnd() {
try { out.write("\n".getBytes()); }
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, byte[] data) {
writeCode(code, name);
writeHeader("Content-Type", type);
writeLastHeader("Content-Length", data.length + "");
try {
out.write(data);
out.close();
}
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, InputStream data) {
writeResponse(code, name, type, Reading.streamToBytes(data));
}
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
this.method = method;
this.path = path;
this.headers = headers;
this.out = out;
}
// We dont need no http library
public static HttpRequest read(Socket socket) {
try {
var str = socket.getInputStream();
var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine();
var i1 = line.indexOf(" ");
var i2 = line.indexOf(" ", i1 + 1);
if (i1 < 0 || i2 < 0) {
socket.close();
return null;
}
var method = line.substring(0, i1).trim().toUpperCase();
var path = line.substring(i1 + 1, i2).trim();
var headers = new HashMap<String, String>();
while (!(line = lines.readLine()).isEmpty()) {
var i = line.indexOf(":");
if (i < 0) continue;
var name = line.substring(0, i).trim().toLowerCase();
var value = line.substring(i + 1).trim();
if (name.length() == 0) continue;
headers.put(name, value);
}
if (headers.containsKey("content-length")) {
try {
var i = Integer.parseInt(headers.get("content-length"));
str.skip(i);
}
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
}
return new HttpRequest(method, path, headers, socket.getOutputStream());
}
catch (IOException | NullPointerException e) { return null; }
}
}

View File

@ -1,76 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import java.util.HashMap;
import java.util.WeakHashMap;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.FunctionMap;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.debug.DebugHandler;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
public class SimpleDebugHandler implements DebugHandler {
private HashMap<Filename, String> sources;
private WeakHashMap<FunctionBody, FunctionMap> maps;
private DebugHandler debugger;
public synchronized boolean attachDebugger(DebugHandler debugger) {
if (this.debugger != null) return false;
if (sources != null) {
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
}
if (maps != null) {
for (var map : maps.entrySet()) debugger.onFunctionLoad(map.getKey(), map.getValue());
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger(DebugHandler debugger) {
if (this.debugger != debugger) return false;
return detachDebugger();
}
public boolean detachDebugger() {
this.debugger = null;
return true;
}
public DebugHandler debugger() {
return debugger;
}
public FunctionMap getMap(Environment env, FunctionBody func) {
if (maps == null) return null;
return maps.get(func);
}
public void onFramePop(Environment env, Frame frame) {
if (debugger != null) debugger.onFramePop(env, frame);
}
public void onFramePush(Environment env, Frame frame) {
if (debugger != null) debugger.onFramePush(env, frame);
}
@Override public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Value returnVal, EngineException error, boolean caught) {
if (debugger != null) return debugger.onInstruction(env, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSourceLoad(Filename filename, String source) {
if (debugger != null) debugger.onSourceLoad(filename, source);
if (sources != null) sources.put(filename, source);
}
@Override public void onFunctionLoad(FunctionBody func, FunctionMap map) {
if (maps != null) maps.put(func, map);
if (debugger != null) debugger.onFunctionLoad(func, map);
}
public SimpleDebugHandler() {
sources = new HashMap<>();
maps = new WeakHashMap<>();
}
}

View File

@ -1,19 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Error {
public final String message;
public V8Error(String message) {
this.message = message;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap().set("error", new JSONMap()
.set("message", message)
));
}
}

View File

@ -1,22 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Event {
public final String name;
public final JSONMap params;
public V8Event(String name, JSONMap params) {
this.name = name;
this.params = params;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
);
}
}

View File

@ -1,50 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import java.util.Map;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONElement;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Message {
public final String name;
public final int id;
public final JSONMap params;
public V8Message(String name, int id, Map<String, JSONElement> params) {
this.name = name;
this.params = new JSONMap(params);
this.id = id;
}
public V8Result respond(JSONMap result) {
return new V8Result(id, result);
}
public V8Result respond() {
return new V8Result(id, new JSONMap());
}
public V8Message(JSONMap raw) {
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
this.name = raw.string("method");
this.id = (int)raw.number("id");
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
}
public V8Message(String raw) {
this(JSON.parse(null, raw).map());
}
public JSONMap toMap() {
var res = new JSONMap();
return res;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
.set("id", id)
);
}
}

View File

@ -1,22 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Result {
public final int id;
public final JSONMap result;
public V8Result(int id, JSONMap result) {
this.id = id;
this.result = result;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("id", id)
.set("result", result)
);
}
}

View File

@ -1,186 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import me.topchetoeu.j2s.lib.debug.WebSocketMessage.Type;
public class WebSocket implements AutoCloseable {
public long maxLength = 1 << 20;
private Socket socket;
private boolean closed = false;
private OutputStream out() throws IOException {
return socket.getOutputStream();
}
private InputStream in() throws IOException {
return socket.getInputStream();
}
private long readLen(int byteLen) throws IOException {
long res = 0;
if (byteLen == 126) {
res |= in().read() << 8;
res |= in().read();
return res;
}
else if (byteLen == 127) {
res |= in().read() << 56;
res |= in().read() << 48;
res |= in().read() << 40;
res |= in().read() << 32;
res |= in().read() << 24;
res |= in().read() << 16;
res |= in().read() << 8;
res |= in().read();
return res;
}
else return byteLen;
}
private byte[] readMask(boolean has) throws IOException {
if (has) {
return new byte[] {
(byte)in().read(),
(byte)in().read(),
(byte)in().read(),
(byte)in().read()
};
}
else return new byte[4];
}
private void writeLength(int len) throws IOException {
if (len < 126) {
out().write((int)len);
}
else if (len <= 0xFFFF) {
out().write(126);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
else {
out().write(127);
out().write(0);
out().write(0);
out().write(0);
out().write(0);
out().write((len >> 24) & 0xFF);
out().write((len >> 16) & 0xFF);
out().write((len >> 8) & 0xFF);
out().write(len & 0xFF);
}
}
private synchronized void write(int type, byte[] data) throws IOException {
out().write(type | 0x80);
writeLength(data.length);
out().write(data);
}
public void send(String data) throws IOException {
if (closed) throw new IllegalStateException("Websocket is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) throws IOException {
if (closed) throw new IllegalStateException("Websocket is closed.");
write(2, data);
}
public void send(WebSocketMessage msg) throws IOException {
if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData());
}
public void send(Object data) throws IOException {
if (closed) throw new IllegalStateException("Websocket is closed.");
write(1, data.toString().getBytes());
}
public void close(String reason) {
if (socket != null) {
try {
write(8, reason.getBytes());
socket.close();
}
catch (Throwable e) { }
}
socket = null;
closed = true;
}
public void close() {
close("");
}
private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason);
close(reason);
return null;
}
private byte[] readData() throws IOException {
var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else {
var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else {
for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)];
}
return buff;
}
}
return null;
}
public WebSocketMessage receive() throws IOException {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
if (finId < 0) break;
var fin = (finId & 0x80) != 0;
int id = finId & 0x0F;
if (id == 0x8) { close(); return null; }
if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray());
continue;
}
if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData();
if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff);
if (!fin) continue;
var raw = data.toByteArray();
if (type == 1) {
return new WebSocketMessage(new String(raw));
}
else return new WebSocketMessage(raw);
}
return null;
}
public WebSocket(Socket socket) {
this.socket = socket;
}
}

View File

@ -1,29 +0,0 @@
package me.topchetoeu.j2s.lib.debug;
public class WebSocketMessage {
public static enum Type {
Text,
Binary,
}
public final Type type;
private final Object data;
public final String textData() {
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
return (String)data;
}
public final byte[] binaryData() {
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
return (byte[])data;
}
public WebSocketMessage(String data) {
this.type = Type.Text;
this.data = data;
}
public WebSocketMessage(byte[] data) {
this.type = Type.Binary;
this.data = data;
}
}

View File

@ -1,3 +0,0 @@
export default function _classPrivateFieldLooseBase(obj) {
return obj;
}

View File

@ -1,13 +0,0 @@
import { print, self, symbol } from "../stdlib/primordials.ts";
import { Object } from "../stdlib/values/object.ts";
self.Object = {
defineProperty: function (obj, key, desc) {
if (obj == null) return obj;
Object.defineProperty(obj, key, desc);
}
};
export default function _classPrivateFieldLooseKey(key) {
return symbol.makeSymbol(key);
}

View File

@ -1,41 +0,0 @@
import { buffer, type InternalBuffer, map, symbol } from "../primordials.ts";
export const abs = new map(true);
export class ArrayBuffer {
#internal!: InternalBuffer;
public get byteLength() {
return this.#internal.length;
}
public get byteOffset() {
return 0;
}
public constructor(val: unknown) {
if (buffer.isBuff(val)) this.#internal = val;
else this.#internal = buffer.buff(Number(val));
}
public static unwrap(instance: ArrayBuffer) {
return instance.#internal;
}
}
function wrapAB(buff: InternalBuffer): ArrayBuffer {
let res = abs.get(buff);
if (res == null) {
res = new ArrayBuffer(buff);
abs.set(buff, res);
}
return res;
}
const unwrapAB = ArrayBuffer.unwrap;
delete (ArrayBuffer as any).unwrap;
export { wrapAB, unwrapAB };

View File

@ -1,149 +0,0 @@
import { next } from "../primordials.ts";
enum PromiseState {
Pending = "pend",
Fulfilled = "ful",
Rejected = "rej",
}
export class Promise<T> {
static #InternalPromise = function <T>(this: Promise<T>) {
this.#state = PromiseState.Pending;
this.#fulHandles = [];
this.#rejHandles = [];
} as any as new <T>() => Promise<T>;
static {
this.#InternalPromise.prototype = this.prototype;
}
#state: PromiseState;
#value?: T | unknown;
#fulHandles?: ((val: T) => void)[] = [];
#rejHandles?: ((val: T) => void)[] = [];
#fulfill(val: any) {
if (this.#state !== PromiseState.Pending) return;
if (this === val) throw new Error("A promise may not be fulfilled with itself");
if (val != null && typeof val.then === "function") {
val.then(
(val: any) => this.#fulfill(val),
(err: any) => this.#reject(err),
);
}
else {
this.#value = val;
this.#state = PromiseState.Fulfilled;
const handles = this.#fulHandles!;
for (let i = 0; i < handles.length; i++) {
handles[i](val);
}
this.#fulHandles = undefined;
this.#rejHandles = undefined;
}
}
#reject(val: any) {
if (this.#state !== PromiseState.Pending) return;
if (this === val) throw new Error("A promise may not be rejected with itself");
if (val != null && typeof val.then === "function") {
val.then(
(val: any) => this.#reject(val),
(err: any) => this.#reject(err),
);
}
else {
this.#value = val;
this.#state = PromiseState.Rejected;
const handles = this.#rejHandles!;
for (let i = 0; i < handles.length; i++) {
handles[i](val);
}
this.#fulHandles = undefined;
this.#rejHandles = undefined;
}
}
#handle(ful?: (val: T) => void, rej?: (err: any) => void) {
if (this.#state === PromiseState.Pending) {
if (ful != null) {
this.#fulHandles![this.#fulHandles!.length] = ful;
}
if (rej != null) {
this.#rejHandles![this.#rejHandles!.length] = rej;
}
}
else if (this.#state === PromiseState.Fulfilled) {
if (ful != null) ful(this.#value as T);
}
else if (this.#state === PromiseState.Rejected) {
if (rej != null) rej(this.#value);
}
}
public then<Res>(ful?: (val: T) => Res, rej?: (err: any) => Res) {
if (typeof ful !== "function") ful = undefined;
if (typeof rej !== "function") rej = undefined;
const promise = new Promise.#InternalPromise<Res>();
this.#handle(
val => next(() => {
if (ful == null) promise.#fulfill(val);
else {
try { promise.#fulfill(ful(val)); }
catch (e) { promise.#reject(e); }
}
}),
err => next(() => {
if (rej == null) promise.#reject(err);
else {
try { promise.#fulfill(rej(err)); }
catch (e) { promise.#reject(e); }
}
}),
);
return promise;
}
public catch<Res>(rej?: (err: any) => Res) {
return this.then(undefined, rej);
}
public finally(fn?: () => void) {
if (typeof fn !== "function") return this["then"]();
return this.then(
v => {
fn();
return v;
},
v => {
fn();
throw v;
},
)
}
public constructor(fn: (fulfil: (val: T) => void, reject: (err: unknown) => void) => void) {
this.#state = PromiseState.Pending;
fn(val => this.#fulfill(val), err => this.#reject(err));
}
public static resolve(val: any) {
const res = new this.#InternalPromise();
res.#fulfill(val);
return res;
}
public static reject(val: any) {
const res = new this.#InternalPromise();
res.#reject(val);
return res;
}
}

View File

@ -1,63 +0,0 @@
import { Promise as Promise } from "./classes/promise";
import { func, InternalServer, InternalSocket, symbol } from "./primordials";
import { Error } from "./values/errors";
const socketToken = symbol.makeSymbol("ServerSocket.token");
export class ServerSocket {
#internal: InternalSocket;
public read() {
return new Promise((ful, rej) => {
this.#internal.read(ful, rej, ful as any);
});
}
public write(data: Uint8Array) {
return new Promise<void>((ful, rej) => {
this.#internal.write(data, ful, rej);
});
}
public next() {
return new Promise((ful, rej) => {
this.#internal.read(
data => ful({ value: data, done: false }),
rej,
() => ful({ value: undefined, done: true })
);
});
}
public [Symbol.iterator](): this {
return this;
}
public constructor(token: typeof socketToken, socket: InternalSocket) {
if (token !== socketToken) throw new Error("Invalid token for creation");
this.#internal = socket;
}
}
export class Server {
#internal: InternalServer;
public bind(address: string) {
return new Promise<void>((res, rej) => this.#internal.bind(address, res, rej));
}
public next() {
return new Promise((ful, rej) => {
this.#internal.next(
data => ful({ value: new ServerSocket(socketToken, data), done: false }),
rej,
() => ful({ value: undefined, done: true })
);
});
}
public [Symbol.iterator](): this {
return this;
}
public constructor(server: InternalServer) {
this.#internal = server;
}
}

View File

@ -1,3 +0,0 @@
import babel from "./babel.ts";
register(babel);

View File

@ -1,3 +0,0 @@
import coffee from "./coffeescript.ts";
register(coffee);

View File

@ -1,3 +0,0 @@
import ts from "./typescript.ts";
register(ts);

View File

@ -1,84 +0,0 @@
import { SourceMap } from "./map.ts";
import { transform, availablePlugins } from "@babel/standalone";
export default function babel(next: Compiler): Compiler {
print("Loaded babel!");
return (filename, code, prevMap) => {
const res = transform(code, {
filename,
sourceMaps: true,
assumptions: {
arrayLikeIsIterable: true,
constantSuper: true,
ignoreFunctionLength: true,
ignoreToPrimitiveHint: true,
mutableTemplateObject: true,
noDocumentAll: true,
noNewArrows: true,
noUninitializedPrivateFieldAccess: true,
privateFieldsAsSymbols: true,
},
plugins: [
// ES2022
availablePlugins["transform-class-properties"],
availablePlugins["transform-class-static-block"],
availablePlugins["transform-private-methods"],
availablePlugins["transform-private-property-in-object"],
// "syntax-top-level-await",
// ES2021
availablePlugins["transform-logical-assignment-operators"],
availablePlugins["transform-numeric-separator"],
// ES2020
availablePlugins["transform-optional-chaining"],
availablePlugins["transform-nullish-coalescing-operator"],
// ES2018
availablePlugins["transform-async-generator-functions"],
availablePlugins["transform-object-rest-spread"],
availablePlugins["transform-unicode-property-regex"],
// ES2017
availablePlugins["transform-async-to-generator"],
// ES2016
availablePlugins["transform-exponentiation-operator"],
// ES2015
availablePlugins["transform-regenerator"],
availablePlugins["transform-arrow-functions"],
availablePlugins["transform-block-scoping"],
availablePlugins["transform-classes"],
availablePlugins["transform-computed-properties"],
availablePlugins["transform-destructuring"],
availablePlugins["transform-duplicate-keys"],
availablePlugins["transform-for-of"],
availablePlugins["transform-function-name"],
availablePlugins["transform-literals"],
availablePlugins["transform-new-target"],
availablePlugins["transform-object-super"],
availablePlugins["transform-parameters"],
availablePlugins["transform-shorthand-properties"],
availablePlugins["transform-spread"],
availablePlugins["transform-sticky-regex"],
availablePlugins["transform-template-literals"],
availablePlugins["transform-unicode-escapes"],
availablePlugins["transform-unicode-regex"],
],
});
const map = SourceMap.parse({
file: "babel-internal://" + filename,
mappings: res.map!.mappings,
sources: [filename],
});
registerSource(filename, code);
const func = next("babel-internal://" + filename, res.code!, SourceMap.chain(map, prevMap));
func.name = filename;
return func;
};
}

View File

@ -1,17 +0,0 @@
{
"include": ["**/*.ts"],
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ESNext",
"noLib": true,
"forceConsistentCasingInFileNames": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowImportingTsExtensions": true,
"noEmit": true
}
}

View File

@ -1,9 +1,7 @@
{
"scripts": {
"build-env": "rollup -c --environment INPUT:src/stdlib/_entry.ts,OUTPUT:build/js/stdlib.js,POLYFILLS:src/polyfills",
"build-babel": "rollup -c --environment INPUT:src/transpiler/_entry-babel.ts,OUTPUT:build/js/babel.js",
"build-coffee": "rollup -c --environment INPUT:src/transpiler/_entry-coffee.ts,OUTPUT:build/js/coffee.js",
"build-typescript": "rollup -c --environment INPUT:src/transpiler/_entry-typescript.ts,OUTPUT:build/js/typescript.js"
"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"
},
"dependencies": {
"@babel/core": "^7.26.0",
@ -17,7 +15,6 @@
"typescript": "^5.7.2"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/plugin-transform-typescript": "^7.25.9",

View File

@ -1,32 +0,0 @@
plugins {
id("common-java");
id("com.gradleup.shadow") version "9.0.0-beta4";
}
description = "A simple REPL for the interpreter, can be used for simple prototyping";
tasks.test {
useJUnitPlatform();
}
tasks.jar {
dependsOn("shadowJar");
manifest {
attributes(
"Main-Class" to properties["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',
);
}
}
dependencies {
implementation(project(":common"));
implementation(project(":compilation"));
implementation(project(":runtime"));
implementation(project(":lib"));
}

View File

@ -1,155 +0,0 @@
package me.topchetoeu.j2s.repl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.Metadata;
import me.topchetoeu.j2s.common.Reading;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.lib.Compilers;
import me.topchetoeu.j2s.lib.StdLib;
import me.topchetoeu.j2s.lib.debug.DebugServer;
import me.topchetoeu.j2s.lib.debug.Debugger;
import me.topchetoeu.j2s.lib.debug.SimpleDebugHandler;
import me.topchetoeu.j2s.lib.debug.SimpleDebugger;
import me.topchetoeu.j2s.runtime.Compiler;
import me.topchetoeu.j2s.runtime.Engine;
import me.topchetoeu.j2s.runtime.EventLoop;
import me.topchetoeu.j2s.runtime.debug.DebugHandler;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.NativeFunction;
public class SimpleRepl {
static Thread engineTask, debugTask;
static Engine engine = new Engine();
static Environment environment = Environment.empty();
static DebugServer server;
static Debugger debugger;
static Key<OutputStream> STDOUT = new Key<>();
static Key<OutputStream> STDERR = new Key<>();
static Key<InputStream> STDIN = new Key<>();
static int j = 0;
static String[] args;
private static void reader() {
try {
server = new DebugServer();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
server.targets.put("default", (socket, req) -> new SimpleDebugger(socket)
.attach((SimpleDebugHandler)DebugHandler.get(environment))
);
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
for (var arg : args) {
var file = new File(arg);
var raw = Reading.streamToString(new FileInputStream(file));
try {
try {
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file), raw, null
).get();
System.err.println(res.toReadable(environment));
}
catch (ExecutionException e) { throw e.getCause(); }
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
}
for (var i = 0; ; i++) {
var raw = Reading.readline();
if (raw == null) break;
try {
try {
var res = engine.pushMsg(
false, environment,
new Filename(Metadata.name(), "repl/" + i + ".js"), raw,
Value.UNDEFINED
).get();
System.err.println(res.toReadable(environment));
}
catch (ExecutionException e) { throw e.getCause(); }
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
}
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
catch (IOException e) {
System.out.println(e.toString());
engine.thread().interrupt();
}
catch (CancellationException | InterruptedException e) { return; }
catch (Throwable ex) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
private static Environment createESEnv() {
var env = StdLib.apply(null);
env.add(EventLoop.KEY, engine);
env.add(DebugHandler.KEY, new SimpleDebugHandler());
env.add(Compiler.KEY, Compilers.chainTranspilers(Compilers.jsCompiler(), env, Compilers::babelCompiler));
var glob = Value.global(env);
glob.defineOwnField(null, "exit", new NativeFunction("exit", args -> {
Thread.currentThread().interrupt();
throw new CancellationException();
}));
glob.defineOwnField(null, "dofile", new NativeFunction("dofile", args -> {
var file = args.get(0).toString(args.env);
var filename = new Filename("file", new File(file).getAbsolutePath());
try {
var src = Reading.streamToString(new FileInputStream(file));
return Compiler.get(args.env).compile(args.env, filename, src, v -> v).apply(args.env, Value.UNDEFINED);
}
catch (FileNotFoundException e) {
throw EngineException.ofError("IOException", e.getMessage());
}
}));
return env;
}
private static void initEngine() {
engineTask = engine.start();
}
public static void main(String args[]) throws InterruptedException {
SimpleRepl.args = args;
var reader = new Thread(SimpleRepl::reader);
environment = createESEnv();
initEngine();
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
reader.join();
engineTask.interrupt();
debugTask.interrupt();
}
}

View File

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>J2S Debugger</title>
</head>
<body>
<p>
This is the debugger of J2S. It implement the <a href="https://chromedevtools.github.io/devtools-protocol/1-2/">V8 Debugging protocol</a>,
so you can use the devtools in chrome.<br>
The debugger is still in early development, so please report any issues to
<a href="https://git.topcheto.eu/topchetoeu/j2s/issues">the git repo</a>.
</p>
<p>
Here are the available entrypoints:
<ul>
<li><a href="json/version">/json/version</a> - version and other stuff about the J2S engine</li>
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
<li>/(any target) - websocket entrypoints for debugging</li>
</ul>
</p>
<p>
Running ${NAME} v${VERSION} by ${AUTHOR}
</p>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ const construct = (input, output) => defineConfig({
optimizeConstEnums: true,
allowDeclareFields: true,
}],
["@babel/plugin-transform-class-properties"],
["@babel/plugin-transform-runtime", {
moduleName: shouldPolyfill() ? "!polyfills:" : undefined,
version: "^7.24.0",
@ -47,7 +48,6 @@ const construct = (input, output) => defineConfig({
assumptions: {
ignoreToPrimitiveHint: true,
noClassCalls: true,
privateFieldsAsProperties: true,
},
env: {
@ -56,7 +56,6 @@ const construct = (input, output) => defineConfig({
babelHelpers: "runtime",
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-classes",
@ -78,7 +77,7 @@ const construct = (input, output) => defineConfig({
"@babel/plugin-transform-optional-chaining",
"@babel/plugin-transform-logical-assignment-operators",
"@babel/plugin-transform-numeric-separator",
"@babel/plugin-transform-private-methods",
"@babel/plugin-transform-class-properties",
"@babel/plugin-transform-class-static-block",
"@babel/plugin-transform-regenerator",

View File

@ -1,13 +0,0 @@
plugins {
id("common-java");
}
description = "The runtime of J2S, used to execute J2S bytecode";
tasks.test {
useJUnitPlatform();
}
dependencies {
implementation(project(":common"));
}

View File

@ -1,53 +0,0 @@
package me.topchetoeu.j2s.runtime;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ArrayLikeValue;
public class ArgumentsValue extends ArrayLikeValue {
public final Frame frame;
private Value[] args;
private boolean shadowed;
private void shadow() {
if (!shadowed) {
var newArgs = new Value[args.length];
System.arraycopy(args, 0, newArgs, 0, args.length);
args = newArgs;
shadowed = true;
}
}
@Override public Value get(int i) {
return args[i];
}
@Override public boolean has(int i) {
if (i < 0 || i >= size()) return false;
if (shadowed) return args[i] != null;
else return true;
}
@Override public boolean remove(int i) {
shadow();
args[i] = null;
return true;
}
@Override public boolean set(Environment env, int i, Value val) {
shadow();
args[i] = val;
return true;
}
@Override public boolean setSize(int val) {
return false;
}
@Override public int size() {
return args.length;
}
public ArgumentsValue(Frame frame) {
this.frame = frame;
this.args = frame.args;
this.shadowed = false;
setPrototype(e -> null);
}
}

View File

@ -1,26 +0,0 @@
package me.topchetoeu.j2s.runtime;
import java.util.function.Function;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
public interface Compiler {
public Key<Compiler> KEY = new Key<>();
public FunctionValue compile(Environment env, Filename filename, String source, Function<Location, Location> map);
public static Compiler get(Environment ext) {
return ext.get(KEY, (env, filename, src, map) -> {
throw EngineException.ofError("No compiler attached to engine");
});
}
public static FunctionValue compileFunc(Environment env, Filename filename, String raw) {
return get(env).compile(env, filename, raw, v -> v);
}
}

View File

@ -1,107 +0,0 @@
package me.topchetoeu.j2s.runtime.debug;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.FunctionMap;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.CodeFunction;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
public interface DebugHandler {
public static final Key<DebugHandler> KEY = new Key<>();
public static final Key<Void> IGNORE = new Key<>();
public static DebugHandler EMPTY = new DebugHandler() {
@Override public void onSourceLoad(Filename filename, String source) { }
@Override public void onFunctionLoad(FunctionBody body, FunctionMap map) { }
@Override public boolean onInstruction(
Environment env, Frame frame, Instruction instruction, Value returnVal, EngineException error, boolean caught
) { return false; }
@Override public void onFramePush(Environment env, Frame frame) { }
@Override public void onFramePop(Environment env, Frame frame) { }
@Override public FunctionMap getMap(Environment env, FunctionBody body) { return null; }
};
/**
* Called when a script has been loaded
* @param filename The name of the source
* @param source The name of the source
*/
public void onSourceLoad(Filename filename, String source);
/**
* Called when a function body has been loaded
* @param body The body loaded
* @param map The map of the function
*/
public void onFunctionLoad(FunctionBody body, FunctionMap map);
/**
* Called immediately before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
* @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
* @return Whether or not the frame should restart (currently does nothing)
*/
public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Value returnVal, EngineException error, boolean caught);
/**
* Called immediately before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @return Whether or not the frame should restart (currently does nothing)
*/
public default boolean onInstruction(Environment env, Frame frame, Instruction instruction) {
return onInstruction(env, frame, instruction, null, null, false);
}
/**
* Called immediatly before a frame has been pushed on the frame stack.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The code frame which was pushed
*/
public void onFramePush(Environment env, Frame frame);
/**
* Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The code frame which was popped out
*/
public void onFramePop(Environment env, Frame frame);
public FunctionMap getMap(Environment env, FunctionBody func);
public default FunctionMap getMap(Environment env, FunctionValue func) {
if (func instanceof CodeFunction codeFunc) return getMap(env, codeFunc.body);
else return null;
}
public default FunctionMap getMapOrEmpty(Environment env, FunctionBody func) {
var res = getMap(env, func);
if (res == null) return FunctionMap.EMPTY;
else return res;
}
public default FunctionMap getMapOrEmpty(Environment env, FunctionValue func) {
if (func instanceof CodeFunction codeFunc) return getMapOrEmpty(env, codeFunc.body);
else return null;
}
public static DebugHandler get(Environment exts) {
if (enabled(exts)) return exts.get(KEY);
else return EMPTY;
}
public static boolean enabled(Environment exts) {
return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE);
}
}

View File

@ -1,39 +0,0 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
public interface PrimitiveValue extends Value {
public default boolean defineOwnField(
Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) { return false; }
public default boolean defineOwnProperty(
Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set,
Boolean enumerable, Boolean configurable
) { return false; }
public default boolean deleteOwnMember(Environment env, KeyCache key) { return true; }
public default boolean isPrimitive() { return true; }
public default Value toPrimitive(Environment env) { return this; }
public default boolean setPrototype(Environment env, ObjectValue val) { return false; }
public default Member getOwnMember(Environment env, KeyCache key) { return null; }
public default Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
public default Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
public default State getState() { return State.FROZEN; }
public default void preventExtensions() {}
public default void seal() {}
public default void freeze() {}
}

View File

@ -1,43 +0,0 @@
package me.topchetoeu.j2s.runtime.values.primitives.numbers;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.PrimitiveValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
public interface NumberValue extends PrimitiveValue {
public static final NumberValue NAN = new DoubleValue(Double.NaN);
@Override public default StringValue type() { return StringValue.of("number"); }
public abstract double getDouble();
public abstract int getInt();
public abstract long getLong();
public abstract boolean isLong();
public abstract boolean isInt();
public abstract boolean equals(Object other);
public abstract String toString();
@Override public default boolean toBoolean() { return getDouble() != 0; }
@Override public default NumberValue toNumber(Environment ext) { return this; }
@Override public default String toString(Environment ext) { return toString(); }
@Override public default ObjectValue getPrototype(Environment env) {
return env.get(NUMBER_PROTO);
}
public static NumberValue of(double value) {
if (Double.isNaN(value)) return NAN;
else if ((int)value == value) return new IntValue((int)value);
else return new DoubleValue(value);
}
public static NumberValue of(long value) {
return new IntValue(value);
}
public static NumberValue of(int value) {
return new IntValue(value);
}
}

12
settings.gradle Normal file
View File

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

View File

@ -1,18 +0,0 @@
pluginManagement {
repositories {
mavenCentral();
gradlePluginPortal();
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0";
}
rootProject.name = extra.properties["project_name"].toString();
include(":lib");
include(":common");
include(":repl");
include(":runtime");
include(":compilation");

View File

@ -1,4 +1,4 @@
import { now, object, print, setGlobalPrototypes, setIntrinsic, target } from "./primordials.ts";
import { object, setGlobalPrototypes, target } from "./primordials.ts";
import { Error, RangeError, SyntaxError, TypeError } from "./values/errors.ts";
import { Boolean } from "./values/boolean.ts";
import { Function } from "./values/function.ts";
@ -21,6 +21,11 @@ import { Uint8Array } from "./arrays/Uint8Array.ts";
import { Int32Array } from "./arrays/Int32Array.ts";
import { TypedArray } from "./arrays/TypedArray.ts";
declare global {
function print(...args: any[]): void;
function measure(func: Function): void;
}
function fixup<T extends Function>(clazz: T) {
object.setPrototype(clazz, Function.prototype);
object.setPrototype(clazz.prototype as any, Object.prototype);
@ -67,16 +72,6 @@ target.NaN = Number.NaN;
target.Infinity = Number.POSITIVE_INFINITY;
target.encodeURI = encodeURI;
target.encodeURIComponent = encodeURIComponent;
target.print = print;
target.measure = (func: () => void) => {
const start = now();
try {
return func();
}
finally {
print(`Took ${now() - start}ms`);
}
};
setGlobalPrototypes({
string: String.prototype,
@ -92,20 +87,5 @@ setGlobalPrototypes({
type: TypeError.prototype,
uint8: Uint8Array.prototype,
int32: Int32Array.prototype,
});
setIntrinsic("regex", RegExp);
setIntrinsic("keys", (obj: object, own: boolean, onlyEnumerable: boolean) => {
const members = object.getMembers(obj, own, onlyEnumerable);
let i = 0;
return () => {
if (i >= members.length) return { done: true };
else return { value: members[i++] };
};
});
setIntrinsic("defGetter", (obj: object, key: any, func: Function) => {
object.defineProperty(obj, key, { g: func, e: true, c: true });
});
setIntrinsic("defSetter", (obj: object, key: any, func: Function) => {
object.defineProperty(obj, key, { s: func, e: true, c: true });
regex: RegExp,
});

View File

@ -0,0 +1,30 @@
import { buffer, type InternalBuffer, map, symbol } from "../primordials.ts";
export const abs = new map(true);
export const abKey: unique symbol = symbol.getSymbol("ArrayBuffer.impl") as any;
export class ArrayBuffer {
public [abKey]!: InternalBuffer;
public get byteLength() {
return this[abKey].length;
}
public get byteOffset() {
return 0;
}
public constructor(val: unknown) {
if (buffer.isBuff(val)) this[abKey] = val;
else this[abKey] = buffer.buff(Number(val));
}
}
export function getAB(buff: InternalBuffer): ArrayBuffer {
let res = abs.get(buff);
if (res == null) {
res = new ArrayBuffer(buff);
abs.set(buff, res);
}
return res;
}

View File

@ -1,5 +1,5 @@
import { buffer } from "../primordials.ts";
import { token, TypedArray, typedArrayFuncs } from "./TypedArray.ts";
import { abstractIgnore, TypedArray, typedArrayFuncs } from "./TypedArray.ts";
const factory = buffer.int32;
const funcs = typedArrayFuncs(4, factory);
@ -19,7 +19,7 @@ export class Int32Array extends TypedArray {
}
public constructor(obj: any, start?: number, end?: number) {
super(token);
super(abstractIgnore);
return funcs.construct(obj, start, end) as any;
}
}

View File

@ -1,9 +1,9 @@
import { buffer, func, type InternalBuffer, object, string, symbol } from "../primordials.ts";
import { symbols, wrapI } from "../utils.ts";
import { Error, TypeError } from "../values/errors.ts";
import { ArrayBuffer, unwrapAB, wrapAB } from "./ArrayBuffer.ts";
import { abKey, ArrayBuffer, getAB } from "./ArrayBuffer.ts";
export const token = symbol.getSymbol("TypedArray.abstractIgnore");
export const abstractIgnore = symbol.getSymbol("TypedArray.abstractIgnore");
export function typedArrayFuncs(perEl: number, constructor: (buff: InternalBuffer, start: number, end: number) => number[]) {
return {
@ -56,7 +56,7 @@ export function typedArrayFuncs(perEl: number, constructor: (buff: InternalBuffe
return constructor(buffer.buff(self * perEl), 0, self);
}
if (self instanceof ArrayBuffer) {
const internal = unwrapAB(self);
const internal = self[abKey];
if (start === undefined) start = 0;
if (end === undefined) end = (internal.length / perEl) | 0;
return constructor(internal, start, end);
@ -90,7 +90,7 @@ export function typedArrayFuncs(perEl: number, constructor: (buff: InternalBuffe
export class TypedArray {
public get buffer() {
return wrapAB(buffer.backer(this as any));
return getAB(buffer.backer(this as any));
}
public get byteOffset(): number {
throw new Error("abstract");
@ -212,8 +212,8 @@ export class TypedArray {
return this;
}
public constructor(_token?: typeof token) {
if (_token !== token) {
public constructor(token?: typeof abstractIgnore) {
if (token !== abstractIgnore) {
throw new TypeError("TypedArray constructor can't be called");
}
}

View File

@ -1,5 +1,5 @@
import { buffer } from "../primordials.ts";
import { token, TypedArray, typedArrayFuncs } from "./TypedArray.ts";
import { abstractIgnore, TypedArray, typedArrayFuncs } from "./TypedArray.ts";
const factory = buffer.uint8;
const funcs = typedArrayFuncs(1, factory);
@ -26,7 +26,7 @@ export class Uint8Array extends TypedArray {
}
public constructor(obj: any, start?: number, end?: number) {
super(token);
super(abstractIgnore);
return funcs.construct(obj, start, end) as any;
}
}

View File

@ -1,11 +1,13 @@
import { now, number, symbol } from "../primordials.ts";
import { now, symbol } from "../primordials.ts";
const timeKey: unique symbol = symbol.makeSymbol("") as any;
export const Date = (() => {
class Date {
#time: number;
[timeKey]!: number;
public constructor() {
this.#time = number.NaN;
}
public static now() {

View File

@ -1,49 +1,51 @@
import { Array } from "../values/array.ts";
import { func, map } from "../primordials.ts";
import { func, map, symbol } from "../primordials.ts";
import { symbols } from "../utils.ts";
const mapKey: unique symbol = symbol.makeSymbol("Map.impl") as any;
export class Map<K, V> {
#map: InstanceType<typeof map>;
private [mapKey]: InstanceType<typeof map>;
public get size() {
return this.#map.size();
return this[mapKey].size();
}
public get(key: K): V {
return this.#map.get(key);
return this[mapKey].get(key);
}
public has(key: K): boolean {
return this.#map.has(key);
return this[mapKey].has(key);
}
public set(key: K, val: V) {
this.#map.set(key, val);
this[mapKey].set(key, val);
return this;
}
public delete(key: K): boolean {
if (!this.#map.has(key)) return false;
if (!this[mapKey].has(key)) return false;
else {
this.#map.delete(key);
this[mapKey].delete(key);
return true;
}
}
public clear() {
this.#map.clear();
this[mapKey].clear();
}
public keys(): K[] {
return this.#map.keys();
return this[mapKey].keys();
}
public values(): V[] {
const res = this.#map.keys();
const res = this[mapKey].keys();
for (let i = 0; i < res.length; i++) {
res[i] = this.#map.get(res[i]);
res[i] = this[mapKey].get(res[i]);
}
return res;
}
public entries(): [K, V][] {
const res = this.#map.keys();
const res = this[mapKey].keys();
for (let i = 0; i < res.length; i++) {
res[i] = [res[i], this.#map.get(res[i])];
res[i] = [res[i], this[mapKey].get(res[i])];
}
return res;
}
@ -60,7 +62,7 @@ export class Map<K, V> {
}
public constructor(iterable?: Iterable<[K, V]>) {
const _map = this.#map = new map();
const _map = this[mapKey] = new map();
if (iterable != null) {
if (Array.isArray(iterable)) {
@ -79,31 +81,31 @@ export class Map<K, V> {
}
}
export class WeakMap<K, V> {
#map: InstanceType<typeof map>;
private [mapKey]: InstanceType<typeof map>;
public get(key: K): V {
return this.#map.get(key);
return this[mapKey].get(key);
}
public has(key: K): boolean {
return this.#map.has(key);
return this[mapKey].has(key);
}
public set(key: K, val: V) {
this.#map.set(key, val);
this[mapKey].set(key, val);
return this;
}
public delete(key: K): boolean {
if (!this.#map.has(key)) return false;
if (!this[mapKey].has(key)) return false;
else {
this.#map.delete(key);
this[mapKey].delete(key);
return true;
}
}
public clear() {
this.#map.clear();
this[mapKey].clear();
}
public constructor(iterable?: Iterable<[K, V]>) {
const _map = this.#map = new map(true);
const _map = this[mapKey] = new map(true);
if (iterable != null) {
if (Array.isArray(iterable)) {
@ -121,3 +123,6 @@ export class WeakMap<K, V> {
}
}
}
func.setCallable(Map, false);
func.setCallable(WeakMap, false);

View File

@ -0,0 +1,154 @@
import { func, next, object, symbol } from "../primordials.ts";
enum PromiseState {
Pending = "pend",
Fulfilled = "ful",
Rejected = "rej",
}
const pState: unique symbol = symbol.makeSymbol("Promise.state") as any;
const pValue: unique symbol = symbol.makeSymbol("Promise.value") as any;
const pFulHandles: unique symbol = symbol.makeSymbol("Promise.fulfillHandles") as any;
const pRejHandles: unique symbol = symbol.makeSymbol("Promise.rejectHandles") as any;
function makePromise<T>(): Promise<T> {
return object.setPrototype({
[pState]: PromiseState.Pending,
[pFulHandles]: [],
[pRejHandles]: [],
}, Promise.prototype) as Promise<T>;
}
function fulfill(self: Promise<any>, val: any) {
if (self[pState] !== PromiseState.Pending) return;
if (self === val) throw new Error("A promise may not be fulfilled with itself");
if (val != null && typeof val.then === "function") {
val.then(
(val: any) => fulfill(self, val),
(err: any) => reject(self, err),
);
}
else {
self[pValue] = val;
self[pState] = PromiseState.Fulfilled;
const handles = self[pFulHandles]!;
for (let i = 0; i < handles.length; i++) {
handles[i](val);
}
self[pFulHandles] = undefined;
self[pRejHandles] = undefined;
}
}
function reject(self: Promise<any>, val: any) {
if (self[pState] !== PromiseState.Pending) return;
if (self === val) throw new Error("A promise may not be rejected with itself");
if (val != null && typeof val.then === "function") {
val.then(
(val: any) => reject(self, val),
(err: any) => reject(self, err),
);
}
else {
self[pValue] = val;
self[pState] = PromiseState.Rejected;
const handles = self[pRejHandles]!;
for (let i = 0; i < handles.length; i++) {
handles[i](val);
}
self[pFulHandles] = undefined;
self[pRejHandles] = undefined;
}
}
function handle<T>(self: Promise<T>, ful?: (val: T) => void, rej?: (err: any) => void) {
if (self[pState] === PromiseState.Pending) {
if (ful != null) {
self[pFulHandles]![self[pFulHandles]!.length] = ful;
}
if (rej != null) {
self[pRejHandles]![self[pRejHandles]!.length] = rej;
}
}
else if (self[pState] === PromiseState.Fulfilled) {
if (ful != null) ful(self[pValue] as T);
}
else if (self[pState] === PromiseState.Rejected) {
if (rej != null) rej(self[pValue]);
}
}
export class Promise<T> {
public [pState]: PromiseState;
public [pValue]?: T | unknown;
public [pFulHandles]?: ((val: T) => void)[] = [];
public [pRejHandles]?: ((val: T) => void)[] = [];
public then<Res>(ful?: (val: T) => Res, rej?: (err: any) => Res) {
if (typeof ful !== "function") ful = undefined;
if (typeof rej !== "function") rej = undefined;
const promise = makePromise<Res>();
handle(this,
val => next(() => {
if (ful == null) fulfill(promise, val);
else {
try { fulfill(promise, ful(val)); }
catch (e) { reject(promise, e); }
}
}),
err => next(() => {
if (rej == null) reject(promise, err);
else {
try { fulfill(promise, rej(err)); }
catch (e) { reject(promise, e); }
}
}),
);
return promise;
}
public catch<Res>(rej?: (err: any) => Res) {
return this.then(undefined, rej);
}
public finally(fn?: () => void) {
if (typeof fn !== "function") return this["then"]();
return this.then(
v => {
fn();
return v;
},
v => {
fn();
throw v;
},
)
}
public constructor(fn: (fulfil: (val: T) => void, reject: (err: unknown) => void) => void) {
this[pState] = PromiseState.Pending;
fn(val => fulfill(this, val), err => reject(this, err));
}
public static resolve(val: any) {
const res = makePromise();
fulfill(res, val);
return res;
}
public static reject(val: any) {
const res = makePromise();
reject(res, val);
return res;
}
}
func.setCallable(Promise, false);

View File

@ -1,40 +1,42 @@
import { Array } from "../values/array.ts";
import { func, map } from "../primordials.ts";
import { func, map, symbol } from "../primordials.ts";
import { symbols } from "../utils.ts";
const mapKey: unique symbol = symbol.makeSymbol("Set.impl") as any;
export class Set<T> {
#map: InstanceType<typeof map>;
private [mapKey]: InstanceType<typeof map>;
public get size() {
return this.#map.size();
return this[mapKey].size();
}
public has(key: T): boolean {
return this.#map.has(key);
return this[mapKey].has(key);
}
public add(val: T) {
this.#map.set(val, true);
this[mapKey].set(val, true);
return this;
}
public delete(val: T): boolean {
if (!this.#map.has(val)) return false;
if (!this[mapKey].has(val)) return false;
else {
this.#map.delete(val);
this[mapKey].delete(val);
return true;
}
}
public clear() {
this.#map.clear();
this[mapKey].clear();
}
public keys(): T[] {
return this.#map.keys();
return this[mapKey].keys();
}
public values(): T[] {
return this.#map.keys();
return this[mapKey].keys();
}
public entries(): [T, T][] {
const res = this.#map.keys();
const res = this[mapKey].keys();
for (let i = 0; i < res.length; i++) {
res[i] = [res[i], res[i]];
@ -55,7 +57,7 @@ export class Set<T> {
}
public constructor(iterable?: Iterable<T>) {
const _map = this.#map = new map();
const _map = this[mapKey] = new map();
if (iterable != null) {
if (Array.isArray(iterable)) {
@ -75,28 +77,28 @@ export class Set<T> {
}
export class WeakSet<T> {
#map: InstanceType<typeof map>;
private [mapKey]: InstanceType<typeof map>;
public has(key: T): boolean {
return this.#map.has(key);
return this[mapKey].has(key);
}
public add(val: T) {
this.#map.set(val, true);
this[mapKey].set(val, true);
return this;
}
public delete(val: T): boolean {
if (!this.#map.has(val)) return false;
if (!this[mapKey].has(val)) return false;
else {
this.#map.delete(val);
this[mapKey].delete(val);
return true;
}
}
public clear() {
this.#map.clear();
this[mapKey].clear();
}
public constructor(iterable?: Iterable<T>) {
const _map = this.#map = new map(true);
const _map = this[mapKey] = new map(true);
if (iterable != null) {
if (Array.isArray(iterable)) {
@ -114,3 +116,6 @@ export class WeakSet<T> {
}
}
}
func.setCallable(Set, false);
func.setCallable(WeakSet, false);

View File

@ -1,4 +1,4 @@
import { func, object, print } from "../primordials.ts";
import { func, json, object } from "../primordials.ts";
export const console = {};

View File

@ -1,4 +1,4 @@
import { func, object } from "../stdlib/primordials.ts";
import { func, object } from "../primordials.ts";
export default function _callSuper(self, constr, args) {
return func.construct(object.getPrototype(constr), func.target(1), args || []);

View File

@ -1,4 +1,4 @@
import { func } from "../stdlib/primordials.ts";
import { func } from "../primordials.ts";
export default function _classCallCheck() {
if (func.invokeTypeInfer() !== "new") throw new TypeError("Cannot call a class as a function");

View File

@ -1,4 +1,4 @@
import { func, object, print } from "../stdlib/primordials.ts";
import { object } from "../primordials.ts";
function _defineProperties(target, arr) {
if (!arr) return;
@ -31,8 +31,5 @@ export default function _createClass(clazz, instance, nonInstance) {
_defineProperties(clazz.prototype, instance);
_defineProperties(clazz, nonInstance);
func.setCallable(clazz, false);
func.setConstructable(clazz, true);
return clazz;
}

View File

@ -1,4 +1,4 @@
import { object } from "../stdlib/primordials.ts";
import { object } from "../primordials.ts";
export default function _defineProperty(obj, key, val) {
if (obj == null) return;

View File

@ -1,4 +1,4 @@
import { object } from "../stdlib/primordials.ts";
import { object } from "../primordials.ts";
export default function _getPrototypeOf(obj) {
return object.getPrototype(obj) || null;

View File

@ -1,4 +1,4 @@
import { object } from "../stdlib/primordials.ts";
import { object } from "../primordials.ts";
export default function _inherits(t, e) {
if (e == null) {

View File

@ -1,4 +1,4 @@
import { object } from "../stdlib/primordials";
import { object } from "../primordials";
export default function _setPrototypeOf(obj, proto) {
object.setPrototype(obj, proto);

View File

@ -4,14 +4,6 @@ export interface InternalBuffer {
length: number;
[buffSymbol]: "buffer";
}
export interface InternalSocket {
read(onRes: (data: Uint8Array) => void, onErr: (err: unknown) => void, onDone: () => void): void;
write(data: Uint8Array, onRes: () => void, onErr: (err: unknown) => void): void;
}
export interface InternalServer {
bind(address: string, onRes: () => void, onErr: (err: unknown) => void): void;
next(onRes: (socket: InternalSocket) => void, onErr: (err: unknown) => void, onDone: () => void): void;
}
export interface SymbolPrimordials {
makeSymbol(name: string): symbol;
@ -47,8 +39,8 @@ export interface ObjectPrimordials {
defineProperty(obj: object, key: string | number | symbol, conf: { g?: Function, s?: Function, e?: boolean, c?: boolean }): boolean;
defineField(obj: object, key: string | number | symbol, conf: { v?: any, e?: boolean, c?: boolean, w?: boolean }): boolean;
getOwnMember(obj: object, key: any): PropertyDescriptor | undefined;
getMembers(obj: object, own: boolean, onlyEnumerable: boolean): string[];
getSymbolMembers(obj: object, own: boolean, onlyEnumerable: boolean): symbol[];
getOwnMembers(obj: object, onlyEnumerable: boolean): string[];
getOwnSymbolMembers(obj: object, onlyEnumerable: boolean): symbol[];
getPrototype(obj: object): object | undefined;
setPrototype(obj: object, proto?: object): object;
preventExt(obj: object): void;
@ -80,7 +72,7 @@ export interface BufferPrimordials {
export interface FunctionPrimordials {
invokeType(args: IArguments, self: any): "new" | "call";
invokeTypeInfer(): "new" | "call";
target(level?: number): Function | null | undefined;
target(): Function | null | undefined;
setConstructable(func: Function, flag: boolean): void;
setCallable(func: Function, flag: boolean): void;
invoke(func: Function, self: any, args: any[]): any;
@ -90,9 +82,6 @@ export interface JSONPrimordials {
parse(data: string): any;
stringify(data: any): string;
}
export interface NetPrimordials {
server(): InternalServer;
}
export interface Primordials {
symbol: SymbolPrimordials;
@ -102,7 +91,6 @@ export interface Primordials {
function: FunctionPrimordials;
json: JSONPrimordials;
buffer: BufferPrimordials;
net: NetPrimordials;
map: new (weak?: boolean) => {
get(key: any): any;
has(key: any): boolean;
@ -117,13 +105,11 @@ export interface Primordials {
exec(target: string, offset: number, indices: boolean): { matches: RegExpMatchArray, end: number } | null;
groupCount(): number;
};
compile(src: string, filename?: string): Function;
compile(src: string): Function;
setGlobalPrototypes(prototype: Record<string, any>): void;
now(): number;
next(func: () => void): void;
schedule(func: () => void, delay: number): () => void;
setIntrinsic(key: string, val: any): void;
print(...args: any[]): void;
}
// prevent optimization to "undefined", which doesn't exist yet
@ -139,7 +125,6 @@ export const {
buffer,
function: func,
json,
net: socket,
map,
regex,
setGlobalPrototypes,
@ -147,9 +132,6 @@ export const {
now,
next,
schedule,
setIntrinsic,
print,
} = primordials;
export type regex = InstanceType<typeof regex>;
export const self = (globalThis as any);

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