Compare commits

..

11 Commits

Author SHA1 Message Date
fd8b0b7cbe fix: source map decoding 2025-05-22 14:20:29 +03:00
a4c09b6cd6 various debugging-related fixes 2025-05-22 14:19:53 +03:00
892408d9dd feat: some much needed REPL improvements 2025-05-22 14:18:49 +03:00
ecfd80f36a fix: typescript transpiler misbehaving 2025-05-22 12:08:41 +03:00
a1a2293af4 fixing gradle (*again*) 2025-05-22 11:35:04 +03:00
6e9250ffd1 minor cleanup 2025-05-22 11:34:29 +03:00
45292990b1 some thesis fixes 2025-01-29 14:12:57 +02:00
2dcfff689a add page numbers to thesis 2025-01-28 14:33:34 +02:00
2619e50e9b fix typos in thesis 2025-01-28 13:25:09 +02:00
4bfda6b0a1 bump
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m38s
2025-01-28 13:11:29 +02:00
58d6110e1d fix: stack overflow!! 2025-01-28 13:10:58 +02:00
29 changed files with 674 additions and 465 deletions

View File

@@ -1,5 +1,5 @@
plugins {
id("common");
id("j2s-common");
}
java {

View File

@@ -22,26 +22,28 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher");
}
if (System.getenv("REPO_URL") != null) {
publishing {
repositories {
maven {
name = "Gitea";
url = uri(System.getenv("REPO_URL") ?: "");
publishing {
repositories {
maven {
name = "Gitea";
url = uri(System.getenv("REPO_URL") ?: "");
credentials(HttpHeaderCredentials::class) {
name = "Authorization";
value = "token ${System.getenv("ACCESS_TOKEN")}";
}
credentials(HttpHeaderCredentials::class) {
name = "Authorization";
value = "token ${System.getenv("ACCESS_TOKEN")}";
}
authentication {
create<HttpHeaderAuthentication>("header");
authentication {
create<HttpHeaderAuthentication>("header");
}
}
}
}
publications {
create<MavenPublication>("maven") {
from(components["java"]);
publications {
create<MavenPublication>("maven") {
from(components["java"]);
}
}
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
}
description = "A collection of utils and structures for the rest of the project";

View File

@@ -6,55 +6,99 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
/**
* The class, used to keep track of important environment-wide values
* Supports an inheritance mechanism via parents
* In essence a simple hashmap of flag objects to values
*/
public class Environment {
/**
* The parent of this environment. Will act as a "fallback" when searching for elements.
* Operations this environment won't write into the parent
*/
public final Environment parent;
private final Map<Key<Object>, Object> map = new HashMap<>();
private final Set<Key<Object>> hidden = new HashSet<>();
/**
* Gets the element, contained in this environment, signified by the given key
* @return The element, or null if not found
*/
@SuppressWarnings("unchecked")
public <T> T get(Key<T> key) {
if (map.containsKey(key)) return (T)map.get(key);
else if (!hidden.contains(key) && parent != null) return parent.get(key);
else return null;
}
/**
* Checks if the environment has the given key
*/
public boolean has(Key<?> key) {
if (map.containsKey(key)) return true;
else if (!hidden.contains(key) && parent != null) return parent.has(key);
else return false;
}
/**
* Checks if the environment has the given key and if the value behind they key is not null
*/
public boolean hasNotNull(Key<?> key) {
return get(key) != null;
}
/**
* Gets the element, contained in this environment, signified by the given key
* @param defaultVal The value to return if the element is not found
* @return The element, or "defaultVal" if not found
*/
public <T> T get(Key<T> key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
}
/**
* Gets the element, contained in this environment, signified by the given key
* @param defaultVal The supplier, from which to return if the element is not found
* @return The element, or the result of "defaultVal" if not found
*/
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
if (has(key)) return get(key);
else return defaultVal.get();
}
/**
* Inserts the given value for the given key, replacing any existing value
* If a parent has a value with the same key, it isn't replaced, but instead - shadowed
* @return The inserted element
*/
@SuppressWarnings("unchecked")
public <T> Environment add(Key<T> key, T val) {
map.put((Key<Object>)key, val);
hidden.remove(key);
return this;
}
/**
* Adds the flag key to the environment
* @return The environment instance
*/
public Environment add(Key<Void> key) {
return add(key, null);
}
/**
* Executes Environment.add for each pair of the map
* @return The environment instance
*/
@SuppressWarnings("all")
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
public Environment addAll(Map<Key<?>, ?> map) {
map.putAll((Map)map);
hidden.removeAll(map.keySet());
return this;
}
public Environment addAll(Map<Key<?>, ?> map) {
return addAll(map, true);
}
/**
* Removes the given key from the environment.
* If a parent has the given key, it is instead just "hidden" by this environment
* @return The environment instance
*/
@SuppressWarnings("unchecked")
public Environment remove(Key<?> key) {
map.remove(key);
@@ -62,6 +106,9 @@ public class Environment {
return this;
}
/**
* If the key exists in the environment, returns it. Otherwise, puts the given value and returns the value
*/
public <T> T init(Key<T> key, T val) {
if (!has(key)) {
this.add(key, val);
@@ -69,6 +116,9 @@ public class Environment {
}
else return get(key);
}
/**
* If the key exists in the environment, returns it. Otherwise, puts the given value from the supplier and returns it
*/
public <T> T initFrom(Key<T> key, Supplier<T> val) {
if (!has(key)) {
var res = val.get();
@@ -78,6 +128,10 @@ public class Environment {
else return get(key);
}
/**
* Creates an environment that is a child of this environment
* @return
*/
public Environment child() {
return new Environment(this);
}
@@ -85,15 +139,25 @@ public class Environment {
public Environment(Environment parent) {
this.parent = parent;
}
/**
* Creates an empty environment
*/
public Environment() {
this.parent = null;
}
/**
* If the environment is null, returns an empty environment
* Otherwise, returns the given value
*/
public static Environment wrap(Environment env) {
if (env == null) return empty();
else return env;
}
/**
* Returns a newly-created environment
*/
public static Environment empty() {
return new Environment();
}

View File

@@ -2,8 +2,17 @@ package me.topchetoeu.j2s.common;
import java.io.File;
/**
* The class that represents all filenames in J2S
*/
public class Filename {
/**
* The protocol of the filename (file://, http://, ftp://, etc...)
*/
public final String protocol;
/**
* The path to the file (/home/me/test.js, example.org/test.js, etc...)
*/
public final String path;
@Override public String toString() {
@@ -42,11 +51,18 @@ public class Filename {
this.path = path;
}
/**
* Parses the given string to a filename.
* If a :// is not found, the protocol will default to "file"
*/
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
/**
* Will convert the File instance to a filename, with the protocol set to "file"
*/
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}

View File

@@ -79,9 +79,9 @@ public class Reading {
if (bytes == null) return null;
else return new String(bytes);
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/" + name);
return Reading.class.getResourceAsStream("/" + name.replaceAll("//", "/"));
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
}
description = "A compiler of EcmaScript 5 code to J2S bytecode";

View File

@@ -1,168 +1,168 @@
package me.topchetoeu.j2s.compilation;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.function.Function;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompilationFunctionMap.FunctionMapBuilder;
import me.topchetoeu.j2s.compilation.control.TryNode;
import me.topchetoeu.j2s.compilation.scope.FunctionScope;
import me.topchetoeu.j2s.compilation.scope.Variable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
public final class CompileResult {
public static final Key<Void> DEBUG_LOG = new Key<>();
private FunctionBody body;
public final List<Instruction> instructions;
public final List<CompileResult> children;
public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
public final FunctionMapBuilder map;
public final Environment env;
public int length;
public final FunctionScope scope;
public final Map<TryNode, Variable> catchBindings = new HashMap<>();
public int temp() {
instructions.add(null);
return instructions.size() - 1;
}
public CompileResult add(Instruction instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, instr);
return this;
}
public int size() { return instructions.size(); }
public void setDebug(Location loc, BreakpointType type) {
map.setDebug(loc, type);
}
public void setLocation(int i, Location loc) {
map.setLocation(i, loc);
}
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
map.setLocationAndDebug(i, loc, type);
}
public void setDebug(BreakpointType type) {
setDebug(map.last(), type);
}
public void setLocation(Location type) {
setLocation(instructions.size() - 1, type);
}
public void setLocationAndDebug(Location loc, BreakpointType type) {
setLocationAndDebug(instructions.size() - 1, loc, type);
}
public Iterable<CompileResult> all() {
var stack = new Stack<CompileResult>();
stack.push(this);
return () -> new Iterator<CompileResult>() {
@Override public CompileResult next() {
if (stack.empty()) return null;
else {
var res = stack.pop();
for (var child : res.children) {
stack.push(child);
}
return res;
}
}
@Override public boolean hasNext() {
return !stack.empty();
}
};
}
public CompileResult addChild(FunctionNode node, CompileResult res) {
this.children.add(res);
this.childrenMap.put(node, res);
this.childrenIndices.put(node, this.children.size() - 1);
return res;
}
public Instruction[] instructions() {
return instructions.toArray(new Instruction[0]);
}
public CompilationFunctionMap map(Function<Location, Location> mapper) {
return map.map(mapper).build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public CompilationFunctionMap map() {
return map.build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public FunctionBody body() {
if (body != null) return body;
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
var instrRes = instructions();
if (env.has(DEBUG_LOG)) {
System.out.println("================= BODY =================");
System.out.println("LOCALS: " + scope.localsCount());
System.out.println("CAPTURABLES: " + scope.capturablesCount());
System.out.println("CAPTURES: " + scope.capturesCount());
for (var instr : instrRes) System.out.println(instr);
}
return body = new FunctionBody(
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren
);
}
public CompileResult subtarget() {
return new CompileResult(env, new FunctionScope(scope), this);
}
public CompileResult setEnvironment(Environment env) {
return new CompileResult(env, scope, this);
}
/**
* Returns a compile result with a child of the environment that relates to the given key.
* In essence, this is used to create a compile result which is back at the root environment of the compilation
*/
public CompileResult rootEnvironment(Key<Environment> env) {
return new CompileResult(this.env.get(env).child(), scope, this);
}
public CompileResult subEnvironment() {
return new CompileResult(env.child(), scope, this);
}
public CompileResult(Environment env, FunctionScope scope, int length) {
this.scope = scope;
this.instructions = new ArrayList<>();
this.children = new LinkedList<>();
this.map = CompilationFunctionMap.builder();
this.env = env;
this.length = length;
}
private CompileResult(Environment env, FunctionScope scope, CompileResult parent) {
this.scope = scope;
this.instructions = parent.instructions;
this.children = parent.children;
this.map = parent.map;
this.env = env;
}
}
package me.topchetoeu.j2s.compilation;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.function.Function;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Key;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.CompilationFunctionMap.FunctionMapBuilder;
import me.topchetoeu.j2s.compilation.control.TryNode;
import me.topchetoeu.j2s.compilation.scope.FunctionScope;
import me.topchetoeu.j2s.compilation.scope.Variable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
public final class CompileResult {
public static final Key<Void> DEBUG_LOG = new Key<>();
private FunctionBody body;
public final List<Instruction> instructions;
public final List<CompileResult> children;
public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
public final FunctionMapBuilder map;
public final Environment env;
public int length;
public final FunctionScope scope;
public final Map<TryNode, Variable> catchBindings = new HashMap<>();
public int temp() {
instructions.add(null);
return instructions.size() - 1;
}
public CompileResult add(Instruction instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, instr);
return this;
}
public int size() { return instructions.size(); }
public void setDebug(Location loc, BreakpointType type) {
map.setDebug(loc, type);
}
public void setLocation(int i, Location loc) {
map.setLocation(i, loc);
}
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
map.setLocationAndDebug(i, loc, type);
}
public void setDebug(BreakpointType type) {
setDebug(map.last(), type);
}
public void setLocation(Location type) {
setLocation(instructions.size() - 1, type);
}
public void setLocationAndDebug(Location loc, BreakpointType type) {
setLocationAndDebug(instructions.size() - 1, loc, type);
}
public Iterable<CompileResult> all() {
var stack = new Stack<CompileResult>();
stack.push(this);
return () -> new Iterator<CompileResult>() {
@Override public CompileResult next() {
if (stack.empty()) return null;
else {
var res = stack.pop();
for (var child : res.children) {
stack.push(child);
}
return res;
}
}
@Override public boolean hasNext() {
return !stack.empty();
}
};
}
public CompileResult addChild(FunctionNode node, CompileResult res) {
this.children.add(res);
this.childrenMap.put(node, res);
this.childrenIndices.put(node, this.children.size() - 1);
return res;
}
public Instruction[] instructions() {
return instructions.toArray(new Instruction[0]);
}
public CompilationFunctionMap map(Function<Location, Location> mapper) {
return map.map(mapper).build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public CompilationFunctionMap map() {
return map.build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public FunctionBody body() {
if (body != null) return body;
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
var instrRes = instructions();
if (env.has(DEBUG_LOG)) {
System.out.println("================= BODY =================");
System.out.println("LOCALS: " + scope.localsCount());
System.out.println("CAPTURABLES: " + scope.capturablesCount());
System.out.println("CAPTURES: " + scope.capturesCount());
for (var instr : instrRes) System.out.println(instr);
}
return body = new FunctionBody(
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren
);
}
public CompileResult subtarget() {
return new CompileResult(env, new FunctionScope(scope), this);
}
public CompileResult setEnvironment(Environment env) {
return new CompileResult(env, scope, this);
}
/**
* Returns a compile result with a child of the environment that relates to the given key.
* In essence, this is used to create a compile result which is back at the root environment of the compilation
*/
public CompileResult rootEnvironment(Key<Environment> env) {
return new CompileResult(this.env.get(env).child(), scope, this);
}
public CompileResult subEnvironment() {
return new CompileResult(env.child(), scope, this);
}
public CompileResult(Environment env, FunctionScope scope, int length) {
this.scope = scope;
this.instructions = new ArrayList<>();
this.children = new LinkedList<>();
this.map = CompilationFunctionMap.builder();
this.env = env;
this.length = length;
}
private CompileResult(Environment env, FunctionScope scope, CompileResult parent) {
this.scope = scope;
this.instructions = parent.instructions;
this.children = parent.children;
this.map = parent.map;
this.env = env;
}
}

View File

@@ -1,112 +1,114 @@
package me.topchetoeu.j2s.compilation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class CompoundNode extends Node {
public final Node[] statements;
public Location end;
@Override public void resolve(CompileResult target) {
for (var stm : statements) stm.resolve(target);
}
@Override public void compileFunctions(CompileResult target) {
for (var stm : statements) stm.compileFunctions(target);
}
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
List<Node> statements = new ArrayList<Node>();
for (var stm : this.statements) {
if (stm instanceof FunctionStatementNode func) {
func.compile(target, false);
}
else statements.add(stm);
}
var polluted = false;
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER);
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
}
if (!polluted && pollute) {
target.add(Instruction.pushUndefined());
}
}
public CompoundNode setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundNode(Location loc, Node ...statements) {
super(loc);
this.statements = statements;
}
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
if (precedence > 1) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, ",")) return ParseRes.failed();
n++;
var curr = JavaScript.parseExpression(src, i + n, 2);
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
n += curr.n;
if (prev instanceof CompoundNode comp) {
var children = new ArrayList<Node>();
children.addAll(Arrays.asList(comp.statements));
children.add(curr.result);
return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n);
}
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
}
public static ParseRes<CompoundNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "{")) return ParseRes.failed();
n++;
var statements = new ArrayList<Node>();
while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
var res = JavaScript.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
n += res.n;
statements.add(res.result);
}
return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
}
}
package me.topchetoeu.j2s.compilation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
import me.topchetoeu.j2s.compilation.parsing.ParseRes;
import me.topchetoeu.j2s.compilation.parsing.Parsing;
import me.topchetoeu.j2s.compilation.parsing.Source;
public class CompoundNode extends Node {
public final Node[] statements;
public Location end;
@Override public void resolve(CompileResult target) {
for (var stm : statements) stm.resolve(target);
}
@Override public void compileFunctions(CompileResult target) {
for (var stm : statements) stm.compileFunctions(target);
}
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
List<Node> statements = new ArrayList<Node>();
for (var stm : this.statements) {
if (stm instanceof FunctionStatementNode func) {
func.compile(target, false);
}
else statements.add(stm);
}
var polluted = false;
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compileStatement(target, false, BreakpointType.STEP_OVER);
else stm.compileStatement(target, polluted = pollute, BreakpointType.STEP_OVER);
target.setDebug(type);
}
if (!polluted && pollute) {
target.add(Instruction.pushUndefined());
}
}
public CompoundNode setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundNode(Location loc, Node ...statements) {
super(loc);
this.statements = statements;
}
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
if (precedence > 1) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, ",")) return ParseRes.failed();
n++;
var curr = JavaScript.parseExpression(src, i + n, 2);
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
n += curr.n;
if (prev instanceof CompoundNode comp) {
var children = new ArrayList<Node>();
children.addAll(Arrays.asList(comp.statements));
children.add(curr.result);
return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n);
}
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
}
public static ParseRes<CompoundNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "{")) return ParseRes.failed();
n++;
var statements = new ArrayList<Node>();
while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
var res = JavaScript.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
n += res.n;
statements.add(res.result);
}
return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
}
}

