refactor: Transition to a Value class

This commit is contained in:
TopchetoEU 2024-08-25 19:10:05 +03:00
parent 3475e3a130
commit bab59d454f
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
123 changed files with 1863 additions and 9808 deletions

View File

@ -1,4 +1,4 @@
project_group = me.topchetoeu project_group = me.topchetoeu
project_name = jscript project_name = jscript
project_version = 0.9.41-beta project_version = 0.9.41-beta
main_class = me.topchetoeu.jscript.utils.JScriptRepl main_class = me.topchetoeu.jscript.runtime.SimpleRepl

View File

@ -1,13 +1,11 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key; import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.CodeFunction; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
public interface Compiler { public interface Compiler {
public Key<Compiler> KEY = new Key<>(); public Key<Compiler> KEY = new Key<>();

View File

@ -1,9 +1,10 @@
package me.topchetoeu.jscript.compilation.parsing; package me.topchetoeu.jscript.common;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.compilation.parsing.TestRes;
import me.topchetoeu.jscript.compilation.parsing.Token;
import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser; import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser;
public class ParseRes<T> { public class ParseRes<T> {

View File

@ -1,79 +1,16 @@
package me.topchetoeu.jscript.common.json; package me.topchetoeu.jscript.common.json;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Operator; import me.topchetoeu.jscript.compilation.parsing.Operator;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing; import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Token; import me.topchetoeu.jscript.compilation.parsing.Token;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
public class JSON { public class JSON {
public static Object toJs(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement fromJs(Environment ext, Object val, HashSet<Object> prev) {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(ext, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : Values.getMembers(ext, val, false, false)) {
var jsonEl = fromJs(ext, Values.getMember(ext, val, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
public static JSONElement fromJs(Environment ext, Object val) {
return fromJs(ext, val, new HashSet<>());
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) { public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i); return Parsing.parseIdentifier(tokens, i);
} }

View File

@ -15,7 +15,6 @@ import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.utils.mapping.SourceMap;
public class FunctionMap { public class FunctionMap {
public static class FunctionMapBuilder { public static class FunctionMapBuilder {
@ -131,27 +130,27 @@ public class FunctionMap {
return pcToLoc.lastEntry().getValue(); return pcToLoc.lastEntry().getValue();
} }
public FunctionMap apply(SourceMap map) { // public static FunctionMap apply(FunctionMap funcMap, SourceMap map) {
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames); // var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames);
for (var el : pcToLoc.entrySet()) { // for (var el : funcMap.pcToLoc.entrySet()) {
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue())); // res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
} // }
res.bps.putAll(bps); // res.bps.putAll(bps);
for (var el : bpLocs.entrySet()) { // for (var el : bpLocs.entrySet()) {
for (var loc : el.getValue()) { // for (var loc : el.getValue()) {
loc = map.toCompiled(loc); // loc = map.toCompiled(loc);
if (loc == null) continue; // if (loc == null) continue;
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>()); // if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
res.bpLocs.get(loc.filename()).add(loc); // res.bpLocs.get(loc.filename()).add(loc);
} // }
} // }
return res; // return res;
} // }
public FunctionMap clone() { public FunctionMap clone() {
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames); var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);

View File

@ -11,11 +11,12 @@ import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.ParseRes;
import me.topchetoeu.jscript.common.ParseRes.State;
import me.topchetoeu.jscript.compilation.*; import me.topchetoeu.jscript.compilation.*;
import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.compilation.values.*; import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;

View File

@ -1,7 +1,8 @@
package me.topchetoeu.jscript.compilation.parsing; package me.topchetoeu.jscript.compilation.parsing;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State; import me.topchetoeu.jscript.common.ParseRes;
import me.topchetoeu.jscript.common.ParseRes.State;
public class TestRes { public class TestRes {
public final State state; public final State state;

View File

@ -1,456 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.Iterator;
import java.util.Stack;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Array")
public class ArrayLib {
private static int normalizeI(int len, int i, boolean clamp) {
if (i < 0) i += len;
if (clamp) {
if (i < 0) i = 0;
if (i > len) i = len;
}
return i;
}
@Expose(value = "length", type = ExposeType.GETTER)
public static int __getLength(Arguments args) {
return args.self(ArrayValue.class).size();
}
@Expose(value = "length", type = ExposeType.SETTER)
public static void __setLength(Arguments args) {
args.self(ArrayValue.class).setSize(args.getInt(0));
}
@Expose public static ObjectValue __values(Arguments args) {
return __iterator(args);
}
@Expose public static ObjectValue __keys(Arguments args) {
return Values.toJSIterator(args.env, () -> new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < args.self(ArrayValue.class).size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return i++;
}
});
}
@Expose public static ObjectValue __entries(Arguments args) {
return Values.toJSIterator(args.env, () -> new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < args.self(ArrayValue.class).size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return new ArrayValue(args.env, i, args.self(ArrayValue.class).get(i++));
}
});
}
@Expose(value = "@@Symbol.iterator")
public static ObjectValue __iterator(Arguments args) {
return Values.toJSIterator(args.env, args.self(ArrayValue.class));
}
@Expose(value = "@@Symbol.asyncIterator")
public static ObjectValue __asyncIterator(Arguments args) {
return Values.toJSAsyncIterator(args.env, args.self(ArrayValue.class).iterator());
}
@Expose public static ArrayValue __concat(Arguments args) {
// TODO: Fully implement with non-array spreadable objects
var arrs = args.slice(-1);
var size = 0;
for (int i = 0; i < arrs.n(); i++) {
if (arrs.get(i) instanceof ArrayValue) size += arrs.convert(i, ArrayValue.class).size();
else i++;
}
var res = new ArrayValue(size);
for (int i = 0, j = 0; i < arrs.n(); i++) {
if (arrs.get(i) instanceof ArrayValue) {
var arrEl = arrs.convert(i, ArrayValue.class);
int n = arrEl.size();
arrEl.copyTo(res, 0, j, n);
j += n;
}
else {
res.set(args.env, j++, arrs.get(i));
}
}
return res;
}
@Expose public static ArrayValue __sort(Arguments args) {
var arr = args.self(ArrayValue.class);
var cmp = args.convert(0, FunctionValue.class);
var defaultCmp = new NativeFunction("", _args -> {
return _args.getString(0).compareTo(_args.getString(1));
});
arr.sort((a, b) -> {
var res = Values.toNumber(args.env, (cmp == null ? defaultCmp : cmp).call(args.env, null, a, b));
if (res < 0) return -1;
if (res > 0) return 1;
return 0;
});
return arr;
}
@Expose public static ArrayValue __fill(Arguments args) {
var arr = args.self(ArrayValue.class);
var val = args.get(0);
var start = normalizeI(arr.size(), args.getInt(1, 0), true);
var end = normalizeI(arr.size(), args.getInt(2, arr.size()), true);
for (; start < end; start++) arr.set(args.env, start, val);
return arr;
}
@Expose public static boolean __every(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = 0; i < arr.size(); i++) {
if (arr.has(i) && !Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, arr
))) return false;
}
return true;
}
@Expose public static boolean __some(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, arr
))) return true;
}
return false;
}
@Expose public static ArrayValue __filter(Arguments args) {
var arr = args.self(ArrayValue.class);
var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, arr
))) res.set(args.env, j++, arr.get(i));
}
return res;
}
@Expose public static ArrayValue __map(Arguments args) {
var arr = args.self(ArrayValue.class);
var res = new ArrayValue(arr.size());
res.setSize(arr.size());
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) res.set(args.env, i, Values.call(args.env, args.get(0), args.get(1), arr.get(i), i, arr));
}
return res;
}
@Expose public static void __forEach(Arguments args) {
var arr = args.self(ArrayValue.class);
var func = args.convert(0, FunctionValue.class);
var thisArg = args.get(1);
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) func.call(args.env, thisArg, arr.get(i), i, arr);
}
}
@Expose public static Object __reduce(Arguments args) {
var arr = args.self(ArrayValue.class);
var func = args.convert(0, FunctionValue.class);
var res = args.get(1);
var i = 0;
if (args.n() < 2) {
for (; i < arr.size(); i++) {
if (arr.has(i)){
res = arr.get(i++);
break;
}
}
}
for (; i < arr.size(); i++) {
if (arr.has(i)) {
res = func.call(args.env, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Expose public static Object __reduceRight(Arguments args) {
var arr = args.self(ArrayValue.class);
var func = args.convert(0, FunctionValue.class);
var res = args.get(1);
var i = arr.size();
if (args.n() < 2) {
while (!arr.has(i--) && i >= 0) {
res = arr.get(i);
}
}
else i--;
for (; i >= 0; i--) {
if (arr.has(i)) {
res = func.call(args.env, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Expose public static ArrayValue __flat(Arguments args) {
var arr = args.self(ArrayValue.class);
var depth = args.getInt(0, 1);
var res = new ArrayValue(arr.size());
var stack = new Stack<Object>();
var depths = new Stack<Integer>();
stack.push(arr);
depths.push(-1);
while (!stack.empty()) {
var el = stack.pop();
int d = depths.pop();
if ((d == -1 || d < depth) && el instanceof ArrayValue) {
var arrEl = (ArrayValue)el;
for (int i = arrEl.size() - 1; i >= 0; i--) {
if (!arrEl.has(i)) continue;
stack.push(arrEl.get(i));
depths.push(d + 1);
}
}
else res.set(args.env, res.size(), el);
}
return res;
}
@Expose public static ArrayValue __flatMap(Arguments args) {
return __flat(new Arguments(args.env, __map(args), 1));
}
@Expose public static Object __find(Arguments args) {
var arr = args.self(ArrayValue.class);
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return arr.get(i);
}
return null;
}
@Expose public static Object __findLast(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return arr.get(i);
}
return null;
}
@Expose public static int __findIndex(Arguments args) {
var arr = args.self(ArrayValue.class);
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return i;
}
return -1;
}
@Expose public static int __findLastIndex(Arguments args) {
var arr = args.self(ArrayValue.class);
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(Values.call(
args.env, args.get(0), args.get(1),
arr.get(i), i, args.self
))) return i;
}
return -1;
}
@Expose public static int __indexOf(Arguments args) {
var arr = args.self(ArrayValue.class);
var val = args.get(0);
var start = normalizeI(arr.size(), args.getInt(1), true);
for (int i = start; i < arr.size(); i++) {
if (Values.strictEquals(args.env, arr.get(i), val)) return i;
}
return -1;
}
@Expose public static int __lastIndexOf(Arguments args) {
var arr = args.self(ArrayValue.class);
var val = args.get(0);
var start = normalizeI(arr.size(), args.getInt(1), true);
for (int i = arr.size(); i >= start; i--) {
if (Values.strictEquals(args.env, arr.get(i), val)) return i;
}
return -1;
}
@Expose public static boolean __includes(Arguments args) {
return __indexOf(args) >= 0;
}
@Expose public static Object __pop(Arguments args) {
var arr = args.self(ArrayValue.class);
if (arr.size() == 0) return null;
var val = arr.get(arr.size() - 1);
arr.shrink(1);
return val;
}
@Expose public static int __push(Arguments args) {
var arr = args.self(ArrayValue.class);
var values = args.args;
arr.copyFrom(args.env, values, 0, arr.size(), values.length);
return arr.size();
}
@Expose public static Object __shift(Arguments args) {
var arr = args.self(ArrayValue.class);
if (arr.size() == 0) return null;
var val = arr.get(0);
arr.move(1, 0, arr.size());
arr.shrink(1);
return val;
}
@Expose public static int __unshift(Arguments args) {
var arr = args.self(ArrayValue.class);
var values = args.slice(0).args;
arr.move(0, values.length, arr.size());
arr.copyFrom(args.env, values, 0, 0, values.length);
return arr.size();
}
@Expose public static ArrayValue __slice(Arguments args) {
var arr = args.self(ArrayValue.class);
var start = normalizeI(arr.size(), args.getInt(0), true);
var end = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
var res = new ArrayValue(end - start);
arr.copyTo(res, start, 0, end - start);
return res;
}
@Expose public static ArrayValue __splice(Arguments args) {
var arr = args.self(ArrayValue.class);
var start = normalizeI(arr.size(), args.getInt(0), true);
var deleteCount = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
var items = args.slice(2).args;
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
var size = arr.size() - deleteCount + items.length;
var res = new ArrayValue(deleteCount);
arr.copyTo(res, start, 0, deleteCount);
arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount);
arr.copyFrom(args.env, items, 0, start, items.length);
arr.setSize(size);
return res;
}
@Expose public static String __toString(Arguments args) {
return __join(new Arguments(args.env, args.self, ","));
}
@Expose public static String __join(Arguments args) {
var arr = args.self(ArrayValue.class);
var sep = args.getString(0, ", ");
var res = new StringBuilder();
var comma = false;
for (int i = 0; i < arr.size(); i++) {
if (!arr.has(i)) continue;
if (comma) res.append(sep);
comma = true;
var el = arr.get(i);
if (el == null || el == Values.NULL) continue;
res.append(Values.toString(args.env, el));
}
return res.toString();
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isArray(Arguments args) {
return args.get(0) instanceof ArrayValue;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __of(Arguments args) {
return new ArrayValue(args.env, args.slice(0).args);
}
@ExposeConstructor public static ArrayValue __constructor(Arguments args) {
ArrayValue res;
if (args.n() == 1 && args.get(0) instanceof Number) {
var len = args.getInt(0);
res = new ArrayValue(len);
res.setSize(len);
}
else {
var val = args.args;
res = new ArrayValue(val.length);
res.copyFrom(args.env, val, 0, 0, val.length);
}
return res;
}
}

View File

@ -1,88 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("AsyncFunction")
public class AsyncFunctionLib extends FunctionValue {
public final CodeFunction func;
private static class AsyncHelper {
public PromiseLib promise = new PromiseLib();
public Frame frame;
private boolean awaiting = false;
private void next(Environment env, Object inducedValue, EngineException inducedError) {
Object res = null;
frame.onPush();
awaiting = false;
while (!awaiting) {
try {
if (inducedValue != Values.NO_RETURN) res = frame.next(inducedValue);
else if (inducedError != null) res = frame.induceError(inducedError);
else res = frame.next();
inducedValue = Values.NO_RETURN;
inducedError = null;
if (res != Values.NO_RETURN) {
promise.fulfill(env, res);
break;
}
}
catch (EngineException e) {
promise.reject(env, e);
break;
}
}
frame.onPop();
if (awaiting) {
PromiseLib.handle(env, frame.pop(), new Handle() {
@Override
public void onFulfil(Object val) {
next(env, val, null);
}
@Override
public void onReject(EngineException err) {
next(env, Values.NO_RETURN, err);
}
}.defer(env));
}
}
public Object await(Arguments args) {
this.awaiting = true;
return args.get(0);
}
}
@Override
public Object call(Environment env, Object thisArg, Object ...args) {
var handler = new AsyncHelper();
var newArgs = new Object[args.length + 1];
newArgs[0] = new NativeFunction("await", handler::await);
System.arraycopy(args, 0, newArgs, 1, args.length);
handler.frame = new Frame(env, thisArg, newArgs, (CodeFunction)func);
handler.next(env, Values.NO_RETURN, null);
return handler.promise;
}
public AsyncFunctionLib(FunctionValue func) {
super(func.name, func.length);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
this.func = (CodeFunction)func;
}
}

View File

@ -1,33 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("AsyncGeneratorFunction")
public class AsyncGeneratorFunctionLib extends FunctionValue {
public final CodeFunction func;
@Override
public Object call(Environment ext, Object thisArg, Object ...args) {
var handler = new AsyncGeneratorLib();
var newArgs = new Object[args.length + 2];
newArgs[0] = new NativeFunction("await", handler::await);
newArgs[1] = new NativeFunction("yield", handler::yield);
System.arraycopy(args, 0, newArgs, 2, args.length);
handler.frame = new Frame(ext, thisArg, newArgs, func);
return handler;
}
public AsyncGeneratorFunctionLib(CodeFunction func) {
super(func.name, func.length);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a code function.");
this.func = func;
}
}

View File

@ -1,111 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.Map;
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("AsyncGenerator")
public class AsyncGeneratorLib {
private int state = 0;
private boolean done = false;
private PromiseLib currPromise;
public Frame frame;
private void next(Environment env, Object inducedValue, Object inducedReturn, EngineException inducedError) {
if (done) {
if (inducedError != null) throw inducedError;
currPromise.fulfill(env, new ObjectValue(env, Map.of(
"done", true,
"value", inducedReturn == Values.NO_RETURN ? null : inducedReturn
)));
return;
}
Object res = null;
state = 0;
frame.onPush();
while (state == 0) {
try {
if (inducedValue != Values.NO_RETURN) res = frame.next(inducedValue);
else if (inducedReturn != Values.NO_RETURN) res = frame.induceReturn(inducedValue);
else if (inducedError != null) res = frame.induceError(inducedError);
else res = frame.next();
inducedValue = inducedReturn = Values.NO_RETURN;
inducedError = null;
if (res != Values.NO_RETURN) {
var obj = new ObjectValue();
obj.defineProperty(env, "done", true);
obj.defineProperty(env, "value", res);
currPromise.fulfill(env, obj);
break;
}
}
catch (EngineException e) {
currPromise.reject(env, e);
break;
}
}
frame.onPop();
if (state == 1) {
PromiseLib.handle(env, frame.pop(), new Handle() {
@Override public void onFulfil(Object val) {
next(env, val, Values.NO_RETURN, null);
}
@Override public void onReject(EngineException err) {
next(env, Values.NO_RETURN, Values.NO_RETURN, err);
}
}.defer(env));
}
else if (state == 2) {
var obj = new ObjectValue();
obj.defineProperty(env, "done", false);
obj.defineProperty(env, "value", frame.pop());
currPromise.fulfill(env, obj);
}
}
@Override
public String toString() {
if (done) return "Generator [closed]";
if (state != 0) return "Generator [suspended]";
return "Generator [running]";
}
public Object await(Arguments args) {
this.state = 1;
return args.get(0);
}
public Object yield(Arguments args) {
this.state = 2;
return args.get(0);
}
@Expose public PromiseLib __next(Arguments args) {
this.currPromise = new PromiseLib();
if (args.has(0)) next(args.env, args.get(0), Values.NO_RETURN, null);
else next(args.env, Values.NO_RETURN, Values.NO_RETURN, null);
return this.currPromise;
}
@Expose public PromiseLib __return(Arguments args) {
this.currPromise = new PromiseLib();
next(args.env, Values.NO_RETURN, args.get(0), null);
return this.currPromise;
}
@Expose public PromiseLib __throw(Arguments args) {
this.currPromise = new PromiseLib();
next(args.env, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setEnvironment(args.env));
return this.currPromise;
}
}

View File

@ -1,37 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Boolean")
public class BooleanLib {
public static final BooleanLib TRUE = new BooleanLib(true);
public static final BooleanLib FALSE = new BooleanLib(false);
public final boolean value;
@Override public String toString() {
return value + "";
}
public BooleanLib(boolean val) {
this.value = val;
}
@ExposeConstructor public static Object __constructor(Arguments args) {
var val = args.getBoolean(0);
if (args.self instanceof ObjectValue) return val ? TRUE : FALSE;
else return val;
}
@Expose public static String __toString(Arguments args) {
return args.self(Boolean.class) ? "true" : "false";
}
@Expose public static boolean __valueOf(Arguments args) {
if (Values.isWrapper(args.self, BooleanLib.class)) return Values.wrapper(args.self, BooleanLib.class).value;
return args.self(Boolean.class);
}
}

View File

@ -1,38 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.io.IOException;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Console")
public class ConsoleLib {
public static interface Writer {
void writeLine(String val) throws IOException;
}
private File file;
@Expose
public void __log(Arguments args) {
var res = new StringBuilder();
var first = true;
for (var el : args.args) {
if (!first) res.append(" ");
first = false;
res.append(Values.toReadable(args.env, el).getBytes());
}
for (var line : res.toString().split("\n", -1)) {
file.write(line.getBytes());
}
}
public ConsoleLib(File file) {
this.file = file;
}
}

View File

@ -1,266 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Date")
public class DateLib {
private Calendar normal;
private Calendar utc;
private void updateUTC() {
if (utc == null || normal == null) return;
utc.setTimeInMillis(normal.getTimeInMillis());
}
private void updateNormal() {
if (utc == null || normal == null) return;
normal.setTimeInMillis(utc.getTimeInMillis());
}
private void invalidate() {
normal = utc = null;
}
@Expose public double __getYear() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.YEAR) - 1900;
}
@Expose public double __setYeard(Arguments args) {
var real = args.getDouble(0);
if (real >= 0 && real <= 99) real = real + 1900;
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.YEAR, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __getFullYear() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.YEAR);
}
@Expose public double __getMonth() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.MONTH);
}
@Expose public double __getDate() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.DAY_OF_MONTH);
}
@Expose public double __getDay() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.DAY_OF_WEEK);
}
@Expose public double __getHours() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.HOUR_OF_DAY);
}
@Expose public double __getMinutes() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.MINUTE);
}
@Expose public double __getSeconds() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.SECOND);
}
@Expose public double __getMilliseconds() {
if (normal == null) return Double.NaN;
return normal.get(Calendar.MILLISECOND);
}
@Expose public double __getUTCFullYear() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.YEAR);
}
@Expose public double __getUTCMonth() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.MONTH);
}
@Expose public double __getUTCDate() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.DAY_OF_MONTH);
}
@Expose public double __getUTCDay() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.DAY_OF_WEEK);
}
@Expose public double __getUTCHours() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.HOUR_OF_DAY);
}
@Expose public double __getUTCMinutes() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.MINUTE);
}
@Expose public double __getUTCSeconds() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.SECOND);
}
@Expose public double __getUTCMilliseconds() {
if (utc == null) return Double.NaN;
return utc.get(Calendar.MILLISECOND);
}
@Expose public double __setFullYear(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.YEAR, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setMonthd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MONTH, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setDated(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.DAY_OF_MONTH, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setDayd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.DAY_OF_WEEK, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setHoursd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.HOUR_OF_DAY, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setMinutesd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MINUTE, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setSecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.SECOND, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setMillisecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MILLISECOND, (int)real);
updateUTC();
return __getTime();
}
@Expose public double __setUTCFullYeard(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.YEAR, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCMonthd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MONTH, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCDated(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.DAY_OF_MONTH, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCDayd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.DAY_OF_WEEK, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCHoursd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.HOUR_OF_DAY, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCMinutesd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MINUTE, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCSecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.SECOND, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __setUTCMillisecondsd(Arguments args) {
var real = args.getDouble(0);
if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MILLISECOND, (int)real);
updateNormal();
return __getTime();
}
@Expose public double __getTime() {
if (utc == null) return Double.NaN;
return utc.getTimeInMillis();
}
@Expose public double __getTimezoneOffset() {
if (normal == null) return Double.NaN;
return normal.getTimeZone().getRawOffset() / 60000;
}
@Expose public double __valueOf() {
if (normal == null) return Double.NaN;
else return normal.getTimeInMillis();
}
@Expose public String __toString() {
return normal.getTime().toString();
}
@Override public String toString() {
return __toString();
}
public DateLib(long timestamp) {
normal = Calendar.getInstance();
utc = Calendar.getInstance();
normal.setTimeInMillis(timestamp);
utc.setTimeZone(TimeZone.getTimeZone("UTC"));
utc.setTimeInMillis(timestamp);
}
public DateLib() {
this(new Date().getTime());
}
@ExposeConstructor public static DateLib init(Arguments args) {
if (args.has(0)) return new DateLib(args.getLong(0));
else return new DateLib();
}
@Expose(target = ExposeTarget.STATIC)
public static double __now() {
return new DateLib().__getTime();
}
}

View File

