refactor: Transition to a Value class
This commit is contained in:
parent
3475e3a130
commit
bab59d454f
@ -1,4 +1,4 @@
|
||||
project_group = me.topchetoeu
|
||||
project_name = jscript
|
||||
project_version = 0.9.41-beta
|
||||
main_class = me.topchetoeu.jscript.utils.JScriptRepl
|
||||
main_class = me.topchetoeu.jscript.runtime.SimpleRepl
|
||||
|
@ -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.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.environment.Key;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
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 Key<Compiler> KEY = new Key<>();
|
@ -1,9 +1,10 @@
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
public class ParseRes<T> {
|
@ -1,79 +1,16 @@
|
||||
package me.topchetoeu.jscript.common.json;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.ParseRes;
|
||||
import me.topchetoeu.jscript.compilation.parsing.Parsing;
|
||||
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.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
|
||||
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) {
|
||||
return Parsing.parseIdentifier(tokens, i);
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.utils.mapping.SourceMap;
|
||||
|
||||
public class FunctionMap {
|
||||
public static class FunctionMapBuilder {
|
||||
@ -131,27 +130,27 @@ public class FunctionMap {
|
||||
return pcToLoc.lastEntry().getValue();
|
||||
}
|
||||
|
||||
public FunctionMap apply(SourceMap map) {
|
||||
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
||||
// public static FunctionMap apply(FunctionMap funcMap, SourceMap map) {
|
||||
// var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames);
|
||||
|
||||
for (var el : pcToLoc.entrySet()) {
|
||||
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
|
||||
}
|
||||
// for (var el : funcMap.pcToLoc.entrySet()) {
|
||||
// res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
|
||||
// }
|
||||
|
||||
res.bps.putAll(bps);
|
||||
// res.bps.putAll(bps);
|
||||
|
||||
for (var el : bpLocs.entrySet()) {
|
||||
for (var loc : el.getValue()) {
|
||||
loc = map.toCompiled(loc);
|
||||
if (loc == null) continue;
|
||||
// for (var el : bpLocs.entrySet()) {
|
||||
// for (var loc : el.getValue()) {
|
||||
// loc = map.toCompiled(loc);
|
||||
// if (loc == null) continue;
|
||||
|
||||
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
|
||||
res.bpLocs.get(loc.filename()).add(loc);
|
||||
}
|
||||
}
|
||||
// if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
|
||||
// res.bpLocs.get(loc.filename()).add(loc);
|
||||
// }
|
||||
// }
|
||||
|
||||
return res;
|
||||
}
|
||||
// return res;
|
||||
// }
|
||||
|
||||
public FunctionMap clone() {
|
||||
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
||||
|
@ -11,11 +11,12 @@ import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
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.VariableDeclareStatement.Pair;
|
||||
import me.topchetoeu.jscript.compilation.control.*;
|
||||
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.values.*;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
@ -1,7 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.parsing;
|
||||
|
||||
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 final State state;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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), ",/?:@&=+$#.");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import me.topchetoeu.jscript.common.Compiler;
|
||||
import me.topchetoeu.jscript.common.Filename;
|
||||
import me.topchetoeu.jscript.common.ResultRunnable;
|
||||
import me.topchetoeu.jscript.common.events.DataNotifier;
|
||||
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.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
|
||||
public interface EventLoop {
|
||||
public static final Key<EventLoop> KEY = new Key<>();
|
||||
@ -25,10 +27,10 @@ public interface EventLoop {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
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.scope.LocalScope;
|
||||
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.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ScopeValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
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.functions.CodeFunction;
|
||||
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 static final Key<Frame> KEY = new Key<>();
|
||||
@ -64,12 +67,12 @@ public class Frame {
|
||||
|
||||
private static class PendingResult {
|
||||
public final boolean isReturn, isJump, isThrow;
|
||||
public final Object value;
|
||||
public final Value value;
|
||||
public final EngineException error;
|
||||
public final int ptr;
|
||||
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.isReturn = isReturn;
|
||||
this.isJump = isJump;
|
||||
@ -82,7 +85,7 @@ public class Frame {
|
||||
public static PendingResult ofNone() {
|
||||
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);
|
||||
}
|
||||
public static PendingResult ofThrow(EngineException error, Instruction instr) {
|
||||
@ -100,7 +103,7 @@ public class Frame {
|
||||
public final CodeFunction function;
|
||||
public final Environment env;
|
||||
|
||||
public Object[] stack = new Object[32];
|
||||
public Value[] stack = new Value[32];
|
||||
public int stackPtr = 0;
|
||||
public int codePtr = 0;
|
||||
public boolean jumpFlag = false;
|
||||
@ -113,52 +116,54 @@ public class Frame {
|
||||
tryStack.add(res);
|
||||
}
|
||||
|
||||
public Object peek() {
|
||||
public Value peek() {
|
||||
return peek(0);
|
||||
}
|
||||
public Object peek(int offset) {
|
||||
public Value peek(int offset) {
|
||||
if (stackPtr <= offset) return null;
|
||||
else return stack[stackPtr - 1 - offset];
|
||||
}
|
||||
public Object pop() {
|
||||
public Value pop() {
|
||||
if (stackPtr == 0) return null;
|
||||
return stack[--stackPtr];
|
||||
}
|
||||
public Object[] take(int n) {
|
||||
public Value[] take(int n) {
|
||||
int srcI = stackPtr - n;
|
||||
if (srcI < 0) srcI = 0;
|
||||
|
||||
int dstI = n + srcI - stackPtr;
|
||||
int copyN = stackPtr - srcI;
|
||||
|
||||
Object[] res = new Object[n];
|
||||
Value[] res = new Value[n];
|
||||
System.arraycopy(stack, srcI, res, dstI, copyN);
|
||||
stackPtr -= copyN;
|
||||
|
||||
return res;
|
||||
}
|
||||
public void push(Object val) {
|
||||
public void push(Value val) {
|
||||
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);
|
||||
stack = newStack;
|
||||
}
|
||||
stack[stackPtr++] = Values.normalize(env, val);
|
||||
|
||||
stack[stackPtr++] = val;
|
||||
}
|
||||
|
||||
private Object next(Object value, Object returnValue, EngineException error) {
|
||||
if (value != Values.NO_RETURN) push(value);
|
||||
// for the love of christ don't touch this
|
||||
private Value next(Value value, Value returnValue, EngineException error) {
|
||||
if (value != null) push(value);
|
||||
|
||||
Instruction instr = null;
|
||||
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 {
|
||||
if (Thread.interrupted()) throw new InterruptException();
|
||||
|
||||
if (instr == null) returnValue = null;
|
||||
else {
|
||||
DebugContext.get(env).onInstruction(env, this, instr, Values.NO_RETURN, null, false);
|
||||
DebugContext.get(env).onInstruction(env, this, instr, null, null, false);
|
||||
|
||||
try {
|
||||
this.jumpFlag = this.popTryFlag = false;
|
||||
@ -181,7 +186,7 @@ public class Frame {
|
||||
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
||||
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));
|
||||
}
|
||||
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
||||
@ -209,7 +214,7 @@ public class Frame {
|
||||
}
|
||||
|
||||
error = null;
|
||||
returnValue = Values.NO_RETURN;
|
||||
returnValue = null;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
@ -227,7 +232,7 @@ public class Frame {
|
||||
tryStack.pop();
|
||||
codePtr = tryCtx.end;
|
||||
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) {
|
||||
codePtr = tryCtx.result.ptr;
|
||||
jumpFlag = true;
|
||||
@ -255,19 +260,19 @@ public class Frame {
|
||||
DebugContext.get(env).onInstruction(env, this, instr, null, error, caught);
|
||||
throw error;
|
||||
}
|
||||
if (returnValue != Values.NO_RETURN) {
|
||||
if (returnValue != null) {
|
||||
DebugContext.get(env).onInstruction(env, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the next instruction in the frame
|
||||
*/
|
||||
public Object next() {
|
||||
return next(Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
public Value next() {
|
||||
return next(null, null, null);
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public Object next(Object value) {
|
||||
return next(value, Values.NO_RETURN, null);
|
||||
public Value next(Value value) {
|
||||
return next(value, null, null);
|
||||
}
|
||||
/**
|
||||
* Induces a thrown error and executes the next instruction.
|
||||
@ -286,8 +291,8 @@ public class Frame {
|
||||
*
|
||||
* @param error The error to induce
|
||||
*/
|
||||
public Object induceError(EngineException error) {
|
||||
return next(Values.NO_RETURN, Values.NO_RETURN, error);
|
||||
public Value induceError(EngineException error) {
|
||||
return next(null, null, error);
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public Object induceReturn(Object value) {
|
||||
return next(Values.NO_RETURN, value, null);
|
||||
public Value induceReturn(Value value) {
|
||||
return next(null, value, null);
|
||||
}
|
||||
|
||||
public void onPush() {
|
||||
@ -348,35 +353,47 @@ public class Frame {
|
||||
*/
|
||||
public ObjectValue getValStackScope() {
|
||||
return new ObjectValue() {
|
||||
@Override
|
||||
protected Object getField(Environment ext, Object key) {
|
||||
var i = (int)Values.toNumber(ext, key);
|
||||
@Override public Member getOwnMember(Environment env, Value key) {
|
||||
var res = super.getOwnMember(env, key);
|
||||
if (res != null) return res;
|
||||
|
||||
var f = key.toNumber(env).value;
|
||||
var i = (int)f;
|
||||
|
||||
if (i < 0 || i >= stackPtr) return null;
|
||||
else return stack[i];
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Environment ext, Object key) {
|
||||
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
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
for (var i = 0; i < stackPtr; i++) res.add(i);
|
||||
};
|
||||
}
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) {
|
||||
var res = new LinkedHashMap<String, Member>();
|
||||
|
||||
for (var i = 0; i < stackPtr; i++) {
|
||||
var _i = i;
|
||||
res.put(i + "", 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.args = args.clone();
|
||||
this.scope = new LocalScope(func.body.localsN, func.captures);
|
||||
this.scope.get(0).set(null, thisArg);
|
||||
var argsObj = new ArrayValue();
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
argsObj.set(env, i, args[i]);
|
||||
}
|
||||
this.scope.get(1).value = argsObj;
|
||||
this.scope.get(0).set(thisArg);
|
||||
this.scope.get(1).value = new ArrayValue(args);
|
||||
|
||||
this.thisArg = thisArg;
|
||||
this.function = func;
|
||||
|
@ -1,5 +1,6 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
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.scope.GlobalScope;
|
||||
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.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.Symbol;
|
||||
import me.topchetoeu.jscript.runtime.values.Values;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
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 {
|
||||
private static Object execReturn(Environment ext, Instruction instr, Frame frame) {
|
||||
private static Value execReturn(Environment env, Instruction instr, Frame frame) {
|
||||
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());
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
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 func = frame.pop();
|
||||
var thisArg = frame.pop();
|
||||
|
||||
frame.push(Values.call(ext, func, thisArg, callArgs));
|
||||
frame.push(func.call(env, thisArg, callArgs));
|
||||
|
||||
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 funcObj = frame.pop();
|
||||
|
||||
frame.push(Values.callNew(ext, funcObj, callArgs));
|
||||
frame.push(funcObj.callNew(env, callArgs));
|
||||
|
||||
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);
|
||||
GlobalScope.get(ext).define(ext, name);
|
||||
GlobalScope.get(env).define(env, false, name);
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
private static Object execDefProp(Environment ext, Instruction instr, Frame frame) {
|
||||
var setter = frame.pop();
|
||||
var getter = frame.pop();
|
||||
private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
|
||||
var setterVal = frame.pop();
|
||||
var getterVal = frame.pop();
|
||||
var name = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
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.");
|
||||
((ObjectValue)obj).defineProperty(ext, name, (FunctionValue)getter, (FunctionValue)setter, false, false);
|
||||
FunctionValue getter, setter;
|
||||
|
||||
if (getterVal == VoidValue.UNDEFINED) getter = null;
|
||||
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.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 members = Values.getMembers(ext, val, false, false);
|
||||
var members = new ArrayList<>(val.getMembers(env, false, true).keySet());
|
||||
Collections.reverse(members);
|
||||
|
||||
frame.push(null);
|
||||
|
||||
for (var el : members) {
|
||||
if (el instanceof Symbol) continue;
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ext, "value", el);
|
||||
obj.defineOwnMember(env, new StringValue("value"), FieldMember.of(new StringValue(el)));
|
||||
frame.push(obj);
|
||||
}
|
||||
|
||||
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 catchStart = (int)instr.get(0);
|
||||
int finallyStart = (int)instr.get(1);
|
||||
@ -95,14 +107,14 @@ public class InstructionRunner {
|
||||
int end = (int)instr.get(2) + start;
|
||||
frame.addTry(start, end, catchStart, finallyStart);
|
||||
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;
|
||||
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);
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
@ -110,45 +122,48 @@ public class InstructionRunner {
|
||||
}
|
||||
|
||||
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) {
|
||||
case PUSH_UNDEFINED: frame.push(null); break;
|
||||
case PUSH_NULL: frame.push(Values.NULL); break;
|
||||
default: frame.push(instr.get(0)); break;
|
||||
case PUSH_UNDEFINED: frame.push(VoidValue.UNDEFINED); break;
|
||||
case PUSH_NULL: frame.push(VoidValue.NULL); 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++;
|
||||
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);
|
||||
|
||||
if (i instanceof String) frame.push(GlobalScope.get(ext).get(ext, (String)i));
|
||||
else frame.push(frame.scope.get((int)i).get(ext));
|
||||
if (i instanceof String) frame.push(GlobalScope.get(env).get(env, (String)i));
|
||||
else frame.push(frame.scope.get((int)i).get(env));
|
||||
|
||||
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.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
private static Object execLoadGlob(Environment ext, Instruction instr, Frame frame) {
|
||||
frame.push(GlobalScope.get(ext).obj);
|
||||
private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) {
|
||||
frame.push(GlobalScope.get(env).object);
|
||||
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();
|
||||
res.setSize(instr.get(0));
|
||||
frame.push(res);
|
||||
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);
|
||||
var captures = new ValueVariable[instr.params.length - 1];
|
||||
|
||||
@ -156,174 +171,175 @@ public class InstructionRunner {
|
||||
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.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 obj = frame.pop();
|
||||
|
||||
try {
|
||||
frame.push(Values.getMember(ext, obj, key));
|
||||
frame.push(obj.getMember(env, key));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType(e.getMessage());
|
||||
}
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
private static Object execLoadRegEx(Environment ext, Instruction instr, Frame frame) {
|
||||
if (ext.hasNotNull(Environment.REGEX_CONSTR)) {
|
||||
frame.push(Values.callNew(ext, ext.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1)));
|
||||
private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) {
|
||||
if (env.hasNotNull(Environment.REGEX_CONSTR)) {
|
||||
frame.push(env.get(Environment.REGEX_CONSTR).callNew(env, instr.get(0), instr.get(1)));
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofSyntax("Regex is not supported.");
|
||||
}
|
||||
|
||||
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.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 key = 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);
|
||||
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 i = instr.get(0);
|
||||
|
||||
if (i instanceof String) GlobalScope.get(ext).set(ext, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ext, val);
|
||||
if (i instanceof String) GlobalScope.get(env).set(env, (String)i, val);
|
||||
else frame.scope.get((int)i).set(env, val);
|
||||
|
||||
frame.codePtr++;
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
private static Object execStoreSelfFunc(Environment ext, Instruction instr, Frame frame) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(ext, frame.function);
|
||||
private static Value execStoreSelfFunc(Environment env, Instruction instr, Frame frame) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(env, frame.function);
|
||||
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.jumpFlag = true;
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
private static Object execJmpIf(Environment ext, Instruction instr, Frame frame) {
|
||||
if (Values.toBoolean(frame.pop())) {
|
||||
private static Value execJmpIf(Environment env, Instruction instr, Frame frame) {
|
||||
if (frame.pop().toBoolean().value) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return Values.NO_RETURN;
|
||||
return null;
|
||||
}
|
||||
private static Object execJmpIfNot(Environment ext, Instruction instr, Frame frame) {
|
||||
if (Values.not(frame.pop())) {
|
||||
private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) {
|
||||
if (frame.pop().not().value) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
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);
|
||||
Object obj;
|
||||
Value obj;
|
||||
|
||||
if (name != null) {
|
||||
if (GlobalScope.get(ext).has(ext, name)) {
|
||||
obj = GlobalScope.get(ext).get(ext, name);
|
||||
if (GlobalScope.get(env).has(env, name)) {
|
||||
obj = GlobalScope.get(env).get(env, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
else obj = frame.pop();
|
||||
|
||||
frame.push(Values.type(obj));
|
||||
frame.push(obj.type());
|
||||
|
||||
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++;
|
||||
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 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++;
|
||||
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);
|
||||
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();
|
||||
|
||||
frame.push(Values.operation(ext, op, args));
|
||||
frame.push(Value.operation(env, op, args));
|
||||
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) {
|
||||
case NOP: return execNop(ext, instr, frame);
|
||||
case RETURN: return execReturn(ext, instr, frame);
|
||||
case THROW: return execThrow(ext, instr, frame);
|
||||
case THROW_SYNTAX: return execThrowSyntax(ext, instr, frame);
|
||||
case CALL: return execCall(ext, instr, frame);
|
||||
case CALL_NEW: return execCallNew(ext, instr, frame);
|
||||
case TRY_START: return execTryStart(ext, instr, frame);
|
||||
case TRY_END: return execTryEnd(ext, instr, frame);
|
||||
case NOP: return execNop(env, instr, frame);
|
||||
case RETURN: return execReturn(env, instr, frame);
|
||||
case THROW: return execThrow(env, instr, frame);
|
||||
case THROW_SYNTAX: return execThrowSyntax(env, instr, frame);
|
||||
case CALL: return execCall(env, instr, frame);
|
||||
case CALL_NEW: return execCallNew(env, instr, frame);
|
||||
case TRY_START: return execTryStart(env, 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_NULL:
|
||||
case PUSH_STRING:
|
||||
case PUSH_NUMBER:
|
||||
case PUSH_BOOL:
|
||||
return execLoadValue(ext, instr, frame);
|
||||
case LOAD_VAR: return execLoadVar(ext, instr, frame);
|
||||
case LOAD_OBJ: return execLoadObj(ext, instr, frame);
|
||||
case LOAD_ARR: return execLoadArr(ext, instr, frame);
|
||||
case LOAD_FUNC: return execLoadFunc(ext, instr, frame);
|
||||
case LOAD_MEMBER: return execLoadMember(ext, instr, frame);
|
||||
case LOAD_REGEX: return execLoadRegEx(ext, instr, frame);
|
||||
case LOAD_GLOB: return execLoadGlob(ext, instr, frame);
|
||||
return execLoadValue(env, instr, frame);
|
||||
case LOAD_VAR: return execLoadVar(env, instr, frame);
|
||||
case LOAD_OBJ: return execLoadObj(env, instr, frame);
|
||||
case LOAD_ARR: return execLoadArr(env, instr, frame);
|
||||
case LOAD_FUNC: return execLoadFunc(env, instr, frame);
|
||||
case LOAD_MEMBER: return execLoadMember(env, instr, frame);
|
||||
case LOAD_REGEX: return execLoadRegEx(env, instr, frame);
|
||||
case LOAD_GLOB: return execLoadGlob(env, instr, frame);
|
||||
|
||||
case DISCARD: return execDiscard(ext, instr, frame);
|
||||
case STORE_MEMBER: return execStoreMember(ext, instr, frame);
|
||||
case STORE_VAR: return execStoreVar(ext, instr, frame);
|
||||
case STORE_SELF_FUNC: return execStoreSelfFunc(ext, instr, frame);
|
||||
case MAKE_VAR: return execMakeVar(ext, instr, frame);
|
||||
case DISCARD: return execDiscard(env, instr, frame);
|
||||
case STORE_MEMBER: return execStoreMember(env, instr, frame);
|
||||
case STORE_VAR: return execStoreVar(env, instr, frame);
|
||||
case STORE_SELF_FUNC: return execStoreSelfFunc(env, instr, frame);
|
||||
case MAKE_VAR: return execMakeVar(env, instr, frame);
|
||||
|
||||
case KEYS: return execKeys(ext, instr, frame);
|
||||
case DEF_PROP: return execDefProp(ext, instr, frame);
|
||||
case TYPEOF: return execTypeof(ext, instr, frame);
|
||||
case DELETE: return execDelete(ext, instr, frame);
|
||||
case KEYS: return execKeys(env, instr, frame);
|
||||
case DEF_PROP: return execDefProp(env, instr, frame);
|
||||
case TYPEOF: return execTypeof(env, instr, frame);
|
||||
case DELETE: return execDelete(env, instr, frame);
|
||||
|
||||
case JMP: return execJmp(ext, instr, frame);
|
||||
case JMP_IF: return execJmpIf(ext, instr, frame);
|
||||
case JMP_IFN: return execJmpIfNot(ext, instr, frame);
|
||||
case JMP: return execJmp(env, instr, frame);
|
||||
case JMP_IF: return execJmpIf(env, 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() + ".");
|
||||
}
|
||||
|
86
src/java/me/topchetoeu/jscript/runtime/JSONConverter.java
Normal file
86
src/java/me/topchetoeu/jscript/runtime/JSONConverter.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
136
src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java
Normal file
136
src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java
Normal 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();
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
|
||||
public interface WrapperProvider {
|
||||
public ObjectValue getProto(Class<?> obj);
|
||||
|
@ -14,8 +14,8 @@ import me.topchetoeu.jscript.runtime.Frame;
|
||||
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.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
|
||||
public class DebugContext {
|
||||
public static final Key<DebugContext> KEY = new Key<>();
|
||||
@ -71,6 +71,7 @@ public class DebugContext {
|
||||
return getMapOrEmpty(((CodeFunction)func).body);
|
||||
}
|
||||
public List<Frame> getStackFrames() {
|
||||
if (debugger == null) return List.of();
|
||||
return this.debugger.getStackFrame();
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@ import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.Compiler;
|
||||
import me.topchetoeu.jscript.runtime.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.common.Compiler;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
|
||||
public class Environment {
|
||||
public static final Key<Compiler> COMPILE_FUNC = new Key<>();
|
||||
|
@ -5,9 +5,11 @@ import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Location;
|
||||
import me.topchetoeu.jscript.runtime.environment.Environment;
|
||||
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.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.objects.ObjectValue.PrototypeProvider;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||
|
||||
public class EngineException extends RuntimeException {
|
||||
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 Environment env = null;
|
||||
public final List<StackElement> stackTrace = new ArrayList<>();
|
||||
@ -61,10 +63,10 @@ public class EngineException extends RuntimeException {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString(Environment ext) {
|
||||
public String toString(Environment env) {
|
||||
var ss = new StringBuilder();
|
||||
try {
|
||||
ss.append(Values.toString(ext, value)).append('\n');
|
||||
ss.append(value.toString(env)).append('\n');
|
||||
}
|
||||
catch (EngineException e) {
|
||||
ss.append("[Error while stringifying]\n");
|
||||
@ -72,38 +74,40 @@ public class EngineException extends RuntimeException {
|
||||
for (var line : stackTrace) {
|
||||
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);
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
private static Object err(String name, String msg, PlaceholderProto proto) {
|
||||
var res = new ObjectValue(proto);
|
||||
if (name != null) res.defineProperty(null, "name", name);
|
||||
res.defineProperty(null, "message", msg);
|
||||
private static ObjectValue err(String name, String msg, PrototypeProvider proto) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(proto);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public EngineException(Object error) {
|
||||
super(error == null ? "null" : error.toString());
|
||||
public EngineException(Value error) {
|
||||
super(error.toReadable(Environment.empty()));
|
||||
|
||||
this.value = error;
|
||||
this.cause = null;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR));
|
||||
return new EngineException(err(null, msg, env -> env.get(Environment.RANGE_ERR_PROTO)));
|
||||
}
|
||||
}
|
||||
|
@ -6,55 +6,48 @@ import java.util.Set;
|
||||
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.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.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
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 static final Key<GlobalScope> KEY = new Key<>();
|
||||
|
||||
public final ObjectValue obj;
|
||||
public final ObjectValue object;
|
||||
|
||||
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() {
|
||||
var obj = new ObjectValue();
|
||||
Values.setPrototype(null, obj, this.obj);
|
||||
return new GlobalScope(obj);
|
||||
var res = new GlobalScope();
|
||||
res.object.setPrototype(null, this.object);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object define(Environment ext, String name) {
|
||||
if (Values.hasMember(ext, obj, name, false)) return name;
|
||||
obj.defineProperty(ext, name, null);
|
||||
return name;
|
||||
public void define(Environment ext, String name, Variable variable) {
|
||||
object.defineOwnMember(ext, new StringValue(name), variable.toField(true, true));
|
||||
}
|
||||
public void define(Environment ext, String name, Variable val) {
|
||||
obj.defineProperty(ext, name,
|
||||
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, boolean readonly, String name, Value val) {
|
||||
object.defineOwnMember(ext, new StringValue(name), FieldMember.of(val, !readonly));
|
||||
}
|
||||
public void define(Environment ext, String name, boolean readonly, Object val) {
|
||||
obj.defineProperty(ext, name, val, readonly, true, true);
|
||||
}
|
||||
public void define(Environment ext, String ...names) {
|
||||
for (var n : names) define(ext, n);
|
||||
public void define(Environment ext, boolean readonly, String ...names) {
|
||||
for (var name : names) define(ext, name, new ValueVariable(readonly, VoidValue.UNDEFINED));
|
||||
}
|
||||
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) {
|
||||
if (!Values.hasMember(ext, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
else return Values.getMember(ext, obj, name);
|
||||
public Value get(Environment env, String name) {
|
||||
if (!object.hasMember(env, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
else return object.getMember(env, new StringValue(name));
|
||||
}
|
||||
public void set(Environment ext, String name, Object val) {
|
||||
if (!Values.hasMember(ext, obj, 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.");
|
||||
public void set(Environment ext, String name, Value val) {
|
||||
if (!object.hasMember(ext, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
if (!object.setMember(ext, new StringValue(name), val)) throw EngineException.ofSyntax("The global '" + name + "' is read-only.");
|
||||
}
|
||||
|
||||
public Set<String> keys() {
|
||||
@ -68,10 +61,11 @@ public class GlobalScope {
|
||||
}
|
||||
|
||||
public GlobalScope() {
|
||||
this.obj = new ObjectValue();
|
||||
this.object = new ObjectValue();
|
||||
this.object.setPrototype(null, null);
|
||||
}
|
||||
public GlobalScope(ObjectValue val) {
|
||||
this.obj = val;
|
||||
this.object = val;
|
||||
}
|
||||
|
||||
public static GlobalScope get(Environment ext) {
|
||||
|
@ -1,27 +1,24 @@
|
||||
package me.topchetoeu.jscript.runtime.scope;
|
||||
|
||||
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 boolean readonly;
|
||||
public Object value;
|
||||
public Value value;
|
||||
|
||||
@Override
|
||||
public boolean readonly() { return readonly; }
|
||||
@Override 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 Object get(Environment ext) {
|
||||
return value;
|
||||
public Value get() { return value; }
|
||||
public boolean set(Value val) {
|
||||
if (readonly) return false;
|
||||
this.value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Environment ext, Object val) {
|
||||
if (readonly) return;
|
||||
this.value = Values.normalize(ext, val);
|
||||
}
|
||||
|
||||
public ValueVariable(boolean readonly, Object val) {
|
||||
public ValueVariable(boolean readonly, Value val) {
|
||||
this.readonly = readonly;
|
||||
this.value = val;
|
||||
}
|
||||
|
@ -1,9 +1,24 @@
|
||||
package me.topchetoeu.jscript.runtime.scope;
|
||||
|
||||
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 {
|
||||
Object get(Environment ext);
|
||||
Value get(Environment env);
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
132
src/java/me/topchetoeu/jscript/runtime/values/Member.java
Normal file
132
src/java/me/topchetoeu/jscript/runtime/values/Member.java
Normal 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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
@ -19,6 +19,11 @@ import me.topchetoeu.jscript.runtime.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
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;
|
||||
|
||||
public class Values {
|
||||
@ -68,7 +73,7 @@ public class Values {
|
||||
if (val instanceof String) return "string";
|
||||
if (val instanceof Number) return "number";
|
||||
if (val instanceof Boolean) return "boolean";
|
||||
if (val instanceof Symbol) return "symbol";
|
||||
if (val instanceof SymbolValue) return "symbol";
|
||||
if (val instanceof FunctionValue) return "function";
|
||||
return "object";
|
||||
}
|
||||
@ -89,7 +94,7 @@ public class Values {
|
||||
obj instanceof Number ||
|
||||
obj instanceof String ||
|
||||
obj instanceof Boolean ||
|
||||
obj instanceof Symbol ||
|
||||
obj instanceof SymbolValue ||
|
||||
obj == null ||
|
||||
obj == NULL;
|
||||
}
|
||||
@ -141,7 +146,7 @@ public class Values {
|
||||
}
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return val.toString();
|
||||
if (val instanceof SymbolValue) return val.toString();
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
@ -335,7 +340,7 @@ public class Values {
|
||||
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 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;
|
||||
}
|
||||
@ -433,7 +438,7 @@ public class Values {
|
||||
b = toPrimitive(ext, b, ConvertHint.VALUEOF);
|
||||
|
||||
// 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 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) {
|
||||
return () -> {
|
||||
try {
|
||||
var symbol = Symbol.get("Symbol.iterator");
|
||||
var symbol = SymbolValue.get("Symbol.iterator");
|
||||
|
||||
var iteratorFunc = getMember(ext, obj, symbol);
|
||||
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));
|
||||
else {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(args.env, "value", it.next());
|
||||
return obj;
|
||||
object.defineProperty(args.env, "value", it.next());
|
||||
return object;
|
||||
}
|
||||
});
|
||||
}));
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
public final FunctionBody body;
|
||||
public final ValueVariable[] captures;
|
||||
public Environment env;
|
||||
|
||||
// public Location loc() {
|
||||
// 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) {
|
||||
@Override public Value call(Environment env, Value thisArg, Value ...args) {
|
||||
var frame = new Frame(env, thisArg, args, this);
|
||||
frame.onPush();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var res = frame.next();
|
||||
if (res != Values.NO_RETURN) return res;
|
||||
if (res != null) return res;
|
||||
}
|
||||
}
|
||||
finally {
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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.utils.interop.Arguments;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
|
||||
public class NativeFunction extends FunctionValue {
|
||||
public static interface NativeFunctionRunner {
|
||||
Object run(Arguments args);
|
||||
Value run(Arguments args);
|
||||
}
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override
|
||||
public Object call(Environment env, Object thisArg, Object ...args) {
|
||||
return action.run(new Arguments(env, thisArg, args));
|
||||
@Override public Value call(Environment env, Value self, Value ...args) {
|
||||
return action.run(new Arguments(env, self, args));
|
||||
}
|
||||
|
||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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(); }
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package me.topchetoeu.jscript.utils.debug;
|
||||
|
||||
public interface DebuggerProvider {
|
||||
Debugger getDebugger(WebSocket socket, HttpRequest req);
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.utils.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface LineWriter {
|
||||
void writeLine(String value) throws IOException;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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("/"));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 + "/..");
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user