View File

@@ -1,28 +1,33 @@
package me.topchetoeu.j2s.compilation;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
public abstract class Node {
private Location loc;
public void resolve(CompileResult target) {}
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
int start = target.size();
compile(target, pollute);
if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
}
public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.NONE);
}
public abstract void compileFunctions(CompileResult target);
public Location loc() { return loc; }
public void setLoc(Location loc) { this.loc = loc; }
protected Node(Location loc) {
this.loc = loc;
}
}
package me.topchetoeu.j2s.compilation;
import me.topchetoeu.j2s.common.Location;
import me.topchetoeu.j2s.common.Instruction.BreakpointType;
public abstract class Node {
private Location loc;
public void resolve(CompileResult target) {}
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
compileStatement(target, pollute, type);
}
public void compileStatement(CompileResult target, boolean pollute, BreakpointType type) {
int start = target.size();
compile(target, pollute);
if (target.size() != start) {
target.setLocationAndDebug(start, loc(), type);
}
}
public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.NONE);
}
public abstract void compileFunctions(CompileResult target);
public Location loc() { return loc; }
public void setLoc(Location loc) { this.loc = loc; }
protected Node(Location loc) {
this.loc = loc;
}
}

View File

@@ -20,6 +20,10 @@ 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",
@@ -34,12 +38,5 @@ emit {
chapter_format_plain = "Глава %s: ",
},
},
profession(config.profession),
year = os.date "%Y",
prev_year = os.date "%Y" - 1,
config,
},
}