@ -1,89 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Encoding")
public class EncodingLib {
private static final String HEX = "0123456789ABCDEF";
public static String encodeUriAny(String str, String keepAlphabet) {
if (str == null) str = "undefined";
var bytes = str.getBytes();
var sb = new StringBuilder(bytes.length);
for (byte c : bytes) {
if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
else {
sb.append('%');
sb.append(HEX.charAt(c / 16));
sb.append(HEX.charAt(c % 16));
}
}
return sb.toString();
}
public static String decodeUriAny(String str, String keepAlphabet) {
if (str == null) str = "undefined";
var res = new Buffer();
var bytes = str.getBytes();
for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
if (c == '%') {
if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
if (!Parsing.isAny((char)b, keepAlphabet)) {
i += 2;
res.append((byte)b);
continue;
}
}
res.append(c);
}
return new String(res.data());
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __encode(Arguments args) {
var res = new ArrayValue();
for (var el : args.getString(0).getBytes()) res.set(null, res.size(), (int)el);
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static String __decode(Arguments args) {
var raw = args.convert(0, ArrayList.class);
var res = new byte[raw.size()];
for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(args.env, raw.get(i));
return new String(res);
}
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURIComponent(Arguments args) {
return EncodingLib.encodeUriAny(args.getString(0), ".-_!~*'()");
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURIComponent(Arguments args) {
return decodeUriAny(args.getString(0), "");
}
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURI(Arguments args) {
return encodeUriAny(args.getString(0), ";,/?:@&=+$#.-_!~*'()");
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURI(Arguments args) {
return decodeUriAny(args.getString(0), ",/?:@&=+$#.");
}
}

View File

@ -1,54 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.ConvertException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Error")
public class ErrorLib {
private static String toString(Environment ctx, Object name, Object message) {
if (name == null) name = "";
else name = Values.toString(ctx, name).trim();
if (message == null) message = "";
else message = Values.toString(ctx, message).trim();
StringBuilder res = new StringBuilder();
if (!name.equals("")) res.append(name);
if (!message.equals("") && !name.equals("")) res.append(": ");
if (!message.equals("")) res.append(message);
return res.toString();
}
@ExposeField public static final String __name = "Error";
@Expose public static String __toString(Arguments args) {
if (args.self instanceof ObjectValue) return toString(args.env,
Values.getMember(args.env, args.self, "name"),
Values.getMember(args.env, args.self, "message")
);
else return "[Invalid error]";
}
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
var target = new ObjectValue();
var message = args.getString(0, "");
try {
target = args.self(ObjectValue.class);
}
catch (ConvertException e) {}
target.setPrototype(PlaceholderProto.ERROR);
target.defineProperty(args.env, "message", Values.toString(args.env, message));
return target;
}
}

View File

@ -1,84 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("File")
public class FileLib {
public final File fd;
@Expose public PromiseLib __pointer(Arguments args) {
return PromiseLib.await(args.env, () -> {
try {
return fd.seek(0, 1);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __length(Arguments args) {
return PromiseLib.await(args.env, () -> {
try {
long curr = fd.seek(0, 1);
long res = fd.seek(0, 2);
fd.seek(curr, 0);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __read(Arguments args) {
return PromiseLib.await(args.env, () -> {
var n = args.getInt(0);
try {
var buff = new byte[n];
var res = new ArrayValue();
int resI = fd.read(buff);
for (var i = resI - 1; i >= 0; i--) res.set(args.env, i, (int)buff[i]);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __write(Arguments args) {
return PromiseLib.await(args.env, () -> {
var val = args.convert(0, ArrayValue.class);
try {
var res = new byte[val.size()];
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.env, val.get(i));
fd.write(res);
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose public PromiseLib __close(Arguments args) {
return PromiseLib.await(args.env, () -> {
fd.close();
return null;
});
}
@Expose public PromiseLib __seek(Arguments args) {
return PromiseLib.await(args.env, () -> {
var ptr = args.getLong(0);
var whence = args.getInt(1);
try {
return fd.seek(ptr, whence);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
public FileLib(File fd) {
this.fd = fd;
}
}

View File

@ -1,193 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.io.IOException;
import java.util.Iterator;
import java.util.Stack;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.ActionType;
import me.topchetoeu.jscript.utils.filesystem.EntryType;
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FileStat;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Filesystem")
public class FilesystemLib {
@ExposeField(target = ExposeTarget.STATIC)
public static final int __SEEK_SET = 0;
@ExposeField(target = ExposeTarget.STATIC)
public static final int __SEEK_CUR = 1;
@ExposeField(target = ExposeTarget.STATIC)
public static final int __SEEK_END = 2;
private static Filesystem fs(Environment env) {
var fs = Filesystem.get(env);
if (fs != null) return fs;
throw EngineException.ofError("Current environment doesn't have a file system.");
}
@Expose(target = ExposeTarget.STATIC)
public static String __normalize(Arguments args) {
return fs(args.env).normalize(args.convert(String.class));
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __open(Arguments args) {
return PromiseLib.await(args.env, () -> {
var fs = fs(args.env);
var path = fs.normalize(args.getString(0));
var _mode = Mode.parse(args.getString(1));
try {
if (fs.stat(path).type != EntryType.FILE) {
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN).setPath(path);
}
return new FileLib(fs.open(path, _mode));
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __ls(Arguments args) {
return Values.toJSAsyncIterator(args.env, new Iterator<>() {
private boolean failed, done;
private File file;
private String nextLine;
private void update() {
if (done) return;
if (!failed) {
if (file == null) {
var fs = fs(args.env);
var path = fs.normalize(args.getString(0));
if (fs.stat(path).type != EntryType.FOLDER) {
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN);
}
file = fs.open(path, Mode.READ);
}
if (nextLine == null) {
while (true) {
nextLine = file.readLine();
if (nextLine == null) {
done = true;
return;
}
nextLine = nextLine.trim();
if (!nextLine.equals("")) break;
}
}
}
}
@Override
public boolean hasNext() {
try {
update();
return !done && !failed;
}
catch (FilesystemException e) { throw e.toEngineException(); }
}
@Override
public String next() {
try {
update();
var res = nextLine;
nextLine = null;
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
}
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __mkdir(Arguments args) throws IOException {
return PromiseLib.await(args.env, () -> {
try {
fs(args.env).create(args.getString(0), EntryType.FOLDER);
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __mkfile(Arguments args) throws IOException {
return PromiseLib.await(args.env, () -> {
try {
fs(args.env).create(args.getString(0), EntryType.FILE);
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __rm(Arguments args) throws IOException {
return PromiseLib.await(args.env, () -> {
try {
var fs = fs(args.env);
var path = fs.normalize(args.getString(0));
var recursive = args.getBoolean(1);
if (!recursive) fs.create(path, EntryType.NONE);
else {
var stack = new Stack<String>();
stack.push(path);
while (!stack.empty()) {
var currPath = stack.pop();
FileStat stat;
try { stat = fs.stat(currPath); }
catch (FilesystemException e) { continue; }
if (stat.type == EntryType.FOLDER) {
for (var el : fs.open(currPath, Mode.READ).readToString().split("\n")) stack.push(el);
}
else fs.create(currPath, EntryType.NONE);
}
}
return null;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __stat(Arguments args) throws IOException {
return PromiseLib.await(args.env, () -> {
try {
var fs = fs(args.env);
var path = fs.normalize(args.getString(0));
var stat = fs.stat(path);
var res = new ObjectValue();
res.defineProperty(args.env, "type", stat.type.name);
res.defineProperty(args.env, "mode", stat.mode.name);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __exists(Arguments args) throws IOException {
return PromiseLib.await(args.env, () -> {
try { fs(args.env).stat(args.getString(0)); return true; }
catch (FilesystemException e) { return false; }
});
}
}

View File

@ -1,80 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.runtime.Compiler;
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Function")
public class FunctionLib {
private static int i;
@Expose public static Object __apply(Arguments args) {
return args.self(FunctionValue.class).call(args.env, args.get(0), args.convert(1, ArrayValue.class).toArray());
}
@Expose public static Object __call(Arguments args) {
return args.self(FunctionValue.class).call(args.env, args.get(0), args.slice(1).args);
}
@Expose public static FunctionValue __bind(Arguments args) {
var self = args.self(FunctionValue.class);
var thisArg = args.get(0);
var bindArgs = args.slice(1).args;
return new NativeFunction(self.name + " (bound)", callArgs -> {
Object[] resArgs;
if (args.n() == 0) resArgs = bindArgs;
else {
resArgs = new Object[bindArgs.length + callArgs.n()];
System.arraycopy(bindArgs, 0, resArgs, 0, bindArgs.length);
System.arraycopy(callArgs.args, 0, resArgs, bindArgs.length, callArgs.n());
}
return self.call(callArgs.env, thisArg, resArgs);
});
}
@Expose public static String __toString(Arguments args) {
return args.self.toString();
}
@Expose(target = ExposeTarget.STATIC)
public static FunctionValue __async(Arguments args) {
return new AsyncFunctionLib(args.convert(0, FunctionValue.class));
}
@Expose(target = ExposeTarget.STATIC)
public static FunctionValue __asyncGenerator(Arguments args) {
return new AsyncGeneratorFunctionLib(args.convert(0, CodeFunction.class));
}
@Expose(target = ExposeTarget.STATIC)
public static FunctionValue __generator(Arguments args) {
return new GeneratorFunctionLib(args.convert(0, CodeFunction.class));
}
@ExposeConstructor
public static Object __constructor(Arguments args) {
var parts = args.convert(String.class);
if (parts.length == 0) parts = new String[] { "" };
var src = "return function(";
for (var i = 0; i < parts.length - 1; i++) {
if (i != 0) src += ",";
src += parts[i];
}
src += "){" + parts[parts.length - 1] + "}";
var body = Compiler.get(args.env).compile(new Filename("jscript", "func/" + i++), src);
var func = new CodeFunction(args.env, "", body, new ValueVariable[0]);
return Values.call(args.env, func, null);
}
}

View File

@ -1,31 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("GeneratorFunction")
public class GeneratorFunctionLib extends FunctionValue {
public final CodeFunction func;
@Override public Object call(Environment env, Object thisArg, Object ...args) {
var handler = new GeneratorLib();
var newArgs = new Object[args.length + 1];
newArgs[0] = new NativeFunction("yield", handler::yield);
System.arraycopy(args, 0, newArgs, 1, args.length);
handler.frame = new Frame(env, thisArg, newArgs, func);
return handler;
}
public GeneratorFunctionLib(CodeFunction func) {
super(func.name, func.length);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
this.func = func;
}
}

View File

@ -1,82 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Generator")
public class GeneratorLib {
private boolean yielding = true;
private boolean done = false;
public Frame frame;
private ObjectValue next(Environment env, Object inducedValue, Object inducedReturn, EngineException inducedError) {
if (done) {
if (inducedError != Values.NO_RETURN) throw inducedError;
var res = new ObjectValue();
res.defineProperty(env, "done", true);
res.defineProperty(env, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn);
return res;
}
Object res = null;
yielding = false;
frame.onPush();
while (!yielding) {
try {
if (inducedValue != Values.NO_RETURN) res = frame.next(inducedValue);
else if (inducedReturn != Values.NO_RETURN) res = frame.induceReturn(inducedValue);
else if (inducedError != null) res = frame.induceError(inducedError);
else res = frame.next();
inducedReturn = Values.NO_RETURN;
inducedError = null;
if (res != Values.NO_RETURN) {
done = true;
break;
}
}
catch (EngineException e) {
done = true;
throw e;
}
}
frame.onPop();
if (done) frame = null;
else res = frame.pop();
var obj = new ObjectValue();
obj.defineProperty(env, "done", done);
obj.defineProperty(env, "value", res);
return obj;
}
@Expose public ObjectValue __next(Arguments args) {
if (args.n() == 0) return next(args.env, Values.NO_RETURN, Values.NO_RETURN, null);
else return next(args.env, args.get(0), Values.NO_RETURN, null);
}
@Expose public ObjectValue __throw(Arguments args) {
return next(args.env, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setEnvironment(args.env));
}
@Expose public ObjectValue __return(Arguments args) {
return next(args.env, Values.NO_RETURN, args.get(0), null);
}
@Override public String toString() {
if (done) return "Generator [closed]";
if (yielding) return "Generator [suspended]";
return "Generator [running]";
}
public Object yield(Arguments args) {
this.yielding = true;
return args.get(0);
}
}

View File

@ -1,229 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.HashMap;
import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
public class Internals {
private static final Key<HashMap<Integer, Thread>> THREADS = new Key<>();
private static final Key<Integer> I = new Key<>();
@Expose(target = ExposeTarget.STATIC)
public static Object __require(Arguments args) {
var repo = ModuleRepo.get(args.env);
if (repo != null) {
var res = repo.getModule(args.env, ModuleRepo.cwd(args.env), args.getString(0));
res.load(args.env);
return res.value();
}
else throw EngineException.ofError("Modules are not supported.");
}
@Expose(target = ExposeTarget.STATIC)
public static Thread __setTimeout(Arguments args) {
var func = args.convert(0, FunctionValue.class);
var delay = args.getDouble(1);
var arguments = args.slice(2).args;
if (!args.env.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
var thread = new Thread(() -> {
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000);
try { Thread.sleep(ms, ns); }
catch (InterruptedException e) { return; }
args.env.get(EventLoop.KEY).pushMsg(() -> func.call(args.env, null, arguments), false);
});
thread.start();
args.env.init(I, 1);
args.env.init(THREADS, new HashMap<>());
var i = args.env.get(I);
args.env.add(I, i + 1);
args.env.get(THREADS).put(i, thread);
return thread;
}
@Expose(target = ExposeTarget.STATIC)
public static Thread __setInterval(Arguments args) {
var func = args.convert(0, FunctionValue.class);
var delay = args.getDouble(1);
var arguments = args.slice(2).args;
if (!args.env.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
var thread = new Thread(() -> {
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000);
while (true) {
try {
Thread.sleep(ms, ns);
}
catch (InterruptedException e) { return; }
args.env.get(EventLoop.KEY).pushMsg(() -> func.call(args.env, null, arguments), false);
}
});
thread.start();
args.env.init(I, 1);
args.env.init(THREADS, new HashMap<>());
var i = args.env.get(I);
args.env.add(I, i + 1);
args.env.get(THREADS).put(i, thread);
return thread;
}
@Expose(target = ExposeTarget.STATIC)
public static void __clearTimeout(Arguments args) {
var i = args.getInt(0);
HashMap<Integer, Thread> map = args.env.get(THREADS);
if (map == null) return;
var thread = map.get(i);
if (thread == null) return;
thread.interrupt();
map.remove(i);
}
@Expose(target = ExposeTarget.STATIC)
public static void __clearInterval(Arguments args) {
__clearTimeout(args);
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseInt(Arguments args) {
return NumberLib.__parseInt(args);
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseFloat(Arguments args) {
return NumberLib.__parseFloat(args);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isNaN(Arguments args) {
return NumberLib.__isNaN(args);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isFinite(Arguments args) {
return NumberLib.__isFinite(args);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isInfinite(Arguments args) {
return NumberLib.__isInfinite(args);
}
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
public static FileLib __stdin(Arguments args) {
return new FileLib(Filesystem.get(args.env).open("std://in", Mode.READ));
}
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
public static FileLib __stdout(Arguments args) {
return new FileLib(Filesystem.get(args.env).open("std://out", Mode.READ));
}
@Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER)
public static FileLib __stderr(Arguments args) {
return new FileLib(Filesystem.get(args.env).open("std://err", Mode.READ));
}
@ExposeField(target = ExposeTarget.STATIC)
public static double __NaN = Double.NaN;
@ExposeField(target = ExposeTarget.STATIC)
public static double __Infinity = Double.POSITIVE_INFINITY;
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURIComponent(Arguments args) {
return EncodingLib.__encodeURIComponent(args);
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURIComponent(Arguments args) {
return EncodingLib.__decodeURIComponent(args);
}
@Expose(target = ExposeTarget.STATIC)
public static String __encodeURI(Arguments args) {
return EncodingLib.__encodeURI(args);
}
@Expose(target = ExposeTarget.STATIC)
public static String __decodeURI(Arguments args) {
return EncodingLib.__decodeURI(args);
}
public static Environment apply(Environment env) {
var wp = new NativeWrapperProvider();
var glob = new GlobalScope(wp.getNamespace(Internals.class));
glob.define(null, "Math", false, wp.getNamespace(MathLib.class));
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class));
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
glob.define(null, false, wp.getConstr(FileLib.class));
glob.define(null, false, wp.getConstr(DateLib.class));
glob.define(null, false, wp.getConstr(ObjectLib.class));
glob.define(null, false, wp.getConstr(FunctionLib.class));
glob.define(null, false, wp.getConstr(ArrayLib.class));
glob.define(null, false, wp.getConstr(BooleanLib.class));
glob.define(null, false, wp.getConstr(NumberLib.class));
glob.define(null, false, wp.getConstr(StringLib.class));
glob.define(null, false, wp.getConstr(SymbolLib.class));
glob.define(null, false, wp.getConstr(PromiseLib.class));
glob.define(null, false, wp.getConstr(RegExpLib.class));
glob.define(null, false, wp.getConstr(MapLib.class));
glob.define(null, false, wp.getConstr(SetLib.class));
glob.define(null, false, wp.getConstr(ErrorLib.class));
glob.define(null, false, wp.getConstr(SyntaxErrorLib.class));
glob.define(null, false, wp.getConstr(TypeErrorLib.class));
glob.define(null, false, wp.getConstr(RangeErrorLib.class));
env.add(Environment.OBJECT_PROTO, wp.getProto(ObjectLib.class));
env.add(Environment.FUNCTION_PROTO, wp.getProto(FunctionLib.class));
env.add(Environment.ARRAY_PROTO, wp.getProto(ArrayLib.class));
env.add(Environment.BOOL_PROTO, wp.getProto(BooleanLib.class));
env.add(Environment.NUMBER_PROTO, wp.getProto(NumberLib.class));
env.add(Environment.STRING_PROTO, wp.getProto(StringLib.class));
env.add(Environment.SYMBOL_PROTO, wp.getProto(SymbolLib.class));
env.add(Environment.ERROR_PROTO, wp.getProto(ErrorLib.class));
env.add(Environment.SYNTAX_ERR_PROTO, wp.getProto(SyntaxErrorLib.class));
env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class));
env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class));
env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class));
Values.setPrototype(Environment.empty(), wp.getProto(ObjectLib.class), null);
env.add(NativeWrapperProvider.KEY, wp);
env.add(GlobalScope.KEY, glob);
return env;
}
}

View File

@ -1,24 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("JSON")
public class JSONLib {
@Expose(target = ExposeTarget.STATIC)
public static Object __parse(Arguments args) {
try {
return JSON.toJs(JSON.parse(null, args.getString(0)));
}
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
}
@Expose(target = ExposeTarget.STATIC)
public static String __stringify(Arguments args) {
return JSON.stringify(JSON.fromJs(args.env, args.get(0)));
}
}

View File

@ -1,87 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Map")
public class MapLib {
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
@Expose("@@Symbol.iterator")
public ObjectValue __iterator(Arguments args) {
return this.__entries(args);
}
@Expose public void __clear() {
map.clear();
}
@Expose public boolean __delete(Arguments args) {
var key = args.get(0);
if (map.containsKey(key)) {
map.remove(key);
return true;
}
return false;
}
@Expose public ObjectValue __entries(Arguments args) {
return Values.toJSIterator(args.env, map
.entrySet()
.stream()
.map(v -> new ArrayValue(args.env, v.getKey(), v.getValue()))
.collect(Collectors.toList())
);
}
@Expose public ObjectValue __keys(Arguments args) {
return Values.toJSIterator(args.env, map.keySet());
}
@Expose public ObjectValue __values(Arguments args) {
return Values.toJSIterator(args.env, map.values());
}
@Expose public Object __get(Arguments args) {
return map.get(args.get(0));
}
@Expose public MapLib __set(Arguments args) {
map.put(args.get(0), args.get(1));
return this;
}
@Expose public boolean __has(Arguments args) {
return map.containsKey(args.get(0));
}
@Expose(type = ExposeType.GETTER)
public int __size() {
return map.size();
}
@Expose public void __forEach(Arguments args) {
var keys = new ArrayList<>(map.keySet());
for (var el : keys) Values.call(args.env, args.get(0), args.get(1), map.get(el), el, args.self);
}
public MapLib(Environment env, Object iterable) {
for (var el : Values.fromJSIterator(env, iterable)) {
try {
map.put(Values.getMember(env, el, 0), Values.getMember(env, el, 1));
}
catch (IllegalArgumentException e) { }
}
}
@ExposeConstructor public static MapLib __constructor(Arguments args) {
return new MapLib(args.env, args.get(0));
}
}

View File

@ -1,211 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Math")
public class MathLib {
@ExposeField(target = ExposeTarget.STATIC)
public static final double __E = Math.E;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __PI = Math.PI;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __SQRT2 = Math.sqrt(2);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __SQRT1_2 = Math.sqrt(.5);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LN2 = Math.log(2);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LN10 = Math.log(10);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LOG2E = Math.log(Math.E) / __LN2;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __LOG10E = Math.log10(Math.E);
@Expose(target = ExposeTarget.STATIC)
public static double __asin(Arguments args) {
return Math.asin(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __acos(Arguments args) {
return Math.acos(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __atan(Arguments args) {
return Math.atan(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __atan2(Arguments args) {
var x = args.getDouble(1);
var y = args.getDouble(0);
if (x == 0) {
if (y == 0) return Double.NaN;
return Math.signum(y) * Math.PI / 2;
}
else {
var val = Math.atan(y / x);
if (x > 0) return val;
else if (y < 0) return val - Math.PI;
else return val + Math.PI;
}
}
@Expose(target = ExposeTarget.STATIC)
public static double __asinh(Arguments args) {
var x = args.getDouble(0);
return Math.log(x + Math.sqrt(x * x + 1));
}
@Expose(target = ExposeTarget.STATIC)
public static double __acosh(Arguments args) {
var x = args.getDouble(0);
return Math.log(x + Math.sqrt(x * x - 1));
}
@Expose(target = ExposeTarget.STATIC)
public static double __atanh(Arguments args) {
var x = args.getDouble(0);
if (x <= -1 || x >= 1) return Double.NaN;
return .5 * Math.log((1 + x) / (1 - x));
}
@Expose(target = ExposeTarget.STATIC)
public static double __sin(Arguments args) {
return Math.sin(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __cos(Arguments args) {
return Math.cos(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __tan(Arguments args) {
return Math.tan(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __sinh(Arguments args) {
return Math.sinh(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __cosh(Arguments args) {
return Math.cosh(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __tanh(Arguments args) {
return Math.tanh(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __sqrt(Arguments args) {
return Math.sqrt(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __cbrt(Arguments args) {
return Math.cbrt(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __hypot(Arguments args) {
var res = 0.;
for (var i = 0; i < args.n(); i++) {
var val = args.getDouble(i);
res += val * val;
}
return Math.sqrt(res);
}
@Expose(target = ExposeTarget.STATIC)
public static int __imul(Arguments args) { return args.getInt(0) * args.getInt(1); }
@Expose(target = ExposeTarget.STATIC)
public static double __exp(Arguments args) {
return Math.exp(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __expm1(Arguments args) {
return Math.expm1(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __pow(Arguments args) { return Math.pow(args.getDouble(0), args.getDouble(1)); }
@Expose(target = ExposeTarget.STATIC)
public static double __log(Arguments args) {
return Math.log(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __log10(Arguments args) {
return Math.log10(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __log1p(Arguments args) {
return Math.log1p(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __log2(Arguments args) {
return Math.log(args.getDouble(0)) / __LN2;
}
@Expose(target = ExposeTarget.STATIC)
public static double __ceil(Arguments args) {
return Math.ceil(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __floor(Arguments args) {
return Math.floor(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __round(Arguments args) {
return Math.round(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static float __fround(Arguments args) {
return (float)args.getDouble(0);
}
@Expose(target = ExposeTarget.STATIC)
public static double __trunc(Arguments args) {
var x = args.getDouble(0);
return Math.floor(Math.abs(x)) * Math.signum(x);
}
@Expose(target = ExposeTarget.STATIC)
public static double __abs(Arguments args) {
return Math.abs(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __max(Arguments args) {
var res = Double.NEGATIVE_INFINITY;
for (var i = 0; i < args.n(); i++) {
var el = args.getDouble(i);
if (el > res) res = el;
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static double __min(Arguments args) {
var res = Double.POSITIVE_INFINITY;
for (var i = 0; i < args.n(); i++) {
var el = args.getDouble(i);
if (el < res) res = el;
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static double __sign(Arguments args) {
return Math.signum(args.getDouble(0));
}
@Expose(target = ExposeTarget.STATIC)
public static double __random() { return Math.random(); }
@Expose(target = ExposeTarget.STATIC)
public static int __clz32(Arguments args) {
return Integer.numberOfLeadingZeros(args.getInt(0));
}
}

View File

@ -1,103 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.text.NumberFormat;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Number")
public class NumberLib {
@ExposeField(target = ExposeTarget.STATIC)
public static final double __EPSILON = Math.ulp(1.0);
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MAX_SAFE_INTEGER = 9007199254740991.;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MIN_SAFE_INTEGER = -__MAX_SAFE_INTEGER;
// lmao big number go brrr
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __MIN_VALUE = -__MAX_VALUE;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __NaN = 0. / 0;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __NEGATIVE_INFINITY = -1. / 0;
@ExposeField(target = ExposeTarget.STATIC)
public static final double __POSITIVE_INFINITY = 1. / 0;
public final double value;
@Override public String toString() { return value + ""; }
public NumberLib(double val) {
this.value = val;
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); }
@Expose(target = ExposeTarget.STATIC)
public static boolean __isInfinite(Arguments args) { return Double.isInfinite(args.getDouble(0)); }
@Expose(target = ExposeTarget.STATIC)
public static boolean __isNaN(Arguments args) { return Double.isNaN(args.getDouble(0)); }
@Expose(target = ExposeTarget.STATIC)
public static boolean __isSafeInteger(Arguments args) {
return args.getDouble(0) > __MIN_SAFE_INTEGER && args.getDouble(0) < __MAX_SAFE_INTEGER;
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseFloat(Arguments args) {
return args.getDouble(0);
}
@Expose(target = ExposeTarget.STATIC)
public static double __parseInt(Arguments args) {
var radix = args.getInt(1, 10);
if (radix < 2 || radix > 36) return Double.NaN;
else {
long res = 0;
for (var c : args.getString(0).toCharArray()) {
var digit = 0;
if (c >= '0' && c <= '9') digit = c - '0';
else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10;
else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10;
else break;
if (digit > radix) break;
res *= radix;
res += digit;
}
return res;
}
}
@ExposeConstructor public static Object __constructor(Arguments args) {
if (args.self instanceof ObjectValue) return new NumberLib(args.getDouble(0));
else return args.getDouble(0);
}
@Expose public static String __toString(Arguments args) {
return Values.toString(args.env, args.self);
}
@Expose public static String __toFixed(Arguments args) {
var digits = args.getInt(0, 0);
var nf = NumberFormat.getNumberInstance();
nf.setMinimumFractionDigits(digits);
nf.setMaximumFractionDigits(digits);
return nf.format(args.getDouble(-1));
}
@Expose public static double __valueOf(Arguments args) {
if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value;
else return Values.toNumber(args.env, args.self);
}
}

View File

@ -1,273 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Symbol;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Object")
public class ObjectLib {
@Expose(target = ExposeTarget.STATIC)
public static Object __assign(Arguments args) {
for (var obj : args.slice(1).args) {
for (var key : Values.getMembers(args.env, obj, true, true)) {
Values.setMember(args.env, args.get(0), key, Values.getMember(args.env, obj, key));
}
}
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __create(Arguments args) {
var obj = new ObjectValue();
Values.setPrototype(args.env, obj, args.get(0));
if (args.n() >= 1) {
var newArgs = new Object[args.n()];
System.arraycopy(args.args, 1, args, 1, args.n() - 1);
newArgs[0] = obj;
__defineProperties(new Arguments(args.env, null, newArgs));
}
return obj;
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __defineProperty(Arguments args) {
var obj = args.convert(0, ObjectValue.class);
var key = args.get(1);
var attrib = args.convert(2, ObjectValue.class);
var hasVal = Values.hasMember(args.env, attrib, "value", false);
var hasGet = Values.hasMember(args.env, attrib, "get", false);
var hasSet = Values.hasMember(args.env, attrib, "set", false);
if (hasVal) {
if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property.");
if (!obj.defineProperty(
args.env, key,
Values.getMember(args.env, attrib, "value"),
Values.toBoolean(Values.getMember(args.env, attrib, "writable")),
Values.toBoolean(Values.getMember(args.env, attrib, "configurable")),
Values.toBoolean(Values.getMember(args.env, attrib, "enumerable"))
)) throw EngineException.ofType("Can't define property '" + key + "'.");
}
else {
var get = Values.getMember(args.env, attrib, "get");
var set = Values.getMember(args.env, attrib, "set");
if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function.");
if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function.");
if (!obj.defineProperty(
args.env, key,
(FunctionValue)get, (FunctionValue)set,
Values.toBoolean(Values.getMember(args.env, attrib, "configurable")),
Values.toBoolean(Values.getMember(args.env, attrib, "enumerable"))
)) throw EngineException.ofType("Can't define property '" + key + "'.");
}
return obj;
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __defineProperties(Arguments args) {
var obj = args.convert(0, ObjectValue.class);
var attrib = args.get(1);
for (var key : Values.getMembers(null, attrib, false, false)) {
__defineProperty(new Arguments(args.env, null, obj, key, Values.getMember(args.env, attrib, key)));
}
return obj;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __keys(Arguments args) {
var obj = args.get(0);
var all = args.getBoolean(1);
var res = new ArrayValue();
for (var key : Values.getMembers(args.env, obj, true, false)) {
if (all || !(key instanceof Symbol)) res.set(args.env, res.size(), key);
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __entries(Arguments args) {
var res = new ArrayValue();
var obj = args.get(0);
var all = args.getBoolean(1);
for (var key : Values.getMembers(args.env, obj, true, false)) {
if (all || !(key instanceof Symbol)) res.set(args.env, res.size(), new ArrayValue(args.env, key, Values.getMember(args.env, obj, key)));
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __values(Arguments args) {
var res = new ArrayValue();
var obj = args.get(0);
var all = args.getBoolean(1);
for (var key : Values.getMembers(args.env, obj, true, false)) {
if (all || !(key instanceof Symbol)) res.set(args.env, res.size(), Values.getMember(args.env, obj, key));
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __getOwnPropertyDescriptor(Arguments args) {
return Values.getMemberDescriptor(args.env, args.get(0), args.get(1));
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __getOwnPropertyDescriptors(Arguments args) {
var res = new ObjectValue();
var obj = args.get(0);
for (var key : Values.getMembers(args.env, obj, true, true)) {
res.defineProperty(args.env, key, Values.getMemberDescriptor(args.env, obj, key));
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __getOwnPropertyNames(Arguments args) {
var res = new ArrayValue();
var obj = args.get(0);
var all = args.getBoolean(1);
for (var key : Values.getMembers(args.env, obj, true, true)) {
if (all || !(key instanceof Symbol)) res.set(args.env, res.size(), key);
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static ArrayValue __getOwnPropertySymbols(Arguments args) {
var obj = args.get(0);
var res = new ArrayValue();
for (var key : Values.getMembers(args.env, obj, true, true)) {
if (key instanceof Symbol) res.set(args.env, res.size(), key);
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __hasOwn(Arguments args) {
return Values.hasMember(args.env, args.get(0), args.get(1), true);
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __getPrototypeOf(Arguments args) {
return Values.getPrototype(args.env, args.get(0));
}
@Expose(target = ExposeTarget.STATIC)
public static Object __setPrototypeOf(Arguments args) {
Values.setPrototype(args.env, args.get(0), args.get(1));
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static ObjectValue __fromEntries(Arguments args) {
var res = new ObjectValue();
for (var el : Values.fromJSIterator(args.env, args.get(0))) {
if (el instanceof ArrayValue) {
res.defineProperty(args.env, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
}
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static Object __preventExtensions(Arguments args) {
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).preventExtensions();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static Object __seal(Arguments args) {
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).seal();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static Object __freeze(Arguments args) {
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).freeze();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isExtensible(Arguments args) {
var obj = args.get(0);
if (!(obj instanceof ObjectValue)) return false;
return ((ObjectValue)obj).extensible();
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isSealed(Arguments args) {
var obj = args.get(0);
if (!(obj instanceof ObjectValue)) return true;
var _obj = (ObjectValue)obj;
if (_obj.extensible()) return false;
for (var key : _obj.keys(true)) {
if (_obj.memberConfigurable(key)) return false;
}
return true;
}
@Expose(target = ExposeTarget.STATIC)
public static boolean __isFrozen(Arguments args) {
var obj = args.get(0);
if (!(obj instanceof ObjectValue)) return true;
var _obj = (ObjectValue)obj;
if (_obj.extensible()) return false;
for (var key : _obj.keys(true)) {
if (_obj.memberConfigurable(key)) return false;
if (_obj.memberWritable(key)) return false;
}
return true;
}
@Expose
public static Object __valueOf(Arguments args) {
return args.self;
}
@Expose
public static String __toString(Arguments args) {
var name = Values.getMember(args.env, args.self, Symbol.get("Symbol.typeName"));
if (name == null) name = "Unknown";
else name = Values.toString(args.env, name);
return "[object " + name + "]";
}
@Expose
public static boolean __hasOwnProperty(Arguments args) {
return Values.hasMember(args.env, args.self, args.get(0), true);
}
@ExposeConstructor
public static Object __constructor(Arguments args) {
var arg = args.get(0);
if (arg == null || arg == Values.NULL) return new ObjectValue();
else if (arg instanceof Boolean) return new BooleanLib((boolean)arg);
else if (arg instanceof Number) return new NumberLib(((Number)arg).doubleValue());
else if (arg instanceof String) return new StringLib((String)arg);
else if (arg instanceof Symbol) return new SymbolLib((Symbol)arg);
else return arg;
}
}

View File

@ -1,403 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Promise")
public class PromiseLib {
public static interface Handle {
void onFulfil(Object val);
void onReject(EngineException err);
default Handle defer(Environment loop) {
var self = this;
return new Handle() {
@Override public void onFulfil(Object val) {
if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
loop.get(EventLoop.KEY).pushMsg(() -> self.onFulfil(val), true);
}
@Override public void onReject(EngineException val) {
if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
loop.get(EventLoop.KEY).pushMsg(() -> self.onReject(val), true);
}
};
}
}
private static final int STATE_PENDING = 0;
private static final int STATE_FULFILLED = 1;
private static final int STATE_REJECTED = 2;
private List<Handle> handles = new ArrayList<>();
private int state = STATE_PENDING;
private boolean handled = false;
private Object val;
private void resolveSynchronized(Environment env, Object val, int newState) {
this.val = val;
this.state = newState;
for (var handle : handles) {
if (newState == STATE_FULFILLED) handle.onFulfil(val);
if (newState == STATE_REJECTED) {
handle.onReject((EngineException)val);
handled = true;
}
}
if (state == STATE_REJECTED && !handled) {
Values.printError(((EngineException)val).setEnvironment(env), "(in promise)");
}
handles = null;
// ctx.get(EventLoop.KEY).pushMsg(() -> {
// if (!ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop");
// handles = null;
// }, true);
}
private synchronized void resolve(Environment env, Object val, int newState) {
if (this.state != STATE_PENDING || newState == STATE_PENDING) return;
handle(env, val, new Handle() {
@Override public void onFulfil(Object val) {
resolveSynchronized(env, val, newState);
}
@Override public void onReject(EngineException err) {
resolveSynchronized(env, val, STATE_REJECTED);
}
});
}
public synchronized void fulfill(Environment env, Object val) {
resolve(env, val, STATE_FULFILLED);
}
public synchronized void reject(Environment env, EngineException val) {
resolve(env, val, STATE_REJECTED);
}
private void handle(Handle handle) {
if (state == STATE_FULFILLED) handle.onFulfil(val);
else if (state == STATE_REJECTED) {
handle.onReject((EngineException)val);
handled = true;
}
else handles.add(handle);
}
@Override public String toString() {
if (state == STATE_PENDING) return "Promise (pending)";
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
else return "Promise (rejected)";
}
public PromiseLib() {
this.state = STATE_PENDING;
this.val = null;
}
public static PromiseLib await(Environment env, ResultRunnable<Object> runner) {
var res = new PromiseLib();
new Thread(() -> {
try {
res.fulfill(env, runner.run());
}
catch (EngineException e) {
res.reject(env, e);
}
catch (Exception e) {
if (e instanceof InterruptException) throw e;
else {
res.reject(env, EngineException.ofError("Native code failed with " + e.getMessage()));
}
}
}, "Promisifier").start();
return res;
}
public static PromiseLib await(Environment env, Runnable runner) {
return await(env, () -> {
runner.run();
return null;
});
}
public static void handle(Environment env, Object obj, Handle handle) {
if (Values.isWrapper(obj, PromiseLib.class)) {
var promise = Values.wrapper(obj, PromiseLib.class);
handle(env, promise, handle);
return;
}
if (obj instanceof PromiseLib) {
((PromiseLib)obj).handle(handle);
return;
}
var rethrow = new boolean[1];
try {
var then = Values.getMember(env, obj, "then");
if (then instanceof FunctionValue) Values.call(env, then, obj,
new NativeFunction(args -> {
try { handle.onFulfil(args.get(0)); }
catch (Exception e) {
rethrow[0] = true;
throw e;
}
return null;
}),
new NativeFunction(args -> {
try { handle.onReject(new EngineException(args.get(0))); }
catch (Exception e) {
rethrow[0] = true;
throw e;
}
return null;
})
);
else handle.onFulfil(obj);
return;
}
catch (Exception e) {
if (rethrow[0]) throw e;
}
handle.onFulfil(obj);
}
public static PromiseLib ofResolved(Environment ctx, Object value) {
var res = new PromiseLib();
res.fulfill(ctx, value);
return res;
}
public static PromiseLib ofRejected(Environment ctx, EngineException value) {
var res = new PromiseLib();
res.reject(ctx, value);
return res;
}
@Expose(value = "resolve", target = ExposeTarget.STATIC)
public static PromiseLib __ofResolved(Arguments args) {
return ofResolved(args.env, args.get(0));
}
@Expose(value = "reject", target = ExposeTarget.STATIC)
public static PromiseLib __ofRejected(Arguments args) {
return ofRejected(args.env, new EngineException(args.get(0)).setEnvironment(args.env));
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __any(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
if (promises.size() == 0) return ofRejected(args.env, EngineException.ofError("No promises passed to 'Promise.any'.").setEnvironment(args.env));
var n = new int[] { promises.size() };
var res = new PromiseLib();
var errors = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
var index = i;
var val = promises.get(i);
if (res.state != STATE_PENDING) break;
handle(args.env, val, new Handle() {
public void onFulfil(Object val) { res.fulfill(args.env, val); }
public void onReject(EngineException err) {
errors.set(args.env, index, err.value);
n[0]--;
if (n[0] <= 0) res.reject(args.env, new EngineException(errors).setEnvironment(args.env));
}
});
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __race(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
var res = new PromiseLib();
for (var i = 0; i < promises.size(); i++) {
var val = promises.get(i);
if (res.state != STATE_PENDING) break;
handle(args.env, val, new Handle() {
@Override public void onFulfil(Object val) { res.fulfill(args.env, val); }
@Override public void onReject(EngineException err) { res.reject(args.env, err); }
});
}
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __all(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
var n = new int[] { promises.size() };
var res = new PromiseLib();
var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
if (res.state != STATE_PENDING) break;
var index = i;
var val = promises.get(i);
handle(args.env, val, new Handle() {
@Override public void onFulfil(Object val) {
result.set(args.env, index, val);
n[0]--;
if (n[0] <= 0) res.fulfill(args.env, result);
}
@Override public void onReject(EngineException err) {
res.reject(args.env, err);
}
});
}
if (n[0] <= 0) res.fulfill(args.env, result);
return res;
}
@Expose(target = ExposeTarget.STATIC)
public static PromiseLib __allSettled(Arguments args) {
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = args.convert(0, ArrayValue.class);
var n = new int[] { promises.size() };
var res = new PromiseLib();
var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
if (res.state != STATE_PENDING) break;
var index = i;
handle(args.env, promises.get(i), new Handle() {
@Override public void onFulfil(Object val) {
var desc = new ObjectValue();
desc.defineProperty(args.env, "status", "fulfilled");
desc.defineProperty(args.env, "value", val);
result.set(args.env, index, desc);
n[0]--;
if (n[0] <= 0) res.fulfill(args.env, res);
}
@Override public void onReject(EngineException err) {
var desc = new ObjectValue();
desc.defineProperty(args.env, "status", "reject");
desc.defineProperty(args.env, "value", err.value);
result.set(args.env, index, desc);
n[0]--;
if (n[0] <= 0) res.fulfill(args.env, res);
}
});
}
if (n[0] <= 0) res.fulfill(args.env, result);
return res;
}
@Expose
public static Object __then(Arguments args) {
var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null;
var res = new PromiseLib();
handle(args.env, args.self, new Handle() {
@Override public void onFulfil(Object val) {
try { res.fulfill(args.env, onFulfill.call(args.env, null, val)); }
catch (EngineException e) { res.reject(args.env, e); }
}
@Override public void onReject(EngineException err) {
try { res.fulfill(args.env, onReject.call(args.env, null, err.value)); }
catch (EngineException e) { res.reject(args.env, e); }
}
}.defer(args.env));
return res;
}
@Expose
public static Object __catch(Arguments args) {
return __then(new Arguments(args.env, args.self, null, args.get(0)));
}
@Expose
public static Object __finally(Arguments args) {
var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
var res = new PromiseLib();
handle(args.env, args.self, new Handle() {
@Override public void onFulfil(Object val) {
try {
func.call(args.env);
res.fulfill(args.env, val);
}
catch (EngineException e) { res.reject(args.env, e); }
}
@Override public void onReject(EngineException err) {
try {
func.call(args.env);
res.reject(args.env, err);
}
catch (EngineException e) { res.reject(args.env, e); }
}
}.defer(args.env));
return res;
}
@ExposeConstructor
public static PromiseLib __constructor(Arguments args) {
var func = args.convert(0, FunctionValue.class);
var res = new PromiseLib();
try {
func.call(
args.env, null,
new NativeFunction(null, _args -> {
res.fulfill(_args.env, _args.get(0));
return null;
}),
new NativeFunction(null, _args -> {
res.reject(_args.env, new EngineException(_args.get(0)).setEnvironment(_args.env));
return null;
})
);
}
catch (EngineException e) {
res.reject(args.env, e);
}
return res;
}
}

View File

@ -1,19 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("RangeError")
public class RangeErrorLib extends ErrorLib {
@ExposeField public static final String __name = "RangeError";
@ExposeConstructor public static ObjectValue constructor(Arguments args) {
var target = ErrorLib.__constructor(args);
target.setPrototype(PlaceholderProto.RANGE_ERROR);
return target;
}
}

View File

@ -1,354 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Pattern;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.NativeWrapper;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("RegExp")
public class RegExpLib {
// I used Regex to analyze Regex
private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL);
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]");
private Pattern pattern;
private String[] namedGroups;
private int flags;
public int lastI = 0;
public final String source;
public final boolean hasIndices;
public final boolean global;
public final boolean sticky;
@Expose(type = ExposeType.GETTER)
public int __lastIndex() { return lastI; }
@Expose(type = ExposeType.SETTER)
public void __setLastIndex(Arguments args) { lastI = args.getInt(0); }
@Expose(type = ExposeType.GETTER)
public String __source() { return source; }
@Expose(type = ExposeType.GETTER)
public boolean __ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; }
@Expose(type = ExposeType.GETTER)
public boolean __multiline() { return (flags & Pattern.MULTILINE) != 0; }
@Expose(type = ExposeType.GETTER)
public boolean __unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; }
@Expose(type = ExposeType.GETTER)
public boolean __dotAll() { return (flags & Pattern.DOTALL) != 0; }
@Expose(type = ExposeType.GETTER)
public boolean __global() { return global; }
@Expose(type = ExposeType.GETTER)
public boolean __sticky() { return sticky; }
@Expose(type = ExposeType.GETTER)
public final String __flags() {
String res = "";
if (hasIndices) res += 'd';
if (global) res += 'g';
if (__ignoreCase()) res += 'i';
if (__multiline()) res += 'm';
if (__dotAll()) res += 's';
if (__unicode()) res += 'u';
if (sticky) res += 'y';
return res;
}
@Expose public Object __exec(Arguments args) {
var str = args.getString(0);
var matcher = pattern.matcher(str);
if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) {
lastI = 0;
return Values.NULL;
}
if (sticky || global) {
lastI = matcher.end();
if (matcher.end() == matcher.start()) lastI++;
}
var obj = new ArrayValue();
ObjectValue groups = null;
for (var el : namedGroups) {
if (groups == null) groups = new ObjectValue();
try { groups.defineProperty(null, el, matcher.group(el)); }
catch (IllegalArgumentException e) { }
}
for (int i = 0; i < matcher.groupCount() + 1; i++) {
obj.set(null, i, matcher.group(i));
}
obj.defineProperty(null, "groups", groups);
obj.defineProperty(null, "index", matcher.start());
obj.defineProperty(null, "input", str);
if (hasIndices) {
var indices = new ArrayValue();
for (int i = 0; i < matcher.groupCount() + 1; i++) {
indices.set(null, i, new ArrayValue(null, matcher.start(i), matcher.end(i)));
}
var groupIndices = new ObjectValue();
for (var el : namedGroups) {
groupIndices.defineProperty(null, el, new ArrayValue(null, matcher.start(el), matcher.end(el)));
}
indices.defineProperty(null, "groups", groupIndices);
obj.defineProperty(null, "indices", indices);
}
return obj;
}
@Expose public boolean __test(Arguments args) {
return this.__exec(args) != Values.NULL;
}
@Expose("@@Symbol.match") public Object __match(Arguments args) {
if (this.global) {
var res = new ArrayValue();
Object val;
while ((val = this.__exec(args)) != Values.NULL) {
res.set(args.env, res.size(), Values.getMember(args.env, val, 0));
}
lastI = 0;
return res;
}
else {
var res = this.__exec(args);
if (!this.sticky) this.lastI = 0;
return res;
}
}
@Expose("@@Symbol.matchAll") public Object __matchAll(Arguments args) {
var pattern = this.toGlobal();
return Values.toJSIterator(args.env, new Iterator<Object>() {
private Object val = null;
private boolean updated = false;
private void update() {
if (!updated) val = pattern.__exec(args);
}
@Override public boolean hasNext() {
update();
return val != Values.NULL;
}
@Override public Object next() {
update();
updated = false;
return val;
}
});
}
@Expose("@@Symbol.split") public ArrayValue __split(Arguments args) {
var pattern = this.toGlobal();
var target = args.getString(0);
var hasLimit = args.get(1) != null;
var lim = args.getInt(1);
var sensible = args.getBoolean(2);
Object match;
int lastEnd = 0;
var res = new ArrayValue();
while ((match = pattern.__exec(args)) != Values.NULL) {
var added = new ArrayList<String>();
var arrMatch = (ArrayValue)match;
int index = (int)Values.toNumber(args.env, Values.getMember(args.env, match, "index"));
var matchVal = (String)arrMatch.get(0);
if (index >= target.length()) break;
if (matchVal.length() == 0 || index - lastEnd > 0) {
added.add(target.substring(lastEnd, pattern.lastI));
if (pattern.lastI < target.length()) {
for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i));
}
}
else {
for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i));
}
if (sensible) {
if (hasLimit && res.size() + added.size() >= lim) break;
else for (var i = 0; i < added.size(); i++) res.set(args.env, res.size(), added.get(i));
}
else {
for (var i = 0; i < added.size(); i++) {
if (hasLimit && res.size() >= lim) return res;
else res.set(args.env, res.size(), added.get(i));
}
}
lastEnd = pattern.lastI;
}
if (lastEnd < target.length()) {
res.set(args.env, res.size(), target.substring(lastEnd));
}
return res;
}
@Expose("@@Symbol.replace") public String __replace(Arguments args) {
var pattern = this.toIndexed();
var target = args.getString(0);
var replacement = args.get(1);
Object match;
var lastEnd = 0;
var res = new StringBuilder();
while ((match = pattern.__exec(args)) != Values.NULL) {
var indices = (ArrayValue)((ArrayValue)Values.getMember(args.env, match, "indices")).get(0);
var arrMatch = (ArrayValue)match;
var start = ((Number)indices.get(0)).intValue();
var end = ((Number)indices.get(1)).intValue();
res.append(target.substring(lastEnd, start));
if (replacement instanceof FunctionValue) {
var callArgs = new Object[arrMatch.size() + 2];
callArgs[0] = target.substring(start, end);
arrMatch.copyTo(callArgs, 1, 1, arrMatch.size() - 1);
callArgs[callArgs.length - 2] = start;
callArgs[callArgs.length - 1] = target;
res.append(Values.toString(args.env, ((FunctionValue)replacement).call(args.env, null, callArgs)));
}
else {
res.append(Values.toString(args.env, replacement));
}
lastEnd = end;
if (!pattern.global) break;
}
if (lastEnd < target.length()) {
res.append(target.substring(lastEnd));
}
return res.toString();
}
// [Symbol.search](target, reverse, start) {
// const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
// if (!reverse) {
// pattern.lastIndex = (start as any) | 0;
// const res = pattern.exec(target);
// if (res) return res.index;
// else return -1;
// }
// else {
// start ??= target.length;
// start |= 0;
// let res: RegExpResult | null = null;
// while (true) {
// const tmp = pattern.exec(target);
// if (tmp === null || tmp.index > start) break;
// res = tmp;
// }
// if (res && res.index <= start) return res.index;
// else return -1;
// }
// },
public RegExpLib toGlobal() {
return new RegExpLib(pattern, namedGroups, flags, source, hasIndices, true, sticky);
}
public RegExpLib toIndexed() {
return new RegExpLib(pattern, namedGroups, flags, source, true, global, sticky);
}
public String toString() {
return "/" + source + "/" + __flags();
}
public RegExpLib(String pattern, String flags) {
if (pattern == null || pattern.equals("")) pattern = "(?:)";
if (flags == null || flags.equals("")) flags = "";
this.flags = 0;
this.hasIndices = flags.contains("d");
this.global = flags.contains("g");
this.sticky = flags.contains("y");
this.source = pattern;
if (flags.contains("i")) this.flags |= Pattern.CASE_INSENSITIVE;
if (flags.contains("m")) this.flags |= Pattern.MULTILINE;
if (flags.contains("s")) this.flags |= Pattern.DOTALL;
if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS;
if (pattern.equals("{(\\d+)}")) pattern = "\\{([0-9]+)\\}";
this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags);
var matcher = NAMED_PATTERN.matcher(source);
var groups = new ArrayList<String>();
while (matcher.find()) {
if (!checkEscaped(source, matcher.start() - 1)) {
groups.add(matcher.group(1));
}
}
namedGroups = groups.toArray(String[]::new);
}
private RegExpLib(Pattern pattern, String[] namedGroups, int flags, String source, boolean hasIndices, boolean global, boolean sticky) {
this.pattern = pattern;
this.namedGroups = namedGroups;
this.flags = flags;
this.source = source;
this.hasIndices = hasIndices;
this.global = global;
this.sticky = sticky;
}
public RegExpLib(String pattern) { this(pattern, null); }
public RegExpLib() { this(null, null); }
@ExposeConstructor
public static RegExpLib __constructor(Arguments args) {
return new RegExpLib(cleanupPattern(args.env, args.get(0)), cleanupFlags(args.env, args.get(1)));
}
@Expose(target = ExposeTarget.STATIC)
public static RegExpLib __escape(Arguments args) {
return escape(Values.toString(args.env, args.get(0)), cleanupFlags(args.env, args.get(1)));
}
private static String cleanupPattern(Environment env, Object val) {
if (val == null) return "(?:)";
if (val instanceof RegExpLib) return ((RegExpLib)val).source;
if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) {
return ((RegExpLib)((NativeWrapper)val).wrapped).source;
}
var res = Values.toString(env, val);
if (res.equals("")) return "(?:)";
return res;
}
private static String cleanupFlags(Environment env, Object val) {
if (val == null) return "";
return Values.toString(env, val);
}
private static boolean checkEscaped(String s, int pos) {
int n = 0;
while (true) {
if (pos <= 0) break;
if (s.charAt(pos) != '\\') break;
n++;
pos--;
}
return (n % 2) != 0;
}
public static RegExpLib escape(String raw, String flags) {
return new RegExpLib(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags);
}
}

View File

@ -1,69 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Set")
public class SetLib {
private LinkedHashSet<Object> set = new LinkedHashSet<>();
@Expose("@@Symbol.iterator")
public ObjectValue __iterator(Arguments args) {
return this.__values(args);
}
@Expose public ObjectValue __entries(Arguments args) {
return Values.toJSIterator(args.env, set.stream().map(v -> new ArrayValue(args.env, v, v)).collect(Collectors.toList()));
}
@Expose public ObjectValue __keys(Arguments args) {
return Values.toJSIterator(args.env, set);
}
@Expose public ObjectValue __values(Arguments args) {
return Values.toJSIterator(args.env, set);
}
@Expose public Object __add(Arguments args) {
return set.add(args.get(0));
}
@Expose public boolean __delete(Arguments args) {
return set.remove(args.get(0));
}
@Expose public boolean __has(Arguments args) {
return set.contains(args.get(0));
}
@Expose public void __clear() {
set.clear();
}
@Expose(type = ExposeType.GETTER)
public int __size() {
return set.size();
}
@Expose public void __forEach(Arguments args) {
var keys = new ArrayList<>(set);
for (var el : keys) Values.call(args.env, args.get(0), args.get(1), el, el, args.self);
}
public SetLib(Environment env, Object iterable) {
for (var el : Values.fromJSIterator(env, iterable)) set.add(el);
}
@ExposeConstructor
public static SetLib __constructor(Arguments args) {
return new SetLib(args.env, args.get(0));
}
}

View File

@ -1,291 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.regex.Pattern;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Symbol;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.ExposeType;
import me.topchetoeu.jscript.utils.interop.WrapperName;
// TODO: implement index wrapping properly
@WrapperName("String")
public class StringLib {
public final String value;
@Override public String toString() { return value; }
public StringLib(String val) {
this.value = val;
}
private static String passThis(Arguments args, String funcName) {
var val = args.self;
if (Values.isWrapper(val, StringLib.class)) return Values.wrapper(val, StringLib.class).value;
else if (val instanceof String) return (String)val;
else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName));
}
private static int normalizeI(int i, int len, boolean clamp) {
if (i < 0) i += len;
if (clamp) {
if (i < 0) i = 0;
if (i > len) i = len;
}
return i;
}
@Expose(type = ExposeType.GETTER)
public static int __length(Arguments args) {
return passThis(args, "length").length();
}
@Expose public static String __substring(Arguments args) {
var val = passThis(args, "substring");
var start = Math.max(0, Math.min(val.length(), args.getInt(0)));
var end = Math.max(0, Math.min(val.length(), args.getInt(1, val.length())));
if (end < start) {
var tmp = end;
end = start;
start = tmp;
}
return val.substring(start, end);
}
@Expose public static String __substr(Arguments args) {
var val = passThis(args, "substr");
var start = normalizeI(args.getInt(0), val.length(), true);
int end = normalizeI(args.getInt(1, val.length() - start) + start, val.length(), true);
return val.substring(start, end);
}
@Expose public static String __toLowerCase(Arguments args) {
return passThis(args, "toLowerCase").toLowerCase();
}
@Expose public static String __toUpperCase(Arguments args) {
return passThis(args, "toUpperCase").toUpperCase();
}
@Expose public static String __charAt(Arguments args) {
return passThis(args, "charAt").charAt(args.getInt(0)) + "";
}
@Expose public static double __charCodeAt(Arguments args) {
var str = passThis(args, "charCodeAt");
var i = args.getInt(0);
if (i < 0 || i >= str.length()) return Double.NaN;
else return str.charAt(i);
}
@Expose public static double __codePointAt(Arguments args) {
var str = passThis(args, "codePointAt");
var i = args.getInt(0);
if (i < 0 || i >= str.length()) return Double.NaN;
else return str.codePointAt(i);
}
@Expose public static boolean __startsWith(Arguments args) {
return passThis(args, "startsWith").startsWith(args.getString(0), args.getInt(1));
}
@Expose public static boolean __endsWith(Arguments args) {
return passThis(args, "endsWith").lastIndexOf(args.getString(0), args.getInt(1)) >= 0;
}
@Expose public static int __indexOf(Arguments args) {
var val = passThis(args, "indexOf");
var term = args.get(0);
var start = args.getInt(1);
var search = Values.getMember(args.env, term, Symbol.get("Symbol.search"));
if (search instanceof FunctionValue) {
return (int)Values.toNumber(args.env, Values.call(args.env, search, term, val, false, start));
}
else return val.indexOf(Values.toString(args.env, term), start);
}
@Expose public static int __lastIndexOf(Arguments args) {
var val = passThis(args, "lastIndexOf");
var term = args.get(0);
var start = args.getInt(1);
var search = Values.getMember(args.env, term, Symbol.get("Symbol.search"));
if (search instanceof FunctionValue) {
return (int)Values.toNumber(args.env, Values.call(args.env, search, term, val, true, start));
}
else return val.lastIndexOf(Values.toString(args.env, term), start);
}
@Expose public static boolean __includes(Arguments args) {
return __indexOf(args) >= 0;
}
@Expose public static String __replace(Arguments args) {
var val = passThis(args, "replace");
var term = args.get(0);
var replacement = args.get(1);
var replace = Values.getMember(args.env, term, Symbol.get("Symbol.replace"));
if (replace instanceof FunctionValue) {
return Values.toString(args.env, Values.call(args.env, replace, term, val, replacement));
}
else return val.replaceFirst(Pattern.quote(Values.toString(args.env, term)), Values.toString(args.env, replacement));
}
@Expose public static String __replaceAll(Arguments args) {
var val = passThis(args, "replaceAll");
var term = args.get(0);
var replacement = args.get(1);
var replace = Values.getMember(args.env, term, Symbol.get("Symbol.replace"));
if (replace instanceof FunctionValue) {
return Values.toString(args.env, Values.call(args.env, replace, term, val, replacement));
}
else return val.replace(Values.toString(args.env, term), Values.toString(args.env, replacement));
}
@Expose public static ArrayValue __match(Arguments args) {
var val = passThis(args, "match");
var term = args.get(0);
FunctionValue match;
try {
var _match = Values.getMember(args.env, term, Symbol.get("Symbol.match"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else if (args.env.hasNotNull(Environment.REGEX_CONSTR)) {
var regex = Values.callNew(args.env, args.env.get(Environment.REGEX_CONSTR), Values.toString(args.env, term), "");
_match = Values.getMember(args.env, regex, Symbol.get("Symbol.match"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else throw EngineException.ofError("Regular expressions don't support matching.");
}
else throw EngineException.ofError("Regular expressions not supported.");
}
catch (IllegalArgumentException e) { return new ArrayValue(args.env, ""); }
var res = match.call(args.env, term, val);
if (res instanceof ArrayValue) return (ArrayValue)res;
else return new ArrayValue(args.env, "");
}
@Expose public static Object __matchAll(Arguments args) {
var val = passThis(args, "matchAll");
var term = args.get(0);
FunctionValue match = null;
try {
var _match = Values.getMember(args.env, term, Symbol.get("Symbol.matchAll"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
}
catch (IllegalArgumentException e) { }
if (match == null && args.env.hasNotNull(Environment.REGEX_CONSTR)) {
var regex = Values.callNew(args.env, args.env.get(Environment.REGEX_CONSTR), Values.toString(args.env, term), "g");
var _match = Values.getMember(args.env, regex, Symbol.get("Symbol.matchAll"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else throw EngineException.ofError("Regular expressions don't support matching.");
}
else throw EngineException.ofError("Regular expressions not supported.");
return match.call(args.env, term, val);
}
@Expose public static ArrayValue __split(Arguments args) {
var val = passThis(args, "split");
var term = args.get(0);
var lim = args.get(1);
var sensible = args.getBoolean(2);
if (lim != null) lim = Values.toNumber(args.env, lim);
if (term != null && term != Values.NULL && !(term instanceof String)) {
var replace = Values.getMember(args.env, term, Symbol.get("Symbol.replace"));
if (replace instanceof FunctionValue) {
var tmp = ((FunctionValue)replace).call(args.env, term, val, lim, sensible);
if (tmp instanceof ArrayValue) {
var parts = new ArrayValue(((ArrayValue)tmp).size());
for (int i = 0; i < parts.size(); i++) parts.set(args.env, i, Values.toString(args.env, ((ArrayValue)tmp).get(i)));
return parts;
}
}
}
String[] parts;
var pattern = Pattern.quote(Values.toString(args.env, term));
if (lim == null) parts = val.split(pattern);
else if ((double)lim < 1) return new ArrayValue();
else if (sensible) parts = val.split(pattern, (int)(double)lim);
else {
var limit = (int)(double)lim;
parts = val.split(pattern, limit + 1);
ArrayValue res;
if (parts.length > limit) res = new ArrayValue(limit);
else res = new ArrayValue(parts.length);
for (var i = 0; i < parts.length && i < limit; i++) res.set(args.env, i, parts[i]);
return res;
}
var res = new ArrayValue(parts.length);
var i = 0;
for (; i < parts.length; i++) {
if (lim != null && (double)lim <= i) break;
res.set(args.env, i, parts[i]);
}
return res;
}
@Expose public static String __slice(Arguments args) {
var self = passThis(args, "slice");
var start = normalizeI(args.getInt(0), self.length(), false);
var end = normalizeI(args.getInt(1, self.length()), self.length(), false);
return __substring(new Arguments(args.env, self, start, end));
}
@Expose public static String __concat(Arguments args) {
var res = new StringBuilder(passThis(args, "concat"));
for (var el : args.convert(String.class)) res.append(el);
return res.toString();
}
@Expose public static String __trim(Arguments args) {
return passThis(args, "trim").trim();
}
@Expose public static String __trimStart(Arguments args) {
return passThis(args, "trimStart").replaceAll("^\\s+", "");
}
@Expose public static String __trimEnd(Arguments args) {
return passThis(args, "trimEnd").replaceAll("\\s+$", "");
}
@ExposeConstructor public static Object __constructor(Arguments args) {
var val = args.getString(0, "");
if (args.self instanceof ObjectValue) return new StringLib(val);
else return val;
}
@Expose public static String __toString(Arguments args) {
return passThis(args, "toString");
}
@Expose public static String __valueOf(Arguments args) {
return passThis(args, "valueOf");
}
@Expose(target = ExposeTarget.STATIC)
public static String __fromCharCode(Arguments args) {
var val = args.convertInt();
char[] arr = new char[val.length];
for (var i = 0; i < val.length; i++) arr[i] = (char)val[i];
return new String(arr);
}
}

View File

@ -1,81 +0,0 @@
package me.topchetoeu.jscript.lib;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Symbol;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.ExposeTarget;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("Symbol")
public class SymbolLib {
private static final Map<String, Symbol> symbols = new HashMap<>();
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __typeName = Symbol.get("Symbol.typeName");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __replace = Symbol.get("Symbol.replace");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __match = Symbol.get("Symbol.match");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __matchAll = Symbol.get("Symbol.matchAll");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __split = Symbol.get("Symbol.split");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __search = Symbol.get("Symbol.search");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __iterator = Symbol.get("Symbol.iterator");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __asyncIterator = Symbol.get("Symbol.asyncIterator");
@ExposeField(target = ExposeTarget.STATIC)
public static final Symbol __cause = Symbol.get("Symbol.cause");
public final Symbol value;
private static Symbol passThis(Arguments args, String funcName) {
var val = args.self;
if (Values.isWrapper(val, SymbolLib.class)) return Values.wrapper(val, SymbolLib.class).value;
else if (val instanceof Symbol) return (Symbol)val;
else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName));
}
public SymbolLib(Symbol val) {
this.value = val;
}
@Expose public static String __toString(Arguments args) {
return passThis(args, "toString").value;
}
@Expose public static Symbol __valueOf(Arguments args) {
return passThis(args, "valueOf");
}
@ExposeConstructor
public static Object __constructor(Arguments args) {
if (args.self instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new.");
if (args.get(0) == null) return new Symbol("");
else return new Symbol(args.getString(0));
}
@Expose(target = ExposeTarget.STATIC)
public static Symbol __for(Arguments args) {
var key = args.getString(0);
if (symbols.containsKey(key)) return symbols.get(key);
else {
var sym = new Symbol(key);
symbols.put(key, sym);
return sym;
}
}
@Expose(target = ExposeTarget.STATIC)
public static String __keyFor(Arguments args) {
return passThis(new Arguments(args.env, args.get(0)), "keyFor").value;
}
}

View File

@ -1,19 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("SyntaxError")
public class SyntaxErrorLib extends ErrorLib {
@ExposeField public static final String __name = "SyntaxError";
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
var target = ErrorLib.__constructor(args);
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
return target;
}
}

View File

@ -1,18 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
public class ThrowableLib {
@Expose public static String __message(Arguments args) {
if (args.self instanceof Throwable) return ((Throwable)args.self).getMessage();
else return null;
}
@Expose public static String __name(Arguments args) {
return args.self.getClass().getSimpleName();
}
@Expose public static String __toString(Arguments args) {
return __name(args) + ": " + __message(args);
}
}

View File

@ -1,19 +0,0 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.ExposeConstructor;
import me.topchetoeu.jscript.utils.interop.ExposeField;
import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("TypeError")
public class TypeErrorLib extends ErrorLib {
@ExposeField public static final String __name = "TypeError";
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
var target = ErrorLib.__constructor(args);
target.setPrototype(PlaceholderProto.TYPE_ERROR);
return target;
}
}

View File

@ -1,12 +1,14 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.runtime;
import me.topchetoeu.jscript.common.Compiler;
import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.ResultRunnable; import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.common.events.DataNotifier; import me.topchetoeu.jscript.common.events.DataNotifier;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key; import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.FunctionValue; import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
public interface EventLoop { public interface EventLoop {
public static final Key<EventLoop> KEY = new Key<>(); public static final Key<EventLoop> KEY = new Key<>();
@ -25,10 +27,10 @@ public interface EventLoop {
return pushMsg(() -> { runnable.run(); return null; }, micro); return pushMsg(() -> { runnable.run(); return null; }, micro);
} }
public default DataNotifier<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) { public default DataNotifier<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
return pushMsg(() -> func.call(env, thisArg, args), micro); return pushMsg(() -> func.call(env, thisArg, args), micro);
} }
public default DataNotifier<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) { public default DataNotifier<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
return pushMsg(() -> Compiler.compile(env, filename, raw).call(env, thisArg, args), micro); return pushMsg(() -> Compiler.compile(env, filename, raw).call(env, thisArg, args), micro);
} }
} }

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.runtime;
import java.util.List; import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
@ -11,11 +12,13 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException; import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
import me.topchetoeu.jscript.runtime.scope.LocalScope; import me.topchetoeu.jscript.runtime.scope.LocalScope;
import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.ArrayValue; import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.CodeFunction; import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.ObjectValue; import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.ScopeValue; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.Values; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.objects.ScopeValue;
public class Frame { public class Frame {
public static final Key<Frame> KEY = new Key<>(); public static final Key<Frame> KEY = new Key<>();
@ -64,12 +67,12 @@ public class Frame {
private static class PendingResult { private static class PendingResult {
public final boolean isReturn, isJump, isThrow; public final boolean isReturn, isJump, isThrow;
public final Object value; public final Value value;
public final EngineException error; public final EngineException error;
public final int ptr; public final int ptr;
public final Instruction instruction; public final Instruction instruction;
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) { private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Value value, EngineException error, int ptr) {
this.instruction = instr; this.instruction = instr;
this.isReturn = isReturn; this.isReturn = isReturn;
this.isJump = isJump; this.isJump = isJump;
@ -82,7 +85,7 @@ public class Frame {
public static PendingResult ofNone() { public static PendingResult ofNone() {
return new PendingResult(null, false, false, false, null, null, 0); return new PendingResult(null, false, false, false, null, null, 0);
} }
public static PendingResult ofReturn(Object value, Instruction instr) { public static PendingResult ofReturn(Value value, Instruction instr) {
return new PendingResult(instr, true, false, false, value, null, 0); return new PendingResult(instr, true, false, false, value, null, 0);
} }
public static PendingResult ofThrow(EngineException error, Instruction instr) { public static PendingResult ofThrow(EngineException error, Instruction instr) {
@ -100,7 +103,7 @@ public class Frame {
public final CodeFunction function; public final CodeFunction function;
public final Environment env; public final Environment env;
public Object[] stack = new Object[32]; public Value[] stack = new Value[32];
public int stackPtr = 0; public int stackPtr = 0;
public int codePtr = 0; public int codePtr = 0;
public boolean jumpFlag = false; public boolean jumpFlag = false;
@ -113,52 +116,54 @@ public class Frame {
tryStack.add(res); tryStack.add(res);
} }
public Object peek() { public Value peek() {
return peek(0); return peek(0);
} }
public Object peek(int offset) { public Value peek(int offset) {
if (stackPtr <= offset) return null; if (stackPtr <= offset) return null;
else return stack[stackPtr - 1 - offset]; else return stack[stackPtr - 1 - offset];
} }
public Object pop() { public Value pop() {
if (stackPtr == 0) return null; if (stackPtr == 0) return null;
return stack[--stackPtr]; return stack[--stackPtr];
} }
public Object[] take(int n) { public Value[] take(int n) {
int srcI = stackPtr - n; int srcI = stackPtr - n;
if (srcI < 0) srcI = 0; if (srcI < 0) srcI = 0;
int dstI = n + srcI - stackPtr; int dstI = n + srcI - stackPtr;
int copyN = stackPtr - srcI; int copyN = stackPtr - srcI;
Object[] res = new Object[n]; Value[] res = new Value[n];
System.arraycopy(stack, srcI, res, dstI, copyN); System.arraycopy(stack, srcI, res, dstI, copyN);
stackPtr -= copyN; stackPtr -= copyN;
return res; return res;
} }
public void push(Object val) { public void push(Value val) {
if (stack.length <= stackPtr) { if (stack.length <= stackPtr) {
var newStack = new Object[stack.length * 2]; var newStack = new Value[stack.length * 2];
System.arraycopy(stack, 0, newStack, 0, stack.length); System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack; stack = newStack;
} }
stack[stackPtr++] = Values.normalize(env, val);
stack[stackPtr++] = val;
} }
private Object next(Object value, Object returnValue, EngineException error) { // for the love of christ don't touch this
if (value != Values.NO_RETURN) push(value); private Value next(Value value, Value returnValue, EngineException error) {
if (value != null) push(value);
Instruction instr = null; Instruction instr = null;
if (codePtr >= 0 && codePtr < function.body.instructions.length) instr = function.body.instructions[codePtr]; if (codePtr >= 0 && codePtr < function.body.instructions.length) instr = function.body.instructions[codePtr];
if (returnValue == Values.NO_RETURN && error == null) { if (returnValue == null && error == null) {
try { try {
if (Thread.interrupted()) throw new InterruptException(); if (Thread.interrupted()) throw new InterruptException();
if (instr == null) returnValue = null; if (instr == null) returnValue = null;
else { else {
DebugContext.get(env).onInstruction(env, this, instr, Values.NO_RETURN, null, false); DebugContext.get(env).onInstruction(env, this, instr, null, null, false);
try { try {
this.jumpFlag = this.popTryFlag = false; this.jumpFlag = this.popTryFlag = false;
@ -181,7 +186,7 @@ public class Frame {
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error); if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr)); else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
} }
else if (returnValue != Values.NO_RETURN) { else if (returnValue != null) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr)); if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
} }
else if (jumpFlag && !tryCtx.inBounds(codePtr)) { else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
@ -209,7 +214,7 @@ public class Frame {
} }
error = null; error = null;
returnValue = Values.NO_RETURN; returnValue = null;
break; break;
} }
else { else {
@ -227,7 +232,7 @@ public class Frame {
tryStack.pop(); tryStack.pop();
codePtr = tryCtx.end; codePtr = tryCtx.end;
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction; if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
if (!jumpFlag && returnValue == Values.NO_RETURN && error == null) { if (!jumpFlag && returnValue == null && error == null) {
if (tryCtx.result.isJump) { if (tryCtx.result.isJump) {
codePtr = tryCtx.result.ptr; codePtr = tryCtx.result.ptr;
jumpFlag = true; jumpFlag = true;
@ -255,19 +260,19 @@ public class Frame {
DebugContext.get(env).onInstruction(env, this, instr, null, error, caught); DebugContext.get(env).onInstruction(env, this, instr, null, error, caught);
throw error; throw error;
} }
if (returnValue != Values.NO_RETURN) { if (returnValue != null) {
DebugContext.get(env).onInstruction(env, this, instr, returnValue, null, false); DebugContext.get(env).onInstruction(env, this, instr, returnValue, null, false);
return returnValue; return returnValue;
} }
return Values.NO_RETURN; return null;
} }
/** /**
* Executes the next instruction in the frame * Executes the next instruction in the frame
*/ */
public Object next() { public Value next() {
return next(Values.NO_RETURN, Values.NO_RETURN, null); return next(null, null, null);
} }
/** /**
* Induces a value on the stack (as if it were returned by the last function call) * Induces a value on the stack (as if it were returned by the last function call)
@ -275,8 +280,8 @@ public class Frame {
* *
* @param value The value to induce * @param value The value to induce
*/ */
public Object next(Object value) { public Value next(Value value) {
return next(value, Values.NO_RETURN, null); return next(value, null, null);
} }
/** /**
* Induces a thrown error and executes the next instruction. * Induces a thrown error and executes the next instruction.
@ -286,8 +291,8 @@ public class Frame {
* *
* @param error The error to induce * @param error The error to induce
*/ */
public Object induceError(EngineException error) { public Value induceError(EngineException error) {
return next(Values.NO_RETURN, Values.NO_RETURN, error); return next(null, null, error);
} }
/** /**
* Induces a return, as if there was a return statement before * Induces a return, as if there was a return statement before
@ -298,8 +303,8 @@ public class Frame {
* *
* @param value The retunr value to induce * @param value The retunr value to induce
*/ */
public Object induceReturn(Object value) { public Value induceReturn(Value value) {
return next(Values.NO_RETURN, value, null); return next(null, value, null);
} }
public void onPush() { public void onPush() {
@ -348,35 +353,47 @@ public class Frame {
*/ */
public ObjectValue getValStackScope() { public ObjectValue getValStackScope() {
return new ObjectValue() { return new ObjectValue() {
@Override @Override public Member getOwnMember(Environment env, Value key) {
protected Object getField(Environment ext, Object key) { var res = super.getOwnMember(env, key);
var i = (int)Values.toNumber(ext, key); if (res != null) return res;
var f = key.toNumber(env).value;
var i = (int)f;
if (i < 0 || i >= stackPtr) return null; if (i < 0 || i >= stackPtr) return null;
else return stack[i]; else return new FieldMember(false, true, true) {
@Override public Value get(Environment env, Value self) { return stack[i]; }
@Override public boolean set(Environment env, Value val, Value self) {
stack[i] = val;
return true;
}
};
} }
@Override @Override public Map<String, Member> getOwnMembers(Environment env) {
protected boolean hasField(Environment ext, Object key) { var res = new LinkedHashMap<String, Member>();
return true;
} for (var i = 0; i < stackPtr; i++) {
@Override var _i = i;
public List<Object> keys(boolean includeNonEnumerable) { res.put(i + "", new FieldMember(false, true, true) {
var res = super.keys(includeNonEnumerable); @Override public Value get(Environment env, Value self) { return stack[_i]; }
for (var i = 0; i < stackPtr; i++) res.add(i); @Override public boolean set(Environment env, Value val, Value self) {
stack[_i] = val;
return true;
}
});
}
return res; return res;
} }
}; };
} }
public Frame(Environment env, Object thisArg, Object[] args, CodeFunction func) { public Frame(Environment env, Value thisArg, Value[] args, CodeFunction func) {
this.env = env; this.env = env;
this.args = args.clone(); this.args = args.clone();
this.scope = new LocalScope(func.body.localsN, func.captures); this.scope = new LocalScope(func.body.localsN, func.captures);
this.scope.get(0).set(null, thisArg); this.scope.get(0).set(thisArg);
var argsObj = new ArrayValue(); this.scope.get(1).value = new ArrayValue(args);
for (var i = 0; i < args.length; i++) {
argsObj.set(env, i, args[i]);
}
this.scope.get(1).value = argsObj;
this.thisArg = thisArg; this.thisArg = thisArg;
this.function = func; this.function = func;

View File

@ -1,5 +1,6 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.runtime;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
@ -8,85 +9,96 @@ import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope; import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.ArrayValue; import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.CodeFunction; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
import me.topchetoeu.jscript.runtime.values.FunctionValue; import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.ObjectValue; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.Symbol; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.Values; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public class InstructionRunner { public class InstructionRunner {
private static Object execReturn(Environment ext, Instruction instr, Frame frame) { private static Value execReturn(Environment env, Instruction instr, Frame frame) {
return frame.pop(); return frame.pop();
} }
private static Object execThrow(Environment ext, Instruction instr, Frame frame) { private static Value execThrow(Environment env, Instruction instr, Frame frame) {
throw new EngineException(frame.pop()); throw new EngineException(frame.pop());
} }
private static Object execThrowSyntax(Environment ext, Instruction instr, Frame frame) { private static Value execThrowSyntax(Environment env, Instruction instr, Frame frame) {
throw EngineException.ofSyntax((String)instr.get(0)); throw EngineException.ofSyntax((String)instr.get(0));
} }
private static Object execCall(Environment ext, Instruction instr, Frame frame) { private static Value execCall(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var func = frame.pop(); var func = frame.pop();
var thisArg = frame.pop(); var thisArg = frame.pop();
frame.push(Values.call(ext, func, thisArg, callArgs)); frame.push(func.call(env, thisArg, callArgs));
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execCallNew(Environment ext, Instruction instr, Frame frame) { private static Value execCallNew(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop(); var funcObj = frame.pop();
frame.push(Values.callNew(ext, funcObj, callArgs)); frame.push(funcObj.callNew(env, callArgs));
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execMakeVar(Environment ext, Instruction instr, Frame frame) { private static Value execMakeVar(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0); var name = (String)instr.get(0);
GlobalScope.get(ext).define(ext, name); GlobalScope.get(env).define(env, false, name);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execDefProp(Environment ext, Instruction instr, Frame frame) { private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
var setter = frame.pop(); var setterVal = frame.pop();
var getter = frame.pop(); var getterVal = frame.pop();
var name = frame.pop(); var name = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined."); FunctionValue getter, setter;
if (setter != null && !(setter instanceof FunctionValue)) throw EngineException.ofType("Setter must be a function or undefined.");
if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object."); if (getterVal == VoidValue.UNDEFINED) getter = null;
((ObjectValue)obj).defineProperty(ext, name, (FunctionValue)getter, (FunctionValue)setter, false, false); else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal;
else throw EngineException.ofType("Getter must be a function or undefined.");
if (setterVal == VoidValue.UNDEFINED) setter = null;
else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal;
else throw EngineException.ofType("Setter must be a function or undefined.");
obj.defineOwnMember(env, name, new PropertyMember(getter, setter, true, true));
frame.push(obj); frame.push(obj);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execKeys(Environment ext, Instruction instr, Frame frame) { private static Value execKeys(Environment env, Instruction instr, Frame frame) {
var val = frame.pop(); var val = frame.pop();
var members = Values.getMembers(ext, val, false, false); var members = new ArrayList<>(val.getMembers(env, false, true).keySet());
Collections.reverse(members); Collections.reverse(members);
frame.push(null); frame.push(null);
for (var el : members) { for (var el : members) {
if (el instanceof Symbol) continue;
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.defineProperty(ext, "value", el); obj.defineOwnMember(env, new StringValue("value"), FieldMember.of(new StringValue(el)));
frame.push(obj); frame.push(obj);
} }
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execTryStart(Environment ext, Instruction instr, Frame frame) { private static Value execTryStart(Environment env, Instruction instr, Frame frame) {
int start = frame.codePtr + 1; int start = frame.codePtr + 1;
int catchStart = (int)instr.get(0); int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1); int finallyStart = (int)instr.get(1);
@ -95,14 +107,14 @@ public class InstructionRunner {
int end = (int)instr.get(2) + start; int end = (int)instr.get(2) + start;
frame.addTry(start, end, catchStart, finallyStart); frame.addTry(start, end, catchStart, finallyStart);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execTryEnd(Environment ext, Instruction instr, Frame frame) { private static Value execTryEnd(Environment env, Instruction instr, Frame frame) {
frame.popTryFlag = true; frame.popTryFlag = true;
return Values.NO_RETURN; return null;
} }
private static Object execDup(Environment ext, Instruction instr, Frame frame) { private static Value execDup(Environment env, Instruction instr, Frame frame) {
int count = instr.get(0); int count = instr.get(0);
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
@ -110,45 +122,48 @@ public class InstructionRunner {
} }
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadValue(Environment ext, Instruction instr, Frame frame) { private static Value execLoadValue(Environment env, Instruction instr, Frame frame) {
switch (instr.type) { switch (instr.type) {
case PUSH_UNDEFINED: frame.push(null); break; case PUSH_UNDEFINED: frame.push(VoidValue.UNDEFINED); break;
case PUSH_NULL: frame.push(Values.NULL); break; case PUSH_NULL: frame.push(VoidValue.NULL); break;
default: frame.push(instr.get(0)); break; case PUSH_BOOL: frame.push(BoolValue.of(instr.get(0))); break;
case PUSH_NUMBER: frame.push(new NumberValue(instr.get(0))); break;
case PUSH_STRING: frame.push(new StringValue(instr.get(0))); break;
default:
} }
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadVar(Environment ext, Instruction instr, Frame frame) { private static Value execLoadVar(Environment env, Instruction instr, Frame frame) {
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) frame.push(GlobalScope.get(ext).get(ext, (String)i)); if (i instanceof String) frame.push(GlobalScope.get(env).get(env, (String)i));
else frame.push(frame.scope.get((int)i).get(ext)); else frame.push(frame.scope.get((int)i).get(env));
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadObj(Environment ext, Instruction instr, Frame frame) { private static Value execLoadObj(Environment env, Instruction instr, Frame frame) {
frame.push(new ObjectValue()); frame.push(new ObjectValue());
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadGlob(Environment ext, Instruction instr, Frame frame) { private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) {
frame.push(GlobalScope.get(ext).obj); frame.push(GlobalScope.get(env).object);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadArr(Environment ext, Instruction instr, Frame frame) { private static Value execLoadArr(Environment env, Instruction instr, Frame frame) {
var res = new ArrayValue(); var res = new ArrayValue();
res.setSize(instr.get(0)); res.setSize(instr.get(0));
frame.push(res); frame.push(res);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadFunc(Environment ext, Instruction instr, Frame frame) { private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) {
int id = instr.get(0); int id = instr.get(0);
var captures = new ValueVariable[instr.params.length - 1]; var captures = new ValueVariable[instr.params.length - 1];
@ -156,174 +171,175 @@ public class InstructionRunner {
captures[i - 1] = frame.scope.get(instr.get(i)); captures[i - 1] = frame.scope.get(instr.get(i));
} }
var func = new CodeFunction(ext, "", frame.function.body.children[id], captures); var func = new CodeFunction(env, "", frame.function.body.children[id], captures);
frame.push(func); frame.push(func);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadMember(Environment ext, Instruction instr, Frame frame) { private static Value execLoadMember(Environment env, Instruction instr, Frame frame) {
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
try { try {
frame.push(Values.getMember(ext, obj, key)); frame.push(obj.getMember(env, key));
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage()); throw EngineException.ofType(e.getMessage());
} }
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execLoadRegEx(Environment ext, Instruction instr, Frame frame) { private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) {
if (ext.hasNotNull(Environment.REGEX_CONSTR)) { if (env.hasNotNull(Environment.REGEX_CONSTR)) {
frame.push(Values.callNew(ext, ext.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1))); frame.push(env.get(Environment.REGEX_CONSTR).callNew(env, instr.get(0), instr.get(1)));
} }
else { else {
throw EngineException.ofSyntax("Regex is not supported."); throw EngineException.ofSyntax("Regex is not supported.");
} }
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execDiscard(Environment ext, Instruction instr, Frame frame) { private static Value execDiscard(Environment env, Instruction instr, Frame frame) {
frame.pop(); frame.pop();
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execStoreMember(Environment ext, Instruction instr, Frame frame) { private static Value execStoreMember(Environment env, Instruction instr, Frame frame) {
var val = frame.pop(); var val = frame.pop();
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
if (!Values.setMember(ext, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'.");
if ((boolean)instr.get(0)) frame.push(val); if ((boolean)instr.get(0)) frame.push(val);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execStoreVar(Environment ext, Instruction instr, Frame frame) { private static Value execStoreVar(Environment env, Instruction instr, Frame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) GlobalScope.get(ext).set(ext, (String)i, val); if (i instanceof String) GlobalScope.get(env).set(env, (String)i, val);
else frame.scope.get((int)i).set(ext, val); else frame.scope.get((int)i).set(env, val);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execStoreSelfFunc(Environment ext, Instruction instr, Frame frame) { private static Value execStoreSelfFunc(Environment env, Instruction instr, Frame frame) {
frame.scope.locals[(int)instr.get(0)].set(ext, frame.function); frame.scope.locals[(int)instr.get(0)].set(env, frame.function);
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execJmp(Environment ext, Instruction instr, Frame frame) { private static Value execJmp(Environment env, Instruction instr, Frame frame) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
return Values.NO_RETURN; return null;
} }
private static Object execJmpIf(Environment ext, Instruction instr, Frame frame) { private static Value execJmpIf(Environment env, Instruction instr, Frame frame) {
if (Values.toBoolean(frame.pop())) { if (frame.pop().toBoolean().value) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
} }
else frame.codePtr ++; else frame.codePtr ++;
return Values.NO_RETURN; return null;
} }
private static Object execJmpIfNot(Environment ext, Instruction instr, Frame frame) { private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) {
if (Values.not(frame.pop())) { if (frame.pop().not().value) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
} }
else frame.codePtr ++; else frame.codePtr ++;
return Values.NO_RETURN; return null;
} }
private static Object execTypeof(Environment ext, Instruction instr, Frame frame) { private static Value execTypeof(Environment env, Instruction instr, Frame frame) {
String name = instr.get(0); String name = instr.get(0);
Object obj; Value obj;
if (name != null) { if (name != null) {
if (GlobalScope.get(ext).has(ext, name)) { if (GlobalScope.get(env).has(env, name)) {
obj = GlobalScope.get(ext).get(ext, name); obj = GlobalScope.get(env).get(env, name);
} }
else obj = null; else obj = null;
} }
else obj = frame.pop(); else obj = frame.pop();
frame.push(Values.type(obj)); frame.push(obj.type());
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execNop(Environment ext, Instruction instr, Frame frame) { private static Value execNop(Environment env, Instruction instr, Frame frame) {
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execDelete(Environment ext, Instruction instr, Frame frame) { private static Value execDelete(Environment env, Instruction instr, Frame frame) {
var key = frame.pop(); var key = frame.pop();
var val = frame.pop(); var val = frame.pop();
if (!Values.deleteMember(ext, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'.");
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
private static Object execOperation(Environment ext, Instruction instr, Frame frame) { private static Value execOperation(Environment env, Instruction instr, Frame frame) {
Operation op = instr.get(0); Operation op = instr.get(0);
var args = new Object[op.operands]; var args = new Value[op.operands];
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
frame.push(Values.operation(ext, op, args)); frame.push(Value.operation(env, op, args));
frame.codePtr++; frame.codePtr++;
return Values.NO_RETURN; return null;
} }
public static Object exec(Environment ext, Instruction instr, Frame frame) { public static Value exec(Environment env, Instruction instr, Frame frame) {
switch (instr.type) { switch (instr.type) {
case NOP: return execNop(ext, instr, frame); case NOP: return execNop(env, instr, frame);
case RETURN: return execReturn(ext, instr, frame); case RETURN: return execReturn(env, instr, frame);
case THROW: return execThrow(ext, instr, frame); case THROW: return execThrow(env, instr, frame);
case THROW_SYNTAX: return execThrowSyntax(ext, instr, frame); case THROW_SYNTAX: return execThrowSyntax(env, instr, frame);
case CALL: return execCall(ext, instr, frame); case CALL: return execCall(env, instr, frame);
case CALL_NEW: return execCallNew(ext, instr, frame); case CALL_NEW: return execCallNew(env, instr, frame);
case TRY_START: return execTryStart(ext, instr, frame); case TRY_START: return execTryStart(env, instr, frame);
case TRY_END: return execTryEnd(ext, instr, frame); case TRY_END: return execTryEnd(env, instr, frame);
case DUP: return execDup(ext, instr, frame); case DUP: return execDup(env, instr, frame);
case PUSH_UNDEFINED: case PUSH_UNDEFINED:
case PUSH_NULL: case PUSH_NULL:
case PUSH_STRING: case PUSH_STRING:
case PUSH_NUMBER: case PUSH_NUMBER:
case PUSH_BOOL: case PUSH_BOOL:
return execLoadValue(ext, instr, frame); return execLoadValue(env, instr, frame);
case LOAD_VAR: return execLoadVar(ext, instr, frame); case LOAD_VAR: return execLoadVar(env, instr, frame);
case LOAD_OBJ: return execLoadObj(ext, instr, frame); case LOAD_OBJ: return execLoadObj(env, instr, frame);
case LOAD_ARR: return execLoadArr(ext, instr, frame); case LOAD_ARR: return execLoadArr(env, instr, frame);
case LOAD_FUNC: return execLoadFunc(ext, instr, frame); case LOAD_FUNC: return execLoadFunc(env, instr, frame);
case LOAD_MEMBER: return execLoadMember(ext, instr, frame); case LOAD_MEMBER: return execLoadMember(env, instr, frame);
case LOAD_REGEX: return execLoadRegEx(ext, instr, frame); case LOAD_REGEX: return execLoadRegEx(env, instr, frame);
case LOAD_GLOB: return execLoadGlob(ext, instr, frame); case LOAD_GLOB: return execLoadGlob(env, instr, frame);
case DISCARD: return execDiscard(ext, instr, frame); case DISCARD: return execDiscard(env, instr, frame);
case STORE_MEMBER: return execStoreMember(ext, instr, frame); case STORE_MEMBER: return execStoreMember(env, instr, frame);
case STORE_VAR: return execStoreVar(ext, instr, frame); case STORE_VAR: return execStoreVar(env, instr, frame);
case STORE_SELF_FUNC: return execStoreSelfFunc(ext, instr, frame); case STORE_SELF_FUNC: return execStoreSelfFunc(env, instr, frame);
case MAKE_VAR: return execMakeVar(ext, instr, frame); case MAKE_VAR: return execMakeVar(env, instr, frame);
case KEYS: return execKeys(ext, instr, frame); case KEYS: return execKeys(env, instr, frame);
case DEF_PROP: return execDefProp(ext, instr, frame); case DEF_PROP: return execDefProp(env, instr, frame);
case TYPEOF: return execTypeof(ext, instr, frame); case TYPEOF: return execTypeof(env, instr, frame);
case DELETE: return execDelete(ext, instr, frame); case DELETE: return execDelete(env, instr, frame);
case JMP: return execJmp(ext, instr, frame); case JMP: return execJmp(env, instr, frame);
case JMP_IF: return execJmpIf(ext, instr, frame); case JMP_IF: return execJmpIf(env, instr, frame);
case JMP_IFN: return execJmpIfNot(ext, instr, frame); case JMP_IFN: return execJmpIfNot(env, instr, frame);
case OPERATION: return execOperation(ext, instr, frame); case OPERATION: return execOperation(env, instr, frame);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
} }

View File

@ -0,0 +1,86 @@
package me.topchetoeu.jscript.runtime;
import java.util.HashSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.json.JSONList;
import me.topchetoeu.jscript.common.json.JSONMap;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public class JSONConverter {
public static Value toJs(JSONElement val) {
if (val.isBoolean()) return BoolValue.of(val.bool());
if (val.isString()) return new StringValue(val.string());
if (val.isNumber()) return new NumberValue(val.number());
if (val.isList()) return ArrayValue.of(val.list().stream().map(JSONConverter::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineOwnMember(null, new StringValue(el.getKey()), FieldMember.of(toJs(el.getValue())));
}
return res;
}
if (val.isNull()) return VoidValue.NULL;
return VoidValue.UNDEFINED;
}
public static JSONElement fromJs(Environment ext, Value val) {
var res = JSONConverter.fromJs(ext, val, new HashSet<>());
if (res == null) return JSONElement.NULL;
else return res;
}
public static JSONElement fromJs(Environment env, Value val, HashSet<Object> prev) {
if (val instanceof BoolValue) return JSONElement.bool(((BoolValue)val).value);
if (val instanceof NumberValue) return JSONElement.number(((NumberValue)val).value);
if (val instanceof StringValue) return JSONElement.string(((StringValue)val).value);
if (val == VoidValue.NULL) return JSONElement.NULL;
if (val instanceof VoidValue) return null;
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(env, el, prev);
if (jsonEl == null) continue;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : val.getMembers(env, true, true).entrySet()) {
var jsonEl = fromJs(env, el.getValue().get(env, val), prev);
if (jsonEl == null) continue;
res.put(el.getKey(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
}

View File

@ -0,0 +1,136 @@
package me.topchetoeu.jscript.runtime;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import me.topchetoeu.jscript.common.Compiler;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Metadata;
import me.topchetoeu.jscript.common.Reading;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
public class SimpleRepl {
static Thread engineTask, debugTask;
static Engine engine = new Engine();
static Environment environment = Environment.empty();
static int j = 0;
static String[] args;
private static void reader() {
try {
for (var arg : args) {
try {
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file.toFile()),
raw, null
).await();
System.err.println(res.toReadable(environment));
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.readline();
if (raw == null) break;
var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw);
var res = engine.pushMsg(false, environment, func, null).await();
System.err.println(res.toReadable(environment));
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
}
}
catch (IOException e) {
System.out.println(e.toString());
engine.thread().interrupt();
}
catch (RuntimeException ex) {
if (ex instanceof InterruptException) return;
else {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
}
private static void initEnv() {
var glob = GlobalScope.get(environment);
glob.define(null, false, new NativeFunction("exit", args -> {
throw new InterruptException();
}));
// glob.define(null, false, new NativeFunction("go", args -> {
// try {
// var f = Path.of("do.js");
// var func = Compiler.compile(args.env, new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
// return func.call(args.env);
// }
// catch (IOException e) {
// throw new EngineException("Couldn't open do.js");
// }
// }));
glob.define(null, false, new NativeFunction("log", args -> {
for (var el : args.args) {
System.out.print(el.toReadable(args.env));
}
return null;
}));
// var fs = new RootFilesystem(PermissionsProvider.get(environment));
// fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
// fs.protocols.put("file", new PhysicalFilesystem("."));
// fs.protocols.put("std", new STDFilesystem(System.in, System.out, System.err));
// environment.add(PermissionsProvider.KEY, PermissionsManager.ALL_PERMS);
// environment.add(Filesystem.KEY, fs);
// environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs));
// environment.add(Compiler.KEY, new JSCompiler(environment));
environment.add(EventLoop.KEY, engine);
environment.add(GlobalScope.KEY, new GlobalScope());
// environment.add(EventLoop.KEY, engine);
environment.add(Compiler.KEY, (filename, source) -> {
return Parsing.compile(filename, source).body();
});
}
private static void initEngine() {
// var ctx = new DebugContext();
// environment.add(DebugContext.KEY, ctx);
// debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
engineTask = engine.start();
// debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
}
public static void main(String args[]) throws InterruptedException {
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
SimpleRepl.args = args;
var reader = new Thread(SimpleRepl::reader);
initEnv();
initEngine();
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
engine.thread().join();
debugTask.interrupt();
engineTask.interrupt();
}
}

View File

@ -1,8 +1,8 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.runtime;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public interface WrapperProvider { public interface WrapperProvider {
public ObjectValue getProto(Class<?> obj); public ObjectValue getProto(Class<?> obj);

View File

@ -14,8 +14,8 @@ import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key; import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.CodeFunction; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
public class DebugContext { public class DebugContext {
public static final Key<DebugContext> KEY = new Key<>(); public static final Key<DebugContext> KEY = new Key<>();
@ -71,6 +71,7 @@ public class DebugContext {
return getMapOrEmpty(((CodeFunction)func).body); return getMapOrEmpty(((CodeFunction)func).body);
} }
public List<Frame> getStackFrames() { public List<Frame> getStackFrames() {
if (debugger == null) return List.of();
return this.debugger.getStackFrame(); return this.debugger.getStackFrame();
} }

View File

@ -7,9 +7,9 @@ import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import me.topchetoeu.jscript.runtime.Compiler; import me.topchetoeu.jscript.common.Compiler;
import me.topchetoeu.jscript.runtime.values.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public class Environment { public class Environment {
public static final Key<Compiler> COMPILE_FUNC = new Key<>(); public static final Key<Compiler> COMPILE_FUNC = new Key<>();

View File

@ -5,9 +5,11 @@ import java.util.List;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.ObjectValue; import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Values; import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
public class EngineException extends RuntimeException { public class EngineException extends RuntimeException {
public static class StackElement { public static class StackElement {
@ -40,7 +42,7 @@ public class EngineException extends RuntimeException {
} }
} }
public final Object value; public final Value value;
public EngineException cause; public EngineException cause;
public Environment env = null; public Environment env = null;
public final List<StackElement> stackTrace = new ArrayList<>(); public final List<StackElement> stackTrace = new ArrayList<>();
@ -61,10 +63,10 @@ public class EngineException extends RuntimeException {
return this; return this;
} }
public String toString(Environment ext) { public String toString(Environment env) {
var ss = new StringBuilder(); var ss = new StringBuilder();
try { try {
ss.append(Values.toString(ext, value)).append('\n'); ss.append(value.toString(env)).append('\n');
} }
catch (EngineException e) { catch (EngineException e) {
ss.append("[Error while stringifying]\n"); ss.append("[Error while stringifying]\n");
@ -72,38 +74,40 @@ public class EngineException extends RuntimeException {
for (var line : stackTrace) { for (var line : stackTrace) {
if (line.visible()) ss.append(" ").append(line.toString()).append("\n"); if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
} }
if (cause != null) ss.append("Caused by ").append(cause.toString(ext)).append('\n'); if (cause != null) ss.append("Caused by ").append(cause.toString(env)).append('\n');
ss.deleteCharAt(ss.length() - 1); ss.deleteCharAt(ss.length() - 1);
return ss.toString(); return ss.toString();
} }
private static Object err(String name, String msg, PlaceholderProto proto) { private static ObjectValue err(String name, String msg, PrototypeProvider proto) {
var res = new ObjectValue(proto); var res = new ObjectValue();
if (name != null) res.defineProperty(null, "name", name); res.setPrototype(proto);
res.defineProperty(null, "message", msg);
if (name != null) res.defineOwnMember(Environment.empty(), new StringValue("name"), FieldMember.of(new StringValue(name)));
res.defineOwnMember(Environment.empty(), new StringValue("message"), FieldMember.of(new StringValue(msg)));
return res; return res;
} }
public EngineException(Object error) { public EngineException(Value error) {
super(error == null ? "null" : error.toString()); super(error.toReadable(Environment.empty()));
this.value = error; this.value = error;
this.cause = null; this.cause = null;
} }
public static EngineException ofError(String name, String msg) { public static EngineException ofError(String name, String msg) {
return new EngineException(err(name, msg, PlaceholderProto.ERROR)); return new EngineException(err(name, msg, env -> env.get(Environment.ERROR_PROTO)));
} }
public static EngineException ofError(String msg) { public static EngineException ofError(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.ERROR)); return new EngineException(err(null, msg, env -> env.get(Environment.ERROR_PROTO)));
} }
public static EngineException ofSyntax(String msg) { public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); return new EngineException(err(null, msg, env -> env.get(Environment.SYNTAX_ERR_PROTO)));
} }
public static EngineException ofType(String msg) { public static EngineException ofType(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR)); return new EngineException(err(null, msg, env -> env.get(Environment.TYPE_ERR_PROTO)));
} }
public static EngineException ofRange(String msg) { public static EngineException ofRange(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR)); return new EngineException(err(null, msg, env -> env.get(Environment.RANGE_ERR_PROTO)));
} }
} }

View File

@ -6,55 +6,48 @@ import java.util.Set;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key; import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.FunctionValue; import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.NativeFunction; import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.ObjectValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.Values; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public class GlobalScope { public class GlobalScope {
public static final Key<GlobalScope> KEY = new Key<>(); public static final Key<GlobalScope> KEY = new Key<>();
public final ObjectValue obj; public final ObjectValue object;
public boolean has(Environment ext, String name) { public boolean has(Environment ext, String name) {
return Values.hasMember(ext, obj, name, false); return object.hasMember(ext, new StringValue(name), false);
} }
public GlobalScope child() { public GlobalScope child() {
var obj = new ObjectValue(); var res = new GlobalScope();
Values.setPrototype(null, obj, this.obj); res.object.setPrototype(null, this.object);
return new GlobalScope(obj); return res;
} }
public Object define(Environment ext, String name) { public void define(Environment ext, String name, Variable variable) {
if (Values.hasMember(ext, obj, name, false)) return name; object.defineOwnMember(ext, new StringValue(name), variable.toField(true, true));
obj.defineProperty(ext, name, null);
return name;
} }
public void define(Environment ext, String name, Variable val) { public void define(Environment ext, boolean readonly, String name, Value val) {
obj.defineProperty(ext, name, object.defineOwnMember(ext, new StringValue(name), FieldMember.of(val, !readonly));
new NativeFunction("get " + name, args -> val.get(args.env)),
new NativeFunction("set " + name, args -> { val.set(args.env, args.get(0)); return null; }),
true, true
);
} }
public void define(Environment ext, String name, boolean readonly, Object val) { public void define(Environment ext, boolean readonly, String ...names) {
obj.defineProperty(ext, name, val, readonly, true, true); for (var name : names) define(ext, name, new ValueVariable(readonly, VoidValue.UNDEFINED));
}
public void define(Environment ext, String ...names) {
for (var n : names) define(ext, n);
} }
public void define(Environment ext, boolean readonly, FunctionValue val) { public void define(Environment ext, boolean readonly, FunctionValue val) {
define(ext, val.name, readonly, val); define(ext, readonly, val.name, val);
} }
public Object get(Environment ext, String name) { public Value get(Environment env, String name) {
if (!Values.hasMember(ext, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!object.hasMember(env, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return Values.getMember(ext, obj, name); else return object.getMember(env, new StringValue(name));
} }
public void set(Environment ext, String name, Object val) { public void set(Environment ext, String name, Value val) {
if (!Values.hasMember(ext, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!object.hasMember(ext, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!Values.setMember(ext, obj, name, val)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); if (!object.setMember(ext, new StringValue(name), val)) throw EngineException.ofSyntax("The global '" + name + "' is read-only.");
} }
public Set<String> keys() { public Set<String> keys() {
@ -68,10 +61,11 @@ public class GlobalScope {
} }
public GlobalScope() { public GlobalScope() {
this.obj = new ObjectValue(); this.object = new ObjectValue();
this.object.setPrototype(null, null);
} }
public GlobalScope(ObjectValue val) { public GlobalScope(ObjectValue val) {
this.obj = val; this.object = val;
} }
public static GlobalScope get(Environment ext) { public static GlobalScope get(Environment ext) {

View File

@ -1,27 +1,24 @@
package me.topchetoeu.jscript.runtime.scope; package me.topchetoeu.jscript.runtime.scope;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Values; import me.topchetoeu.jscript.runtime.values.Value;
public class ValueVariable implements Variable { public class ValueVariable implements Variable {
public boolean readonly; public boolean readonly;
public Object value; public Value value;
@Override @Override public boolean readonly() { return readonly; }
public boolean readonly() { return readonly; } @Override public final Value get(Environment env) { return get(); }
@Override public final boolean set(Environment env, Value val) { return set(val); }
@Override public Value get() { return value; }
public Object get(Environment ext) { public boolean set(Value val) {
return value; if (readonly) return false;
this.value = val;
return true;
} }
@Override public ValueVariable(boolean readonly, Value val) {
public void set(Environment ext, Object val) {
if (readonly) return;
this.value = Values.normalize(ext, val);
}
public ValueVariable(boolean readonly, Object val) {
this.readonly = readonly; this.readonly = readonly;
this.value = val; this.value = val;
} }

View File

@ -1,9 +1,24 @@
package me.topchetoeu.jscript.runtime.scope; package me.topchetoeu.jscript.runtime.scope;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
public interface Variable { public interface Variable {
Object get(Environment ext); Value get(Environment env);
default boolean readonly() { return true; } default boolean readonly() { return true; }
default void set(Environment ext, Object val) { } default boolean set(Environment env, Value val) { return false; }
default FieldMember toField(boolean configurable, boolean enumerable) {
var self = this;
return new FieldMember(!readonly(), configurable, enumerable) {
@Override public Value get(Environment env, Value _self) {
return self.get(env);
}
@Override public boolean set(Environment env, Value val, Value _self) {
return self.set(env, val);
}
};
}
} }

View File

@ -1,227 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.runtime.environment.Environment;
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object();
private Object[] values;
private int size;
private Object[] alloc(int index) {
index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length);
return arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
values = alloc(val);
size = val;
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == UNDEFINED) return null;
else return res;
}
public void set(Environment ext, int i, Object val) {
if (i < 0) return;
values = alloc(i);
val = Values.normalize(ext, val);
if (val == null) val = UNDEFINED;
values[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < size && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Object[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) {
values[--size] = null;
}
}
}
public Object[] toArray() {
Object[] res = new Object[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart];
}
}
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
if (arr == this) {
move(sourceStart, destStart, count);
return;
}
// Iterate in reverse to reallocate at most once
if (destStart + count > arr.size) arr.size = destStart + count;
for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(null, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(null, i + destStart, values[i + sourceStart]);
}
}
public void copyFrom(Environment ext, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
set(ext, i + destStart, arr[i + sourceStart]);
}
}
public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Object> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2;
if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override
protected Object getField(Environment ext, Object key) {
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
return get((int)i);
}
}
return super.getField(ext, key);
}
@Override
protected boolean setField(Environment ext, Object key, Object val) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
set(ext, (int)i, val);
return true;
}
}
return super.setField(ext, key, val);
}
@Override
protected boolean hasField(Environment ext, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
return has((int)i);
}
}
return super.hasField(ext, key);
}
@Override
protected void deleteField(Environment ext, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
remove((int)i);
return;
}
}
super.deleteField(ext, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
return res;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
values = new Object[16];
size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
}
public ArrayValue(Environment ext, Object ...values) {
this();
this.values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ext, values[i]);
}
public static ArrayValue of(Environment ext, Collection<?> values) {
return new ArrayValue(ext, values.toArray(Object[]::new));
}
}

View File

@ -1,69 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.util.List;
import me.topchetoeu.jscript.runtime.environment.Environment;
public abstract class FunctionValue extends ObjectValue {
public String name = "";
public int length;
@Override
public String toString() {
return String.format("function %s(...)", name);
}
public abstract Object call(Environment ext, Object thisArg, Object ...args);
public Object call(Environment ext) {
return call(ext, null);
}
@Override
protected Object getField(Environment ext, Object key) {
if ("name".equals(key)) return name;
if ("length".equals(key)) return length;
return super.getField(ext, key);
}
@Override
protected boolean setField(Environment ext, Object key, Object val) {
if ("name".equals(key)) name = Values.toString(ext, val);
else if ("length".equals(key)) length = (int)Values.toNumber(ext, val);
else return super.setField(ext, key, val);
return true;
}
@Override
protected boolean hasField(Environment ext, Object key) {
if ("name".equals(key)) return true;
if ("length".equals(key)) return true;
return super.hasField(ext, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
if (includeNonEnumerable) {
res.add("name");
res.add("length");
}
return res;
}
public FunctionValue(String name, int length) {
super(PlaceholderProto.FUNCTION);
if (name == null) name = "";
this.length = length;
this.name = name;
nonConfigurableSet.add("name");
nonEnumerableSet.add("name");
nonWritableSet.add("length");
nonConfigurableSet.add("length");
nonEnumerableSet.add("length");
var proto = new ObjectValue();
proto.defineProperty(null, "constructor", this, true, false, false);
this.defineProperty(null, "prototype", proto, true, false, false);
}
}

View File

@ -0,0 +1,132 @@
package me.topchetoeu.jscript.runtime.values;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public interface Member {
public static final class PropertyMember implements Member {
public final FunctionValue getter;
public final FunctionValue setter;
public final boolean configurable;
public final boolean enumerable;
@Override public Value get(Environment env, Value self) {
if (getter != null) return getter.call(env, self);
else return VoidValue.UNDEFINED;
}
@Override public boolean set(Environment env, Value val, Value self) {
if (setter == null) return false;
setter.call(env, self, val);
return true;
}
@Override public boolean configurable() { return configurable; }
@Override public boolean enumerable() { return enumerable; }
@Override public boolean configure(Environment env, Member newMember, Value self) {
if (!(newMember instanceof PropertyMember)) return false;
var prop = (PropertyMember)newMember;
if (prop.configurable != configurable) return false;
if (prop.enumerable != enumerable) return false;
if (prop.getter == getter) return true;
if (prop.setter == setter) return true;
return false;
}
@Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue();
if (getter == null) res.defineOwnMember(env, new StringValue("getter"), FieldMember.of(VoidValue.UNDEFINED));
else res.defineOwnMember(env, new StringValue("getter"), FieldMember.of(getter));
if (setter == null) res.defineOwnMember(env, new StringValue("setter"), FieldMember.of(VoidValue.UNDEFINED));
else res.defineOwnMember(env, new StringValue("setter"), FieldMember.of(setter));
res.defineOwnMember(env, new StringValue("enumerable"), FieldMember.of(BoolValue.of(enumerable)));
res.defineOwnMember(env, new StringValue("configurable"), FieldMember.of(BoolValue.of(configurable)));
return res;
}
public PropertyMember(FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
this.getter = getter;
this.setter = setter;
this.configurable = configurable;
this.enumerable = enumerable;
}
}
public static abstract class FieldMember implements Member {
private static class SimpleFieldMember extends FieldMember {
public Value value;
@Override public Value get(Environment env, Value self) { return value; }
@Override public boolean set(Environment env, Value val, Value self) {
if (!writable) return false;
value = val;
return true;
}
public SimpleFieldMember(Value value, boolean configurable, boolean enumerable, boolean writable) {
super(configurable, enumerable, writable);
this.value = value;
}
}
public boolean configurable;
public boolean enumerable;
public boolean writable;
@Override public final boolean configurable() { return configurable; }
@Override public final boolean enumerable() { return enumerable; }
@Override public final boolean configure(Environment env, Member newMember, Value self) {
if (!(newMember instanceof FieldMember)) return false;
var field = (FieldMember)newMember;
if (field.configurable != configurable) return false;
if (field.enumerable != enumerable) return false;
if (!writable) return field.get(env, self).strictEquals(env, get(env, self));
set(env, field.get(env, self), self);
writable = field.writable;
return true;
}
@Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue();
res.defineOwnMember(env, new StringValue("value"), FieldMember.of(get(env, self)));
res.defineOwnMember(env, new StringValue("writable"), FieldMember.of(BoolValue.of(writable)));
res.defineOwnMember(env, new StringValue("enumerable"), FieldMember.of(BoolValue.of(enumerable)));
res.defineOwnMember(env, new StringValue("configurable"), FieldMember.of(BoolValue.of(configurable)));
return res;
}
public FieldMember(boolean configurable, boolean enumerable, boolean writable) {
this.configurable = configurable;
this.enumerable = enumerable;
this.writable = writable;
}
public static FieldMember of(Value value) {
return new SimpleFieldMember(value, true, true, true);
}
public static FieldMember of(Value value, boolean writable) {
return new SimpleFieldMember(value, true, true, writable);
}
public static FieldMember of(Value value, boolean configurable, boolean enumerable, boolean writable) {
return new SimpleFieldMember(value, configurable, enumerable, writable);
}
}
public boolean configurable();
public boolean enumerable();
public boolean configure(Environment env, Member newMember, Value self);
public ObjectValue descriptor(Environment env, Value self);
public Value get(Environment env, Value self);
public boolean set(Environment env, Value val, Value self);
}

View File

@ -1,77 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.util.WeakHashMap;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
public class NativeWrapper extends ObjectValue {
private static class MapKey {
public final Object key;
@Override
public int hashCode() {
return System.identityHashCode(key);
}
@Override
public boolean equals(Object obj) {
if (this == null || obj == null) return this == null && obj == null;
if (!(obj instanceof MapKey)) return false;
var other = (MapKey)obj;
return other.key == key;
}
public MapKey(Object key) {
this.key = key;
}
}
private static final Key<WeakHashMap<MapKey, NativeWrapper>> WRAPPER_MAP = new Key<>();
private static final Object NATIVE_PROTO = new Object();
public final Object wrapped;
@Override
public ObjectValue getPrototype(Environment ext) {
if (ext != null && prototype == NATIVE_PROTO) {
var clazz = wrapped.getClass();
var res = NativeWrapperProvider.get(ext).getProto(clazz);
if (res != null) return res;
}
return super.getPrototype(ext);
}
@Override
public String toString() {
return wrapped.toString();
}
@Override
public boolean equals(Object obj) {
return wrapped.equals(obj);
}
@Override
public int hashCode() {
return wrapped.hashCode();
}
private NativeWrapper(Object wrapped) {
this.wrapped = wrapped;
prototype = NATIVE_PROTO;
}
public static NativeWrapper of(Environment env, Object wrapped) {
if (env == null) return new NativeWrapper(wrapped);
var wrappers = env.get(WRAPPER_MAP);
if (wrappers == null) return new NativeWrapper(wrapped);
var key = new MapKey(wrapped);
if (wrappers.containsKey(key)) return wrappers.get(key);
var res = new NativeWrapper(wrapped);
wrappers.put(key, res);
return res;
}
}

View File

@ -1,353 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
public class ObjectValue {
public static enum PlaceholderProto {
NONE,
OBJECT,
ARRAY,
FUNCTION,
ERROR,
SYNTAX_ERROR,
TYPE_ERROR,
RANGE_ERROR,
}
public static enum State {
NORMAL,
NO_EXTENSIONS,
SEALED,
FROZEN,
}
public static class Property {
public final FunctionValue getter;
public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter;
this.setter = setter;
}
}
private static final Object OBJ_PROTO = new Object();
private static final Object ARR_PROTO = new Object();
private static final Object FUNC_PROTO = new Object();
private static final Object ERR_PROTO = new Object();
private static final Object SYNTAX_ERR_PROTO = new Object();
private static final Object TYPE_ERR_PROTO = new Object();
private static final Object RANGE_ERR_PROTO = new Object();
protected Object prototype;
public State state = State.NORMAL;
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
private Property getProperty(Environment env, Object key) {
if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(env);
if (proto != null) return proto.getProperty(env, key);
else return null;
}
public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false;
return !values.containsKey(key) || !nonWritableSet.contains(key);
}
public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false;
return !nonConfigurableSet.contains(key);
}
public final boolean memberEnumerable(Object key) {
return !nonEnumerableSet.contains(key);
}
public final boolean extensible() {
return state == State.NORMAL;
}
public final void preventExtensions() {
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
}
public final void seal() {
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
}
public final void freeze() {
state = State.FROZEN;
}
public final boolean defineProperty(Environment env, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
key = Values.normalize(env, key); val = Values.normalize(env, val);
boolean reconfigured =
writable != memberWritable(key) ||
configurable != memberConfigurable(key) ||
enumerable != memberEnumerable(key);
if (!reconfigured) {
if (!memberWritable(key)) {
var a = values.get(key);
var b = val;
if (a == null || b == null) return a == null && b == null;
return a == b || a.equals(b);
}
values.put(key, val);
return true;
}
if (
properties.containsKey(key) &&
values.get(key) == val &&
!reconfigured
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!writable) nonWritableSet.add(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
values.put(key, val);
return true;
}
public final boolean defineProperty(Environment env, Object key, Object val) {
return defineProperty(env, key, val, true, true, true);
}
public final boolean defineProperty(Environment env, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
key = Values.normalize(env, key);
if (
properties.containsKey(key) &&
properties.get(key).getter == getter &&
properties.get(key).setter == setter &&
!configurable == nonConfigurableSet.contains(key) &&
!enumerable == nonEnumerableSet.contains(key)
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
properties.put(key, new Property(getter, setter));
return true;
}
public ObjectValue getPrototype(Environment env) {
if (prototype instanceof ObjectValue || prototype == null) return (ObjectValue)prototype;
try {
if (prototype == ARR_PROTO) return env.get(Environment.ARRAY_PROTO);
if (prototype == FUNC_PROTO) return env.get(Environment.FUNCTION_PROTO);
if (prototype == ERR_PROTO) return env.get(Environment.ERROR_PROTO);
if (prototype == RANGE_ERR_PROTO) return env.get(Environment.RANGE_ERR_PROTO);
if (prototype == SYNTAX_ERR_PROTO) return env.get(Environment.SYNTAX_ERR_PROTO);
if (prototype == TYPE_ERR_PROTO) return env.get(Environment.TYPE_ERR_PROTO);
return env.get(Environment.OBJECT_PROTO);
}
catch (NullPointerException e) { return null; }
}
public final boolean setPrototype(PlaceholderProto val) {
if (!extensible()) return false;
switch (val) {
case OBJECT: prototype = OBJ_PROTO; break;
case FUNCTION: prototype = FUNC_PROTO; break;
case ARRAY: prototype = ARR_PROTO; break;
case ERROR: prototype = ERR_PROTO; break;
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
case NONE: prototype = null; break;
}
return true;
}
/**
* A method, used to get the value of a field. If a property is bound to
* this key, but not a field, this method should return null.
*/
protected Object getField(Environment env, Object key) {
if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(env);
if (proto != null) return proto.getField(env, key);
else return null;
}
/**
* Changes the value of a field, that is bound to the given key. If no field is
* bound to this key, a new field should be created with the given value
* @return Whether or not the operation was successful
*/
protected boolean setField(Environment env, Object key, Object val) {
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(env, key);
}
values.put(key, val);
return true;
}
/**
* Deletes the field bound to the given key.
*/
protected void deleteField(Environment env, Object key) {
values.remove(key);
}
/**
* Returns whether or not there is a field bound to the given key.
* This must ignore properties
*/
protected boolean hasField(Environment env, Object key) {
return values.containsKey(key);
}
public final Object getMember(Environment env, Object key, Object thisArg) {
key = Values.normalize(env, key);
if ("__proto__".equals(key)) {
var res = getPrototype(env);
return res == null ? Values.NULL : res;
}
var prop = getProperty(env, key);
if (prop != null) {
if (prop.getter == null) return null;
else return prop.getter.call(env, Values.normalize(env, thisArg));
}
else return getField(env, key);
}
public final boolean setMember(Environment env, Object key, Object val, Object thisArg, boolean onlyProps) {
key = Values.normalize(env, key); val = Values.normalize(env, val);
var prop = getProperty(env, key);
if (prop != null) {
if (prop.setter == null) return false;
prop.setter.call(env, Values.normalize(env, thisArg), val);
return true;
}
else if (onlyProps) return false;
else if (!extensible() && !values.containsKey(key)) return false;
else if (key == null) {
values.put(key, val);
return true;
}
else if ("__proto__".equals(key)) return setPrototype(env, val);
else if (nonWritableSet.contains(key)) return false;
else return setField(env, key, val);
}
public final boolean hasMember(Environment env, Object key, boolean own) {
key = Values.normalize(env, key);
if (key != null && "__proto__".equals(key)) return true;
if (hasField(env, key)) return true;
if (properties.containsKey(key)) return true;
if (own) return false;
var proto = getPrototype(env);
return proto != null && proto.hasMember(env, key, own);
}
public final boolean deleteMember(Environment env, Object key) {
key = Values.normalize(env, key);
if (!memberConfigurable(key)) return false;
properties.remove(key);
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
deleteField(env, key);
return true;
}
public final boolean setPrototype(Environment env, Object val) {
val = Values.normalize(env, val);
if (!extensible()) return false;
if (val == null || val == Values.NULL) {
prototype = null;
return true;
}
else if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
if (env != null) {
if (obj == env.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO;
else if (obj == env.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO;
else if (obj == env.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO;
else if (obj == env.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO;
else if (obj == env.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO;
else if (obj == env.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO;
else if (obj == env.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO;
else prototype = obj;
}
else prototype = obj;
return true;
}
return false;
}
public final ObjectValue getMemberDescriptor(Environment env, Object key) {
key = Values.normalize(env, key);
var prop = properties.get(key);
var res = new ObjectValue();
res.defineProperty(env, "configurable", memberConfigurable(key));
res.defineProperty(env, "enumerable", memberEnumerable(key));
if (prop != null) {
res.defineProperty(env, "get", prop.getter);
res.defineProperty(env, "set", prop.setter);
}
else if (hasField(env, key)) {
res.defineProperty(env, "value", values.get(key));
res.defineProperty(env, "writable", memberWritable(key));
}
else return null;
return res;
}
public List<Object> keys(boolean includeNonEnumerable) {
var res = new ArrayList<Object>();
for (var key : values.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
for (var key : properties.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
return res;
}
public ObjectValue(Environment env, Map<?, ?> values) {
this(PlaceholderProto.OBJECT);
for (var el : values.entrySet()) {
defineProperty(env, el.getKey(), el.getValue());
}
}
public ObjectValue(PlaceholderProto proto) {
nonConfigurableSet.add("__proto__");
nonEnumerableSet.add("__proto__");
setPrototype(proto);
}
public ObjectValue() {
this(PlaceholderProto.OBJECT);
}
}

View File

@ -1,54 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
public class ScopeValue extends ObjectValue {
public final ValueVariable[] variables;
public final HashMap<String, Integer> names = new HashMap<>();
@Override
protected Object getField(Environment ext, Object key) {
if (names.containsKey(key)) return variables[names.get(key)].get(ext);
return super.getField(ext, key);
}
@Override
protected boolean setField(Environment ext, Object key, Object val) {
if (names.containsKey(key)) {
variables[names.get(key)].set(ext, val);
return true;
}
var proto = getPrototype(ext);
if (proto != null && proto.hasMember(ext, key, false) && proto.setField(ext, key, val)) return true;
return super.setField(ext, key, val);
}
@Override
protected void deleteField(Environment ext, Object key) {
if (names.containsKey(key)) return;
super.deleteField(ext, key);
}
@Override
protected boolean hasField(Environment ext, Object key) {
if (names.containsKey(key)) return true;
return super.hasField(ext, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet());
return res;
}
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i);
this.nonConfigurableSet.add(names[i]);
}
}
}

View File

@ -1,28 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.util.HashMap;
public final class Symbol {
private static final HashMap<String, Symbol> registry = new HashMap<>();
public final String value;
public Symbol(String value) {
this.value = value;
}
@Override
public String toString() {
if (value == null) return "Symbol";
else return "@@" + value;
}
public static Symbol get(String name) {
if (registry.containsKey(name)) return registry.get(name);
else {
var res = new Symbol(name);
registry.put(name, res);
return res;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,11 @@ import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.ConvertException; import me.topchetoeu.jscript.runtime.exceptions.ConvertException;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
public class Values { public class Values {
@ -68,7 +73,7 @@ public class Values {
if (val instanceof String) return "string"; if (val instanceof String) return "string";
if (val instanceof Number) return "number"; if (val instanceof Number) return "number";
if (val instanceof Boolean) return "boolean"; if (val instanceof Boolean) return "boolean";
if (val instanceof Symbol) return "symbol"; if (val instanceof SymbolValue) return "symbol";
if (val instanceof FunctionValue) return "function"; if (val instanceof FunctionValue) return "function";
return "object"; return "object";
} }
@ -89,7 +94,7 @@ public class Values {
obj instanceof Number || obj instanceof Number ||
obj instanceof String || obj instanceof String ||
obj instanceof Boolean || obj instanceof Boolean ||
obj instanceof Symbol || obj instanceof SymbolValue ||
obj == null || obj == null ||
obj == NULL; obj == NULL;
} }
@ -141,7 +146,7 @@ public class Values {
} }
if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
if (val instanceof String) return (String)val; if (val instanceof String) return (String)val;
if (val instanceof Symbol) return val.toString(); if (val instanceof SymbolValue) return val.toString();
return "Unknown value"; return "Unknown value";
} }
@ -335,7 +340,7 @@ public class Values {
if (obj instanceof String) return ext.get(Environment.STRING_PROTO); if (obj instanceof String) return ext.get(Environment.STRING_PROTO);
else if (obj instanceof Number) return ext.get(Environment.NUMBER_PROTO); else if (obj instanceof Number) return ext.get(Environment.NUMBER_PROTO);
else if (obj instanceof Boolean) return ext.get(Environment.BOOL_PROTO); else if (obj instanceof Boolean) return ext.get(Environment.BOOL_PROTO);
else if (obj instanceof Symbol) return ext.get(Environment.SYMBOL_PROTO); else if (obj instanceof SymbolValue) return ext.get(Environment.SYMBOL_PROTO);
return null; return null;
} }
@ -433,7 +438,7 @@ public class Values {
b = toPrimitive(ext, b, ConvertHint.VALUEOF); b = toPrimitive(ext, b, ConvertHint.VALUEOF);
// Compare symbols by reference // Compare symbols by reference
if (a instanceof Symbol || b instanceof Symbol) return a == b; if (a instanceof SymbolValue || b instanceof SymbolValue) return a == b;
if (a instanceof Boolean || b instanceof Boolean) return toBoolean(a) == toBoolean(b); if (a instanceof Boolean || b instanceof Boolean) return toBoolean(a) == toBoolean(b);
if (a instanceof Number || b instanceof Number) return strictEquals(ext, toNumber(ext, a), toNumber(ext, b)); if (a instanceof Number || b instanceof Number) return strictEquals(ext, toNumber(ext, a), toNumber(ext, b));
@ -545,7 +550,7 @@ public class Values {
public static Iterable<Object> fromJSIterator(Environment ext, Object obj) { public static Iterable<Object> fromJSIterator(Environment ext, Object obj) {
return () -> { return () -> {
try { try {
var symbol = Symbol.get("Symbol.iterator"); var symbol = SymbolValue.get("Symbol.iterator");
var iteratorFunc = getMember(ext, obj, symbol); var iteratorFunc = getMember(ext, obj, symbol);
if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator(); if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator();
@ -634,8 +639,8 @@ public class Values {
if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true));
else { else {
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.defineProperty(args.env, "value", it.next()); object.defineProperty(args.env, "value", it.next());
return obj; return object;
} }
}); });
})); }));

View File

@ -0,0 +1,39 @@
package me.topchetoeu.jscript.runtime.values.functions;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public class Arguments {
public final Value self;
public final Value[] args;
public final Environment env;
public int n() {
return args.length;
}
public boolean has(int i) {
return i == -1 || i >= 0 && i < args.length;
}
public Value self() {
return get(-1);
}
public Value get(int i) {
if (i >= args.length || i < -1) return VoidValue.UNDEFINED;
else if (i == -1) return self;
else return args[i];
}
public Value getOrDefault(int i, Value def) {
if (i < -1 || i >= args.length) return def;
else return get(i);
}
public Arguments(Environment env, Value thisArg, Value... args) {
this.env = env;
this.args = args;
this.self = thisArg;
}
}

View File

@ -1,36 +1,24 @@
package me.topchetoeu.jscript.runtime.values; package me.topchetoeu.jscript.runtime.values.functions;
import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.runtime.Frame; import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.Value;
public class CodeFunction extends FunctionValue { public class CodeFunction extends FunctionValue {
public final FunctionBody body; public final FunctionBody body;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public Environment env; public Environment env;
// public Location loc() { @Override public Value call(Environment env, Value thisArg, Value ...args) {
// for (var instr : body.instructions) {
// if (instr.location != null) return instr.location;
// }
// return null;
// }
// public String readable() {
// var loc = loc();
// if (loc == null) return name;
// else if (name.equals("")) return loc.toString();
// else return name + "@" + loc;
// }
@Override public Object call(Environment env, Object thisArg, Object ...args) {
var frame = new Frame(env, thisArg, args, this); var frame = new Frame(env, thisArg, args, this);
frame.onPush(); frame.onPush();
try { try {
while (true) { while (true) {
var res = frame.next(); var res = frame.next();
if (res != Values.NO_RETURN) return res; if (res != null) return res;
} }
} }
finally { finally {

View File

@ -0,0 +1,77 @@
package me.topchetoeu.jscript.runtime.values.functions;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
public abstract class FunctionValue extends ObjectValue {
public String name = "";
public int length;
public Value prototype = new ObjectValue();
private final FieldMember nameField = new FieldMember(false, true, true) {
@Override public Value get(Environment env, Value self) {
return new StringValue(name);
}
@Override public boolean set(Environment env, Value val, Value self) {
name = val.toString(env).value;
return true;
}
};
private final FieldMember lengthField = new FieldMember(false, true, false) {
@Override public Value get(Environment env, Value self) {
return new NumberValue(length);
}
@Override public boolean set(Environment env, Value val, Value self) {
return false;
}
};
private final FieldMember prototypeField = new FieldMember(false, true, true) {
@Override public Value get(Environment env, Value self) {
return prototype;
}
@Override public boolean set(Environment env, Value val, Value self) {
prototype = val;
return true;
}
};
@Override public String toString() { return String.format("function %s(...)", name); }
@Override public abstract Value call(Environment ext, Value thisArg, Value ...args);
@Override public Member getOwnMember(Environment env, Value key) {
var el = key.toString(env).value;
if (el.equals("length")) return lengthField;
if (el.equals("name")) return nameField;
if (el.equals("prototype")) return prototypeField;
return super.getOwnMember(env, key);
}
@Override public boolean deleteOwnMember(Environment env, Value key) {
if (!super.deleteMember(env, key)) return false;
var el = key.toString(env).value;
if (el.equals("length")) return false;
if (el.equals("name")) return false;
if (el.equals("prototype")) return false;
return true;
}
public FunctionValue(String name, int length) {
setPrototype(Environment.FUNCTION_PROTO);
if (name == null) name = "";
this.length = length;
this.name = name;
prototype.defineOwnMember(Environment.empty(), new StringValue("constructor"), FieldMember.of(this));
}
}

View File

@ -1,18 +1,17 @@
package me.topchetoeu.jscript.runtime.values; package me.topchetoeu.jscript.runtime.values.functions;
import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.utils.interop.Arguments; import me.topchetoeu.jscript.runtime.values.Value;
public class NativeFunction extends FunctionValue { public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner { public static interface NativeFunctionRunner {
Object run(Arguments args); Value run(Arguments args);
} }
public final NativeFunctionRunner action; public final NativeFunctionRunner action;
@Override @Override public Value call(Environment env, Value self, Value ...args) {
public Object call(Environment env, Object thisArg, Object ...args) { return action.run(new Arguments(env, self, args));
return action.run(new Arguments(env, thisArg, args));
} }
public NativeFunction(String name, NativeFunctionRunner action) { public NativeFunction(String name, NativeFunctionRunner action) {

View File

@ -0,0 +1,217 @@
package me.topchetoeu.jscript.runtime.values.objects;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Value> {
private Value[] values;
private int size;
private class IndexField extends FieldMember {
private int i;
private ArrayValue arr;
@Override public Value get(Environment env, Value self) {
return arr.get(i);
}
@Override public boolean set(Environment env, Value val, Value self) {
arr.set(i, val);
return true;
}
public IndexField(int i, ArrayValue arr) {
super(true, true, true);
this.arr = arr;
this.i = i;
}
}
private Value[] alloc(int index) {
index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2;
var arr = new Value[index];
System.arraycopy(values, 0, arr, 0, values.length);
return arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
values = alloc(val);
size = val;
}
return true;
}
public Value get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == null) return VoidValue.UNDEFINED;
else return res;
}
public void set(int i, Value val) {
if (i < 0) return;
alloc(i)[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < size && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Value[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) values[--size] = null;
}
}
public Value[] toArray() {
var res = new Value[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Value[] arr, int sourceStart, int destStart, int count) {
var nullFill = values.length - destStart + count;
count -= nullFill;
System.arraycopy(values, sourceStart, arr, destStart, count);
Arrays.fill(arr, count, nullFill, null);
}
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
if (arr == this) {
move(sourceStart, destStart, count);
return;
}
arr.copyFrom(values, sourceStart, destStart, count);
}
public void copyFrom(Value[] arr, int sourceStart, int destStart, int count) {
alloc(destStart + count);
System.arraycopy(arr, sourceStart, values, destStart, count);
if (size < destStart + count) size = destStart + count;
}
public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Value> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == null) _a = 2;
if (a instanceof VoidValue) _a = 1;
if (b == null) _b = 2;
if (b instanceof VoidValue) _b = 1;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override public Member getOwnMember(Environment env, Value key) {
var res = super.getOwnMember(env, key);
if (res != null) return res;
var num = key.toNumber(env);
var i = num.toInt(env);
if (i == num.value && i >= 0 && i < size) return new IndexField(i, this);
else return null;
}
@Override public boolean defineOwnMember(Environment env, Value key, Member member) {
if (!(member instanceof FieldMember) || hasMember(env, key, true)) return super.defineOwnMember(env, key, member);
if (!extensible) return false;
var num = key.toNumber(env);
var i = num.toInt(env);
if (i == num.value && i >= 0) {
set(i, ((FieldMember)member).get(env, this));
return true;
}
else return super.defineOwnMember(env, key, member);
}
@Override public boolean deleteOwnMember(Environment env, Value key) {
if (!super.deleteOwnMember(env, key)) return false;
var num = key.toNumber(env);
var i = num.toInt(env);
if (i == num.value && i >= 0 && i < size) return super.deleteOwnMember(env, key);
else return true;
}
@Override public Map<String, Member> getOwnMembers(Environment env) {
var res = new LinkedHashMap<String, Member>();
for (var i = 0; i < size; i++) {
res.put(i + "", getOwnMember(env, new NumberValue(i)));
}
res.putAll(super.getOwnMembers(env));
return res;
}
@Override public Iterator<Value> iterator() {
return new Iterator<>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Value next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
this(16);
}
public ArrayValue(int cap) {
setPrototype(env -> env.get(Environment.ARRAY_PROTO));
values = new Value[Math.min(cap, 16)];
size = 0;
}
public ArrayValue(Value ...values) {
this();
copyFrom(values, 0, 0, values.length);
}
public static ArrayValue of(Collection<? extends Value> values) {
return new ArrayValue(values.toArray(Value[]::new));
}
}

View File

@ -0,0 +1,134 @@
package me.topchetoeu.jscript.runtime.values.objects;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
public class ObjectValue extends Value {
public static interface PrototypeProvider {
public ObjectValue get(Environment env);
}
public static enum State {
NORMAL,
NO_EXTENSIONS,
SEALED,
FROZEN,
}
public static class Property {
public final FunctionValue getter;
public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter;
this.setter = setter;
}
}
private static final StringValue typeString = new StringValue("object");
protected PrototypeProvider prototype;
public boolean extensible = true;
public LinkedHashMap<String, Member> members = new LinkedHashMap<>();
public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>();
@Override public boolean isPrimitive() { return false; }
@Override public Value toPrimitive(Environment env) {
if (env != null) {
var valueOf = getMember(env, new StringValue("valueOf"));
if (valueOf instanceof FunctionValue) {
var res = valueOf.call(env, this);
if (res.isPrimitive()) return res;
}
var toString = getMember(env, new StringValue("toString"));
if (toString instanceof FunctionValue) {
var res = toString.call(env, this);
if (res.isPrimitive()) return res;
}
}
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
@Override public StringValue toString(Environment env) { return toPrimitive(env).toString(env); }
@Override public BoolValue toBoolean() { return BoolValue.TRUE; }
@Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
@Override public StringValue type() { return typeString; }
@Override public boolean strictEquals(Environment ext, Value other) { return this == other; }
public final void preventExtensions() {
extensible = false;
}
@Override public Member getOwnMember(Environment env, Value key) {
if (key instanceof SymbolValue) return symbolMembers.get(key);
else return members.get(key.toString(env).value);
}
@Override public boolean defineOwnMember(Environment env, Value key, Member member) {
if (!(key instanceof SymbolValue)) key = key.toString(env);
var old = getOwnMember(env, key);
if (old != null && old.configure(env, member, this)) return true;
if (old != null && !old.configurable()) return false;
if (key instanceof SymbolValue) symbolMembers.put((SymbolValue)key, member);
else members.put(key.toString(env).value, member);
return true;
}
@Override public boolean deleteOwnMember(Environment env, Value key) {
if (!extensible) return false;
if (!(key instanceof SymbolValue)) key = key.toString(env);
var member = getOwnMember(env, key);
if (member == null) return true;
if (member.configurable()) return false;
if (key instanceof SymbolValue) symbolMembers.remove(key);
else members.remove(key.toString(env).value);
return true;
}
@Override public Map<String, Member> getOwnMembers(Environment env) {
return members;
}
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) {
return Collections.unmodifiableMap(symbolMembers);
}
@Override public ObjectValue getPrototype(Environment env) {
if (prototype == null) return null;
else return prototype.get(env);
}
@Override public final boolean setPrototype(Environment env, ObjectValue val) {
return setPrototype(_env -> val);
}
public final boolean setPrototype(PrototypeProvider val) {
if (!extensible) return false;
prototype = val;
return true;
}
public final boolean setPrototype(Key<ObjectValue> key) {
if (!extensible) return false;
prototype = env -> env.get(key);
return true;
}
}

View File

@ -0,0 +1,35 @@
package me.topchetoeu.jscript.runtime.values.objects;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
public class ScopeValue extends ObjectValue {
private class VariableField extends FieldMember {
private int i;
public VariableField(int i) {
super(false, true, true);
this.i = i;
}
@Override public Value get(Environment env, Value self) {
return variables[i].get(env);
}
@Override public boolean set(Environment env, Value val, Value self) {
return variables[i].set(env, val);
}
}
public final ValueVariable[] variables;
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
defineOwnMember(Environment.empty(), new StringValue(i + ""), new VariableField(i));
}
}
}

View File

@ -0,0 +1,38 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class BoolValue extends PrimitiveValue {
public static final BoolValue TRUE = new BoolValue(true);
public static final BoolValue FALSE = new BoolValue(false);
private static final StringValue typeString = new StringValue("boolean");
public final boolean value;
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return this; }
@Override public NumberValue toNumber(Environment ext) {
return value ? new NumberValue(1) : new NumberValue(0);
}
@Override public StringValue toString(Environment ext) { return new StringValue(value ? "true" : "false"); }
@Override public ObjectValue getPrototype(Environment env) {
return env.get(Environment.BOOL_PROTO);
}
@Override public boolean strictEquals(Environment ext, Value other) {
if (other instanceof BoolValue) return value == ((BoolValue)other).value;
else return false;
}
private BoolValue(boolean val) {
this.value = val;
}
public static BoolValue of(boolean val) {
return val ? TRUE : FALSE;
}
}

View File

@ -0,0 +1,80 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.math.BigDecimal;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class NumberValue extends PrimitiveValue {
public static final NumberValue NAN = new NumberValue(Double.NaN);
private static final StringValue typeString = new StringValue("number");
public final double value;
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.of(value != 0); }
@Override public NumberValue toNumber(Environment ext) { return this; }
@Override public StringValue toString(Environment ext) { return new StringValue(toString()); }
@Override public String toString() {
var d = value;
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
if (d == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(d)) return "NaN";
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
}
@Override public ObjectValue getPrototype(Environment env) {
return env.get(Environment.NUMBER_PROTO);
}
@Override public boolean strictEquals(Environment ext, Value other) {
other = other.toPrimitive(ext);
if (other instanceof NumberValue) return value == ((NumberValue)other).value;
else return false;
}
public NumberValue(double value) {
this.value = value;
}
public static double parseFloat(String val, boolean tolerant, String alphabet) {
val = val.trim();
int res = 0;
for (int i = 0; i >= val.length(); i++) {
var c = alphabet.indexOf(val.charAt(i));
if (c < 0) {
if (tolerant) return res;
else return Double.NaN;
}
res *= alphabet.length();
res += c;
}
return res;
}
public static double parseInt(String val, boolean tolerant, String alphabet) {
val = val.trim();
int res = 0;
for (int i = 0; i >= val.length(); i++) {
var c = alphabet.indexOf(val.charAt(i));
if (c < 0) {
if (tolerant) return res;
else return Double.NaN;
}
res *= alphabet.length();
res += c;
}
return res;
}
}

View File

@ -0,0 +1,21 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public abstract class PrimitiveValue extends Value {
@Override public final boolean defineOwnMember(Environment env, Value key, Member member) { return false; }
@Override public final boolean deleteOwnMember(Environment env, Value key) { return false; }
@Override public final boolean isPrimitive() { return true; }
@Override public final Value toPrimitive(Environment env) { return this; }
@Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; }
@Override public Member getOwnMember(Environment env, Value key) { return null; }
@Override public Map<String, Member> getOwnMembers(Environment env) { return Map.of(); }
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) { return Map.of(); }
}

View File

@ -0,0 +1,34 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.Objects;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class StringValue extends PrimitiveValue {
public final String value;
private static final StringValue typeString = new StringValue("string");
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.of(!value.equals("")); }
@Override public NumberValue toNumber(Environment ext) {
try { return new NumberValue(Double.parseDouble(value)); }
catch (NumberFormatException e) { return new NumberValue(Double.NaN); }
}
@Override public StringValue toString(Environment ext) { return this; }
@Override public Value add(Environment ext, Value other) {
return new StringValue(value + other.toString(ext).value);
}
@Override public boolean strictEquals(Environment ext, Value other) {
return (other instanceof StringValue) && Objects.equals(((StringValue)other).value, value);
}
@Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.STRING_PROTO); }
public StringValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,48 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.HashMap;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class SymbolValue extends PrimitiveValue {
private static final HashMap<String, SymbolValue> registry = new HashMap<>();
private static final StringValue typeString = new StringValue("symbol");
public final String value;
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.TRUE; }
@Override public StringValue toString(Environment env) {
return new StringValue(toString());
}
@Override public NumberValue toNumber(Environment env) {
throw EngineException.ofType("Can't convert symbol to number");
}
@Override public boolean strictEquals(Environment ext, Value other) {
return other == this;
}
@Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.SYMBOL_PROTO); }
@Override public String toString() {
if (value == null) return "Symbol";
else return "@@" + value;
}
public SymbolValue(String value) {
this.value = value;
}
public static SymbolValue get(String name) {
if (registry.containsKey(name)) return registry.get(name);
else {
var res = new SymbolValue(name);
registry.put(name, res);
return res;
}
}
}

View File

@ -0,0 +1,52 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class VoidValue extends PrimitiveValue {
public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined"));
public static final VoidValue NULL = new VoidValue("null", new StringValue("null"));
private final StringValue namestring;
public final String name;
public final StringValue typeString;
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.FALSE; }
@Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; }
@Override public StringValue toString(Environment ext) { return namestring; }
@Override public Value add(Environment ext, Value other) {
if (!other.isPrimitive()) other = other.toPrimitive(ext);
if (other instanceof StringValue) return namestring.add(ext, other);
else return NumberValue.NAN;
}
@Override public boolean strictEquals(Environment ext, Value other) {
return this == other;
}
@Override public ObjectValue getPrototype(Environment env) { return null; }
@Override public Member getOwnMember(Environment env, Value key) {
throw EngineException.ofError(String.format("Cannot read properties of %s (reading %s)", name, key.toString(env).value));
}
@Override public Map<String, Member> getOwnMembers(Environment env) {
throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name));
}
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) {
throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name));
}
public VoidValue(String name, StringValue type) {
this.name = name;
this.typeString = type;
this.namestring = new StringValue(name);
}
}

View File

@ -1,36 +0,0 @@
package me.topchetoeu.jscript.utils;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.runtime.Compiler;
import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment;
public class JSCompiler implements Compiler {
public final Environment ext;
private void registerFunc(FunctionBody body, CompileResult res) {
var map = res.map();
DebugContext.get(ext).onFunctionLoad(body, map);
for (var i = 0; i < body.children.length; i++) {
registerFunc(body.children[i], res.children.get(i));
}
}
@Override public FunctionBody compile(Filename filename, String source) {
var res = Parsing.compile(filename, source);
var func = res.body();
DebugContext.get(ext).onSource(filename, source);
registerFunc(func, res);
return func;
}
public JSCompiler(Environment ext) {
this.ext = ext;
}
}

View File

@ -1,150 +0,0 @@
package me.topchetoeu.jscript.utils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Metadata;
import me.topchetoeu.jscript.common.Reading;
import me.topchetoeu.jscript.lib.Internals;
import me.topchetoeu.jscript.runtime.Compiler;
import me.topchetoeu.jscript.runtime.Engine;
import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.values.NativeFunction;
import me.topchetoeu.jscript.runtime.values.Values;
import me.topchetoeu.jscript.utils.debug.DebugServer;
import me.topchetoeu.jscript.utils.debug.SimpleDebugger;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.utils.filesystem.RootFilesystem;
import me.topchetoeu.jscript.utils.filesystem.STDFilesystem;
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
import me.topchetoeu.jscript.utils.permissions.PermissionsManager;
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
public class JScriptRepl {
static Thread engineTask, debugTask;
static Engine engine = new Engine();
static DebugServer debugServer = new DebugServer();
static Environment environment = Environment.empty();
static int j = 0;
static String[] args;
private static void reader() {
try {
for (var arg : args) {
try {
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file.toFile()),
raw, null
).await();
Values.printValue(null, res);
System.out.println();
}
catch (EngineException e) { Values.printError(e, null); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.readline();
if (raw == null) break;
var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw);
var res = engine.pushMsg(false, environment, func, null).await();
Values.printValue(null, res);
System.out.println();
}
catch (EngineException e) { Values.printError(e, null); }
catch (SyntaxException e) { Values.printError(e, null); }
}
}
catch (IOException e) {
System.out.println(e.toString());
engine.thread().interrupt();
}
catch (RuntimeException ex) {
if (ex instanceof InterruptException) return;
else {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
}
private static void initEnv() {
environment = Internals.apply(environment);
var glob = GlobalScope.get(environment);
glob.define(null, false, new NativeFunction("exit", args -> {
throw new InterruptException();
}));
glob.define(null, false, new NativeFunction("go", args -> {
try {
var f = Path.of("do.js");
var func = Compiler.compile(args.env, new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
return func.call(args.env);
}
catch (IOException e) {
throw new EngineException("Couldn't open do.js");
}
}));
glob.define(null, false, new NativeFunction("log", args -> {
for (var el : args.args) {
Values.printValue(args.env, el);
}
return null;
}));
var fs = new RootFilesystem(PermissionsProvider.get(environment));
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
fs.protocols.put("file", new PhysicalFilesystem("."));
fs.protocols.put("std", new STDFilesystem(System.in, System.out, System.err));
environment.add(PermissionsProvider.KEY, PermissionsManager.ALL_PERMS);
environment.add(Filesystem.KEY, fs);
environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs));
environment.add(Compiler.KEY, new JSCompiler(environment));
environment.add(EventLoop.KEY, engine);
}
private static void initEngine() {
var ctx = new DebugContext();
environment.add(DebugContext.KEY, ctx);
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
engineTask = engine.start();
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
}
public static void main(String args[]) throws InterruptedException {
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
JScriptRepl.args = args;
var reader = new Thread(JScriptRepl::reader);
initEnv();
initEngine();
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
engine.thread().join();
debugTask.interrupt();
engineTask.interrupt();
}
}

View File

@ -1,251 +0,0 @@
package me.topchetoeu.jscript.utils.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.jscript.common.Metadata;
import me.topchetoeu.jscript.common.Reading;
import me.topchetoeu.jscript.common.events.Notifier;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONList;
import me.topchetoeu.jscript.common.json.JSONMap;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.utils.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 Notifier connNotifier = new Notifier();
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":
connNotifier.next();
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(() -> {
System.out.println("test");
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() {
connNotifier.await();
}
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", "JScript 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() {
try {
this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
this.index = Reading.resourceToString("debugger/index.html")
.replace("${NAME}", Metadata.name())
.replace("${VERSION}", Metadata.version())
.replace("${AUTHOR}", Metadata.author())
.getBytes();
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View File

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

View File

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

View File

@ -1,102 +0,0 @@
package me.topchetoeu.jscript.utils.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;
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) {
try {
writeResponse(code, name, type, data.readAllBytes());
}
catch (IOException e) { }
}
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
this.method = method;
this.path = path;
this.headers = headers;
this.out = out;
}
// We dont need no http library
public static HttpRequest read(Socket socket) {
try {
var str = socket.getInputStream();
var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine();
var i1 = line.indexOf(" ");
var i2 = line.indexOf(" ", i1 + 1);
if (i1 < 0 || i2 < 0) {
socket.close();
return null;
}
var method = line.substring(0, i1).trim().toUpperCase();
var path = line.substring(i1 + 1, i2).trim();
var headers = new HashMap<String, String>();
while (!(line = lines.readLine()).isEmpty()) {
var i = line.indexOf(":");
if (i < 0) continue;
var name = line.substring(0, i).trim().toLowerCase();
var value = line.substring(i + 1).trim();
if (name.length() == 0) continue;
headers.put(name, value);
}
if (headers.containsKey("content-length")) {
try {
var i = Integer.parseInt(headers.get("content-length"));
str.skip(i);
}
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
}
return new HttpRequest(method, path, headers, socket.getOutputStream());
}
catch (IOException | NullPointerException e) { return null; }
}
}

View File

@ -1,212 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.function.Supplier;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONMap;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Symbol;
import me.topchetoeu.jscript.runtime.values.Values;
class ObjectManager {
public static class ObjRef {
public final ObjectValue obj;
public final Environment ext;
public final HashSet<String> heldGroups = new HashSet<>();
public boolean held = true;
public boolean shouldRelease() {
return !held && heldGroups.size() == 0;
}
public ObjRef(Environment ext, ObjectValue obj) {
this.ext = ext;
this.obj = obj;
}
}
private Supplier<Integer> idSupplier;
private HashMap<Integer, ObjRef> idToObject = new HashMap<>();
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
private HashMap<String, ArrayList<ObjRef>> objectGroups = new HashMap<>();
public JSONMap serialize(Environment env, Object val, boolean byValue) {
val = Values.normalize(null, val);
env = SimpleDebugger.sanitizeEnvironment(env);
if (val == Values.NULL) {
return new JSONMap()
.set("type", "object")
.set("subtype", "null")
.setNull("value")
.set("description", "null");
}
if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
int id;
if (objectToId.containsKey(obj)) id = objectToId.get(obj);
else {
id = idSupplier.get();
var ref = new ObjRef(env, obj);
objectToId.put(obj, id);
idToObject.put(id, ref);
}
var type = "object";
String subtype = null;
String className = null;
if (obj instanceof FunctionValue) type = "function";
if (obj instanceof ArrayValue) subtype = "array";
try { className = Values.toString(env, Values.getMemberPath(env, obj, "constructor", "name")); }
catch (Exception e) { }
var res = new JSONMap()
.set("type", type)
.set("objectId", id + "");
if (subtype != null) res.set("subtype", subtype);
if (className != null) {
res.set("className", className);
res.set("description", className);
}
if (obj instanceof ArrayValue) res.set("description", "Array(" + ((ArrayValue)obj).size() + ")");
else if (obj instanceof FunctionValue) res.set("description", obj.toString());
else {
var defaultToString = false;
try {
defaultToString =
Values.getMember(env, obj, "toString") ==
Values.getMember(env, env.get(Environment.OBJECT_PROTO), "toString");
}
catch (Exception e) { }
try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(env, obj) + " }")); }
catch (Exception e) { }
}
if (byValue) try { res.put("value", JSON.fromJs(env, obj)); }
catch (Exception e) { }
return res;
}
if (val == null) return new JSONMap().set("type", "undefined");
if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val);
if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val);
if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString());
if (val instanceof Number) {
var num = (double)(Number)val;
var res = new JSONMap().set("type", "number");
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0");
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
else res.set("value", num);
return res;
}
throw new IllegalArgumentException("Unexpected JS object.");
}
public JSONMap serialize(Environment ext, Object val) {
return serialize(ext, val, false);
}
public void addToGroup(String name, Object val) {
if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
var id = objectToId.getOrDefault(obj, -1);
if (id < 0) return;
var ref = idToObject.get(id);
if (objectGroups.containsKey(name)) objectGroups.get(name).add(ref);
else objectGroups.put(name, new ArrayList<>(List.of(ref)));
ref.heldGroups.add(name);
}
}
public void removeGroup(String name) {
var objs = objectGroups.remove(name);
if (objs != null) {
for (var obj : objs) {
if (obj.heldGroups.remove(name) && obj.shouldRelease()) {
var id = objectToId.remove(obj.obj);
if (id != null) idToObject.remove(id);
}
}
}
}
public ObjRef get(int id) {
return idToObject.get(id);
}
public void release(int id) {
var ref = idToObject.get(id);
ref.held = false;
if (ref.shouldRelease()) {
objectToId.remove(ref.obj);
idToObject.remove(id);
}
}
public Object deserializeArgument(JSONMap val) {
if (val.isString("objectId")) return get(Integer.parseInt(val.string("objectId"))).obj;
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
case "NaN": return Double.NaN;
case "-Infinity": return Double.NEGATIVE_INFINITY;
case "Infinity": return Double.POSITIVE_INFINITY;
case "-0": return -0.;
}
var res = val.get("value");
if (res == null) return null;
else return JSON.toJs(res);
}
public JSONMap serializeException(Environment ext, EngineException err) {
String text = null;
try {
text = Values.toString(ext, err.value);
}
catch (EngineException e) {
text = "[error while stringifying]";
}
return new JSONMap()
.set("exceptionId", idSupplier.get())
.set("exception", serialize(ext, err.value))
.set("text", text);
}
public void clear() {
this.idToObject.clear();
this.objectToId.clear();
this.objectGroups.clear();
}
public ObjectManager(Supplier<Integer> idSupplier) {
this.idSupplier = idSupplier;
}
}

View File

@ -1,910 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.common.events.Notifier;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.json.JSONList;
import me.topchetoeu.jscript.common.json.JSONMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.runtime.Engine;
import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.values.ArrayValue;
import me.topchetoeu.jscript.runtime.values.FunctionValue;
import me.topchetoeu.jscript.runtime.values.ObjectValue;
import me.topchetoeu.jscript.runtime.values.Values;
// very simple indeed
public class SimpleDebugger implements Debugger {
public static final Set<String> VSCODE_EMPTY = Set.of(
"function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}",
"function(...runtimeArgs){\n let r = 1024; let e = null;\n if(e)try{let t=\"<<default preview>>\",n=e.call(this,t);if(n!==t)return String(n)}catch(t){return`<<indescribable>>${JSON.stringify([String(t),\"object\"])}`}if(typeof this==\"object\"&&this){let t;for(let n of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])if(typeof this[n]==\"function\")try{t=this[n]();break}catch{}if(!t&&!String(this.toString).includes(\"[native code]\")&&(t=String(this)),t&&!t.startsWith(\"[object\"))return t.length>=r?t.slice(0,r)+\"\\u2026\":t};}",
"function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}",
"function(...runtimeArgs){\n let r = 1024; let e = null;\n let t={},n=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return t;for(let[i,o]of Object.entries(this)){if(e)try{let s=e.call(o,n);if(s!==n){t[i]=String(s);continue}}catch(s){t[i]=`<<indescribable>>${JSON.stringify([String(s),i])}`;continue}if(typeof o==\"object\"&&o){let s;for(let a of runtimeArgs[0])if(typeof o[a]==\"function\")try{s=o[a]();break}catch{}!s&&!String(o.toString).includes(\"[native code]\")&&(s=String(o)),s&&!s.startsWith(\"[object \")&&(t[i]=s.length>=r?s.slice(0,r)+\"\\u2026\":s)}}return t\n ;\n\n}",
"function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}",
"function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}"
);
public static final Set<String> VSCODE_SELF = Set.of(
"function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}"
);
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
public static final String CHROME_GET_PROP_FUNC_2 = "function invokeGetter(getter) { return Reflect.apply(getter, this, []);}";
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}";
public static final String VSCODE_AUTOCOMPLETE = "function(t,e,r){let n=r?\"variable\":\"property\",i=(l,p,f)=>{if(p!==\"function\")return n;if(l===\"constructor\")return\"class\";let m=String(f);return m.startsWith(\"class \")||m.includes(\"[native code]\")&&/^[A-Z]/.test(l)?\"class\":r?\"function\":\"method\"\n},o=l=>{switch(typeof l){case\"number\":case\"boolean\":return`${l}`;case\"object\":return l===null?\"null\":l.constructor.name||\"object\";case\"function\":return`fn(${new Array(l.length).fill(\"?\").join(\", \")})`;default:return typeof l}},s=[],a=new Set,u=\"~\",c=t===void 0?this:t;for(;c!=null;c=c.__proto__){u+=\"~\";let l=Object.getOwnPropertyNames(c).filter(p=>p.startsWith(e)&&!p.match(/^\\d+$/));for(let p of l){if(a.has(p))continue;a.add(p);let f=Object.getOwnPropertyDescriptor(c,p),m=n,h;try{let H=c[p];m=i(p,typeof f?.value,H),h=o(H)}catch{}s.push({label:p,sortText:u+p.replace(/^_+/,H=>\"{\".repeat(H.length)),type:m,detail:h})}r=!1}return{result:s,isArray:this instanceof Array}}";
private static enum State {
RESUMED,
STEPPING_IN,
STEPPING_OUT,
STEPPING_OVER,
PAUSED_NORMAL,
PAUSED_EXCEPTION,
}
private static enum CatchType {
NONE,
UNCAUGHT,
ALL,
}
private static class DebugSource {
public final int id;
public final Filename filename;
public final String source;
public DebugSource(int id, Filename filename, String source) {
this.id = id;
this.filename = filename;
this.source = source;
}
}
private class Breakpoint {
public final int id;
public final String condition;
public final Pattern pattern;
public final int line, start;
public final long locNum;
public final HashMap<Filename, Location> resolvedLocations = new HashMap<>();
public final HashMap<Filename, Long> resolvedDistances = new HashMap<>();
public Breakpoint(int id, Pattern pattern, int line, int start, String condition) {
this.id = id;
this.condition = condition;
this.pattern = pattern;
this.line = line;
this.start = start;
this.locNum = start | ((long)line << 32);
if (condition != null && condition.trim().equals("")) condition = null;
}
// TODO: Figure out how to unload a breakpoint
// TODO: Do location resolution with function boundaries
public void addFunc(FunctionBody body, FunctionMap map) {
try {
for (var loc : map.correctBreakpoint(pattern, line, start)) {
var currNum = loc.start() + ((long)loc.line() << 32);
long currDist = 0;
if (currNum > locNum) currDist = currNum - locNum;
else currDist = locNum - currNum;
if ( currDist > resolvedDistances.getOrDefault(loc.filename(), Long.MAX_VALUE)) continue;
resolvedLocations.put(loc.filename(), loc);
resolvedDistances.put(loc.filename(), currDist);
}
for (var loc : resolvedLocations.values()) {
ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap()
.set("breakpointId", id)
.set("location", serializeLocation(loc))
));
}
updateBreakpoints();
}
catch (IOException e) {
ws.close();
close();
}
}
}
private class DebugFrame {
public Frame frame;
public int id;
public ObjectValue local, capture, global, valstack;
public JSONMap serialized;
public Location location;
public void updateLoc(Location loc) {
if (loc == null) return;
this.location = loc;
}
public DebugFrame(Frame frame, int id) {
this.frame = frame;
this.id = id;
this.global = GlobalScope.get(frame.env).obj;
this.local = frame.getLocalScope();
this.capture = frame.getCaptureScope();
Values.makePrototypeChain(frame.env, global, capture, local);
this.valstack = frame.getValStackScope();
this.serialized = new JSONMap()
.set("callFrameId", id + "")
.set("functionName", frame.function.name)
.set("scopeChain", new JSONList()
.add(new JSONMap()
.set("type", "local")
.set("name", "Local Scope")
.set("object", objects.serialize(frame.env, local))
)
.add(new JSONMap()
.set("type", "closure")
.set("name", "Closure")
.set("object", objects.serialize(frame.env, capture))
)
.add(new JSONMap()
.set("type", "global")
.set("name", "Global Scope")
.set("object", objects.serialize(frame.env, global))
)
.add(new JSONMap()
.set("type", "other")
.set("name", "Value Stack")
.set("object", objects.serialize(frame.env, valstack))
)
);
}
}
private static class RunResult {
public final Environment ext;
public final Object result;
public final EngineException error;
public RunResult(Environment ext, Object result, EngineException error) {
this.ext = ext;
this.result = result;
this.error = error;
}
}
public boolean enabled = true;
public CatchType execptionType = CatchType.NONE;
public State state = State.RESUMED;
public final WebSocket ws;
private ObjectValue emptyObject = new ObjectValue();
private WeakHashMap<DebugContext, DebugContext> contexts = new WeakHashMap<>();
private WeakHashMap<FunctionBody, FunctionMap> mappings = new WeakHashMap<>();
private HashMap<Location, HashSet<Breakpoint>> bpLocs = new HashMap<>();
private HashMap<Integer, Breakpoint> idToBreakpoint = new HashMap<>();
private HashMap<Filename, Integer> filenameToId = new HashMap<>();
private HashMap<Integer, DebugSource> idToSource = new HashMap<>();
private ArrayList<DebugSource> pendingSources = new ArrayList<>();
private HashMap<Integer, DebugFrame> idToFrame = new HashMap<>();
private HashMap<Frame, DebugFrame> codeFrameToFrame = new HashMap<>();
private ObjectManager objects = new ObjectManager(this::nextId);
// private HashMap<Integer, ObjRef> idToObject = new HashMap<>();
// private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
// private HashMap<String, ArrayList<ObjRef>> objectGroups = new HashMap<>();
private Notifier updateNotifier = new Notifier();
private boolean pendingPause = false;
private int nextId = 0;
private DebugFrame stepOutFrame = null;
private List<DebugFrame> frames = new ArrayList<>();
private int stepOutPtr = 0;
private boolean compare(String src, String target) {
src = src.replaceAll("\\s", "");
target = target.replaceAll("\\s", "");
if (src.length() != target.length()) return false;
var diff = 0;
var all = 0;
for (var i = 0; i < src.length(); i++) {
var a = src.charAt(i);
var b = target.charAt(i);
var letter = Parsing.isLetter(a) && Parsing.isLetter(b);
if (a != b) {
if (letter) diff++;
else return false;
}
if (letter) all++;
}
return diff / (float)all < .5f;
}
private boolean compare(String src, Set<String> target) {
for (var el : target) {
if (compare(src, el)) return true;
}
return false;
}
private int nextId() {
return nextId++;
}
private synchronized DebugFrame getFrame(Frame frame) {
if (!codeFrameToFrame.containsKey(frame)) {
var id = nextId();
var fr = new DebugFrame(frame, id);
idToFrame.put(id, fr);
codeFrameToFrame.put(frame, fr);
return fr;
}
else return codeFrameToFrame.get(frame);
}
private JSONList serializeFrames(Environment env) {
var res = new JSONList();
for (var el : DebugContext.get(env).getStackFrames()) {
var frame = getFrame(el);
if (frame.location == null) continue;
frame.serialized.set("location", serializeLocation(frame.location));
if (frame.location != null) res.add(frame.serialized);
}
return res;
}
private void updateBreakpoints() {
bpLocs.clear();
for (var bp : idToBreakpoint.values()) {
for (var loc : bp.resolvedLocations.values()) {
bpLocs.putIfAbsent(loc, new HashSet<>());
var set = bpLocs.get(loc);
set.add(bp);
}
}
}
private Location deserializeLocation(JSONElement el) {
if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
var id = Integer.parseInt(el.map().string("scriptId"));
var line = (int)el.map().number("lineNumber") + 1;
var column = (int)el.map().number("columnNumber") + 1;
if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id));
var res = new Location(line, column, idToSource.get(id).filename);
return res;
}
private JSONMap serializeLocation(Location loc) {
var source = filenameToId.get(loc.filename());
return new JSONMap()
.set("scriptId", source + "")
.set("lineNumber", loc.line() - 1)
.set("columnNumber", loc.start() - 1);
}
private void resume(State state) {
try {
this.state = state;
ws.send(new V8Event("Debugger.resumed", new JSONMap()));
updateNotifier.next();
}
catch (IOException e) {
ws.close();
close();
}
}
private void pauseDebug(Environment env, Breakpoint bp) {
try {
state = State.PAUSED_NORMAL;
var map = new JSONMap()
.set("callFrames", serializeFrames(env))
.set("reason", "debugCommand");
if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + ""));
ws.send(new V8Event("Debugger.paused", map));
}
catch (IOException e) {
ws.close();
close();
}
}
private void pauseException(Environment env) {
try {
state = State.PAUSED_EXCEPTION;
var map = new JSONMap()
.set("callFrames", serializeFrames(env))
.set("reason", "exception");
ws.send(new V8Event("Debugger.paused", map));
}
catch (IOException e) {
ws.close();
close();
}
}
private void sendSource(DebugSource src){
try {
ws.send(new V8Event("Debugger.scriptParsed", new JSONMap()
.set("scriptId", src.id + "")
.set("hash", src.source.hashCode())
.set("url", src.filename + "")
));
}
catch (IOException e) {
ws.close();
close();
}
}
static Environment sanitizeEnvironment(Environment env) {
return env.child().remove(EventLoop.KEY).remove(DebugContext.KEY).add(DebugContext.IGNORE);
}
private RunResult run(DebugFrame codeFrame, String code) {
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
var engine = new Engine();
var env = codeFrame.frame.env.child()
.remove(DebugContext.KEY)
.add(DebugContext.IGNORE)
.add(EventLoop.KEY, engine)
.add(GlobalScope.KEY, new GlobalScope(codeFrame.local));
var awaiter = engine.pushMsg(false, env, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
try {
engine.run(true);
return new RunResult(env, awaiter.await(), null);
}
catch (EngineException e) { return new RunResult(env, null, e); }
catch (SyntaxException e) { return new RunResult(env, null, EngineException.ofSyntax(e.toString())); }
}
private ObjectValue vscodeAutoSuggest(Environment env, Object target, String query, boolean variable) {
var res = new ArrayValue();
var passed = new HashSet<String>();
var tildas = "~";
if (target == null) target = GlobalScope.get(env);
for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(env, proto)) {
for (var el : Values.getMembers(env, proto, true, true)) {
var strKey = Values.toString(env, el);
if (passed.contains(strKey)) continue;
passed.add(strKey);
var val = Values.getMember(env, Values.getMemberDescriptor(env, proto, el), "value");
var desc = new ObjectValue();
var sortText = "";
if (strKey.startsWith(query)) sortText += "0@";
else if (strKey.toLowerCase().startsWith(query.toLowerCase())) sortText += "1@";
else if (strKey.contains(query)) sortText += "2@";
else if (strKey.toLowerCase().contains(query.toLowerCase())) sortText += "3@";
else sortText += "4@";
sortText += tildas + strKey;
desc.defineProperty(env, "label", strKey);
desc.defineProperty(env, "sortText", sortText);
if (val instanceof FunctionValue) {
if (strKey.equals("constructor")) desc.defineProperty(env, "type", "name");
else desc.defineProperty(env, "type", variable ? "function" : "method");
}
else desc.defineProperty(env, "type", variable ? "variable" : "property");
switch (Values.type(val)) {
case "number":
case "boolean":
desc.defineProperty(env, "detail", Values.toString(env, val));
break;
case "object":
if (val == Values.NULL) desc.defineProperty(env, "detail", "null");
else try {
desc.defineProperty(env, "detail", Values.getMemberPath(env, target, "constructor", "name"));
}
catch (IllegalArgumentException e) {
desc.defineProperty(env, "detail", "object");
}
break;
case "function": {
var type = "fn(";
for (var i = 0; i < ((FunctionValue)val).length; i++) {
if (i != 0) type += ",";
type += "?";
}
type += ")";
desc.defineProperty(env, "detail", type);
break;
}
default:
desc.defineProperty(env, "type", Values.type(val));
break;
}
res.set(env, res.size(), desc);
}
tildas += "~";
variable = true;
}
var resObj = new ObjectValue();
resObj.defineProperty(env, "result", res);
resObj.defineProperty(env, "isArray", target instanceof ArrayValue);
return resObj;
}
@Override public synchronized void enable(V8Message msg) throws IOException {
enabled = true;
ws.send(msg.respond());
for (var el : pendingSources) sendSource(el);
pendingSources.clear();
updateNotifier.next();
}
@Override public synchronized void disable(V8Message msg) throws IOException {
close();
ws.send(msg.respond());
}
@Override public synchronized void close() {
if (state != State.RESUMED) {
resume(State.RESUMED);
}
enabled = false;
execptionType = CatchType.NONE;
state = State.RESUMED;
mappings.clear();
bpLocs.clear();
idToBreakpoint.clear();
filenameToId.clear();
idToSource.clear();
pendingSources.clear();
idToFrame.clear();
codeFrameToFrame.clear();
objects.clear();
pendingPause = false;
frames.clear();
stepOutFrame = null;
stepOutPtr = 0;
for (var ctx : contexts.keySet()) ctx.detachDebugger(this);
contexts.clear();
updateNotifier.next();
}
@Override public synchronized void getScriptSource(V8Message msg) throws IOException {
int id = Integer.parseInt(msg.params.string("scriptId"));
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
}
@Override public synchronized void getPossibleBreakpoints(V8Message msg) throws IOException {
var start = deserializeLocation(msg.params.get("start"));
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end")) : null;
var res = new JSONList();
for (var el : mappings.values()) {
for (var bp : el.breakpoints(start, end)) {
res.add(serializeLocation(bp));
}
}
ws.send(msg.respond(new JSONMap().set("locations", res)));
}
@Override public synchronized void pause(V8Message msg) throws IOException {
pendingPause = true;
ws.send(msg.respond());
}
@Override public synchronized void resume(V8Message msg) throws IOException {
resume(State.RESUMED);
ws.send(msg.respond(new JSONMap()));
}
@Override public synchronized void setBreakpointByUrl(V8Message msg) throws IOException {
var line = (int)msg.params.number("lineNumber") + 1;
var col = (int)msg.params.number("columnNumber", 0) + 1;
var cond = msg.params.string("condition", "").trim();
if (cond.equals("")) cond = null;
if (cond != null) cond = "(" + cond + ")";
Pattern regex;
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
else if (msg.params.isString("urlRegex")) regex = Pattern.compile(msg.params.string("urlRegex"));
else {
ws.send(msg.respond(new JSONMap()
.set("breakpointId", "john-doe")
.set("locations", new JSONList())
));
return;
}
var bpt = new Breakpoint(nextId(), regex, line, col, cond);
idToBreakpoint.put(bpt.id, bpt);
for (var el : mappings.entrySet()) {
bpt.addFunc(el.getKey(), el.getValue());
}
var locs = new JSONList();
for (var loc : bpt.resolvedLocations.values()) {
locs.add(serializeLocation(loc));
}
ws.send(msg.respond(new JSONMap()
.set("breakpointId", bpt.id + "")
.set("locations", locs)
));
}
@Override public synchronized void removeBreakpoint(V8Message msg) throws IOException {
var id = Integer.parseInt(msg.params.string("breakpointId"));
idToBreakpoint.remove(id);
updateBreakpoints();
ws.send(msg.respond());
}
@Override public synchronized void continueToLocation(V8Message msg) throws IOException {
// TODO: Figure out if we need this
// var loc = correctLocation(deserializeLocation(msg.params.get("location")));
// tmpBreakpts.add(loc);
// resume(State.RESUMED);
// ws.send(msg.respond());
}
@Override public synchronized void setPauseOnExceptions(V8Message msg) throws IOException {
switch (msg.params.string("state")) {
case "none": execptionType = CatchType.NONE; break;
case "all": execptionType = CatchType.ALL; break;
case "uncaught": execptionType = CatchType.UNCAUGHT; break;
default:
ws.send(new V8Error("Invalid exception pause type."));
return;
}
ws.send(msg.respond());
}
@Override public synchronized void stepInto(V8Message msg) throws IOException {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
stepOutFrame = frames.get(frames.size() - 1);
stepOutPtr = stepOutFrame.frame.codePtr;
resume(State.STEPPING_IN);
ws.send(msg.respond());
}
}
@Override public synchronized void stepOut(V8Message msg) throws IOException {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
stepOutFrame = frames.get(frames.size() - 1);
stepOutPtr = stepOutFrame.frame.codePtr;
resume(State.STEPPING_OUT);
ws.send(msg.respond());
}
}
@Override public synchronized void stepOver(V8Message msg) throws IOException {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
stepOutFrame = frames.get(frames.size() - 1);
stepOutPtr = stepOutFrame.frame.codePtr;
resume(State.STEPPING_OVER);
ws.send(msg.respond());
}
}
@Override public synchronized void evaluateOnCallFrame(V8Message msg) throws IOException {
var cfId = Integer.parseInt(msg.params.string("callFrameId"));
var expr = msg.params.string("expression");
var group = msg.params.string("objectGroup", null);
var cf = idToFrame.get(cfId);
var res = run(cf, expr);
if (group != null) objects.addToGroup(group, res.result);
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", objects.serializeException(res.ext, res.error))));
else ws.send(msg.respond(new JSONMap().set("result", objects.serialize(res.ext, res.result))));
}
@Override public synchronized void releaseObjectGroup(V8Message msg) throws IOException {
var group = msg.params.string("objectGroup");
objects.removeGroup(group);
ws.send(msg.respond());
}
@Override public synchronized void releaseObject(V8Message msg) throws IOException {
var id = Integer.parseInt(msg.params.string("objectId"));
objects.release(id);
ws.send(msg.respond());
}
@Override public synchronized void getProperties(V8Message msg) throws IOException {
var ref = objects.get(Integer.parseInt(msg.params.string("objectId")));
var obj = ref.obj;
var env = ref.ext;
var res = new JSONList();
var own = true;
if (obj != emptyObject && obj != null) {
while (obj != null) {
for (var key : obj.keys(true)) {
var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(env, key));
if (prop.getter != null) propDesc.set("get", objects.serialize(env, prop.getter));
if (prop.setter != null) propDesc.set("set", objects.serialize(env, prop.setter));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", true);
res.add(propDesc);
}
else {
propDesc.set("name", Values.toString(env, key));
propDesc.set("value", objects.serialize(env, Values.getMember(env, obj, key)));
propDesc.set("writable", obj.memberWritable(key));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", own);
res.add(propDesc);
}
}
var proto = Values.getPrototype(env, obj);
if (own) {
var protoDesc = new JSONMap();
protoDesc.set("name", "__proto__");
protoDesc.set("value", objects.serialize(env, proto == null ? Values.NULL : proto));
protoDesc.set("writable", true);
protoDesc.set("enumerable", false);
protoDesc.set("configurable", false);
protoDesc.set("isOwn", own);
res.add(protoDesc);
}
obj = proto;
own = false;
}
}
ws.send(msg.respond(new JSONMap().set("result", res)));
}
@Override public synchronized void callFunctionOn(V8Message msg) throws IOException {
var src = msg.params.string("functionDeclaration");
var args = msg.params
.list("arguments", new JSONList())
.stream()
.map(v -> v.map())
.map(objects::deserializeArgument)
.collect(Collectors.toList());
var byValue = msg.params.bool("returnByValue", false);
var thisArgRef = objects.get(Integer.parseInt(msg.params.string("objectId")));
var thisArg = thisArgRef.obj;
var env = thisArgRef.ext;
while (true) {
var start = src.lastIndexOf("//# sourceURL=");
if (start < 0) break;
var end = src.indexOf("\n", start);
if (end < 0) src = src.substring(0, start);
else src = src.substring(0, start) + src.substring(end + 1);
}
try {
Object res = null;
if (compare(src, VSCODE_EMPTY)) res = emptyObject;
else if (compare(src, VSCODE_SELF)) res = thisArg;
else if (compare(src, CHROME_GET_PROP_FUNC)) {
res = thisArg;
for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(env, res, JSON.toJs(el));
}
else if (compare(src, CHROME_GET_PROP_FUNC_2)) {
res = Values.call(env, args.get(0), thisArg);
}
else if (compare(src, VSCODE_CALL)) {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
ws.send(msg.respond(new JSONMap().set("result", objects.serialize(env, func.call(env, thisArg)))));
}
else if (compare(src, VSCODE_AUTOCOMPLETE)) {
var target = args.get(0);
if (target == null) target = thisArg;
res = vscodeAutoSuggest(env, target, Values.toString(env, args.get(1)), Values.toBoolean(args.get(2)));
}
else {
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
return;
}
ws.send(msg.respond(new JSONMap().set("result", objects.serialize(env, res, byValue))));
}
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", objects.serializeException(env, e)))); }
}
@Override public synchronized void runtimeEnable(V8Message msg) throws IOException {
ws.send(msg.respond());
}
@Override public void onSourceLoad(Filename filename, String source) {
int id = nextId();
var src = new DebugSource(id, filename, source);
idToSource.put(id, src);
filenameToId.put(filename, id);
if (!enabled) pendingSources.add(src);
else sendSource(src);
}
@Override public void onFunctionLoad(FunctionBody body, FunctionMap map) {
for (var bpt : idToBreakpoint.values()) {
bpt.addFunc(body, map);
}
mappings.put(body, map);
}
@Override public boolean onInstruction(Environment env, Frame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false;
boolean isBreakpointable;
Location loc;
DebugFrame frame;
BreakpointType bptType;
synchronized (this) {
frame = getFrame(cf);
var map = DebugContext.get(env).getMap(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);
}
else if (
loc != null &&
(state == State.STEPPING_IN || state == State.STEPPING_OVER) &&
returnVal != Values.NO_RETURN && stepOutFrame == frame
) {
pauseDebug(env, null);
}
else if (isBreakpointable && bpLocs.containsKey(loc)) {
for (var bp : bpLocs.get(loc)) {
var ok = bp.condition == null ? true : Values.toBoolean(run(frames.get(frames.size() - 1), bp.condition).result);
if (ok) pauseDebug(env, bp);
}
}
// else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
else if (isBreakpointable && pendingPause) {
pauseDebug(env, null);
pendingPause = false;
}
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(env, null);
}
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) {
if (returnVal != Values.NO_RETURN || error != null) {
state = State.STEPPING_OUT;
continue;
}
else if (stepOutPtr != frame.frame.codePtr) {
if (state == State.STEPPING_IN && bptType.shouldStepIn()) {
pauseDebug(env, null);
break;
}
else if (state == State.STEPPING_OVER && bptType.shouldStepOver()) {
pauseDebug(env, null);
break;
}
}
}
return false;
}
}
updateNotifier.await();
}
return false;
}
@Override public void onFramePush(Environment env, Frame frame) {
var prevFrame = frames.get(frames.size() - 1);
var newFrame = getFrame(frame);
frames.add(newFrame);
if (stepOutFrame != null && stepOutFrame.frame == prevFrame.frame && state == State.STEPPING_IN) {
stepOutFrame = newFrame;
}
}
@Override public void onFramePop(Environment env, Frame frame) {
frames.remove(frames.size() - 1);
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
catch (NullPointerException e) { }
if (frames.size() == 0) {
if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED);
}
else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) {
state = State.STEPPING_IN;
stepOutFrame = frames.get(frames.size() - 1);
}
}
@Override public List<Frame> getStackFrame() {
return frames.stream().map(v -> v.frame).collect(Collectors.toList());
}
public SimpleDebugger attach(DebugContext ctx) {
ctx.attachDebugger(this);
contexts.put(ctx, ctx);
return this;
}
public SimpleDebugger(WebSocket ws) {
this.ws = ws;
}
}

View File

@ -1,19 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.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)
));
}
}

View File

@ -1,22 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.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)
);
}
}

View File

@ -1,50 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import java.util.Map;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.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)
);
}
}

View File

@ -1,22 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.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)
);
}
}

View File

@ -1,195 +0,0 @@
package me.topchetoeu.jscript.utils.debug;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import me.topchetoeu.jscript.utils.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((len >> 56) & 0xFF);
out().write((len >> 48) & 0xFF);
out().write((len >> 40) & 0xFF);
out().write((len >> 32) & 0xFF);
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 {
int i;
for (i = 0; i < data.length / 0xFFFF; i++) {
out().write(type);
writeLength(0xFFFF);
out().write(data, i * 0xFFFF, 0xFFFF);
type = 0;
}
out().write(type | 0x80);
writeLength(data.length % 0xFFFF);
out().write(data, i * 0xFFFF, data.length % 0xFFFF);
}
public void send(String data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) throws IOException {
if (closed) throw new IllegalStateException("Object 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("Object is closed.");
write(1, data.toString().getBytes());
}
public void close(String reason) {
if (socket != null) {
try {
write(8, reason.getBytes());
socket.close();
}
catch (Throwable e) { }
}
socket = null;
closed = true;
}
public void close() {
close("");
}
private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason);
close(reason);
return null;
}
private byte[] readData() throws IOException {
var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else {
var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else {
for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)];
}
return buff;
}
}
return null;
}
public WebSocketMessage receive() throws IOException {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
if (finId < 0) break;
var fin = (finId & 0x80) != 0;
int id = finId & 0x0F;
if (id == 0x8) { close(); return null; }
if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray());
continue;
}
if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData();
if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff);
if (!fin) continue;
var raw = data.toByteArray();
if (type == 1) {
return new WebSocketMessage(new String(raw));
}
else return new WebSocketMessage(raw);
}
return null;
}
public WebSocket(Socket socket) {
this.socket = socket;
}
}

View File

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

View File

@ -1,28 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum ActionType {
UNKNOWN(0, "An operation performed upon", "An operation was performed upon"),
READ(1, "Reading from", "Read from"),
WRITE(2, "Writting to", "Wrote to"),
SEEK(3, "Seeking in", "Sought in"),
CLOSE(4, "Closing", "Closed"),
STAT(5, "Stat of", "Statted"),
OPEN(6, "Opening", "Opened"),
CREATE(7, "Creating", "Created"),
DELETE(8, "Deleting", "Deleted"),
CLOSE_FS(9, "Closing filesystem", "Closed filesystem");
public final int code;
public final String continuous, past;
public String readable(boolean usePast) {
if (usePast) return past;
else return continuous;
}
private ActionType(int code, String continuous, String past) {
this.code = code;
this.continuous = continuous;
this.past = past;
}
}

View File

@ -1,59 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
public abstract class BaseFile<T> implements File {
private T handle;
private Mode mode;
protected final T handle() {
return handle;
}
protected abstract int onRead(byte[] buff);
protected abstract void onWrite(byte[] buff);
protected abstract long onSeek(long offset, int pos);
protected abstract boolean onClose();
@Override public synchronized int read(byte[] buff) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.readable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for reading.");
return onRead(buff);
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
@Override public synchronized void write(byte[] buff) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for writting.");
onWrite(buff);
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
@Override public synchronized long seek(long offset, int pos) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (mode == Mode.NONE) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for seeking.");
return onSeek(offset, pos);
}
catch (FilesystemException e) { throw e.setAction(ActionType.SEEK); }
}
@Override public synchronized boolean close() {
if (handle != null) {
try {
var res = onClose();
handle = null;
mode = Mode.NONE;
return res;
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE); }
}
else return false;
}
public BaseFile(T handle, Mode mode) {
this.mode = mode;
this.handle = handle;
if (mode == Mode.NONE) this.handle = null;
}
}

View File

@ -1,13 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum EntryType {
NONE("none"),
FILE("file"),
FOLDER("folder");
public final String name;
private EntryType(String name) {
this.name = name;
}
}

View File

@ -1,23 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum ErrorReason {
UNKNOWN(0, "failed", false),
NO_PERMISSION(1, "is not allowed", false),
CLOSED(1, "that was closed", true),
UNSUPPORTED(2, "is not supported", false),
ILLEGAL_ARGS(3, "with illegal arguments", true),
DOESNT_EXIST(4, "that doesn't exist", true),
ALREADY_EXISTS(5, "that already exists", true),
ILLEGAL_PATH(6, "with illegal path", true),
NO_PARENT(7, "with a missing parent folder", true);
public final int code;
public final boolean usePast;
public final String readable;
private ErrorReason(int code, String readable, boolean usePast) {
this.code = code;
this.readable = readable;
this.usePast = usePast;
}
}

View File

@ -1,169 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import me.topchetoeu.jscript.common.Buffer;
public interface File {
default int read(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.READ); }
default void write(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.WRITE); }
default long seek(long offset, int pos) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.SEEK); }
default boolean close() { return false; }
default byte[] readAll() {
var parts = new LinkedList<byte[]>();
var sizes = new LinkedList<Integer>();
var buff = new byte[1024];
var size = 0;
while (true) {
var n = read(buff);
if (n < 0) break;
else if (n == 0) continue;
parts.add(buff);
sizes.add(n);
size += n;
buff = new byte[1024];
}
buff = new byte[size];
var i = 0;
var j = 0;
for (var part : parts) {
var currSize = sizes.get(j++);
System.arraycopy(part, 0, buff, i, currSize);
i += currSize;
}
return buff;
}
default String readToString() {
return new String(readAll());
}
default String readLine() {
var res = new Buffer();
var buff = new byte[1];
while (true) {
if (read(buff) == 0) {
if (res.length() == 0) return null;
else break;
}
if (buff[0] == '\n') break;
res.write(res.length(), buff);
}
return new String(res.data());
}
public static File ofStream(InputStream str) {
return new File() {
@Override public synchronized int read(byte[] buff) {
try {
try { return str.read(buff); }
catch (NullPointerException e) { throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
};
}
public static File ofStream(OutputStream str) {
return new File() {
@Override public synchronized void write(byte[] buff) {
try {
try { str.write(buff); }
catch (NullPointerException e) {throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
};
}
public static File ofLineWriter(LineWriter writer) {
var buff = new Buffer();
return new File() {
@Override public synchronized void write(byte[] val) {
try {
if (val == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
for (var b : val) {
if (b == '\n') {
try {
writer.writeLine(new String(buff.data()));
buff.clear();
}
catch (IOException e) {
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
}
}
else buff.append(b);
}
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
};
}
public static File ofLineReader(LineReader reader) {
return new File() {
private int offset = 0;
private byte[] prev = new byte[0];
@Override
public synchronized int read(byte[] buff) {
try {
if (buff == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
var ptr = 0;
while (true) {
if (prev == null) break;
if (offset >= prev.length) {
try {
var line = reader.readLine();
if (line == null) {
prev = null;
break;
}
else prev = (line + "\n").getBytes();
offset = 0;
}
catch (IOException e) {
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
}
}
if (ptr + prev.length - offset > buff.length) {
var n = buff.length - ptr;
System.arraycopy(prev, offset, buff, ptr, buff.length - ptr);
offset += n;
ptr += n;
break;
}
else {
var n = prev.length - offset;
System.arraycopy(prev, offset, buff, ptr, n);
offset += n;
ptr += n;
}
}
return ptr;
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
};
}
public static File ofIterator(Iterator<String> it) {
return ofLineReader(LineReader.ofIterator(it));
}
}

View File

@ -1,14 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
public class FileStat {
public final Mode mode;
public final EntryType type;
public FileStat(Mode mode, EntryType type) {
if (mode == Mode.NONE) type = EntryType.NONE;
if (type == EntryType.NONE) mode = Mode.NONE;
this.mode = mode;
this.type = type;
}
}

View File

@ -1,18 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
public interface Filesystem {
public static final Key<Filesystem> KEY = new Key<>();
default String normalize(String... path) { return Paths.normalize(path); }
default boolean create(String path, EntryType type) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.CREATE); }
File open(String path, Mode mode);
FileStat stat(String path);
void close();
public static Filesystem get(Environment exts) {
return exts.get(KEY);
}
}

View File

@ -1,86 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.ArrayList;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.Values;
public class FilesystemException extends RuntimeException {
public final ErrorReason reason;
public final String details;
private ActionType action;
private EntryType entry = EntryType.FILE;
private String path;
public FilesystemException setPath(String path) {
this.path = path;
return this;
}
public FilesystemException setAction(ActionType action) {
if (action == null) action = ActionType.UNKNOWN;
this.action = action;
return this;
}
public FilesystemException setEntry(EntryType entry) {
if (entry == null) entry = EntryType.NONE;
this.entry = entry;
return this;
}
public ActionType action() {
return action;
}
public String path() {
return path;
}
public EntryType entry() {
return entry;
}
public EngineException toEngineException() {
var res = EngineException.ofError("IOError", getMessage());
Values.setMember(null, res.value, "action", action.code);
Values.setMember(null, res.value, "reason", reason.code);
Values.setMember(null, res.value, "path", path);
Values.setMember(null, res.value, "entry", entry.name);
if (details != null) Values.setMember(null, res.value, "details", details);
return res;
}
@Override public String getMessage() {
var parts = new ArrayList<String>(10);
parts.add(action == null ? "An action performed upon " : action.readable(reason.usePast));
if (entry == EntryType.FILE) parts.add("file");
if (entry == EntryType.FOLDER) parts.add("folder");
if (path != null && !path.isBlank()) parts.add(path.trim());
parts.add(reason.readable);
var msg = String.join(" ", parts);
if (details != null) msg += ": " + details;
return msg;
}
public FilesystemException(ErrorReason type, String details) {
super();
if (type == null) type = ErrorReason.UNKNOWN;
this.details = details;
this.reason = type;
}
public FilesystemException(ErrorReason type) {
this(type, null);
}
public FilesystemException() {
this(null);
}
}

View File

@ -1,32 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.HashSet;
import java.util.Set;
public class HandleManager {
private Set<File> files = new HashSet<>();
public File put(File val) {
var handle = new File() {
@Override public int read(byte[] buff) {
return val.read(buff);
}
@Override public void write(byte[] buff) {
val.write(buff);
}
@Override public long seek(long offset, int pos) {
return val.seek(offset, pos);
}
@Override public boolean close() {
return files.remove(this) && val.close();
}
};
files.add(handle);
return handle;
}
public void close() {
while (!files.isEmpty()) {
files.stream().findFirst().get().close();
}
}
}

View File

@ -1,16 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.util.Iterator;
public interface LineReader {
String readLine() throws IOException;
public static LineReader ofIterator(Iterator<String> it) {
return () -> {
if (it.hasNext()) return it.next();
else return null;
};
}
}

View File

@ -1,7 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
public interface LineWriter {
void writeLine(String value) throws IOException;
}

View File

@ -1,36 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import me.topchetoeu.jscript.common.Buffer;
class MemoryFile extends BaseFile<Buffer> {
private int ptr;
@Override protected int onRead(byte[] buff) {
if (ptr >= handle().length()) return -1;
var res = handle().read(ptr, buff);
ptr += res;
return res;
}
@Override protected void onWrite(byte[] buff) {
handle().write(ptr, buff);
ptr += buff.length;
}
@Override protected long onSeek(long offset, int pos) {
if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset;
else if (pos == 2) ptr = handle().length() - (int)offset;
if (ptr < 0) ptr = 0;
if (ptr > handle().length()) ptr = handle().length();
return pos;
}
@Override protected boolean onClose() {
ptr = 0;
return true;
}
public MemoryFile(Buffer buff, Mode mode) {
super(buff, mode);
}
}

View File

@ -1,100 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.common.Filename;
public class MemoryFilesystem implements Filesystem {
public final Mode mode;
private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>();
private HandleManager handles = new HandleManager();
private Path realPath(String path) {
return Filename.normalize(path);
}
@Override public String normalize(String... path) {
return Paths.normalize(path);
}
@Override public synchronized File open(String _path, Mode perms) {
try {
var path = realPath(_path);
var pcount = path.getNameCount();
if (files.containsKey(path)) return handles.put(new MemoryFile(files.get(path), perms));
else if (folders.contains(path)) {
var res = new StringBuilder();
for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(path)) continue;
res.append(folder.toFile().getName()).append('\n');
}
for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(path)) continue;
res.append(file.toFile().getName()).append('\n');
}
return handles.put(new MemoryFile(new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)));
}
else throw new FilesystemException(ErrorReason.DOESNT_EXIST);
}
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.OPEN); }
}
@Override public synchronized boolean create(String _path, EntryType type) {
try {
var path = realPath(_path);
switch (type) {
case FILE:
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
if (folders.contains(path) || files.containsKey(path)) return false;
files.put(path, new Buffer());
return true;
case FOLDER:
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
if (folders.contains(path) || files.containsKey(path)) return false;
folders.add(path);
return true;
default:
case NONE:
return folders.remove(path) || files.remove(path) != null;
}
}
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.CREATE); }
}
@Override public synchronized FileStat stat(String _path) {
var path = realPath(_path);
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
else return new FileStat(Mode.NONE, EntryType.NONE);
}
@Override public synchronized void close() throws FilesystemException {
handles.close();
}
public MemoryFilesystem put(String path, byte[] data) {
var _path = realPath(path);
var _curr = "/";
for (var seg : _path) {
create(_curr, EntryType.FOLDER);
_curr += seg + "/";
}
files.put(_path, new Buffer(data));
return this;
}
public MemoryFilesystem(Mode mode) {
this.mode = mode;
folders.add(Path.of("/"));
}
}

View File

@ -1,41 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum Mode {
NONE("", false, false),
READ("r", true, false),
WRITE("rw", false, true),
READ_WRITE("rw", true, true);
public final String name;
public final boolean readable;
public final boolean writable;
public Mode intersect(Mode other) {
return of(readable && other.readable, writable && other.writable);
}
private Mode(String mode, boolean r, boolean w) {
this.name = mode;
this.readable = r;
this.writable = w;
}
public static Mode of(boolean read, boolean write) {
if (read && write) return READ_WRITE;
if (read) return READ;
if (write) return WRITE;
return NONE;
}
public static Mode parse(String mode) {
switch (mode.toLowerCase()) {
case "r": return READ;
case "w": return WRITE;
case "r+":
case "w+":
case "wr":
case "rw": return READ_WRITE;
default: return NONE;
}
}
}

View File

@ -1,52 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.ArrayList;
public class Paths {
public static String normalize(String... path) {
var parts = String.join("/", path).split("[\\\\/]");
var res = new ArrayList<String>();
for (var part : parts) {
if (part.equals("...")) res.clear();
else if (part.equals("..")) {
if (res.size() > 0) res.remove(res.size() - 1);
}
else if (!part.equals(".") && !part.isEmpty()) res.add(part);
}
var sb = new StringBuilder();
for (var el : res) sb.append("/").append(el);
if (sb.length() == 0) return "/";
else return sb.toString();
}
public static String chroot(String root, String path) {
return normalize(root) + normalize(path);
}
public static String cwd(String cwd, String path) {
return normalize(cwd + "/" + path);
}
public static String filename(String path) {
var i = path.lastIndexOf('/');
if (i < 0) i = path.lastIndexOf('\\');
if (i < 0) return path;
else return path.substring(i + 1);
}
public static String extension(String path) {
var i = path.lastIndexOf('.');
if (i < 0) return "";
else return path.substring(i + 1);
}
public static String dir(String path) {
return normalize(path + "/..");
}
}

View File

@ -1,35 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;
public class PhysicalFile extends BaseFile<RandomAccessFile> {
@Override protected int onRead(byte[] buff) {
try { return handle().read(buff); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.READ); }
}
@Override protected void onWrite(byte[] buff) {
try { handle().write(buff); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.WRITE); }
}
@Override protected long onSeek(long offset, int pos) {
try {
if (pos == 1) offset += handle().getFilePointer();
else if (pos == 2) offset += handle().length();
handle().seek(offset);
return offset;
}
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.SEEK); }
}
@Override protected boolean onClose() {
try { handle().close(); }
catch (IOException e) {} // SHUT
return true;
}
public PhysicalFile(Path path, Mode mode) throws FileNotFoundException {
super(new RandomAccessFile(path.toFile(), mode.name), mode);
}
}

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