build: split up into multiple projects, use kotlin DLS
All checks were successful
tagged-release / Tagged Release (push) Successful in 3m41s
All checks were successful
tagged-release / Tagged Release (push) Successful in 3m41s
This commit is contained in:
51
repl/build.gradle.kts
Normal file
51
repl/build.gradle.kts
Normal file
@@ -0,0 +1,51 @@
|
||||
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";
|
||||
|
||||
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");
|
||||
implementation(project(":common"));
|
||||
implementation(project(":compilation"));
|
||||
implementation(project(":runtime"));
|
||||
implementation(project(":lib"));
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
filesMatching("metadata.json", {
|
||||
expand(
|
||||
"version" to properties["project_version"],
|
||||
"name" to properties["project_name"],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
930
repl/src/main/java/me/topchetoeu/j2s/repl/SimpleRepl.java
Normal file
930
repl/src/main/java/me/topchetoeu/j2s/repl/SimpleRepl.java
Normal file
@@ -0,0 +1,930 @@
|
||||
package me.topchetoeu.j2s.repl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import me.topchetoeu.j2s.common.FunctionBody;
|
||||
import me.topchetoeu.j2s.common.Metadata;
|
||||
import me.topchetoeu.j2s.common.Reading;
|
||||
import me.topchetoeu.j2s.common.SyntaxException;
|
||||
import me.topchetoeu.j2s.common.environment.Environment;
|
||||
import me.topchetoeu.j2s.common.environment.Key;
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.parsing.Filename;
|
||||
import me.topchetoeu.j2s.common.parsing.Location;
|
||||
import me.topchetoeu.j2s.compilation.CompileResult;
|
||||
import me.topchetoeu.j2s.compilation.JavaScript;
|
||||
import me.topchetoeu.j2s.repl.debug.DebugServer;
|
||||
import me.topchetoeu.j2s.repl.debug.Debugger;
|
||||
import me.topchetoeu.j2s.repl.debug.SimpleDebugger;
|
||||
import me.topchetoeu.j2s.repl.mapping.NativeMapper;
|
||||
import me.topchetoeu.j2s.runtime.ArgumentsValue;
|
||||
import me.topchetoeu.j2s.runtime.Compiler;
|
||||
import me.topchetoeu.j2s.runtime.Engine;
|
||||
import me.topchetoeu.j2s.runtime.EventLoop;
|
||||
import me.topchetoeu.j2s.runtime.Frame;
|
||||
import me.topchetoeu.j2s.runtime.JSONConverter;
|
||||
import me.topchetoeu.j2s.runtime.debug.DebugContext;
|
||||
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.objects.ArrayLikeValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.ArrayValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.buffers.Int32ArrayValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.buffers.Int8ArrayValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.buffers.TypedArrayValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.buffers.Uint8ArrayValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.BoolValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.SymbolValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.UserValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.VoidValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
|
||||
|
||||
public class SimpleRepl {
|
||||
public static final Compiler DEFAULT_COMPILER = (env, filename, raw, mapper) -> {
|
||||
try {
|
||||
var res = JavaScript.compile(env, filename, raw, true);
|
||||
var body = res.body();
|
||||
DebugContext.get(env).onSource(filename, raw);
|
||||
registerFunc(env, body, res, 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;
|
||||
}
|
||||
};
|
||||
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine = new Engine();
|
||||
static Environment environment = Environment.empty(), tsEnvironment;
|
||||
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 {
|
||||
try {
|
||||
environment = createESEnv();
|
||||
tsEnvironment = createESEnv();
|
||||
}
|
||||
catch (ExecutionException e) { throw e.getCause(); }
|
||||
|
||||
server = new DebugServer();
|
||||
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
server.targets.put("default", (socket, req) -> new SimpleDebugger(socket)
|
||||
.attach(DebugContext.get(environment))
|
||||
.attach(DebugContext.get(tsEnvironment))
|
||||
);
|
||||
|
||||
try {
|
||||
try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); }
|
||||
}
|
||||
catch (EngineException | SyntaxException e) {
|
||||
System.err.println("Failed to load stdlib. Falling back to barebones environment...");
|
||||
System.err.println(Value.errorToReadable(environment, e, null));
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static ObjectValue mapPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
var prototype = new ObjectValue[1];
|
||||
NativeFunction mapConstr = new NativeFunction(args -> {
|
||||
var isWeak = args.get(0).toBoolean();
|
||||
return UserValue.of(isWeak ? new WeakHashMap<>() : new LinkedHashMap<>(), prototype[0]);
|
||||
});
|
||||
mapConstr.prototype.defineOwnField(env, "get", new NativeFunction(getArgs -> {
|
||||
var map = getArgs.self(Map.class);
|
||||
var key = getArgs.get(0);
|
||||
var val = map.get(key);
|
||||
return val == null ? Value.UNDEFINED : (Value)val;
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "set", new NativeFunction(getArgs -> {
|
||||
var map = getArgs.self(Map.class);
|
||||
var key = getArgs.get(0);
|
||||
var val = getArgs.get(1);
|
||||
map.put(key, val);
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "has", new NativeFunction(getArgs -> {
|
||||
var map = getArgs.self(Map.class);
|
||||
var key = getArgs.get(0);
|
||||
return BoolValue.of(map.containsKey(key));
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "delete", new NativeFunction(getArgs -> {
|
||||
var map = getArgs.self(Map.class);
|
||||
var key = getArgs.get(0);
|
||||
map.remove(key);
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "keys", new NativeFunction(getArgs -> {
|
||||
var map = getArgs.self(Map.class);
|
||||
return ArrayValue.of(map.keySet());
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "clear", new NativeFunction(getArgs -> {
|
||||
getArgs.self(Map.class).clear();
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "size", new NativeFunction(getArgs -> {
|
||||
return NumberValue.of(getArgs.self(Map.class).size());
|
||||
}));
|
||||
prototype[0] = (ObjectValue)mapConstr.prototype;
|
||||
|
||||
return mapConstr;
|
||||
}
|
||||
|
||||
public static String processRegex(String src) {
|
||||
var n = 0;
|
||||
|
||||
var source = new StringBuilder();
|
||||
|
||||
StringBuilder bracesSource = null;
|
||||
StringBuilder bracketsSource = null;
|
||||
|
||||
while (true) {
|
||||
if (n >= src.length()) break;
|
||||
var c = src.charAt(n++);
|
||||
|
||||
if (c == '\\' && n + 1 < src.length() && src.charAt(n) == 'b') {
|
||||
c = '\b';
|
||||
n++;
|
||||
}
|
||||
|
||||
if (bracesSource != null) {
|
||||
var failed = true;
|
||||
|
||||
if (Character.isDigit(c)) {
|
||||
bracesSource.append(c);
|
||||
failed = false;
|
||||
}
|
||||
else if (c == ',' && bracesSource.indexOf(",") < 0) {
|
||||
bracesSource.append(c);
|
||||
failed = false;
|
||||
}
|
||||
else if (c == '}' && bracesSource.length() > 0) {
|
||||
bracesSource.append(c);
|
||||
source.append(bracesSource);
|
||||
bracesSource = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
source.append("\\");
|
||||
source.append(bracesSource);
|
||||
bracesSource = null;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
else if (bracketsSource != null) {
|
||||
if (c == '[') bracketsSource.append("\\[");
|
||||
else if (c == ']') {
|
||||
var res = bracketsSource.append(']').toString();
|
||||
bracketsSource = null;
|
||||
if (res.equals("[^]")) res = "[\\s\\S]";
|
||||
else if (res.equals("[]")) res = "[^\\s\\S]";
|
||||
source.append(res);
|
||||
}
|
||||
else if (c == '\\') {
|
||||
if (n >= src.length()) break;
|
||||
bracketsSource.append(c).append(src.charAt(n++));
|
||||
}
|
||||
else bracketsSource.append(c);
|
||||
}
|
||||
else if (c == '\\') {
|
||||
if (n >= src.length()) throw new PatternSyntaxException("Unexpected end", src, n);
|
||||
c = src.charAt(n++);
|
||||
source.append('\\').append(c);
|
||||
}
|
||||
else if (c == '[') {
|
||||
bracketsSource = new StringBuilder("[");
|
||||
}
|
||||
else if (c == '{' && bracketsSource == null) {
|
||||
bracesSource = new StringBuilder("{");
|
||||
}
|
||||
else source.append(c);
|
||||
}
|
||||
|
||||
if (bracesSource != null) {
|
||||
source.append("\\");
|
||||
source.append(bracesSource);
|
||||
}
|
||||
if (bracketsSource != null) throw new PatternSyntaxException("Unmatched '['", src, n - bracketsSource.length());
|
||||
|
||||
return source.toString();
|
||||
}
|
||||
|
||||
private static ObjectValue regexPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
var prototype = new ObjectValue[1];
|
||||
NativeFunction mapConstr = new NativeFunction(args -> {
|
||||
var flags = 0;
|
||||
if (args.get(1).toBoolean()) flags |= Pattern.MULTILINE;
|
||||
if (args.get(2).toBoolean()) flags |= Pattern.CASE_INSENSITIVE;
|
||||
if (args.get(3).toBoolean()) flags |= Pattern.DOTALL;
|
||||
if (args.get(4).toBoolean()) flags |= Pattern.UNICODE_CASE | Pattern.CANON_EQ;
|
||||
if (args.get(5).toBoolean()) flags |= Pattern.UNICODE_CHARACTER_CLASS;
|
||||
try {
|
||||
var pattern = Pattern.compile(processRegex(args.get(0).toString(args.env)), flags);
|
||||
return UserValue.of(pattern, prototype[0]);
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
throw EngineException.ofSyntax("(regex):" + e.getIndex() + ": " + e.getDescription());
|
||||
}
|
||||
});
|
||||
mapConstr.prototype.defineOwnField(env, "exec", new NativeFunction(args -> {
|
||||
var pattern = args.self(Pattern.class);
|
||||
var target = args.get(0).toString(args.env);
|
||||
var offset = args.get(1).toNumber(args.env).getInt();
|
||||
var index = args.get(2).toBoolean();
|
||||
|
||||
if (offset > target.length()) return Value.NULL;
|
||||
|
||||
var matcher = pattern.matcher(target).region(offset, target.length());
|
||||
if (!matcher.find()) return Value.NULL;
|
||||
|
||||
var matchesArr = new ArrayValue(matcher.groupCount() + 1);
|
||||
for (var i = 0; i < matcher.groupCount() + 1; i++) {
|
||||
var group = matcher.group(i);
|
||||
if (group == null) continue;
|
||||
matchesArr.set(args.env, i, StringValue.of(group));
|
||||
}
|
||||
|
||||
matchesArr.defineOwnField(args.env, "index", NumberValue.of(matcher.start()));
|
||||
matchesArr.defineOwnField(args.env, "input", StringValue.of(target));
|
||||
if (index) {
|
||||
var indices = new ArrayValue();
|
||||
indices.setPrototype(args.env, null);
|
||||
for (var i = 0; i < matcher.groupCount(); i++) {
|
||||
matchesArr.set(args.env, i, ArrayValue.of(Arrays.asList(
|
||||
NumberValue.of(matcher.start(i)),
|
||||
NumberValue.of(matcher.end(i))
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
var obj = new ObjectValue();
|
||||
obj.defineOwnField(args.env, "matches", matchesArr);
|
||||
obj.defineOwnField(args.env, "end", NumberValue.of(matcher.end()));
|
||||
|
||||
return obj;
|
||||
// return val == null ? Value.UNDEFINED : (Value)val;
|
||||
}));
|
||||
mapConstr.prototype.defineOwnField(env, "groupCount", new NativeFunction(args -> {
|
||||
var pattern = args.self(Pattern.class);
|
||||
return NumberValue.of(pattern.matcher("").groupCount());
|
||||
}));
|
||||
prototype[0] = (ObjectValue)mapConstr.prototype;
|
||||
|
||||
return mapConstr;
|
||||
}
|
||||
|
||||
private static ObjectValue symbolPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "makeSymbol", new NativeFunction(args -> new SymbolValue(args.get(0).toString(args.env))));
|
||||
res.defineOwnField(env, "getSymbol", new NativeFunction(args -> SymbolValue.get(args.get(0).toString(args.env))));
|
||||
res.defineOwnField(env, "getSymbolKey", new NativeFunction(args -> ((SymbolValue)args.get(0)).key()));
|
||||
res.defineOwnField(env, "getSymbolDescriptor", new NativeFunction(args -> StringValue.of(((SymbolValue)args.get(0)).value)));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ObjectValue numberPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "parseInt", new NativeFunction(args -> {
|
||||
var nradix = args.get(1).toNumber(env);
|
||||
var radix = nradix.isInt() ? nradix.getInt() : 10;
|
||||
|
||||
if (radix != 10 && args.get(0) instanceof NumberValue num) {
|
||||
if (num.isInt()) return num;
|
||||
else return NumberValue.of(num.getDouble() - num.getDouble() % 1);
|
||||
}
|
||||
else return NumberValue.parseInt(args.get(0).toString(), radix, false);
|
||||
}));
|
||||
res.defineOwnField(env, "parseFloat", new NativeFunction(args -> {
|
||||
if (args.get(0) instanceof NumberValue) {
|
||||
return args.get(0);
|
||||
}
|
||||
else return NumberValue.parseFloat(args.get(0).toString(), false);
|
||||
}));
|
||||
res.defineOwnField(env, "isNaN", new NativeFunction(args -> BoolValue.of(args.get(0).isNaN())));
|
||||
|
||||
res.defineOwnField(env, "pow", new NativeFunction(args -> {
|
||||
return NumberValue.of(Math.pow(args.get(0).toNumber(args.env).getDouble(), args.get(1).toNumber(args.env).getDouble()));
|
||||
}));
|
||||
res.defineOwnField(env, "log", new NativeFunction(args -> {
|
||||
return NumberValue.of(Math.log(args.get(0).toNumber(args.env).getDouble()));
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "NaN", NumberValue.NAN);
|
||||
res.defineOwnField(env, "Infinity", NumberValue.of(Double.POSITIVE_INFINITY));
|
||||
res.defineOwnField(env, "PI", NumberValue.of(Math.PI));
|
||||
res.defineOwnField(env, "E", NumberValue.of(Math.E));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ObjectValue stringPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "stringBuild", new NativeFunction(args -> {
|
||||
var parts = ((ArrayValue)args.get(0)).toArray();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
sb.append(((StringValue)parts[i]).value);
|
||||
}
|
||||
|
||||
return StringValue.of(sb.toString());
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "fromCharCode", new NativeFunction(args -> {
|
||||
return StringValue.of(new String(new char[] { (char)args.get(0).toNumber(args.env).getInt() }));
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "toCharCode", new NativeFunction(args -> {
|
||||
return NumberValue.of(args.get(0).toString(args.env).charAt(0));
|
||||
}));
|
||||
res.defineOwnField(env, "toCodePoint", new NativeFunction(args -> {
|
||||
return NumberValue.of(args.get(0).toString(args.env).codePointAt(args.get(1).toNumber(args.env).getInt()));
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "substring", new NativeFunction(args -> {
|
||||
var str = args.get(0).toString(args.env);
|
||||
var start = args.get(1).toNumber(args.env).getInt();
|
||||
var end = args.get(2).toNumber(args.env).getInt();
|
||||
|
||||
if (end <= start) return StringValue.of("");
|
||||
|
||||
start = Math.max(Math.min(start, str.length()), 0);
|
||||
end = Math.max(Math.min(end, str.length()), 0);
|
||||
|
||||
return StringValue.of(str.substring(start, end));
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "indexOf", new NativeFunction(args -> {
|
||||
var str = args.get(0).toString(args.env);
|
||||
var search = args.get(1).toString(args.env);
|
||||
var start = args.get(2).toNumber(args.env).getInt();
|
||||
if (start > str.length()) return NumberValue.of(-1);
|
||||
var reverse = args.get(3).toBoolean();
|
||||
|
||||
if (reverse) return NumberValue.of(str.lastIndexOf(search, start));
|
||||
else return NumberValue.of(str.indexOf(search, start));
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "lower", new NativeFunction(args -> {
|
||||
return StringValue.of(args.get(0).toString(args.env).toLowerCase());
|
||||
}));
|
||||
res.defineOwnField(env, "upper", new NativeFunction(args -> {
|
||||
return StringValue.of(args.get(0).toString(args.env).toUpperCase());
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ObjectValue objectPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "defineField", new NativeFunction(args -> {
|
||||
var obj = (ObjectValue)args.get(0);
|
||||
var key = args.get(1);
|
||||
var desc = (ObjectValue)args.get(2);
|
||||
|
||||
var valField = desc.getOwnMember(env, "v");
|
||||
var writeField = desc.getOwnMember(env, "w");
|
||||
var configField = desc.getOwnMember(env, "c");
|
||||
var enumField = desc.getOwnMember(env, "e");
|
||||
|
||||
var enumerable = enumField == null ? null : enumField.get(env, desc).toBoolean();
|
||||
var configurable = configField == null ? null : configField.get(env, desc).toBoolean();
|
||||
var writable = writeField == null ? null : writeField.get(env, desc).toBoolean();
|
||||
var value = valField == null ? null : valField.get(env, desc);
|
||||
|
||||
return BoolValue.of(obj.defineOwnField(args.env, key, value, configurable, enumerable, writable));
|
||||
}));
|
||||
res.defineOwnField(env, "defineProperty", new NativeFunction(args -> {
|
||||
var obj = (ObjectValue)args.get(0);
|
||||
var key = args.get(1);
|
||||
var desc = args.get(2);
|
||||
|
||||
var configField = desc.getOwnMember(env, "c");
|
||||
var enumField = desc.getOwnMember(env, "e");
|
||||
var getField = desc.getOwnMember(env, "g");
|
||||
var setField = desc.getOwnMember(env, "s");
|
||||
|
||||
var enumerable = enumField == null ? null : enumField.get(env, desc).toBoolean();
|
||||
var configurable = configField == null ? null : configField.get(env, desc).toBoolean();
|
||||
Optional<FunctionValue> getter = null, setter = null;
|
||||
|
||||
if (getField != null) {
|
||||
var getVal = getField.get(env, desc);
|
||||
if (getVal == Value.UNDEFINED) getter = Optional.empty();
|
||||
else getter = Optional.of((FunctionValue)getVal);
|
||||
}
|
||||
if (setField != null) {
|
||||
var setVal = setField.get(env, desc);
|
||||
if (setVal == Value.UNDEFINED) setter = Optional.empty();
|
||||
else setter = Optional.of((FunctionValue)setVal);
|
||||
}
|
||||
|
||||
return BoolValue.of(obj.defineOwnProperty(args.env, key, getter, setter, configurable, enumerable));
|
||||
}));
|
||||
res.defineOwnField(env, "getPrototype", new NativeFunction(args -> {
|
||||
var proto = args.get(0).getPrototype(env);
|
||||
if (proto == null) return Value.NULL;
|
||||
else return proto;
|
||||
}));
|
||||
res.defineOwnField(env, "setPrototype", new NativeFunction(args -> {
|
||||
var proto = args.get(1) instanceof VoidValue ? null : (ObjectValue)args.get(1);
|
||||
args.get(0).setPrototype(env, proto);
|
||||
return args.get(0);
|
||||
}));
|
||||
res.defineOwnField(env, "getOwnMembers", new NativeFunction(args -> {
|
||||
var val = new ArrayValue();
|
||||
|
||||
for (var key : args.get(0).getOwnMembers(env, args.get(1).toBoolean())) {
|
||||
val.set(args.env, val.size(), StringValue.of(key));
|
||||
}
|
||||
|
||||
return val;
|
||||
}));
|
||||
res.defineOwnField(env, "getOwnSymbolMembers", new NativeFunction(args -> {
|
||||
return ArrayValue.of(args.get(0).getOwnSymbolMembers(env, args.get(1).toBoolean()));
|
||||
}));
|
||||
res.defineOwnField(env, "getOwnMember", new NativeFunction(args -> {
|
||||
var obj = args.get(0);
|
||||
var key = args.get(1);
|
||||
|
||||
var member = obj.getOwnMember(args.env, key);
|
||||
if (member == null) return Value.UNDEFINED;
|
||||
else return member.descriptor(args.env, obj);
|
||||
}));
|
||||
res.defineOwnField(env, "preventExt", new NativeFunction(args -> {
|
||||
args.get(0).preventExtensions();
|
||||
return VoidValue.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "seal", new NativeFunction(args -> {
|
||||
args.get(0).seal();
|
||||
return VoidValue.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "freeze", new NativeFunction(args -> {
|
||||
args.get(0).freeze();
|
||||
return VoidValue.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "memcpy", new NativeFunction(args -> {
|
||||
var src = (ArrayValue)args.get(0);
|
||||
var dst = (ArrayValue)args.get(1);
|
||||
var srcI = args.get(2).toNumber(args.env).getInt();
|
||||
var dstI = args.get(3).toNumber(args.env).getInt();
|
||||
var n = args.get(4).toNumber(args.env).getInt();
|
||||
|
||||
src.copyTo(dst, srcI, dstI, n);
|
||||
|
||||
return VoidValue.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "sort", new NativeFunction(args -> {
|
||||
var arr = (ArrayValue)args.get(0);
|
||||
var func = (FunctionValue)args.get(1);
|
||||
|
||||
arr.sort((a, b) -> {
|
||||
return func.apply(args.env, Value.UNDEFINED, a, b).toNumber(args.env).getInt();
|
||||
});
|
||||
|
||||
return arr;
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "isArray", new NativeFunction(args -> {
|
||||
return BoolValue.of(args.get(0) instanceof ArrayLikeValue);
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ObjectValue bufferPrimordials(Environment env) {
|
||||
var buffProto = new ObjectValue();
|
||||
buffProto.defineOwnProperty(env, "length", Optional.of(new NativeFunction(args -> {
|
||||
return NumberValue.of(args.self(byte[].class).length);
|
||||
})), Optional.empty(), false, true);
|
||||
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "buff", new NativeFunction(args -> {
|
||||
var size = args.get(0).toNumber(env).getInt();
|
||||
return TypedArrayValue.buffer(new byte[size], buffProto);
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "uint8", new NativeFunction(args -> {
|
||||
var buff = args.get(byte[].class, 0);
|
||||
var start = args.get(1).toNumber(env).getInt();
|
||||
var end = args.get(2).toNumber(env).getInt();
|
||||
return new Uint8ArrayValue(buff, start, end);
|
||||
}));
|
||||
res.defineOwnField(env, "int8", new NativeFunction(args -> {
|
||||
var buff = args.get(byte[].class, 0);
|
||||
var start = args.get(1).toNumber(env).getInt();
|
||||
var end = args.get(2).toNumber(env).getInt();
|
||||
return new Int8ArrayValue(buff, start, end);
|
||||
}));
|
||||
res.defineOwnField(env, "int32", new NativeFunction(args -> {
|
||||
var buff = args.get(byte[].class, 0);
|
||||
var start = args.get(1).toNumber(env).getInt();
|
||||
var end = args.get(2).toNumber(env).getInt();
|
||||
return new Int32ArrayValue(buff, start, end);
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "isUint8", new NativeFunction(args -> {
|
||||
return BoolValue.of(args.get(0) instanceof Uint8ArrayValue);
|
||||
}));
|
||||
res.defineOwnField(env, "isInt8", new NativeFunction(args -> {
|
||||
return BoolValue.of(args.get(0) instanceof Int8ArrayValue);
|
||||
}));
|
||||
res.defineOwnField(env, "isInt32", new NativeFunction(args -> {
|
||||
return BoolValue.of(args.get(0) instanceof Int32ArrayValue);
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "is", new NativeFunction(args -> {
|
||||
return BoolValue.of(args.get(0) instanceof TypedArrayValue);
|
||||
}));
|
||||
res.defineOwnField(env, "isBuff", new NativeFunction(args -> {
|
||||
return BoolValue.of(args.get(byte[].class, 0) != null);
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "backer", new NativeFunction(args -> {
|
||||
return TypedArrayValue.buffer(((TypedArrayValue)args.get(0)).buffer, buffProto);
|
||||
}));
|
||||
res.defineOwnField(env, "start", new NativeFunction(args -> {
|
||||
return NumberValue.of(((TypedArrayValue)args.get(0)).start);
|
||||
}));
|
||||
res.defineOwnField(env, "end", new NativeFunction(args -> {
|
||||
return NumberValue.of(((TypedArrayValue)args.get(0)).end);
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ObjectValue functionPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "setCallable", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
func.enableApply = args.get(1).toBoolean();
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "setConstructable", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
func.enableConstruct = args.get(1).toBoolean();
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "invokeType", new NativeFunction(args -> {
|
||||
if (((ArgumentsValue)args.get(0)).frame.isNew) return StringValue.of("new");
|
||||
else return StringValue.of("call");
|
||||
}));
|
||||
res.defineOwnField(env, "invokeTypeInfer", new NativeFunction(args -> {
|
||||
var frame = Frame.get(args.env, args.get(0).toNumber(args.env).getInt());
|
||||
if (frame.isNew) return StringValue.of("new");
|
||||
else return StringValue.of("call");
|
||||
}));
|
||||
res.defineOwnField(env, "target", new NativeFunction(args -> {
|
||||
var frame = Frame.get(args.env, args.get(0).toNumber(args.env).getInt());
|
||||
if (frame.target == null) return Value.UNDEFINED;
|
||||
else return frame.target;
|
||||
}));
|
||||
|
||||
res.defineOwnField(env, "invoke", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var self = args.get(1);
|
||||
var funcArgs = (ArrayValue)args.get(2);
|
||||
|
||||
return func.apply(env, self, funcArgs.toArray());
|
||||
}));
|
||||
res.defineOwnField(env, "construct", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var target = args.get(1);
|
||||
var funcArgs = (ArrayValue)args.get(2);
|
||||
|
||||
if (target == Value.UNDEFINED) return func.constructNoSelf(env, funcArgs.toArray());
|
||||
else return func.construct(env, target, funcArgs.toArray());
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ObjectValue jsonPrimordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "stringify", new NativeFunction(args -> {
|
||||
return StringValue.of(JSON.stringify(JSONConverter.fromJs(env, args.get(0))));
|
||||
}));
|
||||
res.defineOwnField(env, "parse", new NativeFunction(args -> {
|
||||
try {
|
||||
return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env)));
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
throw EngineException.ofSyntax(e.msg).add(env, e.loc.filename() + "", e.loc);
|
||||
}
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void setProto(Environment env, Environment target, Key<ObjectValue> key, ObjectValue repo, String name) {
|
||||
var val = repo.getMember(env, name);
|
||||
if (val instanceof ObjectValue obj) {
|
||||
target.add(key, obj);
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectValue primordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
|
||||
res.defineOwnField(env, "symbol", symbolPrimordials(env));
|
||||
res.defineOwnField(env, "number", numberPrimordials(env));
|
||||
res.defineOwnField(env, "string", stringPrimordials(env));
|
||||
res.defineOwnField(env, "object", objectPrimordials(env));
|
||||
res.defineOwnField(env, "buffer", bufferPrimordials(env));
|
||||
res.defineOwnField(env, "function", functionPrimordials(env));
|
||||
res.defineOwnField(env, "json", jsonPrimordials(env));
|
||||
res.defineOwnField(env, "map", mapPrimordials(env));
|
||||
res.defineOwnField(env, "regex", regexPrimordials(env));
|
||||
|
||||
int[] i = new int[1];
|
||||
|
||||
res.defineOwnField(env, "setGlobalPrototypes", new NativeFunction(args -> {
|
||||
var obj = (ObjectValue)args.get(0);
|
||||
|
||||
setProto(args.env, env, Value.OBJECT_PROTO, obj, "object");
|
||||
setProto(args.env, env, Value.FUNCTION_PROTO, obj, "function");
|
||||
setProto(args.env, env, Value.ARRAY_PROTO, obj, "array");
|
||||
setProto(args.env, env, Value.BOOL_PROTO, obj, "boolean");
|
||||
setProto(args.env, env, Value.NUMBER_PROTO, obj, "number");
|
||||
setProto(args.env, env, Value.STRING_PROTO, obj, "string");
|
||||
setProto(args.env, env, Value.SYMBOL_PROTO, obj, "symbol");
|
||||
setProto(args.env, env, Value.ERROR_PROTO, obj, "error");
|
||||
setProto(args.env, env, Value.SYNTAX_ERR_PROTO, obj, "syntax");
|
||||
setProto(args.env, env, Value.TYPE_ERR_PROTO, obj, "type");
|
||||
setProto(args.env, env, Value.RANGE_ERR_PROTO, obj, "range");
|
||||
setProto(args.env, env, Value.UINT8_ARR_PROTO, obj, "uint8");
|
||||
setProto(args.env, env, Value.INT32_ARR_PROTO, obj, "int32");
|
||||
var val = obj.getMember(args.env, "regex");
|
||||
if (val instanceof FunctionValue func) {
|
||||
env.add(Value.REGEX_CONSTR, func);
|
||||
}
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "setIntrinsic", new NativeFunction(args -> {
|
||||
var name = args.get(0).toString(env);
|
||||
var val = args.get(1);
|
||||
|
||||
Value.intrinsics(environment).put(name, val);
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnField(env, "compile", new NativeFunction(args -> {
|
||||
return Compiler.compileFunc(env, new Filename(Metadata.name(), "func" + i[0]++ + ".js"), args.get(0).toString(env));
|
||||
}));
|
||||
res.defineOwnField(env, "now", new NativeFunction(args -> {
|
||||
return NumberValue.of(System.currentTimeMillis());
|
||||
}));
|
||||
res.defineOwnField(env, "next", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
EventLoop.get(env).pushMsg(() -> {
|
||||
func.apply(env, Value.UNDEFINED);
|
||||
}, true);
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Environment createESEnv() throws InterruptedException, ExecutionException {
|
||||
var env = initEnv();
|
||||
var stubEnv = initEnv();
|
||||
Value.global(stubEnv).defineOwnField(stubEnv, "target", Value.global(env));
|
||||
Value.global(stubEnv).defineOwnField(stubEnv, "primordials", primordials(env));
|
||||
|
||||
EventLoop.get(stubEnv).pushMsg(
|
||||
false, stubEnv,
|
||||
new Filename(Metadata.name(), "init.js"), Reading.resourceToString("lib/stdlib.js"),
|
||||
Value.UNDEFINED
|
||||
).get();
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
private 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)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static Environment initEnv() {
|
||||
var env = new Environment();
|
||||
env.add(EventLoop.KEY, engine);
|
||||
env.add(DebugContext.KEY, new DebugContext());
|
||||
env.add(Compiler.KEY, DEFAULT_COMPILER);
|
||||
// env.add(CompileResult.DEBUG_LOG);
|
||||
|
||||
var glob = Value.global(env);
|
||||
|
||||
glob.defineOwnField(null, "exit", new NativeFunction("exit", args -> {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new CancellationException();
|
||||
}));
|
||||
glob.defineOwnField(null, "print", new NativeFunction("print", args -> {
|
||||
for (var el : args.args) {
|
||||
if (el instanceof StringValue) System.out.print(((StringValue)el).value + " \t");
|
||||
else System.out.print(el.toReadable(args.env) + " \t");
|
||||
}
|
||||
System.out.println();
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
glob.defineOwnField(null, "measure", new NativeFunction("measure", args -> {
|
||||
var start = System.nanoTime();
|
||||
|
||||
((FunctionValue)args.get(0)).apply(args.env, Value.UNDEFINED);
|
||||
|
||||
System.out.println(String.format("Finished in %sms", (System.nanoTime() - start) / 1000000.));
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
|
||||
return env;
|
||||
}
|
||||
private static void initEngine() {
|
||||
engineTask = engine.start();
|
||||
}
|
||||
private static void initGlobals() throws InterruptedException, ExecutionException {
|
||||
var res = new FunctionValue[1];
|
||||
var setter = new NativeFunction(args -> {
|
||||
res[0] = (FunctionValue)args.get(0);
|
||||
return Value.UNDEFINED;
|
||||
});
|
||||
|
||||
var tsGlob = Value.global(tsEnvironment);
|
||||
var tsCompilerFactory = new FunctionValue[1];
|
||||
|
||||
tsGlob.defineOwnField(tsEnvironment, "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);
|
||||
}));
|
||||
tsGlob.defineOwnField(tsEnvironment, "register", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
tsCompilerFactory[0] = func;
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
tsGlob.defineOwnField(tsEnvironment, "registerSource", new NativeFunction(args -> {
|
||||
var filename = Filename.parse(args.get(0).toString(args.env));
|
||||
var src = args.get(1).toString(args.env);
|
||||
DebugContext.get(environment).onSource(filename, src);
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
|
||||
var ts = Reading.resourceToString("lib/transpiler.js");
|
||||
if (ts != null) EventLoop.get(tsEnvironment).pushMsg(
|
||||
false, tsEnvironment,
|
||||
new Filename(Metadata.name(), "transpiler.js"), ts,
|
||||
Value.UNDEFINED, setter
|
||||
).get();
|
||||
|
||||
var tsCompiler = wrap(Compiler.get(environment), tsEnvironment, environment, tsCompilerFactory[0]);
|
||||
environment.add(Compiler.KEY, tsCompiler);
|
||||
}
|
||||
private static void registerFunc(Environment env, FunctionBody body, CompileResult res, Function<Location, Location> mapper) {
|
||||
var map = res.map(mapper);
|
||||
|
||||
DebugContext.get(env).onFunctionLoad(body, map);
|
||||
|
||||
for (var i = 0; i < body.children.length; i++) {
|
||||
registerFunc(env, body.children[i], res.children.get(i), mapper);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) throws InterruptedException {
|
||||
SimpleRepl.args = args;
|
||||
var reader = new Thread(SimpleRepl::reader);
|
||||
|
||||
environment = initEnv();
|
||||
|
||||
initEngine();
|
||||
|
||||
reader.setDaemon(true);
|
||||
reader.setName("STD Reader");
|
||||
reader.start();
|
||||
|
||||
reader.join();
|
||||
engineTask.interrupt();
|
||||
debugTask.interrupt();
|
||||
}
|
||||
}
|
||||
19
repl/src/main/java/me/topchetoeu/j2s/repl/V8Error.java
Normal file
19
repl/src/main/java/me/topchetoeu/j2s/repl/V8Error.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.j2s.repl;
|
||||
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.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)
|
||||
));
|
||||
}
|
||||
}
|
||||
246
repl/src/main/java/me/topchetoeu/j2s/repl/debug/DebugServer.java
Normal file
246
repl/src/main/java/me/topchetoeu/j2s/repl/debug/DebugServer.java
Normal file
@@ -0,0 +1,246 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.j2s.common.Metadata;
|
||||
import me.topchetoeu.j2s.common.Reading;
|
||||
import me.topchetoeu.j2s.common.SyntaxException;
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.json.JSONList;
|
||||
import me.topchetoeu.j2s.common.json.JSONMap;
|
||||
import me.topchetoeu.j2s.repl.debug.WebSocketMessage.Type;
|
||||
|
||||
public class DebugServer {
|
||||
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
|
||||
|
||||
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
|
||||
|
||||
private final byte[] favicon, index, protocol;
|
||||
private final Object connNotifier = new Object();
|
||||
|
||||
private static void send(HttpRequest req, String val) throws IOException {
|
||||
req.writeResponse(200, "OK", "application/json", val.getBytes());
|
||||
}
|
||||
|
||||
// SILENCE JAVA
|
||||
private MessageDigest getDigestInstance() {
|
||||
try {
|
||||
return MessageDigest.getInstance("sha1");
|
||||
}
|
||||
catch (Throwable e) { throw new RuntimeException(e); }
|
||||
}
|
||||
|
||||
private static Thread runAsync(Runnable func, String name) {
|
||||
var res = new Thread(func);
|
||||
res.setName(name);
|
||||
res.start();
|
||||
return res;
|
||||
}
|
||||
|
||||
private void handle(WebSocket ws, Debugger debugger) throws IOException {
|
||||
WebSocketMessage raw;
|
||||
|
||||
while ((raw = ws.receive()) != null) {
|
||||
if (raw.type != Type.Text) {
|
||||
ws.send(new V8Error("Expected a text message."));
|
||||
continue;
|
||||
}
|
||||
|
||||
V8Message msg;
|
||||
|
||||
try {
|
||||
msg = new V8Message(raw.textData());
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "Debugger.enable":
|
||||
synchronized (connNotifier) {
|
||||
connNotifier.notify();
|
||||
}
|
||||
debugger.enable(msg);
|
||||
continue;
|
||||
case "Debugger.disable": debugger.close(); continue;
|
||||
|
||||
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
|
||||
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
|
||||
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
|
||||
|
||||
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
|
||||
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
|
||||
|
||||
case "Debugger.resume": debugger.resume(msg); continue;
|
||||
case "Debugger.pause": debugger.pause(msg); continue;
|
||||
|
||||
case "Debugger.stepInto": debugger.stepInto(msg); continue;
|
||||
case "Debugger.stepOut": debugger.stepOut(msg); continue;
|
||||
case "Debugger.stepOver": debugger.stepOver(msg); continue;
|
||||
|
||||
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue;
|
||||
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
|
||||
|
||||
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
|
||||
case "Runtime.releaseObject": debugger.releaseObject(msg); continue;
|
||||
case "Runtime.getProperties": debugger.getProperties(msg); continue;
|
||||
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
|
||||
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
|
||||
}
|
||||
|
||||
if (
|
||||
msg.name.startsWith("DOM.") ||
|
||||
msg.name.startsWith("DOMDebugger.") ||
|
||||
msg.name.startsWith("Emulation.") ||
|
||||
msg.name.startsWith("Input.") ||
|
||||
msg.name.startsWith("Network.") ||
|
||||
msg.name.startsWith("Page.")
|
||||
) ws.send(new V8Error("This isn't a browser..."));
|
||||
else ws.send(new V8Error("This API is not supported yet."));
|
||||
}
|
||||
|
||||
debugger.close();
|
||||
}
|
||||
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
|
||||
var key = req.headers.get("sec-websocket-key");
|
||||
|
||||
if (key == null) {
|
||||
req.writeResponse(
|
||||
426, "Upgrade Required", "text/txt",
|
||||
"Expected a WS upgrade".getBytes()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
|
||||
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
|
||||
));
|
||||
|
||||
req.writeCode(101, "Switching Protocols");
|
||||
req.writeHeader("Connection", "Upgrade");
|
||||
req.writeHeader("Sec-WebSocket-Accept", resKey);
|
||||
req.writeLastHeader("Upgrade", "WebSocket");
|
||||
|
||||
var ws = new WebSocket(socket);
|
||||
var debugger = debuggerProvider.getDebugger(ws, req);
|
||||
|
||||
if (debugger == null) {
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
runAsync(() -> {
|
||||
var handle = new Thread(debugger::close);
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(handle);
|
||||
|
||||
try { handle(ws, debugger); }
|
||||
catch (RuntimeException | IOException e) {
|
||||
try {
|
||||
e.printStackTrace();
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
}
|
||||
catch (IOException e2) { /* Shit outta luck */ }
|
||||
}
|
||||
finally {
|
||||
Runtime.getRuntime().removeShutdownHook(handle);
|
||||
ws.close();
|
||||
debugger.close();
|
||||
}
|
||||
}, "Debug Handler");
|
||||
}
|
||||
|
||||
public void awaitConnection() throws InterruptedException {
|
||||
synchronized (connNotifier) {
|
||||
connNotifier.wait();
|
||||
}
|
||||
}
|
||||
|
||||
public void run(InetSocketAddress address) {
|
||||
try {
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(address);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var socket = server.accept();
|
||||
var req = HttpRequest.read(socket);
|
||||
|
||||
if (req == null) continue;
|
||||
switch (req.path) {
|
||||
case "/json/version":
|
||||
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
|
||||
break;
|
||||
case "/json/list":
|
||||
case "/json": {
|
||||
var res = new JSONList();
|
||||
|
||||
for (var el : targets.entrySet()) {
|
||||
res.add(new JSONMap()
|
||||
.set("description", "J2S debugger")
|
||||
.set("favicon", "/favicon.ico")
|
||||
.set("id", el.getKey())
|
||||
.set("type", "node")
|
||||
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
|
||||
);
|
||||
}
|
||||
send(req, JSON.stringify(res));
|
||||
break;
|
||||
}
|
||||
case "/json/protocol":
|
||||
req.writeResponse(200, "OK", "application/json", protocol);
|
||||
break;
|
||||
case "/json/new":
|
||||
case "/json/activate":
|
||||
case "/json/close":
|
||||
case "/devtools/inspector.html":
|
||||
req.writeResponse(
|
||||
501, "Not Implemented", "text/txt",
|
||||
"This feature isn't (and probably won't be) implemented.".getBytes()
|
||||
);
|
||||
break;
|
||||
case "/":
|
||||
case "/index.html":
|
||||
req.writeResponse(200, "OK", "text/html", index);
|
||||
break;
|
||||
case "/favicon.ico":
|
||||
req.writeResponse(200, "OK", "image/png", favicon);
|
||||
break;
|
||||
default:
|
||||
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
|
||||
onWsConnect(req, socket, targets.get(req.path.substring(1)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally { server.close(); }
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
}
|
||||
|
||||
public Thread start(InetSocketAddress address, boolean daemon) {
|
||||
var res = new Thread(() -> run(address), "Debug Server");
|
||||
res.setDaemon(daemon);
|
||||
res.start();
|
||||
return res;
|
||||
}
|
||||
|
||||
public DebugServer() {
|
||||
this.favicon = Reading.resourceToBytes("debugger/favicon.png");
|
||||
this.protocol = Reading.resourceToBytes("debugger/protocol.json");
|
||||
this.index = Reading.resourceToString("debugger/index.html")
|
||||
.replace("${NAME}", Metadata.name())
|
||||
.replace("${VERSION}", Metadata.version())
|
||||
.replace("${AUTHOR}", Metadata.author())
|
||||
.getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.topchetoeu.j2s.repl.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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
public interface DebuggerProvider {
|
||||
Debugger getDebugger(WebSocket socket, HttpRequest req);
|
||||
}
|
||||
101
repl/src/main/java/me/topchetoeu/j2s/repl/debug/HttpRequest.java
Normal file
101
repl/src/main/java/me/topchetoeu/j2s/repl/debug/HttpRequest.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package me.topchetoeu.j2s.repl.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; }
|
||||
}
|
||||
}
|
||||
|
||||
216
repl/src/main/java/me/topchetoeu/j2s/repl/debug/ScopeObject.java
Normal file
216
repl/src/main/java/me/topchetoeu/j2s/repl/debug/ScopeObject.java
Normal file
@@ -0,0 +1,216 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
import me.topchetoeu.j2s.common.environment.Environment;
|
||||
import me.topchetoeu.j2s.runtime.Frame;
|
||||
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
|
||||
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.Member.FieldMember;
|
||||
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.SymbolValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
|
||||
|
||||
public class ScopeObject extends Value {
|
||||
public static final class ScopeMember extends FieldMember {
|
||||
public final Frame frame;
|
||||
public final int i;
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return frame.getVar(i);
|
||||
}
|
||||
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
frame.setVar(i, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
public ScopeMember(Value self, Frame frame, int i) {
|
||||
super(self, false, true, true);
|
||||
|
||||
this.frame = frame;
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, FieldMember> fields = new HashMap<>();
|
||||
public final ObjectValue proto;
|
||||
|
||||
@Override public StringValue type() {
|
||||
return StringValue.of("object");
|
||||
}
|
||||
@Override public boolean isPrimitive() {
|
||||
return false;
|
||||
}
|
||||
@Override public Value toPrimitive(Environment env) {
|
||||
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||
}
|
||||
@Override public NumberValue toNumber(Environment env) {
|
||||
return NumberValue.NAN;
|
||||
}
|
||||
@Override public String toString(Environment env) {
|
||||
return "[Scope]";
|
||||
}
|
||||
@Override public boolean toBoolean() {
|
||||
return true;
|
||||
}
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
if (key.isSymbol()) return null;
|
||||
var strKey = key.toString(env);
|
||||
return fields.get(strKey);
|
||||
}
|
||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||
return fields.keySet();
|
||||
}
|
||||
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
@Override public boolean defineOwnField(Environment env, KeyCache key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
|
||||
if (key.isSymbol()) return false;
|
||||
var strKey = key.toString(env);
|
||||
var field = fields.get(strKey);
|
||||
if (field == null) return false;
|
||||
return field.reconfigure(env, this, val, writable, enumerable, configurable);
|
||||
}
|
||||
@Override public boolean defineOwnProperty(Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
|
||||
return false;
|
||||
}
|
||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||
return key.isSymbol() || !fields.containsKey(key.toString(env));
|
||||
}
|
||||
@Override public boolean setPrototype(Environment env, ObjectValue val) {
|
||||
return false;
|
||||
}
|
||||
@Override public State getState() {
|
||||
return State.SEALED;
|
||||
}
|
||||
|
||||
@Override public void preventExtensions() { }
|
||||
@Override public void seal() { }
|
||||
@Override public void freeze() { }
|
||||
|
||||
@Override public ObjectValue getPrototype(Environment env) {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public void add(String name, Value val) {
|
||||
fields.put(name, FieldMember.of(this, val, false));
|
||||
}
|
||||
public void remove(String name) {
|
||||
fields.remove(name);
|
||||
}
|
||||
|
||||
private ScopeObject(ObjectValue proto) {
|
||||
this.proto = proto;
|
||||
}
|
||||
public ScopeObject(Frame frame, String[] names, IntUnaryOperator transformer, ObjectValue proto) {
|
||||
this.proto = proto;
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
fields.put(names[i], new ScopeMember(this, frame, transformer.applyAsInt(i)));
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] fixCaptures(Frame frame, String[] names) {
|
||||
if (names == null) {
|
||||
names = new String[frame.captures.length];
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
names[i] = "var_" + i;
|
||||
}
|
||||
}
|
||||
else if (names.length > frame.captures.length) {
|
||||
var newNames = new String[frame.captures.length];
|
||||
System.arraycopy(names, 0, newNames, 0, frame.captures.length);
|
||||
names = newNames;
|
||||
}
|
||||
else if (names.length < frame.captures.length) {
|
||||
var newNames = new String[frame.captures.length];
|
||||
System.arraycopy(names, 0, newNames, 0, names.length);
|
||||
for (var i = names.length; i < frame.captures.length; i++) {
|
||||
names[i] = "cap_" + i;
|
||||
}
|
||||
names = newNames;
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
private static String[] fixLocals(Frame frame, String[] names) {
|
||||
if (names == null) {
|
||||
names = new String[frame.locals.length];
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
names[i] = "var_" + i;
|
||||
}
|
||||
}
|
||||
else if (names.length > frame.locals.length) {
|
||||
var newNames = new String[frame.locals.length];
|
||||
System.arraycopy(names, 0, newNames, 0, frame.locals.length);
|
||||
names = newNames;
|
||||
}
|
||||
else if (names.length < frame.locals.length) {
|
||||
var newNames = new String[frame.locals.length];
|
||||
System.arraycopy(names, 0, newNames, 0, names.length);
|
||||
for (var i = names.length; i < frame.locals.length; i++) {
|
||||
names[i] = "var_" + i;
|
||||
}
|
||||
names = newNames;
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
private static String[] fixCapturables(Frame frame, String[] names) {
|
||||
if (names == null) {
|
||||
names = new String[frame.capturables.length];
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
names[i] = "var_" + (frame.locals.length + i);
|
||||
}
|
||||
}
|
||||
else if (names.length > frame.capturables.length) {
|
||||
var newNames = new String[frame.capturables.length];
|
||||
System.arraycopy(names, 0, newNames, 0, frame.capturables.length);
|
||||
names = newNames;
|
||||
}
|
||||
else if (names.length < frame.capturables.length) {
|
||||
var newNames = new String[frame.capturables.length];
|
||||
System.arraycopy(names, 0, newNames, 0, names.length);
|
||||
for (var i = names.length; i < frame.capturables.length; i++) {
|
||||
names[i] = "var_" + (frame.locals.length + i);
|
||||
}
|
||||
names = newNames;
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
public static ScopeObject locals(Frame frame, String[] names) {
|
||||
return new ScopeObject(frame, fixLocals(frame, names), v -> v, null);
|
||||
}
|
||||
public static ScopeObject capturables(Frame frame, String[] names) {
|
||||
return new ScopeObject(frame, fixCapturables(frame, names), v -> v + frame.locals.length, null);
|
||||
}
|
||||
public static ScopeObject captures(Frame frame, String[] names) {
|
||||
return new ScopeObject(frame, fixCaptures(frame, names), v -> ~v, null);
|
||||
}
|
||||
|
||||
public static ScopeObject combine(ObjectValue proto, ScopeObject ...objs) {
|
||||
var res = new ScopeObject(proto);
|
||||
|
||||
for (var el : objs) {
|
||||
res.fields.putAll(el.fields);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ScopeObject all(Frame frame, String[] local, String[] capturables, String[] captures) {
|
||||
return combine((ObjectValue)Value.global(frame.env), locals(frame, local), capturables(frame, capturables), captures(frame, captures));
|
||||
}
|
||||
}
|
||||
1209
repl/src/main/java/me/topchetoeu/j2s/repl/debug/SimpleDebugger.java
Normal file
1209
repl/src/main/java/me/topchetoeu/j2s/repl/debug/SimpleDebugger.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import me.topchetoeu.j2s.common.environment.Environment;
|
||||
import me.topchetoeu.j2s.runtime.Frame;
|
||||
import me.topchetoeu.j2s.runtime.values.Value;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.ArrayLikeValue;
|
||||
|
||||
public class StackObject extends ArrayLikeValue {
|
||||
public final Frame frame;
|
||||
|
||||
@Override public Value get(int i) {
|
||||
if (!has(i)) return null;
|
||||
return frame.stack[i];
|
||||
}
|
||||
@Override public boolean set(Environment env, int i, Value val) {
|
||||
if (!has(i)) return false;
|
||||
frame.stack[i] = val;
|
||||
return true;
|
||||
}
|
||||
@Override public boolean has(int i) {
|
||||
return i >= 0 && i < frame.stackPtr;
|
||||
}
|
||||
@Override public boolean remove(int i) {
|
||||
return false;
|
||||
}
|
||||
@Override public boolean setSize(int val) {
|
||||
return false;
|
||||
}
|
||||
@Override public int size() {
|
||||
return frame.stackPtr;
|
||||
}
|
||||
// @Override public void set(int i, Value val) {
|
||||
// }
|
||||
|
||||
public StackObject(Frame frame) {
|
||||
super();
|
||||
this.frame = frame;
|
||||
}
|
||||
}
|
||||
19
repl/src/main/java/me/topchetoeu/j2s/repl/debug/V8Error.java
Normal file
19
repl/src/main/java/me/topchetoeu/j2s/repl/debug/V8Error.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.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)
|
||||
));
|
||||
}
|
||||
}
|
||||
22
repl/src/main/java/me/topchetoeu/j2s/repl/debug/V8Event.java
Normal file
22
repl/src/main/java/me/topchetoeu/j2s/repl/debug/V8Event.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.json.JSONElement;
|
||||
import me.topchetoeu.j2s.common.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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.j2s.repl.debug;
|
||||
|
||||
import me.topchetoeu.j2s.common.json.JSON;
|
||||
import me.topchetoeu.j2s.common.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)
|
||||
);
|
||||
}
|
||||
}
|
||||
186
repl/src/main/java/me/topchetoeu/j2s/repl/debug/WebSocket.java
Normal file
186
repl/src/main/java/me/topchetoeu/j2s/repl/debug/WebSocket.java
Normal file
@@ -0,0 +1,186 @@
|
||||
package me.topchetoeu.j2s.repl.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.repl.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.j2s.repl.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.topchetoeu.j2s.repl.mapping;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import me.topchetoeu.j2s.common.environment.Environment;
|
||||
import me.topchetoeu.j2s.common.parsing.Filename;
|
||||
import me.topchetoeu.j2s.common.parsing.Location;
|
||||
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.j2s.runtime.values.Value;
|
||||
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.ArrayLikeValue;
|
||||
import me.topchetoeu.j2s.runtime.values.objects.ArrayValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
|
||||
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
|
||||
|
||||
public class NativeMapper extends FunctionValue {
|
||||
public final Function<Location, Location> mapper;
|
||||
|
||||
@Override protected Value onApply(Environment env, Value thisArg, Value... args) {
|
||||
var rawLoc = (ArrayLikeValue)args[0];
|
||||
var loc = Location.of(
|
||||
Filename.parse(rawLoc.get(0).toString(env)),
|
||||
rawLoc.get(1).toNumber(env).getInt(),
|
||||
rawLoc.get(2).toNumber(env).getInt()
|
||||
);
|
||||
|
||||
var res = mapper.apply(loc);
|
||||
if (res == null) return Value.UNDEFINED;
|
||||
|
||||
return new ArrayValue(
|
||||
StringValue.of(res.filename().toString()),
|
||||
NumberValue.of(res.line()),
|
||||
NumberValue.of(res.start())
|
||||
);
|
||||
}
|
||||
|
||||
@Override protected Value onConstruct(Environment ext, Value target, Value... args) {
|
||||
throw EngineException.ofType("Function cannot be constructed");
|
||||
}
|
||||
|
||||
public NativeMapper(Function<Location, Location> mapper) {
|
||||
super("mapper", 1);
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public static Function<Location, Location> unwrap(Environment env, FunctionValue func) {
|
||||
if (func instanceof NativeMapper nat) return nat.mapper;
|
||||
|
||||
return loc -> {
|
||||
var rawLoc = new ArrayValue(
|
||||
StringValue.of(loc.filename().toString()),
|
||||
NumberValue.of(loc.line()),
|
||||
NumberValue.of(loc.start())
|
||||
);
|
||||
|
||||
var rawRes = func.apply(env, Value.UNDEFINED, rawLoc);
|
||||
if (rawRes instanceof ArrayLikeValue arr) return Location.of(
|
||||
Filename.parse(arr.get(0).toString(env)),
|
||||
arr.get(1).toNumber(env).getInt(),
|
||||
arr.get(2).toNumber(env).getInt()
|
||||
);
|
||||
else if (rawRes == Value.UNDEFINED || rawRes == Value.NULL) return null;
|
||||
else throw EngineException.ofType("Location must be an array, null or undefined");
|
||||
};
|
||||
}
|
||||
}
|
||||
BIN
repl/src/main/resources/debugger/favicon.png
Normal file
BIN
repl/src/main/resources/debugger/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
30
repl/src/main/resources/debugger/index.html
Normal file
30
repl/src/main/resources/debugger/index.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!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>
|
||||
1004
repl/src/main/resources/debugger/protocol.json
Normal file
1004
repl/src/main/resources/debugger/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
package me.topchetoeu.j2s;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestHelloWorld {
|
||||
|
||||
@Test
|
||||
public void testHelloWorld() {
|
||||
final String message = "Hello World!";
|
||||
assertEquals("Hello World!", message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user