View File

@@ -3,13 +3,21 @@ title: Дипломна работа
---
# Увод {.nonum}
Един от най-важните принципи на програмирането е този на разширимостта. Този принцип повелява, че софтуерът трябва да изграждан по начин, по който може неговата функционалност да бъде разширена без вече написаният софтуер да бъде променян. Това е добре известен проблем в сферата на софтуерното инженерство, като едно от най-ефективните решения е интегрирането на втори помощен език, чрез който потребителя да може да разширява функционалността на оригиналният продукт.
Един от най-важните принципи на програмирането е този на разширимостта. Този принцип повелява, че софтуерът трябва да бъде изграждан по начин, по който може неговата функционалност да бъде разширена, без вече написаният софтуер да бъде променян. Това е добре известен проблем в сферата на софтуерното инженерство.
Но защо разширимостта е толкова важна? Това се дължи на факта, че често потребителите на софтуерните продукти искат да разширят функционалността на продукта. Традиционно, разширяването може да стане само като потребителя се свърже с разработчика на продукта и да помоли за добавянето на функционалността. Това обаче е не само непрактично, но и води до прекомерно нарастване на софтуера, което го прави сложен за поддръжка и използване.
Но защо разширимостта е толкова важна? Често потребителите на софтуерните продукти искат да разширят и персонализират функционалността на продукта. Традиционно, разширяването става само чрез модификация на кода на софтуера. Това обаче е не само непрактично, но и води до прекомерно нарастване на обема на софтуера, което го прави сложен за поддръжка. Едно добро решение на този проблем е внедряването на втори помощен език, който да служи за допълването на функционалността на продукта, без неговата промяна.
Въвеждането на скриптови езици като помощни механизми за разширяване на функционалността предлага значително по-ефективно решение. Езиците като Python, Lua и JavaScript могат да бъдат използвани за добавяне на нова функционалност, без да се променя основният код на приложението, като по този начин се осигурява гъвкавост на софтуерния продукт. JavaScript, в частност, е един от най-широко използваните езици в съвременната разработка, поради своята популярност, която се дължи на употребата на езика в уеб технологиите.
Езици като Python, Lua и JavaScript са едни от най-често вгражданите езици, като има безброй такива примери:
Целта на тази дипломна работа е да създаде лесно вградим, малък на размер интерпретатор на „EcmaScript 5.1“ езика (по-добре познат като JavaScript, както ще се и нарича оттук нататък) за продукти, написани на езикът „Java“.
- Roblox, платформа за създаване на игри, внедрява модифицирана версия на Lua
- Nginx, софтуер за интернет сървъри, внедрява JavaScript
- WirePlumber, софтуер за контролиране на аудиото на линукс машини, използва Lua като основен език за конфигурация
- Vim, конзолен текстов редактор, внедрява Lua като език за разширения
- Blender, софтуер за създаване на 3D модели, внедрява Python като скриптов език, както и език за разширения
JavaScript, в частност, е език, който първоначално е бил използван само за уеб приложения. Понеже обаче езикът е станал изключително популярен, е започнал да се ползва и извън уеб приложенията. Езикът има изключително богата екосистема от инструменти за разработка и библиотеки, заради което е добър избор за език, който да бъде внедрен в даден софтуерен продукт.
Заради това, тази дипломна работа цели да създаде лесно вградим и малък на размер интерпретатор на „EcmaScript 5.1“ езика (по-добре познат като JavaScript) за продукти, написани на езикът „Java“. Интерпретатора трябва да изпълнява бързо и правилно JavaScript код от голям размер и да бъде удобен за внедряване във вече съществуващи продукти.
# Проучвателна част
@@ -325,6 +333,18 @@ tasks {
Методи, съответстващи на събития в интерпретатора
:::
### Събитиен цикъл
Това е основен механизъм за поддръжката на привидно многонижково програмиране само чрез една нижка. В основата на събитийния цикъл стои опашка от „съобщения“, като всяко съобщение представлява команда, която събитийният цикъл трябва да изпълни. Командите се подреждат на тази опашка, като първите, които са постъпили в опашката биват изпълнени.
Събитийния цикъл гарантира, че съобщенията, който постъпват във събитийния цикъл, ще бъдат изпълнявани последователно. Това освобождава програмиста от нуждата да създава код, който се грижи за синхронизацията между две ядра.
Комуникацията между два събитийни цикъла може да бъде осъществена като първият цикъл изпраща съобщение на втория събитиен цикъл, който евентуално обработва това съобщение. Това съобщение пък може да направи дадени изчисление и да върне резултата чрез второ съобщение, което се праща на първия събитиен цикъл. На практика, приложението имплементира два събитийни цикъла - този, който изпълнява интерпретирания код и този, който приема резултатите от събитийния цикъл.
Друг модел на комуникация е да се изпрати съобщение от текущата нижка на друга нижка, която изпълнява съобщенията, като текущата нижка трябва да изчака изпълнението на съобщението. При този метод употребата на събитиен цикъл се обезмисля до известна степен, понеже се елиминира всякакво паралелно изпълнение на интерпретирания код и основното приложение.
В кода, интерфейса, който е основен за всички събитиини цикли е `EventLoop`, а неговата основна имплементация е `Engine`. `EventLoop` предоставя само един метод - `pushMsg`, на който се подава функционалния интерфейс `Runnable`, който се очаква да бъде изпълнен евентуално. `Engine` е имплементацията, която използва „блокираща“ опашка (опашка, при която присъства операцията за изчакване на добавяне на елемент).
----
## Имплементация на JavaScript стойностите
@@ -350,8 +370,6 @@ tasks {
В кода и двете стойности са имплементирани с класа `VoidValue`, който може да бъде използван за да бъдат създадени и други празни стойности.
----
### Елементарни стойности
В JavaScript елементарните стойности са всички стойности, които не са обекти. Такива стойности не могат да съдържат референция към друга JavaScript стойност. Такива стойности биват:
@@ -380,6 +398,10 @@ tasks {
В кода, целите числа са имплементирани в класа `IntValue`, а числата с плаваща запетая - класа `DoubleValue`. Тези класове обаче не могат да бъдат инстанцирани ръчно - трябва да бъде използван метода `NumberValue.of`, който създава една от двете инстанции, в зависимост от това дали подаденото число може да бъде представено с 32-битово число.
### Потребителски стойности
Това са служебен тип стойности, които съдържат само една Java стойност. Те се използват за подаване на Java стойности на JavaScript кода, за да бъдат използвани по-късно от Java кода. Потребителските стойности се имплементират от класа „UserValue“, като той съдържа само две полета: съдържаната Java стойност, както и прототипа, който стойността да използва.
### Обекти
Обектите са основополагаща концепция в JavaScript. В основата на обектите стои списък от уникални низове (т. нар. ключове) и съответстващият им член [@членове] (съществува и паралелен списък със списък от ключове-символи към членове). Обектите се използват най-вече за съхранение на няколко подстойности под формата на структурирани данни (подобно на класовете на Java), но могат да бъдат използвани и като хеш-таблици.
@@ -391,7 +413,7 @@ tasks {
- Режим без пренастройвани (запечатване) - освен ограниченията от предишният режим, полетата не могат да бъдат трити и пренастройвани
- Режим на заключване (замразяване) - обекта става на практика константа, нито едно негово свойство не може да бъде променено
JavaScript кода може да променя режима от по-свободен към по-ограничаващ, но не и обратно. Това разрешава на разработчицитие да осигурят дадено ниво на константност на данните на обектите си.
JavaScript кода може да променя режима от по-свободен към по-ограничаващ, но не и обратно. Това разрешава на разработчиците да осигурят дадено ниво на константност на данните на обектите си.
Накрая, обектите имат прототипи. Прототипа може да е `null` или обект. При търсенето на член, може обект да няма дадения ключ. Тогава търсенето продължава в прототипа на обекта. Понеже обаче прототипа е обект на практика съществува свързан списък от обекти, или т. нар. прототипна верига. Прототипите са в основата на обектно-ориентираното програмиране в JavaScript, понеже може чрез прототипите да бъде дефиниран един основен обект, който да съдържа всички функции за даден „тип“ обекти, а всички обекти от този „тип“ просто имат този основен обект като прототип. Освен това обаче прототипите могат да бъдат използвани и за осъществяване на наследство на обекти - обекта „AnimalPrototype“, който съдържа члена „speak“ може да бъде прототип на обекта „DogPrototype“, който съдържа члена „bark“. При тази конфигурация, „DogPrototype“ де факто има и двете полета - „speak“ и „bark“.
@@ -445,7 +467,7 @@ console.log(arr); // [5, 6, <empty x 2>, 8, <empty x 7> 3]
- Промяната на една от тези настройки, освен ако презаписваемо, но ненастройваемо поле се направи непрезаписваемо
- Изтриване на полето от обекта
Членовете са дефинирани чрез интерфейса „Member“, полетата чрез класа „FieldMember“, а свойствата чрез „PropertyMember“. Понеже полетата могат да бъдат виртуални и контролирани от Java код, е осигурена базова имплементация за съвсем прости полета, които са само контейнер за JavaScript стойност, в класа „SimpleFieldMember“, докато по-сложна логика може да създаде собствена имплементация на „FieldMember“ класа (на този механизъм са базирани масивите).
Членовете са дефинирани чрез интерфейса „Member“, полетата чрез класа „FieldMember“, а свойствата чрез „PropertyMember“. Понеже полетата могат да бъдат виртуални (като тези на масивите), е „FieldMember“ е абстрактен, а „SimpleFieldMember“ е имплементацията на нормалните полета.
----
@@ -688,7 +710,7 @@ public class Frame {
```txt
; Добавя 10 към стека
LOAD_NUMBER 10
; Добадя 1 към стека
; Добавя 1 към стека
LOAD_NUMBER 1
; Добавя първият елемент на
; стека с новодобавената единица
@@ -792,7 +814,7 @@ STORE_MEMBER_STR | key, keep | value, object | Еквивалент на ST
STORE_MEMBER_INT | key, keep | value, object | Еквивалент на STORE_MEMBER, но с константен числов ключ `key`
GLOB_GET | name, force | | Зарежда променливата `name` от глобалния обект. Ако `force` е `true` не се хвърля грешка ако променливата не съществува. Стойността на променливата се добавя към стека
GLOB_SET | name, keep, force | value | Съхранява променливата `name` в глобалния обект. Ако `force` е `true` не се хвърля грешка ако променливата не съществува. Стойността на променливата се добавя към стека, ако `keep` е `true`
GLOB_DEF | name | value | Създава променливата `name` със стойност `undefined` в глобавният обект, ако не съществува
GLOB_DEF | name | value | Създава променливата `name` със стойност `undefined` в глобалния обект, ако не съществува
OPERATION | type | values... | Изпълнява дадената операция (фиг. [@operations-table]) с даденият брой операнда, които операцията изисква. Добавя стойността при изчислението на операцията към стека
Набор от инструкции на междинния език (бележка: в колоната с аргументи от стека са показани аргументите в реда, в който се взимат от стека, освен ако друг ред не е уточнен)
@@ -906,7 +928,7 @@ class Frame {
## Компилиране на проекта
Можете да изтеглите кода на проекта, като и всичко нужно за компилирането му от приложената флашка (в директорията „j2s“) или от Git репозиторията <https://git.topcheto.eu/topchetoeu/j2s.git>. За компилацията на кода е нужен следния софтуер:
Можете да изтеглите кода на проекта, като и всичко нужно за компилирането му от приложената флашка (в директорията „j2s“) или от Git хранилището <https://git.topcheto.eu/topchetoeu/j2s.git>. За компилацията на кода е нужен следния софтуер:
- OpenJDK 17 SDK
- Gradle 8.10
@@ -924,7 +946,7 @@ class Frame {
## Използване на проекта като зависимост в друг проект
За проекта е налично и Maven хранилище, достъпно чрез URL адреса [https://git.topcheto.eu/api/packages/topchetoeu/maven], в което присъстват всички компонети на проекта.
За проекта е налично и Maven хранилище, достъпно чрез URL адреса [https://git.topcheto.eu/api/packages/topchetoeu/maven], в което присъстват всички компоненти на проекта.
----
@@ -1026,7 +1048,7 @@ $ for (const el of myGen(0, 30, 10)) print(el);
20i
undefined
```
Употреба на по-нов синтаксиси, с помощта на Babel
Употреба на по-нови синтаксиси, с помощта на Babel
:::
----
@@ -1055,6 +1077,8 @@ StdLib.addGlobals(env);
env.add(Compiler.KEY, Compilers.jsCompiler());
```
Важно е да се отбележе, че събитийния цикъл работи в отделна нижка по подразбиране. Не е нужно обаче да бъде стартиран събитийния цикъл, а може да бъде повиквана само неговия метод `Engine.run(true)`, като `true` в това повикване означава „изпълнение на събитийния цикъл докато опашката от съобщения приключи“.
Ако искате да регистрирате транспилатори, може да използвате следния код (имайте в предвид, че тези функции създават нова среда и нова стандартна библиотека за всеки транспилатор, за да бъдат изолирани от останалия код.):
```java
@@ -1081,7 +1105,7 @@ engine.pushMsg(false, env, new Filename("test", "test"), "console.log(10 + 5, \"
Работата с JavaScript обекти е лесна, трябва да бъдат използвани само описаните в [@имплементация-на-javascript-стойностите] методи:
```java
ArrayValue doInterestingStuff(Environment env, Value input) {
ArrayValue doStuff(Environment env, Value input) {
var obj = new ObjectValue();
obj.defineOwnField(env, "test", NumberValue.of(10));
obj.defineOwnField(env, "a", StringValue.of("fsadf"));
@@ -1095,7 +1119,7 @@ ArrayValue doInterestingStuff(Environment env, Value input) {
}
```
Често ще Ви се налага да създавате и Java функцкии (трябва да правите такива функции с повишено внимание, понеже те имат неограничен достъп до Java средата на изпълнение, което може да означава че зле написана Java функция може да компрометира сигурността). Следния код е пример как се създава Java функция:
Често ще Ви се налага да създавате и Java функции, за да давате достъп на JavaScript до Вашият софтуер. Подобни функции обаче трябва да бъдат създавани с повишено внимание, понеже могат да бъдат използвани и злонамерено. Подобни функции се създават по следния начин:
```java
var func = new NativeFunction("myFunc", args -> {
@@ -1106,6 +1130,18 @@ var func = new NativeFunction("myFunc", args -> {
});
```
`args` аргумента е от типа `Argument`. Това е служебен клас, който служи за улеснен достъп до аргументите, подадени на функцията. Той има следните основни методи и полета:
- self - „this“ аргумента
- args - масив от подадените аргументи
- env - средата, подадена при извикването
- isNew - „true“, ако е извикана в режим на конструкция (в който случай „self“ е „target“ функцията)
- setTargetProto(obj) - в контекста на конструкция, прилака прототипа на „target“ функцията на подадения обект
- n() - връща броя на аргументите
- has(i) - проверка дали този аргумент е бил подаден
- self(clazz) - ако „this“ аргумента е „UserValue“, превръща съдържаната стойност в дадения клас, иначе връща „null“
- get(i) - връща съответния на i аргумент, или „null“, ако индекса е извън обхвата на масива
# Заключение {.nonum}
В настоящата дипломна работа е демонстриран интерпретатора на JavaScript, написан на Java. За да бъде създаден, беше направено проучване на съществуващите технологии, както и методите на интерпретиране. Беше отделено и голямо внимание на добрата и проста архитектура на проекта. Архитектурата се развиваше заедно с кода, докато достигне до днешната си форма, като не стана заплетена и ненужно сложна. Интерпретатора беше тестван най-вече ръчно чрез сравнение с държанието на V8 при същия код, както и чрез трите транспилатора „Babel“, „TypeScript“ и „CoffeeScript“.
@@ -1171,7 +1207,7 @@ else return end.chainError(src.loc(i + n), "Expected end of statement");
Всеки клас за елемент от синтактичното дърво съответно има следните три основни функции:
- `compile` - Добавя съответставщите на елемента инструкции в подадения `CompileResult`
- `compile` - Добавя съответставащите на елемента инструкции в подадения `CompileResult`
- `compileFunctions` - Използва се като служебна функция, която да позволява компилирането първо на по-дълбоките функции
- `resolve` - Дефинира в подадения `CompileResult` променливите, които елемента декларира
@@ -1193,7 +1229,7 @@ else return end.chainError(src.loc(i + n), "Expected end of statement");
Създадената система за добавяне на интерпретатори от своя страна е създадена такава, че може няколко интерпретатора да бъдат „наредени“ един след друг. Това позволява изключителна гъвкавост, както и допълнителна изолация на средата - може код, който използва компилатор да дефинира собствен компилатор без да знае за компилатора, на който е базиран.
Освен кода, за изпълнението на TypeScript кода са включени и типизации на стандартните билиотеки, описващи специфичната стандартна библиотека, дефинирана тук.
Освен кода, за изпълнението на TypeScript кода са включени и типизации на стандартните библиотеки, описващи специфичната стандартна библиотека, дефинирана тук.
За да има достъп кода на стандартната библиотека до някои вътрешни функции на интерпретатора, беше създаден и класа „Primordials“, който създава набор от функции, които могат да бъдат използвани за ***незащитен*** достъп до иначе недостъпните чрез синтаксис функционалности.
@@ -1203,6 +1239,22 @@ else return end.chainError(src.loc(i + n), "Expected end of statement");
Това е втората част от „lib“ компонента. Той използва `DebugHandler` механизма, за да прихване различните събития на изпълнението на кода. Специалното на дебъгера е, че имплементира V8 протокола за дебъгване [@refs-v8-debug-protocol], което означава, че дебъгването на програма, изпълнявана в този интерпретатор, е възможно в инструментите за разработчици на Chrome, както и във Visual Studio Code.
За да бъде използван дебъгера, трябва бъде създаден „DebugServer“, който имплементира прост HTTP и WebSocket сървър, който да комуникира с V8 дебъг клиента. След това, трябва да бъде създаден регистратор на дебъгера при осъществяването на връзка със сървъра. За целта може да бъде използван вградения „SimpleDebugger“. Кода, който може да използвате за целта е следния:
```java
var handler = new SimpleDebugHandler();
env.add(DebugHandler.KEY, handler);
var server = new DebugServer();
var debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
server.targets.put("default", (socket, req) -> {
var debugger = new SimpleDebugger(socket);
debugger.attach(handler);
});
```
След това, за да бъде използван дебъгера може да се използва или Chrome (или всеки браузър, базиран на Chromium), или VSCode, като Chrome автоматично ще засече присъствието на Debug сървър и ще предложи стартирането на дебъг клиент чрез зелена икона на NodeJS, която ще се покаже в горния ляв ъгъл на опциите на разработчиците (достъпно чрез F12).
![Пример за дебъгване на програма от интерпретатора](./img/debugging-example.png "debug")
# Използвани термини и чуждици {.nonum}

View File

@@ -315,7 +315,7 @@ function converters.Image(data, ctx)
return text, title and { title } or alt_plain or url or "[picture]";
end
function converters.Figure(data, ctx)
local chapter_i = #(ctx.headers or {});
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];

View File

@@ -102,16 +102,17 @@ local function parse_impl(str, pos, end_delim)
local delim_found;
if c == "{" then
pos = pos + 1;
pos = str:find("%S", pos + 1) or pos;
local key;
local obj = {};
c = string.sub(str, pos, pos);
if c == "}" then
return obj, pos
return obj, pos;
else
while true do
pos = skip_delim(str, pos);
key, pos = parse_str_val(str, pos, true);
if key == nil then error("Expected a string key") end

View File

@@ -8,6 +8,15 @@
@page {
size: A4;
}
@page {
@bottom-right-corner {
text-align: center;
content: counter(page);
}
}
@page scan-page {
margin: 0;
}
h1 {
break-before: page;
}
@@ -34,6 +43,7 @@
}
.page {
height: 100%;
line-height: 1.25;
h1, h2, h3, h4, h5, h6 {
margin: 0;
@@ -167,6 +177,7 @@
.page {
height: 100%;
max-height: 100%;
font-size: 12pt;
}
@@ -175,9 +186,12 @@
flex-direction: row;
width: 100%;
gap: 2em;
font-size: 1.25em;
text-align: right;
margin-bottom: 1em;
}
.school-img {
height: 5em;
height: 6em;
width: unset;
}
@@ -185,6 +199,13 @@
break-after: page;
}
.scan-page {
display: flex;
flex-direction: column;
justify-content: stretch;
align-items: stretch;
page: scan-page;
}
.title-page {
display: flex;
flex-direction: column;
@@ -213,6 +234,9 @@
text-align: right; */
}
.asm-title {
display: flex;
flex-direction: column;
gap: 1em;
text-align: center;
}
.asm-content {
@@ -226,8 +250,6 @@
height: 100%;
}
.asm-page {
height: 100%;
max-height: 100%;
display: flex;
flex-direction: column;
}
@@ -471,6 +493,11 @@
// 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>
@@ -484,10 +511,10 @@
<div class="title-content">
<h2>ДИПЛОМНА РАБОТА</h2>
<h4>по професия код {{profession}}</h4>
<h4>специалност код {{specialty}}</h4>
<div>Тема: {{topic}}</div>
<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>
@@ -501,7 +528,15 @@
<div class="title-end">СОФИЯ - {{year}}</div>
</div>
<div class="page asm-page">
<div class="page scan-page">
<img src="./img-secret/scan0001.jpg"/>
</div>
<div class="page scan-page">
<img src="./img-secret/scan0003.jpg"/>
</div>
<!-- <div class="page asm-page">
<div class="school-header">
<img class="school-img" src="{{school_img}}"/>
<h4>{{school_name}}</h4>
@@ -518,11 +553,12 @@
</div>
<div class="asm-content">
<div class="asm-title">
<h2>ЗАДАНИЕ</h2>
<h2>за дипломна работа</h2>
<h2>ЗАДАНИЕ<br/>за дипломна работа</h2>
<h4>ДЪРЖАВЕН ИЗПИТ ЗА ПРИДОБИВАНЕ НА ТРЕТА СТЕПЕН НА ПРОФЕСИОНАЛНА КВАЛИФИКАЦИЯ</h4>
<h4>по професия код {{profession}}</h4>
<h4>специалност код {{specialty}}</h4>
<div>
<div>по професия код {{profession}}</div>
<div>специалност код {{specialty}}</div>
</div>
</div>
<div class="asm-requirements">
на ученика {{author}} от {{class}} клас<br/>
@@ -543,7 +579,7 @@
</div>
</div>
</div>
</div>
</div> -->
{{content}}

View File

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

View File

@@ -1,7 +1,7 @@
import com.github.gradle.node.npm.task.NpmTask;
plugins {
id("common-java");
id("j2s-common-java");
id("com.github.node-gradle.node") version "5.0.0";
}

View File

@@ -21,12 +21,12 @@ public class Compilers {
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) {
@@ -59,9 +59,6 @@ public class Compilers {
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];
@@ -86,10 +83,6 @@ public class Compilers {
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]);

View File

@@ -423,7 +423,7 @@ public class SimpleDebugger implements Debugger {
desc.append("...");
break;
}
if (arr.has(i)) {
try {
var curr = arr.get(i);
@@ -442,7 +442,7 @@ public class SimpleDebugger implements Debugger {
desc.append("<empty>");
}
}
desc.append("]");
}
@@ -814,7 +814,6 @@ public class SimpleDebugger implements Debugger {
var cond = msg.params.string("condition", "").trim();
if (cond.equals("")) cond = null;
if (cond != null) cond = "(" + cond + ")";
Pattern regex;
@@ -1064,7 +1063,7 @@ public class SimpleDebugger implements Debugger {
}
mappings.put(body, map);
}
private boolean instructionLock;
@Override public boolean onInstruction(Environment env, Frame cf, Instruction instruction, Value returnVal, EngineException error, boolean caught) {
@@ -1077,16 +1076,16 @@ public class SimpleDebugger implements Debugger {
Location loc;
DebugFrame frame;
BreakpointType bptType;
frame = getFrame(cf);
var map = DebugHandler.get(env).getMapOrEmpty(env, frame.frame.function);
frame.updateLoc(map.toLocation(frame.frame.codePtr));
loc = frame.location;
bptType = map.getBreakpoint(frame.frame.codePtr);
isBreakpointable = loc != null && (bptType.shouldStepIn());
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
pauseException(env, error);
}
@@ -1114,19 +1113,19 @@ public class SimpleDebugger implements Debugger {
instruction.params.length == 1 &&
instruction.get(0).equals("debug")
) pauseDebug(env, null);
synchronized (this) {
}
while (enabled) {
synchronized (this) {
switch (state) {
case PAUSED_EXCEPTION:
case PAUSED_NORMAL: break;
case STEPPING_OUT:
case RESUMED: return false;
case STEPPING_IN:
case STEPPING_OVER:
if (stepOutFrame.frame == frame.frame) {
@@ -1135,7 +1134,7 @@ public class SimpleDebugger implements Debugger {
continue;
}
else if (stepOutPtr != frame.frame.codePtr) {
if (state == State.STEPPING_IN && bptType.shouldStepIn()) {
pauseDebug(env, null);
break;
@@ -1149,7 +1148,7 @@ public class SimpleDebugger implements Debugger {
return false;
}
}
try {
synchronized (updateNotifier) {
updateNotifier.wait();

View File

@@ -1,10 +1,11 @@
const map: number[] = [];
let j = 0;
for (let i = 65; i <= 90; i++) map[i] = j++;
for (let i = 97; i <= 122; i++) map[i] = j++;
map[43] = j++;
map[47] = j++;
function decodeBase64(val: number) {
if (val >= 65 && val <= 90) return val - 65;
else if (val >= 97 && val <= 122) return val - 97 + 26;
else if (val >= 48 && val <= 57) return val - 48 + 52;
else if (val == 43) return 62;
else if (val == 47) return 63;
else throw "Invalid Base64 char";
}
export function decodeVLQ(val: string): number[][][] {
const lines: number[][][] = [];
@@ -30,14 +31,14 @@ export function decodeVLQ(val: string): number[][][] {
for (let i = 0; i < el.length;) {
let sign = 1;
let curr = map[el.charCodeAt(i++)];
let curr = decodeBase64(el.charCodeAt(i++));
let cont = (curr & 0x20) === 0x20;
if ((curr & 1) === 1) sign = -1;
let res = (curr & 0b11110) >> 1;
let n = 4;
for (; i < el.length && cont;) {
curr = map[el.charCodeAt(i++)];
curr = decodeBase64(el.charCodeAt(i++));
cont = (curr & 0x20) == 0x20;
res |= (curr & 0b11111) << n;
n += 5;
@@ -99,7 +100,7 @@ export class VLQSourceMap {
while (true) {
const done = b - a <= 1;
const mid = (a + b) >> 1;
const el = line[mid];
@@ -134,7 +135,6 @@ export class VLQSourceMap {
let originalRow = 0;
let originalCol = 0;
let originalFile = 0;
const lastCols = new Set<number>();
for (let compiledRow = 0; compiledRow < mapping.length; compiledRow++) {
const line: [start: number, dst: Location][] = file[compiledRow] = [];
@@ -149,10 +149,7 @@ export class VLQSourceMap {
originalRow += rawSeg.length > 2 ? rawSeg[2] : 0;
originalCol += rawSeg.length > 3 ? rawSeg[3] : 0;
if (!lastCols.has(compiledCol)) {
line[line.length] = [compiledCol, [filenames[originalFile], originalRow, originalCol]];
}
lastCols.add(compiledCol);
line[line.length] = [compiledCol, [filenames[originalFile], originalRow, originalCol]];
}
line.sort((a, b) => a[0] - b[0]);

View File

@@ -16,10 +16,10 @@ export default function typescript(next: Compiler): Compiler {
const settings: CompilerOptions = {
target: ScriptTarget.ES5,
module: ModuleKind.Preserve,
allowImportingTsExtensions: true,
verbatimModuleSyntax: true,
verbatimModuleSyntax: false,
strict: false,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
@@ -52,7 +52,7 @@ export default function typescript(next: Compiler): Compiler {
}
},
getScriptVersion: (filename) => String(versions[filename] || 0),
readFile: () => { throw "no"; },
writeFile: () => { throw "no"; },
}, createDocumentRegistry());
@@ -61,7 +61,7 @@ export default function typescript(next: Compiler): Compiler {
service.getEmitOutput("/lib.d.ts");
});
print("Loaded typescript!");
return (filename, code, prevMap) => {
files["/src.ts"] = ScriptSnapshot.fromString(code);
versions["/src.ts"] ??= 0;

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
id("com.gradleup.shadow") version "9.0.0-beta4";
}

View File

@@ -7,6 +7,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -41,55 +42,61 @@ public class SimpleRepl {
static Key<InputStream> STDIN = new Key<>();
static int j = 0;
static String[] args;
static String[] files;
static boolean inspect = false;
static int inspectPort = 9229;
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))
);
if (inspect) {
server = new DebugServer();
debugTask = server.start(new InetSocketAddress("127.0.0.1", inspectPort), true);
server.targets.put("default", (socket, req) -> new SimpleDebugger(socket)
.attach((SimpleDebugHandler)DebugHandler.get(environment))
);
System.out.println("Debug server started at localhost:" + inspectPort);
}
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));
if (files.length > 0) {
for (var arg : files) {
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();
try {
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file), raw, null
).get();
System.err.println(res.toReadable(environment));
System.err.println(res.toReadable(environment));
}
catch (ExecutionException e) { throw e.getCause(); }
}
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)); }
}
else {
for (var i = 0; ; i++) {
var raw = Reading.readline();
for (var i = 0; ; i++) {
var raw = Reading.readline();
if (raw == null) break;
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));
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 (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 (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
@@ -104,11 +111,30 @@ public class SimpleRepl {
}
}
private static Environment createESEnv() {
private static Environment createESEnv(String compiler) {
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));
switch (compiler) {
case "typescript":
case "ts":
env.add(Compiler.KEY, Compilers.chainTranspilers(Compilers.jsCompiler(), env, Compilers::typescriptCompiler));
break;
case "coffeescript":
case "cs":
env.add(Compiler.KEY, Compilers.chainTranspilers(Compilers.jsCompiler(), env, Compilers::babelCompiler, Compilers::coffeescriptCompiler));
break;
case "babel":
case "es6":
case "esnext":
env.add(Compiler.KEY, Compilers.chainTranspilers(Compilers.jsCompiler(), env, Compilers::babelCompiler));
break;
default:
case "js":
env.add(Compiler.KEY, Compilers.jsCompiler());
break;
}
var glob = Value.global(env);
@@ -137,10 +163,29 @@ public class SimpleRepl {
}
public static void main(String args[]) throws InterruptedException {
SimpleRepl.args = args;
var compiler = "js";
var files = new ArrayList<String>();
for (String arg : args) {
if (arg.startsWith("--lang=")) {
compiler = arg.substring(7);
}
else if (arg.equals("--inspect")) {
inspect = true;
}
else if (arg.startsWith("--inspect=")) {
inspect = true;
inspectPort = Integer.parseInt(arg.substring(10));
}
else {
files.add(arg);
}
}
SimpleRepl.files = files.toArray(new String[0]);
var reader = new Thread(SimpleRepl::reader);
environment = createESEnv();
environment = createESEnv(compiler);
initEngine();
@@ -150,6 +195,6 @@ public class SimpleRepl {
reader.join();
engineTask.interrupt();
debugTask.interrupt();
if (debugTask != null) debugTask.interrupt();
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id("common-java");
id("j2s-common-java");
}
description = "The runtime of J2S, used to execute J2S bytecode";

View File

@@ -323,7 +323,7 @@ public final class Frame {
/**
* Induces a value on the stack (as if it were returned by the last function call)
* and executes the next instruction in the frame.
*
*
* @param value The value to induce
*/
public final Value next(Value value) {
@@ -334,7 +334,7 @@ public final class Frame {
* Note that this is different than just throwing the error outside the
* function, as the function executed could have a try-catch which
* would otherwise handle the error
*
*
* @param error The error to induce
*/
public final Value induceError(EngineException error) {
@@ -346,7 +346,7 @@ public final class Frame {
* Note that this is different than just returning the value outside the
* function, as the function executed could have a try-catch which
* would otherwise handle the error
*
*
* @param value The retunr value to induce
*/
public final Value induceReturn(Value value) {

View File

@@ -93,7 +93,7 @@ public interface DebugHandler {
}
public default FunctionMap getMapOrEmpty(Environment env, FunctionValue func) {
if (func instanceof CodeFunction codeFunc) return getMapOrEmpty(env, codeFunc.body);
else return null;
else return FunctionMap.EMPTY;
}
public static DebugHandler get(Environment exts) {

View File

@@ -15,8 +15,9 @@ public class Arguments {
public final <T extends Value> T setTargetProto(T obj) {
if (!self.isPrimitive()) {
var proto = self.getMember(env, "prototype");
if (proto instanceof ObjectValue objProto) self.setPrototype(env, objProto);
else if (proto == Value.NULL) self.setPrototype(env, null);
if (proto instanceof ObjectValue objProto) obj.setPrototype(env, objProto);
else if (proto == Value.NULL) obj.setPrototype(env, null);
}
return obj;
}

View File

@@ -195,7 +195,6 @@ public abstract class ArrayLikeValue extends ObjectValue {
res.add(" " + line);
}
}
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
res.add("]");
return res;

View File

@@ -57,7 +57,7 @@ public final class StringValue implements PrimitiveValue {
}
}
return StringValue.this.getOwnMember(env, key);
return PrimitiveValue.super.getOwnMember(env, key);
}
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {