diff --git a/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java b/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java index d0328a0..f1e2033 100644 --- a/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java +++ b/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java @@ -3,12 +3,13 @@ package me.topchetoeu.jscript.common; public class FunctionBody { public final FunctionBody[] children; public final Instruction[] instructions; - public final int localsN, argsN; + public final int localsN, capturesN, length; - public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) { + public FunctionBody(int localsN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) { this.children = children; - this.argsN = argsN; + this.length = length; this.localsN = localsN; + this.capturesN = capturesN; this.instructions = instructions; } } diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index 9460c39..d846072 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -1,56 +1,71 @@ package me.topchetoeu.jscript.common; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.HashMap; +import java.util.function.IntFunction; +import java.util.function.IntSupplier; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Instruction { public static enum Type { - NOP(0), - RETURN(1), - THROW(2), - THROW_SYNTAX(3), - DELETE(4), - TRY_START(5), - TRY_END(6), + RETURN(0x00), + NOP(0x01), + THROW(0x02), + THROW_SYNTAX(0x03), + DELETE(0x04), + TRY_START(0x05), + TRY_END(0x06), - CALL(7), - CALL_NEW(8), - JMP_IF(9), - JMP_IFN(10), - JMP(11), + CALL(0x10), + CALL_MEMBER(0x11), + CALL_NEW(0x12), + JMP_IF(0x13), + JMP_IFN(0x14), + JMP(0x15), - PUSH_UNDEFINED(12), - PUSH_NULL(13), - PUSH_BOOL(14), - PUSH_NUMBER(15), - PUSH_STRING(16), + PUSH_UNDEFINED(0x20), + PUSH_NULL(0x21), + PUSH_BOOL(0x22), + PUSH_NUMBER(0x23), + PUSH_STRING(0x24), + DUP(0x25), + DISCARD(0x26), - LOAD_VAR(17), - LOAD_MEMBER(18), - LOAD_GLOB(20), + LOAD_FUNC(0x30), + LOAD_ARR(0x31), + LOAD_OBJ(0x32), + LOAD_GLOB(0x33), + LOAD_INTRINSICS(0x34), + LOAD_REGEX(0x35), - LOAD_FUNC(21), - LOAD_ARR(22), - LOAD_OBJ(23), - STORE_SELF_FUNC(24), - LOAD_REGEX(25), + LOAD_VAR(0x40), + LOAD_MEMBER(0x41), + LOAD_MEMBER_INT(0x42), + LOAD_MEMBER_STR(0x43), - DUP(26), + LOAD_ARGS(0x44), + LOAD_REST_ARGS(0x45), + LOAD_CALLEE(0x46), + LOAD_THIS(0x47), - STORE_VAR(27), - STORE_MEMBER(28), - DISCARD(29), + STORE_VAR(0x48), + STORE_MEMBER(0x49), + STORE_MEMBER_INT(0x4A), + STORE_MEMBER_STR(0x4B), - MAKE_VAR(30), - DEF_PROP(31), - KEYS(32), + DEF_PROP(0x50), + KEYS(0x51), + TYPEOF(0x52), + OPERATION(0x53), - TYPEOF(33), - OPERATION(34); + GLOB_GET(0x60), + GLOB_SET(0x61), + GLOB_DEF(0x62), + + STACK_ALLOC(0x70), + STACK_REALLOC(0x71), + STACK_FREE(0x72); private static final HashMap types = new HashMap<>(); public final int numeric; @@ -110,117 +125,128 @@ public class Instruction { return params[i].equals(arg); } - public void write(DataOutputStream writer) throws IOException { - var rawType = type.numeric; + // public void write(DataOutputStream writer) throws IOException { + // var rawType = type.numeric; - switch (type) { - case KEYS: - case PUSH_BOOL: - case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break; - case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break; - case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break; - default: - } + // switch (type) { + // case KEYS: + // case PUSH_BOOL: + // case STORE_MEMBER: + // case GLOB_SET: + // rawType |= (boolean)get(0) ? 128 : 0; break; + // case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break; + // default: + // } - writer.writeByte(rawType); + // writer.writeByte(rawType); - switch (type) { - case CALL: writer.writeInt(get(0)); break; - case CALL_NEW: writer.writeInt(get(0)); break; - case DUP: writer.writeInt(get(0)); break; - case JMP: writer.writeInt(get(0)); break; - case JMP_IF: writer.writeInt(get(0)); break; - case JMP_IFN: writer.writeInt(get(0)); break; - case LOAD_ARR: writer.writeInt(get(0)); break; - case LOAD_FUNC: { - writer.writeInt(params.length - 1); + // switch (type) { + // case CALL: + // case CALL_NEW: + // case CALL_MEMBER: + // writer.writeInt(get(0)); + // writer.writeUTF(get(1)); + // break; + // case DUP: writer.writeInt(get(0)); break; + // case JMP: writer.writeInt(get(0)); break; + // case JMP_IF: writer.writeInt(get(0)); break; + // case JMP_IFN: writer.writeInt(get(0)); break; + // case LOAD_ARR: writer.writeInt(get(0)); break; + // case LOAD_FUNC: { + // writer.writeInt(params.length - 1); - for (var i = 0; i < params.length; i++) { - writer.writeInt(get(i + 1)); - } + // for (var i = 0; i < params.length; i++) { + // writer.writeInt(get(i + 1)); + // } - writer.writeInt(get(0)); - break; - } - case LOAD_REGEX: writer.writeUTF(get(0)); break; - case LOAD_VAR: writer.writeInt(get(0)); break; - case MAKE_VAR: writer.writeUTF(get(0)); break; - case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; - case PUSH_NUMBER: writer.writeDouble(get(0)); break; - case PUSH_STRING: writer.writeUTF(get(0)); break; - case STORE_SELF_FUNC: writer.writeInt(get(0)); break; - case STORE_VAR: writer.writeInt(get(0)); break; - case THROW_SYNTAX: writer.writeUTF(get(0)); - case TRY_START: - writer.writeInt(get(0)); - writer.writeInt(get(1)); - writer.writeInt(get(2)); - break; - case TYPEOF: - if (params.length > 0) writer.writeUTF(get(0)); - break; - default: - } - } + // writer.writeInt(get(0)); + // writer.writeUTF(get(0)); + // break; + // } + // case LOAD_REGEX: writer.writeUTF(get(0)); break; + // case LOAD_VAR: writer.writeInt(get(0)); break; + // case GLOB_DEF: writer.writeUTF(get(0)); break; + // case GLOB_GET: writer.writeUTF(get(0)); break; + // case GLOB_SET: + // writer.writeUTF(get(0)); + // break; + // case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; + // case PUSH_NUMBER: writer.writeDouble(get(0)); break; + // case PUSH_STRING: writer.writeUTF(get(0)); break; + // case STORE_VAR: writer.writeInt(get(0)); break; + // case THROW_SYNTAX: writer.writeUTF(get(0)); + // case TRY_START: + // writer.writeInt(get(0)); + // writer.writeInt(get(1)); + // writer.writeInt(get(2)); + // break; + // case TYPEOF: + // if (params.length > 0) writer.writeUTF(get(0)); + // break; + // default: + // } + // } private Instruction(Type type, Object ...params) { this.type = type; this.params = params; } - public static Instruction read(DataInputStream stream) throws IOException { - var rawType = stream.readUnsignedByte(); - var type = Type.fromNumeric(rawType & 127); - var flag = (rawType & 128) != 0; + // public static Instruction read(DataInputStream stream) throws IOException { + // var rawType = stream.readUnsignedByte(); + // var type = Type.fromNumeric(rawType & 127); + // var flag = (rawType & 128) != 0; - switch (type) { - case CALL: return call(stream.readInt()); - case CALL_NEW: return callNew(stream.readInt()); - case DEF_PROP: return defProp(); - case DELETE: return delete(); - case DISCARD: return discard(); - case DUP: return dup(stream.readInt()); - case JMP: return jmp(stream.readInt()); - case JMP_IF: return jmpIf(stream.readInt()); - case JMP_IFN: return jmpIfNot(stream.readInt()); - case KEYS: return keys(flag); - case LOAD_ARR: return loadArr(stream.readInt()); - case LOAD_FUNC: { - var captures = new int[stream.readInt()]; + // switch (type) { + // case CALL: return call(stream.readInt(), stream.readUTF()); + // case CALL_NEW: return callNew(stream.readInt(), stream.readUTF()); + // case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF()); + // case DEF_PROP: return defProp(); + // case DELETE: return delete(); + // case DISCARD: return discard(); + // case DUP: return dup(stream.readInt()); + // case JMP: return jmp(stream.readInt()); + // case JMP_IF: return jmpIf(stream.readInt()); + // case JMP_IFN: return jmpIfNot(stream.readInt()); + // case KEYS: return keys(flag); + // case LOAD_ARR: return loadArr(stream.readInt()); + // case LOAD_FUNC: { + // var captures = new int[stream.readInt()]; - for (var i = 0; i < captures.length; i++) { - captures[i] = stream.readInt(); - } + // for (var i = 0; i < captures.length; i++) { + // captures[i] = stream.readInt(); + // } - return loadFunc(stream.readInt(), captures); - } - case LOAD_GLOB: return loadGlob(); - case LOAD_MEMBER: return loadMember(); - case LOAD_OBJ: return loadObj(); - case LOAD_REGEX: return loadRegex(stream.readUTF(), null); - case LOAD_VAR: return loadVar(stream.readInt()); - case MAKE_VAR: return makeVar(stream.readUTF()); - case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte())); - case PUSH_NULL: return pushNull(); - case PUSH_UNDEFINED: return pushUndefined(); - case PUSH_BOOL: return pushValue(flag); - case PUSH_NUMBER: return pushValue(stream.readDouble()); - case PUSH_STRING: return pushValue(stream.readUTF()); - case RETURN: return ret(); - case STORE_MEMBER: return storeMember(flag); - case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt()); - case STORE_VAR: return storeVar(stream.readInt(), flag); - case THROW: return throwInstr(); - case THROW_SYNTAX: return throwSyntax(stream.readUTF()); - case TRY_END: return tryEnd(); - case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt()); - case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof(); - case NOP: - if (flag) return null; - else return nop(); - default: return null; - } - } + // return loadFunc(stream.readInt(), stream.readUTF(), captures); + // } + // case LOAD_GLOB: return loadGlob(); + // case LOAD_MEMBER: return loadMember(); + // case LOAD_OBJ: return loadObj(); + // case LOAD_REGEX: return loadRegex(stream.readUTF(), null); + // case LOAD_VAR: return loadVar(stream.readInt()); + // case GLOB_DEF: return globDef(stream.readUTF()); + // case GLOB_GET: return globGet(stream.readUTF()); + // case GLOB_SET: return globSet(stream.readUTF(), flag); + // case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte())); + // case PUSH_NULL: return pushNull(); + // case PUSH_UNDEFINED: return pushUndefined(); + // case PUSH_BOOL: return pushValue(flag); + // case PUSH_NUMBER: return pushValue(stream.readDouble()); + // case PUSH_STRING: return pushValue(stream.readUTF()); + // case RETURN: return ret(); + // case STORE_MEMBER: return storeMember(flag); + // case STORE_VAR: return storeVar(stream.readInt(), flag); + // case THROW: return throwInstr(); + // case THROW_SYNTAX: return throwSyntax(stream.readUTF()); + // case TRY_END: return tryEnd(); + // case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt(), stream.readInt()); + // case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof(); + // case NOP: + // if (flag) return null; + // else return nop(); + // default: return null; + // } + // } public static Instruction tryStart(int catchStart, int finallyStart, int end) { return new Instruction(Type.TRY_START, catchStart, finallyStart, end); @@ -237,6 +263,9 @@ public class Instruction { public static Instruction throwSyntax(String err) { return new Instruction(Type.THROW_SYNTAX, err); } + public static Instruction throwSyntax(Location loc, String err) { + return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage()); + } public static Instruction delete() { return new Instruction(Type.DELETE); } @@ -251,12 +280,25 @@ public class Instruction { return new Instruction(Type.NOP, params); } + public static Instruction call(int argn, String name) { + return new Instruction(Type.CALL, argn, name); + } public static Instruction call(int argn) { - return new Instruction(Type.CALL, argn); + return call(argn, ""); + } + public static Instruction callMember(int argn, String name) { + return new Instruction(Type.CALL_MEMBER, argn, name); + } + public static Instruction callMember(int argn) { + return new Instruction(Type.CALL_MEMBER, argn, ""); + } + public static Instruction callNew(int argn, String name) { + return new Instruction(Type.CALL_NEW, argn, name); } public static Instruction callNew(int argn) { - return new Instruction(Type.CALL_NEW, argn); + return new Instruction(Type.CALL_NEW, argn, ""); } + public static Instruction jmp(int offset) { return new Instruction(Type.JMP, offset); } @@ -267,6 +309,17 @@ public class Instruction { return new Instruction(Type.JMP_IFN, offset); } + public static IntFunction jmp(IntSupplier pos) { + return i -> new Instruction(Type.JMP, pos.getAsInt() - i); + } + public static IntFunction jmpIf(IntSupplier pos) { + return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i); + } + public static IntFunction jmpIfNot(IntSupplier pos) { + return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i); + } + + public static Instruction pushUndefined() { return new Instruction(Type.PUSH_UNDEFINED); } @@ -283,26 +336,61 @@ public class Instruction { return new Instruction(Type.PUSH_STRING, val); } - public static Instruction makeVar(String name) { - return new Instruction(Type.MAKE_VAR, name); + public static Instruction globDef(String name) { + return new Instruction(Type.GLOB_DEF, name); } - public static Instruction loadVar(Object i) { + + public static Instruction globGet(String name) { + return new Instruction(Type.GLOB_GET, name); + } + public static Instruction globSet(String name, boolean keep, boolean define) { + return new Instruction(Type.GLOB_SET, name, keep, define); + } + + public static Instruction loadVar(int i) { return new Instruction(Type.LOAD_VAR, i); } + public static Instruction loadThis() { + return new Instruction(Type.LOAD_THIS); + } + public static Instruction loadArgs(boolean real) { + return new Instruction(Type.LOAD_ARGS, real); + } + public static Instruction loadRestArgs(int offset) { + return new Instruction(Type.LOAD_REST_ARGS, offset); + } + public static Instruction loadCallee() { + return new Instruction(Type.LOAD_CALLEE); + } public static Instruction loadGlob() { return new Instruction(Type.LOAD_GLOB); } + public static Instruction loadIntrinsics(String key) { + return new Instruction(Type.LOAD_INTRINSICS, key); + } public static Instruction loadMember() { return new Instruction(Type.LOAD_MEMBER); } + public static Instruction loadMember(int member) { + return new Instruction(Type.LOAD_MEMBER_INT, member); + } + public static Instruction loadMember(String member) { + return new Instruction(Type.LOAD_MEMBER_STR, member); + } public static Instruction loadRegex(String pattern, String flags) { return new Instruction(Type.LOAD_REGEX, pattern, flags); } - public static Instruction loadFunc(int id, int[] captures) { - var args = new Object[1 + captures.length]; + public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, String name, int[] captures) { + if (name == null) name = ""; + + var args = new Object[5 + captures.length]; args[0] = id; - for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i]; + args[1] = name; + args[2] = callable; + args[3] = constructible; + args[4] = captureThis; + for (var i = 0; i < captures.length; i++) args[i + 5] = captures[i]; return new Instruction(Type.LOAD_FUNC, args); } public static Instruction loadObj() { @@ -318,21 +406,34 @@ public class Instruction { return new Instruction(Type.DUP, count); } - public static Instruction storeSelfFunc(int i) { - return new Instruction(Type.STORE_SELF_FUNC, i); - } - public static Instruction storeVar(Object i) { + public static Instruction storeVar(int i) { return new Instruction(Type.STORE_VAR, i, false); } - public static Instruction storeVar(Object i, boolean keep) { + public static Instruction storeVar(int i, boolean keep) { return new Instruction(Type.STORE_VAR, i, keep); } + public static Instruction storeMember() { return new Instruction(Type.STORE_MEMBER, false); } public static Instruction storeMember(boolean keep) { return new Instruction(Type.STORE_MEMBER, keep); } + + public static Instruction storeMember(String key) { + return new Instruction(Type.STORE_MEMBER_STR, key, false); + } + public static Instruction storeMember(String key, boolean keep) { + return new Instruction(Type.STORE_MEMBER_STR, key, keep); + } + + public static Instruction storeMember(int key) { + return new Instruction(Type.STORE_MEMBER_INT, key, false); + } + public static Instruction storeMember(int key, boolean keep) { + return new Instruction(Type.STORE_MEMBER_STR, key, keep); + } + public static Instruction discard() { return new Instruction(Type.DISCARD); } @@ -356,8 +457,17 @@ public class Instruction { return new Instruction(Type.OPERATION, op); } - @Override - public String toString() { + public static Instruction stackAlloc(int n) { + return new Instruction(Type.STACK_ALLOC, n); + } + public static Instruction stackRealloc(int n) { + return new Instruction(Type.STACK_REALLOC, n); + } + public static Instruction stackFree(int n) { + return new Instruction(Type.STACK_FREE, n); + } + + @Override public String toString() { var res = type.toString(); for (int i = 0; i < params.length; i++) { diff --git a/src/main/java/me/topchetoeu/jscript/common/Location.java b/src/main/java/me/topchetoeu/jscript/common/Location.java deleted file mode 100644 index e3030b8..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/Location.java +++ /dev/null @@ -1,101 +0,0 @@ -package me.topchetoeu.jscript.common; - -import java.util.ArrayList; - -public class Location implements Comparable { - public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native")); - private int line; - private int start; - private Filename filename; - - public int line() { return line; } - public int start() { return start; } - public Filename filename() { return filename; } - - @Override - public String toString() { - var res = new ArrayList(); - - if (filename != null) res.add(filename.toString()); - if (line >= 0) res.add(line + ""); - if (start >= 0) res.add(start + ""); - - return String.join(":", res); - } - - public Location add(int n, boolean clone) { - if (clone) return new Location(line, start + n, filename); - this.start += n; - return this; - } - public Location add(int n) { - return add(n, false); - } - public Location nextLine() { - line++; - start = 0; - return this; - } - public Location clone() { - return new Location(line, start, filename); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + line; - result = prime * result + start; - result = prime * result + ((filename == null) ? 0 : filename.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - Location other = (Location) obj; - if (line != other.line) return false; - if (start != other.start) return false; - if (filename == null && other.filename != null) return false; - else if (!filename.equals(other.filename)) return false; - return true; - } - - @Override - public int compareTo(Location other) { - int a = filename.toString().compareTo(other.filename.toString()); - int b = Integer.compare(line, other.line); - int c = Integer.compare(start, other.start); - - if (a != 0) return a; - if (b != 0) return b; - return c; - } - - public Location(int line, int start, Filename filename) { - this.line = line; - this.start = start; - this.filename = filename; - } - - public static Location parse(String raw) { - int i0 = -1, i1 = -1; - for (var i = raw.length() - 1; i >= 0; i--) { - if (raw.charAt(i) == ':') { - if (i1 == -1) i1 = i; - else if (i0 == -1) { - i0 = i; - break; - } - } - } - - return new Location( - Integer.parseInt(raw.substring(i0 + 1, i1)), - Integer.parseInt(raw.substring(i1 + 1)), - Filename.parse(raw.substring(0, i0)) - ); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/RefTracker.java b/src/main/java/me/topchetoeu/jscript/common/RefTracker.java deleted file mode 100644 index 249605f..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/RefTracker.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.topchetoeu.jscript.common; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; - -public class RefTracker { - public static void onDestroy(Object obj, Runnable runnable) { - var queue = new ReferenceQueue<>(); - var ref = new WeakReference<>(obj, queue); - obj = null; - - var th = new Thread(() -> { - try { - queue.remove(); - ref.get(); - runnable.run(); - } - catch (InterruptedException e) { return; } - }); - th.setDaemon(true); - th.start(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java b/src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java deleted file mode 100644 index 7bfc608..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.common; - -public interface ResultRunnable { - T run(); -} diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java b/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java new file mode 100644 index 0000000..7efce20 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java @@ -0,0 +1,187 @@ +package me.topchetoeu.jscript.common.environment; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.function.Supplier; + +public class Environment { + public final Environment parent; + private final Map, Object> map = new HashMap<>(); + private final Set> hidden = new HashSet<>(); + + private final Map, Set> multi = new HashMap<>(); + private final Map, Set> multiHidden = new HashMap<>(); + + @SuppressWarnings("unchecked") + private Set getAll(MultiKey key, boolean forceClone) { + Set parent = null, child = null; + boolean cloned = false; + + if (this.parent != null && !hidden.contains(key)) { + parent = this.parent.getAll(key, false); + if (parent.size() == 0) parent = null; + else if (multiHidden.containsKey(key)) { + parent = new HashSet<>(parent); + parent.removeAll(multiHidden.get(key)); + cloned = true; + } + } + if (multi.containsKey(key)) { + child = (Set)multi.get(key); + if (child.size() == 0) child = null; + } + + if (!forceClone) { + if (parent == null && child == null) return Set.of(); + if (parent == null && child != null) return child; + if (parent != null && child == null) return parent; + } + + if (!cloned) parent = new HashSet<>(); + parent.addAll(child); + return parent; + } + private T getMulti(MultiKey key) { + return key.of(getAll(key, false)); + } + private boolean hasMulti(MultiKey key) { + return getAll(key, false).size() > 0; + } + + @SuppressWarnings("all") + private Environment addMulti(MultiKey key, T value) { + if (!multi.containsKey(key)) { + if (hidden.contains(key)) { + multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true)); + hidden.remove(key); + } + + multi.put((MultiKey)key, new HashSet<>()); + } + + multi.get(key).add(value); + return this; + } + + @SuppressWarnings("unchecked") + public T get(Key key) { + if (key instanceof MultiKey) return getMulti((MultiKey)key); + + if (map.containsKey(key)) return (T)map.get(key); + else if (!hidden.contains(key) && parent != null) return parent.get(key); + else return null; + } + public boolean has(Key key) { + if (key instanceof MultiKey) return hasMulti((MultiKey)key); + + if (map.containsKey(key)) return true; + else if (!hidden.contains(key) && parent != null) return parent.has(key); + else return false; + } + + public boolean hasNotNull(Key key) { + return get(key) != null; + } + + public T get(Key key, T defaultVal) { + if (has(key)) return get(key); + else return defaultVal; + } + public T get(Key key, Supplier defaultVal) { + if (has(key)) return get(key); + else return defaultVal.get(); + } + + @SuppressWarnings("unchecked") + public Environment add(Key key, T val) { + if (key instanceof MultiKey) return add(key, val); + + map.put((Key)key, val); + hidden.remove(key); + return this; + } + public Environment add(Key key) { + return add(key, null); + } + @SuppressWarnings("all") + public Environment addAll(Map, ?> map, boolean iterableAsMulti) { + for (var pair : map.entrySet()) { + if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) { + for (var val : (Iterable)pair.getValue()) { + addMulti((MultiKey)pair.getKey(), val); + } + } + else add((Key)pair.getKey(), pair.getValue()); + } + map.putAll((Map)map); + hidden.removeAll(map.keySet()); + return this; + } + public Environment addAll(Map, ?> map) { + return addAll(map, true); + } + + @SuppressWarnings("unchecked") + public Environment remove(Key key) { + map.remove(key); + multi.remove(key); + multiHidden.remove(key); + hidden.add((Key)key); + return this; + } + @SuppressWarnings("all") + public Environment remove(MultiKey key, T val) { + if (multi.containsKey(key)) { + multi.get(key).remove(val); + multiHidden.get(key).add(val); + + if (multi.get(key).size() == 0) { + multi.remove(key); + multiHidden.remove(key); + hidden.add((Key)key); + } + } + + return this; + } + + public T init(Key key, T val) { + if (!has(key)) this.add(key, val); + return val; + } + public T initFrom(Key key, Supplier val) { + if (!has(key)) { + var res = val.get(); + this.add(key, res); + return res; + } + else return get(key); + } + + public Environment child() { + return new Environment(this); + } + + public Environment(Environment parent) { + this.parent = parent; + } + public Environment() { + this.parent = null; + } + + public static Environment wrap(Environment env) { + if (env == null) return empty(); + else return env; + } + + public static Environment empty() { + return new Environment(); + } + + public static int nextId() { + return new Random().nextInt(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/Key.java b/src/main/java/me/topchetoeu/jscript/common/environment/Key.java new file mode 100644 index 0000000..1bb96b3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/environment/Key.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.common.environment; + +public interface Key { + public static Key of() { + return new Key<>() { }; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java b/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java new file mode 100644 index 0000000..79e8404 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.common.environment; + +import java.util.Set; + +public interface MultiKey extends Key { + public T of(Set values); +} diff --git a/src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java b/src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java deleted file mode 100644 index b498b85..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.topchetoeu.jscript.common.events; - -public class DataNotifier { - private Notifier notifier = new Notifier(); - private boolean isErr; - private T val; - private RuntimeException err; - - public void error(RuntimeException t) { - err = t; - isErr = true; - notifier.next(); - } - public void next(T val) { - this.val = val; - isErr = false; - notifier.next(); - } - public T await() { - notifier.await(); - - try { - if (isErr) throw err; - else return val; - } - finally { - this.err = null; - this.val = null; - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/events/Notifier.java b/src/main/java/me/topchetoeu/jscript/common/events/Notifier.java deleted file mode 100644 index eb6ad4d..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/events/Notifier.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.common.events; - -import me.topchetoeu.jscript.runtime.exceptions.InterruptException; - -public class Notifier { - private boolean ok = false; - - public synchronized void next() { - ok = true; - notifyAll(); - } - public synchronized void await() { - try { - while (!ok) wait(); - ok = false; - } - catch (InterruptedException e) { throw new InterruptException(e); } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSON.java b/src/main/java/me/topchetoeu/jscript/common/json/JSON.java index 2aaa8f1..e93f3f3 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSON.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSON.java @@ -1,147 +1,72 @@ package me.topchetoeu.jscript.common.json; -import java.util.HashSet; -import java.util.List; +import java.math.BigDecimal; +import java.util.Map; import java.util.stream.Collectors; -import me.topchetoeu.jscript.common.Filename; -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.Extensions; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; 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; + public static ParseRes parseString(Source src, int i) { + var res = Parsing.parseString(src, i); + if (!res.isSuccess()) return res.chainError(); + return ParseRes.res(JSONElement.string(res.result), res.n); } - private static JSONElement fromJs(Extensions ext, Object val, HashSet 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 ParseRes parseNumber(Source src, int i) { + var res = Parsing.parseNumber(src, i, true); + if (!res.isSuccess()) return res.chainError(); + else return ParseRes.res(JSONElement.number(res.result), res.n); } - public static JSONElement fromJs(Extensions ext, Object val) { - return fromJs(ext, val, new HashSet<>()); - } - - public static ParseRes parseIdentifier(List tokens, int i) { - return Parsing.parseIdentifier(tokens, i); - } - public static ParseRes parseString(Filename filename, List tokens, int i) { - var res = Parsing.parseString(filename, tokens, i); - if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); - else return res.transform(); - } - public static ParseRes parseNumber(Filename filename, List tokens, int i) { - var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT); - if (minus) i++; - - var res = Parsing.parseNumber(filename, tokens, i); - if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0)); - else return res.transform(); - } - public static ParseRes parseBool(Filename filename, List tokens, int i) { - var id = parseIdentifier(tokens, i); + public static ParseRes parseLiteral(Source src, int i) { + var id = Parsing.parseIdentifier(src, i); if (!id.isSuccess()) return ParseRes.failed(); - else if (id.result.equals("true")) return ParseRes.res(true, 1); - else if (id.result.equals("false")) return ParseRes.res(false, 1); + else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n); + else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n); + else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n); else return ParseRes.failed(); } - public static ParseRes parseValue(Filename filename, List tokens, int i) { - return ParseRes.any( - parseString(filename, tokens, i), - parseNumber(filename, tokens, i), - parseBool(filename, tokens, i), - parseMap(filename, tokens, i), - parseList(filename, tokens, i) + public static ParseRes parseValue(Source src, int i) { + return ParseRes.first(src, i, + JSON::parseString, + JSON::parseNumber, + JSON::parseLiteral, + JSON::parseMap, + JSON::parseList ); } - public static ParseRes parseMap(Filename filename, List tokens, int i) { - int n = 0; - if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); + public static ParseRes parseMap(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; var values = new JSONMap(); + if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(Map.of()), n + 1); while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } + var name = parseString(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); - var name = ParseRes.any( - parseIdentifier(tokens, i + n), - parseString(filename, tokens, i + n), - parseNumber(filename, tokens, i + n) - ); - if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name); - else n += name.n; - - if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) { - return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name); - } + if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon"); n++; - var res = parseValue(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res); - else n += res.n; + var res = parseValue(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); + values.put(name.result.toString(), res.result); + n += res.n; + n += Parsing.skipEmpty(src, i + n); - values.put(name.result.toString(), JSONElement.of(res.result)); - - if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + if (src.is(i + n, ",")) n++; + else if (src.is(i + n, "}")) { n++; break; } @@ -149,26 +74,23 @@ public class JSON { return ParseRes.res(values, n); } - public static ParseRes parseList(Filename filename, List tokens, int i) { - int n = 0; - if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); + public static ParseRes parseList(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n++, "[]")) return ParseRes.failed(); var values = new JSONList(); + if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1); while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break; - } + var res = parseValue(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); + values.add(res.result); + n += res.n; + n += Parsing.skipEmpty(src, i + n); - var res = parseValue(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res); - else n += res.n; - - values.add(JSONElement.of(res.result)); - - if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + if (src.is(i + n, ",")) n++; + else if (src.is(i + n, "]")) { n++; break; } @@ -178,14 +100,21 @@ public class JSON { } public static JSONElement parse(Filename filename, String raw) { if (filename == null) filename = new Filename("jscript", "json"); - var res = parseValue(filename, Parsing.tokenize(filename, raw), 0); + + var res = parseValue(new Source(null, filename, raw), 0); if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); else if (res.isError()) throw new SyntaxException(null, res.error); else return JSONElement.of(res.result); } public static String stringify(JSONElement el) { - if (el.isNumber()) return Double.toString(el.number()); + if (el.isNumber()) { + var d = el.number(); + 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(); + } if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isNull()) return "null"; if (el.isString()) { diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java b/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java index 0b3af33..a8d8490 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java @@ -69,8 +69,7 @@ public class JSONElement { return (boolean)value; } - @Override - public String toString() { + @Override public String toString() { if (isMap()) return "{...}"; if (isList()) return "[...]"; if (isString()) return (String)value; diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java b/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java index ac0cb49..2e2f1f9 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java @@ -116,32 +116,20 @@ public class JSONMap implements Map { public JSONMap set(String key, Map val) { elements.put(key, JSONElement.of(val)); return this; } public JSONMap set(String key, Collection val) { elements.put(key, JSONElement.of(val)); return this; } - @Override - public int size() { return elements.size(); } - @Override - public boolean isEmpty() { return elements.isEmpty(); } - @Override - public boolean containsKey(Object key) { return elements.containsKey(key); } - @Override - public boolean containsValue(Object value) { return elements.containsValue(value); } - @Override - public JSONElement get(Object key) { return elements.get(key); } - @Override - public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } - @Override - public JSONElement remove(Object key) { return elements.remove(key); } - @Override - public void putAll(Map m) { elements.putAll(m); } + @Override public int size() { return elements.size(); } + @Override public boolean isEmpty() { return elements.isEmpty(); } + @Override public boolean containsKey(Object key) { return elements.containsKey(key); } + @Override public boolean containsValue(Object value) { return elements.containsValue(value); } + @Override public JSONElement get(Object key) { return elements.get(key); } + @Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } + @Override public JSONElement remove(Object key) { return elements.remove(key); } + @Override public void putAll(Map m) { elements.putAll(m); } - @Override - public void clear() { elements.clear(); } + @Override public void clear() { elements.clear(); } - @Override - public Set keySet() { return elements.keySet(); } - @Override - public Collection values() { return elements.values(); } - @Override - public Set> entrySet() { return elements.entrySet(); } + @Override public Set keySet() { return elements.keySet(); } + @Override public Collection values() { return elements.values(); } + @Override public Set> entrySet() { return elements.entrySet(); } public JSONMap() { } public JSONMap(Map els) { diff --git a/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java b/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java index b6b992a..fea96ff 100644 --- a/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java +++ b/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java @@ -11,11 +11,10 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; -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; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.scope.Scope; public class FunctionMap { public static class FunctionMapBuilder { @@ -54,8 +53,8 @@ public class FunctionMap { public FunctionMap build(String[] localNames, String[] captureNames) { return new FunctionMap(sourceMap, breakpoints, localNames, captureNames); } - public FunctionMap build(LocalScopeRecord scope) { - return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures()); + public FunctionMap build(Scope scope) { + return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); } public FunctionMap build() { return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); @@ -104,7 +103,7 @@ public class FunctionMap { var res = new ArrayList(candidates.size()); for (var candidate : candidates.entrySet()) { - var val = correctBreakpoint(new Location(line, column, candidate.getKey())); + var val = correctBreakpoint(Location.of(candidate.getKey(), line, column)); if (val == null) continue; res.add(val); } @@ -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); diff --git a/src/main/java/me/topchetoeu/jscript/common/Filename.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Filename.java similarity index 97% rename from src/main/java/me/topchetoeu/jscript/common/Filename.java rename to src/main/java/me/topchetoeu/jscript/common/parsing/Filename.java index 7ced6cb..8e57751 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Filename.java +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/Filename.java @@ -1,4 +1,4 @@ -package me.topchetoeu.jscript.common; +package me.topchetoeu.jscript.common.parsing; import java.io.File; import java.nio.file.Path; diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Location.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Location.java new file mode 100644 index 0000000..1d86866 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/Location.java @@ -0,0 +1,108 @@ +package me.topchetoeu.jscript.common.parsing; + +import java.util.ArrayList; +import java.util.Objects; + +public abstract class Location implements Comparable { + public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1); + + public abstract int line(); + public abstract int start(); + public abstract Filename filename(); + + public final String toString() { + var res = new ArrayList(); + + if (filename() != null) res.add(filename().toString()); + if (line() >= 0) res.add(line() + 1 + ""); + if (start() >= 0) res.add(start() + 1 + ""); + + return String.join(":", res); + } + + public final Location add(int n) { + var self = this; + + return new Location() { + @Override public Filename filename() { return self.filename(); } + @Override public int start() { return self.start() + n; } + @Override public int line() { return self.line(); } + }; + } + public final Location nextLine() { + var self = this; + + return new Location() { + @Override public Filename filename() { return self.filename(); } + @Override public int start() { return 0; } + @Override public int line() { return self.line() + 1; } + }; + } + + @Override public int hashCode() { + return Objects.hash(line(), start(), filename()); + } + @Override public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Location)) return false; + var other = (Location)obj; + + if (!Objects.equals(this.start(), other.start())) return false; + if (!Objects.equals(this.line(), other.line())) return false; + if (!Objects.equals(this.filename(), other.filename())) return false; + + return true; + } + + @Override public int compareTo(Location other) { + int a = filename().toString().compareTo(other.filename().toString()); + int b = Integer.compare(line(), other.line()); + int c = Integer.compare(start(), other.start()); + + if (a != 0) return a; + if (b != 0) return b; + + return c; + } + + public static Location of(Filename filename, int line, int start) { + return new Location() { + @Override public Filename filename() { return filename; } + @Override public int start() { return start; } + @Override public int line() { return line; } + }; + } + + public static Location of(String raw) { + var i0 = raw.lastIndexOf(':'); + if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1); + + var i1 = raw.lastIndexOf(':', i0); + if (i0 < 0) { + try { + return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1); + } + catch (NumberFormatException e) { + return Location.of(Filename.parse(raw), -1, -1); + } + } + + int start, line; + + try { + start = Integer.parseInt(raw.substring(i1 + 1)); + } + catch (NumberFormatException e) { + return Location.of(Filename.parse(raw), -1, -1); + } + + try { + line = Integer.parseInt(raw.substring(i0 + 1, i1)); + } + catch (NumberFormatException e) { + return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1); + } + + return Location.of(Filename.parse(raw.substring(0, i0)), start, line); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java new file mode 100644 index 0000000..17873e4 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java @@ -0,0 +1,74 @@ +package me.topchetoeu.jscript.common.parsing; + +public class ParseRes { + public static enum State { + SUCCESS, + FAILED, + ERROR; + + public boolean isSuccess() { return this == SUCCESS; } + public boolean isFailed() { return this == FAILED; } + public boolean isError() { return this == ERROR; } + } + + public final ParseRes.State state; + public final Location errorLocation; + public final String error; + public final T result; + public final int n; + + private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) { + this.result = result; + this.n = readN; + this.state = state; + this.error = error; + this.errorLocation = errorLocation; + } + + public ParseRes setN(int i) { + if (!state.isSuccess()) return this; + return new ParseRes<>(state, null, null, result, i); + } + public ParseRes addN(int n) { + if (!state.isSuccess()) return this; + return new ParseRes<>(state, null, null, result, this.n + n); + } + public ParseRes chainError() { + if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); + return new ParseRes<>(state, errorLocation, error, null, 0); + } + @SuppressWarnings("unchecked") + public ParseRes chainError(Location loc, String error) { + if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0); + return (ParseRes) this; + } + + public boolean isSuccess() { return state.isSuccess(); } + public boolean isFailed() { return state.isFailed(); } + public boolean isError() { return state.isError(); } + + public static ParseRes failed() { + return new ParseRes(State.FAILED, null, null, null, 0); + } + public static ParseRes error(Location loc, String error) { + return new ParseRes<>(State.ERROR, loc, error, null, 0); + } + public static ParseRes res(T val, int i) { + return new ParseRes<>(State.SUCCESS, null, null, val, i); + } + + @SafeVarargs + @SuppressWarnings("all") + public static ParseRes first(Source src, int i, Parser ...parsers) { + int n = Parsing.skipEmpty(src, i); + ParseRes error = ParseRes.failed(); + + for (var parser : parsers) { + var res = parser.parse(src, i + n); + if (res.isSuccess()) return res.addN(n); + if (res.isError() && error.isFailed()) error = res.chainError(); + } + + return error; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java new file mode 100644 index 0000000..d40f4f4 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.common.parsing; + +public interface Parser { + public ParseRes parse(Source src, int i); +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java new file mode 100644 index 0000000..925c3d3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java @@ -0,0 +1,420 @@ +package me.topchetoeu.jscript.common.parsing; + +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class Parsing { + public static boolean isDigit(Character c) { + return c != null && c >= '0' && c <= '9'; + } + public static boolean isAny(char c, String alphabet) { + return alphabet.contains(Character.toString(c)); + } + + public static int fromHex(char c) { + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= '0' && c <= '9') return c - '0'; + return -1; + } + + public static int skipEmpty(Source src, int i) { + return skipEmpty(src, i, true); + } + + public static int skipEmpty(Source src, int i, boolean noComments) { + int n = 0; + + if (i == 0 && src.is(0, "#!")) { + while (!src.is(n, '\n')) n++; + n++; + } + + var isSingle = false; + var isMulti = false; + + while (i + n < src.size()) { + if (isSingle) { + if (src.is(i + n, '\n')) { + n++; + isSingle = false; + } + else n++; + } + else if (isMulti) { + if (src.is(i + n, "*/")) { + n += 2; + isMulti = false; + } + else n++; + } + else if (src.is(i + n, "//")) { + n += 2; + isSingle = true; + } + else if (src.is(i + n, "/*")) { + n += 2; + isMulti = true; + } + else if (src.is(i + n, Character::isWhitespace)) { + n++; + } + else break; + } + + return n; + } + + public static ParseRes parseChar(Source src, int i) { + int n = 0; + + if (src.is(i + n, '\\')) { + n++; + char c = src.at(i + n++); + + if (c == 'b') return ParseRes.res('\b', n); + else if (c == 't') return ParseRes.res('\t', n); + else if (c == 'n') return ParseRes.res('\n', n); + else if (c == 'f') return ParseRes.res('\f', n); + else if (c == 'r') return ParseRes.res('\r', n); + else if (c == '0') { + if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); + else return ParseRes.res('\0', n); + } + else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); + else if (c == 'x') { + var newC = 0; + + for (var j = 0; j < 2; j++) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); + + int val = fromHex(src.at(i + n)); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); + n++; + + newC = (newC << 4) | val; + } + + return ParseRes.res((char)newC, n); + } + else if (c == 'u') { + var newC = 0; + + for (var j = 0; j < 4; j++) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence"); + + int val = fromHex(src.at(i + n)); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence"); + n++; + + newC = (newC << 4) | val; + } + + return ParseRes.res((char)newC, n); + } + else if (c == '\n') return ParseRes.res(null, n); + } + + return ParseRes.res(src.at(i + n), n + 1); + } + + public static ParseRes parseIdentifier(Source src, int i) { + var n = skipEmpty(src, i); + var res = new StringBuilder(); + var first = true; + + while (true) { + if (i + n > src.size()) break; + char c = src.at(i + n, '\0'); + + if (first && Parsing.isDigit(c)) break; + if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; + res.append(c); + n++; + first = false; + } + + if (res.length() <= 0) return ParseRes.failed(); + else return ParseRes.res(res.toString(), n); + } + public static ParseRes parseIdentifier(Source src, int i, String test) { + var n = skipEmpty(src, i); + var res = new StringBuilder(); + var first = true; + + while (true) { + if (i + n > src.size()) break; + char c = src.at(i + n, '\0'); + + if (first && Parsing.isDigit(c)) break; + if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; + res.append(c); + n++; + first = false; + } + + if (res.length() <= 0) return ParseRes.failed(); + else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n); + else return ParseRes.failed(); + } + public static boolean isIdentifier(Source src, int i, String test) { + return parseIdentifier(src, i, test).isSuccess(); + } + + public static ParseRes parseOperator(Source src, int i, String op) { + var n = skipEmpty(src, i); + + if (src.is(i + n, op)) return ParseRes.res(op, n + op.length()); + else return ParseRes.failed(); + } + + private static ParseRes parseHex(Source src, int i) { + int n = 0; + double res = 0; + + while (true) { + int digit = Parsing.fromHex(src.at(i + n, '\0')); + if (digit < 0) { + if (n <= 0) return ParseRes.failed(); + else return ParseRes.res(res, n); + } + n++; + + res *= 16; + res += digit; + } + } + private static ParseRes parseOct(Source src, int i) { + int n = 0; + double res = 0; + + while (true) { + int digit = src.at(i + n, '\0') - '0'; + if (digit < 0 || digit > 9) break; + if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit); + + if (digit < 0) { + if (n <= 0) return ParseRes.failed(); + else return ParseRes.res(res, n); + } + n++; + + res *= 8; + res += digit; + } + + return ParseRes.res(res, n); + } + + public static ParseRes parseString(Source src, int i) { + var n = skipEmpty(src, i); + + char quote; + + if (src.is(i + n, '\'')) quote = '\''; + else if (src.is(i + n, '"')) quote = '"'; + else return ParseRes.failed(); + n++; + + var res = new StringBuilder(); + + while (true) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal"); + if (src.is(i + n, quote)) { + n++; + return ParseRes.res(res.toString(), n); + } + + var charRes = parseChar(src, i + n); + if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character"); + n += charRes.n; + + if (charRes.result != null) res.append(charRes.result); + } + } + public static ParseRes parseNumber(Source src, int i, boolean withMinus) { + var n = skipEmpty(src, i); + + double whole = 0; + double fract = 0; + long exponent = 0; + boolean parsedAny = false; + boolean negative = false; + + if (withMinus && src.is(i + n, "-")) { + negative = true; + n++; + } + + if (src.is(i + n, "0x") || src.is(i + n, "0X")) { + n += 2; + + var res = parseHex(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); + n += res.n; + + if (negative) return ParseRes.res(-res.result, n); + else return ParseRes.res(res.result, n); + } + else if (src.is(i + n, "0o") || src.is(i + n, "0O")) { + n += 2; + + var res = parseOct(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal"); + n += res.n; + + if (negative) return ParseRes.res(-res.result, n); + else return ParseRes.res(res.result, n); + } + else if (src.is(i + n, '0')) { + n++; + parsedAny = true; + if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed"); + } + + while (src.is(i + n, Parsing::isDigit)) { + parsedAny = true; + whole *= 10; + whole += src.at(i + n++) - '0'; + } + + if (src.is(i + n, '.')) { + parsedAny = true; + n++; + + while (src.is(i + n, Parsing::isDigit)) { + fract += src.at(i + n++) - '0'; + fract /= 10; + } + } + + if (src.is(i + n, 'e') || src.is(i + n, 'E')) { + n++; + parsedAny = true; + boolean expNegative = false; + boolean parsedE = false; + + if (src.is(i + n, '-')) { + expNegative = true; + n++; + } + else if (src.is(i + n, '+')) n++; + + while (src.is(i + n, Parsing::isDigit)) { + parsedE = true; + exponent *= 10; + + if (expNegative) exponent -= src.at(i + n++) - '0'; + else exponent += src.at(i + n++) - '0'; + } + + if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); + } + + if (!parsedAny) { + if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); + return ParseRes.failed(); + } + else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n); + else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n); + } + public static ParseRes parseFloat(Source src, int i, boolean withMinus) { + var n = skipEmpty(src, i); + + double whole = 0; + double fract = 0; + long exponent = 0; + boolean parsedAny = false; + boolean negative = false; + + if (withMinus && src.is(i + n, "-")) { + negative = true; + n++; + } + + while (src.is(i + n, Parsing::isDigit)) { + parsedAny = true; + whole *= 10; + whole += src.at(i + n++) - '0'; + } + + if (src.is(i + n, '.')) { + parsedAny = true; + n++; + + while (src.is(i + n, Parsing::isDigit)) { + fract += src.at(i + n++) - '0'; + fract /= 10; + } + } + + if (src.is(i + n, 'e') || src.is(i + n, 'E')) { + n++; + parsedAny = true; + boolean expNegative = false; + boolean parsedE = false; + + if (src.is(i + n, '-')) { + expNegative = true; + n++; + } + else if (src.is(i + n, '+')) n++; + + while (src.is(i + n, Parsing::isDigit)) { + parsedE = true; + exponent *= 10; + + if (expNegative) exponent -= src.at(i + n++) - '0'; + else exponent += src.at(i + n++) - '0'; + } + + if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); + } + + if (!parsedAny) { + if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); + return ParseRes.failed(); + } + else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n); + else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n); + } + public static ParseRes parseInt(Source src, int i, String alphabet, boolean withMinus) { + var n = skipEmpty(src, i); + + double result = 0; + boolean parsedAny = false; + boolean negative = false; + + if (withMinus && src.is(i + n, "-")) { + negative = true; + n++; + } + + if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) { + n += 2; + + var res = parseHex(src, i); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); + n += res.n; + + if (negative) return ParseRes.res(-res.result, n); + else return ParseRes.res(res.result, n); + } + + while (true) { + var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n))); + if (digit < 0) break; + + parsedAny = true; + result += digit; + result *= alphabet.length(); + } + + if (!parsedAny) { + if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); + return ParseRes.failed(); + } + else if (negative) return ParseRes.res(-result, n); + else return ParseRes.res(-result, n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Source.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Source.java new file mode 100644 index 0000000..d04c1e4 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/Source.java @@ -0,0 +1,75 @@ +package me.topchetoeu.jscript.common.parsing; + +import java.util.function.Predicate; + +import me.topchetoeu.jscript.common.environment.Environment; + +public class Source { + public final Environment env; + public final Filename filename; + public final String src; + + private int[] lineStarts; + + public Location loc(int offset) { + return new SourceLocation(filename, lineStarts, offset); + } + public boolean is(int i, char c) { + return i >= 0 && i < src.length() && src.charAt(i) == c; + } + public boolean is(int i, String src) { + if (i < 0 || i + src.length() > size()) return false; + + for (int j = 0; j < src.length(); j++) { + if (at(i + j) != src.charAt(j)) return false; + } + + return true; + } + public boolean is(int i, Predicate predicate) { + if (i < 0 || i >= src.length()) return false; + return predicate.test(at(i)); + } + public char at(int i) { + return src.charAt(i); + } + public char at(int i, char defaultVal) { + if (i < 0 || i >= src.length()) return defaultVal; + else return src.charAt(i); + } + public int size() { + return src.length(); + } + public String slice(int start, int end) { + return src.substring(start, end); + } + + public Source(Environment env, Filename filename, String src) { + if (env == null) this.env = new Environment(); + else this.env = env; + + this.filename = filename; + this.src = src; + + int n = 1; + lineStarts = new int[16]; + lineStarts[0] = 0; + + for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) { + if (n >= lineStarts.length) { + var newArr = new int[lineStarts.length * 2]; + System.arraycopy(lineStarts, 0, newArr, 0, n); + lineStarts = newArr; + } + + lineStarts[n++] = i + 1; + } + + var newArr = new int[n]; + System.arraycopy(lineStarts, 0, newArr, 0, n); + lineStarts = newArr; + } + public Source(String src) { + this(null, null, src); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java b/src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java new file mode 100644 index 0000000..5819565 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java @@ -0,0 +1,66 @@ +package me.topchetoeu.jscript.common.parsing; + +import java.util.Objects; + +public class SourceLocation extends Location { + private int[] lineStarts; + private int line; + private int start; + private final Filename filename; + private final int offset; + + private void update() { + if (lineStarts == null) return; + + int a = 0; + int b = lineStarts.length; + + while (true) { + if (a + 1 >= b) break; + var mid = -((-a - b) >> 1); + var el = lineStarts[mid]; + + if (el < offset) a = mid; + else if (el > offset) b = mid; + else { + this.line = mid; + this.start = 0; + this.lineStarts = null; + return; + } + } + + this.line = a; + this.start = offset - lineStarts[a]; + this.lineStarts = null; + return; + } + + @Override public Filename filename() { return filename; } + @Override public int line() { + update(); + return line; + } + @Override public int start() { + update(); + return start; + } + + @Override public int hashCode() { + return Objects.hash(offset); + } + @Override public int compareTo(Location other) { + if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset); + else return super.compareTo(other); + } + @Override public boolean equals(Object obj) { + if (obj instanceof SourceLocation other) return this.offset == other.offset; + else return super.equals(obj); + } + + public SourceLocation(Filename filename, int[] lineStarts, int offset) { + this.filename = filename; + this.lineStarts = lineStarts; + this.offset = offset; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java new file mode 100644 index 0000000..e684c44 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Operation; + +public interface AssignableNode { + public abstract Node toAssign(Node val, Operation operation); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java deleted file mode 100644 index 9602c2e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; - -public abstract class AssignableStatement extends Statement { - public abstract Statement toAssign(Statement val, Operation operation); - - protected AssignableStatement(Location loc) { - super(loc); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index 00f6db7..4234653 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -1,23 +1,40 @@ package me.topchetoeu.jscript.compilation; import java.util.List; +import java.util.ArrayList; import java.util.LinkedList; -import java.util.Vector; +import java.util.function.Consumer; +import java.util.function.IntFunction; 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.environment.Environment; import me.topchetoeu.jscript.common.mapping.FunctionMap; import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder; -import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.scope.Scope; -public class CompileResult { - public final Vector instructions = new Vector<>(); - public final List children = new LinkedList<>(); - public final FunctionMapBuilder map = FunctionMap.builder(); - public final LocalScopeRecord scope; - public int length = 0; +public final class CompileResult { + public static final class ChildData { + public final int id; + public final CompileResult result; + + public ChildData(int id, CompileResult result) { + this.result = result; + this.id = id; + } + } + + public final List> instructions; + public final List children; + public final FunctionMapBuilder map; + public final Environment env; + public int length; + public Runnable buildTask = () -> { + throw new IllegalStateException("Compile result is not ready to be built"); + }; + public final Scope scope; public int temp() { instructions.add(null); @@ -25,15 +42,20 @@ public class CompileResult { } public CompileResult add(Instruction instr) { + instructions.add(i -> instr); + return this; + } + public CompileResult add(IntFunction instr) { instructions.add(instr); return this; } public CompileResult set(int i, Instruction instr) { - instructions.set(i, instr); + instructions.set(i, _i -> instr); return this; } - public Instruction get(int i) { - return instructions.get(i); + public CompileResult set(int i, IntFunctioninstr) { + instructions.set(i, instr); + return this; } public int size() { return instructions.size(); } @@ -56,9 +78,19 @@ public class CompileResult { setLocationAndDebug(instructions.size() - 1, loc, type); } - public CompileResult addChild(CompileResult child) { - this.children.add(child); - return child; + public int addChild(CompileResult res) { + this.children.add(res); + return this.children.size() - 1; + } + + public Instruction[] instructions() { + var res = new Instruction[instructions.size()]; + var i = 0; + for (var suppl : instructions) { + res[i] = suppl.apply(i); + i++; + } + return res; } public FunctionMap map() { @@ -69,10 +101,38 @@ public class CompileResult { for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body(); - return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren); + var instrRes = new Instruction[instructions.size()]; + var i = 0; + + for (var suppl : instructions) { + instrRes[i] = suppl.apply(i); + i++; + } + + return new FunctionBody( + scope.localsCount() + scope.allocCount(), scope.capturesCount(), + length, instrRes, builtChildren + ); } - public CompileResult(LocalScopeRecord scope) { + public CompileResult subtarget() { + return new CompileResult(new Scope(scope), this); + } + + public CompileResult(Environment env, Scope scope, int length, Consumer task) { this.scope = scope; + this.instructions = new ArrayList<>(); + this.children = new LinkedList<>(); + this.map = FunctionMap.builder(); + this.env = env; + this.length = length; + this.buildTask = () -> task.accept(this); + } + private CompileResult(Scope scope, CompileResult parent) { + this.scope = scope; + this.instructions = parent.instructions; + this.children = parent.children; + this.map = parent.map; + this.env = parent.env; } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java new file mode 100644 index 0000000..29888f6 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -0,0 +1,134 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +import java.util.List; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; + + +public class CompoundNode extends Node { + public final Node[] statements; + public final boolean hasScope; + public Location end; + + @Override public void resolve(CompileResult target) { + for (var stm : statements) stm.resolve(target); + } + + public void compile(CompileResult target, boolean pollute, boolean singleEntry, BreakpointType type) { + List statements = new ArrayList(); + + var subtarget = hasScope ? target.subtarget() : target; + if (hasScope) { + subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); + subtarget.scope.singleEntry = singleEntry; + } + + for (var stm : this.statements) { + if (stm instanceof FunctionStatementNode func) { + func.compile(subtarget, false); + } + else statements.add(stm); + } + + var polluted = false; + + for (var i = 0; i < statements.size(); i++) { + var stm = statements.get(i); + + if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER); + else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); + } + + if (hasScope) { + subtarget.scope.end(); + subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount())); + } + + if (!polluted && pollute) { + target.add(Instruction.pushUndefined()); + } + } + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { + compile(target, pollute, true, type); + } + + public CompoundNode setEnd(Location loc) { + this.end = loc; + return this; + } + + public CompoundNode(Location loc, boolean hasScope, Node ...statements) { + super(loc); + this.hasScope = hasScope; + this.statements = statements; + } + + public static void compileMultiEntry(Node node, CompileResult target, boolean pollute, BreakpointType type) { + if (node instanceof CompoundNode comp) { + comp.compile(target, pollute, false, type); + } + else { + node.compile(target, pollute, type); + } + } + + public static ParseRes parseComma(Source src, int i, Node prev, int precedence) { + if (precedence > 1) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, ",")) return ParseRes.failed(); + n++; + + var curr = JavaScript.parseExpression(src, i + n, 2); + if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma"); + n += curr.n; + + if (prev instanceof CompoundNode comp) { + var children = new ArrayList(); + children.addAll(List.of(comp.statements)); + children.add(curr.result); + + return ParseRes.res(new CompoundNode(loc, comp.hasScope, children.toArray(Node[]::new)), n); + } + else return ParseRes.res(new CompoundNode(loc, false, prev, curr.result), n); + } + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; + + var statements = new ArrayList(); + + while (true) { + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { + n++; + break; + } + if (src.is(i + n, ";")) { + n++; + continue; + } + + var res = JavaScript.parseStatement(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement"); + n += res.n; + + statements.add(res.result); + } + + return ParseRes.res(new CompoundNode(loc, true, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java deleted file mode 100644 index 06eed66..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ /dev/null @@ -1,64 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.List; -import java.util.Vector; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.values.FunctionStatement; - -public class CompoundStatement extends Statement { - public final Statement[] statements; - public final boolean separateFuncs; - public Location end; - - @Override public boolean pure() { - for (var stm : statements) { - if (!stm.pure()) return false; - } - - return true; - } - - @Override - public void declare(CompileResult target) { - for (var stm : statements) stm.declare(target); - } - - @Override - public void compile(CompileResult target, boolean pollute, BreakpointType type) { - List statements = new Vector(); - if (separateFuncs) for (var stm : this.statements) { - if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) { - stm.compile(target, false); - } - else statements.add(stm); - } - else statements = List.of(this.statements); - - var polluted = false; - - for (var i = 0; i < statements.size(); i++) { - var stm = statements.get(i); - - if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER); - else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER); - } - - if (!polluted && pollute) { - target.add(Instruction.pushUndefined()); - } - } - - public CompoundStatement setEnd(Location loc) { - this.end = loc; - return this; - } - - public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) { - super(loc); - this.separateFuncs = separateFuncs; - this.statements = statements; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java b/src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java new file mode 100644 index 0000000..e0cb484 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.function.IntSupplier; + +public final class DeferredIntSupplier implements IntSupplier { + private int value; + private boolean set; + + public void set(int val) { + if (set) throw new RuntimeException("A deferred int supplier may be set only once"); + value = val; + set = true; + } + + @Override public int getAsInt() { + if (!set) throw new RuntimeException("Deferred int supplier accessed too early"); + return value; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java new file mode 100644 index 0000000..036533c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java @@ -0,0 +1,74 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.List; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.control.ReturnNode; + +public class FunctionArrowNode extends FunctionNode { + @Override public String name() { return null; } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + var id = target.addChild(compileBody(target, name, null)); + target.add(_i -> Instruction.loadFunc(id, true, false, true, null, captures(id, target))); + } + + public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) { + super(loc, end, params, expToBody(body)); + } + + private static final CompoundNode expToBody(Node node) { + if (node instanceof CompoundNode res) return res; + else return new CompoundNode(node.loc(), false, new ReturnNode(node.loc(), node)); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + Parameters params; + + if (src.is(i + n, "(")) { + var paramsRes = JavaScript.parseParameters(src, i + n); + if (!paramsRes.isSuccess()) return paramsRes.chainError(); + n += paramsRes.n; + n += Parsing.skipEmpty(src, i + n); + + params = paramsRes.result; + } + else { + var singleParam = Parsing.parseIdentifier(src, i + n); + if (!singleParam.isSuccess()) return ParseRes.failed(); + + var paramLoc = src.loc(i + n); + n += singleParam.n; + n += Parsing.skipEmpty(src, i + n); + + params = new Parameters(List.of(new Parameter(paramLoc, singleParam.result, null))); + } + + if (!src.is(i + n, "=>")) return ParseRes.failed(); + n += 2; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "{")) { + var body = CompoundNode.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'"); + n += body.n; + + return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n); + } + else { + var body = JavaScript.parseExpression(src, i + n, 2); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'"); + n += body.n; + + return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java new file mode 100644 index 0000000..154b360 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -0,0 +1,157 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.scope.FunctionScope; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public abstract class FunctionNode extends Node { + public final CompoundNode body; + public final Parameters params; + public final Location end; + + public abstract String name(); + + protected final int[] captures(int id, CompileResult target) { + return ((FunctionScope)target.children.get(id).scope).getCaptureIndices(); + } + + public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) { + var name = this.name() != null ? this.name() : _name; + + env = env.child() + .remove(LabelContext.BREAK_CTX) + .remove(LabelContext.CONTINUE_CTX); + + return new CompileResult(env, scope, params.params.size(), target -> { + // if (params.params.size() > 0) target.add(Instruction.loadArgs(true)); + + // if (hasArgs) { + // var argsVar = scope.defineStrict(new Variable("arguments", true), loc()); + // target.add(_i -> Instruction.storeVar(argsVar.index(), params.params.size() > 0)); + // } + + if (params.params.size() > 0) { + target.add(Instruction.loadArgs(true)); + if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1)); + var i = 0; + + for (var param : params.params) { + if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); + if (!JavaScript.checkVarName(param.name)) { + throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name)); + } + var varI = scope.define(new Variable(param.name, false), param.loc); + + target.add(Instruction.loadMember(i++)); + + if (param.node != null) { + var end = new DeferredIntSupplier(); + + target.add(Instruction.dup()); + target.add(Instruction.pushUndefined()); + target.add(Instruction.operation(Operation.EQUALS)); + target.add(Instruction.jmpIfNot(end)); + target.add(Instruction.discard()); + param.node.compile(target, true); + + end.set(target.size()); + } + + target.add(_i -> Instruction.storeVar(varI.index())); + } + } + + if (params.restName != null) { + if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed"); + var restVar = scope.define(new Variable(params.restName, false), params.restLocation); + target.add(Instruction.loadRestArgs(params.params.size())); + target.add(_i -> Instruction.storeVar(restVar.index())); + } + + if (selfName != null && !scope.has(name, false)) { + var i = scope.defineSpecial(new Variable(selfName, true), end); + + target.add(Instruction.loadCallee()); + target.add(_i -> Instruction.storeVar(i.index(), false)); + } + + body.resolve(target); + body.compile(target, lastReturn, BreakpointType.NONE); + + scope.end(); + + for (var child : target.children) child.buildTask.run(); + + scope.finish(); + }); + } + public final CompileResult compileBody(CompileResult parent, String name, String selfName) { + return compileBody(parent.env, new FunctionScope(parent.scope), false, name, selfName); + } + + public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp); + public void compile(CompileResult target, boolean pollute, String name) { + compile(target, pollute, name, BreakpointType.NONE); + } + @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { + compile(target, pollute, (String)null, bp); + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, (String)null, BreakpointType.NONE); + } + + public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) { + super(loc); + + this.end = end; + this.params = params; + this.body = body; + } + + public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { + if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name); + else stm.compile(target, pollute); + } + public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) { + if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp); + else stm.compile(target, pollute, bp); + } + + public static ParseRes parseFunction(Source src, int i, boolean statement) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed(); + n += 8; + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + var params = JavaScript.parseParameters(src, i + n); + if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list"); + n += params.n; + + var body = CompoundNode.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function"); + n += body.n; + + if (statement) return ParseRes.res(new FunctionStatementNode( + loc, src.loc(i + n - 1), + params.result, body.result, name.result + ), n); + else return ParseRes.res(new FunctionValueNode( + loc, src.loc(i + n - 1), + params.result, body.result, name.result + ), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java new file mode 100644 index 0000000..550730a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class FunctionStatementNode extends FunctionNode { + public final String name; + + @Override public String name() { return name; } + + @Override public void resolve(CompileResult target) { + target.scope.define(new Variable(name, false), end); + } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + var id = target.addChild(compileBody(target, name, null)); + target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target))); + target.add(VariableNode.toSet(target, end, this.name, pollute, true)); + } + + public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { + super(loc, end, params, body); + this.name = name; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java new file mode 100644 index 0000000..db22d72 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java @@ -0,0 +1,21 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; + +public class FunctionValueNode extends FunctionNode { + public final String name; + + @Override public String name() { return name; } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + var id = target.addChild(compileBody(target, name, null)); + target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target))); + } + + public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { + super(loc, end, params, body); + this.name = name; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java new file mode 100644 index 0000000..5e06b59 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -0,0 +1,353 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.control.BreakNode; +import me.topchetoeu.jscript.compilation.control.ContinueNode; +import me.topchetoeu.jscript.compilation.control.DebugNode; +import me.topchetoeu.jscript.compilation.control.DeleteNode; +import me.topchetoeu.jscript.compilation.control.DoWhileNode; +import me.topchetoeu.jscript.compilation.control.ForInNode; +import me.topchetoeu.jscript.compilation.control.ForNode; +import me.topchetoeu.jscript.compilation.control.ForOfNode; +import me.topchetoeu.jscript.compilation.control.IfNode; +import me.topchetoeu.jscript.compilation.control.ReturnNode; +import me.topchetoeu.jscript.compilation.control.SwitchNode; +import me.topchetoeu.jscript.compilation.control.ThrowNode; +import me.topchetoeu.jscript.compilation.control.TryNode; +import me.topchetoeu.jscript.compilation.control.WhileNode; +import me.topchetoeu.jscript.compilation.scope.FunctionScope; +import me.topchetoeu.jscript.compilation.values.ArgumentsNode; +import me.topchetoeu.jscript.compilation.values.ArrayNode; +import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.RegexNode; +import me.topchetoeu.jscript.compilation.values.ThisNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.values.constants.BoolNode; +import me.topchetoeu.jscript.compilation.values.constants.NullNode; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.compilation.values.operations.CallNode; +import me.topchetoeu.jscript.compilation.values.operations.ChangeNode; +import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; +import me.topchetoeu.jscript.compilation.values.operations.IndexNode; +import me.topchetoeu.jscript.compilation.values.operations.OperationNode; +import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public final class JavaScript { + public static enum DeclarationType { + VAR(false, false), + CONST(true, true), + LET(true, false); + + public final boolean strict, readonly; + + private DeclarationType(boolean strict, boolean readonly) { + this.strict = strict; + this.readonly = readonly; + } + } + + static final Set reserved = Set.of( + "true", "false", "void", "null", "this", "if", "else", "try", "catch", + "finally", "for", "do", "while", "switch", "case", "default", "new", + "function", "var", "return", "throw", "typeof", "delete", "break", + "continue", "debugger", "implements", "interface", "package", "private", + "protected", "public", "static", "arguments" + ); + + public static ParseRes parseParens(Source src, int i) { + int n = 0; + + var openParen = Parsing.parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(); + n += openParen.n; + + var res = JavaScript.parseExpression(src, i + n, 0); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens"); + n += res.n; + + var closeParen = Parsing.parseOperator(src, i + n, ")"); + if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren"); + n += closeParen.n; + + return ParseRes.res(res.result, n); + } + + public static ParseRes parseSimple(Source src, int i, boolean statement) { + return ParseRes.first(src, i, + (s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j), + (s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false), + JavaScript::parseLiteral, + StringNode::parse, + RegexNode::parse, + NumberNode::parse, + ChangeNode::parsePrefixDecrease, + ChangeNode::parsePrefixIncrease, + OperationNode::parsePrefix, + ArrayNode::parse, + FunctionArrowNode::parse, + JavaScript::parseParens, + CallNode::parseNew, + TypeofNode::parse, + DiscardNode::parse, + DeleteNode::parse, + VariableNode::parse + ); + } + + public static ParseRes parseLiteral(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var id = Parsing.parseIdentifier(src, i); + if (!id.isSuccess()) return id.chainError(); + n += id.n; + + if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n); + if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n); + if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n); + if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n); + if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n); + + return ParseRes.failed(); + } + + public static ParseRes parseExpression(Source src, int i, int precedence, boolean statement) { + var n = Parsing.skipEmpty(src, i); + Node prev = null; + + while (true) { + if (prev == null) { + var res = parseSimple(src, i + n, statement); + if (res.isSuccess()) { + n += res.n; + prev = res.result; + } + else if (res.isError()) return res.chainError(); + else break; + } + else { + var _prev = prev; + ParseRes res = ParseRes.first(src, i + n, + (s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence), + (s, j) -> OperationNode.parseIn(s, j, _prev, precedence), + (s, j) -> ChangeNode.parsePostfixIncrease(s, j, _prev, precedence), + (s, j) -> ChangeNode.parsePostfixDecrease(s, j, _prev, precedence), + (s, j) -> OperationNode.parseOperator(s, j, _prev, precedence), + (s, j) -> IfNode.parseTernary(s, j, _prev, precedence), + (s, j) -> IndexNode.parseMember(s, j, _prev, precedence), + (s, j) -> IndexNode.parseIndex(s, j, _prev, precedence), + (s, j) -> CallNode.parseCall(s, j, _prev, precedence), + (s, j) -> CompoundNode.parseComma(s, j, _prev, precedence) + ); + + if (res.isSuccess()) { + n += res.n; + prev = res.result; + continue; + } + else if (res.isError()) return res.chainError(); + + break; + } + } + + if (prev == null) return ParseRes.failed(); + else return ParseRes.res(prev, n); + } + + public static ParseRes parseExpression(Source src, int i, int precedence) { + return parseExpression(src, i, precedence, false); + } + + public static ParseRes parseExpressionStatement(Source src, int i) { + var res = parseExpression(src, i, 0, true); + if (!res.isSuccess()) return res.chainError(); + + var end = JavaScript.parseStatementEnd(src, i + res.n); + if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement"); + + return res.addN(end.n); + } + + public static ParseRes parseStatement(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1); + if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); + + ParseRes res = ParseRes.first(src, i + n, + VariableDeclareNode::parse, + ReturnNode::parse, + ThrowNode::parse, + ContinueNode::parse, + BreakNode::parse, + DebugNode::parse, + IfNode::parse, + WhileNode::parse, + SwitchNode::parse, + ForNode::parse, + ForInNode::parse, + ForOfNode::parse, + DoWhileNode::parse, + TryNode::parse, + CompoundNode::parse, + (s, j) -> FunctionNode.parseFunction(s, j, true), + JavaScript::parseExpressionStatement + ); + return res.addN(n); + } + + public static ParseRes parseStatementEnd(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + if (i >= src.size()) return ParseRes.res(true, n + 1); + + for (var j = i; j < i + n; j++) { + if (src.is(j, '\n')) return ParseRes.res(true, n); + } + + if (src.is(i + n, ';')) return ParseRes.res(true, n + 1); + if (src.is(i + n, '}')) return ParseRes.res(true, n); + + return ParseRes.failed(); + } + + public static ParseRes parseParameters(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var openParen = Parsing.parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list"); + n += openParen.n; + + var params = new ArrayList(); + + var closeParen = Parsing.parseOperator(src, i + n, ")"); + n += closeParen.n; + + if (!closeParen.isSuccess()) { + while (true) { + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "...")) { + n += 3; + var restLoc = src.loc(i); + + var restName = Parsing.parseIdentifier(src, i + n); + if (!restName.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter"); + n += restName.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter"); + n++; + + return ParseRes.res(new Parameters(params, restName.result, restLoc), n); + } + + var paramLoc = src.loc(i); + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "=")) { + n++; + + var val = parseExpression(src, i + n, 2); + if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter default value"); + n += val.n; + n += Parsing.skipEmpty(src, i + n); + + params.add(new Parameter(paramLoc, name.result, val.result)); + } + else params.add(new Parameter(paramLoc, name.result, null)); + + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + } + + if (src.is(i + n, ")")) { + n++; + break; + } + } + } + + return ParseRes.res(new Parameters(params), n); + } + + public static ParseRes parseDeclarationType(Source src, int i) { + var res = Parsing.parseIdentifier(src, i); + if (!res.isSuccess()) return res.chainError(); + + if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n); + if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n); + if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n); + + return ParseRes.failed(); + } + + public static Node[] parse(Environment env, Filename filename, String raw) { + var src = new Source(env, filename, raw); + var list = new ArrayList(); + int i = 0; + + while (true) { + if (i >= src.size()) break; + + var res = parseStatement(src, i); + + if (res.isError()) throw new SyntaxException(res.errorLocation, res.error); + else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); + + i += res.n; + + list.add(res.result); + } + + return list.toArray(Node[]::new); + } + + public static boolean checkVarName(String name) { + return !JavaScript.reserved.contains(name); + } + + public static CompileResult compile(Environment env, Node ...statements) { + var func = new FunctionValueNode(null, null, new Parameters(List.of()), new CompoundNode(null, true, statements), null); + var res = func.compileBody(env, new FunctionScope(true), true, null, null); + res.buildTask.run(); + return res; + } + + public static CompileResult compile(Environment env, Filename filename, String raw) { + return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); + } + public static CompileResult compile(Filename filename, String raw) { + var env = new Environment(); + return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); + } + + public static ParseRes parseLabel(Source src, int i) { + int n = Parsing.skipEmpty(src, i); + + var nameRes = Parsing.parseIdentifier(src, i + n); + if (!nameRes.isSuccess()) return nameRes.chainError(); + n += nameRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.failed(); + n++; + + return ParseRes.res(nameRes.result, n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java b/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java new file mode 100644 index 0000000..0dea994 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java @@ -0,0 +1,85 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.function.IntFunction; +import java.util.function.IntSupplier; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class LabelContext { + public static final Key BREAK_CTX = Key.of(); + public static final Key CONTINUE_CTX = Key.of(); + + private final LinkedList list = new LinkedList<>(); + private final HashMap map = new HashMap<>(); + + public IntSupplier get() { + return list.peekLast(); + } + public IntSupplier get(String name) { + return map.get(name); + } + + public IntFunction getJump() { + var res = get(); + if (res == null) return null; + else return i -> Instruction.jmp(res.getAsInt() - i); + } + public IntFunction getJump(String name) { + var res = get(name); + if (res == null) return null; + else return i -> Instruction.jmp(res.getAsInt() - i); + } + + public void push(IntSupplier jumpTarget) { + list.add(jumpTarget); + } + public void push(Location loc, String name, IntSupplier jumpTarget) { + if (name == null) return; + if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name)); + map.put(name, jumpTarget); + } + + public void pushLoop(Location loc, String name, IntSupplier jumpTarget) { + push(jumpTarget); + push(loc, name, jumpTarget); + } + + public void pop() { + list.removeLast(); + } + public void pop(String name) { + if (name == null) return; + map.remove(name); + } + + public void popLoop(String name) { + pop(); + pop(name); + } + + public static LabelContext getBreak(Environment env) { + return env.initFrom(BREAK_CTX, () -> new LabelContext()); + } + public static LabelContext getCont(Environment env) { + return env.initFrom(CONTINUE_CTX, () -> new LabelContext()); + } + + public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) { + LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); + LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget); + } + public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) { + LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); + LabelContext.getCont(env).pushLoop(loc, name, contTarget); + } + public static void popLoop(Environment env, String name) { + LabelContext.getBreak(env).popLoop(name); + LabelContext.getCont(env).popLoop(name); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Statement.java b/src/main/java/me/topchetoeu/jscript/compilation/Node.java similarity index 55% rename from src/main/java/me/topchetoeu/jscript/compilation/Statement.java rename to src/main/java/me/topchetoeu/jscript/compilation/Node.java index de9d23b..7da1c3e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/Statement.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/Node.java @@ -1,13 +1,12 @@ package me.topchetoeu.jscript.compilation; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; -public abstract class Statement { - private Location _loc; +public abstract class Node { + private Location loc; - public boolean pure() { return false; } - public void declare(CompileResult target) { } + public void resolve(CompileResult target) {} public void compile(CompileResult target, boolean pollute, BreakpointType type) { int start = target.size(); @@ -18,10 +17,10 @@ public abstract class Statement { compile(target, pollute, BreakpointType.NONE); } - public Location loc() { return _loc; } - public void setLoc(Location loc) { _loc = loc; } + public Location loc() { return loc; } + public void setLoc(Location loc) { this.loc = loc; } - protected Statement(Location loc) { - this._loc = loc; + protected Node(Location loc) { + this.loc = loc; } } \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java b/src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java new file mode 100644 index 0000000..920c6d0 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java @@ -0,0 +1,93 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.function.Function; + +public final class NodeChildren implements Iterable { + public static final class Slot { + private Node node; + private final Function replacer; + + public final void replace(Node node) { + this.node = this.replacer.apply(node); + } + + public Slot(Node nodes, Function replacer) { + this.node = nodes; + this.replacer = replacer; + } + } + + private final Slot[] slots; + + private NodeChildren(Slot[] slots) { + this.slots = slots; + } + + @Override public Iterator iterator() { + return new Iterator() { + private int i = 0; + private Slot[] arr = slots; + + @Override public boolean hasNext() { + if (arr == null) return false; + else if (i >= arr.length) { + arr = null; + return false; + } + else return true; + } + @Override public Node next() { + if (!hasNext()) return null; + return arr[i++].node; + } + }; + } + public Iterable slots() { + return () -> new Iterator() { + private int i = 0; + private Slot[] arr = slots; + + @Override public boolean hasNext() { + if (arr == null) return false; + else if (i >= arr.length) { + arr = null; + return false; + } + else return true; + } + @Override public Slot next() { + if (!hasNext()) return null; + return arr[i++]; + } + }; + } + + public static final class Builder { + private final ArrayList slots = new ArrayList<>(); + + public final Builder add(Slot ...children) { + for (var child : children) { + this.slots.add(child); + } + + return this; + } + public final Builder add(Iterable children) { + for (var child : children) { + this.slots.add(child); + } + + return this; + } + public final Builder add(Node child, Function replacer) { + slots.add(new Slot(child, replacer)); + return this; + } + + public final NodeChildren build() { + return new NodeChildren(slots.toArray(Slot[]::new)); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameter.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameter.java new file mode 100644 index 0000000..9404d5d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/Parameter.java @@ -0,0 +1,15 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.parsing.Location; + +public final class Parameter { + public final Location loc; + public final String name; + public final Node node; + + public Parameter(Location loc, String name, Node node) { + this.name = name; + this.node = node; + this.loc = loc; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java new file mode 100644 index 0000000..307170f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java @@ -0,0 +1,29 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.List; + +import me.topchetoeu.jscript.common.parsing.Location; + +public final class Parameters { + public final int length; + public final List params; + public final String restName; + public final Location restLocation; + + public Parameters(List params, String restName, Location restLocation) { + var len = params.size(); + + for (var i = params.size() - 1; i >= 0; i--) { + if (params.get(i).node == null) break; + len--; + } + + this.params = params; + this.length = len; + this.restName = restName; + this.restLocation = restLocation; + } + public Parameters(List params) { + this(params, null, null); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java deleted file mode 100644 index 523e10a..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class ThrowSyntaxStatement extends Statement { - public final String name; - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.throwSyntax(name)); - } - - public ThrowSyntaxStatement(SyntaxException e) { - super(e.loc); - this.name = e.msg; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java new file mode 100644 index 0000000..7584d48 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -0,0 +1,123 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +import java.util.List; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class VariableDeclareNode extends Node { + public static class Pair { + public final String name; + public final Node value; + public final Location location; + + public Pair(String name, Node value, Location location) { + this.name = name; + this.value = value; + this.location = location; + } + } + + public final List values; + public final DeclarationType declType; + + @Override public void resolve(CompileResult target) { + if (!declType.strict) { + for (var entry : values) { + target.scope.define(new Variable(entry.name, false), entry.location); + } + } + } + @Override public void compile(CompileResult target, boolean pollute) { + for (var entry : values) { + if (entry.name == null) continue; + if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location); + + if (entry.value != null) { + FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); + target.add(VariableNode.toSet(target, entry.location, entry.name, false, true)); + } + else target.add(_i -> { + var i = target.scope.get(entry.name, false); + + if (i == null) return Instruction.globDef(entry.name); + else return Instruction.nop(); + }); + } + + if (pollute) target.add(Instruction.pushUndefined()); + } + + public VariableDeclareNode(Location loc, DeclarationType declType, List values) { + super(loc); + this.values = values; + this.declType = declType; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var declType = JavaScript.parseDeclarationType(src, i + n); + if (!declType.isSuccess()) return declType.chainError(); + n += declType.n; + + var res = new ArrayList(); + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); + } + + while (true) { + var nameLoc = src.loc(i + n); + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name"); + n += name.n; + + if (!JavaScript.checkVarName(name.result)) { + return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); + } + + Node val = null; + var endN = n; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "=")) { + n++; + + var valRes = JavaScript.parseExpression(src, i + n, 2); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='"); + + n += valRes.n; + endN = n; + n += Parsing.skipEmpty(src, i + n); + val = valRes.result; + } + + res.add(new Pair(name.result, val, nameLoc)); + + if (src.is(i + n, ",")) { + n++; + continue; + } + + end = JavaScript.parseStatementEnd(src, i + endN); + + if (end.isSuccess()) { + n += end.n + endN - n; + return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); + } + else return end.chainError(src.loc(i + n), "Expected a comma or end of statement"); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java deleted file mode 100644 index 1f4b6a4..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ /dev/null @@ -1,52 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.List; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.values.FunctionStatement; - -public class VariableDeclareStatement extends Statement { - public static class Pair { - public final String name; - public final Statement value; - public final Location location; - - public Pair(String name, Statement value, Location location) { - this.name = name; - this.value = value; - this.location = location; - } - } - - public final List values; - - @Override - public void declare(CompileResult target) { - for (var key : values) { - target.scope.define(key.name); - } - } - @Override - public void compile(CompileResult target, boolean pollute) { - for (var entry : values) { - if (entry.name == null) continue; - var key = target.scope.getKey(entry.name); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); - - if (entry.value != null) { - FunctionStatement.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); - target.add(Instruction.storeVar(key)); - } - } - - if (pollute) target.add(Instruction.pushUndefined()); - } - - public VariableDeclareStatement(Location loc, List values) { - super(loc); - this.values = values; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java new file mode 100644 index 0000000..b09b8ca --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java @@ -0,0 +1,57 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class BreakNode extends Node { + public final String label; + + @Override public void compile(CompileResult target, boolean pollute) { + var res = LabelContext.getBreak(target.env).getJump(); + if (res == null) { + if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); + else throw new SyntaxException(loc(), "Illegal break statement"); + } + target.add(res); + + if (pollute) target.add(Instruction.pushUndefined()); + } + + public BreakNode(Location loc, String label) { + super(loc); + this.label = label; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed(); + n += 5; + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new BreakNode(loc, null), n); + } + + var label = Parsing.parseIdentifier(src, i + n); + if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); + n += label.n; + + end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new BreakNode(loc, label.result), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement"); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java deleted file mode 100644 index eafae48..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class BreakStatement extends Statement { - public final String label; - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.nop("break", label)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public BreakStatement(Location loc, String label) { - super(loc); - this.label = label; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java new file mode 100644 index 0000000..da7eebb --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java @@ -0,0 +1,57 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class ContinueNode extends Node { + public final String label; + + @Override public void compile(CompileResult target, boolean pollute) { + var res = LabelContext.getCont(target.env).getJump(); + if (res == null) { + if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); + else throw new SyntaxException(loc(), "Illegal continue statement"); + } + target.add(res); + + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ContinueNode(Location loc, String label) { + super(loc); + this.label = label; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed(); + n += 8; + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ContinueNode(loc, null), n); + } + + var label = Parsing.parseIdentifier(src, i + n); + if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); + n += label.n; + + end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ContinueNode(loc, label.result), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement"); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java deleted file mode 100644 index dfc3abd..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ContinueStatement extends Statement { - public final String label; - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.nop("cont", label)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ContinueStatement(Location loc, String label) { - super(loc); - this.label = label; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java new file mode 100644 index 0000000..9464e9d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java @@ -0,0 +1,37 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + +public class DebugNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.debug()); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public DebugNode(Location loc) { + super(loc); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed(); + n += 8; + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new DebugNode(loc), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement"); + } + +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java deleted file mode 100644 index 20d23ef..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class DebugStatement extends Statement { - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.debug()); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public DebugStatement(Location loc) { - super(loc); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java new file mode 100644 index 0000000..6df811d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java @@ -0,0 +1,53 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.values.constants.BoolNode; +import me.topchetoeu.jscript.compilation.values.operations.IndexNode; + +public class DeleteNode extends Node { + public final Node key; + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + value.compile(target, true); + key.compile(target, true); + + target.add(Instruction.delete()); + if (pollute) target.add(Instruction.pushValue(true)); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed(); + n += 6; + + var valRes = JavaScript.parseExpression(src, i + n, 15); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'"); + n += valRes.n; + + if (valRes.result instanceof IndexNode) { + var index = (IndexNode)valRes.result; + return ParseRes.res(new DeleteNode(loc, index.index, index.object), n); + } + else if (valRes.result instanceof VariableNode) { + return ParseRes.error(src.loc(i + n), "A variable may not be deleted"); + } + else return ParseRes.res(new BoolNode(loc, true), n); + } + + public DeleteNode(Location loc, Node key, Node value) { + super(loc); + this.key = key; + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java deleted file mode 100644 index 9355182..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class DeleteStatement extends Statement { - public final Statement key; - public final Statement value; - - @Override - public void compile(CompileResult target, boolean pollute) { - value.compile(target, true); - key.compile(target, true); - - target.add(Instruction.delete()); - if (pollute) target.add(Instruction.pushValue(true)); - } - - public DeleteStatement(Location loc, Statement key, Statement value) { - super(loc); - this.key = key; - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java new file mode 100644 index 0000000..1f3de82 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java @@ -0,0 +1,84 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; + +public class DoWhileNode extends Node { + public final Node condition, body; + public final String label; + + @Override public void resolve(CompileResult target) { + body.resolve(target); + } + + @Override public void compile(CompileResult target, boolean pollute) { + int start = target.size(); + var end = new DeferredIntSupplier(); + var mid = new DeferredIntSupplier(); + + LabelContext.pushLoop(target.env, loc(), label, end, start); + body.compile(target, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(target.env, label); + + mid.set(target.size()); + condition.compile(target, true, BreakpointType.STEP_OVER); + int endI = target.size(); + end.set(endI + 1); + + target.add(Instruction.jmpIf(start - endI)); + } + + public DoWhileNode(Location loc, String label, Node condition, Node body) { + super(loc); + this.label = label; + this.condition = condition; + this.body = body; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var labelRes = JavaScript.parseLabel(src, i + n); + n += labelRes.n; + + if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed(); + n += 2; + + var bodyRes = JavaScript.parseStatement(src, i + n); + if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body."); + n += bodyRes.n; + + if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); + n += 5; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); + n++; + + var condRes = JavaScript.parseExpression(src, i + n, 0); + if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition."); + n += condRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition."); + n++; + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement"); + } + +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java deleted file mode 100644 index 2417902..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class DoWhileStatement extends Statement { - public final Statement condition, body; - public final String label; - - @Override - public void declare(CompileResult target) { - body.declare(target); - } - - @Override - public void compile(CompileResult target, boolean pollute) { - int start = target.size(); - body.compile(target, false, BreakpointType.STEP_OVER); - int mid = target.size(); - condition.compile(target, true, BreakpointType.STEP_OVER); - int end = target.size(); - - WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1); - target.add(Instruction.jmpIf(start - end)); - } - - public DoWhileStatement(Location loc, String label, Statement condition, Statement body) { - super(loc); - this.label = label; - this.condition = condition; - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java new file mode 100644 index 0000000..59688a5 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -0,0 +1,114 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class ForInNode extends Node { + public final String varName; + public final DeclarationType declType; + public final Node object, body; + public final String label; + public final Location varLocation; + + @Override public void resolve(CompileResult target) { + body.resolve(target); + if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), loc()); + } + + @Override public void compile(CompileResult target, boolean pollute) { + if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation); + + object.compile(target, true, BreakpointType.STEP_OVER); + target.add(Instruction.keys(true)); + + int start = target.size(); + target.add(Instruction.dup()); + target.add(Instruction.pushUndefined()); + target.add(Instruction.operation(Operation.EQUALS)); + int mid = target.temp(); + + target.add(Instruction.loadMember("value")).setLocation(varLocation); + target.add(VariableNode.toSet(target, loc(), varName, pollute, declType != null && declType.strict)); + target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); + + var end = new DeferredIntSupplier(); + + LabelContext.pushLoop(target.env, loc(), label, end, start); + CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(target.env, label); + + int endI = target.size(); + + target.add(Instruction.jmp(start - endI)); + target.add(Instruction.discard()); + target.set(mid, Instruction.jmpIf(endI - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ForInNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) { + super(loc); + this.varLocation = varLocation; + this.label = label; + this.declType = declType; + this.varName = varName; + this.object = object; + this.body = body; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); + n += 3; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren"); + n++; + n += Parsing.skipEmpty(src, i + n); + + var declType = JavaScript.parseDeclarationType(src, i + n); + n += declType.n; + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-in loop"); + var nameLoc = src.loc(i + n); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); + n += 2; + + var obj = JavaScript.parseExpression(src, i + n, 0); + if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); + n += obj.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); + n++; + + var bodyRes = JavaScript.parseStatement(src, i + n); + if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); + n += bodyRes.n; + + return ParseRes.res(new ForInNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java deleted file mode 100644 index 216509a..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java +++ /dev/null @@ -1,69 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ForInStatement extends Statement { - public final String varName; - public final boolean isDeclaration; - public final Statement varValue, object, body; - public final String label; - public final Location varLocation; - - @Override - public void declare(CompileResult target) { - body.declare(target); - if (isDeclaration) target.scope.define(varName); - } - - @Override - public void compile(CompileResult target, boolean pollute) { - var key = target.scope.getKey(varName); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); - - if (varValue != null) { - varValue.compile(target, true); - target.add(Instruction.storeVar(target.scope.getKey(varName))); - } - - object.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.keys(true)); - - int start = target.size(); - target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); - int mid = target.temp(); - - target.add(Instruction.pushValue("value")).setLocation(varLocation); - target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); - - body.compile(target, false, BreakpointType.STEP_OVER); - - int end = target.size(); - - WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); - - target.add(Instruction.jmp(start - end)); - target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(end - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { - super(loc); - this.varLocation = varLocation; - this.label = label; - this.isDeclaration = isDecl; - this.varName = varName; - this.varValue = varValue; - this.object = object; - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java new file mode 100644 index 0000000..e2181e2 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java @@ -0,0 +1,132 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.VariableDeclareNode; +import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; + +public class ForNode extends Node { + public final Node declaration, assignment, condition, body; + public final String label; + + @Override public void resolve(CompileResult target) { + declaration.resolve(target); + body.resolve(target); + } + @Override public void compile(CompileResult target, boolean pollute) { + var subtarget = target.subtarget(); + subtarget.scope.singleEntry = false; + subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); + + declaration.compile(subtarget, false, BreakpointType.STEP_OVER); + + int start = subtarget.size(); + CompoundNode.compileMultiEntry(condition, subtarget, true, BreakpointType.STEP_OVER); + int mid = subtarget.temp(); + + var end = new DeferredIntSupplier(); + + LabelContext.pushLoop(subtarget.env, loc(), label, end, start); + CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(subtarget.env, label); + + subtarget.add(_i -> Instruction.stackRealloc(subtarget.scope.allocCount())); + + CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER); + int endI = subtarget.size(); + + end.set(endI); + + subtarget.add(Instruction.jmp(start - endI)); + subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1)); + if (pollute) subtarget.add(Instruction.pushUndefined()); + + subtarget.scope.end(); + subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount())); + } + + public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { + super(loc); + this.label = label; + this.declaration = declaration; + this.condition = condition; + this.assignment = assignment; + this.body = body; + } + + private static ParseRes parseSemicolon(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, ";")) return ParseRes.failed(); + else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1); + } + private static ParseRes parseCondition(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var res = JavaScript.parseExpression(src, i + n, 0); + if (!res.isSuccess()) return res.chainError(); + n += res.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon"); + else return ParseRes.res(res.result, n + 1); + } + private static ParseRes parseUpdater(Source src, int i) { + return JavaScript.parseExpression(src, i, 0); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var labelRes = JavaScript.parseLabel(src, i + n); + n += labelRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); + n += 3; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'"); + n++; + + ParseRes decl = ParseRes.first(src, i + n, + ForNode::parseSemicolon, + VariableDeclareNode::parse, + ForNode::parseCondition + ); + if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression"); + n += decl.n; + + ParseRes cond = ParseRes.first(src, i + n, + ForNode::parseSemicolon, + ForNode::parseCondition + ); + if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition"); + n += cond.n; + + var update = parseUpdater(src, i + n); + if (update.isError()) return update.chainError(); + n += update.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater"); + n++; + + var body = JavaScript.parseStatement(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body."); + n += body.n; + + return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java new file mode 100644 index 0000000..b675e7b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java @@ -0,0 +1,120 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class ForOfNode extends Node { + public final String varName; + public final DeclarationType declType; + public final Node iterable, body; + public final String label; + public final Location varLocation; + + @Override public void resolve(CompileResult target) { + body.resolve(target); + if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), varLocation); + } + + @Override public void compile(CompileResult target, boolean pollute) { + if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation); + + iterable.compile(target, true, BreakpointType.STEP_OVER); + target.add(Instruction.dup()); + target.add(Instruction.loadIntrinsics("it_key")); + target.add(Instruction.loadMember()).setLocation(iterable.loc()); + target.add(Instruction.call(0)).setLocation(iterable.loc()); + + int start = target.size(); + target.add(Instruction.dup()); + target.add(Instruction.dup()); + target.add(Instruction.loadMember("next")).setLocation(iterable.loc()); + target.add(Instruction.call(0)).setLocation(iterable.loc()); + target.add(Instruction.dup()); + target.add(Instruction.loadMember("done")).setLocation(iterable.loc()); + int mid = target.temp(); + + target.add(Instruction.loadMember("value")).setLocation(varLocation); + target.add(VariableNode.toSet(target, varLocation, varName, false, declType != null && declType.strict)); + + var end = new DeferredIntSupplier(); + + LabelContext.pushLoop(target.env, loc(), label, end, start); + CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(target.env, label); + + int endI = target.size(); + end.set(endI); + + target.add(Instruction.jmp(start - endI)); + target.add(Instruction.discard()); + target.add(Instruction.discard()); + target.set(mid, Instruction.jmpIf(endI - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ForOfNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) { + super(loc); + this.varLocation = varLocation; + this.label = label; + this.declType = declType; + this.varName = varName; + this.iterable = object; + this.body = body; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); + n += 3; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren"); + n++; + n += Parsing.skipEmpty(src, i + n); + + var declType = JavaScript.parseDeclarationType(src, i + n); + n += declType.n; + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-of loop"); + var nameLoc = src.loc(i + n); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration"); + n += 2; + + var obj = JavaScript.parseExpression(src, i + n, 0); + if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); + n += obj.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); + n++; + + var bodyRes = JavaScript.parseStatement(src, i + n); + if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body"); + n += bodyRes.n; + + return ParseRes.res(new ForOfNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java deleted file mode 100644 index fba5fc7..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java +++ /dev/null @@ -1,73 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ForOfStatement extends Statement { - public final String varName; - public final boolean isDeclaration; - public final Statement iterable, body; - public final String label; - public final Location varLocation; - - @Override - public void declare(CompileResult target) { - body.declare(target); - if (isDeclaration) target.scope.define(varName); - } - - @Override - public void compile(CompileResult target, boolean pollute) { - var key = target.scope.getKey(varName); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); - - iterable.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.dup()); - target.add(Instruction.loadVar("Symbol")); - target.add(Instruction.pushValue("iterator")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.call(0)).setLocation(iterable.loc()); - - int start = target.size(); - target.add(Instruction.dup()); - target.add(Instruction.dup()); - target.add(Instruction.pushValue("next")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.call(0)).setLocation(iterable.loc()); - target.add(Instruction.dup()); - target.add(Instruction.pushValue("done")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - int mid = target.temp(); - - target.add(Instruction.pushValue("value")); - target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(Instruction.storeVar(key)).setLocationAndDebug(iterable.loc(), BreakpointType.STEP_OVER); - - body.compile(target, false, BreakpointType.STEP_OVER); - - int end = target.size(); - - WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); - - target.add(Instruction.jmp(start - end)); - target.add(Instruction.discard()); - target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(end - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForOfStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement object, Statement body) { - super(loc); - this.varLocation = varLocation; - this.label = label; - this.isDeclaration = isDecl; - this.varName = varName; - this.iterable = object; - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java deleted file mode 100644 index 9a59613..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ForStatement extends Statement { - public final Statement declaration, assignment, condition, body; - public final String label; - - @Override - public void declare(CompileResult target) { - declaration.declare(target); - body.declare(target); - } - @Override - public void compile(CompileResult target, boolean pollute) { - declaration.compile(target, false, BreakpointType.STEP_OVER); - - int start = target.size(); - condition.compile(target, true, BreakpointType.STEP_OVER); - int mid = target.temp(); - body.compile(target, false, BreakpointType.STEP_OVER); - int beforeAssign = target.size(); - assignment.compile(target, false, BreakpointType.STEP_OVER); - int end = target.size(); - - WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); - - target.add(Instruction.jmp(start - end)); - target.set(mid, Instruction.jmpIfNot(end - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) { - super(loc); - this.label = label; - this.declaration = declaration; - this.condition = condition; - this.assignment = assignment; - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java new file mode 100644 index 0000000..ef6546b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java @@ -0,0 +1,131 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; + +public class IfNode extends Node { + public final Node condition, body, elseBody; + public final String label; + + @Override public void resolve(CompileResult target) { + body.resolve(target); + if (elseBody != null) elseBody.resolve(target); + } + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { + condition.compile(target, true, breakpoint); + + if (elseBody == null) { + int start = target.temp(); + var end = new DeferredIntSupplier(); + + LabelContext.getBreak(target.env).push(loc(), label, end); + body.compile(target, false, BreakpointType.STEP_OVER); + LabelContext.getBreak(target.env).pop(label); + + int endI = target.size(); + end.set(endI); + + target.set(start, Instruction.jmpIfNot(endI - start)); + } + else { + int start = target.temp(); + var end = new DeferredIntSupplier(); + + LabelContext.getBreak(target.env).push(loc(), label, end); + body.compile(target, false, BreakpointType.STEP_OVER); + + int mid = target.temp(); + + elseBody.compile(target, false, BreakpointType.STEP_OVER); + LabelContext.getBreak(target.env).pop(label); + + int endI = target.size(); + end.set(endI); + + target.set(start, Instruction.jmpIfNot(mid - start + 1)); + target.set(mid, Instruction.jmp(endI - mid)); + } + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, BreakpointType.STEP_IN); + } + + public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) { + super(loc); + this.condition = condition; + this.body = body; + this.elseBody = elseBody; + this.label = label; + } + + public static ParseRes parseTernary(Source src, int i, Node prev, int precedence) { + if (precedence > 2) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "?")) return ParseRes.failed(); + var loc = src.loc(i + n); + n++; + + var a = JavaScript.parseExpression(src, i + n, 2); + if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator."); + n += a.n; + n += Parsing.skipEmpty(src, i); + + if (!src.is(i + n, ":")) return ParseRes.failed(); + n++; + + var b = JavaScript.parseExpression(src, i + n, 2); + if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator."); + n += b.n; + + return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n); + } + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed(); + n += 2; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'."); + n++; + + var condRes = JavaScript.parseExpression(src, i + n, 0); + if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition."); + n += condRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition."); + n++; + + var res = JavaScript.parseStatement(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body."); + n += res.n; + + var elseKw = Parsing.parseIdentifier(src, i + n, "else"); + if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n); + n += elseKw.n; + + var elseRes = JavaScript.parseStatement(src, i + n); + if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body."); + n += elseRes.n; + + return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java deleted file mode 100644 index ba37671..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class IfStatement extends Statement { - public final Statement condition, body, elseBody; - - @Override - public void declare(CompileResult target) { - body.declare(target); - if (elseBody != null) elseBody.declare(target); - } - - @Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { - condition.compile(target, true, breakpoint); - - if (elseBody == null) { - int i = target.temp(); - body.compile(target, pollute, breakpoint); - int endI = target.size(); - target.set(i, Instruction.jmpIfNot(endI - i)); - } - else { - int start = target.temp(); - body.compile(target, pollute, breakpoint); - int mid = target.temp(); - elseBody.compile(target, pollute, breakpoint); - int end = target.size(); - - target.set(start, Instruction.jmpIfNot(mid - start + 1)); - target.set(mid, Instruction.jmp(end - mid)); - } - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, BreakpointType.STEP_IN); - } - - public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) { - super(loc); - this.condition = condition; - this.body = body; - this.elseBody = elseBody; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java new file mode 100644 index 0000000..3caccea --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java @@ -0,0 +1,50 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + +public class ReturnNode extends Node { + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (value == null) target.add(Instruction.pushUndefined()); + else value.compile(target, true); + target.add(Instruction.ret()).setLocation(loc()); + } + + public ReturnNode(Location loc, Node value) { + super(loc); + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed(); + n += 6; + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ReturnNode(loc, null), n); + } + + var val = JavaScript.parseExpression(src, i + n, 0); + if (val.isError()) return val.chainError(); + n += val.n; + + end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ReturnNode(loc, val.result), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement or a return value"); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java deleted file mode 100644 index 4e915fd..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ReturnStatement extends Statement { - public final Statement value; - - @Override - public void compile(CompileResult target, boolean pollute) { - if (value == null) target.add(Instruction.pushUndefined()); - else value.compile(target, true); - target.add(Instruction.ret()).setLocation(loc()); - } - - public ReturnStatement(Location loc, Statement value) { - super(loc); - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java new file mode 100644 index 0000000..e3b1496 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java @@ -0,0 +1,197 @@ +package me.topchetoeu.jscript.compilation.control; + +import java.util.ArrayList; +import java.util.HashMap; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; + +public class SwitchNode extends Node { + public static class SwitchCase { + public final Node value; + public final int statementI; + + public SwitchCase(Node value, int statementI) { + this.value = value; + this.statementI = statementI; + } + } + + public final Node value; + public final SwitchCase[] cases; + public final Node[] body; + public final int defaultI; + public final String label; + + @Override public void resolve(CompileResult target) { + for (var stm : body) stm.resolve(target); + } + + @Override public void compile(CompileResult target, boolean pollute) { + var caseToStatement = new HashMap(); + var statementToIndex = new HashMap(); + + value.compile(target, true, BreakpointType.STEP_OVER); + + var subtarget = target.subtarget(); + subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.allocCount())); + + // TODO: create a jump map + for (var ccase : cases) { + subtarget.add(Instruction.dup()); + ccase.value.compile(subtarget, true); + subtarget.add(Instruction.operation(Operation.EQUALS)); + caseToStatement.put(subtarget.temp(), ccase.statementI); + } + + int start = subtarget.temp(); + var end = new DeferredIntSupplier(); + + LabelContext.getBreak(target.env).push(loc(), label, end); + for (var stm : body) { + statementToIndex.put(statementToIndex.size(), subtarget.size()); + stm.compile(subtarget, false, BreakpointType.STEP_OVER); + } + LabelContext.getBreak(target.env).pop(label); + + subtarget.scope.end(); + subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount())); + + int endI = subtarget.size(); + end.set(endI); + subtarget.add(Instruction.discard()); + if (pollute) subtarget.add(Instruction.pushUndefined()); + + if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start)); + else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); + + for (var el : caseToStatement.entrySet()) { + var i = statementToIndex.get(el.getValue()); + if (i == null) i = endI; + subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); + } + + } + + public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) { + super(loc); + this.label = label; + this.value = value; + this.defaultI = defaultI; + this.cases = cases; + this.body = body; + } + + private static ParseRes parseSwitchCase(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed(); + n += 4; + + var val = JavaScript.parseExpression(src, i + n, 0); + if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'"); + n += val.n; + + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value"); + n++; + + return ParseRes.res(val.result, n); + } + private static ParseRes parseDefaultCase(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed(); + n += 7; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'"); + n++; + + return ParseRes.res(null, n); + } + @SuppressWarnings("unused") + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed(); + n += 6; + n += Parsing.skipEmpty(src, i + n); + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'"); + n++; + + var val = JavaScript.parseExpression(src, i + n, 0); + if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value"); + n += val.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value"); + n++; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value"); + n++; + n += Parsing.skipEmpty(src, i + n); + + var statements = new ArrayList(); + var cases = new ArrayList(); + var defaultI = -1; + + while (true) { + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { + n++; + break; + } + if (src.is(i + n, ";")) { + n++; + continue; + } + + ParseRes caseRes = ParseRes.first(src, i + n, + SwitchNode::parseDefaultCase, + SwitchNode::parseSwitchCase + ); + + // Parsing::parseStatement + + if (caseRes.isSuccess()) { + n += caseRes.n; + + if (caseRes.result == null) defaultI = statements.size(); + else cases.add(new SwitchCase(caseRes.result, statements.size())); + continue; + } + if (caseRes.isError()) return caseRes.chainError(); + + var stm = JavaScript.parseStatement(src, i + n); + if (stm.isSuccess()) { + n += stm.n; + statements.add(stm.result); + continue; + } + else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'"); + } + + return ParseRes.res(new SwitchNode( + loc, label.result, val.result, defaultI, + cases.toArray(SwitchCase[]::new), + statements.toArray(Node[]::new) + ), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java deleted file mode 100644 index bf4988c..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ /dev/null @@ -1,83 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import java.util.HashMap; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.Instruction.Type; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class SwitchStatement extends Statement { - public static class SwitchCase { - public final Statement value; - public final int statementI; - - public SwitchCase(Statement value, int statementI) { - this.value = value; - this.statementI = statementI; - } - } - - public final Statement value; - public final SwitchCase[] cases; - public final Statement[] body; - public final int defaultI; - - @Override - public void declare(CompileResult target) { - for (var stm : body) stm.declare(target); - } - - @Override - public void compile(CompileResult target, boolean pollute) { - var caseToStatement = new HashMap(); - var statementToIndex = new HashMap(); - - value.compile(target, true, BreakpointType.STEP_OVER); - - for (var ccase : cases) { - target.add(Instruction.dup()); - ccase.value.compile(target, true); - target.add(Instruction.operation(Operation.EQUALS)); - caseToStatement.put(target.temp(), ccase.statementI); - } - - int start = target.temp(); - - for (var stm : body) { - statementToIndex.put(statementToIndex.size(), target.size()); - stm.compile(target, false, BreakpointType.STEP_OVER); - } - - int end = target.size(); - target.add(Instruction.discard()); - if (pollute) target.add(Instruction.pushUndefined()); - - if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start)); - else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); - - for (int i = start; i < end; i++) { - var instr = target.get(i); - if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { - target.set(i, Instruction.jmp(end - i)); - } - } - for (var el : caseToStatement.entrySet()) { - var i = statementToIndex.get(el.getValue()); - if (i == null) i = end; - target.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); - } - - } - - public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { - super(loc); - this.value = value; - this.defaultI = defaultI; - this.cases = cases; - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java new file mode 100644 index 0000000..2c6cb37 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java @@ -0,0 +1,49 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + +public class ThrowNode extends Node { + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + value.compile(target, true); + target.add(Instruction.throwInstr()).setLocation(loc()); + } + + public ThrowNode(Location loc, Node value) { + super(loc); + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed(); + n += 5; + + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ThrowNode(loc, null), n); + } + + var val = JavaScript.parseExpression(src, i + n, 0); + if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); + n += val.n; + + end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ThrowNode(loc, val.result), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement"); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java deleted file mode 100644 index 156f7ae..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ThrowStatement extends Statement { - public final Statement value; - - @Override - public void compile(CompileResult target, boolean pollute) { - value.compile(target, true); - target.add(Instruction.throwInstr()).setLocation(loc()); - } - - public ThrowStatement(Location loc, Statement value) { - super(loc); - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java new file mode 100644 index 0000000..b972a1a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java @@ -0,0 +1,135 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.scope.Variable; + +public class TryNode extends Node { + public final CompoundNode tryBody; + public final CompoundNode catchBody; + public final CompoundNode finallyBody; + public final String captureName; + public final String label; + + @Override public void resolve(CompileResult target) { + tryBody.resolve(target); + catchBody.resolve(target); + finallyBody.resolve(target); + } + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { + int replace = target.temp(); + var endSuppl = new DeferredIntSupplier(); + + int start = replace + 1, catchStart = -1, finallyStart = -1; + + LabelContext.getBreak(target.env).push(loc(), label, endSuppl); + + tryBody.compile(target, false); + target.add(Instruction.tryEnd()); + + if (catchBody != null) { + catchStart = target.size() - start; + + if (captureName != null) { + var subtarget = target.subtarget(); + subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc()); + catchBody.compile(subtarget, false); + subtarget.scope.end(); + } + else catchBody.compile(target, false); + + target.add(Instruction.tryEnd()); + } + + if (finallyBody != null) { + finallyStart = target.size() - start; + finallyBody.compile(target, false); + } + + LabelContext.getBreak(target.env).pop(label); + + endSuppl.set(target.size()); + + target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start)); + target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER); + + if (pollute) target.add(Instruction.pushUndefined()); + } + + public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) { + super(loc); + this.tryBody = tryBody; + this.catchBody = catchBody; + this.finallyBody = finallyBody; + this.captureName = captureName; + this.label = label; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var labelRes = JavaScript.parseLabel(src, i + n); + n += labelRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed(); + n += 3; + + var tryBody = CompoundNode.parse(src, i + n); + if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body"); + n += tryBody.n; + n += Parsing.skipEmpty(src, i + n); + + String capture = null; + CompoundNode catchBody = null, finallyBody = null; + + if (Parsing.isIdentifier(src, i + n, "catch")) { + n += 5; + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, "(")) { + n++; + var nameRes = Parsing.parseIdentifier(src, i + n); + if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name"); + capture = nameRes.result; + n += nameRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name"); + n++; + } + + var bodyRes = CompoundNode.parse(src, i + n); + if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body"); + n += bodyRes.n; + n += Parsing.skipEmpty(src, i + n); + + catchBody = bodyRes.result; + } + + if (Parsing.isIdentifier(src, i + n, "finally")) { + n += 7; + + var bodyRes = CompoundNode.parse(src, i + n); + if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body"); + n += bodyRes.n; + n += Parsing.skipEmpty(src, i + n); + finallyBody = bodyRes.result; + } + + if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally"); + + return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java deleted file mode 100644 index 41ce48f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class TryStatement extends Statement { - public final Statement tryBody; - public final Statement catchBody; - public final Statement finallyBody; - public final String name; - - @Override - public void declare(CompileResult target) { - tryBody.declare(target); - if (catchBody != null) catchBody.declare(target); - if (finallyBody != null) finallyBody.declare(target); - } - - @Override - public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { - int replace = target.temp(); - - int start = replace + 1, catchStart = -1, finallyStart = -1; - - tryBody.compile(target, false); - target.add(Instruction.tryEnd()); - - if (catchBody != null) { - catchStart = target.size() - start; - target.scope.define(name, true); - catchBody.compile(target, false); - target.scope.undefine(); - target.add(Instruction.tryEnd()); - } - - if (finallyBody != null) { - finallyStart = target.size() - start; - finallyBody.compile(target, false); - target.add(Instruction.tryEnd()); - } - - target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start)); - target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER); - - if (pollute) target.add(Instruction.pushUndefined()); - } - - public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) { - super(loc); - this.tryBody = tryBody; - this.catchBody = catchBody; - this.finallyBody = finallyBody; - this.name = name; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java new file mode 100644 index 0000000..d6d9447 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java @@ -0,0 +1,79 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.Node; + +public class WhileNode extends Node { + public final Node condition, body; + public final String label; + + @Override public void resolve(CompileResult target) { + body.resolve(target); + } + @Override public void compile(CompileResult target, boolean pollute) { + int start = target.size(); + condition.compile(target, true); + int mid = target.temp(); + + var end = new DeferredIntSupplier(); + + + LabelContext.pushLoop(target.env, loc(), label, end, start); + CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(target.env, label); + + var endI = target.size(); + end.set(endI + 1); + + target.add(Instruction.jmp(start - end.getAsInt())); + target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public WhileNode(Location loc, String label, Node condition, Node body) { + super(loc); + this.label = label; + this.condition = condition; + this.body = body; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); + n += 5; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); + n++; + + var cond = JavaScript.parseExpression(src, i + n, 0); + if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition."); + n += cond.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition."); + n++; + + var body = JavaScript.parseStatement(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body."); + n += body.n; + + return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java deleted file mode 100644 index 459e4ef..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java +++ /dev/null @@ -1,52 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -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.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class WhileStatement extends Statement { - public final Statement condition, body; - public final String label; - - @Override - public void declare(CompileResult target) { - body.declare(target); - } - @Override - public void compile(CompileResult target, boolean pollute) { - int start = target.size(); - condition.compile(target, true); - int mid = target.temp(); - body.compile(target, false, BreakpointType.STEP_OVER); - - int end = target.size(); - - replaceBreaks(target, label, mid + 1, end, start, end + 1); - - target.add(Instruction.jmp(start - end)); - target.set(mid, Instruction.jmpIfNot(end - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public WhileStatement(Location loc, String label, Statement condition, Statement body) { - super(loc); - this.label = label; - this.condition = condition; - this.body = body; - } - - public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) { - for (int i = start; i < end; i++) { - var instr = target.get(i); - if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { - target.set(i, Instruction.jmp(continuePoint - i)); - } - if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { - target.set(i, Instruction.jmp(breakPoint - i)); - } - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java deleted file mode 100644 index 3e06258..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java +++ /dev/null @@ -1,113 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import java.util.HashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.Operation; - -public enum Operator { - MULTIPLY("*", Operation.MULTIPLY, 13), - DIVIDE("/", Operation.DIVIDE, 12), - MODULO("%", Operation.MODULO, 12), - SUBTRACT("-", Operation.SUBTRACT, 11), - ADD("+", Operation.ADD, 11), - SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10), - SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10), - USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10), - GREATER(">", Operation.GREATER, 9), - LESS("<", Operation.LESS, 9), - GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9), - LESS_EQUALS("<=", Operation.LESS_EQUALS, 9), - NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8), - LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8), - EQUALS("==", Operation.LOOSE_EQUALS, 8), - LOOSE_EQUALS("===", Operation.EQUALS, 8), - AND("&", Operation.AND, 7), - XOR("^", Operation.XOR, 6), - OR("|", Operation.OR, 5), - LAZY_AND("&&", 4), - LAZY_OR("||", 3), - ASSIGN_SHIFT_LEFT("<<=", 2, true), - ASSIGN_SHIFT_RIGHT(">>=", 2, true), - ASSIGN_USHIFT_RIGHT(">>>=", 2, true), - ASSIGN_AND("&=", 2, true), - ASSIGN_OR("|=", 2, true), - ASSIGN_XOR("^=", 2, true), - ASSIGN_MODULO("%=", 2, true), - ASSIGN_DIVIDE("/=", 2, true), - ASSIGN_MULTIPLY("*=", 2, true), - ASSIGN_SUBTRACT("-=", 2, true), - ASSIGN_ADD("+=", 2, true), - ASSIGN("=", 2, true), - SEMICOLON(";"), - COLON(":"), - PAREN_OPEN("("), - PAREN_CLOSE(")"), - BRACKET_OPEN("["), - BRACKET_CLOSE("]"), - BRACE_OPEN("{"), - BRACE_CLOSE("}"), - DOT("."), - COMMA(","), - NOT("!"), - QUESTION("?"), - INVERSE("~"), - INCREASE("++"), - DECREASE("--"); - - public final String readable; - public final Operation operation; - public final int precedence; - public final boolean reverse; - private static final Map ops = new HashMap<>(); - - static { - for (var el : Operator.values()) { - ops.put(el.readable, el); - } - } - - public boolean isAssign() { return precedence == 2; } - - public static Operator parse(String val) { - return ops.get(val); - } - - private Operator() { - this.readable = null; - this.operation = null; - this.precedence = -1; - this.reverse = false; - } - private Operator(String value) { - this.readable = value; - this.operation = null; - this.precedence = -1; - this.reverse = false; - } - private Operator(String value, int precedence) { - this.readable = value; - this.operation = null; - this.precedence = precedence; - this.reverse = false; - } - private Operator(String value, int precedence, boolean reverse) { - this.readable = value; - this.operation = null; - this.precedence = precedence; - this.reverse = reverse; - } - - private Operator(String value, Operation funcName, int precedence) { - this.readable = value; - this.operation = funcName; - this.precedence = precedence; - this.reverse = false; - } - private Operator(String value, Operation funcName, int precedence, boolean reverse) { - this.readable = value; - this.operation = funcName; - this.precedence = precedence; - this.reverse = reverse; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java deleted file mode 100644 index 982c504..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java +++ /dev/null @@ -1,100 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser; - -public class ParseRes { - public static enum State { - SUCCESS, - FAILED, - ERROR; - - public boolean isSuccess() { return this == SUCCESS; } - public boolean isFailed() { return this == FAILED; } - public boolean isError() { return this == ERROR; } - } - - public final ParseRes.State state; - public final String error; - public final T result; - public final int n; - - private ParseRes(ParseRes.State state, String error, T result, int readN) { - this.result = result; - this.n = readN; - this.state = state; - this.error = error; - } - - public ParseRes setN(int i) { - if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, result, i); - } - public ParseRes addN(int i) { - if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, result, this.n + i); - } - public ParseRes transform() { - if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); - return new ParseRes<>(state, error, null, 0); - } - public TestRes toTest() { - if (isSuccess()) return TestRes.res(n); - else if (isError()) return TestRes.error(null, error); - else return TestRes.failed(); - } - - public boolean isSuccess() { return state.isSuccess(); } - public boolean isFailed() { return state.isFailed(); } - public boolean isError() { return state.isError(); } - - public static ParseRes failed() { - return new ParseRes(State.FAILED, null, null, 0); - } - public static ParseRes error(Location loc, String error) { - if (loc != null) error = loc + ": " + error; - return new ParseRes<>(State.ERROR, error, null, 0); - } - public static ParseRes error(Location loc, String error, ParseRes other) { - if (loc != null) error = loc + ": " + error; - if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0); - return new ParseRes<>(State.ERROR, other.error, null, 0); - } - public static ParseRes res(T val, int i) { - return new ParseRes<>(State.SUCCESS, null, val, i); - } - - @SafeVarargs - public static ParseRes any(ParseRes ...parsers) { - return any(List.of(parsers)); - } - public static ParseRes any(List> parsers) { - ParseRes best = null; - ParseRes error = ParseRes.failed(); - - for (var parser : parsers) { - if (parser.isSuccess()) { - if (best == null || best.n < parser.n) best = parser; - } - else if (parser.isError() && error.isFailed()) error = parser.transform(); - } - - if (best != null) return best; - else return error; - } - @SafeVarargs - public static ParseRes first(String filename, List tokens, Map> named, Parser ...parsers) { - ParseRes error = ParseRes.failed(); - - for (var parser : parsers) { - var res = parser.parse(null, tokens, 0); - if (res.isSuccess()) return res; - else if (res.isError() && error.isFailed()) error = res.transform(); - } - - return error; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java deleted file mode 100644 index def1449..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java +++ /dev/null @@ -1,1933 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; - -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.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; - -// TODO: this has to be rewritten -public class Parsing { - public static interface Parser { - ParseRes parse(Filename filename, List tokens, int i); - } - - private static class ObjProp { - public final String name; - public final String access; - public final FunctionStatement func; - - public ObjProp(String name, String access, FunctionStatement func) { - this.name = name; - this.access = access; - this.func = func; - } - } - - public static final HashMap> functions = new HashMap<>(); - - private static final HashSet reserved = new HashSet(); - static { - reserved.add("true"); - reserved.add("false"); - reserved.add("void"); - reserved.add("null"); - reserved.add("this"); - reserved.add("if"); - reserved.add("else"); - reserved.add("try"); - reserved.add("catch"); - reserved.add("finally"); - reserved.add("for"); - reserved.add("do"); - reserved.add("while"); - reserved.add("switch"); - reserved.add("case"); - reserved.add("default"); - reserved.add("new"); - reserved.add("function"); - reserved.add("var"); - reserved.add("return"); - reserved.add("throw"); - reserved.add("typeof"); - reserved.add("delete"); - reserved.add("break"); - reserved.add("continue"); - reserved.add("debugger"); - reserved.add("implements"); - reserved.add("interface"); - reserved.add("package"); - reserved.add("private"); - reserved.add("protected"); - reserved.add("public"); - reserved.add("static"); - // Although ES5 allow these, we will comply to ES6 here - reserved.add("const"); - reserved.add("let"); - // These are allowed too, however our parser considers them keywords - reserved.add("undefined"); - reserved.add("arguments"); - reserved.add("globalThis"); - reserved.add("window"); - reserved.add("self"); - // We allow yield and await, because they're part of the custom async and generator functions - } - - public static boolean isDigit(char c) { - return c >= '0' && c <= '9'; - } - public static boolean isWhitespace(char c) { - return isAny(c, " \t\r\n"); - } - public static boolean isLetter(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - public static boolean isAlphanumeric(char c) { - return isLetter(c) || isDigit(c); - } - public static boolean isAny(char c, String alphabet) { - return alphabet.contains(Character.toString(c)); - } - - private static final int CURR_NONE = 0; - private static final int CURR_NUMBER = 1; - private static final int CURR_FLOAT = 11; - private static final int CURR_SCIENTIFIC_NOT = 12; - private static final int CURR_NEG_SCIENTIFIC_NOT = 13; - private static final int CURR_HEX = 14; - private static final int CURR_STRING = 2; - private static final int CURR_LITERAL = 3; - private static final int CURR_OPERATOR = 4; - private static final int CURR_REGEX = 6; - private static final int CURR_REGEX_FLAGS = 7; - private static final int CURR_MULTI_COMMENT = 8; - private static final int CURR_SINGLE_COMMENT = 9; - - private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List tokens) { - var res = currToken.toString(); - - switch (currStage) { - case CURR_STRING: tokens.add(new RawToken(res, TokenType.STRING, line, lastStart)); break; - case CURR_REGEX_FLAGS: tokens.add(new RawToken(res, TokenType.REGEX, line, lastStart)); break; - case CURR_NUMBER: - case CURR_HEX: - case CURR_NEG_SCIENTIFIC_NOT: - case CURR_SCIENTIFIC_NOT: - case CURR_FLOAT: - tokens.add(new RawToken(res, TokenType.NUMBER, line, lastStart)); break; - case CURR_LITERAL: tokens.add(new RawToken(res, TokenType.LITERAL, line, lastStart)); break; - case CURR_OPERATOR: tokens.add(new RawToken(res, TokenType.OPERATOR, line, lastStart)); break; - } - - currToken.delete(0, currToken.length()); - } - - // This method is so long because we're tokenizing the string using an iterative approach - // instead of a recursive descent parser. This is mainly done for performance reasons. - private static ArrayList splitTokens(Filename filename, String raw) { - var tokens = new ArrayList(); - var currToken = new StringBuilder(64); - - // Those are state variables, and will be reset every time a token has ended parsing - boolean lastEscape = false, inBrackets = false; - - int line = 1, start = 1, lastStart = 1, parenI = 0; - var loc = new Location(line, lastStart, filename); - int currStage = CURR_NONE; - - // when we want to continue parsing a token, we will execute continue;, which will skip - // the token end logic - loop: for (int i = 0; i < raw.length(); i++) { - char c = raw.charAt(i); - - start++; - - switch (currStage) { - case CURR_STRING: - currToken.append(c); - - if (!lastEscape) { - if (c == '\n') throw new SyntaxException(loc, "Can't have a multiline string."); - else if (c == '\\') { - lastEscape = true; - continue; - } - else if (c != currToken.charAt(0)) continue; - } - else { - lastEscape = false; - continue; - } - break; - case CURR_REGEX: - currToken.append(c); - if (!lastEscape) { - if (c == '\\') lastEscape = true; - if (c == '/' & parenI == 0 & !inBrackets) { - currStage = CURR_REGEX_FLAGS; - continue; - } - if (c == '[') inBrackets = true; - if (c == ']') inBrackets = false; - if (c == '(' && !inBrackets) parenI++; - if (c == ')' && !inBrackets) parenI--; - } - else lastEscape = false; - continue; - case CURR_REGEX_FLAGS: - if (isAny(c, "dgimsuy")) { - currToken.append(c); - continue; - } - i--; start--; - break; - case CURR_NUMBER: - if (c == '.') currStage = CURR_FLOAT; - else if (c == 'e' || c == 'E') currStage = CURR_SCIENTIFIC_NOT; - else if ((c == 'x' || c == 'X') && currToken.toString().equals("0")) currStage = CURR_HEX; - else if (!isDigit(c)) { - i--; start--; - break; - } - currToken.append(c); - continue; - case CURR_FLOAT: - if (c == 'e' || c == 'E') currStage = CURR_SCIENTIFIC_NOT; - else if (!isDigit(c)) { - i--; start--; - break; - } - currToken.append(c); - continue; - case CURR_SCIENTIFIC_NOT: - if (c == '-') { - if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT; - } - if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) { - i--; start--; - break; - } - currToken.append(c); - continue; - case CURR_NEG_SCIENTIFIC_NOT: - if (isDigit(c)) currToken.append(c); - else { - i--; start--; - break; - } - continue; - case CURR_HEX: - if (isDigit(c) || isAny(c, "ABCDEFabcdef")) currToken.append(c); - else { - i--; start--; - break; - } - continue; - case CURR_SINGLE_COMMENT: - currToken.delete(0, currToken.length()); - if (c != '\n') continue; - else { - line++; - start = 1; - } - break; - case CURR_MULTI_COMMENT: - if (c == '\n') line++; - if (!(currToken.charAt(0) == '*' && c == '/')) { - currToken.delete(0, currToken.length()); - currToken.append(c); - continue; - } - break; - case CURR_LITERAL: - if (isAlphanumeric(c) || c == '_' || c == '$') { - currToken.append(c); - continue; - } - else { i--; start--; } - break; - case CURR_OPERATOR: { - // here we do several things: - // - detect a comment - // - detect a regular expression - // - detect a float number (.xxxx) - // - read an operator greedily - - // this variable keeps track of whether we're still reading an operator - boolean ok = false; - if (currToken.length() == 1) { - // double operators - if (currToken.charAt(0) == c && isAny(c, "&|=+-<>")) ok = true; - // assignments - else if (c == '=' && isAny(currToken.charAt(0), "&|^+-/*%!<>")) ok = true; - // detect float numbers - else if (isDigit(c) && currToken.charAt(0) == '.') { - currStage = CURR_FLOAT; - currToken.append(c); - continue; - } - else if (currToken.charAt(0) == '/') { - // single line comments - if (c == '/') { - currStage = CURR_SINGLE_COMMENT; - continue; - } - // multiline comments - else if (c == '*') { - currStage = CURR_MULTI_COMMENT; - continue; - } - // regular expressions - else { - // regular expressions must be in the start of a file, or be followed by a - // newline, or an operator - // this is because of expressions like 1 / 2 / 3 (/ 2 /) will get recognized as regex - // still, the closing paren must be ignored, because in an expression, we can't have a value, following a paren - var prevToken = tokens.size() == 0 ? null : tokens.get(tokens.size() - 1); - if (tokens.size() == 0 || ( - prevToken.line < line || - prevToken.type == TokenType.OPERATOR && !prevToken.value.equals(")") || - prevToken.value.equals("return") || - prevToken.value.equals("throe") - )) { - // we look for a second / on the same line - // if we don't find one, we determine the current operator - // to be a division - for (int j = i; j < raw.length(); j++) { - if (raw.charAt(j) == '/') { - i--; start--; - currStage = CURR_REGEX; - continue loop; - } - if (raw.charAt(j) == '\n') break; - } - } - } - } - } - if (currToken.length() == 2) { - var a = currToken.charAt(0); - var b = currToken.charAt(1); - if (( - a == '=' && b == '=' || - a == '!' && b == '=' || - a == '<' && b == '<' || - a == '>' && b == '>' || - a == '>' && b == '>' - ) && c == '=') ok = true; - if (a == '>' && b == '>' && c == '>') ok = true; - } - if ( - currToken.length() == 3 && - currToken.charAt(0) == '>' && - currToken.charAt(1) == '>' && - currToken.charAt(2) == '>' && - c == '=' - ) ok = true; - - if (ok) { - currToken.append(c); - continue; - } - else { i--; start--; } - break; - } - default: - // here we detect what type of token we're reading - if (isAny(c, " \t\n\r")) { - if (c == '\n') { - line++; - start = 1; - } - } - else if (isDigit(c)) { - currToken.append(c); - currStage = CURR_NUMBER; - continue; - } - else if (isAlphanumeric(c) || c == '_' || c == '$') { - currToken.append(c); - currStage = CURR_LITERAL; - continue; - } - else if (isAny(c, "+-/*%=!&|^(){}[];.,<>!:~?")) { - currToken.append(c); - currStage = CURR_OPERATOR; - continue; - } - else if (c == '"' || c == '\'') { - currToken.append(c); - currStage = CURR_STRING; - continue; - } - else throw new SyntaxException(new Location(line, start, filename), String.format("Unrecognized character %s.", c)); - } - - // if we got here, we know that we have encountered the end of a token - addToken(currToken, currStage, line, lastStart, filename, tokens); - lastEscape = inBrackets = false; - currStage = CURR_NONE; - lastStart = start; - } - - // here, we save a leftover token (if any) - switch (currStage) { - case CURR_STRING: throw new SyntaxException(new Location(line, start, filename), "Unterminated string literal."); - case CURR_REGEX: throw new SyntaxException(new Location(line, start, filename), "Incomplete regex."); - } - addToken(currToken, currStage, line, lastStart, filename, tokens); - - return tokens; - } - - public static int fromHex(char c) { - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= '0' && c <= '9') return c - '0'; - return -1; - } - - private static boolean inBounds(List tokens, int i) { - return i >= 0 && i < tokens.size(); - } - - private static String parseString(Location loc, String literal) { - var res = new StringBuilder(); - - for (var i = 1; i < literal.length() - 1; i++) { - if (literal.charAt(i) == '\\') { - char c = literal.charAt(++i); - if (c == 'b') res.append('\b'); - else if (c == 't') res.append('\t'); - else if (c == 'n') res.append('\n'); - else if (c == 'f') res.append('\f'); - else if (c == 'r') res.append('\r'); - else if (c == '0') { - if (i + 1 >= literal.length()) res.append((char)0); - c = literal.charAt(i + 1); - if (c >= '0' && c <= '9') throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); - res.append((char)0); - } - else if (c >= '1' && c <= '9') { - throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); - } - else if (c == 'x') { - var newC = 0; - i++; - for (var j = 0; j < 2; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else if (c == 'u') { - var newC = 0; - i++; - for (var j = 0; j < 4; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else res.append(c); - } - else res.append(literal.charAt(i)); - } - - return res.toString(); - } - private static String parseRegex(Location loc, String literal) { - var res = new StringBuilder(); - - int end = literal.lastIndexOf('/'); - - for (var i = 1; i < end; i++) { - if (literal.charAt(i) == '\\') { - char c = literal.charAt(++i); - if (c == 'b') res.append('\b'); - else if (c == 't') res.append('\t'); - else if (c == 'n') res.append('\n'); - else if (c == 'f') res.append('\f'); - else if (c == 'r') res.append('\r'); - else if (c == '0') { - if (i + 1 >= literal.length()) res.append((char)0); - c = literal.charAt(i + 1); - if (c >= '0' && c <= '9') throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); - res.append((char)0); - } - else if (c >= '1' && c <= '9') { - res.append((char)(c - '0')); - i++; - } - else if (c == 'x') { - var newC = 0; - i++; - for (var j = 0; j < 2; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else if (c == 'u') { - var newC = 0; - i++; - for (var j = 0; j < 4; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else res.append("\\" + c); - } - else res.append(literal.charAt(i)); - } - - return '/' + res.toString() + literal.substring(end); - } - - private static double parseHex(String literal) { - double res = 0; - - for (int i = 2; i < literal.length(); i++) { - res *= 16; - int dig = fromHex(literal.charAt(i)); - res += dig; - } - - return res; - } - - public static Double parseNumber(boolean octals, String value) { - if (value.startsWith("0x") || value.startsWith("0X")) { - if (value.length() == 2) return null; - return parseHex(value); - } - if (value.endsWith("e") || value.endsWith("E") || value.endsWith("-")) return null; - - int i = 0; - double res = 0, dotDivisor = 1; - boolean e = false, dot = false; - int exponent = 0; - - for (; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '.') { dot = true; break; } - if (c == 'e') { e = true; break; } - if (!isDigit(c)) break; - - res = res * 10 + c - '0'; - } - - if (dot) for (i++; i < value.length(); i++) { - char c = value.charAt(i); - if (c == 'e') { e = true; break; } - if (!isDigit(c)) break; - - res += (c - '0') / (dotDivisor *= 10); - } - - if (e) for (i++; i < value.length(); i++) { - char c = value.charAt(i); - if (!isDigit(c)) break; - exponent = 10 * exponent + c - '0'; - } - - if (exponent < 0) for (int j = 0; j < -exponent; j++) res /= 10; - else for (int j = 0; j < exponent; j++) res *= 10; - - return res; - } - private static double parseNumber(Location loc, String value) { - var res = parseNumber(false, value); - if (res == null) - throw new SyntaxException(loc, "Invalid number format."); - else return res; - } - - private static List parseTokens(Filename filename, Collection tokens) { - var res = new ArrayList(); - - for (var el : tokens) { - var loc = new Location(el.line, el.start, filename); - switch (el.type) { - case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break; - case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value), el.value)); break; - case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value), el.value)); break; - case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value), el.value)); break; - case OPERATOR: - Operator op = Operator.parse(el.value); - if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value)); - res.add(Token.operator(el.line, el.start, op)); - break; - } - } - - return res; - } - - public static List tokenize(Filename filename, String raw) { - return parseTokens(filename, splitTokens(filename, raw)); - } - - public static Location getLoc(Filename filename, List tokens, int i) { - if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename); - if (i >= tokens.size()) i = tokens.size() - 1; - return new Location(tokens.get(i).line, tokens.get(i).start, filename); - } - public static int getLines(List tokens) { - if (tokens.size() == 0) return 1; - return tokens.get(tokens.size() - 1).line; - } - - public static ParseRes parseIdentifier(List tokens, int i) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isIdentifier()) { - return ParseRes.res(tokens.get(i).identifier(), 1); - } - else return ParseRes.failed(); - } - else return ParseRes.failed(); - } - public static ParseRes parseOperator(List tokens, int i) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isOperator()) { - return ParseRes.res(tokens.get(i).operator(), 1); - } - else return ParseRes.failed(); - } - else return ParseRes.failed(); - } - - public static boolean isIdentifier(List tokens, int i, String lit) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isIdentifier(lit)) { - return true; - } - else return false; - } - else return false; - } - public static boolean isOperator(List tokens, int i, Operator op) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isOperator(op)) { - return true; - } - else return false; - } - else return false; - } - public static boolean isStatementEnd(List tokens, int i) { - if (isOperator(tokens, i, Operator.SEMICOLON)) return true; - if (isOperator(tokens, i, Operator.BRACE_CLOSE)) return true; - if (i < 0) return false; - if (i >= tokens.size()) return true; - return getLoc(null, tokens, i).line() > getLoc(null, tokens, i - 1).line(); - } - public static boolean checkVarName(String name) { - return !reserved.contains(name); - } - - public static ParseRes parseString(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - if (inBounds(tokens, i)) { - if (tokens.get(i).isString()) { - return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1); - } - else return ParseRes.failed(); - } - else return ParseRes.failed(); - } - public static ParseRes parseNumber(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - if (inBounds(tokens, i)) { - if (tokens.get(i).isNumber()) { - return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1); - } - else return ParseRes.failed(); - } - else return ParseRes.failed(); - } - public static ParseRes parseRegex(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - if (inBounds(tokens, i)) { - if (tokens.get(i).isRegex()) { - var val = tokens.get(i).regex(); - var index = val.lastIndexOf('/'); - var first = val.substring(1, index); - var second = val.substring(index + 1); - return ParseRes.res(new RegexStatement(loc, first, second), 1); - } - else return ParseRes.failed(); - } - return ParseRes.failed(); - } - - public static ParseRes parseArray(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); - - var values = new ArrayList(); - - // Java allows labels, so me uses labels - loop: while (true) { - if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break; - } - - while (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - values.add(null); - if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break loop; - } - } - - var res = parseValue(filename, tokens, i + n, 2); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an array element.", res); - else n += res.n; - - values.add(res.result); - - if (isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break; - } - } - - return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n); - } - - public static ParseRes> parseParamList(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); - - var args = new ArrayList(); - - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - } - else { - while (true) { - var argRes = parseIdentifier(tokens, i + n); - if (argRes.isSuccess()) { - args.add(argRes.result); - n++; - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - } - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - break; - } - } - else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); - } - } - - return ParseRes.res(args, n); - } - - public static ParseRes parsePropName(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - - if (inBounds(tokens, i)) { - var token = tokens.get(i); - - if (token.isNumber() || token.isIdentifier() || token.isString()) return ParseRes.res(token.rawValue, 1); - else return ParseRes.error(loc, "Expected identifier, string or number literal."); - } - else return ParseRes.failed(); - } - public static ParseRes parseObjectProp(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var accessRes = parseIdentifier(tokens, i + n++); - if (!accessRes.isSuccess()) return ParseRes.failed(); - var access = accessRes.result; - if (!access.equals("get") && !access.equals("set")) return ParseRes.failed(); - - var nameRes = parsePropName(filename, tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a property name after '" + access + "'."); - var name = nameRes.result; - n += nameRes.n; - - var argsRes = parseParamList(filename, tokens, i + n); - if (!argsRes.isSuccess()) return ParseRes.error(loc, "Expected an argument list.", argsRes); - n += argsRes.n; - - var res = parseCompound(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res); - n += res.n; - - var end = getLoc(filename, tokens, i + n - 1); - - return ParseRes.res(new ObjProp( - name, access, - new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result) - ), n); - } - public static ParseRes parseObject(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); - - var values = new LinkedHashMap(); - var getters = new LinkedHashMap(); - var setters = new LinkedHashMap(); - - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); - } - - while (true) { - var propRes = parseObjectProp(filename, tokens, i + n); - - if (propRes.isSuccess()) { - n += propRes.n; - if (propRes.result.access.equals("set")) { - setters.put(propRes.result.name, propRes.result.func); - } - else { - getters.put(propRes.result.name, propRes.result.func); - } - } - else { - var nameRes = parsePropName(filename, tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a field name.", propRes); - n += nameRes.n; - - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected a colon."); - - var valRes = parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in array list.", valRes); - n += valRes.n; - - values.put(nameRes.result, valRes.result); - } - - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - continue; - } - else if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - else ParseRes.error(loc, "Expected a comma or a closing brace."); - } - - return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); - } - public static ParseRes parseNew(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var n = 0; - if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 18); - n += valRes.n; - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'new' keyword.", valRes); - var callRes = parseCall(filename, tokens, i + n, valRes.result, 0); - n += callRes.n; - if (callRes.isError()) return callRes.transform(); - else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); - var call = (CallStatement)callRes.result; - - return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n); - } - public static ParseRes parseTypeof(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var n = 0; - if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 15); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'typeof' keyword.", valRes); - n += valRes.n; - - return ParseRes.res(new TypeofStatement(loc, valRes.result), n); - } - public static ParseRes parseVoid(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var n = 0; - if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 14); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); - n += valRes.n; - - return ParseRes.res(new DiscardStatement(loc, valRes.result), n); - } - public static ParseRes parseDelete(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 15); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'delete'.", valRes); - n += valRes.n; - - if (valRes.result instanceof IndexStatement) { - var index = (IndexStatement)valRes.result; - return ParseRes.res(new DeleteStatement(loc, index.index, index.object), n); - } - else if (valRes.result instanceof VariableStatement) { - return ParseRes.error(loc, "A variable may not be deleted."); - } - else { - return ParseRes.res(new ConstantStatement(loc, true), n); - } - } - - public static ParseRes parseFunction(Filename filename, List tokens, int i, boolean statement) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "function")) return ParseRes.failed(); - - var nameRes = parseIdentifier(tokens, i + n); - if (!nameRes.isSuccess() && statement) return ParseRes.error(loc, "A statement function requires a name, one is not present."); - var name = nameRes.result; - n += nameRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); - - var args = new ArrayList(); - - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - } - else { - while (true) { - var argRes = parseIdentifier(tokens, i + n); - if (argRes.isSuccess()) { - args.add(argRes.result); - n++; - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - } - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - break; - } - } - else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); - } - } - - var res = parseCompound(filename, tokens, i + n); - n += res.n; - var end = getLoc(filename, tokens, i + n - 1); - - if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n); - else return ParseRes.error(loc, "Expected a compound statement for function.", res); - } - - public static ParseRes parseUnary(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var opState = parseOperator(tokens, i + n++); - if (!opState.isSuccess()) return ParseRes.failed(); - var op = opState.result; - - Operation operation = null; - - if (op == Operator.ADD) operation = Operation.POS; - else if (op == Operator.SUBTRACT) operation = Operation.NEG; - else if (op == Operator.INVERSE) operation = Operation.INVERSE; - else if (op == Operator.NOT) operation = Operation.NOT; - else return ParseRes.failed(); - - var res = parseValue(filename, tokens, n + i, 14); - - if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); - else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.readable), res); - } - public static ParseRes parsePrefixChange(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var opState = parseOperator(tokens, i + n++); - if (!opState.isSuccess()) return ParseRes.failed(); - - int change = 0; - - if (opState.result == Operator.INCREASE) change = 1; - else if (opState.result == Operator.DECREASE) change = -1; - else return ParseRes.failed(); - - var res = parseValue(filename, tokens, i + n, 15); - if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n); - } - public static ParseRes parseParens(Filename filename, List tokens, int i) { - int n = 0; - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); - - var res = parseValue(filename, tokens, i + n, 0); - if (!res.isSuccess()) return res; - n += res.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.failed(); - - return ParseRes.res(res.result, n); - } - public static ParseRes parseSimple(Filename filename, List tokens, int i, boolean statement) { - var res = new ArrayList>(); - - if (!statement) { - res.add(parseObject(filename, tokens, i)); - res.add(parseFunction(filename, tokens, i, false)); - } - - res.addAll(List.of( - parseVariable(filename, tokens, i), - parseLiteral(filename, tokens, i), - parseString(filename, tokens, i), - parseRegex(filename, tokens, i), - parseNumber(filename, tokens, i), - parseUnary(filename, tokens, i), - parseArray(filename, tokens, i), - parsePrefixChange(filename, tokens, i), - parseParens(filename, tokens, i), - parseNew(filename, tokens, i), - parseTypeof(filename, tokens, i), - parseVoid(filename, tokens, i), - parseDelete(filename, tokens, i) - )); - - return ParseRes.any(res); - } - - public static ParseRes parseVariable(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var literal = parseIdentifier(tokens, i); - - if (!literal.isSuccess()) return ParseRes.failed(); - - if (!checkVarName(literal.result)) { - if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported."); - if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); - if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); - return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result)); - } - - return ParseRes.res(new VariableStatement(loc, literal.result), 1); - } - public static ParseRes parseLiteral(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var id = parseIdentifier(tokens, i); - if (!id.isSuccess()) return id.transform(); - - if (id.result.equals("true")) { - return ParseRes.res(new ConstantStatement(loc, true), 1); - } - if (id.result.equals("false")) { - return ParseRes.res(new ConstantStatement(loc, false), 1); - } - if (id.result.equals("undefined")) { - return ParseRes.res(ConstantStatement.ofUndefined(loc), 1); - } - if (id.result.equals("null")) { - return ParseRes.res(ConstantStatement.ofNull(loc), 1); - } - if (id.result.equals("this")) { - return ParseRes.res(new VariableIndexStatement(loc, 0), 1); - } - if (id.result.equals("arguments")) { - return ParseRes.res(new VariableIndexStatement(loc, 1), 1); - } - if (id.result.equals("globalThis") || id.result.equals("window") || id.result.equals("self")) { - return ParseRes.res(new GlobalThisStatement(loc), 1); - } - return ParseRes.failed(); - } - public static ParseRes parseMember(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 18) return ParseRes.failed(); - - if (!isOperator(tokens, i + n++, Operator.DOT)) return ParseRes.failed(); - - var literal = parseIdentifier(tokens, i + n++); - if (!literal.isSuccess()) return ParseRes.error(loc, "Expected an identifier after member access."); - - return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n); - } - public static ParseRes parseIndex(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 18) return ParseRes.failed(); - - if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in index expression.", valRes); - n += valRes.n; - - if (!isOperator(tokens, i + n++, Operator.BRACKET_CLOSE)) return ParseRes.error(loc, "Expected a closing bracket."); - - return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n); - } - public static ParseRes parseAssign(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0 ; - - if (precedence > 2) return ParseRes.failed(); - - var opRes = parseOperator(tokens, i + n++); - if (opRes.state != State.SUCCESS) return ParseRes.failed(); - - var op = opRes.result; - if (!op.isAssign()) return ParseRes.failed(); - - if (!(prev instanceof AssignableStatement)) { - return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); - } - - var res = parseValue(filename, tokens, i + n, 2); - if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.readable), res); - n += res.n; - - Operation operation = null; - - if (op == Operator.ASSIGN_ADD) operation = Operation.ADD; - if (op == Operator.ASSIGN_SUBTRACT) operation = Operation.SUBTRACT; - if (op == Operator.ASSIGN_MULTIPLY) operation = Operation.MULTIPLY; - if (op == Operator.ASSIGN_DIVIDE) operation = Operation.DIVIDE; - if (op == Operator.ASSIGN_MODULO) operation = Operation.MODULO; - if (op == Operator.ASSIGN_OR) operation = Operation.OR; - if (op == Operator.ASSIGN_XOR) operation = Operation.XOR; - if (op == Operator.ASSIGN_AND) operation = Operation.AND; - if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Operation.SHIFT_LEFT; - if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Operation.SHIFT_RIGHT; - if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Operation.USHIFT_RIGHT; - - return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); - } - public static ParseRes parseCall(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 17) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); - - var args = new ArrayList(); - boolean prevArg = false; - - while (true) { - var argRes = parseValue(filename, tokens, i + n, 2); - if (argRes.isSuccess()) { - args.add(argRes.result); - n += argRes.n; - prevArg = true; - } - else if (argRes.isError()) return argRes.transform(); - else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) { - prevArg = false; - n++; - } - else if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - break; - } - else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren."); - } - - return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); - } - public static ParseRes parsePostfixChange(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (precedence > 15) return ParseRes.failed(); - - var opState = parseOperator(tokens, i + n++); - if (!opState.isSuccess()) return ParseRes.failed(); - - int change = 0; - - if (opState.result == Operator.INCREASE) change = 1; - else if (opState.result == Operator.DECREASE) change = -1; - else return ParseRes.failed(); - - if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator."); - return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n); - } - public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (precedence > 9) return ParseRes.failed(); - if (!isIdentifier(tokens, i + n++, "instanceof")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 10); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'instanceof'.", valRes); - n += valRes.n; - - return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); - } - public static ParseRes parseIn(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (precedence > 9) return ParseRes.failed(); - if (!isIdentifier(tokens, i + n++, "in")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 10); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'in'.", valRes); - n += valRes.n; - - return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); - } - public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 1) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.COMMA)) return ParseRes.failed(); - - var res = parseValue(filename, tokens, i + n, 2); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); - n += res.n; - - return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n); - } - public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 2) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.QUESTION)) return ParseRes.failed(); - - var a = parseValue(filename, tokens, i + n, 2); - if (!a.isSuccess()) return ParseRes.error(loc, "Expected a value after the ternary operator.", a); - n += a.n; - - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); - - var b = parseValue(filename, tokens, i + n, 2); - if (!b.isSuccess()) return ParseRes.error(loc, "Expected a second value after the ternary operator.", b); - n += b.n; - - return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n); - } - public static ParseRes parseOperator(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - var opRes = parseOperator(tokens, i + n++); - if (!opRes.isSuccess()) return ParseRes.failed(); - var op = opRes.result; - - if (op.precedence < precedence) return ParseRes.failed(); - if (op.isAssign()) return parseAssign(filename, tokens, i + n - 1, prev, precedence); - - var res = parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1)); - if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.readable), res); - n += res.n; - - if (op == Operator.LAZY_AND) { - return ParseRes.res(new LazyAndStatement(loc, prev, res.result), n); - } - if (op == Operator.LAZY_OR) { - return ParseRes.res(new LazyOrStatement(loc, prev, res.result), n); - } - - return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); - } - - public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence, boolean statement) { - Statement prev = null; - int n = 0; - - while (true) { - if (prev == null) { - var res = parseSimple(filename, tokens, i + n, statement); - if (res.isSuccess()) { - n += res.n; - prev = res.result; - } - else if (res.isError()) return res.transform(); - else break; - } - else { - var res = ParseRes.any( - parseOperator(filename, tokens, i + n, prev, precedence), - parseMember(filename, tokens, i + n, prev, precedence), - parseIndex(filename, tokens, i + n, prev, precedence), - parseCall(filename, tokens, i + n, prev, precedence), - parsePostfixChange(filename, tokens, i + n, prev, precedence), - parseInstanceof(filename, tokens, i + n, prev, precedence), - parseIn(filename, tokens, i + n, prev, precedence), - parseComma(filename, tokens, i + n, prev, precedence), - parseTernary(filename, tokens, i + n, prev, precedence) - ); - - if (res.isSuccess()) { - n += res.n; - prev = res.result; - continue; - } - else if (res.isError()) return res.transform(); - - break; - } - } - - if (prev == null) return ParseRes.failed(); - else return ParseRes.res(prev, n); - } - public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence) { - return parseValue(filename, tokens, i, precedence, false); - } - - public static ParseRes parseValueStatement(Filename filename, List tokens, int i) { - var valRes = parseValue(filename, tokens, i, 0, true); - if (!valRes.isSuccess()) return valRes.transform(); - - // valRes.result.setLoc(loc); - var res = ParseRes.res(valRes.result, valRes.n); - - if (isStatementEnd(tokens, i + res.n)) { - if (isOperator(tokens, i + res.n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else if (isIdentifier(tokens, i, "const") || isIdentifier(tokens, i, "let")) { - return ParseRes.error(getLoc(filename, tokens, i), "Detected the usage of 'const'/'let'. Please, use 'var' instead."); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res); - } - public static ParseRes parseVariableDeclare(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed(); - - var res = new ArrayList(); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), 2); - else return ParseRes.res(new VariableDeclareStatement(loc, res), 1); - } - - while (true) { - var nameLoc = getLoc(filename, tokens, i + n); - var nameRes = parseIdentifier(tokens, i + n++); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); - - if (!checkVarName(nameRes.result)) { - return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", nameRes.result)); - } - - Statement val = null; - - if (isOperator(tokens, i + n, Operator.ASSIGN)) { - n++; - var valRes = parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); - n += valRes.n; - val = valRes.result; - } - - res.add(new Pair(nameRes.result, val, nameLoc)); - - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - continue; - } - else if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), n + 1); - else return ParseRes.res(new VariableDeclareStatement(loc, res), n); - } - else return ParseRes.error(loc, "Expected a comma or end of statement."); - } - } - - public static ParseRes parseReturn(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new ReturnStatement(loc, null), 2); - else return ParseRes.res(new ReturnStatement(loc, null), 1); - } - - var valRes = parseValue(filename, tokens, i + n, 0); - n += valRes.n; - if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes); - - var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else - return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); - } - public static ParseRes parseThrow(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 0); - n += valRes.n; - if (valRes.isError()) return ParseRes.error(loc, "Expected a throw value.", valRes); - - var res = ParseRes.res(new ThrowStatement(loc, valRes.result), n); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); - } - - public static ParseRes parseBreak(Filename filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "break")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + 1)) { - if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), null), 2); - else return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), null), 1); - } - - var labelRes = parseIdentifier(tokens, i + 1); - if (labelRes.isFailed()) return ParseRes.error(getLoc(filename, tokens, i), "Expected a label name or an end of statement."); - var label = labelRes.result; - - if (isStatementEnd(tokens, i + 2)) { - if (isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), label), 3); - else return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), label), 2); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); - } - public static ParseRes parseContinue(Filename filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + 1)) { - if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), null), 2); - else return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), null), 1); - } - - var labelRes = parseIdentifier(tokens, i + 1); - if (labelRes.isFailed()) return ParseRes.error(getLoc(filename, tokens, i), "Expected a label name or an end of statement."); - var label = labelRes.result; - - if (isStatementEnd(tokens, i + 2)) { - if (isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), label), 3); - else return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), label), 2); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); - } - public static ParseRes parseDebug(Filename filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + 1)) { - if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2); - else return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 1); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); - } - - public static ParseRes parseCompound(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); - - var statements = new ArrayList(); - - while (true) { - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - continue; - } - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) { - return ParseRes.error(getLoc(filename, tokens, i), "Expected a statement.", res); - } - n += res.n; - - statements.add(res.result); - } - - return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); - } - public static ParseRes parseLabel(List tokens, int i) { - int n = 0; - - var nameRes = parseIdentifier(tokens, i + n++); - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); - - return ParseRes.res(nameRes.result, n); - } - public static ParseRes parseIf(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "if")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'if'."); - - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes); - n += condRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition."); - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); - n += res.n; - - if (!isIdentifier(tokens, i + n, "else")) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n); - n++; - - var elseRes = parseStatement(filename, tokens, i + n); - if (!elseRes.isSuccess()) return ParseRes.error(loc, "Expected an else body.", elseRes); - n += elseRes.n; - - return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n); - } - public static ParseRes parseWhile(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "while")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); - - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); - n += condRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a while body.", res); - n += res.n; - - return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n); - } - public static ParseRes parseSwitchCase(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "case")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'case'.", valRes); - n += valRes.n; - - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected colons after 'case' value."); - - return ParseRes.res(valRes.result, n); - } - public static ParseRes parseDefaultCase(List tokens, int i) { - if (!isIdentifier(tokens, i, "default")) return ParseRes.failed(); - if (!isOperator(tokens, i + 1, Operator.COLON)) return ParseRes.error(getLoc(null, tokens, i), "Expected colons after 'default'."); - - return ParseRes.res(null, 2); - } - public static ParseRes parseSwitch(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "switch")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'switch'."); - - var valRes = parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a switch value.", valRes); - n += valRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after switch value."); - if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.error(loc, "Expected an opening brace after switch value."); - - var statements = new ArrayList(); - var cases = new ArrayList(); - var defaultI = -1; - - while (true) { - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - continue; - } - - var defaultRes = parseDefaultCase(tokens, i + n); - var caseRes = parseSwitchCase(filename, tokens, i + n); - - if (defaultRes.isSuccess()) { - defaultI = statements.size(); - n += defaultRes.n; - } - else if (caseRes.isSuccess()) { - cases.add(new SwitchCase(caseRes.result, statements.size())); - n += caseRes.n; - } - else if (defaultRes.isError()) return defaultRes.transform(); - else if (caseRes.isError()) return defaultRes.transform(); - else { - var res = ParseRes.any( - parseStatement(filename, tokens, i + n), - parseCompound(filename, tokens, i + n) - ); - if (!res.isSuccess()) { - return ParseRes.error(getLoc(filename, tokens, i), "Expected a statement.", res); - } - n += res.n; - statements.add(res.result); - } - } - - return ParseRes.res(new SwitchStatement( - loc, valRes.result, defaultI, - cases.toArray(SwitchCase[]::new), - statements.toArray(Statement[]::new) - ), n); - } - public static ParseRes parseDoWhile(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "do")) return ParseRes.failed(); - var bodyRes = parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a do-while body.", bodyRes); - n += bodyRes.n; - - if (!isIdentifier(tokens, i + n++, "while")) return ParseRes.error(loc, "Expected 'while' keyword."); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); - - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); - n += condRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); - - var res = ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon."); - } - public static ParseRes parseFor(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); - - Statement decl, cond, inc; - - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - decl = new CompoundStatement(loc, false); - } - else { - var declRes = ParseRes.any( - parseVariableDeclare(filename, tokens, i + n), - parseValueStatement(filename, tokens, i + n) - ); - if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes); - n += declRes.n; - decl = declRes.result; - } - - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - cond = new ConstantStatement(loc, 1); - } - else { - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", condRes); - n += condRes.n; - if (!isOperator(tokens, i + n++, Operator.SEMICOLON)) return ParseRes.error(loc, "Expected a semicolon.", condRes); - cond = condRes.result; - } - - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - inc = new CompoundStatement(loc, false); - } - else { - var incRes = parseValue(filename, tokens, i + n, 0); - if (!incRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", incRes); - n += incRes.n; - inc = incRes.result; - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - } - - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a for body.", res); - n += res.n; - - return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); - } - public static ParseRes parseForIn(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - var isDecl = false; - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); - - if (isIdentifier(tokens, i + n, "var")) { - isDecl = true; - n++; - } - - var nameRes = parseIdentifier(tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); - var nameLoc = getLoc(filename, tokens, i + n); - n += nameRes.n; - - Statement varVal = null; - - if (isOperator(tokens, i + n, Operator.ASSIGN)) { - n++; - - var valRes = parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); - n += nameRes.n; - - varVal = valRes.result; - } - - if (!isIdentifier(tokens, i + n++, "in")) { - if (varVal == null) { - if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); - else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); - } - return ParseRes.error(loc, "Expected 'in' keyword after variable declaration."); - } - - var objRes = parseValue(filename, tokens, i + n, 0); - if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); - n += objRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - - - var bodyRes = parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); - n += bodyRes.n; - - return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); - } - public static ParseRes parseForOf(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - var isDecl = false; - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); - - if (isIdentifier(tokens, i + n, "var")) { - isDecl = true; - n++; - } - - var nameRes = parseIdentifier(tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); - var nameLoc = getLoc(filename, tokens, i + n); - n += nameRes.n; - - if (!isIdentifier(tokens, i + n++, "of")) { - if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); - else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); - else return ParseRes.error(loc, "Expected 'of' keyword after variable declaration."); - } - - var objRes = parseValue(filename, tokens, i + n, 0); - if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); - n += objRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - - - var bodyRes = parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); - n += bodyRes.n; - - return ParseRes.res(new ForOfStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, objRes.result, bodyRes.result), n); - } - public static ParseRes parseCatch(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "try")) return ParseRes.failed(); - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); - n += res.n; - - String name = null; - Statement catchBody = null, finallyBody = null; - - - if (isIdentifier(tokens, i + n, "catch")) { - n++; - if (isOperator(tokens, i + n, Operator.PAREN_OPEN)) { - n++; - var nameRes = parseIdentifier(tokens, i + n++); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a catch variable name."); - name = nameRes.result; - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after catch variable name."); - } - - var catchRes = parseStatement(filename, tokens, i + n); - if (!catchRes.isSuccess()) return ParseRes.error(loc, "Expected a catch body.", catchRes); - n += catchRes.n; - catchBody = catchRes.result; - } - - if (isIdentifier(tokens, i + n, "finally")) { - n++; - var finallyRes = parseStatement(filename, tokens, i + n); - if (!finallyRes.isSuccess()) return ParseRes.error(loc, "Expected a finally body.", finallyRes); - n += finallyRes.n; - finallyBody = finallyRes.result; - } - - return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); - } - - public static ParseRes parseStatement(Filename filename, List tokens, int i) { - if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1); - if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); - return ParseRes.any( - parseVariableDeclare(filename, tokens, i), - parseReturn(filename, tokens, i), - parseThrow(filename, tokens, i), - parseContinue(filename, tokens, i), - parseBreak(filename, tokens, i), - parseDebug(filename, tokens, i), - parseIf(filename, tokens, i), - parseWhile(filename, tokens, i), - parseSwitch(filename, tokens, i), - parseFor(filename, tokens, i), - parseForIn(filename, tokens, i), - parseForOf(filename, tokens, i), - parseDoWhile(filename, tokens, i), - parseCatch(filename, tokens, i), - parseCompound(filename, tokens, i), - parseFunction(filename, tokens, i, true), - parseValueStatement(filename, tokens, i) - ); - } - - public static Statement[] parse(Filename filename, String raw) { - var tokens = tokenize(filename, raw); - var list = new ArrayList(); - int i = 0; - - while (true) { - if (i >= tokens.size()) break; - - var res = Parsing.parseStatement(filename, tokens, i); - - if (res.isError()) throw new SyntaxException(getLoc(filename, tokens, i), res.error); - else if (res.isFailed()) throw new SyntaxException(getLoc(filename, tokens, i), "Unexpected syntax."); - - i += res.n; - - list.add(res.result); - } - - return list.toArray(Statement[]::new); - } - - public static CompileResult compile(Statement ...statements) { - var target = new CompileResult(new LocalScopeRecord()); - var stm = new CompoundStatement(null, true, statements); - - target.scope.define("this"); - target.scope.define("arguments"); - - try { - stm.compile(target, true); - FunctionStatement.checkBreakAndCont(target, 0); - } - catch (SyntaxException e) { - target = new CompileResult(new LocalScopeRecord()); - - target.scope.define("this"); - target.scope.define("arguments"); - - target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); - } - - target.add(Instruction.ret()).setLocation(stm.loc()); - - return target; - } - public static CompileResult compile(Filename filename, String raw) { - return compile(parse(filename, raw)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java deleted file mode 100644 index e300d6b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -public class RawToken { - public final String value; - public final TokenType type; - public final int line; - public final int start; - - public RawToken(String value, TokenType type, int line, int start) { - this.value = value; - this.type = type; - this.line = line; - this.start = start; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java deleted file mode 100644 index c399a9e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.parsing.ParseRes.State; - -public class TestRes { - public final State state; - public final String error; - public final int i; - - private TestRes(ParseRes.State state, String error, int i) { - this.i = i; - this.state = state; - this.error = error; - } - - public TestRes add(int n) { - return new TestRes(state, null, this.i + n); - } - public ParseRes transform() { - if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed."); - else if (isError()) return ParseRes.error(null, error); - else return ParseRes.failed(); - } - - public boolean isSuccess() { return state.isSuccess(); } - public boolean isFailed() { return state.isFailed(); } - public boolean isError() { return state.isError(); } - - public static TestRes failed() { - return new TestRes(State.FAILED, null, 0); - } - public static TestRes error(Location loc, String error) { - if (loc != null) error = loc + ": " + error; - return new TestRes(State.ERROR, error, 0); - } - public static TestRes error(Location loc, String error, TestRes other) { - if (loc != null) error = loc + ": " + error; - if (!other.isError()) return new TestRes(State.ERROR, error, 0); - return new TestRes(State.ERROR, other.error, 0); - } - public static TestRes res(int i) { - return new TestRes(State.SUCCESS, null, i); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java deleted file mode 100644 index a3f4f0a..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -public class Token { - public final Object value; - public final String rawValue; - public final boolean isString; - public final boolean isRegex; - public final int line; - public final int start; - - private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) { - this.value = value; - this.rawValue = rawValue; - this.line = line; - this.start = start; - this.isString = isString; - this.isRegex = isRegex; - } - private Token(int line, int start, Object value, String rawValue) { - this.value = value; - this.rawValue = rawValue; - this.line = line; - this.start = start; - this.isString = false; - this.isRegex = false; - } - - public boolean isString() { return isString; } - public boolean isRegex() { return isRegex; } - public boolean isNumber() { return value instanceof Number; } - public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; } - public boolean isOperator() { return value instanceof Operator; } - - public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); } - public boolean isOperator(Operator op) { return value.equals(op); } - - public String string() { return (String)value; } - public String regex() { return (String)value; } - public double number() { return (double)value; } - public String identifier() { return (String)value; } - public Operator operator() { return (Operator)value; } - - public static Token regex(int line, int start, String val, String rawValue) { - return new Token(line, start, val, rawValue, false, true); - } - public static Token string(int line, int start, String val, String rawValue) { - return new Token(line, start, val, rawValue, true, false); - } - public static Token number(int line, int start, double val, String rawValue) { - return new Token(line, start, val, rawValue); - } - public static Token identifier(int line, int start, String val) { - return new Token(line, start, val, val); - } - public static Token operator(int line, int start, Operator val) { - return new Token(line, start, val, val.readable); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java deleted file mode 100644 index a34e821..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -enum TokenType { - REGEX, - STRING, - NUMBER, - LITERAL, - OPERATOR, -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java new file mode 100644 index 0000000..4f8f143 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -0,0 +1,139 @@ +package me.topchetoeu.jscript.compilation.scope; + +import java.util.HashMap; +import java.util.HashSet; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class FunctionScope extends Scope { + private final VariableList captures = new VariableList().setIndexMap(v -> ~v); + private final VariableList specials = new VariableList(); + private final VariableList locals = new VariableList(specials); + private final HashMap childToParent = new HashMap<>(); + private final HashSet blacklistNames = new HashSet<>(); + + private final Scope captureParent; + + public final boolean passtrough; + + private void removeCapture(String name) { + var res = captures.remove(name); + if (res != null) { + childToParent.remove(res); + res.setIndexSupplier(() -> { throw new SyntaxException(null, res.name + " has been shadowed"); }); + } + } + + @Override public Variable define(Variable var, Location loc) { + checkNotEnded(); + if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name); + + if (passtrough) { + blacklistNames.add(var.name); + return null; + } + + removeCapture(var.name); + return locals.add(var); + } + @Override public Variable defineStrict(Variable var, Location loc) { + checkNotEnded(); + if (locals.has(var.name)) throw alreadyDefinedErr(loc, var.name); + if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name); + + var res = super.defineStrict(var, loc); + removeCapture(var.name); + return res; + } + public Variable defineSpecial(Variable var, Location loc) { + return specials.add(var); + } + + @Override public boolean flattenVariable(Variable variable, boolean capturable) { + // if (!ended()) throw new IllegalStateException("Tried to flatten a variable before the scope has ended"); + this.locals.overlay(variable); + return true; + } + + @Override public Variable get(String name, boolean capture) { + var superRes = super.get(name, capture); + if (superRes != null) return superRes; + + if (specials.has(name)) return addCaptured(specials.get(name), capture); + if (locals.has(name)) return addCaptured(locals.get(name), capture); + if (captures.has(name)) return addCaptured(captures.get(name), capture); + + if (captureParent == null) return null; + + var parentVar = captureParent.get(name, true); + if (parentVar == null) return null; + + var childVar = captures.add(parentVar.clone()); + + childToParent.put(childVar, parentVar); + + return childVar; + } + + @Override public boolean has(String name, boolean capture) { + if (specials.has(name)) return true; + if (locals.has(name)) return true; + + if (capture) { + if (captures.has(name)) return true; + if (captureParent != null) return captureParent.has(name, true); + } + + return false; + } + + @Override public boolean finish() { + if (!super.finish()) return false; + + captures.freeze(); + locals.freeze(); + specials.freeze(); + + return true; + } + + @Override public int allocCount() { + return 0; + } + @Override public int capturesCount() { + return captures.size(); + } + @Override public int localsCount() { + return locals.size() + specials.size() + super.allocCount(); + } + + public int offset() { + return specials.size() + locals.size(); + } + + public int[] getCaptureIndices() { + var res = new int[captures.size()]; + var i = 0; + + for (var el : captures.all()) { + assert childToParent.containsKey(el); + res[i] = childToParent.get(el).index(); + i++; + } + + return res; + } + + public FunctionScope(Scope parent) { + super(); + if (parent.finished()) throw new RuntimeException("Parent is finished"); + this.captureParent = parent; + this.passtrough = false; + } + public FunctionScope(boolean passtrough) { + super(); + this.captureParent = null; + this.passtrough = passtrough; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java deleted file mode 100644 index 070168d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java +++ /dev/null @@ -1,77 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import java.util.ArrayList; - -public class LocalScopeRecord implements ScopeRecord { - public final LocalScopeRecord parent; - - private final ArrayList captures = new ArrayList<>(); - private final ArrayList locals = new ArrayList<>(); - - public String[] captures() { - return captures.toArray(String[]::new); - } - public String[] locals() { - return locals.toArray(String[]::new); - } - - public LocalScopeRecord child() { - return new LocalScopeRecord(this); - } - - public int localsCount() { - return locals.size(); - } - public int capturesCount() { - return captures.size(); - } - - public int[] getCaptures() { - var buff = new int[captures.size()]; - var i = 0; - - for (var name : captures) { - var index = parent.getKey(name); - if (index instanceof Integer) buff[i++] = (int)index; - } - - var res = new int[i]; - System.arraycopy(buff, 0, res, 0, i); - - return res; - } - - public Object getKey(String name) { - var capI = captures.indexOf(name); - var locI = locals.lastIndexOf(name); - if (locI >= 0) return locI; - if (capI >= 0) return ~capI; - if (parent != null) { - var res = parent.getKey(name); - if (res != null && res instanceof Integer) { - captures.add(name); - return -captures.size(); - } - } - - return name; - } - public Object define(String name, boolean force) { - if (!force && locals.contains(name)) return locals.indexOf(name); - locals.add(name); - return locals.size() - 1; - } - public Object define(String name) { - return define(name, false); - } - public void undefine() { - locals.remove(locals.size() - 1); - } - - public LocalScopeRecord() { - this.parent = null; - } - public LocalScopeRecord(LocalScopeRecord parent) { - this.parent = parent; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java new file mode 100644 index 0000000..3129e36 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -0,0 +1,197 @@ +package me.topchetoeu.jscript.compilation.scope; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class Scope { + protected final VariableList variables = new VariableList(this::parentOffset); + + private boolean ended = false; + private boolean finished = false; + private Scope child; + private List prevChildren = new LinkedList<>(); + + public final Scope parent; + public final HashSet captured = new HashSet<>(); + + protected final Variable addCaptured(Variable var, boolean captured) { + if (captured) this.captured.add(var); + return var; + } + + /** + * Wether or not the scope is going to be entered multiple times. + * If set to true, captured variables will be kept as allocations, otherwise will be converted to locals + */ + public boolean singleEntry = true; + + private final int parentOffset() { + if (parent != null) return parent.offset(); + else return 0; + } + + protected final SyntaxException alreadyDefinedErr(Location loc, String name) { + return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name)); + } + + /** + * Throws if the scope is ended + */ + protected final void checkNotEnded() { + if (ended) throw new IllegalStateException("Cannot define in an ended scope"); + } + + /** + * Defines an ES5-style variable + * + * @returns The index supplier of the variable if it is a local, or null if it is a global + * @throws SyntaxException If an ES2015-style variable with the same name exists anywhere from the current function to the current scope + * @throws RuntimeException If the scope is finalized or has an active child + */ + public Variable define(Variable var, Location loc) { + checkNotEnded(); + if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name); + if (parent != null) return parent.define(var, loc); + + return null; + } + + /** + * Defines an ES2015-style variable + * @param readonly True if const, false if let + * @return The index supplier of the variable + * @throws SyntaxException If any variable with the same name exists in the current scope + * @throws RuntimeException If the scope is finalized or has an active child + */ + public Variable defineStrict(Variable var, Location loc) { + checkNotEnded(); + if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name); + + variables.add(var); + return var.setIndexSupplier(() -> variables.indexOfKey(var.name)); + } + /** + * Gets the index supplier of the given variable name, or null if it is a global + * + * @param capture If true, the variable is being captured by a function + */ + public Variable get(String name, boolean capture) { + var res = variables.get(name); + if (res != null) return addCaptured(res, capture); + if (parent != null) return parent.get(name, capture); + + return null; + } + /** + * Checks if the given variable name is accessible + * + * @param capture If true, will check beyond this function's scope + */ + public boolean has(String name, boolean capture) { + if (variables.has(name)) return true; + if (parent != null) return parent.has(name, capture); + + return false; + } + /** + * Gets the index offset from this scope to its children + */ + public int offset() { + if (parent != null) return parent.offset() + variables.size(); + else return variables.size(); + } + + /** + * Adds this variable to the current function's locals record. Capturable indicates whether or not the variable + * should still be capturable, or be put in an array (still not implemented) + * + * @return Whether or not the request was actually fuliflled + */ + public boolean flattenVariable(Variable variable, boolean capturable) { + if (singleEntry || !capturable) { + if (parent == null) return false; + return parent.flattenVariable(variable, capturable); + } + else { + variables.overlay(variable); + return true; + } + } + + public int localsCount() { return 0; } + public int capturesCount() { return 0; } + public int allocCount() { return variables.size(); } + + /** + * Ends this scope. This will make it possible for another child to take its place + */ + public boolean end() { + if (ended) return false; + + this.ended = true; + + if (this.parent != null) { + assert this.parent.child == this; + this.parent.child = null; + } + + return true; + } + + /** + * Finalizes this scope. The scope will become immutable after this call + * @return + */ + public boolean finish() { + if (finished) return false; + if (parent != null && parent.finished) throw new IllegalStateException("Tried to finish a child after the parent was finished"); + + for (var child : prevChildren) child.finish(); + + var captured = new HashSet(); + var normal = new HashSet(); + + for (var v : variables.all()) { + if (this.captured.contains(v)) { + if (singleEntry) captured.add(v); + } + else normal.add(v); + } + + for (var v : captured) variables.remove(v); + for (var v : normal) variables.remove(v); + + for (var v : captured) flattenVariable(v, true); + for (var v : normal) flattenVariable(v, false); + + + this.variables.freeze(); + this.finished = true; + + return true; + } + + public final boolean ended() { return ended; } + public final boolean finished() { return finished; } + public final Scope child() { return child; } + + public Scope() { + this(null); + } + public Scope(Scope parent) { + if (parent != null) { + if (parent.ended) throw new RuntimeException("Parent is not active"); + if (parent.finished) throw new RuntimeException("Parent is finished"); + if (parent.child != null) throw new RuntimeException("Parent has an active child"); + + this.parent = parent; + this.parent.child = this; + this.parent.prevChildren.add(this); + } + else this.parent = null; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java deleted file mode 100644 index fe08968..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -public interface ScopeRecord { - public Object getKey(String name); - public Object define(String name); - public LocalScopeRecord child(); -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java new file mode 100644 index 0000000..af74088 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java @@ -0,0 +1,41 @@ +package me.topchetoeu.jscript.compilation.scope; + +import java.util.function.IntSupplier; + +public final class Variable { + private IntSupplier indexSupplier; + private boolean frozen; + + public final boolean readonly; + public final String name; + + public final int index() { + if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized"); + return indexSupplier.getAsInt(); + } + + public final void freeze() { + this.frozen = true; + } + + public final Variable setIndexSupplier(IntSupplier index) { + this.indexSupplier = index; + return this; + } + public final IntSupplier indexSupplier() { + return indexSupplier; + } + + public final Variable clone() { + return new Variable(name, readonly).setIndexSupplier(indexSupplier); + } + + public Variable(String name, boolean readonly) { + this.name = name; + this.readonly = readonly; + } + + public static Variable of(String name, boolean readonly, int i) { + return new Variable(name, readonly).setIndexSupplier(() -> i); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java new file mode 100644 index 0000000..9dfed4b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java @@ -0,0 +1,233 @@ +package me.topchetoeu.jscript.compilation.scope; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.function.IntSupplier; +import java.util.function.IntUnaryOperator; +import java.util.stream.StreamSupport; + +public final class VariableList { + private final class Node implements IntSupplier { + public Variable var; + public Node next; + public Node prev; + public boolean frozen; + public int index; + + @Override public int getAsInt() { + if (frozen) { + if (offset == null) { + return indexConverter == null ? index : indexConverter.applyAsInt(index); + } + else { + return indexConverter == null ? + index + offset.getAsInt() : + indexConverter.applyAsInt(index + offset.getAsInt()); + } + } + + var res = 0; + if (offset != null) res = offset.getAsInt(); + + for (var it = prev; it != null; it = it.prev) { + res++; + } + + return indexConverter == null ? res : indexConverter.applyAsInt(res); + } + + public void freeze() { + if (frozen) return; + this.frozen = true; + this.next = null; + this.var.freeze(); + if (prev == null) return; + + this.index = prev.index + 1; + this.next = null; + + return; + } + + public Node(Variable var, Node next, Node prev) { + this.var = var; + this.next = next; + this.prev = prev; + } + } + + private Node first, last; + + private final HashMap map = new HashMap<>(); + private ArrayList frozenList = null; + private HashMap varMap = new HashMap<>(); + + private final IntSupplier offset; + private IntUnaryOperator indexConverter = null; + + public boolean frozen() { + if (frozenList != null) { + assert frozenList != null; + assert map != null; + assert first == null; + assert last == null; + + return true; + } + else { + assert frozenList == null; + assert map != null; + + return false; + } + } + + private Variable add(Variable val, boolean overlay) { + if (frozen()) throw new RuntimeException("The scope has been frozen"); + if (!overlay && map.containsKey(val.name)) { + var node = this.map.get(val.name); + val.setIndexSupplier(node); + return node.var; + } + + var node = new Node(val, null, last); + + if (last != null) { + assert first != null; + + last.next = node; + node.prev = last; + + last = node; + } + else { + first = last = node; + } + + map.put(val.name, node); + varMap.put(val, node); + val.setIndexSupplier(node); + + return val; + } + + public Variable add(Variable val) { + return this.add(val, false); + } + public Variable overlay(Variable val) { + return this.add(val, true); + } + public Variable remove(String key) { + var res = map.get(key); + if (res != null) return remove(res.var); + else return null; + } + public Variable remove(Variable var) { + if (var == null) return null; + if (frozen()) throw new RuntimeException("The scope has been frozen"); + + var node = varMap.get(var); + if (node == null) return null; + + if (node.prev != null) { + assert node != first; + node.prev.next = node.next; + } + else { + assert node == first; + first = first.next; + } + + if (node.next != null) { + assert node != last; + node.next.prev = node.prev; + } + else { + assert node == last; + last = last.prev; + } + + node.next = null; + node.prev = null; + + map.remove(node.var.name); + varMap.remove(node.var); + + return node.var; + } + + public Variable get(String name) { + var res = map.get(name); + if (res != null) return res.var; + else return null; + } + public int indexOfKey(String name) { + return map.get(name).getAsInt(); + } + + public boolean has(String name) { + return this.map.containsKey(name); + } + + public int size() { + if (frozen()) return frozenList.size(); + else return map.size(); + } + + public void freeze() { + if (frozen()) return; + + frozenList = new ArrayList<>(); + + for (var node = first; node != null; ) { + frozenList.add(node); + + var tmp = node; + node = node.next; + tmp.freeze(); + } + + first = last = null; + varMap = null; + } + + public Iterable all() { + if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator(); + else return () -> new Iterator() { + private Node curr = first; + + @Override public boolean hasNext() { + return curr != null; + } + @Override public Variable next() { + if (curr == null) return null; + + var res = curr; + curr = curr.next; + return res.var; + } + }; + } + public Iterable keys() { + return () -> StreamSupport.stream(all().spliterator(), false).map(v -> v.name).iterator(); + } + + public VariableList setIndexMap(IntUnaryOperator map) { + indexConverter = map; + return this; + } + + public VariableList(IntSupplier offset) { + this.offset = offset; + } + public VariableList(int offset) { + this.offset = () -> offset; + } + public VariableList(VariableList prev) { + this.offset = prev::size; + } + public VariableList() { + this.offset = null; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java new file mode 100644 index 0000000..38ab529 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + + +public class ArgumentsNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadArgs(false)); + } + + public ArgumentsNode(Location loc) { + super(loc); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java new file mode 100644 index 0000000..4cbf300 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java @@ -0,0 +1,82 @@ +package me.topchetoeu.jscript.compilation.values; + +import java.util.ArrayList; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + + +public class ArrayNode extends Node { + public final Node[] statements; + + @Override public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.loadArr(statements.length)); + + if (statements.length > 0) target.add(Instruction.dup(statements.length)); + + for (var i = 0; i < statements.length; i++) { + var el = statements[i]; + if (el != null) { + el.compile(target, true); + target.add(Instruction.storeMember(i)); + } + } + + if (!pollute) target.add(Instruction.discard()); + } + + public ArrayNode(Location loc, Node[] statements) { + super(loc); + this.statements = statements; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "[")) return ParseRes.failed(); + n++; + + var values = new ArrayList(); + + loop: while (true) { + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, "]")) { + n++; + break; + } + + while (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + values.add(null); + + if (src.is(i + n, "]")) { + n++; + break loop; + } + } + + var res = JavaScript.parseExpression(src, i + n, 2); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element."); + n += res.n; + n += Parsing.skipEmpty(src, i + n); + + values.add(res.result); + + if (src.is(i + n, ",")) n++; + else if (src.is(i + n, "]")) { + n++; + break; + } + } + + return ParseRes.res(new ArrayNode(loc, values.toArray(Node[]::new)), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java deleted file mode 100644 index e24e50b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ArrayStatement extends Statement { - public final Statement[] statements; - - @Override public boolean pure() { - for (var stm : statements) { - if (!stm.pure()) return false; - } - - return true; - } - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.loadArr(statements.length)); - - for (var i = 0; i < statements.length; i++) { - var el = statements[i]; - if (el != null) { - target.add(Instruction.dup()); - target.add(Instruction.pushValue(i)); - el.compile(target, true); - target.add(Instruction.storeMember()); - } - } - - if (!pollute) target.add(Instruction.discard()); - } - - public ArrayStatement(Location loc, Statement[] statements) { - super(loc); - this.statements = statements; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java deleted file mode 100644 index 412eacc..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ /dev/null @@ -1,43 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class CallStatement extends Statement { - public final Statement func; - public final Statement[] args; - public final boolean isNew; - - @Override - public void compile(CompileResult target, boolean pollute, BreakpointType type) { - if (isNew) func.compile(target, true); - else if (func instanceof IndexStatement) { - ((IndexStatement)func).compile(target, true, true); - } - else { - target.add(Instruction.pushUndefined()); - func.compile(target, true); - } - - for (var arg : args) arg.compile(target, true); - - if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type); - else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type); - - if (!pollute) target.add(Instruction.discard()); - } - @Override - public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, BreakpointType.STEP_IN); - } - - public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) { - super(loc); - this.isNew = isNew; - this.func = func; - this.args = args; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java deleted file mode 100644 index 26ae605..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.compilation.AssignableStatement; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ChangeStatement extends Statement { - public final AssignableStatement value; - public final double addAmount; - public final boolean postfix; - - @Override - public void compile(CompileResult target, boolean pollute) { - value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); - if (!pollute) target.add(Instruction.discard()); - else if (postfix) { - target.add(Instruction.pushValue(addAmount)); - target.add(Instruction.operation(Operation.SUBTRACT)); - } - } - - public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { - super(loc); - this.value = value; - this.addAmount = addAmount; - this.postfix = postfix; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java deleted file mode 100644 index 2def204..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ConstantStatement extends Statement { - public final Object value; - public final boolean isNull; - - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - if (pollute) { - if (isNull) target.add(Instruction.pushNull()); - else if (value instanceof Double) target.add(Instruction.pushValue((Double)value)); - else if (value instanceof String) target.add(Instruction.pushValue((String)value)); - else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value)); - else target.add(Instruction.pushUndefined()); - } - } - - private ConstantStatement(Location loc, Object val, boolean isNull) { - super(loc); - this.value = val; - this.isNull = isNull; - } - - public ConstantStatement(Location loc, boolean val) { - this(loc, val, false); - } - public ConstantStatement(Location loc, String val) { - this(loc, val, false); - } - public ConstantStatement(Location loc, double val) { - this(loc, val, false); - } - - public static ConstantStatement ofUndefined(Location loc) { - return new ConstantStatement(loc, null, false); - } - public static ConstantStatement ofNull(Location loc) { - return new ConstantStatement(loc, null, true); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java deleted file mode 100644 index 40ad0cd..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class DiscardStatement extends Statement { - public final Statement value; - - @Override public boolean pure() { return value.pure(); } - - @Override - public void compile(CompileResult target, boolean pollute) { - value.compile(target, false); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public DiscardStatement(Location loc, Statement val) { - super(loc); - this.value = val; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java deleted file mode 100644 index db7e331..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ /dev/null @@ -1,127 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -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.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundStatement; -import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class FunctionStatement extends Statement { - public final CompoundStatement body; - public final String varName; - public final String[] args; - public final boolean statement; - public final Location end; - - @Override public boolean pure() { return varName == null && statement; } - - @Override - public void declare(CompileResult target) { - if (varName != null && statement) target.scope.define(varName); - } - - public static void checkBreakAndCont(CompileResult target, int start) { - for (int i = start; i < target.size(); i++) { - if (target.get(i).type == Type.NOP) { - if (target.get(i).is(0, "break") ) { - throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop."); - } - if (target.get(i).is(0, "cont")) { - throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop."); - } - } - } - } - - private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) { - for (var i = 0; i < args.length; i++) { - for (var j = 0; j < i; j++) { - if (args[i].equals(args[j])) { - throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."); - } - } - } - - var subtarget = new CompileResult(target.scope.child()); - - subtarget.scope.define("this"); - var argsVar = subtarget.scope.define("arguments"); - - if (args.length > 0) { - for (var i = 0; i < args.length; i++) { - subtarget.add(Instruction.loadVar(argsVar)); - subtarget.add(Instruction.pushValue(i)); - subtarget.add(Instruction.loadMember()); - subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i]))); - } - } - - if (!statement && this.varName != null) { - subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp); - } - - body.declare(subtarget); - body.compile(subtarget, false); - subtarget.length = args.length; - subtarget.add(Instruction.ret()).setLocation(end); - checkBreakAndCont(subtarget, 0); - - if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures())); - return target.addChild(subtarget); - } - - public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - if (this.varName != null) name = this.varName; - - var hasVar = this.varName != null && statement; - var hasName = name != null; - - compileBody(target, pollute || hasVar || hasName, bp); - - if (hasName) { - if (pollute || hasVar) target.add(Instruction.dup()); - target.add(Instruction.pushValue("name")); - target.add(Instruction.pushValue(name)); - target.add(Instruction.storeMember()); - } - - if (hasVar) { - var key = target.scope.getKey(this.varName); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); - target.add(Instruction.storeVar(target.scope.getKey(this.varName), false)); - } - } - public void compile(CompileResult target, boolean pollute, String name) { - compile(target, pollute, name, BreakpointType.NONE); - } - @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { - compile(target, pollute, (String)null, bp); - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, (String)null, BreakpointType.NONE); - } - - public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) { - super(loc); - - this.end = end; - this.varName = varName; - this.statement = statement; - - this.args = args; - this.body = body; - } - - public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name) { - if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name); - else stm.compile(target, pollute); - } - public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name, BreakpointType bp) { - if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp); - else stm.compile(target, pollute, bp); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java new file mode 100644 index 0000000..11823a3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + + +public class GlobalThisNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadGlob()); + } + + public GlobalThisNode(Location loc) { + super(loc); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java deleted file mode 100644 index b43cfc2..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class GlobalThisStatement extends Statement { - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadGlob()); - } - - public GlobalThisStatement(Location loc) { - super(loc); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java deleted file mode 100644 index 1716336..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class IndexAssignStatement extends Statement { - public final Statement object; - public final Statement index; - public final Statement value; - public final Operation operation; - - @Override - public void compile(CompileResult target, boolean pollute) { - if (operation != null) { - object.compile(target, true); - index.compile(target, true); - target.add(Instruction.dup(2)); - - target.add(Instruction.loadMember()); - value.compile(target, true); - target.add(Instruction.operation(operation)); - - target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN); - } - else { - object.compile(target, true); - index.compile(target, true); - value.compile(target, true); - - target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN);; - } - } - - public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) { - super(loc); - this.object = object; - this.index = index; - this.value = value; - this.operation = operation; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java deleted file mode 100644 index 58315dc..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.AssignableStatement; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class IndexStatement extends AssignableStatement { - public final Statement object; - public final Statement index; - - @Override - public Statement toAssign(Statement val, Operation operation) { - return new IndexAssignStatement(loc(), object, index, val, operation); - } - public void compile(CompileResult target, boolean dupObj, boolean pollute) { - object.compile(target, true); - if (dupObj) target.add(Instruction.dup()); - - index.compile(target, true); - target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN); - if (!pollute) target.add(Instruction.discard()); - } - @Override - public void compile(CompileResult target, boolean pollute) { - compile(target, false, pollute); - } - - public IndexStatement(Location loc, Statement object, Statement index) { - super(loc); - this.object = object; - this.index = index; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java deleted file mode 100644 index 306ec25..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class LazyAndStatement extends Statement { - public final Statement first, second; - - @Override public boolean pure() { return first.pure() && second.pure(); } - - @Override - public void compile(CompileResult target, boolean pollute) { - first.compile(target, true); - if (pollute) target.add(Instruction.dup()); - int start = target.temp(); - if (pollute) target.add(Instruction.discard()); - second.compile(target, pollute); - target.set(start, Instruction.jmpIfNot(target.size() - start)); - } - - public LazyAndStatement(Location loc, Statement first, Statement second) { - super(loc); - this.first = first; - this.second = second; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java deleted file mode 100644 index b1461ed..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class LazyOrStatement extends Statement { - public final Statement first, second; - - @Override public boolean pure() { return first.pure() && second.pure(); } - - @Override - public void compile(CompileResult target, boolean pollute) { - first.compile(target, true); - if (pollute) target.add(Instruction.dup()); - int start = target.temp(); - if (pollute) target.add(Instruction.discard()); - second.compile(target, pollute); - target.set(start, Instruction.jmpIf(target.size() - start)); - } - - public LazyOrStatement(Location loc, Statement first, Statement second) { - super(loc); - this.first = first; - this.second = second; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java new file mode 100644 index 0000000..5a5de0e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -0,0 +1,179 @@ +package me.topchetoeu.jscript.compilation.values; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.FunctionNode; +import me.topchetoeu.jscript.compilation.FunctionValueNode; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + + +public class ObjectNode extends Node { + public static class ObjProp { + public final String name; + public final String access; + public final FunctionValueNode func; + + public ObjProp(String name, String access, FunctionValueNode func) { + this.name = name; + this.access = access; + this.func = func; + } + } + + public final Map map; + public final Map getters; + public final Map setters; + + @Override public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.loadObj()); + + for (var el : map.entrySet()) { + target.add(Instruction.dup()); + var val = el.getValue(); + FunctionNode.compileWithName(val, target, true, el.getKey().toString()); + target.add(Instruction.storeMember(el.getKey())); + } + + var keys = new ArrayList(); + keys.addAll(getters.keySet()); + keys.addAll(setters.keySet()); + + for (var key : keys) { + target.add(Instruction.pushValue((String)key)); + + if (getters.containsKey(key)) getters.get(key).compile(target, true); + else target.add(Instruction.pushUndefined()); + + if (setters.containsKey(key)) setters.get(key).compile(target, true); + else target.add(Instruction.pushUndefined()); + + target.add(Instruction.defProp()); + } + + if (!pollute) target.add(Instruction.discard()); + } + + public ObjectNode(Location loc, Map map, Map getters, Map setters) { + super(loc); + this.map = map; + this.getters = getters; + this.setters = setters; + } + + private static ParseRes parsePropName(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var res = ParseRes.first(src, i + n, + Parsing::parseIdentifier, + Parsing::parseString, + (s, j) -> Parsing.parseNumber(s, j, false) + ); + n += res.n; + + if (!res.isSuccess()) return res.chainError(); + return ParseRes.res(res.result.toString(), n); + } + private static ParseRes parseObjectProp(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var access = Parsing.parseIdentifier(src, i + n); + if (!access.isSuccess()) return ParseRes.failed(); + if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); + n += access.n; + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); + n += name.n; + + var params = JavaScript.parseParameters(src, i + n); + if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); + n += params.n; + + var body = CompoundNode.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); + n += body.n; + + var end = src.loc(i + n - 1); + + return ParseRes.res(new ObjProp( + name.result, access.result, + new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString()) + ), n); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; + n += Parsing.skipEmpty(src, i + n); + + var values = new LinkedHashMap(); + var getters = new LinkedHashMap(); + var setters = new LinkedHashMap(); + + if (src.is(i + n, "}")) { + n++; + return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); + } + + while (true) { + var prop = parseObjectProp(src, i + n); + + if (prop.isSuccess()) { + n += prop.n; + + if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func); + else getters.put(prop.result.name, prop.result.func); + } + else { + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); + n++; + + var valRes = JavaScript.parseExpression(src, i + n, 2); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list"); + n += valRes.n; + + values.put(name.result, valRes.result); + } + + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { + n++; + break; + } + + continue; + } + else if (src.is(i + n, "}")) { + n++; + break; + } + else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); + } + + return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); + } + +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java deleted file mode 100644 index f5af390..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java +++ /dev/null @@ -1,61 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.ArrayList; -import java.util.Map; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class ObjectStatement extends Statement { - public final Map map; - public final Map getters; - public final Map setters; - - @Override public boolean pure() { - for (var el : map.values()) { - if (!el.pure()) return false; - } - - return true; - } - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.loadObj()); - - for (var el : map.entrySet()) { - target.add(Instruction.dup()); - target.add(Instruction.pushValue(el.getKey())); - var val = el.getValue(); - FunctionStatement.compileWithName(val, target, true, el.getKey().toString()); - target.add(Instruction.storeMember()); - } - - var keys = new ArrayList(); - keys.addAll(getters.keySet()); - keys.addAll(setters.keySet()); - - for (var key : keys) { - target.add(Instruction.pushValue((String)key)); - - if (getters.containsKey(key)) getters.get(key).compile(target, true); - else target.add(Instruction.pushUndefined()); - - if (setters.containsKey(key)) setters.get(key).compile(target, true); - else target.add(Instruction.pushUndefined()); - - target.add(Instruction.defProp()); - } - - if (!pollute) target.add(Instruction.discard()); - } - - public ObjectStatement(Location loc, Map map, Map getters, Map setters) { - super(loc); - this.map = map; - this.getters = getters; - this.setters = setters; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java deleted file mode 100644 index cf66133..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class OperationStatement extends Statement { - public final Statement[] args; - public final Operation operation; - - @Override public boolean pure() { - for (var el : args) { - if (!el.pure()) return false; - } - - return true; - } - - @Override - public void compile(CompileResult target, boolean pollute) { - for (var arg : args) { - arg.compile(target, true); - } - - if (pollute) target.add(Instruction.operation(operation)); - else target.add(Instruction.discard()); - } - - public OperationStatement(Location loc, Operation operation, Statement ...args) { - super(loc); - this.operation = operation; - this.args = args; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java new file mode 100644 index 0000000..7ac7c51 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java @@ -0,0 +1,76 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + +public class RegexNode extends Node { + public final String pattern, flags; + + @Override public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.loadRegex(pattern, flags)); + if (!pollute) target.add(Instruction.discard()); + } + + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, '/')) return ParseRes.failed(); + var loc = src.loc(i + n); + n++; + + var source = new StringBuilder(); + var flags = new StringBuilder(); + + var inBrackets = false; + + while (true) { + if (src.is(i + n, '[')) { + n++; + inBrackets = true; + source.append(src.at(i + n)); + continue; + } + else if (src.is(i + n, ']')) { + n++; + inBrackets = false; + source.append(src.at(i + n)); + continue; + } + else if (src.is(i + n, '/') && !inBrackets) { + n++; + break; + } + + var charRes = Parsing.parseChar(src, i + n); + if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed"); + source.append(charRes.result); + n++; + } + + while (true) { + char c = src.at(i + n, '\0'); + + if (src.is(i + n, v -> Parsing.isAny(c, "dgimsuy"))) { + if (flags.indexOf(c + "") >= 0) return ParseRes.error(src.loc(i + n), "The flags of a regular expression may not be repeated"); + flags.append(c); + } + else break; + + n++; + } + + return ParseRes.res(new RegexNode(loc, source.toString(), flags.toString()), n); + } + + public RegexNode(Location loc, String pattern, String flags) { + super(loc); + this.pattern = pattern; + this.flags = flags; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java deleted file mode 100644 index fe2bdb8..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class RegexStatement extends Statement { - public final String pattern, flags; - - // Not really pure, since a function is called, but can be ignored. - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.loadRegex(pattern, flags)); - if (!pollute) target.add(Instruction.discard()); - } - - public RegexStatement(Location loc, String pattern, String flags) { - super(loc); - this.pattern = pattern; - this.flags = flags; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java new file mode 100644 index 0000000..ad74007 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + + +public class ThisNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadThis()); + } + + public ThisNode(Location loc) { + super(loc); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java deleted file mode 100644 index 11ab78d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class TypeofStatement extends Statement { - public final Statement value; - - // Not really pure, since a variable from the global scope could be accessed, - // which could lead to code execution, that would get omitted - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - if (value instanceof VariableStatement) { - var i = target.scope.getKey(((VariableStatement)value).name); - if (i instanceof String) { - target.add(Instruction.typeof((String)i)); - return; - } - } - value.compile(target, pollute); - target.add(Instruction.typeof()); - } - - public TypeofStatement(Location loc, Statement value) { - super(loc); - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java deleted file mode 100644 index 31bf6c9..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class VariableAssignStatement extends Statement { - public final String name; - public final Statement value; - public final Operation operation; - - @Override public boolean pure() { return false; } - - @Override - public void compile(CompileResult target, boolean pollute) { - var i = target.scope.getKey(name); - if (operation != null) { - target.add(Instruction.loadVar(i)); - FunctionStatement.compileWithName(value, target, true, name); - target.add(Instruction.operation(operation)); - target.add(Instruction.storeVar(i, pollute)); - } - else { - FunctionStatement.compileWithName(value, target, true, name); - target.add(Instruction.storeVar(i, pollute)); - } - } - - public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { - super(loc); - this.name = name; - this.value = val; - this.operation = operation; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java deleted file mode 100644 index db3e4f0..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class VariableIndexStatement extends Statement { - public final int index; - - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadVar(index)); - } - - public VariableIndexStatement(Location loc, int i) { - super(loc); - this.index = i; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java new file mode 100644 index 0000000..0bf8ea6 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -0,0 +1,89 @@ +package me.topchetoeu.jscript.compilation.values; + +import java.util.function.IntFunction; +import java.util.function.Supplier; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.AssignableNode; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.operations.VariableAssignNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class VariableNode extends Node implements AssignableNode { + public final String name; + + @Override public Node toAssign(Node val, Operation operation) { + return new VariableAssignNode(loc(), name, val, operation); + } + + @Override public void compile(CompileResult target, boolean pollute) { + var i = target.scope.get(name, false); + + if (i == null) { + target.add(_i -> { + if (target.scope.has(name, false)) return Instruction.throwSyntax(loc(), String.format("Cannot access '%s' before initialization", name)); + return Instruction.globGet(name); + }); + + if (!pollute) target.add(Instruction.discard()); + } + else if (pollute) { + target.add(_i -> Instruction.loadVar(i.index())); + } + } + + public static IntFunction toGet(CompileResult target, Location loc, String name, Supplier onGlobal) { + var i = target.scope.get(name, false); + + if (i == null) return _i -> { + if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name)); + else return onGlobal.get(); + }; + else return _i -> Instruction.loadVar(i.index()); + } + public static IntFunction toGet(CompileResult target, Location loc, String name) { + return toGet(target, loc, name, () -> Instruction.globGet(name)); + } + + + public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) { + var i = target.scope.get(name, false); + + if (i == null) return _i -> { + if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name)); + else return Instruction.globSet(name, keep, define); + }; + else if (!define && i.readonly) return _i -> Instruction.throwSyntax(new SyntaxException(loc, "Assignment to constant variable")); + else return _i -> Instruction.storeVar(i.index(), keep); + } + + public VariableNode(Location loc, String name) { + super(loc); + this.name = name; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var literal = Parsing.parseIdentifier(src, i); + if (!literal.isSuccess()) return literal.chainError(); + n += literal.n; + + if (!JavaScript.checkVarName(literal.result)) { + if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported."); + if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported."); + if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported."); + return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result)); + } + + return ParseRes.res(new VariableNode(loc, literal.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java deleted file mode 100644 index 1ae3d2d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.compilation.AssignableStatement; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; - -public class VariableStatement extends AssignableStatement { - public final String name; - - @Override public boolean pure() { return false; } - - @Override - public Statement toAssign(Statement val, Operation operation) { - return new VariableAssignStatement(loc(), name, val, operation); - } - - @Override - public void compile(CompileResult target, boolean pollute) { - var i = target.scope.getKey(name); - target.add(Instruction.loadVar(i)); - if (!pollute) target.add(Instruction.discard()); - } - - public VariableStatement(Location loc, String name) { - super(loc); - this.name = name; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java new file mode 100644 index 0000000..8a5462b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + +public class BoolNode extends Node { + public final boolean value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushValue(value)); + } + + public BoolNode(Location loc, boolean value) { + super(loc); + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java new file mode 100644 index 0000000..cae8bd6 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java @@ -0,0 +1,14 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + +public class NullNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushNull()); + } + + public NullNode(Location loc) { super(loc); } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java new file mode 100644 index 0000000..5afd40e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java @@ -0,0 +1,40 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + +public class NumberNode extends Node { + public final double value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushValue(value)); + } + + public NumberNode(Location loc, double value) { + super(loc); + this.value = value; + } + + public static double power(double a, long b) { + if (b == 0) return 1; + if (b == 1) return a; + if (b < 0) return 1 / power(a, -b); + + if ((b & 1) == 0) return power(a * a, b / 2); + else return a * power(a * a, b / 2); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var res = Parsing.parseNumber(src, i + n, false); + if (res.isSuccess()) return ParseRes.res(new NumberNode(loc, res.result), n + res.n); + else return res.chainError(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java new file mode 100644 index 0000000..e50d82b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java @@ -0,0 +1,31 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + +public class StringNode extends Node { + public final String value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushValue(value)); + } + + public StringNode(Location loc, String value) { + super(loc); + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var res = Parsing.parseString(src, i + n); + if (res.isSuccess()) return ParseRes.res(new StringNode(loc, res.result), n + res.n); + else return res.chainError(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java new file mode 100644 index 0000000..74f11c0 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java @@ -0,0 +1,182 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import java.util.ArrayList; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.ArgumentsNode; +import me.topchetoeu.jscript.compilation.values.ArrayNode; +import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.ThisNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.values.constants.BoolNode; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; + +public class CallNode extends Node { + public static boolean ATTACH_NAME = true; + + public final Node func; + public final Node[] args; + public final boolean isNew; + + private String generateName(Node func, Node index) { + String res = "(intermediate value)"; + boolean shouldParen = false; + + if (func instanceof ObjectNode) { + var obj = (ObjectNode)func; + + shouldParen = true; + + if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}"; + else res = "{(intermediate value)}"; + } + else if (func instanceof StringNode) { + res = JSON.stringify(JSONElement.string(((StringNode)func).value)); + } + else if (func instanceof NumberNode) { + res = JSON.stringify(JSONElement.number(((NumberNode)func).value)); + } + else if (func instanceof BoolNode) { + res = ((BoolNode)func).value ? "true" : "false"; + } + else if (func instanceof VariableNode) { + res = ((VariableNode)func).name; + } + else if (func instanceof ThisNode) { + res = "this"; + } + else if (func instanceof ArgumentsNode) { + res = "arguments"; + } + else if (func instanceof ArrayNode) { + var els = new ArrayList(); + + for (var el : ((ArrayNode)func).statements) { + if (el != null) els.add(generateName(el, null)); + else els.add("(intermediate value)"); + } + + res = "[" + String.join(",", els) + "]"; + } + + if (index == null) return res; + + if (shouldParen) res = "(" + res + ")"; + + if (index instanceof StringNode) { + var val = ((StringNode)index).value; + var bracket = JSON.stringify(JSONElement.string(val)); + + if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]"; + if (Parsing.parseIdentifier(new Source(val), 0).n != val.length()) return res + "[" + bracket + "]"; + + return res + "." + val; + } + + return res + "[" + generateName(index, null) + "]"; + } + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { + if (!isNew && func instanceof IndexNode) { + var obj = ((IndexNode)func).object; + var index = ((IndexNode)func).index; + String name = ""; + + obj.compile(target, true); + index.compile(target, true); + for (var arg : args) arg.compile(target, true); + + if (ATTACH_NAME) name = generateName(obj, index); + + target.add(Instruction.callMember(args.length, name)).setLocationAndDebug(loc(), type); + } + else { + String name = ""; + + func.compile(target, true); + for (var arg : args) arg.compile(target, true); + + if (ATTACH_NAME) name = generateName(func, null); + + if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type); + else target.add(Instruction.call(args.length, name)).setLocationAndDebug(loc(), type); + } + if (!pollute) target.add(Instruction.discard()); + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, BreakpointType.STEP_IN); + } + + public CallNode(Location loc, boolean isNew, Node func, Node ...args) { + super(loc); + this.isNew = isNew; + this.func = func; + this.args = args; + } + + public static ParseRes parseCall(Source src, int i, Node prev, int precedence) { + if (precedence > 17) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "(")) return ParseRes.failed(); + n++; + + var args = new ArrayList(); + boolean prevArg = false; + + while (true) { + var argRes = JavaScript.parseExpression(src, i + n, 2); + n += argRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (argRes.isSuccess()) { + args.add(argRes.result); + prevArg = true; + } + else if (argRes.isError()) return argRes.chainError(); + else if (prevArg && src.is(i + n, ",")) { + prevArg = false; + n++; + } + else if (src.is(i + n, ")")) { + n++; + break; + } + else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren"); + else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren"); + } + + return ParseRes.res(new CallNode(loc, false, prev, args.toArray(Node[]::new)), n); + } + public static ParseRes parseNew(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed(); + n += 3; + + var valRes = JavaScript.parseExpression(src, i + n, 18); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword."); + n += valRes.n; + + var callRes = CallNode.parseCall(src, i + n, valRes.result, 0); + if (callRes.isFailed()) return ParseRes.res(new CallNode(loc, true, valRes.result), n); + if (callRes.isError()) return callRes.chainError(); + n += callRes.n; + + return ParseRes.res(new CallNode(loc, true, callRes.result.func, callRes.result.args), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java new file mode 100644 index 0000000..e8b403a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java @@ -0,0 +1,87 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.AssignableNode; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; + +public class ChangeNode extends Node { + public final AssignableNode value; + public final double addAmount; + public final boolean postfix; + + @Override public void compile(CompileResult target, boolean pollute) { + value.toAssign(new NumberNode(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); + if (!pollute) target.add(Instruction.discard()); + else if (postfix) { + target.add(Instruction.pushValue(addAmount)); + target.add(Instruction.operation(Operation.SUBTRACT)); + } + } + + public ChangeNode(Location loc, AssignableNode value, double addAmount, boolean postfix) { + super(loc); + this.value = value; + this.addAmount = addAmount; + this.postfix = postfix; + } + + public static ParseRes parsePrefixIncrease(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "++")) return ParseRes.failed(); + n += 2; + + var res = JavaScript.parseExpression(src, i + n, 15); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + + return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, 1, false), n + res.n); + } + public static ParseRes parsePrefixDecrease(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "--")) return ParseRes.failed(); + n += 2; + + var res = JavaScript.parseExpression(src, i + n, 15); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + + return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, -1, false), n + res.n); + } + + public static ParseRes parsePostfixIncrease(Source src, int i, Node prev, int precedence) { + if (precedence > 15) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "++")) return ParseRes.failed(); + if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + n += 2; + + return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, 1, true), n); + } + public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { + if (precedence > 15) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "--")) return ParseRes.failed(); + if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + n += 2; + + return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, -1, true), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java new file mode 100644 index 0000000..eaf7b89 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java @@ -0,0 +1,39 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + + +public class DiscardNode extends Node { + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (value != null) value.compile(target, false); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public DiscardNode(Location loc, Node val) { + super(loc); + this.value = val; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed(); + n += 4; + + var valRes = JavaScript.parseExpression(src, i + n, 14); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword."); + n += valRes.n; + + return ParseRes.res(new DiscardNode(loc, valRes.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java new file mode 100644 index 0000000..8765690 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java @@ -0,0 +1,74 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; + +public class IndexAssignNode extends Node { + public final Node object; + public final Node index; + public final Node value; + public final Operation operation; + + @Override public void compile(CompileResult target, boolean pollute) { + if (operation != null) { + object.compile(target, true); + + if (index instanceof NumberNode num && (int)num.value == num.value) { + target.add(Instruction.loadMember((int)num.value)); + value.compile(target, true); + target.add(Instruction.operation(operation)); + target.add(Instruction.storeMember((int)num.value, pollute)); + } + else if (index instanceof StringNode str) { + target.add(Instruction.loadMember(str.value)); + value.compile(target, true); + target.add(Instruction.operation(operation)); + target.add(Instruction.storeMember(str.value, pollute)); + } + else { + index.compile(target, true); + target.add(Instruction.dup(2)); + + target.add(Instruction.loadMember()); + value.compile(target, true); + target.add(Instruction.operation(operation)); + + target.add(Instruction.storeMember(pollute)); + } + target.setLocationAndDebug(loc(), BreakpointType.STEP_IN); + } + else { + object.compile(target, true); + + if (index instanceof NumberNode num && (int)num.value == num.value) { + value.compile(target, true); + target.add(Instruction.storeMember((int)num.value, pollute)); + } + else if (index instanceof StringNode str) { + value.compile(target, true); + target.add(Instruction.storeMember(str.value, pollute)); + } + else { + index.compile(target, true); + value.compile(target, true); + target.add(Instruction.storeMember(pollute)); + } + + target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);; + } + } + + public IndexAssignNode(Location loc, Node object, Node index, Node value, Operation operation) { + super(loc); + this.object = object; + this.index = index; + this.value = value; + this.operation = operation; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java new file mode 100644 index 0000000..d2791ac --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -0,0 +1,87 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.AssignableNode; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; + +public class IndexNode extends Node implements AssignableNode { + public final Node object; + public final Node index; + + @Override public Node toAssign(Node val, Operation operation) { + return new IndexAssignNode(loc(), object, index, val, operation); + } + public void compile(CompileResult target, boolean dupObj, boolean pollute) { + object.compile(target, true); + if (dupObj) target.add(Instruction.dup()); + + if (index instanceof NumberNode num && (int)num.value == num.value) { + target.add(Instruction.loadMember((int)num.value)); + } + else if (index instanceof StringNode str) { + target.add(Instruction.loadMember(str.value)); + } + else { + index.compile(target, true); + target.add(Instruction.loadMember()); + } + + target.setLocationAndDebug(loc(), BreakpointType.STEP_IN); + + if (!pollute) target.add(Instruction.discard()); + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, false, pollute); + } + + public IndexNode(Location loc, Node object, Node index) { + super(loc); + this.object = object; + this.index = index; + } + + public static ParseRes parseIndex(Source src, int i, Node prev, int precedence) { + if (precedence > 18) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "[")) return ParseRes.failed(); + n++; + + var valRes = JavaScript.parseExpression(src, i + n, 0); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression"); + n += valRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket"); + n++; + + return ParseRes.res(new IndexNode(loc, prev, valRes.result), n); + } + public static ParseRes parseMember(Source src, int i, Node prev, int precedence) { + if (precedence > 18) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, ".")) return ParseRes.failed(); + n++; + + var literal = Parsing.parseIdentifier(src, i + n); + if (!literal.isSuccess()) return literal.chainError(src.loc(i + n), "Expected an identifier after member access."); + n += literal.n; + + return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java new file mode 100644 index 0000000..318751e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java @@ -0,0 +1,45 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + +public class LazyAndNode extends Node { + public final Node first, second; + + @Override public void compile(CompileResult target, boolean pollute) { + first.compile(target, true); + if (pollute) target.add(Instruction.dup()); + int start = target.temp(); + if (pollute) target.add(Instruction.discard()); + second.compile(target, pollute); + target.set(start, Instruction.jmpIfNot(target.size() - start)); + } + + public LazyAndNode(Location loc, Node first, Node second) { + super(loc); + this.first = first; + this.second = second; + } + + + public static ParseRes parse(Source src, int i, Node prev, int precedence) { + if (precedence < 4) return ParseRes.failed(); + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "&&")) return ParseRes.failed(); + var loc = src.loc(i + n); + n += 2; + + var res = JavaScript.parseExpression(src, i + n, 4); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator."); + n += res.n; + + return ParseRes.res(new LazyAndNode(loc, prev, res.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java new file mode 100644 index 0000000..9b1e5a9 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java @@ -0,0 +1,46 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + + +public class LazyOrNode extends Node { + public final Node first, second; + + @Override public void compile(CompileResult target, boolean pollute) { + first.compile(target, true); + if (pollute) target.add(Instruction.dup()); + int start = target.temp(); + if (pollute) target.add(Instruction.discard()); + second.compile(target, pollute); + target.set(start, Instruction.jmpIf(target.size() - start)); + } + + public LazyOrNode(Location loc, Node first, Node second) { + super(loc); + this.first = first; + this.second = second; + } + + + public static ParseRes parse(Source src, int i, Node prev, int precedence) { + if (precedence < 3) return ParseRes.failed(); + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "||")) return ParseRes.failed(); + var loc = src.loc(i + n); + n += 2; + + var res = JavaScript.parseExpression(src, i + n, 4); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator."); + n += res.n; + + return ParseRes.res(new LazyOrNode(loc, prev, res.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java new file mode 100644 index 0000000..57039c6 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -0,0 +1,226 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.AssignableNode; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + +public class OperationNode extends Node { + private static interface OperatorFactory { + String token(); + int precedence(); + ParseRes construct(Source src, int i, Node prev); + } + + private static class NormalOperatorFactory implements OperatorFactory { + public final String token; + public final int precedence; + public final Operation operation; + + @Override public int precedence() { return precedence; } + @Override public String token() { return token; } + @Override public ParseRes construct(Source src, int i, Node prev) { + var loc = src.loc(i); + + var other = JavaScript.parseExpression(src, i, precedence + 1); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); + return ParseRes.res(new OperationNode(loc, operation, prev, (Node)other.result), other.n); + } + + public NormalOperatorFactory(String token, int precedence, Operation operation) { + this.token = token; + this.precedence = precedence; + this.operation = operation; + } + } + private static class AssignmentOperatorFactory implements OperatorFactory { + public final String token; + public final int precedence; + public final Operation operation; + + @Override public int precedence() { return precedence; } + @Override public String token() { return token; } + @Override public ParseRes construct(Source src, int i, Node prev) { + var loc = src.loc(i); + + if (!(prev instanceof AssignableNode)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); + + var other = JavaScript.parseExpression(src, i, precedence); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); + return ParseRes.res(((AssignableNode)prev).toAssign(other.result, operation), other.n); + } + + public AssignmentOperatorFactory(String token, int precedence, Operation operation) { + this.token = token; + this.precedence = precedence; + this.operation = operation; + } + } + private static class LazyAndFactory implements OperatorFactory { + @Override public int precedence() { return 4; } + @Override public String token() { return "&&"; } + @Override public ParseRes construct(Source src, int i, Node prev) { + var loc = src.loc(i); + + var other = JavaScript.parseExpression(src, i, 5); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '&&'"); + return ParseRes.res(new LazyAndNode(loc, prev, (Node)other.result), other.n); + } + } + private static class LazyOrFactory implements OperatorFactory { + @Override public int precedence() { return 5; } + @Override public String token() { return "||"; } + @Override public ParseRes construct(Source src, int i, Node prev) { + var loc = src.loc(i); + + var other = JavaScript.parseExpression(src, i, 6); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '||'"); + return ParseRes.res(new LazyOrNode(loc, prev, (Node)other.result), other.n); + } + } + + public final Node[] args; + public final Operation operation; + + @Override public void compile(CompileResult target, boolean pollute) { + for (var arg : args) { + arg.compile(target, true); + } + + if (pollute) target.add(Instruction.operation(operation)); + else target.add(Instruction.discard()); + } + + public OperationNode(Location loc, Operation operation, Node ...args) { + super(loc); + this.operation = operation; + this.args = args; + } + + private static final Map factories = Set.of( + new NormalOperatorFactory("*", 13, Operation.MULTIPLY), + new NormalOperatorFactory("/", 12, Operation.DIVIDE), + new NormalOperatorFactory("%", 12, Operation.MODULO), + new NormalOperatorFactory("-", 11, Operation.SUBTRACT), + new NormalOperatorFactory("+", 11, Operation.ADD), + new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT), + new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT), + new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT), + new NormalOperatorFactory(">", 9, Operation.GREATER), + new NormalOperatorFactory("<", 9, Operation.LESS), + new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS), + new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS), + new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS), + new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS), + new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS), + new NormalOperatorFactory("===", 8, Operation.EQUALS), + new NormalOperatorFactory("&", 7, Operation.AND), + new NormalOperatorFactory("^", 6, Operation.XOR), + new NormalOperatorFactory("|", 5, Operation.OR), + + new AssignmentOperatorFactory("=", 2, null), + new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY), + new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE), + new AssignmentOperatorFactory("%=", 2, Operation.MODULO), + new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT), + new AssignmentOperatorFactory("+=", 2, Operation.ADD), + new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT), + new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT), + new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT), + new AssignmentOperatorFactory("&=", 2, Operation.AND), + new AssignmentOperatorFactory("^=", 2, Operation.XOR), + new AssignmentOperatorFactory("|=", 2, Operation.OR), + + new LazyAndFactory(), + new LazyOrFactory() + ).stream().collect(Collectors.toMap(v -> v.token(), v -> v)); + + private static final List operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList()); + + public static ParseRes parsePrefix(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + Operation operation = null; + String op; + + if (src.is(i + n, op = "+")) operation = Operation.POS; + else if (src.is(i + n, op = "-")) operation = Operation.NEG; + else if (src.is(i + n, op = "~")) operation = Operation.INVERSE; + else if (src.is(i + n, op = "!")) operation = Operation.NOT; + else return ParseRes.failed(); + + n++; + + var res = JavaScript.parseExpression(src, i + n, 14); + + if (res.isSuccess()) return ParseRes.res(new OperationNode(loc, operation, res.result), n + res.n); + else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op)); + } + public static ParseRes parseInstanceof(Source src, int i, Node prev, int precedence) { + if (precedence > 9) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var kw = Parsing.parseIdentifier(src, i + n, "instanceof"); + if (!kw.isSuccess()) return kw.chainError(); + n += kw.n; + + var valRes = JavaScript.parseExpression(src, i + n, 10); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'."); + n += valRes.n; + + return ParseRes.res(new OperationNode(loc, Operation.INSTANCEOF, prev, valRes.result), n); + } + public static ParseRes parseIn(Source src, int i, Node prev, int precedence) { + if (precedence > 9) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var kw = Parsing.parseIdentifier(src, i + n, "in"); + if (!kw.isSuccess()) return kw.chainError(); + n += kw.n; + + var valRes = JavaScript.parseExpression(src, i + n, 10); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'."); + n += valRes.n; + + return ParseRes.res(new OperationNode(loc, Operation.IN, valRes.result, prev), n); + } + public static ParseRes parseOperator(Source src, int i, Node prev, int precedence) { + var n = Parsing.skipEmpty(src, i); + + for (var token : operatorsByLength) { + var factory = factories.get(token); + + if (!src.is(i + n, token)) continue; + if (factory.precedence() < precedence) ParseRes.failed(); + + n += token.length(); + n += Parsing.skipEmpty(src, i + n); + + var res = factory.construct(src, i + n, prev); + return res.addN(n); + // var res = Parsing.parseValue(src, i + n, prec + 1); + // if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", token)); + // n += res.n; + + // return ParseRes.res(new OperationStatement(loc, factories.get(token), prev, res.result), n); + } + + return ParseRes.failed(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java new file mode 100644 index 0000000..4d3b07d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java @@ -0,0 +1,48 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; + +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class TypeofNode extends Node { + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (value instanceof VariableNode varNode) { + target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name))); + if (!pollute) target.add(Instruction.discard()); + + return; + } + + value.compile(target, pollute); + target.add(Instruction.typeof()); + if (!pollute) target.add(Instruction.discard()); + } + + public TypeofNode(Location loc, Node value) { + super(loc); + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed(); + n += 6; + + var valRes = JavaScript.parseExpression(src, i + n, 15); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword."); + n += valRes.n; + + return ParseRes.res(new TypeofNode(loc, valRes.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java new file mode 100644 index 0000000..940a27c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java @@ -0,0 +1,35 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.FunctionNode; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class VariableAssignNode extends Node { + public final String name; + public final Node value; + public final Operation operation; + + @Override public void compile(CompileResult target, boolean pollute) { + if (operation != null) { + target.add(VariableNode.toGet(target, loc(), name)); + FunctionNode.compileWithName(value, target, true, name); + target.add(Instruction.operation(operation)); + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + } + else { + FunctionNode.compileWithName(value, target, true, name); + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + } + } + + public VariableAssignNode(Location loc, String name, Node val, Operation operation) { + super(loc); + this.name = name; + this.value = val; + this.operation = operation; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java b/src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java deleted file mode 100644 index cfe9708..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java +++ /dev/null @@ -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.ctx, () -> new Iterator() { - 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.ctx, () -> new Iterator() { - 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.ctx, i, args.self(ArrayValue.class).get(i++)); - } - }); - } - - @Expose(value = "@@Symbol.iterator") - public static ObjectValue __iterator(Arguments args) { - return Values.toJSIterator(args.ctx, args.self(ArrayValue.class)); - } - @Expose(value = "@@Symbol.asyncIterator") - public static ObjectValue __asyncIterator(Arguments args) { - return Values.toJSAsyncIterator(args.ctx, 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.ctx, 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.ctx, (cmp == null ? defaultCmp : cmp).call(args.ctx, 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.ctx, 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.ctx, 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.ctx, 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.ctx, args.get(0), args.get(1), - arr.get(i), i, arr - ))) res.set(args.ctx, 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.ctx, i, Values.call(args.ctx, 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.ctx, 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.ctx, 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.ctx, 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(); - var depths = new Stack(); - - 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.ctx, res.size(), el); - } - - return res; - } - @Expose public static ArrayValue __flatMap(Arguments args) { - return __flat(new Arguments(args.ctx, __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.ctx, 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.ctx, 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.ctx, 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.ctx, 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.ctx, 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.ctx, 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.ctx, 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.ctx, 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.ctx, items, 0, start, items.length); - arr.setSize(size); - - return res; - } - @Expose public static String __toString(Arguments args) { - return __join(new Arguments(args.ctx, 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.ctx, 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.ctx, 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.ctx, val, 0, 0, val.length); - } - - return res; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java deleted file mode 100644 index 2151164..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ /dev/null @@ -1,87 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import me.topchetoeu.jscript.lib.PromiseLib.Handle; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Frame; -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(Context ctx, Object inducedValue, EngineException inducedError) { - Object res = null; - - frame.onPush(); - awaiting = false; - while (!awaiting) { - try { - res = frame.next(inducedValue, Values.NO_RETURN, inducedError); - inducedValue = Values.NO_RETURN; - inducedError = null; - - if (res != Values.NO_RETURN) { - promise.fulfill(ctx, res); - break; - } - } - catch (EngineException e) { - promise.reject(ctx, e); - break; - } - } - frame.onPop(); - - if (awaiting) { - PromiseLib.handle(ctx, frame.pop(), new Handle() { - @Override - public void onFulfil(Object val) { - next(ctx, val, null); - } - @Override - public void onReject(EngineException err) { - next(ctx, Values.NO_RETURN, err); - } - }.defer(ctx)); - } - } - - public Object await(Arguments args) { - this.awaiting = true; - return args.get(0); - } - } - - @Override - public Object call(Extensions ext, Object thisArg, Object ...args) { - var handler = new AsyncHelper(); - var ctx = Context.of(ext); - - 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(ctx, thisArg, newArgs, (CodeFunction)func); - handler.next(ctx, 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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java deleted file mode 100644 index 43e7689..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Frame; -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(Extensions 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(Context.of(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 js function."); - this.func = func; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java deleted file mode 100644 index 32778ac..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ /dev/null @@ -1,107 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import java.util.Map; - -import me.topchetoeu.jscript.lib.PromiseLib.Handle; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Frame; -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(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { - if (done) { - if (inducedError != null) throw inducedError; - currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of( - "done", true, - "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn - ))); - return; - } - - Object res = null; - state = 0; - - frame.onPush(); - while (state == 0) { - try { - res = frame.next(inducedValue, inducedReturn, inducedError); - inducedValue = inducedReturn = Values.NO_RETURN; - inducedError = null; - - if (res != Values.NO_RETURN) { - var obj = new ObjectValue(); - obj.defineProperty(ctx, "done", true); - obj.defineProperty(ctx, "value", res); - currPromise.fulfill(ctx, obj); - break; - } - } - catch (EngineException e) { - currPromise.reject(ctx, e); - break; - } - } - frame.onPop(); - - if (state == 1) { - PromiseLib.handle(ctx, frame.pop(), new Handle() { - @Override public void onFulfil(Object val) { - next(ctx, val, Values.NO_RETURN, null); - } - @Override public void onReject(EngineException err) { - next(ctx, Values.NO_RETURN, Values.NO_RETURN, err); - } - }.defer(ctx)); - } - else if (state == 2) { - var obj = new ObjectValue(); - obj.defineProperty(ctx, "done", false); - obj.defineProperty(ctx, "value", frame.pop()); - currPromise.fulfill(ctx, 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.ctx, args.get(0), Values.NO_RETURN, null); - else next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null); - return this.currPromise; - } - @Expose public PromiseLib __return(Arguments args) { - this.currPromise = new PromiseLib(); - next(args.ctx, Values.NO_RETURN, args.get(0), null); - return this.currPromise; - } - @Expose public PromiseLib __throw(Arguments args) { - this.currPromise = new PromiseLib(); - next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setExtensions(args.ctx)); - return this.currPromise; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java b/src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java deleted file mode 100644 index 019a061..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java b/src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java deleted file mode 100644 index 2a7c205..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java +++ /dev/null @@ -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.ctx, el).getBytes()); - } - - for (var line : res.toString().split("\n", -1)) { - file.write(line.getBytes()); - } - } - - public ConsoleLib(File file) { - this.file = file; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/DateLib.java b/src/main/java/me/topchetoeu/jscript/lib/DateLib.java deleted file mode 100644 index b4b5082..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/DateLib.java +++ /dev/null @@ -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(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java b/src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java deleted file mode 100644 index 04ec153..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java +++ /dev/null @@ -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.ctx, 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), ",/?:@&=+$#."); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java deleted file mode 100644 index abda9c3..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import me.topchetoeu.jscript.runtime.Context; -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(Context 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.ctx, - Values.getMember(args.ctx, args.self, "name"), - Values.getMember(args.ctx, 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.ctx, "message", Values.toString(args.ctx, message)); - - return target; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/FileLib.java b/src/main/java/me/topchetoeu/jscript/lib/FileLib.java deleted file mode 100644 index 95b7baa..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/FileLib.java +++ /dev/null @@ -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.ctx, () -> { - try { - return fd.seek(0, 1); - } - catch (FilesystemException e) { throw e.toEngineException(); } - }); - } - @Expose public PromiseLib __length(Arguments args) { - return PromiseLib.await(args.ctx, () -> { - 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.ctx, () -> { - 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.ctx, i, (int)buff[i]); - return res; - } - catch (FilesystemException e) { throw e.toEngineException(); } - }); - } - @Expose public PromiseLib __write(Arguments args) { - return PromiseLib.await(args.ctx, () -> { - 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.ctx, val.get(i)); - fd.write(res); - - return null; - } - catch (FilesystemException e) { throw e.toEngineException(); } - }); - } - @Expose public PromiseLib __close(Arguments args) { - return PromiseLib.await(args.ctx, () -> { - fd.close(); - return null; - }); - } - @Expose public PromiseLib __seek(Arguments args) { - return PromiseLib.await(args.ctx, () -> { - 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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java deleted file mode 100644 index 6582495..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java +++ /dev/null @@ -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.Context; -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(Context ctx) { - var fs = Filesystem.get(ctx); - 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.ctx).normalize(args.convert(String.class)); - } - - @Expose(target = ExposeTarget.STATIC) - public static PromiseLib __open(Arguments args) { - return PromiseLib.await(args.ctx, () -> { - var fs = fs(args.ctx); - 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.ctx, 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.ctx); - 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.ctx, () -> { - try { - fs(args.ctx).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.ctx, () -> { - try { - fs(args.ctx).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.ctx, () -> { - try { - var fs = fs(args.ctx); - var path = fs.normalize(args.getString(0)); - var recursive = args.getBoolean(1); - - if (!recursive) fs.create(path, EntryType.NONE); - else { - var stack = new Stack(); - 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.ctx, () -> { - try { - var fs = fs(args.ctx); - var path = fs.normalize(args.getString(0)); - var stat = fs.stat(path); - var res = new ObjectValue(); - - res.defineProperty(args.ctx, "type", stat.type.name); - res.defineProperty(args.ctx, "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.ctx, () -> { - try { fs(args.ctx).stat(args.getString(0)); return true; } - catch (FilesystemException e) { return false; } - }); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java deleted file mode 100644 index e50a927..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java +++ /dev/null @@ -1,83 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.runtime.Compiler; -import me.topchetoeu.jscript.runtime.Context; -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.ctx, args.get(0), args.convert(1, ArrayValue.class).toArray()); - } - @Expose public static Object __call(Arguments args) { - return args.self(FunctionValue.class).call(args.ctx, 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.ctx, 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 compiler = Compiler.get(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.compile(new Filename("jscript", "func/" + i++), src); - var func = new CodeFunction(Context.clean(args.ctx), "", body, new ValueVariable[0]); - return Values.call(args, func, null); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java deleted file mode 100644 index 7075dae..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Frame; -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(Extensions ext, 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(Context.of(ext), 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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java deleted file mode 100644 index 9849c7d..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java +++ /dev/null @@ -1,78 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Frame; -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(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { - if (done) { - if (inducedError != Values.NO_RETURN) throw inducedError; - var res = new ObjectValue(); - res.defineProperty(ctx, "done", true); - res.defineProperty(ctx, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn); - return res; - } - - Object res = null; - yielding = false; - - frame.onPush(); - while (!yielding) { - try { - res = frame.next(inducedValue, inducedReturn, inducedError); - 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(ctx, "done", done); - obj.defineProperty(ctx, "value", res); - return obj; - } - - @Expose public ObjectValue __next(Arguments args) { - if (args.n() == 0) return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null); - else return next(args.ctx, args.get(0), Values.NO_RETURN, null); - } - @Expose public ObjectValue __throw(Arguments args) { - return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setExtensions(args.ctx)); - } - @Expose public ObjectValue __return(Arguments args) { - return next(args.ctx, 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); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/Internals.java b/src/main/java/me/topchetoeu/jscript/lib/Internals.java deleted file mode 100644 index 67564c6..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/Internals.java +++ /dev/null @@ -1,222 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import java.util.HashMap; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Environment; -import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.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> THREADS = new Key<>(); - private static final Key I = new Key<>(); - - @Expose(target = ExposeTarget.STATIC) - public static Object __require(Arguments args) { - var repo = ModuleRepo.get(args.ctx); - - if (repo != null) { - var res = repo.getModule(args.ctx, ModuleRepo.cwd(args.ctx), args.getString(0)); - res.load(args.ctx); - 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.ctx.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.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.extensions), null, arguments), false); - }); - - thread.start(); - var i = args.ctx.init(I, 1); - args.ctx.add(I, i + 1); - args.ctx.init(THREADS, new HashMap()).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.ctx.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.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.extensions), null, arguments), false); - } - }); - thread.start(); - var i = args.ctx.init(I, 1); - args.ctx.add(I, i + 1); - args.ctx.init(THREADS, new HashMap()).put(i, thread); - - return thread; - } - - @Expose(target = ExposeTarget.STATIC) - public static void __clearTimeout(Arguments args) { - var i = args.getInt(0); - HashMap map = args.ctx.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.ctx).open("std://in", Mode.READ)); - } - @Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER) - public static FileLib __stdout(Arguments args) { - return new FileLib(Filesystem.get(args.ctx).open("std://out", Mode.READ)); - } - @Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER) - public static FileLib __stderr(Arguments args) { - return new FileLib(Filesystem.get(args.ctx).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(new Context(), wp.getProto(ObjectLib.class), null); - - env.add(NativeWrapperProvider.KEY, wp); - env.add(GlobalScope.KEY, glob); - - - return env; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/JSONLib.java b/src/main/java/me/topchetoeu/jscript/lib/JSONLib.java deleted file mode 100644 index 31a1e11..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/JSONLib.java +++ /dev/null @@ -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.ctx, args.get(0))); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/MapLib.java b/src/main/java/me/topchetoeu/jscript/lib/MapLib.java deleted file mode 100644 index 849bf00..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/MapLib.java +++ /dev/null @@ -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.Context; -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 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.ctx, map - .entrySet() - .stream() - .map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue())) - .collect(Collectors.toList()) - ); - } - @Expose public ObjectValue __keys(Arguments args) { - return Values.toJSIterator(args.ctx, map.keySet()); - } - @Expose public ObjectValue __values(Arguments args) { - return Values.toJSIterator(args.ctx, 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.ctx, args.get(0), args.get(1), map.get(el), el, args.self); - } - - public MapLib(Context ctx, Object iterable) { - for (var el : Values.fromJSIterator(ctx, iterable)) { - try { - map.put(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); - } - catch (IllegalArgumentException e) { } - } - } - - @ExposeConstructor public static MapLib __constructor(Arguments args) { - return new MapLib(args.ctx, args.get(0)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/MathLib.java b/src/main/java/me/topchetoeu/jscript/lib/MathLib.java deleted file mode 100644 index a0b5d53..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/MathLib.java +++ /dev/null @@ -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)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/NumberLib.java b/src/main/java/me/topchetoeu/jscript/lib/NumberLib.java deleted file mode 100644 index 07eb00b..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/NumberLib.java +++ /dev/null @@ -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.ctx, 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.ctx, args.self); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java b/src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java deleted file mode 100644 index c9b2964..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java +++ /dev/null @@ -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.ctx, obj, true, true)) { - Values.setMember(args.ctx, args.get(0), key, Values.getMember(args.ctx, obj, key)); - } - } - return args.get(0); - } - @Expose(target = ExposeTarget.STATIC) - public static ObjectValue __create(Arguments args) { - var obj = new ObjectValue(); - Values.setPrototype(args.ctx, 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.ctx, 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.ctx, attrib, "value", false); - var hasGet = Values.hasMember(args.ctx, attrib, "get", false); - var hasSet = Values.hasMember(args.ctx, attrib, "set", false); - - if (hasVal) { - if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); - if (!obj.defineProperty( - args.ctx, key, - Values.getMember(args.ctx, attrib, "value"), - Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")), - Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), - Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable")) - )) throw EngineException.ofType("Can't define property '" + key + "'."); - } - else { - var get = Values.getMember(args.ctx, attrib, "get"); - var set = Values.getMember(args.ctx, 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.ctx, key, - (FunctionValue)get, (FunctionValue)set, - Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), - Values.toBoolean(Values.getMember(args.ctx, 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.ctx, null, obj, key, Values.getMember(args.ctx, 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.ctx, obj, true, false)) { - if (all || !(key instanceof Symbol)) res.set(args.ctx, 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.ctx, obj, true, false)) { - if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), new ArrayValue(args.ctx, key, Values.getMember(args.ctx, 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.ctx, obj, true, false)) { - if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key)); - } - - return res; - } - - @Expose(target = ExposeTarget.STATIC) - public static ObjectValue __getOwnPropertyDescriptor(Arguments args) { - return Values.getMemberDescriptor(args.ctx, 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.ctx, obj, true, true)) { - res.defineProperty(args.ctx, key, Values.getMemberDescriptor(args.ctx, 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.ctx, obj, true, true)) { - if (all || !(key instanceof Symbol)) res.set(args.ctx, 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.ctx, obj, true, true)) { - if (key instanceof Symbol) res.set(args.ctx, res.size(), key); - } - - return res; - } - @Expose(target = ExposeTarget.STATIC) - public static boolean __hasOwn(Arguments args) { - return Values.hasMember(args.ctx, args.get(0), args.get(1), true); - } - - @Expose(target = ExposeTarget.STATIC) - public static ObjectValue __getPrototypeOf(Arguments args) { - return Values.getPrototype(args.ctx, args.get(0)); - } - @Expose(target = ExposeTarget.STATIC) - public static Object __setPrototypeOf(Arguments args) { - Values.setPrototype(args.ctx, 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.ctx, args.get(0))) { - if (el instanceof ArrayValue) { - res.defineProperty(args.ctx, ((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.ctx, args.self, Symbol.get("Symbol.typeName")); - if (name == null) name = "Unknown"; - else name = Values.toString(args.ctx, name); - - return "[object " + name + "]"; - } - @Expose - public static boolean __hasOwnProperty(Arguments args) { - return Values.hasMember(args.ctx, 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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java b/src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java deleted file mode 100644 index 66829de..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java +++ /dev/null @@ -1,404 +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.Context; -import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.Extensions; -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(Extensions 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 handles = new ArrayList<>(); - - private int state = STATE_PENDING; - private boolean handled = false; - private Object val; - - private void resolveSynchronized(Context ctx, 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).setExtensions(ctx), "(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(Context ctx, Object val, int newState) { - if (this.state != STATE_PENDING || newState == STATE_PENDING) return; - - handle(ctx, val, new Handle() { - @Override public void onFulfil(Object val) { - resolveSynchronized(ctx, val, newState); - } - @Override public void onReject(EngineException err) { - resolveSynchronized(ctx, val, STATE_REJECTED); - } - }); - } - - public synchronized void fulfill(Context ctx, Object val) { - resolve(ctx, val, STATE_FULFILLED); - } - public synchronized void reject(Context ctx, EngineException val) { - resolve(ctx, 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(Context ctx, ResultRunnable runner) { - var res = new PromiseLib(); - - new Thread(() -> { - try { - res.fulfill(ctx, runner.run()); - } - catch (EngineException e) { - res.reject(ctx, e); - } - catch (Exception e) { - if (e instanceof InterruptException) throw e; - else { - res.reject(ctx, EngineException.ofError("Native code failed with " + e.getMessage())); - } - } - }, "Promisifier").start(); - - return res; - } - public static PromiseLib await(Context ctx, Runnable runner) { - return await(ctx, () -> { - runner.run(); - return null; - }); - } - - public static void handle(Context ctx, Object obj, Handle handle) { - if (Values.isWrapper(obj, PromiseLib.class)) { - var promise = Values.wrapper(obj, PromiseLib.class); - handle(ctx, promise, handle); - return; - } - if (obj instanceof PromiseLib) { - ((PromiseLib)obj).handle(handle); - return; - } - - var rethrow = new boolean[1]; - - try { - var then = Values.getMember(ctx, obj, "then"); - - if (then instanceof FunctionValue) Values.call(ctx, 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(Context ctx, Object value) { - var res = new PromiseLib(); - res.fulfill(ctx, value); - return res; - } - public static PromiseLib ofRejected(Context 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.ctx, args.get(0)); - } - @Expose(value = "reject", target = ExposeTarget.STATIC) - public static PromiseLib __ofRejected(Arguments args) { - return ofRejected(args.ctx, new EngineException(args.get(0)).setExtensions(args.ctx)); - } - - @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.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setExtensions(args.ctx)); - 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.ctx, val, new Handle() { - public void onFulfil(Object val) { res.fulfill(args.ctx, val); } - public void onReject(EngineException err) { - errors.set(args.ctx, index, err.value); - n[0]--; - if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setExtensions(args.ctx)); - } - }); - } - - 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.ctx, val, new Handle() { - @Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); } - @Override public void onReject(EngineException err) { res.reject(args.ctx, 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.ctx, val, new Handle() { - @Override public void onFulfil(Object val) { - result.set(args.ctx, index, val); - n[0]--; - if (n[0] <= 0) res.fulfill(args.ctx, result); - } - @Override public void onReject(EngineException err) { - res.reject(args.ctx, err); - } - }); - } - - if (n[0] <= 0) res.fulfill(args.ctx, 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.ctx, promises.get(i), new Handle() { - @Override public void onFulfil(Object val) { - var desc = new ObjectValue(); - desc.defineProperty(args.ctx, "status", "fulfilled"); - desc.defineProperty(args.ctx, "value", val); - - result.set(args.ctx, index, desc); - - n[0]--; - if (n[0] <= 0) res.fulfill(args.ctx, res); - } - @Override public void onReject(EngineException err) { - var desc = new ObjectValue(); - desc.defineProperty(args.ctx, "status", "reject"); - desc.defineProperty(args.ctx, "value", err.value); - - result.set(args.ctx, index, desc); - - n[0]--; - if (n[0] <= 0) res.fulfill(args.ctx, res); - } - }); - } - - if (n[0] <= 0) res.fulfill(args.ctx, 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.ctx, args.self, new Handle() { - @Override public void onFulfil(Object val) { - try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); } - catch (EngineException e) { res.reject(args.ctx, e); } - } - @Override public void onReject(EngineException err) { - try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); } - catch (EngineException e) { res.reject(args.ctx, e); } - } - }.defer(args.ctx)); - - return res; - } - @Expose - public static Object __catch(Arguments args) { - return __then(new Arguments(args.ctx, 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.ctx, args.self, new Handle() { - @Override public void onFulfil(Object val) { - try { - func.call(args.ctx); - res.fulfill(args.ctx, val); - } - catch (EngineException e) { res.reject(args.ctx, e); } - } - @Override public void onReject(EngineException err) { - try { - func.call(args.ctx); - res.reject(args.ctx, err); - } - catch (EngineException e) { res.reject(args.ctx, e); } - } - }.defer(args.ctx)); - - return res; - } - - @ExposeConstructor - public static PromiseLib __constructor(Arguments args) { - var func = args.convert(0, FunctionValue.class); - var res = new PromiseLib(); - - try { - func.call( - args.ctx, null, - new NativeFunction(null, _args -> { - res.fulfill(_args.ctx, _args.get(0)); - return null; - }), - new NativeFunction(null, _args -> { - res.reject(_args.ctx, new EngineException(_args.get(0)).setExtensions(_args.ctx)); - return null; - }) - ); - } - catch (EngineException e) { - res.reject(args.ctx, e); - } - - return res; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java deleted file mode 100644 index 78877ce..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java b/src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java deleted file mode 100644 index 47c8b92..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java +++ /dev/null @@ -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.Context; -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.ctx, res.size(), Values.getMember(args.ctx, 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.ctx, new Iterator() { - 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(); - var arrMatch = (ArrayValue)match; - int index = (int)Values.toNumber(args.ctx, Values.getMember(args.ctx, 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.ctx, 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.ctx, res.size(), added.get(i)); - } - } - lastEnd = pattern.lastI; - } - if (lastEnd < target.length()) { - res.set(args.ctx, 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.ctx, 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.ctx, ((FunctionValue)replacement).call(args.ctx, null, callArgs))); - } - else { - res.append(Values.toString(args.ctx, 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(); - - 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.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1))); - } - @Expose(target = ExposeTarget.STATIC) - public static RegExpLib __escape(Arguments args) { - return escape(Values.toString(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1))); - } - - private static String cleanupPattern(Context ctx, 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(ctx, val); - if (res.equals("")) return "(?:)"; - return res; - } - private static String cleanupFlags(Context ctx, Object val) { - if (val == null) return ""; - return Values.toString(ctx, 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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/SetLib.java b/src/main/java/me/topchetoeu/jscript/lib/SetLib.java deleted file mode 100644 index 273a27c..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/SetLib.java +++ /dev/null @@ -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.Context; -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 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.ctx, set.stream().map(v -> new ArrayValue(args.ctx, v, v)).collect(Collectors.toList())); - } - @Expose public ObjectValue __keys(Arguments args) { - return Values.toJSIterator(args.ctx, set); - } - @Expose public ObjectValue __values(Arguments args) { - return Values.toJSIterator(args.ctx, 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.ctx, args.get(0), args.get(1), el, el, args.self); - } - - public SetLib(Context ctx, Object iterable) { - for (var el : Values.fromJSIterator(ctx, iterable)) set.add(el); - } - - @ExposeConstructor - public static SetLib __constructor(Arguments args) { - return new SetLib(args.ctx, args.get(0)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/StringLib.java b/src/main/java/me/topchetoeu/jscript/lib/StringLib.java deleted file mode 100644 index 93317a3..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/StringLib.java +++ /dev/null @@ -1,291 +0,0 @@ -package me.topchetoeu.jscript.lib; - -import java.util.regex.Pattern; - -import me.topchetoeu.jscript.runtime.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.ctx, term, Symbol.get("Symbol.search")); - - if (search instanceof FunctionValue) { - return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, false, start)); - } - else return val.indexOf(Values.toString(args.ctx, 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.ctx, term, Symbol.get("Symbol.search")); - - if (search instanceof FunctionValue) { - return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, true, start)); - } - else return val.lastIndexOf(Values.toString(args.ctx, 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.ctx, term, Symbol.get("Symbol.replace")); - - if (replace instanceof FunctionValue) { - return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement)); - } - else return val.replaceFirst(Pattern.quote(Values.toString(args.ctx, term)), Values.toString(args.ctx, 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.ctx, term, Symbol.get("Symbol.replace")); - - if (replace instanceof FunctionValue) { - return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement)); - } - else return val.replace(Values.toString(args.ctx, term), Values.toString(args.ctx, 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.ctx, term, Symbol.get("Symbol.match")); - if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else if (args.ctx.hasNotNull(Environment.REGEX_CONSTR)) { - var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), ""); - _match = Values.getMember(args.ctx, 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.ctx, ""); } - - var res = match.call(args.ctx, term, val); - if (res instanceof ArrayValue) return (ArrayValue)res; - else return new ArrayValue(args.ctx, ""); - } - @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.ctx, term, Symbol.get("Symbol.matchAll")); - if (_match instanceof FunctionValue) match = (FunctionValue)_match; - } - catch (IllegalArgumentException e) { } - - if (match == null && args.ctx.hasNotNull(Environment.REGEX_CONSTR)) { - var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), "g"); - var _match = Values.getMember(args.ctx, 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.ctx, 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.ctx, lim); - - if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); - if (replace instanceof FunctionValue) { - var tmp = ((FunctionValue)replace).call(args.ctx, 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.ctx, i, Values.toString(args.ctx, ((ArrayValue)tmp).get(i))); - return parts; - } - } - } - - String[] parts; - var pattern = Pattern.quote(Values.toString(args.ctx, 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.ctx, 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.ctx, 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.ctx, 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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java b/src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java deleted file mode 100644 index 9de6a74..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java +++ /dev/null @@ -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 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.ctx, args.get(0)), "keyFor").value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java deleted file mode 100644 index 1557729..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java b/src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java deleted file mode 100644 index 960229f..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java deleted file mode 100644 index 98107ae..0000000 --- a/src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java b/src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java new file mode 100644 index 0000000..df5a7e3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.runtime; + +import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; + +public class ArgumentsValue extends ArrayValue { + public final Frame frame; + + public ArgumentsValue(Frame frame, Value... args) { + super(args); + this.frame = frame; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Childable.java b/src/main/java/me/topchetoeu/jscript/runtime/Childable.java deleted file mode 100644 index f1f358e..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/Childable.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -public interface Childable { - Object child(); -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java index d332da5..70edd70 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java @@ -1,23 +1,52 @@ package me.topchetoeu.jscript.runtime; -import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.FunctionBody; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.runtime.debug.DebugContext; 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.exceptions.SyntaxException; +import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; public interface Compiler { - public Key KEY = new Key<>(); + public static final Compiler DEFAULT = (env, filename, raw) -> { + try { + var res = JavaScript.compile(env, filename, raw); + var body = res.body(); + DebugContext.get(env).onSource(filename, raw); + registerFunc(env, body, res); + return body; + } + catch (SyntaxException e) { + throw EngineException.ofSyntax(e.loc + ": " + e.msg); + } + }; - public FunctionBody compile(Filename filename, String source); + public Key KEY = Key.of(); - public static Compiler get(Extensions ext) { - return ext.get(KEY, (filename, src) -> { + public FunctionBody compile(Environment env, Filename filename, String source); + + public static Compiler get(Environment ext) { + return ext.get(KEY, (env, filename, src) -> { throw EngineException.ofError("No compiler attached to engine."); }); } - public static CodeFunction compile(Environment env, Filename filename, String raw) { - return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]); + static void registerFunc(Environment env, FunctionBody body, CompileResult res) { + var map = res.map(); + + DebugContext.get(env).onFunctionLoad(body, map); + + for (var i = 0; i < body.children.length; i++) { + registerFunc(env, body.children[i], res.children.get(i)); + } + } + + public static CodeFunction compileFunc(Environment env, Filename filename, String raw) { + return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new Value[0][]); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Context.java b/src/main/java/me/topchetoeu/jscript/runtime/Context.java deleted file mode 100644 index e20a62b..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/Context.java +++ /dev/null @@ -1,98 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import java.util.Iterator; -import java.util.List; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -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.FunctionValue; - -public class Context implements Extensions { - public final Context parent; - public final Extensions extensions; - public final Frame frame; - public final int stackSize; - - @Override public void add(Key key, T obj) { - if (extensions != null) extensions.add(key, obj); - } - @Override public T get(Key key) { - if (extensions != null && extensions.has(key)) return extensions.get(key); - return null; - } - @Override public boolean has(Key key) { - return extensions != null && extensions.has(key); - } - @Override public boolean remove(Key key) { - var res = false; - if (extensions != null) res |= extensions.remove(key); - return res; - } - @Override public Iterable> keys() { - if (extensions == null) return List.of(); - else return extensions.keys(); - } - - public FunctionValue compile(Filename filename, String raw) { - DebugContext.get(this).onSource(filename, raw); - var result = new CodeFunction(extensions, filename.toString(), Compiler.get(this).compile(filename, raw), new ValueVariable[0]); - return result; - } - - public Context pushFrame(Frame frame) { - var res = new Context(this, frame.function.extensions, frame, stackSize + 1); - return res; - } - - public Iterable frames() { - var self = this; - return () -> new Iterator() { - private Context curr = self; - - private void update() { - while (curr != null && curr.frame == null) curr = curr.parent; - } - - @Override public boolean hasNext() { - update(); - return curr != null; - } - @Override public Frame next() { - update(); - var res = curr.frame; - curr = curr.parent; - return res; - } - }; - } - - private Context(Context parent, Extensions ext, Frame frame, int stackSize) { - this.parent = parent; - this.extensions = ext; - this.frame = frame; - this.stackSize = stackSize; - - if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) { - throw EngineException.ofRange("Stack overflow!"); - } - } - - public Context() { - this(null, null, null, 0); - } - public Context(Extensions ext) { - this(null, clean(ext), null, 0); - } - - public static Context of(Extensions ext) { - if (ext instanceof Context) return (Context)ext; - return new Context(ext); - } - public static Extensions clean(Extensions ext) { - if (ext instanceof Context) return clean(((Context)ext).extensions); - else return ext; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Copyable.java b/src/main/java/me/topchetoeu/jscript/runtime/Copyable.java deleted file mode 100644 index cb2e75e..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/Copyable.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -public interface Copyable { - Object copy(); -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Engine.java b/src/main/java/me/topchetoeu/jscript/runtime/Engine.java index 9e2d6d1..e3cb05e 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Engine.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Engine.java @@ -1,24 +1,24 @@ package me.topchetoeu.jscript.runtime; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import java.util.concurrent.PriorityBlockingQueue; +import java.util.function.Supplier; -import me.topchetoeu.jscript.common.ResultRunnable; -import me.topchetoeu.jscript.common.events.DataNotifier; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -public class Engine implements EventLoop { +public final class Engine implements EventLoop { private static class Task implements Comparable> { - public final ResultRunnable runnable; - public final DataNotifier notifier = new DataNotifier<>(); + public final Supplier runnable; + public final CompletableFuture notifier = new CompletableFuture(); public final boolean micro; - public Task(ResultRunnable runnable, boolean micro) { + public Task(Supplier runnable, boolean micro) { this.runnable = runnable; this.micro = micro; } - @Override - public int compareTo(Task other) { + @Override public int compareTo(Task other) { return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1); } } @@ -26,8 +26,7 @@ public class Engine implements EventLoop { private PriorityBlockingQueue> tasks = new PriorityBlockingQueue<>(); private Thread thread; - @Override - public DataNotifier pushMsg(ResultRunnable runnable, boolean micro) { + @Override public Future pushMsg(Supplier runnable, boolean micro) { var msg = new Task(runnable, micro); tasks.add(msg); return msg.notifier; @@ -40,15 +39,15 @@ public class Engine implements EventLoop { var task = tasks.take(); try { - ((Task)task).notifier.next(task.runnable.run()); + ((Task)task).notifier.complete(task.runnable.get()); } catch (RuntimeException e) { if (e instanceof InterruptException) throw e; - task.notifier.error(e); + task.notifier.completeExceptionally(e); } } catch (InterruptedException | InterruptException e) { - for (var msg : tasks) msg.notifier.error(new InterruptException(e)); + for (var msg : tasks) msg.notifier.cancel(false); break; } } @@ -75,7 +74,4 @@ public class Engine implements EventLoop { public boolean isRunning() { return this.thread != null; } - - public Engine() { - } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Environment.java b/src/main/java/me/topchetoeu/jscript/runtime/Environment.java deleted file mode 100644 index 4e232d8..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/Environment.java +++ /dev/null @@ -1,61 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import java.util.HashMap; - -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; - -@SuppressWarnings("unchecked") -public class Environment implements Extensions { - public static final Key COMPILE_FUNC = new Key<>(); - - public static final Key REGEX_CONSTR = new Key<>(); - public static final Key MAX_STACK_COUNT = new Key<>(); - public static final Key HIDE_STACK = new Key<>(); - - public static final Key OBJECT_PROTO = new Key<>(); - public static final Key FUNCTION_PROTO = new Key<>(); - public static final Key ARRAY_PROTO = new Key<>(); - public static final Key BOOL_PROTO = new Key<>(); - public static final Key NUMBER_PROTO = new Key<>(); - public static final Key STRING_PROTO = new Key<>(); - public static final Key SYMBOL_PROTO = new Key<>(); - public static final Key ERROR_PROTO = new Key<>(); - public static final Key SYNTAX_ERR_PROTO = new Key<>(); - public static final Key TYPE_ERR_PROTO = new Key<>(); - public static final Key RANGE_ERR_PROTO = new Key<>(); - - private HashMap, Object> data = new HashMap<>(); - - @Override public void add(Key key, T obj) { - data.put(key, obj); - } - @Override public T get(Key key) { - return (T)data.get(key); - } - @Override public boolean remove(Key key) { - if (data.containsKey(key)) { - data.remove(key); - return true; - } - return false; - } - @Override public boolean has(Key key) { - return data.containsKey(key); - } - @Override public Iterable> keys() { - return data.keySet(); - } - - public static FunctionValue regexConstructor(Extensions ext) { - return ext.init(REGEX_CONSTR, new NativeFunction("RegExp", args -> { - throw EngineException.ofError("Regular expressions not supported.").setExtensions(args.ctx); - })); - } - - public Context context() { - return new Context(this); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java index 6046971..2c548ba 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java @@ -1,37 +1,36 @@ package me.topchetoeu.jscript.runtime; -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.common.ResultRunnable; -import me.topchetoeu.jscript.common.events.DataNotifier; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.common.parsing.Filename; 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 KEY = new Key<>(); + public static final Key KEY = Key.of(); - public static EventLoop get(Extensions ext) { + public static EventLoop get(Environment ext) { if (ext.hasNotNull(KEY)) return ext.get(KEY); else return new EventLoop() { - @Override public DataNotifier pushMsg(ResultRunnable runnable, boolean micro) { + @Override public Future pushMsg(Supplier runnable, boolean micro) { throw EngineException.ofError("No event loop attached to environment."); } }; } - public DataNotifier pushMsg(ResultRunnable runnable, boolean micro); - public default DataNotifier pushMsg(Runnable runnable, boolean micro) { + public Future pushMsg(Supplier runnable, boolean micro); + public default Future pushMsg(Runnable runnable, boolean micro) { return pushMsg(() -> { runnable.run(); return null; }, micro); } - public default DataNotifier pushMsg(boolean micro, Extensions ext, FunctionValue func, Object thisArg, Object ...args) { - return pushMsg(() -> { - return func.call(Context.of(ext), thisArg, args); - }, micro); + public default Future pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { + return pushMsg(() -> func.call(env, thisArg, args), micro); } - public default DataNotifier pushMsg(boolean micro, Extensions ext, Filename filename, String raw, Object thisArg, Object ...args) { - return pushMsg(() -> { - var ctx = Context.of(ext); - return ctx.compile(filename, raw).call(Context.of(ext), thisArg, args); - }, micro); + public default Future pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) { + return pushMsg(() -> Compiler.compileFunc(env, filename, raw).call(env, thisArg, args), micro); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Extensions.java b/src/main/java/me/topchetoeu/jscript/runtime/Extensions.java deleted file mode 100644 index 3410e3e..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/Extensions.java +++ /dev/null @@ -1,77 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import java.util.List; - -public interface Extensions extends Childable, Copyable { - public static Extensions EMPTY = new Extensions() { - @Override public void add(Key key, T obj) { } - @Override public boolean remove(Key key) { return false; } - - @Override public T get(Key key) { return null; } - @Override public boolean has(Key key) { return false; } - @Override public Iterable> keys() { return List.of(); } - }; - - T get(Key key); - void add(Key key, T obj); - Iterable> keys(); - - boolean has(Key key); - boolean remove(Key key); - - default void add(Key key) { - add(key, null); - } - - default boolean hasNotNull(Key key) { - return has(key) && get(key) != null; - } - - default T get(Key key, T defaultVal) { - if (has(key)) return get(key); - else return defaultVal; - } - - default T init(Key key, T val) { - if (has(key)) return get(key); - else { - add(key, val); - return val; - } - } - @SuppressWarnings("unchecked") - default void addAll(Extensions source) { - if (source == null) return; - for (var key : source.keys()) { - add((Key)key, (Object)source.get(key)); - } - } - - @Override - @SuppressWarnings("unchecked") - default Extensions copy() { - var res = new Environment(); - for (var key : keys()) { - var val = get(key); - if (val instanceof Copyable) val = ((Copyable)val).copy(); - res.add((Key)key, val); - } - return res; - } - @Override - @SuppressWarnings("unchecked") - default Extensions child() { - var res = new Environment(); - for (var key : keys()) { - var val = get(key); - if (val instanceof Childable) val = ((Childable)val).child(); - res.add((Key)key, val); - } - return res; - } - - public static Extensions wrap(Extensions ext) { - if (ext == null) return EMPTY; - else return ext; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index 7cdb025..31f9434 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,21 +1,28 @@ package me.topchetoeu.jscript.runtime; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Stack; import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.runtime.debug.DebugContext; 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.KeyCache; +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.ObjectValue; +import me.topchetoeu.jscript.runtime.values.objects.ScopeValue; + +public final class Frame { + public static final Key KEY = Key.of(); -public class Frame { public static enum TryState { TRY, CATCH, @@ -60,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; @@ -78,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) { @@ -89,16 +96,31 @@ public class Frame { } } - public final LocalScope scope; - public final Object thisArg; - public final Object[] args; + /** + * A list of one-element arrays of values. This is so that we can pass captures to other functions + */ + public final Value[][] captures; + public final List locals = new ArrayList<>(); + public final Value argsVal; + public Value self; + public Value fakeArgs; + public final Value[] args; + public final boolean isNew; public final Stack tryStack = new Stack<>(); public final CodeFunction function; - public final Context ctx; - public Object[] stack = new Object[32]; + public final Environment env; + private final DebugContext dbg; + + public Value[] getVar(int i) { + if (i < 0) return captures[~i]; + else return locals.get(i); + } + + public Value[] stack = new Value[32]; public int stackPtr = 0; public int codePtr = 0; - public boolean jumpFlag = false, popTryFlag = false; + public boolean jumpFlag = false; + public boolean popTryFlag = false; public void addTry(int start, int end, int catchStart, int finallyStart) { var err = tryStack.empty() ? null : tryStack.peek().error; @@ -107,63 +129,70 @@ public class Frame { tryStack.add(res); } - public Object peek() { + public Value peek() { return peek(0); } - public Object peek(int offset) { - if (stackPtr <= offset) return null; - else return stack[stackPtr - 1 - offset]; + public Value peek(int offset) { + return stack[stackPtr - 1 - offset]; } - public Object pop() { - if (stackPtr == 0) return null; + public Value pop() { return stack[--stackPtr]; } - public Object[] 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]; - System.arraycopy(stack, srcI, res, dstI, copyN); - stackPtr -= copyN; + public Value[] take(int n) { + Value[] res = new Value[n]; + System.arraycopy(stack, stackPtr - n, res, 0, n); + stackPtr -= n; 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(ctx, val); + + stack[stackPtr++] = val; + } + public void replace(Value val) { + stack[stackPtr - 1] = val; } - public Object next(Object value, Object returnValue, EngineException error) { - if (value != Values.NO_RETURN) push(value); + // for the love of christ don't touch this + /** + * This is provided only for optimization-sike. All parameters must be null except at most one, otherwise undefined behavior + */ + public final 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 (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.currentThread().isInterrupted()) throw new InterruptException(); + if (Thread.interrupted()) throw new InterruptException(); - if (instr == null) returnValue = null; + if (instr == null) { + if (stackPtr > 0) returnValue = stack[stackPtr - 1]; + else returnValue = Value.UNDEFINED; + } else { - DebugContext.get(ctx).onInstruction(ctx, this, instr, Values.NO_RETURN, null, false); + dbg.onInstruction(env, this, instr); try { this.jumpFlag = this.popTryFlag = false; - returnValue = InstructionRunner.exec(ctx, instr, this); + returnValue = InstructionRunner.exec(env, instr, this); } catch (EngineException e) { - error = e.add(ctx, function.name, DebugContext.get(ctx).getMapOrEmpty(function).toLocation(codePtr, true)); + error = e.add(env, function.name, dbg.getMapOrEmpty(function).toLocation(codePtr, true)); } } } catch (EngineException e) { error = e; } + catch (RuntimeException e) { + System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true)); + throw e; + } } while (!tryStack.empty()) { @@ -175,7 +204,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)) { @@ -187,12 +216,12 @@ public class Frame { if (newCtx != tryCtx) { switch (newCtx.state) { case CATCH: - if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value)); + if (tryCtx.state != TryState.CATCH) locals.add(new Value[] { error.value }); codePtr = tryCtx.catchStart; stackPtr = tryCtx.restoreStackPtr; break; case FINALLY: - if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); codePtr = tryCtx.finallyStart; stackPtr = tryCtx.restoreStackPtr; default: @@ -201,13 +230,14 @@ public class Frame { tryStack.pop(); tryStack.push(newCtx); } + error = null; - returnValue = Values.NO_RETURN; + returnValue = null; break; } else { popTryFlag = false; - if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) { codePtr = tryCtx.finallyStart; @@ -220,7 +250,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; @@ -239,91 +269,162 @@ public class Frame { if (error != null) { var caught = false; - for (var frame : ctx.frames()) { + for (var frame : dbg.getStackFrames()) { for (var tryCtx : frame.tryStack) { if (tryCtx.state == TryState.TRY) caught = true; } } - DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught); + dbg.onInstruction(env, this, instr, null, error, caught); throw error; } - if (returnValue != Values.NO_RETURN) { - DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false); + if (returnValue != null) { + dbg.onInstruction(env, this, instr, returnValue, null, false); return returnValue; } - return Values.NO_RETURN; + return null; + } + + /** + * Executes the next instruction in the frame + */ + public final Value next() { + return next(null, null, null); + } + /** + * Induces a value on the stack (as if it were returned by the last function call) + * and executes the next instruction in the frame. + * + * @param value The value to induce + */ + public final Value next(Value value) { + return next(value, null, null); + } + /** + * Induces a thrown error and executes the next instruction. + * Note that this is different than just throwing the error outside the + * function, as the function executed could have a try-catch which + * would otherwise handle the error + * + * @param error The error to induce + */ + public final Value induceError(EngineException error) { + return next(null, null, error); + } + /** + * Induces a return, as if there was a return statement before + * the currently executed instruction and executes the next instruction. + * Note that this is different than just returning the value outside the + * function, as the function executed could have a try-catch which + * would otherwise handle the error + * + * @param value The retunr value to induce + */ + public final Value induceReturn(Value value) { + return next(null, value, null); } public void onPush() { - DebugContext.get(ctx).onFramePush(ctx, this); + DebugContext.get(env).onFramePush(env, this); } public void onPop() { - DebugContext.get(ctx.parent).onFramePop(ctx.parent, this); + DebugContext.get(env).onFramePop(env, this); } + /** + * Gets an object proxy of the local locals + */ public ObjectValue getLocalScope() { - var names = new String[scope.locals.length]; - var map = DebugContext.get(ctx).getMapOrEmpty(function); + throw new RuntimeException("Not supported"); - for (int i = 0; i < scope.locals.length; i++) { - var name = "local_" + (i - 2); + // var names = new String[locals.locals.length]; + // var map = DebugContext.get(env).getMapOrEmpty(function); - if (i == 0) name = "this"; - else if (i == 1) name = "arguments"; - else if (i < map.localNames.length) name = map.localNames[i]; + // for (int i = 0; i < locals.locals.length; i++) { + // var name = "local_" + (i - 2); - names[i] = name; - } + // if (i == 0) name = "this"; + // else if (i == 1) name = "arguments"; + // else if (i < map.localNames.length) name = map.localNames[i]; - return new ScopeValue(scope.locals, names); + // names[i] = name; + // } + + // return new ScopeValue(locals, names); } + /** + * Gets an object proxy of the capture locals + */ public ObjectValue getCaptureScope() { - var names = new String[scope.captures.length]; - var map = DebugContext.get(ctx).getMapOrEmpty(function); + // throw new RuntimeException("Not supported"); - for (int i = 0; i < scope.captures.length; i++) { + var names = new String[captures.length]; + var map = DebugContext.get(env).getMapOrEmpty(function); + + for (int i = 0; i < captures.length; i++) { var name = "capture_" + (i - 2); if (i < map.captureNames.length) name = map.captureNames[i]; names[i] = name; } - return new ScopeValue(scope.captures, names); + return new ScopeValue(captures, names); } + /** + * Gets an array proxy of the local locals + */ public ObjectValue getValStackScope() { return new ObjectValue() { - @Override - protected Object getField(Extensions ext, Object key) { - var i = (int)Values.toNumber(ext, key); - if (i < 0 || i >= stackPtr) return null; - else return stack[i]; + @Override public Member getOwnMember(Environment env, KeyCache key) { + var res = super.getOwnMember(env, key); + if (res != null) return res; + + var num = key.toNumber(env); + var i = key.toInt(env); + + if (num != i || i < 0 || i >= stackPtr) return null; + 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 - protected boolean hasField(Extensions ext, Object key) { - return true; - } - @Override - public List keys(boolean includeNonEnumerable) { - var res = super.keys(includeNonEnumerable); - for (var i = 0; i < stackPtr; i++) res.add(i); + @Override public Map getOwnMembers(Environment env) { + var res = new LinkedHashMap(); + + 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(Context ctx, Object thisArg, Object[] args, CodeFunction func) { - 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(ctx, i, args[i]); - } - this.scope.get(1).value = argsObj; - - this.thisArg = thisArg; + public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) { + this.env = env; + this.dbg = DebugContext.get(env); this.function = func; - this.ctx = ctx.pushFrame(this); + this.isNew = isNew; + + this.self = thisArg; + this.args = args; + this.argsVal = new ArgumentsValue(this, args); + this.captures = func.captures; + + var i = 0; + + for (i = 0; i < func.body.localsN; i++) { + this.locals.add(new Value[] { Value.UNDEFINED }); + } } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 7a31109..67badc2 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -1,91 +1,104 @@ package me.topchetoeu.jscript.runtime; +import java.util.ArrayList; import java.util.Collections; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.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; public class InstructionRunner { - private static Object execReturn(Extensions ext, Instruction instr, Frame frame) { + private static Value execReturn(Environment env, Instruction instr, Frame frame) { return frame.pop(); } - private static Object execThrow(Extensions ext, Instruction instr, Frame frame) { + private static Value execThrow(Environment env, Instruction instr, Frame frame) { throw new EngineException(frame.pop()); } - private static Object execThrowSyntax(Extensions 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(Extensions 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, false, instr.get(1), Value.UNDEFINED, callArgs)); frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execCallNew(Extensions ext, Instruction instr, Frame frame) { + private static Value execCallMember(Environment env, Instruction instr, Frame frame) { + var callArgs = frame.take(instr.get(0)); + var index = frame.pop(); + var obj = frame.pop(); + + frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs)); + + frame.codePtr++; + return null; + } + 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, instr.get(1), callArgs)); frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execMakeVar(Extensions ext, Instruction instr, Frame frame) { - var name = (String)instr.get(0); - GlobalScope.get(ext).define(ext, name); - frame.codePtr++; - return Values.NO_RETURN; - } - private static Object execDefProp(Extensions ext, Instruction instr, Frame frame) { - var setter = frame.pop(); - var getter = frame.pop(); - var name = frame.pop(); + private static Value execDefProp(Environment env, Instruction instr, Frame frame) { + var setterVal = frame.pop(); + var getterVal = frame.pop(); + var key = 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 == Value.UNDEFINED) getter = null; + else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal; + else throw EngineException.ofType("Getter must be a function or undefined."); + + if (setterVal == Value.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, key, new PropertyMember(getter, setter, true, true)); frame.push(obj); frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execKeys(Extensions 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, "value", FieldMember.of(new StringValue(el))); frame.push(obj); } frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execTryStart(Extensions 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); @@ -94,235 +107,485 @@ 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(Extensions 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(Extensions 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++) { - frame.push(frame.peek(count - 1)); + frame.push(frame.peek()); } frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execLoadValue(Extensions 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(Value.UNDEFINED); break; + case PUSH_NULL: frame.push(Value.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(Extensions ext, Instruction instr, Frame frame) { - var i = instr.get(0); + private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { + int 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)); + frame.push(frame.getVar(i)[0]); + frame.codePtr++; - frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execLoadObj(Extensions ext, Instruction instr, Frame frame) { - frame.push(new ObjectValue()); + private static Value execLoadObj(Environment env, Instruction instr, Frame frame) { + var obj = new ObjectValue(); + obj.setPrototype(Value.OBJECT_PROTO); + frame.push(obj); frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execLoadGlob(Extensions ext, Instruction instr, Frame frame) { - frame.push(GlobalScope.get(ext).obj); + private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) { + frame.push(Value.global(env)); frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execLoadArr(Extensions ext, Instruction instr, Frame frame) { + private static Value execLoadIntrinsics(Environment env, Instruction instr, Frame frame) { + frame.push(Value.intrinsics(env).get((String)instr.get(0))); + frame.codePtr++; + return null; + } + 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(Extensions 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]; + String name = instr.get(1); + boolean callable = instr.get(2); + boolean constructible = instr.get(3); + boolean captureThis = instr.get(4); + + var captures = new Value[instr.params.length - 5][]; - for (var i = 1; i < instr.params.length; i++) { - captures[i - 1] = frame.scope.get(instr.get(i)); + for (var i = 5; i < instr.params.length; i++) { + captures[i - 5] = frame.getVar(instr.get(i)); } - var func = new CodeFunction(ext, "", frame.function.body.children[id], captures); - + var func = new CodeFunction(env, name, frame.function.body.children[id], captures); + if (!callable) func.enableCall = false; + if (!constructible) func.enableNew = false; + if (captureThis) { + func.self = frame.self; + func.argsVal = frame.argsVal; + } frame.push(func); frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execLoadMember(Extensions 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)); + var top = frame.stackPtr - 1; + frame.stack[top] = frame.stack[top].getMember(env, key); } catch (IllegalArgumentException e) { throw EngineException.ofType(e.getMessage()); } frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execLoadRegEx(Extensions 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 execLoadMemberInt(Environment env, Instruction instr, Frame frame) { + try { + var top = frame.stackPtr - 1; + frame.stack[top] = frame.stack[top].getMember(env, (int)instr.get(0)); + } + catch (IllegalArgumentException e) { + throw EngineException.ofType(e.getMessage()); + } + frame.codePtr++; + return null; + } + private static Value execLoadMemberStr(Environment env, Instruction instr, Frame frame) { + try { + var top = frame.stackPtr - 1; + frame.stack[top] = frame.stack[top].getMember(env, (String)instr.get(0)); + } + catch (IllegalArgumentException e) { + throw EngineException.ofType(e.getMessage()); + } + frame.codePtr++; + return null; + } + private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) { + if (env.hasNotNull(Value.REGEX_CONSTR)) { + frame.push(env.get(Value.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(Extensions 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(Extensions 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(Extensions ext, Instruction instr, Frame frame) { + private static Value execStoreMemberStr(Environment env, Instruction instr, Frame frame) { + var val = frame.pop(); + var obj = frame.pop(); + + if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'."); + if ((boolean)instr.get(1)) frame.push(val); + frame.codePtr++; + return null; + } + private static Value execStoreMemberInt(Environment env, Instruction instr, Frame frame) { + var val = frame.pop(); + var obj = frame.pop(); + + if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'."); + if ((boolean)instr.get(1)) frame.push(val); + frame.codePtr++; + return null; + } + 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); + int 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); + frame.getVar(i)[0] = val; + frame.codePtr++; - frame.codePtr++; - return Values.NO_RETURN; + return null; } - private static Object execStoreSelfFunc(Extensions ext, Instruction instr, Frame frame) { - frame.scope.locals[(int)instr.get(0)].set(ext, frame.function); - frame.codePtr++; - return Values.NO_RETURN; - } - - private static Object execJmp(Extensions 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(Extensions ext, Instruction instr, Frame frame) { - if (Values.toBoolean(frame.pop())) { + private static Value execJmpIf(Environment env, Instruction instr, Frame frame) { + if (frame.pop().toBoolean()) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; } else frame.codePtr ++; - return Values.NO_RETURN; + return null; } - private static Object execJmpIfNot(Extensions ext, Instruction instr, Frame frame) { - if (Values.not(frame.pop())) { + private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) { + if (!frame.pop().toBoolean()) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; } else frame.codePtr ++; - return Values.NO_RETURN; + return null; } - private static Object execTypeof(Extensions 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); - } - else obj = null; - } + if (name != null) obj = Value.global(env).getMember(env, name); 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(Extensions 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(Extensions 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(Extensions 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]; + Value res; + var stack = frame.stack; - for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); + frame.stackPtr -= 1; + var ptr = frame.stackPtr; - frame.push(Values.operation(ext, op, args)); + // for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); + + switch (op) { + case ADD: + res = Value.add(env, stack[ptr - 1], stack[ptr]); + break; + case SUBTRACT: + res = Value.subtract(env, stack[ptr - 1], stack[ptr]); + break; + case DIVIDE: + res = Value.divide(env, stack[ptr - 1], stack[ptr]); + break; + case MULTIPLY: + res = Value.multiply(env, stack[ptr - 1], stack[ptr]); + break; + case MODULO: + res = Value.modulo(env, stack[ptr - 1], stack[ptr]); + break; + + case AND: + res = Value.and(env, stack[ptr - 1], stack[ptr]); + break; + case OR: + res = Value.or(env, stack[ptr - 1], stack[ptr]); + break; + case XOR: + res = Value.xor(env, stack[ptr - 1], stack[ptr]); + break; + + case EQUALS: + res = BoolValue.of(stack[ptr - 1].equals(stack[ptr])); + break; + case NOT_EQUALS: + res = BoolValue.of(!stack[ptr - 1].equals(stack[ptr])); + break; + case LOOSE_EQUALS: + res = BoolValue.of(Value.looseEqual(env, stack[ptr - 1], stack[ptr])); + break; + case LOOSE_NOT_EQUALS: + res = BoolValue.of(!Value.looseEqual(env, stack[ptr - 1], stack[ptr])); + break; + + case GREATER: + res = BoolValue.of(Value.greater(env, stack[ptr - 1], stack[ptr])); + break; + case GREATER_EQUALS: + res = BoolValue.of(Value.greaterOrEqual(env, stack[ptr - 1], stack[ptr])); + break; + case LESS: + res = BoolValue.of(Value.less(env, stack[ptr - 1], stack[ptr])); + break; + case LESS_EQUALS: + res = BoolValue.of(Value.lessOrEqual(env, stack[ptr - 1], stack[ptr])); + break; + + case INVERSE: + res = Value.bitwiseNot(env, stack[ptr++]); + frame.stackPtr++; + break; + case NOT: + res = BoolValue.of(!stack[ptr++].toBoolean()); + frame.stackPtr++; + break; + case POS: + res = stack[ptr++].toNumber(env); + frame.stackPtr++; + break; + case NEG: + res = Value.negative(env, stack[ptr++]); + frame.stackPtr++; + break; + + case SHIFT_LEFT: + res = Value.shiftLeft(env, stack[ptr], stack[ptr]); + break; + case SHIFT_RIGHT: + res = Value.shiftRight(env, stack[ptr], stack[ptr]); + break; + case USHIFT_RIGHT: + res = Value.unsignedShiftRight(env, stack[ptr], stack[ptr]); + break; + + case IN: + res = BoolValue.of(stack[ptr - 1].hasMember(env, stack[ptr], false)); + break; + case INSTANCEOF: + res = BoolValue.of(stack[ptr - 1].isInstanceOf(env, stack[ptr].getMember(env, new StringValue("prototype")))); + break; + + default: return null; + } + + stack[ptr - 1] = res; frame.codePtr++; - return Values.NO_RETURN; + return null; } - public static Object exec(Extensions ext, 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); + private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) { + var name = (String)instr.get(0); - case DUP: return execDup(ext, instr, frame); + if (!Value.global(env).hasMember(env, name, false)) { + if (!Value.global(env).defineOwnMember(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name); + } + + frame.codePtr++; + return null; + } + private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) { + var name = (String)instr.get(0); + var res = Value.global(env).getMemberOrNull(env, name); + + if (res == null) throw EngineException.ofSyntax(name + " is not defined"); + else frame.push(res); + + frame.codePtr++; + return null; + } + private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) { + var name = (String)instr.get(0); + var keep = (boolean)instr.get(1); + var define = (boolean)instr.get(2); + + var val = keep ? frame.peek() : frame.pop(); + var res = false; + + if (define) res = Value.global(env).setMember(env, name, val); + else res = Value.global(env).setMemberIfExists(env, name, val); + + if (!res) throw EngineException.ofError("Couldn't set variable " + name); + + frame.codePtr++; + return null; + } + + private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) { + if ((boolean)instr.get(0) || frame.fakeArgs == null) frame.push(frame.argsVal); + else frame.push(frame.fakeArgs); + frame.codePtr++; + return null; + } + private static Value execLoadRestArgs(Environment env, Instruction instr, Frame frame) { + int offset = instr.get(0); + var res = new ArrayValue(); + + if (offset < frame.args.length) res.copyFrom(frame.args, instr.get(0), 0, frame.args.length - offset); + + frame.push(res); + frame.codePtr++; + return null; + } + private static Value execLoadCallee(Environment env, Instruction instr, Frame frame) { + frame.push(frame.function); + frame.codePtr++; + return null; + } + private static Value execLoadThis(Environment env, Instruction instr, Frame frame) { + frame.push(frame.self); + frame.codePtr++; + return null; + } + + private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) { + int n = instr.get(0); + + for (var i = 0; i < n; i++) frame.locals.add(new Value[] { Value.UNDEFINED }); + + frame.codePtr++; + return null; + } + private static Value execStackRealloc(Environment env, Instruction instr, Frame frame) { + int n = instr.get(0); + + for (var i = frame.locals.size() - n; i < frame.locals.size(); i++) frame.locals.set(i, new Value[] { frame.locals.get(i)[0] }); + + frame.codePtr++; + return null; + } + private static Value execStackFree(Environment env, Instruction instr, Frame frame) { + int n = instr.get(0); + + for (var i = 0; i < n; i++) { + frame.locals.remove(frame.locals.size() - 1); + } + + frame.codePtr++; + return null; + } + + public static Value exec(Environment env, Instruction instr, Frame frame) { + switch (instr.type) { + 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 CALL_MEMBER: return execCallMember(env, instr, frame); + case TRY_START: return execTryStart(env, instr, frame); + case TRY_END: return execTryEnd(env, 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_MEMBER_INT: return execLoadMemberInt(env, instr, frame); + case LOAD_MEMBER_STR: return execLoadMemberStr(env, instr, frame); + case LOAD_REGEX: return execLoadRegEx(env, instr, frame); + case LOAD_GLOB: return execLoadGlob(env, instr, frame); + case LOAD_INTRINSICS: return execLoadIntrinsics(env, instr, frame); + case LOAD_ARGS: return execLoadArgs(env, instr, frame); + case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame); + case LOAD_CALLEE: return execLoadCallee(env, instr, frame); + case LOAD_THIS: return execLoadThis(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_MEMBER_STR: return execStoreMemberStr(env, instr, frame); + case STORE_MEMBER_INT: return execStoreMemberInt(env, instr, frame); + case STORE_VAR: return execStoreVar(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); + + case GLOB_DEF: return exexGlobDef(env, instr, frame); + case GLOB_GET: return exexGlobGet(env, instr, frame); + case GLOB_SET: return exexGlobSet(env, instr, frame); + + case STACK_ALLOC: return execStackAlloc(env, instr, frame); + case STACK_REALLOC: return execStackRealloc(env, instr, frame); + case STACK_FREE: return execStackFree(env, instr, frame); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java new file mode 100644 index 0000000..b895dea --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java @@ -0,0 +1,86 @@ +package me.topchetoeu.jscript.runtime; + +import java.util.HashSet; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.common.environment.Environment; +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.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, el.getKey(), FieldMember.of(toJs(el.getValue()))); + } + + return res; + } + if (val.isNull()) return Value.NULL; + return Value.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 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 == Value.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; + } + +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Key.java b/src/main/java/me/topchetoeu/jscript/runtime/Key.java deleted file mode 100644 index 656efc8..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/Key.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -public class Key { - -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java new file mode 100644 index 0000000..8fc9e68 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -0,0 +1,378 @@ +package me.topchetoeu.jscript.runtime; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +import me.topchetoeu.jscript.common.Metadata; +import me.topchetoeu.jscript.common.Reading; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +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.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.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.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; +import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; + +public class SimpleRepl { + static Thread engineTask; + static Engine engine = new Engine(); + static Environment environment = Environment.empty(); + + static int j = 0; + static String[] args; + + private static void reader() { + try { + try { + try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); } + } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + + for (var arg : args) { + try { + var file = Path.of(arg); + var raw = Files.readString(file); + + try { + var res = engine.pushMsg( + false, environment, + Filename.fromFile(file.toFile()), raw, null + ).get(); + + System.err.println(res.toReadable(environment)); + } + catch (ExecutionException e) { throw e.getCause(); } + } + 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; + + try { + var res = engine.pushMsg( + false, environment, + new Filename("jscript", "repl/" + i + ".js"), raw, + Value.UNDEFINED + ).get(); + System.err.println(res.toReadable(environment)); + } + catch (ExecutionException e) { throw e.getCause(); } + } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + } + } + catch (IOException e) { + System.out.println(e.toString()); + engine.thread().interrupt(); + } + catch (CancellationException | InterruptedException e) { return; } + catch (Throwable ex) { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); + } + } + + private static ObjectValue symbolPrimordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "makeSymbol", new NativeFunction(args -> new SymbolValue(args.get(0).toString(args.env).value))); + res.defineOwnMember(env, "getSymbol", new NativeFunction(args -> SymbolValue.get(args.get(0).toString(args.env).value))); + res.defineOwnMember(env, "getSymbolKey", new NativeFunction(args -> ((SymbolValue)args.get(0)).key())); + res.defineOwnMember(env, "getSymbolDescriptor", new NativeFunction(args -> new StringValue(((SymbolValue)args.get(0)).value))); + + return res; + } + + private static ObjectValue numberPrimordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "parseInt", new NativeFunction(args -> { + var radix = args.get(1).toInt(env); + + if (radix != 10 && args.get(0) instanceof NumberValue) { + return new NumberValue(args.get(0).toNumber(env).value - args.get(0).toNumber(env).value % 1); + } + else { + return NumberValue.parseInt(args.get(0).toString(), radix, false); + } + })); + res.defineOwnMember(env, "parseFloat", new NativeFunction(args -> { + if (args.get(0) instanceof NumberValue) { + return args.get(0); + } + else return NumberValue.parseFloat(args.get(0).toString(), false); + })); + res.defineOwnMember(env, "isNaN", new NativeFunction(args -> BoolValue.of(args.get(0).isNaN()))); + res.defineOwnMember(env, "NaN", new NumberValue(Double.NaN)); + res.defineOwnMember(env, "Infinity", new NumberValue(Double.POSITIVE_INFINITY)); + + return res; + } + + private static ObjectValue stringPrimordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "stringBuild", new NativeFunction(args -> { + var parts = ((ArrayValue)args.get(0)).toArray(); + var sb = new StringBuilder(); + + for (var i = 0; i < parts.length; i++) { + sb.append(((StringValue)parts[i]).value); + } + + return new StringValue(sb.toString()); + })); + + res.defineOwnMember(env, "fromCharCode", new NativeFunction(args -> { + var parts = ((ArrayValue)args.get(0)).toArray(); + var sb = new StringBuilder(); + + for (var i = 0; i < parts.length; i++) { + sb.append(((StringValue)parts[i]).value); + } + + return new StringValue(sb.toString()); + })); + + return res; + } + + private static ObjectValue objectPrimordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "defineField", new NativeFunction(args -> { + var obj = (ObjectValue)args.get(0); + var key = args.get(1); + var writable = args.get(2).toBoolean(); + var enumerable = args.get(3).toBoolean(); + var configurable = args.get(4).toBoolean(); + var value = args.get(5); + + obj.defineOwnMember(args.env, key, FieldMember.of(value, enumerable, configurable, writable)); + + return Value.UNDEFINED; + })); + res.defineOwnMember(env, "defineProperty", new NativeFunction(args -> { + var obj = (ObjectValue)args.get(0); + var key = args.get(1); + var enumerable = args.get(2).toBoolean(); + var configurable = args.get(3).toBoolean(); + var getter = args.get(4) instanceof VoidValue ? null : (FunctionValue)args.get(4); + var setter = args.get(5) instanceof VoidValue ? null : (FunctionValue)args.get(5); + + obj.defineOwnMember(args.env, key, new PropertyMember(getter, setter, configurable, enumerable)); + + return Value.UNDEFINED; + })); + res.defineOwnMember(env, "getPrototype", new NativeFunction(args -> { + return args.get(0).getPrototype(env); + })); + res.defineOwnMember(env, "setPrototype", new NativeFunction(args -> { + var proto = args.get(1) instanceof VoidValue ? null : (ObjectValue)args.get(1); + args.get(0).setPrototype(env, proto); + return args.get(0); + })); + return res; + } + + private static ObjectValue functionPrimordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "setCallable", new NativeFunction(args -> { + var func = (FunctionValue)args.get(0); + func.enableCall = args.get(1).toBoolean(); + return Value.UNDEFINED; + })); + res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { + var func = (FunctionValue)args.get(0); + func.enableNew = args.get(1).toBoolean(); + return Value.UNDEFINED; + })); + res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { + if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new"); + else return new StringValue("call"); + })); + res.defineOwnMember(env, "invoke", new NativeFunction(args -> { + var func = (FunctionValue)args.get(0); + var self = args.get(1); + var funcArgs = (ArrayValue)args.get(2); + + return func.call(env, self, funcArgs.toArray()); + })); + + return res; + } + + private static ObjectValue jsonPrimordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "stringify", new NativeFunction(args -> { + return new StringValue(JSON.stringify(JSONConverter.fromJs(env, args.get(0)))); + })); + res.defineOwnMember(env, "parse", new NativeFunction(args -> { + return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env).value)); + })); + res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { + var func = (FunctionValue)args.get(0); + func.enableNew = args.get(1).toBoolean(); + return Value.UNDEFINED; + })); + res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { + if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new"); + else return new StringValue("call"); + })); + res.defineOwnMember(env, "invoke", new NativeFunction(args -> { + var func = (FunctionValue)args.get(0); + var self = args.get(1); + var funcArgs = (ArrayValue)args.get(2); + + return func.call(env, self, funcArgs.toArray()); + })); + + return res; + } + + private static ObjectValue primordials(Environment env) { + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnMember(env, "symbol", symbolPrimordials(env)); + res.defineOwnMember(env, "number", numberPrimordials(env)); + res.defineOwnMember(env, "string", stringPrimordials(env)); + res.defineOwnMember(env, "object", objectPrimordials(env)); + res.defineOwnMember(env, "function", functionPrimordials(env)); + res.defineOwnMember(env, "json", jsonPrimordials(env)); + + int[] i = new int[1]; + + res.defineOwnMember(env, "setGlobalPrototype", new NativeFunction(args -> { + var type = args.get(0).toString(env).value; + var obj = (ObjectValue)args.get(1); + + switch (type) { + case "string": + args.env.add(Value.STRING_PROTO, obj); + break; + case "number": + args.env.add(Value.NUMBER_PROTO, obj); + break; + case "boolean": + args.env.add(Value.BOOL_PROTO, obj); + break; + case "symbol": + args.env.add(Value.SYMBOL_PROTO, obj); + break; + case "object": + args.env.add(Value.OBJECT_PROTO, obj); + break; + } + + return Value.UNDEFINED; + })); + res.defineOwnMember(env, "compile", new NativeFunction(args -> { + return Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env).value); + })); + return res; + } + + private static void initEnv() { + // 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"); + // } + // })); + + // 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(DebugContext.KEY, new DebugContext()); + // environment.add(EventLoop.KEY, engine); + environment.add(Compiler.KEY, Compiler.DEFAULT); + + var glob = Value.global(environment); + + glob.defineOwnMember(null, "exit", new NativeFunction("exit", args -> { + Thread.currentThread().interrupt(); + throw new InterruptException(); + })); + glob.defineOwnMember(null, "print", new NativeFunction("print", args -> { + for (var el : args.args) { + if (el instanceof StringValue) System.out.print(((StringValue)el).value); + else System.out.print(el.toReadable(args.env)); + } + System.out.println(); + + return Value.UNDEFINED; + })); + } + 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); + } + private static void initGlobals() throws InterruptedException, ExecutionException { + EventLoop.get(environment).pushMsg( + false, environment, + Filename.parse("jscript://init.js"), Reading.resourceToString("lib/index.js"), + Value.UNDEFINED, Value.global(environment), primordials(environment) + ).get(); + } + + 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(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java b/src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java deleted file mode 100644 index 8665625..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import me.topchetoeu.jscript.runtime.values.FunctionValue; -import me.topchetoeu.jscript.runtime.values.ObjectValue; - -public interface WrapperProvider { - public ObjectValue getProto(Class obj); - public ObjectValue getNamespace(Class obj); - public FunctionValue getConstr(Class obj); - - public WrapperProvider fork(Environment env); -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java index f43e7bc..d8f709b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java @@ -5,22 +5,21 @@ import java.util.HashMap; import java.util.List; import java.util.WeakHashMap; -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.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.mapping.FunctionMap; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.runtime.Frame; -import me.topchetoeu.jscript.runtime.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 KEY = new Key<>(); - public static final Key IGNORE = new Key<>(); + public static final Key KEY = Key.of(); + public static final Key IGNORE = Key.of(); private HashMap sources; private WeakHashMap maps; @@ -71,15 +70,24 @@ public class DebugContext { if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY; return getMapOrEmpty(((CodeFunction)func).body); } + public List getStackFrames() { + if (debugger == null) return List.of(); + return this.debugger.getStackFrame(); + } - public void onFramePop(Context ctx, Frame frame) { - if (debugger != null) debugger.onFramePop(ctx, frame); + public void onFramePop(Environment env, Frame frame) { + if (debugger != null) debugger.onFramePop(env, frame); } - public void onFramePush(Context ctx, Frame frame) { - if (debugger != null) debugger.onFramePush(ctx, frame); + public void onFramePush(Environment env, Frame frame) { + if (debugger != null) debugger.onFramePush(env, frame); } - public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { - if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); + + public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (debugger != null) return debugger.onInstruction(env, frame, instruction, returnVal, error, caught); + else return false; + } + public boolean onInstruction(Environment env, Frame frame, Instruction instruction) { + if (debugger != null) return debugger.onInstruction(env, frame, instruction, null, null, false); else return false; } public void onSource(Filename filename, String source) { @@ -102,26 +110,26 @@ public class DebugContext { this(true); } - public static boolean enabled(Extensions exts) { + public static boolean enabled(Environment exts) { return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE); } - public static DebugContext get(Extensions exts) { + public static DebugContext get(Environment exts) { if (enabled(exts)) return exts.get(KEY); else return new DebugContext(false); } - public static List stackTrace(Context ctx) { + public static List stackTrace(Environment env) { var res = new ArrayList(); - var dbgCtx = get(ctx); + var dbgCtx = get(env); - for (var el : ctx.frames()) { - var name = el.function.name; + for (var frame : dbgCtx.getStackFrames()) { + var name = frame.function.name; - var map = dbgCtx.getMapOrEmpty(el.function); + var map = dbgCtx.getMapOrEmpty(frame.function); Location loc = null; if (map != null) { - loc = map.toLocation(el.codePtr, true); + loc = map.toLocation(frame.codePtr, true); if (loc == null) loc = map.start(); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java index cb4b786..2d67c6f 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java @@ -1,10 +1,12 @@ package me.topchetoeu.jscript.runtime.debug; -import me.topchetoeu.jscript.common.Filename; +import java.util.List; + import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.mapping.FunctionMap; -import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.runtime.Frame; import me.topchetoeu.jscript.runtime.exceptions.EngineException; @@ -35,7 +37,7 @@ public interface DebugHandler { /** * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. * This function might pause in order to await debugging commands. - * @param ctx The context of execution + * @param env The context of execution * @param frame The frame in which execution is occuring * @param instruction The instruction which was or will be executed * @param returnVal The return value of the instruction, Values.NO_RETURN if none @@ -43,32 +45,35 @@ public interface DebugHandler { * @param caught Whether or not the error has been caught * @return Whether or not the frame should restart (currently does nothing) */ - boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); + boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); /** * Called immediatly before a frame has been pushed on the frame stack. * This function might pause in order to await debugging commands. - * @param ctx The context of execution + * @param env The context of execution * @param frame The code frame which was pushed */ - void onFramePush(Context ctx, Frame frame); + void onFramePush(Environment env, Frame frame); /** * Called immediatly after a frame has been popped out of the frame stack. * This function might pause in order to await debugging commands. - * @param ctx The context of execution + * @param env The context of execution * @param frame The code frame which was popped out */ - void onFramePop(Context ctx, Frame frame); + void onFramePop(Environment env, Frame frame); + + List getStackFrame(); public static DebugHandler empty() { return new DebugHandler () { - @Override public void onFramePop(Context ctx, Frame frame) { } - @Override public void onFramePush(Context ctx, Frame frame) { } - @Override public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + @Override public void onFramePop(Environment env, Frame frame) { } + @Override public void onFramePush(Environment env, Frame frame) { } + @Override public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { return false; } @Override public void onSourceLoad(Filename filename, String source) { } @Override public void onFunctionLoad(FunctionBody body, FunctionMap map) { } + @Override public List getStackFrame() { return List.of(); } }; } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java deleted file mode 100644 index 72cb63a..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java +++ /dev/null @@ -1,11 +0,0 @@ -package me.topchetoeu.jscript.runtime.exceptions; - -public class ConvertException extends RuntimeException { - public final String source, target; - - public ConvertException(String source, String target) { - super(String.format("Cannot convert '%s' to '%s'.", source, target)); - this.source = source; - this.target = target; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index 20d19bc..f9a21f4 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -3,22 +3,23 @@ package me.topchetoeu.jscript.runtime.exceptions; import java.util.ArrayList; import java.util.List; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Environment; -import me.topchetoeu.jscript.runtime.Extensions; -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.common.environment.Environment; +import me.topchetoeu.jscript.common.parsing.Location; +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; +import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public class EngineException extends RuntimeException { public static class StackElement { public final Location location; public final String name; - public final Extensions ext; + public final Environment ext; public boolean visible() { - return ext == null || !ext.get(Environment.HIDE_STACK, false); + return ext == null || !ext.get(Value.HIDE_STACK, false); } public String toString() { var res = ""; @@ -30,27 +31,27 @@ public class EngineException extends RuntimeException { return res.trim(); } - public StackElement(Extensions ext, Location location, String name) { + public StackElement(Environment ext, Location location, String name) { if (name != null) name = name.trim(); if (name.equals("")) name = null; if (ext == null) this.ext = null; - else this.ext = Context.clean(ext); + else this.ext = ext; this.location = location; this.name = name; } } - public final Object value; + public final Value value; public EngineException cause; - public Extensions ext = null; + public Environment env = null; public final List stackTrace = new ArrayList<>(); - public EngineException add(Extensions ext, String name, Location location) { - var el = new StackElement(ext, location, name); + public EngineException add(Environment env, String name, Location location) { + var el = new StackElement(env, location, name); if (el.name == null && el.location == null) return this; - setExtensions(ext); + setEnvironment(env); stackTrace.add(el); return this; } @@ -58,54 +59,70 @@ public class EngineException extends RuntimeException { this.cause = cause; return this; } - public EngineException setExtensions(Extensions ext) { - if (this.ext == null) this.ext = Context.clean(ext); + public EngineException setEnvironment(Environment env) { + if (this.env == null) this.env = env; return this; } - public String toString(Extensions 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"); + var name = value.getMember(env, "name"); + var desc = value.getMember(env, "message"); + + if (name.isPrimitive() && desc.isPrimitive()) { + if (name instanceof VoidValue) ss.append("Error: "); + else ss.append(name.toString(env).value + ": "); + + if (desc instanceof VoidValue) ss.append("An error occurred"); + else ss.append(desc.toString(env).value); + + ss.append("\n"); + } + else ss.append("[Error while stringifying]\n"); } 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 (msg == null) msg = ""; + + if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name))); + res.defineOwnMember(Environment.empty(), "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(Value.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(Value.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(Value.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(Value.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(Value.RANGE_ERR_PROTO))); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java index 23bd206..023df78 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java @@ -1,13 +1,13 @@ package me.topchetoeu.jscript.runtime.exceptions; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; public class SyntaxException extends RuntimeException { public final Location loc; public final String msg; public SyntaxException(Location loc, String msg) { - super(String.format("Syntax error (at %s): %s", loc, msg)); + super(String.format("Syntax error %s: %s", loc, msg)); this.loc = loc; this.msg = msg; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java deleted file mode 100644 index 63eed8e..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import java.util.HashSet; -import java.util.Set; - -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.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; - -public class GlobalScope { - public static final Key KEY = new Key<>(); - - public final ObjectValue obj; - - public boolean has(Extensions ext, String name) { - return Values.hasMember(ext, obj, name, false); - } - - public GlobalScope child() { - var obj = new ObjectValue(); - Values.setPrototype(null, obj, this.obj); - return new GlobalScope(obj); - } - - public Object define(Extensions ext, String name) { - if (Values.hasMember(ext, obj, name, false)) return name; - obj.defineProperty(ext, name, null); - return name; - } - public void define(Extensions ext, String name, Variable val) { - obj.defineProperty(ext, name, - new NativeFunction("get " + name, args -> val.get(args.ctx)), - new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }), - true, true - ); - } - public void define(Extensions ext, String name, boolean readonly, Object val) { - obj.defineProperty(ext, name, val, readonly, true, true); - } - public void define(Extensions ext, String ...names) { - for (var n : names) define(ext, n); - } - public void define(Extensions ext, boolean readonly, FunctionValue val) { - define(ext, val.name, readonly, val); - } - - public Object get(Extensions 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 void set(Extensions 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 Set keys() { - var res = new HashSet(); - - for (var key : keys()) { - if (key instanceof String) res.add((String)key); - } - - return res; - } - - public GlobalScope() { - this.obj = new ObjectValue(); - } - public GlobalScope(ObjectValue val) { - this.obj = val; - } - - public static GlobalScope get(Extensions ext) { - if (ext.has(KEY)) return ext.get(KEY); - else return new GlobalScope(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java deleted file mode 100644 index 6adce80..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import java.util.ArrayList; - -public class LocalScope { - public final ValueVariable[] captures; - public final ValueVariable[] locals; - public final ArrayList catchVars = new ArrayList<>(); - - public ValueVariable get(int i) { - if (i >= locals.length) return catchVars.get(i - locals.length); - if (i >= 0) return locals[i]; - else return captures[~i]; - } - - - public int size() { - return captures.length + locals.length; - } - - public LocalScope(int n, ValueVariable[] captures) { - locals = new ValueVariable[n]; - this.captures = captures; - - for (int i = 0; i < n; i++) { - locals[i] = new ValueVariable(false, null); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java deleted file mode 100644 index eb988b3..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.values.Values; - -public class ValueVariable implements Variable { - public boolean readonly; - public Object value; - - @Override - public boolean readonly() { return readonly; } - - @Override - public Object get(Extensions ext) { - return value; - } - - @Override - public void set(Extensions ext, Object val) { - if (readonly) return; - this.value = Values.normalize(ext, val); - } - - public ValueVariable(boolean readonly, Object val) { - this.readonly = readonly; - this.value = val; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java deleted file mode 100644 index 6d00d80..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import me.topchetoeu.jscript.runtime.Extensions; - -public interface Variable { - Object get(Extensions ext); - default boolean readonly() { return true; } - default void set(Extensions ext, Object val) { } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java deleted file mode 100644 index a13c014..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java +++ /dev/null @@ -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.Extensions; - -// TODO: Make methods generic -public class ArrayValue extends ObjectValue implements Iterable { - 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(Extensions 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(Extensions 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 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(Extensions 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(Extensions 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(Extensions 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(Extensions 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 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 iterator() { - return new Iterator() { - 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(Extensions 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(Extensions ext, Collection values) { - return new ArrayValue(ext, values.toArray(Object[]::new)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java deleted file mode 100644 index 2d0df77..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java +++ /dev/null @@ -1,50 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import me.topchetoeu.jscript.common.FunctionBody; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Frame; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; - -public class CodeFunction extends FunctionValue { - public final FunctionBody body; - public final ValueVariable[] captures; - public Extensions extensions; - - // 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(Extensions ext, Object thisArg, Object ...args) { - var frame = new Frame(Context.of(ext), thisArg, args, this); - - frame.onPush(); - - try { - while (true) { - var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null); - if (res != Values.NO_RETURN) return res; - } - } - finally { - frame.onPop(); - } - } - - public CodeFunction(Extensions extensions, String name, FunctionBody body, ValueVariable[] captures) { - super(name, body.argsN); - this.captures = captures; - this.extensions = Context.clean(extensions); - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java deleted file mode 100644 index 1717f40..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java +++ /dev/null @@ -1,6 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -public enum ConvertHint { - TOSTRING, - VALUEOF, -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java deleted file mode 100644 index ced4c9a..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java +++ /dev/null @@ -1,69 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.util.List; - -import me.topchetoeu.jscript.runtime.Extensions; - -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(Extensions ext, Object thisArg, Object ...args); - public Object call(Extensions ext) { - return call(ext, null); - } - - @Override - protected Object getField(Extensions ext, Object key) { - if ("name".equals(key)) return name; - if ("length".equals(key)) return length; - return super.getField(ext, key); - } - @Override - protected boolean setField(Extensions 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(Extensions ext, Object key) { - if ("name".equals(key)) return true; - if ("length".equals(key)) return true; - return super.hasField(ext, key); - } - - @Override - public List 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); - } -} - diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java b/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java new file mode 100644 index 0000000..f5660ed --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java @@ -0,0 +1,59 @@ +package me.topchetoeu.jscript.runtime.values; + +import me.topchetoeu.jscript.common.environment.Environment; +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 final class KeyCache { + public final Value value; + private Integer intCache; + private Double doubleCache; + private Boolean booleanCache; + private String stringCache; + + public String toString(Environment env) { + if (stringCache != null) return stringCache; + else return stringCache = value.toString(env).value; + } + public double toNumber(Environment env) { + if (doubleCache != null) return doubleCache; + else return doubleCache = value.toNumber(env).value; + } + public int toInt(Environment env) { + if (intCache != null) return intCache; + else return intCache = (int)toNumber(env); + } + public boolean toBoolean() { + if (booleanCache != null) return booleanCache; + else return booleanCache = value.toBoolean(); + } + public SymbolValue toSymbol() { + if (value instanceof SymbolValue) return (SymbolValue)value; + else return null; + } + public boolean isSymbol() { + return value instanceof SymbolValue; + } + + public KeyCache(Value value) { + this.value = value; + } + public KeyCache(String value) { + this.value = new StringValue(value); + this.stringCache = value; + this.booleanCache = !value.equals(""); + } + public KeyCache(int value) { + this.value = new NumberValue(value); + this.intCache = value; + this.doubleCache = (double)value; + this.booleanCache = value != 0; + } + public KeyCache(double value) { + this.value = new NumberValue(value); + this.intCache = (int)value; + this.doubleCache = value; + this.booleanCache = value != 0; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java new file mode 100644 index 0000000..92828a0 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java @@ -0,0 +1,130 @@ +package me.topchetoeu.jscript.runtime.values; + +import me.topchetoeu.jscript.common.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; + +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 Value.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, "getter", FieldMember.of(Value.UNDEFINED)); + else res.defineOwnMember(env, "getter", FieldMember.of(getter)); + + if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(Value.UNDEFINED)); + else res.defineOwnMember(env, "setter", FieldMember.of(setter)); + + res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); + res.defineOwnMember(env, "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).equals(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, "value", FieldMember.of(get(env, self))); + res.defineOwnMember(env, "writable", FieldMember.of(BoolValue.of(writable))); + res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); + res.defineOwnMember(env, "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); +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java deleted file mode 100644 index e1145f9..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.utils.interop.Arguments; - -public class NativeFunction extends FunctionValue { - public static interface NativeFunctionRunner { - Object run(Arguments args); - } - - public final NativeFunctionRunner action; - - @Override - public Object call(Extensions ext, Object thisArg, Object ...args) { - return action.run(new Arguments(Context.of(ext), thisArg, args)); - } - - public NativeFunction(String name, NativeFunctionRunner action) { - super(name, 0); - this.action = action; - } - public NativeFunction(NativeFunctionRunner action) { - super("", 0); - this.action = action; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java b/src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java deleted file mode 100644 index 938bc73..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.util.WeakHashMap; - -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.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> WRAPPERS = new Key<>(); - private static final Object NATIVE_PROTO = new Object(); - public final Object wrapped; - - @Override - public ObjectValue getPrototype(Extensions 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(Extensions exts, Object wrapped) { - if (exts == null) return new NativeWrapper(wrapped); - var wrappers = exts.get(WRAPPERS); - - if (wrappers == null) { - wrappers = new WeakHashMap<>(); - exts.add(WRAPPERS, wrappers); - } - - var key = new MapKey(wrapped); - - if (wrappers.containsKey(key)) return wrappers.get(key); - - var res = new NativeWrapper(wrapped); - wrappers.put(key, res); - - return res; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java deleted file mode 100644 index c1e0768..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java +++ /dev/null @@ -1,354 +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; -import me.topchetoeu.jscript.runtime.Extensions; - -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 values = new LinkedHashMap<>(); - public LinkedHashMap properties = new LinkedHashMap<>(); - public LinkedHashSet nonWritableSet = new LinkedHashSet<>(); - public LinkedHashSet nonConfigurableSet = new LinkedHashSet<>(); - public LinkedHashSet nonEnumerableSet = new LinkedHashSet<>(); - - private Property getProperty(Extensions ext, Object key) { - if (properties.containsKey(key)) return properties.get(key); - var proto = getPrototype(ext); - if (proto != null) return proto.getProperty(ext, 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(Extensions ext, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) { - key = Values.normalize(ext, key); val = Values.normalize(ext, 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(Extensions ext, Object key, Object val) { - return defineProperty(ext, key, val, true, true, true); - } - public final boolean defineProperty(Extensions ext, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { - key = Values.normalize(ext, 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(Extensions ext) { - if (prototype instanceof ObjectValue || prototype == null) return (ObjectValue)prototype; - - try { - if (prototype == ARR_PROTO) return ext.get(Environment.ARRAY_PROTO); - if (prototype == FUNC_PROTO) return ext.get(Environment.FUNCTION_PROTO); - if (prototype == ERR_PROTO) return ext.get(Environment.ERROR_PROTO); - if (prototype == RANGE_ERR_PROTO) return ext.get(Environment.RANGE_ERR_PROTO); - if (prototype == SYNTAX_ERR_PROTO) return ext.get(Environment.SYNTAX_ERR_PROTO); - if (prototype == TYPE_ERR_PROTO) return ext.get(Environment.TYPE_ERR_PROTO); - return ext.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(Extensions ext, Object key) { - if (values.containsKey(key)) return values.get(key); - var proto = getPrototype(ext); - if (proto != null) return proto.getField(ext, 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(Extensions ext, Object key, Object val) { - if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { - ((FunctionValue)val).name = Values.toString(ext, key); - } - - values.put(key, val); - return true; - } - /** - * Deletes the field bound to the given key. - */ - protected void deleteField(Extensions ext, 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(Extensions ext, Object key) { - return values.containsKey(key); - } - - public final Object getMember(Extensions ext, Object key, Object thisArg) { - key = Values.normalize(ext, key); - - if ("__proto__".equals(key)) { - var res = getPrototype(ext); - return res == null ? Values.NULL : res; - } - - var prop = getProperty(ext, key); - - if (prop != null) { - if (prop.getter == null) return null; - else return prop.getter.call(ext, Values.normalize(ext, thisArg)); - } - else return getField(ext, key); - } - public final boolean setMember(Extensions ext, Object key, Object val, Object thisArg, boolean onlyProps) { - key = Values.normalize(ext, key); val = Values.normalize(ext, val); - - var prop = getProperty(ext, key); - if (prop != null) { - if (prop.setter == null) return false; - prop.setter.call(ext, Values.normalize(ext, 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(ext, val); - else if (nonWritableSet.contains(key)) return false; - else return setField(ext, key, val); - } - public final boolean hasMember(Extensions ext, Object key, boolean own) { - key = Values.normalize(ext, key); - - if (key != null && "__proto__".equals(key)) return true; - if (hasField(ext, key)) return true; - if (properties.containsKey(key)) return true; - if (own) return false; - var proto = getPrototype(ext); - return proto != null && proto.hasMember(ext, key, own); - } - public final boolean deleteMember(Extensions ext, Object key) { - key = Values.normalize(ext, key); - - if (!memberConfigurable(key)) return false; - properties.remove(key); - nonWritableSet.remove(key); - nonEnumerableSet.remove(key); - deleteField(ext, key); - return true; - } - public final boolean setPrototype(Extensions ext, Object val) { - val = Values.normalize(ext, 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 (ext != null) { - if (obj == ext.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO; - else if (obj == ext.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO; - else if (obj == ext.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO; - else if (obj == ext.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO; - else if (obj == ext.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO; - else if (obj == ext.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO; - else if (obj == ext.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO; - else prototype = obj; - } - else prototype = obj; - - return true; - } - return false; - } - - public final ObjectValue getMemberDescriptor(Extensions ext, Object key) { - key = Values.normalize(ext, key); - - var prop = properties.get(key); - var res = new ObjectValue(); - - res.defineProperty(ext, "configurable", memberConfigurable(key)); - res.defineProperty(ext, "enumerable", memberEnumerable(key)); - - if (prop != null) { - res.defineProperty(ext, "get", prop.getter); - res.defineProperty(ext, "set", prop.setter); - } - else if (hasField(ext, key)) { - res.defineProperty(ext, "value", values.get(key)); - res.defineProperty(ext, "writable", memberWritable(key)); - } - else return null; - return res; - } - - public List keys(boolean includeNonEnumerable) { - var res = new ArrayList(); - - 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(Extensions ext, Map values) { - this(PlaceholderProto.OBJECT); - for (var el : values.entrySet()) { - defineProperty(ext, el.getKey(), el.getValue()); - } - } - public ObjectValue(PlaceholderProto proto) { - nonConfigurableSet.add("__proto__"); - nonEnumerableSet.add("__proto__"); - setPrototype(proto); - } - public ObjectValue() { - this(PlaceholderProto.OBJECT); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java deleted file mode 100644 index a64f4c2..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.util.HashMap; -import java.util.List; - -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; - -public class ScopeValue extends ObjectValue { - public final ValueVariable[] variables; - public final HashMap names = new HashMap<>(); - - @Override - protected Object getField(Extensions ext, Object key) { - if (names.containsKey(key)) return variables[names.get(key)].get(ext); - return super.getField(ext, key); - } - @Override - protected boolean setField(Extensions 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(Extensions ext, Object key) { - if (names.containsKey(key)) return; - super.deleteField(ext, key); - } - @Override - protected boolean hasField(Extensions ext, Object key) { - if (names.containsKey(key)) return true; - return super.hasField(ext, key); - } - @Override - public List 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]); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java deleted file mode 100644 index efb8f32..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.util.HashMap; - -public final class Symbol { - private static final HashMap 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; - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java new file mode 100644 index 0000000..bf17a7f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -0,0 +1,686 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.runtime.values.Member.FieldMember; +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.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; +import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; + +public abstract class Value { + public static enum CompareResult { + NOT_EQUAL, + EQUAL, + LESS, + GREATER; + + public boolean less() { return this == LESS; } + public boolean greater() { return this == GREATER; } + public boolean lessOrEqual() { return this == LESS || this == EQUAL; } + public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; } + + public static CompareResult from(int cmp) { + if (cmp < 0) return LESS; + if (cmp > 0) return GREATER; + return EQUAL; + } + } + + public static final Key REGEX_CONSTR = Key.of(); + + public static final Key MAX_STACK_COUNT = Key.of(); + public static final Key HIDE_STACK = Key.of(); + public static final Key OBJECT_PROTO = Key.of(); + public static final Key FUNCTION_PROTO = Key.of(); + public static final Key ARRAY_PROTO = Key.of(); + public static final Key BOOL_PROTO = Key.of(); + public static final Key NUMBER_PROTO = Key.of(); + public static final Key STRING_PROTO = Key.of(); + public static final Key SYMBOL_PROTO = Key.of(); + public static final Key ERROR_PROTO = Key.of(); + public static final Key SYNTAX_ERR_PROTO = Key.of(); + public static final Key TYPE_ERR_PROTO = Key.of(); + public static final Key RANGE_ERR_PROTO = Key.of(); + public static final Key GLOBAL = Key.of(); + public static final Key> INTRINSICS = Key.of(); + + public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined")); + public static final VoidValue NULL = new VoidValue("null", new StringValue("object")); + + public abstract StringValue type(); + public abstract boolean isPrimitive(); + + public final boolean isNaN() { + return this instanceof NumberValue && Double.isNaN(((NumberValue)this).value); + } + + public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) { + if (name == null || name.equals("")) name = "(intermediate value)"; + + if (isNew) throw EngineException.ofType(name + " is not a constructor"); + else throw EngineException.ofType(name + " is not a function"); + } + public final Value callNew(Environment env, String name, Value ...args) { + var res = new ObjectValue(); + var proto = getMember(env, new StringValue("prototype")); + + if (proto instanceof ObjectValue) res.setPrototype(env, (ObjectValue)proto); + else res.setPrototype(env, null); + + var ret = this.call(env, true, name, res, args); + + if (!ret.isPrimitive()) return ret; + return res; + } + + public final Value call(Environment env, Value self, Value ...args) { + return call(env, false, "", self, args); + } + public final Value callNew(Environment env, Value ...args) { + return callNew(env, "", args); + } + + public abstract Value toPrimitive(Environment env); + public abstract NumberValue toNumber(Environment env); + public abstract StringValue toString(Environment env); + public abstract boolean toBoolean(); + + public final int toInt(Environment env) { return (int)toNumber(env).value; } + public final long toLong(Environment env) { return (long)toNumber(env).value; } + + public final boolean isInstanceOf(Environment env, Value proto) { + for (var val = getPrototype(env); val != null; val = getPrototype(env)) { + if (val.equals(proto)) return true; + } + + return false; + } + + public abstract Member getOwnMember(Environment env, KeyCache key); + public abstract Map getOwnMembers(Environment env); + public abstract Map getOwnSymbolMembers(Environment env); + public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member); + public abstract boolean deleteOwnMember(Environment env, KeyCache key); + + public abstract ObjectValue getPrototype(Environment env); + public abstract boolean setPrototype(Environment env, ObjectValue val); + + public final Member getOwnMember(Environment env, Value key) { + return getOwnMember(env, new KeyCache(key)); + } + public final Member getOwnMember(Environment env, String key) { + return getOwnMember(env, new KeyCache(key)); + } + public final Member getOwnMember(Environment env, int key) { + return getOwnMember(env, new KeyCache(key)); + } + public final Member getOwnMember(Environment env, double key) { + return getOwnMember(env, new KeyCache(key)); + } + + public final boolean defineOwnMember(Environment env, Value key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + public final boolean defineOwnMember(Environment env, String key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + public final boolean defineOwnMember(Environment env, int key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + public final boolean defineOwnMember(Environment env, double key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + + public final boolean defineOwnMember(Environment env, KeyCache key, Value val) { + return defineOwnMember(env, key, FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, Value key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, String key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, int key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, double key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + + public final boolean deleteOwnMember(Environment env, Value key) { + return deleteOwnMember(env, new KeyCache(key)); + } + public final boolean deleteOwnMember(Environment env, String key) { + return deleteOwnMember(env, new KeyCache(key)); + } + public final boolean deleteOwnMember(Environment env, int key) { + return deleteOwnMember(env, new KeyCache(key)); + } + public final boolean deleteOwnMember(Environment env, double key) { + return deleteOwnMember(env, new KeyCache(key)); + } + + public final Value getMemberOrNull(Environment env, KeyCache key) { + for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { + var member = obj.getOwnMember(env, key); + if (member != null) return member.get(env, obj); + } + + return null; + } + public final Value getMemberOrNull(Environment env, Value key) { + return getMemberOrNull(env, new KeyCache(key)); + } + public final Value getMemberOrNull(Environment env, String key) { + return getMemberOrNull(env, new KeyCache(key)); + } + public final Value getMemberOrNull(Environment env, int key) { + return getMemberOrNull(env, new KeyCache(key)); + } + public final Value getMemberOrNull(Environment env, double key) { + return getMemberOrNull(env, new KeyCache(key)); + } + + public final Value getMember(Environment env, KeyCache key) { + var res = getMemberOrNull(env, key); + if (res != null) return res; + else return Value.UNDEFINED; + } + public final Value getMember(Environment env, Value key) { + return getMember(env, new KeyCache(key)); + } + public final Value getMember(Environment env, String key) { + return getMember(env, new KeyCache(key)); + } + public final Value getMember(Environment env, int key) { + return getMember(env, new KeyCache(key)); + } + public final Value getMember(Environment env, double key) { + return getMember(env, new KeyCache(key)); + } + + public final boolean setMember(Environment env, KeyCache key, Value val) { + for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { + var member = obj.getOwnMember(env, key); + if (member != null) { + if (member.set(env, val, obj)) { + if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + return true; + } + else return false; + } + } + + if (defineOwnMember(env, key, FieldMember.of(val))) { + if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + return true; + } + else return false; + } + public final boolean setMember(Environment env, Value key, Value val) { + return setMember(env, new KeyCache(key), val); + } + public final boolean setMember(Environment env, String key, Value val) { + return setMember(env, new KeyCache(key), val); + } + public final boolean setMember(Environment env, int key, Value val) { + return setMember(env, new KeyCache(key), val); + } + public final boolean setMember(Environment env, double key, Value val) { + return setMember(env, new KeyCache(key), val); + } + + public final boolean setMemberIfExists(Environment env, KeyCache key, Value val) { + for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { + var member = obj.getOwnMember(env, key); + if (member != null) { + if (member.set(env, val, obj)) { + if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + return true; + } + else return false; + } + } + + return false; + } + public final boolean setMemberIfExists(Environment env, Value key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean setMemberIfExists(Environment env, String key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean setMemberIfExists(Environment env, int key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean setMemberIfExists(Environment env, double key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + + public final boolean hasMember(Environment env, KeyCache key, boolean own) { + for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { + if (obj.getOwnMember(env, key) != null) return true; + if (own) return false; + } + + return false; + } + public final boolean hasMember(Environment env, Value key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + public final boolean hasMember(Environment env, String key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + public final boolean hasMember(Environment env, int key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + public final boolean hasMember(Environment env, double key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + + public final boolean deleteMember(Environment env, KeyCache key) { + if (!hasMember(env, key, true)) return true; + return deleteOwnMember(env, key); + } + public final boolean deleteMember(Environment env, Value key) { + return deleteMember(env, new KeyCache(key)); + } + public final boolean deleteMember(Environment env, String key) { + return deleteMember(env, new KeyCache(key)); + } + public final boolean deleteMember(Environment env, int key) { + return deleteMember(env, new KeyCache(key)); + } + public final boolean deleteMember(Environment env, double key) { + return deleteMember(env, new KeyCache(key)); + } + + public final Map getMembers(Environment env, boolean own, boolean onlyEnumerable) { + var res = new LinkedHashMap(); + var protos = new ArrayList(); + + for (var proto = this; proto != null; proto = proto.getPrototype(env)) { + protos.add(proto); + if (own) break; + } + + Collections.reverse(protos); + + for (var proto : protos) { + if (onlyEnumerable) { + for (var el : proto.getOwnMembers(env).entrySet()) { + if (!el.getValue().enumerable()) continue; + res.put(el.getKey(), el.getValue()); + } + } + else res.putAll(proto.getOwnMembers(env)); + } + + return res; + } + public final Map getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) { + var res = new LinkedHashMap(); + var protos = new ArrayList(); + + for (var proto = this; proto != null; proto = proto.getPrototype(env)) { + protos.add(proto); + if (own) break; + } + + Collections.reverse(protos); + + for (var proto : protos) { + if (onlyEnumerable) { + for (var el : proto.getOwnSymbolMembers(env).entrySet()) { + if (!el.getValue().enumerable()) continue; + res.put(el.getKey(), el.getValue()); + } + } + else res.putAll(proto.getOwnSymbolMembers(env)); + } + + return res; + } + + public final Value getMemberPath(Environment env, Value ...path) { + var res = this; + for (var key : path) res = res.getMember(env, key); + return res; + } + public final ObjectValue getMemberDescriptor(Environment env, Value key) { + var member = getOwnMember(env, new KeyCache(key)); + + if (member != null) return member.descriptor(env, this); + else return null; + } + + public Iterable toIterable(Environment env) { + return () -> { + if (!(this instanceof FunctionValue)) return Collections.emptyIterator(); + var func = (FunctionValue)this; + + return new Iterator() { + private Object value = null; + public boolean consumed = true; + private FunctionValue supplier = func; + + private void loadNext() { + if (supplier == null) value = null; + else if (consumed) { + var curr = supplier.call(env, Value.UNDEFINED); + + if (curr == null) { supplier = null; value = null; } + if (curr.getMember(env, new StringValue("done")).toBoolean()) { supplier = null; value = null; } + else { + this.value = curr.getMember(env, new StringValue("value")); + consumed = false; + } + } + } + + @Override public boolean hasNext() { + loadNext(); + return supplier != null; + } + @Override public Object next() { + loadNext(); + var res = value; + value = null; + consumed = true; + return res; + } + }; + }; + } + + public void callWith(Environment env, Iterable it) { + for (var el : it) { + this.call(env, Value.UNDEFINED, el); + } + } + public void callWithAsync(Environment env, Iterable it, boolean async) { + for (var el : it) { + env.get(EventLoop.KEY).pushMsg(() -> this.call(env, Value.UNDEFINED, el), true); + } + } + + private final String toReadable(Environment env, HashSet passed, int tab) { + if (passed.contains(this)) return "[circular]"; + + if (this instanceof ObjectValue obj) { + var res = new StringBuilder(); + var dbg = DebugContext.get(env); + var printed = true; + var keys = this.getMembers(env, true, false); + + if (this instanceof FunctionValue func) { + res.append(this.toString()); + var loc = dbg.getMapOrEmpty(func).start(); + + if (loc != null) res.append(" @ " + loc); + + if ( + func.prototype instanceof ObjectValue objProto && + objProto.getMember(env, "constructor") == func && + objProto.getOwnMembers(env).size() + objProto.getOwnSymbolMembers(env).size() == 1 + ) { keys.remove("constructor"); } + } + else if (this instanceof ArrayValue) { + res.append("["); + var arr = (ArrayValue)this; + + for (int i = 0; i < arr.size(); i++) { + if (i != 0) res.append(", "); + else res.append(" "); + + if (arr.hasMember(env, i, true)) { + res.append(arr.getMember(env, i).toReadable(env, passed, tab)); + keys.remove(i + ""); + } + else res.append(""); + } + + res.append(" ] "); + } + else printed = false; + + + passed.add(this); + + if (keys.size() + obj.getOwnSymbolMembers(env).size() == 0) { + if (!printed) res.append("{}\n"); + } + else if (!printed) { + if (tab > 3) return "{...}"; + res.append("{\n"); + + for (var entry : obj.getOwnSymbolMembers(env).entrySet()) { + for (int i = 0; i < tab + 1; i++) res.append(" "); + res.append("[" + entry.getKey().value + "]" + ": "); + + var member = entry.getValue(); + if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); + else res.append("[property]"); + + res.append(",\n"); + } + for (var entry : obj.getOwnMembers(env).entrySet()) { + for (int i = 0; i < tab + 1; i++) res.append(" "); + res.append(entry.getKey() + ": "); + + var member = entry.getValue(); + if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); + else res.append("[property]"); + + res.append(",\n"); + } + + for (int i = 0; i < tab; i++) res.append(" "); + res.append("}"); + } + + passed.remove(this); + return res.toString(); + } + else if (this instanceof VoidValue) return ((VoidValue)this).name; + else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value)); + else if (this instanceof SymbolValue) return this.toString(); + else return this.toString(env).value; + } + + public final String toReadable(Environment ext) { + return toReadable(ext, new HashSet<>(), 0); + } + + public static final ObjectValue global(Environment env) { + return env.initFrom(GLOBAL, () -> new ObjectValue()); + } + public static final Map intrinsics(Environment env) { + return env.initFrom(INTRINSICS, () -> new HashMap<>()); + } + + public static FunctionValue fromIterator(Environment ext, Iterable iterable) { + var it = iterable.iterator(); + + return new NativeFunction("", args -> { + var obj = new ObjectValue(); + + if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE)); + else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next())); + + return obj; + }); + } + + public static final boolean lessOrEqual(Environment env, Value a, Value b) { + a = a.toPrimitive(env); + b = b.toPrimitive(env); + + if (a instanceof StringValue aStr && b instanceof StringValue bStr) { + return aStr.value.compareTo(bStr.value) <= 0; + } + else { + return a.toNumber(env).value <= b.toNumber(env).value; + } + } + public static final boolean greaterOrEqual(Environment env, Value a, Value b) { + a = a.toPrimitive(env); + b = b.toPrimitive(env); + + if (a instanceof StringValue aStr && b instanceof StringValue bStr) { + return aStr.value.compareTo(bStr.value) >= 0; + } + else { + return a.toNumber(env).value >= b.toNumber(env).value; + } + } + public static final boolean less(Environment env, Value a, Value b) { + a = a.toPrimitive(env); + b = b.toPrimitive(env); + + if (a instanceof StringValue aStr && b instanceof StringValue bStr) { + return aStr.value.compareTo(bStr.value) >= 0; + } + else { + return a.toNumber(env).value < b.toNumber(env).value; + } + } + public static final boolean greater(Environment env, Value a, Value b) { + a = a.toPrimitive(env); + b = b.toPrimitive(env); + + if (a instanceof StringValue aStr && b instanceof StringValue bStr) { + return aStr.value.compareTo(bStr.value) >= 0; + } + else { + return a.toNumber(env).value > b.toNumber(env).value; + } + } + + public static final Value add(Environment env, Value a, Value b) { + a = a.toPrimitive(env); + b = b.toPrimitive(env); + + if (a instanceof StringValue || b instanceof StringValue) { + return new StringValue(a.toString(env).value + b.toString(env).value); + } + else { + return new NumberValue(a.toNumber(env).value + b.toNumber(env).value); + } + } + + public static final NumberValue subtract(Environment env, Value a, Value b) { + return new NumberValue(a.toNumber(env).value - b.toNumber(env).value); + } + public static final NumberValue multiply(Environment env, Value a, Value b) { + return new NumberValue(a.toNumber(env).value - b.toNumber(env).value); + } + public static final NumberValue divide(Environment env, Value a, Value b) { + return new NumberValue(a.toNumber(env).value / b.toNumber(env).value); + } + public static final NumberValue modulo(Environment env, Value a, Value b) { + return new NumberValue(a.toNumber(env).value % b.toNumber(env).value); + } + public static final NumberValue negative(Environment env, Value a) { + return new NumberValue(-a.toNumber(env).value); + } + + public static final NumberValue and(Environment env, Value a, Value b) { + return new NumberValue(a.toInt(env) & b.toInt(env)); + } + public static final NumberValue or(Environment env, Value a, Value b) { + return new NumberValue(a.toInt(env) | b.toInt(env)); + } + public static final NumberValue xor(Environment env, Value a, Value b) { + return new NumberValue(a.toInt(env) ^ b.toInt(env)); + } + public static final NumberValue bitwiseNot(Environment env, Value a) { + return new NumberValue(~a.toInt(env)); + } + + public static final NumberValue shiftLeft(Environment env, Value a, Value b) { + return new NumberValue(a.toInt(env) << b.toInt(env)); + } + public static final NumberValue shiftRight(Environment env, Value a, Value b) { + return new NumberValue(a.toInt(env) >> b.toInt(env)); + } + public static final NumberValue unsignedShiftRight(Environment env, Value a, Value b) { + long _a = a.toInt(env); + long _b = b.toInt(env); + + if (_a < 0) _a += 0x100000000l; + if (_b < 0) _b += 0x100000000l; + + return new NumberValue(_a >>> _b); + } + + public static final boolean looseEqual(Environment env, Value a, Value b) { + // In loose equality, null is equivalent to undefined + if (a instanceof VoidValue || b instanceof VoidValue) return a instanceof VoidValue && b instanceof VoidValue; + + // If both are objects, just compare their references + if (!a.isPrimitive() && !b.isPrimitive()) return a.equals(b); + + // Convert values to primitives + a = a.toPrimitive(env); + b = b.toPrimitive(env); + + // Compare symbols by reference + if (a instanceof SymbolValue || b instanceof SymbolValue) return a.equals(b); + // Compare booleans as numbers + if (a instanceof BoolValue || b instanceof BoolValue) return a.toNumber(env).equals(b.toNumber(env)); + // Comparse numbers as numbers + if (a instanceof NumberValue || b instanceof NumberValue) return a.toNumber(env).equals(b.toNumber(env)); + + // Default to strings + return a.toString(env).equals(b.toString(env)); + } + + // public static Value operation(Environment env, Operation op, Value ...args) { + // } + + public static final String errorToReadable(RuntimeException err, String prefix) { + prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; + if (err instanceof EngineException) { + var ee = ((EngineException)err); + try { + return prefix + " " + ee.toString(ee.env); + } + catch (EngineException ex) { + return prefix + " " + ee.value.toReadable(ee.env); + } + } + else if (err instanceof SyntaxException) { + return prefix + " SyntaxError " + ((SyntaxException)err).msg; + } + else if (err.getCause() instanceof InterruptedException) return ""; + else { + var str = new ByteArrayOutputStream(); + err.printStackTrace(new PrintStream(str)); + + return prefix + " internal error " + str.toString(); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Values.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Values.java deleted file mode 100644 index db93cd8..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Values.java +++ /dev/null @@ -1,761 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.lang.reflect.Array; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.lib.PromiseLib; -import me.topchetoeu.jscript.runtime.Environment; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -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.utils.interop.NativeWrapperProvider; - -public class Values { - public static enum CompareResult { - NOT_EQUAL, - EQUAL, - LESS, - GREATER; - - public boolean less() { return this == LESS; } - public boolean greater() { return this == GREATER; } - public boolean lessOrEqual() { return this == LESS || this == EQUAL; } - public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; } - - public static CompareResult from(int cmp) { - if (cmp < 0) return LESS; - if (cmp > 0) return GREATER; - return EQUAL; - } - } - - public static final Object NULL = new Object(); - public static final Object NO_RETURN = new Object(); - - public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; } - public static boolean isWrapper(Object val, Class clazz) { - if (!isWrapper(val)) return false; - var res = (NativeWrapper)val; - return res != null && clazz.isInstance(res.wrapped); - } - public static boolean isNan(Object val) { return val instanceof Number && Double.isNaN(number(val)); } - - public static double number(Object val) { - if (val instanceof Number) return ((Number)val).doubleValue(); - else return Double.NaN; - } - - @SuppressWarnings("unchecked") - public static T wrapper(Object val, Class clazz) { - if (isWrapper(val)) val = ((NativeWrapper)val).wrapped; - if (val != null && clazz.isInstance(val)) return (T)val; - else return null; - } - - public static String type(Object val) { - if (val == null) return "undefined"; - 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 FunctionValue) return "function"; - return "object"; - } - - private static Object tryCallConvertFunc(Extensions ext, Object obj, String name) { - var func = getMember(ext, obj, name); - - if (func instanceof FunctionValue) { - var res = Values.call(ext, func, obj); - if (isPrimitive(res)) return res; - } - - throw EngineException.ofType("Value couldn't be converted to a primitive."); - } - - public static boolean isPrimitive(Object obj) { - return - obj instanceof Number || - obj instanceof String || - obj instanceof Boolean || - obj instanceof Symbol || - obj == null || - obj == NULL; - } - - public static Object toPrimitive(Extensions ext, Object obj, ConvertHint hint) { - obj = normalize(ext, obj); - if (isPrimitive(obj)) return obj; - - var first = hint == ConvertHint.VALUEOF ? "valueOf" : "toString"; - var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; - - if (ext != null) { - try { return tryCallConvertFunc(ext, obj, first); } - catch (EngineException unused) { return tryCallConvertFunc(ext, obj, second); } - } - - throw EngineException.ofType("Value couldn't be converted to a primitive."); - } - public static boolean toBoolean(Object obj) { - if (obj == NULL || obj == null) return false; - if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false; - if (obj instanceof String && ((String)obj).equals("")) return false; - if (obj instanceof Boolean) return (Boolean)obj; - return true; - } - public static double toNumber(Extensions ext, Object obj) { - var val = toPrimitive(ext, obj, ConvertHint.VALUEOF); - - if (val instanceof Number) return number(val); - if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; - if (val instanceof String) { - try { return Double.parseDouble((String)val); } - catch (NumberFormatException e) { return Double.NaN; } - } - return Double.NaN; - } - public static String toString(Extensions ext, Object obj) { - var val = toPrimitive(ext, obj, ConvertHint.VALUEOF); - - if (val == null) return "undefined"; - if (val == NULL) return "null"; - - if (val instanceof Number) { - var d = number(val); - 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(); - } - if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; - if (val instanceof String) return (String)val; - if (val instanceof Symbol) return val.toString(); - - return "Unknown value"; - } - - public static Object add(Extensions ext, Object a, Object b) { - if (a instanceof String || b instanceof String) return toString(ext, a) + toString(ext, b); - else return toNumber(ext, a) + toNumber(ext, b); - } - public static double subtract(Extensions ext, Object a, Object b) { - return toNumber(ext, a) - toNumber(ext, b); - } - public static double multiply(Extensions ext, Object a, Object b) { - return toNumber(ext, a) * toNumber(ext, b); - } - public static double divide(Extensions ext, Object a, Object b) { - return toNumber(ext, a) / toNumber(ext, b); - } - public static double modulo(Extensions ext, Object a, Object b) { - return toNumber(ext, a) % toNumber(ext, b); - } - - public static double negative(Extensions ext, Object obj) { - return -toNumber(ext, obj); - } - - public static int and(Extensions ext, Object a, Object b) { - return (int)toNumber(ext, a) & (int)toNumber(ext, b); - } - public static int or(Extensions ext, Object a, Object b) { - return (int)toNumber(ext, a) | (int)toNumber(ext, b); - } - public static int xor(Extensions ext, Object a, Object b) { - return (int)toNumber(ext, a) ^ (int)toNumber(ext, b); - } - public static int bitwiseNot(Extensions ext, Object obj) { - return ~(int)toNumber(ext, obj); - } - - public static int shiftLeft(Extensions ext, Object a, Object b) { - return (int)toNumber(ext, a) << (int)toNumber(ext, b); - } - public static int shiftRight(Extensions ext, Object a, Object b) { - return (int)toNumber(ext, a) >> (int)toNumber(ext, b); - } - public static long unsignedShiftRight(Extensions ext, Object a, Object b) { - long _a = (long)toNumber(ext, a); - long _b = (long)toNumber(ext, b); - - if (_a < 0) _a += 0x100000000l; - if (_b < 0) _b += 0x100000000l; - return _a >>> _b; - } - - public static CompareResult compare(Extensions ext, Object a, Object b) { - a = toPrimitive(ext, a, ConvertHint.VALUEOF); - b = toPrimitive(ext, b, ConvertHint.VALUEOF); - - if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b)); - - var _a = toNumber(ext, a); - var _b = toNumber(ext, b); - - if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL; - - return CompareResult.from(Double.compare(_a, _b)); - } - - public static boolean not(Object obj) { - return !toBoolean(obj); - } - - public static boolean isInstanceOf(Extensions ext, Object obj, Object proto) { - if (obj == null || obj == NULL || proto == null || proto == NULL) return false; - var val = getPrototype(ext, obj); - - while (val != null) { - if (val.equals(proto)) return true; - val = val.getPrototype(ext); - } - - return false; - } - - public static Object operation(Extensions ext, Operation op, Object ...args) { - switch (op) { - case ADD: return add(ext, args[0], args[1]); - case SUBTRACT: return subtract(ext, args[0], args[1]); - case DIVIDE: return divide(ext, args[0], args[1]); - case MULTIPLY: return multiply(ext, args[0], args[1]); - case MODULO: return modulo(ext, args[0], args[1]); - - case AND: return and(ext, args[0], args[1]); - case OR: return or(ext, args[0], args[1]); - case XOR: return xor(ext, args[0], args[1]); - - case EQUALS: return strictEquals(ext, args[0], args[1]); - case NOT_EQUALS: return !strictEquals(ext, args[0], args[1]); - case LOOSE_EQUALS: return looseEqual(ext, args[0], args[1]); - case LOOSE_NOT_EQUALS: return !looseEqual(ext, args[0], args[1]); - - case GREATER: return compare(ext, args[0], args[1]).greater(); - case GREATER_EQUALS: return compare(ext, args[0], args[1]).greaterOrEqual(); - case LESS: return compare(ext, args[0], args[1]).less(); - case LESS_EQUALS: return compare(ext, args[0], args[1]).lessOrEqual(); - - case INVERSE: return bitwiseNot(ext, args[0]); - case NOT: return not(args[0]); - case POS: return toNumber(ext, args[0]); - case NEG: return negative(ext, args[0]); - - case SHIFT_LEFT: return shiftLeft(ext, args[0], args[1]); - case SHIFT_RIGHT: return shiftRight(ext, args[0], args[1]); - case USHIFT_RIGHT: return unsignedShiftRight(ext, args[0], args[1]); - - case IN: return hasMember(ext, args[1], args[0], false); - case INSTANCEOF: { - var proto = getMember(ext, args[1], "prototype"); - return isInstanceOf(ext, args[0], proto); - } - - default: return null; - } - } - - public static Object getMember(Extensions ctx, Object obj, Object key) { - obj = normalize(ctx, obj); key = normalize(ctx, key); - if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); - if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); - if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj); - - if (obj instanceof String && key instanceof Number) { - var i = number(key); - var s = (String)obj; - if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) { - return s.charAt((int)i) + ""; - } - } - - var proto = getPrototype(ctx, obj); - - if (proto == null) return "__proto__".equals(key) ? NULL : null; - else if (key != null && "__proto__".equals(key)) return proto; - else return proto.getMember(ctx, key, obj); - } - public static Object getMemberPath(Extensions ctx, Object obj, Object ...path) { - var res = obj; - for (var key : path) res = getMember(ctx, res, key); - return res; - } - public static boolean setMember(Extensions ctx, Object obj, Object key, Object val) { - obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); - if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); - if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); - if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); - if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false); - - var proto = getPrototype(ctx, obj); - return proto.setMember(ctx, key, val, obj, true); - } - public static boolean hasMember(Extensions ctx, Object obj, Object key, boolean own) { - if (obj == null || obj == NULL) return false; - obj = normalize(ctx, obj); key = normalize(ctx, key); - - if ("__proto__".equals(key)) return true; - if (obj instanceof ObjectValue) return ((ObjectValue)obj).hasMember(ctx, key, own); - - if (obj instanceof String && key instanceof Number) { - var i = number(key); - var s = (String)obj; - if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) return true; - } - - if (own) return false; - - var proto = getPrototype(ctx, obj); - return proto != null && proto.hasMember(ctx, key, own); - } - public static boolean deleteMember(Extensions ext, Object obj, Object key) { - if (obj == null || obj == NULL) return false; - obj = normalize(ext, obj); key = normalize(ext, key); - - if (obj instanceof ObjectValue) return ((ObjectValue)obj).deleteMember(ext, key); - else return false; - } - public static ObjectValue getPrototype(Extensions ext, Object obj) { - if (obj == null || obj == NULL) return null; - obj = normalize(ext, obj); - if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ext); - if (ext == null) return null; - - 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); - - return null; - } - public static boolean setPrototype(Extensions ext, Object obj, Object proto) { - obj = normalize(ext, obj); - return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ext, proto); - } - public static void makePrototypeChain(Extensions ext, Object... chain) { - for(var i = 1; i < chain.length; i++) { - setPrototype(ext, chain[i], chain[i - 1]); - } - } - public static List getMembers(Extensions ext, Object obj, boolean own, boolean includeNonEnumerable) { - List res = new ArrayList<>(); - - if (obj instanceof ObjectValue) res = ((ObjectValue)obj).keys(includeNonEnumerable); - if (obj instanceof String) { - for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i); - } - - if (!own) { - var proto = getPrototype(ext, obj); - - while (proto != null) { - res.addAll(proto.keys(includeNonEnumerable)); - proto = getPrototype(ext, proto); - } - } - - - return res; - } - public static ObjectValue getMemberDescriptor(Extensions ext, Object obj, Object key) { - if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ext, key); - else if (obj instanceof String && key instanceof Number) { - var i = ((Number)key).intValue(); - var _i = ((Number)key).doubleValue(); - if (i - _i != 0) return null; - if (i < 0 || i >= ((String)obj).length()) return null; - - return new ObjectValue(ext, Map.of( - "value", ((String)obj).charAt(i) + "", - "writable", false, - "enumerable", true, - "configurable", false - )); - } - else return null; - } - - public static Object call(Extensions ext, Object func, Object thisArg, Object ...args) { - if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value."); - return ((FunctionValue)func).call(ext, thisArg, args); - } - public static Object callNew(Extensions ext, Object func, Object ...args) { - var res = new ObjectValue(); - try { - var proto = Values.getMember(ext, func, "prototype"); - setPrototype(ext, res, proto); - - var ret = call(ext, func, res, args); - - if (!isPrimitive(ret)) return ret; - return res; - } - catch (IllegalArgumentException e) { - throw EngineException.ofType("Tried to call new on an invalid constructor."); - } - } - - public static boolean strictEquals(Extensions ext, Object a, Object b) { - a = normalize(ext, a); - b = normalize(ext, b); - - if (a == null || b == null) return a == null && b == null; - if (isNan(a) || isNan(b)) return false; - if (a instanceof Number && number(a) == -0.) a = 0.; - if (b instanceof Number && number(b) == -0.) b = 0.; - - return a == b || a.equals(b); - } - public static boolean looseEqual(Extensions ext, Object a, Object b) { - a = normalize(ext, a); b = normalize(ext, b); - - // In loose equality, null is equivalent to undefined - if (a == NULL) a = null; - if (b == NULL) b = null; - - if (a == null || b == null) return a == null && b == null; - // If both are objects, just compare their references - if (!isPrimitive(a) && !isPrimitive(b)) return a == b; - - // Convert values to primitives - a = toPrimitive(ext, a, ConvertHint.VALUEOF); - b = toPrimitive(ext, b, ConvertHint.VALUEOF); - - // Compare symbols by reference - if (a instanceof Symbol || b instanceof Symbol) 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)); - - // Default to strings - return toString(ext, a).equals(toString(ext, b)); - } - - public static Object normalize(Extensions ext, Object val) { - if (val instanceof Number) return number(val); - if (isPrimitive(val) || val instanceof ObjectValue) return val; - if (val instanceof Character) return val + ""; - - if (val instanceof Map) { - var res = new ObjectValue(); - - for (var entry : ((Map)val).entrySet()) { - res.defineProperty(ext, entry.getKey(), entry.getValue()); - } - - return res; - } - - if (val instanceof Iterable) { - var res = new ArrayValue(); - - for (var entry : ((Iterable)val)) { - res.set(ext, res.size(), entry); - } - - return res; - } - - if (val instanceof Class) { - if (ext == null) return null; - else return NativeWrapperProvider.get(ext).getConstr((Class)val); - } - - return NativeWrapper.of(ext, val); - } - - @SuppressWarnings("unchecked") - public static T convert(Extensions ext, Object obj, Class clazz) { - if (clazz == Void.class) return null; - - if (obj instanceof NativeWrapper) { - var res = ((NativeWrapper)obj).wrapped; - if (clazz.isInstance(res)) return (T)res; - } - - if (clazz == null || clazz == Object.class) return (T)obj; - - if (obj instanceof ArrayValue) { - if (clazz.isAssignableFrom(ArrayList.class)) { - var raw = ((ArrayValue)obj).toArray(); - var res = new ArrayList<>(); - for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class)); - return (T)new ArrayList<>(res); - } - if (clazz.isAssignableFrom(HashSet.class)) { - var raw = ((ArrayValue)obj).toArray(); - var res = new HashSet<>(); - for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class)); - return (T)new HashSet<>(res); - } - if (clazz.isArray()) { - var raw = ((ArrayValue)obj).toArray(); - Object res = Array.newInstance(clazz.getComponentType(), raw.length); - for (var i = 0; i < raw.length; i++) Array.set(res, i, convert(ext, raw[i], Object.class)); - return (T)res; - } - } - - if (obj instanceof ObjectValue && clazz.isAssignableFrom(HashMap.class)) { - var res = new HashMap<>(); - for (var el : ((ObjectValue)obj).values.entrySet()) res.put( - convert(ext, el.getKey(), null), - convert(ext, el.getValue(), null) - ); - return (T)res; - } - - if (clazz == String.class) return (T)toString(ext, obj); - if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj); - if (clazz == Byte.class || clazz == byte.class) return (T)(Byte)(byte)toNumber(ext, obj); - if (clazz == Integer.class || clazz == int.class) return (T)(Integer)(int)toNumber(ext, obj); - if (clazz == Long.class || clazz == long.class) return (T)(Long)(long)toNumber(ext, obj); - if (clazz == Short.class || clazz == short.class) return (T)(Short)(short)toNumber(ext, obj); - if (clazz == Float.class || clazz == float.class) return (T)(Float)(float)toNumber(ext, obj); - if (clazz == Double.class || clazz == double.class) return (T)(Double)toNumber(ext, obj); - - if (clazz == Character.class || clazz == char.class) { - if (obj instanceof Number) return (T)(Character)(char)number(obj); - else { - var res = toString(ext, obj); - if (res.length() == 0) throw new ConvertException("\"\"", "Character"); - else return (T)(Character)res.charAt(0); - } - } - - if (obj == null) return null; - if (clazz.isInstance(obj)) return (T)obj; - if (clazz.isAssignableFrom(NativeWrapper.class)) { - return (T)NativeWrapper.of(ext, obj); - } - - throw new ConvertException(type(obj), clazz.getSimpleName()); - } - - public static Iterable fromJSIterator(Extensions ext, Object obj) { - return () -> { - try { - var symbol = Symbol.get("Symbol.iterator"); - - var iteratorFunc = getMember(ext, obj, symbol); - if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator(); - var iterator = iteratorFunc instanceof FunctionValue ? - ((FunctionValue)iteratorFunc).call(ext, obj, obj) : - iteratorFunc; - var nextFunc = getMember(ext, call(ext, iteratorFunc, obj), "next"); - - if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator(); - - return new Iterator() { - private Object value = null; - public boolean consumed = true; - private FunctionValue next = (FunctionValue)nextFunc; - - private void loadNext() { - if (next == null) value = null; - else if (consumed) { - var curr = next.call(ext, iterator); - if (curr == null) { next = null; value = null; } - if (toBoolean(Values.getMember(ext, curr, "done"))) { next = null; value = null; } - else { - this.value = Values.getMember(ext, curr, "value"); - consumed = false; - } - } - } - - @Override - public boolean hasNext() { - loadNext(); - return next != null; - } - @Override - public Object next() { - loadNext(); - var res = value; - value = null; - consumed = true; - return res; - } - }; - } - catch (IllegalArgumentException | NullPointerException e) { - return Collections.emptyIterator(); - } - }; - } - - public static ObjectValue toJSIterator(Extensions ext, Iterator it) { - var res = new ObjectValue(); - - try { - var key = getMember(ext, getMember(ext, ext.get(Environment.SYMBOL_PROTO), "constructor"), "iterator"); - res.defineProperty(ext, key, new NativeFunction("", args -> args.self)); - } - catch (IllegalArgumentException | NullPointerException e) { } - - res.defineProperty(ext, "next", new NativeFunction("", args -> { - if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); - else { - var obj = new ObjectValue(); - obj.defineProperty(args.ctx, "value", it.next()); - return obj; - } - })); - - return res; - } - - public static ObjectValue toJSIterator(Extensions ext, Iterable it) { - return toJSIterator(ext, it.iterator()); - } - - public static ObjectValue toJSAsyncIterator(Extensions ext, Iterator it) { - var res = new ObjectValue(); - - try { - var key = getMemberPath(ext, ext.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator"); - res.defineProperty(ext, key, new NativeFunction("", args -> args.self)); - } - catch (IllegalArgumentException | NullPointerException e) { } - - res.defineProperty(ext, "next", new NativeFunction("", args -> { - return PromiseLib.await(args.ctx, () -> { - if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); - else { - var obj = new ObjectValue(); - obj.defineProperty(args.ctx, "value", it.next()); - return obj; - } - }); - })); - - return res; - } - - private static boolean isEmptyFunc(ObjectValue val) { - if (!(val instanceof FunctionValue)) return false; - if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false; - var proto = val.values.get("prototype"); - if (!(proto instanceof ObjectValue)) return false; - var protoObj = (ObjectValue)proto; - if (protoObj.values.get("constructor") != val) return false; - if (protoObj.values.size() + protoObj.properties.size() != 1) return false; - return true; - } - private static String toReadable(Extensions ext, Object val, HashSet passed, int tab) { - if (tab == 0 && val instanceof String) return (String)val; - - if (passed.contains(val)) return "[circular]"; - - var printed = true; - var res = new StringBuilder(); - var dbg = DebugContext.get(ext); - - if (val instanceof FunctionValue) { - res.append(val.toString()); - var loc = val instanceof CodeFunction ? dbg.getMapOrEmpty((CodeFunction)val).start() : null; - - if (loc != null) res.append(" @ " + loc); - } - else if (val instanceof ArrayValue) { - res.append("["); - var obj = ((ArrayValue)val); - for (int i = 0; i < obj.size(); i++) { - if (i != 0) res.append(", "); - else res.append(" "); - if (obj.has(i)) res.append(toReadable(ext, obj.get(i), passed, tab)); - else res.append(""); - } - res.append(" ] "); - } - else if (val instanceof NativeWrapper) { - var obj = ((NativeWrapper)val).wrapped; - res.append("Native " + obj.toString() + " "); - } - else printed = false; - - if (val instanceof ObjectValue) { - if (tab > 3) { - return "{...}"; - } - - passed.add(val); - - var obj = (ObjectValue)val; - if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) { - if (!printed) res.append("{}\n"); - } - else { - res.append("{\n"); - - for (var el : obj.values.entrySet()) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(toReadable(ext, el.getKey(), passed, tab + 1)); - res.append(": "); - res.append(toReadable(ext, el.getValue(), passed, tab + 1)); - res.append(",\n"); - } - for (var el : obj.properties.entrySet()) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(toReadable(ext, el.getKey(), passed, tab + 1)); - res.append(": [prop],\n"); - } - - for (int i = 0; i < tab; i++) res.append(" "); - res.append("}"); - } - - passed.remove(val); - } - else if (val == null) return "undefined"; - else if (val == Values.NULL) return "null"; - else if (val instanceof String) return "'" + val + "'"; - else return Values.toString(ext, val); - - return res.toString(); - } - - public static String toReadable(Extensions ext, Object val) { - return toReadable(ext, val, new HashSet<>(), 0); - } - public static String errorToReadable(RuntimeException err, String prefix) { - prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; - if (err instanceof EngineException) { - var ee = ((EngineException)err); - try { - return prefix + " " + ee.toString(ee.ext); - } - catch (EngineException ex) { - return prefix + " " + toReadable(ee.ext, ee.value); - } - } - else if (err instanceof SyntaxException) { - return prefix + " SyntaxError " + ((SyntaxException)err).msg; - } - else if (err.getCause() instanceof InterruptedException) return ""; - else { - var str = new ByteArrayOutputStream(); - err.printStackTrace(new PrintStream(str)); - - return prefix + " internal error " + str.toString(); - } - } - public static void printValue(Extensions ext, Object val) { - System.out.print(toReadable(ext, val)); - } - public static void printError(RuntimeException err, String prefix) { - System.out.println(errorToReadable(err, prefix)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java new file mode 100644 index 0000000..1a7f276 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java @@ -0,0 +1,40 @@ +package me.topchetoeu.jscript.runtime.values.functions; + + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.Value; + +public class Arguments { + public final Value self; + public final Value[] args; + public final Environment env; + public final boolean isNew; + + 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 Value.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, boolean isNew, Value thisArg, Value... args) { + this.env = env; + this.args = args; + this.self = thisArg; + this.isNew = isNew; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java new file mode 100644 index 0000000..7d900fa --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java @@ -0,0 +1,43 @@ +package me.topchetoeu.jscript.runtime.values.functions; + +import me.topchetoeu.jscript.common.FunctionBody; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.values.Value; + +public final class CodeFunction extends FunctionValue { + public final FunctionBody body; + public final Value[][] captures; + public Value self; + public Value argsVal; + public Environment env; + + private Value onCall(Frame frame) { + frame.onPush(); + + try { + while (true) { + var res = frame.next(null, null, null); + if (res != null) return res; + } + } + finally { + frame.onPop(); + } + } + + @Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) { + var frame = new Frame(env, isNew, thisArg, args, this); + if (argsVal != null) frame.fakeArgs = argsVal; + if (self != null) frame.self = self; + + return onCall(frame); + } + + public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) { + super(name, body.length); + this.captures = captures; + this.env = env; + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java new file mode 100644 index 0000000..543a6d1 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -0,0 +1,94 @@ +package me.topchetoeu.jscript.runtime.values.functions; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; +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(); + + public boolean enableCall = true; + public boolean enableNew = true; + + private final FieldMember nameField = new FieldMember(true, false, false) { + @Override public Value get(Environment env, Value self) { + if (name == null) return new StringValue(""); + 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(true, false, 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, false, 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; + } + }; + + protected abstract Value onCall(Environment ext, boolean isNew, String name, Value thisArg, Value ...args); + + @Override public String toString() { return String.format("function %s(...)", name); } + @Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) { + if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args); + if (!isNew && !enableCall) super.call(ext, isNew, name, thisArg, args); + + return onCall(ext, isNew, name, thisArg, args); + } + + @Override public Member getOwnMember(Environment env, KeyCache key) { + switch (key.toString(env)) { + case "length": return lengthField; + case "name": return nameField; + case "prototype": return prototypeField; + default: return super.getOwnMember(env, key); + } + } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { + switch (key.toString(env)) { + case "length": + length = 0; + return true; + case "name": + name = ""; + return true; + case "prototype": + return false; + default: return super.deleteOwnMember(env, key); + } + } + + public void setName(String val) { + if (this.name == null || this.name.equals("")) this.name = val; + } + + public FunctionValue(String name, int length) { + setPrototype(FUNCTION_PROTO); + + if (name == null) name = ""; + this.length = length; + this.name = name; + + prototype.defineOwnMember(null, "constructor", FieldMember.of(this)); + } +} + diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java new file mode 100644 index 0000000..0ad250a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java @@ -0,0 +1,25 @@ +package me.topchetoeu.jscript.runtime.values.functions; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.Value; + +public final class NativeFunction extends FunctionValue { + public static interface NativeFunctionRunner { + Value run(Arguments args); + } + + public final NativeFunctionRunner action; + + @Override public Value onCall(Environment env, boolean isNew, String name, Value self, Value ...args) { + return action.run(new Arguments(env, isNew, self, args)); + } + + public NativeFunction(String name, NativeFunctionRunner action) { + super(name, 0); + this.action = action; + } + public NativeFunction(NativeFunctionRunner action) { + super("", 0); + this.action = action; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java new file mode 100644 index 0000000..43a326b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java @@ -0,0 +1,230 @@ +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.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; +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 { + private Value[] values; + private int size; + + private final FieldMember lengthField = new FieldMember(false, false, true) { + @Override public Value get(Environment env, Value self) { + return new NumberValue(size); + } + @Override public boolean set(Environment env, Value val, Value self) { + size = val.toInt(env); + return true; + } + }; + + 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 values = 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 Value.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 = Math.max(0, arr.length - size - destStart); + count -= nullFill; + + System.arraycopy(values, sourceStart, arr, destStart, count); + Arrays.fill(arr, count, nullFill + count, 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 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, KeyCache key) { + var res = super.getOwnMember(env, key); + if (res != null) return res; + + var num = key.toNumber(env); + var i = key.toInt(env); + + if (i == num && i >= 0 && i < size && has(i)) return new IndexField(i, this); + else if (key.toString(env).equals("length")) return lengthField; + else return null; + } + @Override public boolean defineOwnMember(Environment env, KeyCache 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 = key.toInt(env); + + if (i == num && i >= 0) { + set(i, ((FieldMember)member).get(env, this)); + return true; + } + else return super.defineOwnMember(env, key, member); + } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { + if (!super.deleteOwnMember(env, key)) return false; + + var num = key.toNumber(env); + var i = key.toInt(env); + + if (i == num && i >= 0 && i < size) return super.deleteOwnMember(env, key); + else return true; + } + + @Override public Map getOwnMembers(Environment env) { + var res = new LinkedHashMap(); + + for (var i = 0; i < size; i++) { + var member = getOwnMember(env, i); + if (member != null) res.put(i + "", member); + } + + res.put("length", lengthField); + + res.putAll(super.getOwnMembers(env)); + + return res; + } + @Override public Iterator 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(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 values) { + return new ArrayValue(values.toArray(Value[]::new)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java new file mode 100644 index 0000000..da5cba4 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -0,0 +1,128 @@ +package me.topchetoeu.jscript.runtime.values.objects; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.KeyCache; +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.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 members = new LinkedHashMap<>(); + public LinkedHashMap 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 boolean toBoolean() { return true; } + @Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); } + @Override public StringValue type() { return typeString; } + + public final void preventExtensions() { + extensible = false; + } + + @Override public Member getOwnMember(Environment env, KeyCache key) { + if (key.isSymbol()) return symbolMembers.get(key.toSymbol()); + else return members.get(key.toString(env)); + } + @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { + var old = getOwnMember(env, key); + if (old != null && old.configure(env, member, this)) return true; + if (old != null && !old.configurable()) return false; + + if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); + else members.put(key.toString(env), member); + + return true; + } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { + if (!extensible) return false; + + var member = getOwnMember(env, key); + if (member == null) return true; + if (member.configurable()) return false; + + if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); + else members.remove(key.toString(env)); + return true; + } + + @Override public Map getOwnMembers(Environment env) { + return members; + } + @Override public Map getOwnSymbolMembers(Environment env) { + return Collections.unmodifiableMap(symbolMembers); + } + + @Override public ObjectValue getPrototype(Environment env) { + if (prototype == null || env == 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 key) { + if (!extensible) return false; + prototype = env -> env.get(key); + return true; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java new file mode 100644 index 0000000..a968637 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java @@ -0,0 +1,34 @@ +package me.topchetoeu.jscript.runtime.values.objects; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.Member.FieldMember; + +public final 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][0]; + } + + @Override public boolean set(Environment env, Value val, Value self) { + variables[i][0] = val; + return true; + } + } + + public final Value[][] variables; + + public ScopeValue(Value[][] variables, String[] names) { + this.variables = variables; + for (var i = 0; i < names.length && i < variables.length; i++) { + defineOwnMember(Environment.empty(), i, new VariableField(i)); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java new file mode 100644 index 0000000..4b567db --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java @@ -0,0 +1,37 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import me.topchetoeu.jscript.common.environment.Environment; +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 boolean toBoolean() { return value; } + @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(BOOL_PROTO); + } + + @Override public boolean equals(Object other) { + if (other instanceof BoolValue bool) return value == bool.value; + else return false; + } + + private BoolValue(boolean val) { + this.value = val; + } + + public static BoolValue of(boolean val) { + return val ? TRUE : FALSE; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java new file mode 100644 index 0000000..2fe9265 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java @@ -0,0 +1,54 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +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 boolean toBoolean() { return value != 0; } + @Override public NumberValue toNumber(Environment ext) { return this; } + @Override public StringValue toString(Environment ext) { return new StringValue(toString()); } + @Override public String toString() { return JSON.stringify(JSONElement.number(value)); } + + @Override public ObjectValue getPrototype(Environment env) { + return env.get(NUMBER_PROTO); + } + + @Override public boolean equals(Object other) { + if (other instanceof NumberValue val) return value == val.value; + else return false; + } + + public NumberValue(double value) { + this.value = value; + } + + public static NumberValue parseInt(String str, int radix, boolean relaxed) { + if (radix < 2 || radix > 36) return new NumberValue(Double.NaN); + + str = str.trim(); + var res = Parsing.parseInt(new Source(str), 0, "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, radix), true); + if (res.isSuccess()) { + if (relaxed || res.n == str.length()) return new NumberValue(res.result); + } + return new NumberValue(Double.NaN); + } + public static NumberValue parseFloat(String str, boolean relaxed) { + str = str.trim(); + var res = Parsing.parseFloat(new Source(str), 0, true); + if (res.isSuccess()) { + if (relaxed || res.n == str.length()) return new NumberValue(res.result); + } + return new NumberValue(Double.NaN); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java new file mode 100644 index 0000000..4d18f7a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import java.util.Map; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; +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, KeyCache key, Member member) { return false; } + @Override public final boolean deleteOwnMember(Environment env, KeyCache 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, KeyCache key) { return null; } + @Override public Map getOwnMembers(Environment env) { return Map.of(); } + @Override public Map getOwnSymbolMembers(Environment env) { return Map.of(); } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java new file mode 100644 index 0000000..8df3f52 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -0,0 +1,43 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import java.util.Map; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.runtime.values.Member; +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 boolean toBoolean() { return !value.equals(""); } + @Override public NumberValue toNumber(Environment ext) { + var val = value.trim(); + if (val.equals("")) return new NumberValue(0); + var res = Parsing.parseNumber(new Source(val), 0, true); + + if (res.isSuccess() && res.n == val.length()) return new NumberValue(res.result); + else return new NumberValue(Double.NaN); + } + @Override public StringValue toString(Environment ext) { return this; } + + @Override public boolean equals(Object other) { + if (other instanceof StringValue val) return value.length() == val.value.length() && value.equals(val.value); + else return false; + } + + @Override public ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); } + + @Override public Map getOwnMembers(Environment env) { + // TODO Auto-generated method stub + return super.getOwnMembers(env); + } + + public StringValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java new file mode 100644 index 0000000..e5baeb4 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java @@ -0,0 +1,49 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import java.util.HashMap; + +import me.topchetoeu.jscript.common.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 registry = new HashMap<>(); + private static final StringValue typeString = new StringValue("symbol"); + + public final String value; + + public Value key() { + return registry.containsKey(value) && registry.get(value) == this ? new StringValue(value) : Value.UNDEFINED; + } + + @Override public StringValue type() { return typeString; } + + @Override public boolean toBoolean() { return false; } + @Override public StringValue toString(Environment env) { + throw EngineException.ofType("Cannot convert a Symbol value to a string"); + } + @Override public NumberValue toNumber(Environment env) { + throw EngineException.ofType("Cannot convert a Symbol value to a number"); + } + + @Override public ObjectValue getPrototype(Environment env) { return env.get(SYMBOL_PROTO); } + + @Override public String toString() { + if (value == null) return "Symbol()"; + else return "Symbol(" + 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; + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java new file mode 100644 index 0000000..9e0539d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -0,0 +1,43 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import java.util.Map; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.KeyCache; +import me.topchetoeu.jscript.runtime.values.Member; +import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; + +public final class VoidValue extends PrimitiveValue { + private final StringValue nameString; + + public final String name; + public final StringValue typeString; + + @Override public StringValue type() { return typeString; } + @Override public boolean toBoolean() { return false; } + @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } + @Override public StringValue toString(Environment ext) { return nameString; } + + @Override public ObjectValue getPrototype(Environment env) { return null; } + + @Override public Member getOwnMember(Environment env, KeyCache key) { + throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); + } + @Override public Map getOwnMembers(Environment env) { + throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name)); + } + @Override public Map getOwnSymbolMembers(Environment env) { + throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name)); + } + + // @Override public Value call(Environment env, Value self, Value... args) { + // throw EngineException.ofType(String.format("Tried to call a value of %s", name)); + // } + + public VoidValue(String name, StringValue type) { + this.name = name; + this.typeString = type; + this.nameString = new StringValue(name); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java b/src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java deleted file mode 100644 index 81e2c5a..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java +++ /dev/null @@ -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.Extensions; -import me.topchetoeu.jscript.runtime.debug.DebugContext; - -public class JSCompiler implements Compiler { - public final Extensions 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(Extensions ext) { - this.ext = ext; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java b/src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java deleted file mode 100644 index 5f55ed5..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java +++ /dev/null @@ -1,152 +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.Context; -import me.topchetoeu.jscript.runtime.Engine; -import me.topchetoeu.jscript.runtime.Environment; -import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -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.interop.NativeWrapperProvider; -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 = new Environment(); - - 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 = args.ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); - return func.call(args.ctx); - } - 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.ctx, 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(new Context(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(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java b/src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java deleted file mode 100644 index db69e7f..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java +++ /dev/null @@ -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 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); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java b/src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java deleted file mode 100644 index bc64a2e..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java +++ /dev/null @@ -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; -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java b/src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java deleted file mode 100644 index fba409d..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.utils.debug; - -public interface DebuggerProvider { - Debugger getDebugger(WebSocket socket, HttpRequest req); -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java b/src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java deleted file mode 100644 index 4fcc42f..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java +++ /dev/null @@ -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 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 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(); - - 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; } - } -} - diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java b/src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java deleted file mode 100644 index 9b89386..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java +++ /dev/null @@ -1,1091 +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.Context; -import me.topchetoeu.jscript.runtime.Engine; -import me.topchetoeu.jscript.runtime.Environment; -import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Frame; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -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.Symbol; -import me.topchetoeu.jscript.runtime.values.Values; - -// very simple indeed -public class SimpleDebugger implements Debugger { - public static final Set VSCODE_EMPTY = Set.of( - "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<>${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=\"<>\",n=e.call(this,t);if(n!==t)return String(n)}catch(t){return`<>${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=\"<>\";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]=`<>${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=\"<>\";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]=`<>${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>>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 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{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 resolvedLocations = new HashMap<>(); - public final HashMap 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.ctx).obj; - this.local = frame.getLocalScope(); - this.capture = frame.getCaptureScope(); - Values.makePrototypeChain(frame.ctx, 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", serializeObj(frame.ctx, local)) - ) - .add(new JSONMap() - .set("type", "closure") - .set("name", "Closure") - .set("object", serializeObj(frame.ctx, capture)) - ) - .add(new JSONMap() - .set("type", "global") - .set("name", "Global Scope") - .set("object", serializeObj(frame.ctx.extensions, global)) - ) - .add(new JSONMap() - .set("type", "other") - .set("name", "Value Stack") - .set("object", serializeObj(frame.ctx.extensions, valstack)) - ) - ); - } - } - private class ObjRef { - public final ObjectValue obj; - public final Extensions ext; - public final HashSet heldGroups = new HashSet<>(); - public boolean held = true; - - public boolean shouldRelease() { - return !held && heldGroups.size() == 0; - } - - public ObjRef(Extensions ext, ObjectValue obj) { - this.ext = ext; - this.obj = obj; - } - } - - private static class RunResult { - public final Extensions ext; - public final Object result; - public final EngineException error; - - public RunResult(Extensions 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 contexts = new WeakHashMap<>(); - private WeakHashMap mappings = new WeakHashMap<>(); - private HashMap> bpLocs = new HashMap<>(); - - private HashMap idToBreakpoint = new HashMap<>(); - - private HashMap filenameToId = new HashMap<>(); - private HashMap idToSource = new HashMap<>(); - private ArrayList pendingSources = new ArrayList<>(); - - private HashMap idToFrame = new HashMap<>(); - private HashMap codeFrameToFrame = new HashMap<>(); - - private HashMap idToObject = new HashMap<>(); - private HashMap objectToId = new HashMap<>(); - private HashMap> objectGroups = new HashMap<>(); - - private Notifier updateNotifier = new Notifier(); - private boolean pendingPause = false; - - private int nextId = 0; - private DebugFrame stepOutFrame = null, currFrame = null; - 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 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 synchronized void updateFrames(Context ctx) { - var frame = ctx.frame; - if (frame == null) return; - - currFrame = getFrame(frame); - } - private JSONList serializeFrames(Context ctx) { - var res = new JSONList(); - - for (var el : ctx.frames()) { - 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 JSONMap serializeObj(Extensions env, Object val, boolean byValue) { - val = Values.normalize(null, val); - env = sanitizeEnvironment(env); - var ctx = Context.of(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 = nextId(); - 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(ctx, Values.getMemberPath(ctx, 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(ctx, obj, "toString") == - Values.getMember(ctx, env.get(Environment.OBJECT_PROTO), "toString"); - } - catch (Exception e) { } - - try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(ctx, 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."); - } - private JSONMap serializeObj(Extensions ext, Object val) { - return serializeObj(ext, val, false); - } - private void addObjectGroup(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); - } - } - private void releaseGroup(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); - } - } - } - private Object deserializeArgument(JSONMap val) { - if (val.isString("objectId")) return idToObject.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); - } - - private JSONMap serializeException(Extensions ext, EngineException err) { - String text = null; - - try { - text = Values.toString(Context.of(ext), err.value); - } - catch (EngineException e) { - text = "[error while stringifying]"; - } - - var res = new JSONMap() - .set("exceptionId", nextId()) - .set("exception", serializeObj(ext, err.value)) - .set("text", text); - - return res; - } - - 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(Context ctx, Breakpoint bp) { - try { - state = State.PAUSED_NORMAL; - var map = new JSONMap() - .set("callFrames", serializeFrames(ctx)) - .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(Context ctx) { - try { - state = State.PAUSED_EXCEPTION; - var map = new JSONMap() - .set("callFrames", serializeFrames(ctx)) - .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(); - } - } - - private Extensions sanitizeEnvironment(Extensions ext) { - var res = ext.child(); - - res.remove(EventLoop.KEY); - res.remove(DebugContext.KEY); - res.add(DebugContext.IGNORE); - - return res; - } - - 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.ctx.extensions.copy(); - - env.remove(DebugContext.KEY); - env.remove(EventLoop.KEY); - env.remove(GlobalScope.KEY); - env.add(EventLoop.KEY, engine); - env.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(Extensions ext, Object target, String query, boolean variable) { - var res = new ArrayValue(); - var passed = new HashSet(); - var tildas = "~"; - var ctx = Context.of(ext); - if (target == null) target = GlobalScope.get(ext); - - for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) { - for (var el : Values.getMembers(ctx, proto, true, true)) { - var strKey = Values.toString(ctx, el); - if (passed.contains(strKey)) continue; - passed.add(strKey); - - var val = Values.getMember(ctx, Values.getMemberDescriptor(ctx, 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(ctx, "label", strKey); - desc.defineProperty(ctx, "sortText", sortText); - - if (val instanceof FunctionValue) { - if (strKey.equals("constructor")) desc.defineProperty(ctx, "type", "name"); - else desc.defineProperty(ctx, "type", variable ? "function" : "method"); - } - else desc.defineProperty(ctx, "type", variable ? "variable" : "property"); - - switch (Values.type(val)) { - case "number": - case "boolean": - desc.defineProperty(ctx, "detail", Values.toString(ctx, val)); - break; - case "object": - if (val == Values.NULL) desc.defineProperty(ctx, "detail", "null"); - else try { - desc.defineProperty(ctx, "detail", Values.getMemberPath(ctx, target, "constructor", "name")); - } - catch (IllegalArgumentException e) { - desc.defineProperty(ctx, "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(ctx, "detail", type); - break; - } - default: - desc.defineProperty(ctx, "type", Values.type(val)); - break; - } - - res.set(ctx, res.size(), desc); - } - - tildas += "~"; - variable = true; - } - - var resObj = new ObjectValue(); - resObj.defineProperty(ctx, "result", res); - resObj.defineProperty(ctx, "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(); - - idToObject.clear(); - objectToId.clear(); - objectGroups.clear(); - - pendingPause = false; - - stepOutFrame = currFrame = 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 = currFrame; - stepOutPtr = currFrame.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 = currFrame; - stepOutPtr = currFrame.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 = currFrame; - stepOutPtr = currFrame.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) addObjectGroup(group, res.result); - - if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(res.ext, res.error)))); - else ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ext, res.result)))); - } - - @Override public synchronized void releaseObjectGroup(V8Message msg) throws IOException { - var group = msg.params.string("objectGroup"); - releaseGroup(group); - ws.send(msg.respond()); - } - @Override public synchronized void releaseObject(V8Message msg) throws IOException { - var id = Integer.parseInt(msg.params.string("objectId")); - var ref = idToObject.get(id); - ref.held = false; - - if (ref.shouldRelease()) { - objectToId.remove(ref.obj); - idToObject.remove(id); - } - - ws.send(msg.respond()); - } - @Override public synchronized void getProperties(V8Message msg) throws IOException { - var ref = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); - var obj = ref.obj; - var ext = ref.ext; - var ctx = Context.of(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(ctx, key)); - if (prop.getter != null) propDesc.set("get", serializeObj(ext, prop.getter)); - if (prop.setter != null) propDesc.set("set", serializeObj(ext, 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(ctx, key)); - propDesc.set("value", serializeObj(ext, Values.getMember(ctx, 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(ctx, obj); - - if (own) { - var protoDesc = new JSONMap(); - protoDesc.set("name", "__proto__"); - protoDesc.set("value", serializeObj(ext, 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(this::deserializeArgument) - .collect(Collectors.toList()); - var byValue = msg.params.bool("returnByValue", false); - - var thisArgRef = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); - var thisArg = thisArgRef.obj; - var ext = thisArgRef.ext; - var ctx = Context.of(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(ctx, res, JSON.toJs(el)); - } - else if (compare(src, CHROME_GET_PROP_FUNC_2)) { - res = Values.call(ctx, 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", serializeObj(ext, func.call(ctx, thisArg))))); - } - else if (compare(src, VSCODE_AUTOCOMPLETE)) { - var target = args.get(0); - if (target == null) target = thisArg; - res = vscodeAutoSuggest(ext, target, Values.toString(ctx, 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", serializeObj(ext, res, byValue)))); - } - catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ext, 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(Context ctx, 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(ctx).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(ctx); - } - else if ( - loc != null && - (state == State.STEPPING_IN || state == State.STEPPING_OVER) && - returnVal != Values.NO_RETURN && stepOutFrame == frame - ) { - pauseDebug(ctx, null); - } - else if (isBreakpointable && bpLocs.containsKey(loc)) { - for (var bp : bpLocs.get(loc)) { - var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result); - if (ok) pauseDebug(ctx, bp); - } - } - // else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); - else if (isBreakpointable && pendingPause) { - pauseDebug(ctx, null); - pendingPause = false; - } - else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, 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(ctx, null); - break; - } - else if (state == State.STEPPING_OVER && bptType.shouldStepOver()) { - pauseDebug(ctx, null); - break; - } - } - } - return false; - } - } - updateNotifier.await(); - } - - return false; - } - @Override public void onFramePush(Context ctx, Frame frame) { - var prevFrame = currFrame; - updateFrames(ctx); - - if (stepOutFrame != null && stepOutFrame.frame == prevFrame.frame && state == State.STEPPING_IN) { - stepOutFrame = currFrame; - } - } - @Override public void onFramePop(Context ctx, Frame frame) { - updateFrames(ctx); - - try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } - catch (NullPointerException e) { } - - if (ctx.stackSize == 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 = currFrame; - } - } - - public SimpleDebugger attach(DebugContext ctx) { - ctx.attachDebugger(this); - contexts.put(ctx, ctx); - return this; - } - - public SimpleDebugger(WebSocket ws) { - this.ws = ws; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java deleted file mode 100644 index 352dc70..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java +++ /dev/null @@ -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) - )); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java deleted file mode 100644 index 4ce0a36..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java +++ /dev/null @@ -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) - ); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java deleted file mode 100644 index 6d31e0d..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java +++ /dev/null @@ -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 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) - ); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java deleted file mode 100644 index d28d33a..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java +++ /dev/null @@ -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) - ); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java b/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java deleted file mode 100644 index bb80125..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java b/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java deleted file mode 100644 index 10d3959..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java deleted file mode 100644 index 323fadb..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java deleted file mode 100644 index f552bba..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java +++ /dev/null @@ -1,59 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -public abstract class BaseFile 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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java deleted file mode 100644 index 09bd373..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java +++ /dev/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; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java deleted file mode 100644 index 91cdc2b..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java deleted file mode 100644 index f98c566..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java +++ /dev/null @@ -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(); - var sizes = new LinkedList(); - 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 it) { - return ofLineReader(LineReader.ofIterator(it)); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java deleted file mode 100644 index 903bde1..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java deleted file mode 100644 index 6f477cb..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Key; - -public interface Filesystem { - public static final Key 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(Extensions exts) { - return exts.get(KEY); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java deleted file mode 100644 index 802179a..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java +++ /dev/null @@ -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(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); - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java deleted file mode 100644 index c6dbacb..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import java.util.HashSet; -import java.util.Set; - -public class HandleManager { - private Set 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(); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java deleted file mode 100644 index 6d84450..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java +++ /dev/null @@ -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 it) { - return () -> { - if (it.hasNext()) return it.next(); - else return null; - }; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java deleted file mode 100644 index 501631a..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import java.io.IOException; - -public interface LineWriter { - void writeLine(String value) throws IOException; -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java deleted file mode 100644 index cb89d1d..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import me.topchetoeu.jscript.common.Buffer; - -class MemoryFile extends BaseFile { - 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); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java deleted file mode 100644 index 68b1e4c..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java +++ /dev/null @@ -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 files = new HashMap<>(); - private HashSet 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("/")); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java deleted file mode 100644 index b64d288..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java +++ /dev/null @@ -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; - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java deleted file mode 100644 index 0bc3967..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java +++ /dev/null @@ -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(); - - 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 + "/.."); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java deleted file mode 100644 index 67efb80..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java +++ /dev/null @@ -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 { - @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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java deleted file mode 100644 index 9ff0c0e..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java +++ /dev/null @@ -1,92 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; - -public class PhysicalFilesystem implements Filesystem { - public final String root; - private HandleManager handles = new HandleManager(); - - private void checkMode(Path path, Mode mode) { - if (!path.startsWith(root)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "Tried to jailbreak the sandbox."); - - if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions"); - if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No write permissions"); - } - - private Path realPath(String path) { - return Path.of(Paths.chroot(root, path)); - } - - @Override public String normalize(String... paths) { - return Paths.normalize(paths); - } - @Override public synchronized File open(String _path, Mode perms) { - try { - var path = realPath(normalize(_path)); - checkMode(path, perms); - - try { - if (Files.isDirectory(path)) return handles.put(File.ofIterator( - Files.list(path).map(v -> v.getFileName().toString()).iterator() - )); - else return handles.put(new PhysicalFile(path, perms)); - } - catch (FileNotFoundException e) { throw new FilesystemException(ErrorReason.DOESNT_EXIST); } - catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION); } - } - catch (FilesystemException e) { throw e.setAction(ActionType.OPEN).setPath(_path); } - } - @Override public synchronized boolean create(String _path, EntryType type) { - try { - var path = realPath(_path); - - try { - switch (type) { - case FILE: - Files.createFile(path); - break; - case FOLDER: - Files.createDirectories(path); - break; - case NONE: - default: - Files.delete(path); - } - } - catch (FileAlreadyExistsException | NoSuchFileException e) { return false; } - catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PARENT); } - } - catch (FilesystemException e) { throw e.setAction(ActionType.CREATE).setPath(_path); } - - return true; - } - @Override public synchronized FileStat stat(String _path) { - var path = realPath(_path); - - if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE); - - var perms = Mode.of(Files.isReadable(path), Files.isWritable(path)); - - if (perms == Mode.NONE) return new FileStat(Mode.NONE, EntryType.NONE); - - return new FileStat( - perms, - Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE - ); - } - @Override public synchronized void close() throws FilesystemException { - try { - handles.close(); - } - catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); } - } - - public PhysicalFilesystem(String root) { - this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString()); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java deleted file mode 100644 index 1db7355..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java +++ /dev/null @@ -1,99 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import java.util.HashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.utils.permissions.Matcher; -import me.topchetoeu.jscript.utils.permissions.Permission; -import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; - -public class RootFilesystem implements Filesystem { - public final Map protocols = new HashMap<>(); - public final PermissionsProvider perms; - - public static final Permission PERM_READ = new Permission("jscript.file.read", Matcher.fileWildcard()); - public static final Permission PERM_WRITE = new Permission("jscript.file.read", Matcher.fileWildcard()); - - private boolean canRead(String _path) { - return perms.hasPermission(PERM_READ, _path); - } - private boolean canWrite(String _path) { - return perms.hasPermission(PERM_WRITE, _path); - } - - private void modeAllowed(String _path, Mode mode) throws FilesystemException { - if (mode.readable && perms != null && !canRead(_path)) { - throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions").setPath(_path); - } - if (mode.writable && perms != null && !canWrite(_path)) { - throw new FilesystemException(ErrorReason.NO_PERMISSION, "No wtrite permissions").setPath(_path); - } - } - - private Filesystem getProtocol(Filename filename) { - var protocol = protocols.get(filename.protocol); - - if (protocol == null) { - throw new FilesystemException(ErrorReason.DOESNT_EXIST, "The protocol '" + filename.protocol + "' doesn't exist."); - } - - return protocol; - } - - @Override public String normalize(String... paths) { - if (paths.length == 0) return "file://"; - else { - var filename = Filename.parse(paths[0]); - var protocol = protocols.get(filename.protocol); - paths[0] = filename.path; - - - if (protocol == null) return Paths.normalize(paths); - else return filename.protocol + "://" + protocol.normalize(paths); - } - } - @Override public synchronized File open(String path, Mode perms) throws FilesystemException { - try { - var filename = Filename.parse(path); - var protocol = getProtocol(filename); - - modeAllowed(filename.toString(), perms); - return protocol.open(filename.path, perms); - } - catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.OPEN); } - } - @Override public synchronized boolean create(String path, EntryType type) throws FilesystemException { - try { - var filename = Filename.parse(path); - var protocol = getProtocol(filename); - - modeAllowed(filename.toString(), Mode.WRITE); - return protocol.create(filename.path, type); - } - catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.CREATE); } - } - @Override public synchronized FileStat stat(String path) throws FilesystemException { - try { - var filename = Filename.parse(path); - var protocol = getProtocol(filename); - - return protocol.stat(filename.path); - } - catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.STAT); } - } - @Override public synchronized void close() throws FilesystemException { - try { - for (var protocol : protocols.values()) { - protocol.close(); - } - - protocols.clear(); - } - catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); } - } - - public RootFilesystem(PermissionsProvider perms) { - this.perms = perms; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java deleted file mode 100644 index 5b82c0e..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java +++ /dev/null @@ -1,52 +0,0 @@ -package me.topchetoeu.jscript.utils.filesystem; - -import java.io.InputStream; -import java.io.OutputStream; - -public class STDFilesystem implements Filesystem { - private File in; - private File out; - private File err; - - @Override - public String normalize(String... path) { - var res = Paths.normalize(path); - while (res.startsWith("/")) res = res.substring(1); - while (res.endsWith("/")) res = res.substring(0, res.length() - 1); - return res; - } - - @Override public synchronized File open(String path, Mode mode) { - path = normalize(path); - if (in != null && path.equals("in")) return in; - else if (out != null && path.equals("out")) return out; - else if (err != null && path.equals("err")) return err; - else throw new FilesystemException(ErrorReason.DOESNT_EXIST).setAction(ActionType.OPEN).setPath(path); - } - @Override public synchronized FileStat stat(String path) { - path = normalize(path); - if (path.equals("in") || path.equals("out") || path.equals("err")) return new FileStat(Mode.READ_WRITE, EntryType.FILE); - else return new FileStat(Mode.NONE, EntryType.NONE); - } - @Override public synchronized void close() { - in = out = err = null; - } - - public STDFilesystem(File in, File out, File err) { - this.in = in; - this.out = out; - this.err = err; - } - public STDFilesystem(InputStream in, OutputStream out, OutputStream err) { - if (in != null) this.in = File.ofStream(in); - if (out != null) this.out = File.ofStream(out); - if (err != null) this.err = File.ofStream(err); - } - public STDFilesystem(LineReader in, LineWriter out) { - if (in != null) this.in = File.ofLineReader(in); - if (out != null) { - this.out = File.ofLineWriter(out); - this.err = File.ofLineWriter(out); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java b/src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java deleted file mode 100644 index 4e8c2ff..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java +++ /dev/null @@ -1,139 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.reflect.Array; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Key; -import me.topchetoeu.jscript.runtime.values.NativeWrapper; -import me.topchetoeu.jscript.runtime.values.Values; - -public class Arguments implements Extensions { - public final Object self; - public final Object[] args; - public final Context ctx; - - @Override public void add(Key key, T obj) { - ctx.add(key, obj); - } - @Override public T get(Key key) { - return ctx.get(key); - } - @Override public boolean has(Key key) { - return ctx.has(key); - } - @Override public boolean remove(Key key) { - return ctx.remove(key); - } - @Override public Iterable> keys() { - return ctx.keys(); - } - - public int n() { - return args.length; - } - - public boolean has(int i) { - return i == -1 || i >= 0 && i < args.length; - } - - public T self(Class type) { - return convert(-1, type); - } - public T convert(int i, Class type) { - return Values.convert(ctx, get(i), type); - } - public Object get(int i, boolean unwrap) { - Object res = null; - - if (i == -1) res = self; - if (i >= 0 && i < args.length) res = args[i]; - if (unwrap && res instanceof NativeWrapper) res = ((NativeWrapper)res).wrapped; - - return res; - } - public Object get(int i) { - return get(i, false); - } - public Object getOrDefault(int i, Object def) { - if (i < 0 || i >= args.length) return def; - else return get(i); - } - - public Arguments slice(int start) { - var res = new Object[Math.max(0, args.length - start)]; - for (int j = start; j < args.length; j++) res[j - start] = get(j); - return new Arguments(ctx, args, res); - } - - @SuppressWarnings("unchecked") - public T[] convert(Class type) { - var res = Array.newInstance(type, args.length); - for (int i = 0; i < args.length; i++) Array.set(res, i, convert(i, type)); - return (T[])res; - } - public int[] convertInt() { - var res = new int[args.length]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Integer.class); - return res; - } - public long[] convertLong() { - var res = new long[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Long.class); - return res; - } - public short[] sliceShort() { - var res = new short[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Short.class); - return res; - } - public float[] sliceFloat() { - var res = new float[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Float.class); - return res; - } - public double[] sliceDouble() { - var res = new double[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Double.class); - return res; - } - public byte[] sliceByte() { - var res = new byte[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Byte.class); - return res; - } - public char[] sliceChar() { - var res = new char[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Character.class); - return res; - } - public boolean[] sliceBool() { - var res = new boolean[Math.max(0, args.length)]; - for (int i = 0; i < args.length; i++) res[i] = convert(i, Boolean.class); - return res; - } - - public String getString(int i) { return Values.toString(ctx, get(i)); } - public boolean getBoolean(int i) { return Values.toBoolean(get(i)); } - public int getInt(int i) { return (int)Values.toNumber(ctx, get(i)); } - public long getLong(int i) { return (long)Values.toNumber(ctx, get(i)); } - public double getDouble(int i) { return Values.toNumber(ctx, get(i)); } - public float getFloat(int i) { return (float)Values.toNumber(ctx, get(i)); } - - public int getInt(int i, int def) { - var res = get(i); - if (res == null) return def; - else return (int)Values.toNumber(ctx, res); - } - public String getString(int i, String def) { - var res = get(i); - if (res == null) return def; - else return Values.toString(ctx, res); - } - - public Arguments(Context ctx, Object thisArg, Object... args) { - this.ctx = ctx; - this.args = args; - this.self = thisArg; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java b/src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java deleted file mode 100644 index 5ef6815..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java +++ /dev/null @@ -1,14 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Expose { - public String value() default ""; - public ExposeType type() default ExposeType.METHOD; - public ExposeTarget target() default ExposeTarget.MEMBER; -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java deleted file mode 100644 index f5b97e6..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface ExposeConstructor { } diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java deleted file mode 100644 index a66330e..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ ElementType.METHOD, ElementType.FIELD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface ExposeField { - public String value() default ""; - public ExposeTarget target() default ExposeTarget.MEMBER; -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java deleted file mode 100644 index 311a627..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -public enum ExposeTarget { - STATIC(true, true, false), - MEMBER(false, false, true), - NAMESPACE(false, true, false), - CONSTRUCTOR(true, false, false), - PROTOTYPE(false, false, true), - ALL(true, true, true); - - public final boolean constructor; - public final boolean namespace; - public final boolean prototype; - - public boolean shouldApply(ExposeTarget other) { - if (other.constructor && !constructor) return false; - if (other.namespace && !namespace) return false; - if (other.prototype && !prototype) return false; - - return true; - } - - private ExposeTarget(boolean constructor, boolean namespace, boolean prototype) { - this.constructor = constructor; - this.namespace = namespace; - this.prototype = prototype; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java deleted file mode 100644 index 15e91a9..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -public enum ExposeType { - METHOD, - GETTER, - SETTER, -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java b/src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java deleted file mode 100644 index 3f7573a..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java +++ /dev/null @@ -1,456 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Copyable; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Key; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -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.Symbol; -import me.topchetoeu.jscript.runtime.values.Values; - -public class NativeWrapperProvider implements Copyable { - public static final Key KEY = new Key<>(); - - private final HashMap, FunctionValue> constructors = new HashMap<>(); - private final HashMap, ObjectValue> prototypes = new HashMap<>(); - private final HashMap, ObjectValue> namespaces = new HashMap<>(); - private final HashMap, Class> classToProxy = new HashMap<>(); - private final HashMap, Class> proxyToClass = new HashMap<>(); - private final HashMap, Class> interfaceToProxy = new HashMap<>(); - private final HashSet> ignore = new HashSet<>(); - - private static Object call(Context ctx, String name, Method method, Object thisArg, Object... args) { - try { - var realArgs = new Object[method.getParameterCount()]; - System.arraycopy(args, 0, realArgs, 0, realArgs.length); - if (Modifier.isStatic(method.getModifiers())) thisArg = null; - return Values.normalize(ctx, method.invoke(Values.convert(ctx, thisArg, method.getDeclaringClass()), realArgs)); - } - catch (InvocationTargetException e) { - if (e.getTargetException() instanceof EngineException) { - throw ((EngineException)e.getTargetException()).add(ctx, name, Location.INTERNAL); - } - else if (e.getTargetException() instanceof NullPointerException) { - // e.getTargetException().printStackTrace(); - throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, Location.INTERNAL); - } - else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) { - throw new InterruptException(); - } - else throw new EngineException(e.getTargetException()).add(ctx, name, Location.INTERNAL); - } - catch (ReflectiveOperationException e) { - throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL); - } - } - private static FunctionValue create(String name, Method method) { - return new NativeFunction(name, args -> call(args.ctx, name, method, args.self, args)); - } - private static void checkSignature(Method method, boolean forceStatic, Class ...params) { - if (forceStatic && !Modifier.isStatic(method.getModifiers())) throw new IllegalArgumentException(String.format( - "Method %s must be static.", - method.getDeclaringClass().getName() + "." + method.getName() - )); - - var actual = method.getParameterTypes(); - - boolean failed = actual.length > params.length; - - if (!failed) for (var i = 0; i < actual.length; i++) { - if (!actual[i].isAssignableFrom(params[i])) { - failed = true; - break; - } - } - - if (failed) throw new IllegalArgumentException(String.format( - "Method %s was expected to have a signature of '%s', found '%s' instead.", - method.getDeclaringClass().getName() + "." + method.getName(), - String.join(", ", Arrays.stream(params).map(v -> v.getName()).collect(Collectors.toList())), - String.join(", ", Arrays.stream(actual).map(v -> v.getName()).collect(Collectors.toList())) - )); - } - private static String getName(Class ...classes) { - String last = null; - - for (var clazz : classes) { - var classNat = clazz.getAnnotation(WrapperName.class); - if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim(); - else if (last != null) last = clazz.getSimpleName(); - } - - return last; - } - - private static void checkUnderscore(Member member) { - if (!member.getName().startsWith("__")) { - System.out.println(String.format("WARNING: The name of the exposed member '%s.%s' doesn't start with '__'.", - member.getDeclaringClass().getName(), - member.getName() - )); - } - } - private static String getName(Member member, String overrideName) { - if (overrideName == null) overrideName = ""; - if (overrideName.isBlank()) { - var res = member.getName(); - if (res.startsWith("__")) res = res.substring(2); - return res; - } - else return overrideName.trim(); - } - private static Object getKey(String name) { - if (name.startsWith("@@")) return Symbol.get(name.substring(2)); - else return name; - } - - private static boolean apply(ObjectValue obj, ExposeTarget target, Class clazz) { - var getters = new HashMap(); - var setters = new HashMap(); - var props = new HashSet(); - var nonProps = new HashSet(); - var any = false; - - for (var method : clazz.getDeclaredMethods()) { - for (var annotation : method.getAnnotationsByType(Expose.class)) { - any = true; - if (!annotation.target().shouldApply(target)) continue; - - checkUnderscore(method); - var name = getName(method, annotation.value()); - var key = getKey(name); - var repeat = false; - - switch (annotation.type()) { - case METHOD: - if (props.contains(key) || nonProps.contains(key)) repeat = true; - else { - checkSignature(method, false, Arguments.class); - obj.defineProperty(null, key, create(name, method), true, true, false); - nonProps.add(key); - } - break; - case GETTER: - if (nonProps.contains(key) || getters.containsKey(key)) repeat = true; - else { - checkSignature(method, false, Arguments.class); - getters.put(key, create(name, method)); - props.add(key); - } - break; - case SETTER: - if (nonProps.contains(key) || setters.containsKey(key)) repeat = true; - else { - checkSignature(method, false, Arguments.class); - setters.put(key, create(name, method)); - props.add(key); - } - break; - } - - if (repeat) - throw new IllegalArgumentException(String.format( - "A member '%s' in the wrapper for '%s' of type '%s' is already present.", - name, clazz.getName(), target.toString() - )); - } - for (var annotation : method.getAnnotationsByType(ExposeField.class)) { - any = true; - if (!annotation.target().shouldApply(target)) continue; - - checkUnderscore(method); - var name = getName(method, annotation.value()); - var key = getKey(name); - var repeat = false; - - if (props.contains(key) || nonProps.contains(key)) repeat = true; - else { - checkSignature(method, true); - try { - obj.defineProperty(null, key, method.invoke(null), true, true, false); - } - catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e); - } - nonProps.add(key); - } - - if (repeat) - throw new IllegalArgumentException(String.format( - "A member '%s' in the wrapper for '%s' of type '%s' is already present.", - name, clazz.getName(), target.toString() - )); - } - } - for (var field : clazz.getDeclaredFields()) { - for (var annotation : field.getAnnotationsByType(ExposeField.class)) { - any = true; - if (!annotation.target().shouldApply(target)) continue; - - checkUnderscore(field); - var name = getName(field, annotation.value()); - var key = getKey(name); - var repeat = false; - - if (props.contains(key) || nonProps.contains(key)) repeat = true; - else { - try { - if (Modifier.isStatic(field.getModifiers())) { - obj.defineProperty(null, key, Values.normalize(null, field.get(null)), true, true, false); - } - else { - obj.defineProperty( - null, key, - new NativeFunction("get " + key, args -> { - try { return field.get(args.self(clazz)); } - catch (IllegalAccessException e) { e.printStackTrace(); return null; } - }), - Modifier.isFinal(field.getModifiers()) ? null : new NativeFunction("get " + key, args -> { - try { field.set(args.self(clazz), args.convert(0, field.getType())); } - catch (IllegalAccessException e) { e.printStackTrace(); } - return null; - }), - true, false - ); - } - nonProps.add(key); - } - catch (IllegalArgumentException | IllegalAccessException e) { } - } - - if (repeat) - throw new IllegalArgumentException(String.format( - "A member '%s' in the wrapper for '%s' of type '%s' is already present.", - name, clazz.getName(), target.toString() - )); - } - } - - for (var key : props) obj.defineProperty(null, key, getters.get(key), setters.get(key), true, false); - - return any; - } - private static boolean apply(ObjectValue obj, ExposeTarget target, Class ...appliers) { - var res = false; - - for (var i = appliers.length - 1; i >= 0; i--) { - res |= apply(obj, target, appliers[i]); - } - - return res; - } - - private static Method getConstructor(Class ...appliers) { - for (var clazz : appliers) { - for (var method : clazz.getDeclaredMethods()) { - if (!method.isAnnotationPresent(ExposeConstructor.class)) continue; - checkSignature(method, true, Arguments.class); - return method; - } - } - - return null; - } - - /** - * Generates a prototype for the given class. - * The returned object will have appropriate wrappers for all instance members. - * All accessors and methods will expect the this argument to be a native wrapper of the given class type. - * @param clazz The class for which a prototype should be generated - */ - public static ObjectValue makeProto(Class ...appliers) { - var res = new ObjectValue(); - res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(appliers)); - if (!apply(res, ExposeTarget.PROTOTYPE, appliers)) return null; - return res; - } - /** - * Generates a constructor for the given class. - * The returned function will have appropriate wrappers for all static members. - * When the function gets called, the underlying constructor will get called, unless the constructor is inaccessible. - * @param clazz The class for which a constructor should be generated - */ - public static FunctionValue makeConstructor(Class ...appliers) { - var constr = getConstructor(appliers); - - FunctionValue res = constr == null ? - new NativeFunction(getName(appliers), args -> { throw EngineException.ofError("This constructor is not invokable."); }) : - create(getName(appliers), constr); - - if (!apply(res, ExposeTarget.CONSTRUCTOR, appliers) && constr == null) return null; - - return res; - } - /** - * Generates a namespace for the given class. - * The returned function will have appropriate wrappers for all static members. - * This method behaves almost like {@link NativeWrapperProvider#makeConstructor}, but will return an object instead. - * @param clazz The class for which a constructor should be generated - */ - public static ObjectValue makeNamespace(Class ...appliers) { - var res = new ObjectValue(); - - if (!apply(res, ExposeTarget.NAMESPACE, appliers)) return null; - - return res; - } - - private Class[] getAppliers(Class clazz) { - var res = new ArrayList>(); - - res.add(clazz); - - if (classToProxy.containsKey(clazz)) res.add(classToProxy.get(clazz)); - for (var intf : interfaceToProxy.keySet()) { - if (intf.isAssignableFrom(clazz)) res.add(interfaceToProxy.get(intf)); - } - - return res.toArray(Class[]::new); - } - - private void updateProtoChain(Class clazz, ObjectValue proto, FunctionValue constr) { - var parent = clazz; - - while (true) { - parent = parent.getSuperclass(); - if (parent == null) break; - - var parentProto = getProto(parent); - var parentConstr = getConstr(parent); - - if (parentProto != null && parentConstr != null) { - Values.setPrototype(Extensions.EMPTY, proto, parentProto); - Values.setPrototype(Extensions.EMPTY, constr, parentConstr); - - return; - } - } - } - - private void initType(Class clazz, FunctionValue constr, ObjectValue proto) { - if (constr != null && proto != null || ignore.contains(clazz)) return; - // i vomit - if ( - clazz == Object.class || - clazz == Void.class || - clazz == Number.class || clazz == Double.class || clazz == Float.class || - clazz == Long.class || clazz == Integer.class || clazz == Short.class || - clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || - clazz.isPrimitive() || - clazz.isArray() || - clazz.isAnonymousClass() || - clazz.isEnum() || - clazz.isInterface() || - clazz.isSynthetic() - ) return; - - var appliers = getAppliers(clazz); - - if (constr == null) constr = makeConstructor(appliers); - if (proto == null) proto = makeProto(appliers); - - if (constr == null || proto == null) return; - - proto.defineProperty(null, "constructor", constr, true, false, false); - constr.defineProperty(null, "prototype", proto, true, false, false); - - prototypes.put(clazz, proto); - constructors.put(clazz, constr); - - updateProtoChain(clazz, proto, constr); - } - - public ObjectValue getProto(Class clazz) { - if (proxyToClass.containsKey(clazz)) return getProto(proxyToClass.get(clazz)); - - initType(clazz, constructors.get(clazz), prototypes.get(clazz)); - while (clazz != null) { - var res = prototypes.get(clazz); - if (res != null) return res; - clazz = clazz.getSuperclass(); - } - return null; - } - public ObjectValue getNamespace(Class clazz) { - if (proxyToClass.containsKey(clazz)) return getNamespace(proxyToClass.get(clazz)); - - if (!namespaces.containsKey(clazz)) namespaces.put(clazz, makeNamespace(clazz)); - while (clazz != null) { - var res = namespaces.get(clazz); - if (res != null) return res; - clazz = clazz.getSuperclass(); - } - return null; - } - public FunctionValue getConstr(Class clazz) { - if (proxyToClass.containsKey(clazz)) return getConstr(proxyToClass.get(clazz)); - - initType(clazz, constructors.get(clazz), prototypes.get(clazz)); - while (clazz != null) { - var res = constructors.get(clazz); - if (res != null) return res; - clazz = clazz.getSuperclass(); - } - return null; - } - - public NativeWrapperProvider copy() { - var res = new NativeWrapperProvider(); - - for (var pair : classToProxy.entrySet()) { - res.set(pair.getKey(), pair.getValue()); - } - - return this; - } - - public void set(Class clazz, Class wrapper) { - if (clazz == null) return; - - if (clazz.isInterface()) { - if (wrapper == null || wrapper == clazz) interfaceToProxy.remove(clazz); - else interfaceToProxy.put(clazz, wrapper); - } - else { - if (wrapper == null || wrapper == clazz) classToProxy.remove(clazz); - else classToProxy.put(clazz, wrapper); - } - - var classes = Stream.concat( - Stream.of(clazz), - prototypes.keySet().stream().filter(clazz::isAssignableFrom) - ).toArray(Class[]::new); - - for (var el : classes) { - prototypes.remove(el); - constructors.remove(el); - namespaces.remove(el); - } - - for (var el : classes) { - initType(el, null, null); - } - } - - public NativeWrapperProvider() { } - - public static NativeWrapperProvider get(Extensions ext) { - return ext.get(KEY); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java b/src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java deleted file mode 100644 index 7b0e67d..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface OnWrapperInit { - -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java b/src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java deleted file mode 100644 index daf078c..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.topchetoeu.jscript.utils.interop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface WrapperName { - String value(); -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java b/src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java deleted file mode 100644 index a7523b3..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java +++ /dev/null @@ -1,109 +0,0 @@ -package me.topchetoeu.jscript.utils.mapping; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.json.JSON; - -public class SourceMap { - private final TreeMap origToComp = new TreeMap<>(); - private final TreeMap compToOrig = new TreeMap<>(); - - public Location toCompiled(Location loc) { return convert(loc, origToComp); } - public Location toOriginal(Location loc) { return convert(loc, compToOrig); } - - private void add(long orig, long comp) { - var a = origToComp.remove(orig); - var b = compToOrig.remove(comp); - - if (b != null) origToComp.remove(b); - if (a != null) compToOrig.remove(a); - - origToComp.put(orig, comp); - compToOrig.put(comp, orig); - } - - public SourceMap apply(SourceMap map) { - var res = new SourceMap(); - - for (var el : new ArrayList<>(origToComp.entrySet())) { - var mapped = convert(el.getValue(), map.origToComp); - res.origToComp.put(el.getKey(), mapped); - } - for (var el : new ArrayList<>(compToOrig.entrySet())) { - var mapped = convert(el.getKey(), map.compToOrig); - res.compToOrig.put(mapped, el.getValue()); - res.add(el.getValue(), mapped); - } - - return res; - } - - public SourceMap clone() { - var res = new SourceMap(); - res.origToComp.putAll(this.origToComp); - res.compToOrig.putAll(this.compToOrig); - return res; - } - - public static SourceMap parse(String raw) { - var mapping = VLQ.decodeMapping(raw); - var res = new SourceMap(); - - var compRow = 0l; - var compCol = 0l; - - for (var origRow = 0; origRow < mapping.length; origRow++) { - var origCol = 0; - - for (var rawSeg : mapping[origRow]) { - if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files."); - origCol += rawSeg.length > 0 ? rawSeg[0] : 0; - compRow += rawSeg.length > 2 ? rawSeg[2] : 0; - compCol += rawSeg.length > 3 ? rawSeg[3] : 0; - - var compPacked = ((long)compRow << 32) | compCol; - var origPacked = ((long)origRow << 32) | origCol; - - res.add(origPacked, compPacked); - } - } - - return res; - } - public static List getSources(String raw) { - var json = JSON.parse(null, raw).map(); - return json - .list("sourcesContent") - .stream() - .map(v -> v.string()) - .collect(Collectors.toList()); - } - - public static SourceMap chain(SourceMap ...maps) { - if (maps.length == 0) return null; - var res = maps[0]; - - for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]); - - return res; - } - - private static Long convert(long packed, TreeMap map) { - if (map.containsKey(packed)) return map.get(packed); - var key = map.floorKey(packed); - if (key == null) return null; - else return map.get(key); - } - - private static Location convert(Location loc, TreeMap map) { - var packed = ((loc.line() - 1l) << 32) | (loc.start() - 1); - var resPacked = convert(packed, map); - - if (resPacked == null) return null; - else return new Location((int)(resPacked >> 32) + 1, (int)(resPacked & 0xFFFF) + 1, loc.filename()); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java b/src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java deleted file mode 100644 index 5f49aea..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java +++ /dev/null @@ -1,95 +0,0 @@ -package me.topchetoeu.jscript.utils.mapping; - -import java.util.ArrayList; -import java.util.List; - -public class VLQ { - private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - private static long[] toArray(List list) { - var arr = new long[list.size()]; - for (var i = 0; i < list.size(); i++) arr[i] = list.get(i); - return arr; - } - - public static String encode(long... arr) { - var raw = new StringBuilder(); - - for (var data : arr) { - var b = data < 0 ? 1 : 0; - data = Math.abs(data); - b |= (int)(data & 0b1111) << 1; - data >>= 4; - b |= data > 0 ? 0x20 : 0;; - raw.append(ALPHABET.charAt(b)); - - while (data > 0) { - b = (int)(data & 0b11111); - data >>= 5; - b |= data > 0 ? 0x20 : 0; - raw.append(ALPHABET.charAt(b)); - } - } - - return raw.toString(); - } - public static long[] decode(String val) { - if (val.length() == 0) return new long[0]; - - var list = new ArrayList(); - - for (var i = 0; i < val.length();) { - var sign = 1; - var curr = ALPHABET.indexOf(val.charAt(i++)); - var cont = (curr & 0x20) == 0x20; - if ((curr & 1) == 1) sign = -1; - long res = (curr & 0b11110) >> 1; - var n = 4; - - for (; i < val.length() && cont;) { - curr = ALPHABET.indexOf(val.charAt(i++)); - cont = (curr & 0x20) == 0x20; - res |= (curr & 0b11111) << n; - n += 5; - if (!cont) break; - } - - list.add(res * sign); - } - - return toArray(list); - } - - public static String encodeMapping(long[][][] arr) { - var res = new StringBuilder(); - var semicolon = false; - - for (var line : arr) { - var comma = false; - - if (semicolon) res.append(";"); - semicolon = true; - - for (var el : line) { - if (comma) res.append(","); - comma = true; - res.append(encode(el)); - } - } - - return res.toString(); - } - public static long[][][] decodeMapping(String val) { - var lines = new ArrayList(); - - for (var line : val.split(";", -1)) { - var elements = new ArrayList(); - for (var el : line.split(",", -1)) { - elements.add(decode(el)); - } - lines.add(elements.toArray(long[][]::new)); - } - - return lines.toArray(long[][][]::new); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/Module.java b/src/main/java/me/topchetoeu/jscript/utils/modules/Module.java deleted file mode 100644 index ad84d6c..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/modules/Module.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.topchetoeu.jscript.utils.modules; - -import me.topchetoeu.jscript.runtime.Context; - -public abstract class Module { - private Object value; - private boolean loaded; - - public Object value() { return value; } - public boolean loaded() { return loaded; } - - protected abstract Object onLoad(Context ctx); - - public void load(Context ctx) { - if (loaded) return; - this.value = onLoad(ctx); - this.loaded = true; - } -} - diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java b/src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java deleted file mode 100644 index 1138485..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.topchetoeu.jscript.utils.modules; - -import java.util.HashMap; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Key; -import me.topchetoeu.jscript.runtime.scope.GlobalScope; -import me.topchetoeu.jscript.utils.filesystem.Filesystem; -import me.topchetoeu.jscript.utils.filesystem.Mode; - -public interface ModuleRepo { - public static final Key KEY = new Key<>(); - public static final Key CWD = new Key<>(); - - public Module getModule(Context ctx, String cwd, String name); - - public static ModuleRepo ofFilesystem(Filesystem fs) { - var modules = new HashMap(); - - return (ctx, cwd, name) -> { - name = fs.normalize(cwd, name); - var filename = Filename.parse(name); - var src = fs.open(name, Mode.READ).readToString(); - - if (modules.containsKey(name)) return modules.get(name); - - var env = Context.clean(ctx.extensions).child(); - env.add(CWD, fs.normalize(name, "..")); - var glob = env.get(GlobalScope.KEY); - env.add(GlobalScope.KEY, glob.child()); - - var mod = new SourceModule(filename, src, env); - modules.put(name, mod); - - return mod; - }; - } - - public static String cwd(Extensions exts) { - return exts.init(CWD, "/"); - } - public static ModuleRepo get(Extensions exts) { - return exts.get(KEY); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java b/src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java deleted file mode 100644 index 6e9ca40..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.topchetoeu.jscript.utils.modules; - -import java.util.HashMap; - -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; - -public class RootModuleRepo implements ModuleRepo { - public final HashMap repos = new HashMap<>(); - - @Override - public Module getModule(Context ctx, String cwd, String name) { - var i = name.indexOf(":"); - String repoName, modName; - - if (i < 0) { - repoName = "file"; - modName = name; - } - else { - repoName = name.substring(0, i); - modName = name.substring(i + 1); - } - - var repo = repos.get(repoName); - if (repo == null) throw EngineException.ofError("ModuleError", "Couldn't find module repo '" + repoName + "'."); - - return repo.getModule(ctx, cwd, modName); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java b/src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java deleted file mode 100644 index 4efd72a..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.topchetoeu.jscript.utils.modules; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.runtime.Context; -import me.topchetoeu.jscript.runtime.Extensions; - -public class SourceModule extends Module { - public final Filename filename; - public final String source; - public final Extensions ext; - - @Override - protected Object onLoad(Context ctx) { - var res = new Context(ext).compile(filename, source); - return res.call(ctx); - } - - public SourceModule(Filename filename, String source, Extensions ext) { - this.filename = filename; - this.source = source; - this.ext = ext; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java deleted file mode 100644 index fc1138c..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java +++ /dev/null @@ -1,69 +0,0 @@ -package me.topchetoeu.jscript.utils.permissions; - -import java.util.LinkedList; - -public interface Matcher { - static class State { - public final int predI, trgI, wildcardI; - public final boolean wildcard; - - @Override - public String toString() { - return String.format("State [pr=%s;trg=%s;wildN=%s;wild=%s]", predI, trgI, wildcardI, wildcard); - } - - public State(int predicateI, int targetI, int wildcardI, boolean wildcard) { - this.predI = predicateI; - this.trgI = targetI; - this.wildcardI = wildcardI; - this.wildcard = wildcard; - } - } - - boolean match(String predicate, String value); - - public static Matcher fileWildcard() { - return (predicate, value) -> execWildcard(predicate, value, '/'); - } - public static Matcher namespaceWildcard() { - return (predicate, value) -> execWildcard(predicate, value, '.'); - } - public static Matcher wildcard() { - return (predicate, value) -> execWildcard(predicate, value, '\0'); - } - - public static boolean execWildcard(String predicate, String target, char delim) { - if (predicate.equals("")) return target.equals(""); - - var queue = new LinkedList(); - queue.push(new State(0, 0, 0, false)); - - while (!queue.isEmpty()) { - var state = queue.poll(); - var predEnd = state.predI >= predicate.length(); - - if (state.trgI >= target.length()) return predEnd; - var predC = predEnd ? 0 : predicate.charAt(state.predI); - var trgC = target.charAt(state.trgI); - - if (state.wildcard) { - if (state.wildcardI == 2 || trgC != delim) { - queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); - } - queue.add(new State(state.predI, state.trgI, 0, false)); - } - else if (predC == '*') { - queue.add(new State(state.predI + 1, state.trgI, state.wildcardI + 1, false)); - } - else if (state.wildcardI > 0) { - if (state.wildcardI > 2) throw new IllegalArgumentException("Too many sequential stars."); - queue.add(new State(state.predI, state.trgI, state.wildcardI, true)); - } - else if (!predEnd && (predC == '?' || predC == trgC)) { - queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); - } - } - - return false; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java deleted file mode 100644 index f120500..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.utils.permissions; - - -public class Permission { - public final String namespace; - public final Matcher matcher; - - @Override public String toString() { - return namespace; - } - - public Permission(String namespace, Matcher matcher) { - this.namespace = namespace; - this.matcher = matcher; - } - public Permission(String raw) { - this(raw, null); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java deleted file mode 100644 index 5bedf8d..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.topchetoeu.jscript.utils.permissions; - -public class PermissionPredicate { - public final String namespace; - public final String value; - public final boolean denies; - - public boolean match(Permission permission, String value) { - if (!match(permission)) return false; - if (this.value == null || value == null) return true; - if (permission.matcher == null) return true; - else return permission.matcher.match(this.value, value); - } - public boolean match(Permission permission) { - return Matcher.namespaceWildcard().match(namespace, permission.namespace); - } - - @Override - public String toString() { - if (value != null) return namespace + ":" + value; - else return namespace; - } - - public PermissionPredicate(String raw) { - raw = raw.trim(); - - if (raw.startsWith("!")) { - denies = true; - raw = raw.substring(1).trim(); - } - else denies = false; - - var i = raw.indexOf(':'); - - if (i > 0) { - value = raw.substring(i + 1); - namespace = raw.substring(0, i); - } - else { - value = null; - namespace = raw; - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java deleted file mode 100644 index 72468cc..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java +++ /dev/null @@ -1,59 +0,0 @@ -package me.topchetoeu.jscript.utils.permissions; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; - -public class PermissionsManager implements PermissionsProvider { - public final ArrayList predicates = new ArrayList<>(); - - public PermissionsProvider add(PermissionPredicate perm) { - predicates.add(perm); - return this; - } - public PermissionsProvider add(String perm) { - predicates.add(new PermissionPredicate(perm)); - return this; - } - - @Override public boolean hasPermission(Permission perm, String value) { - for (var el : predicates) { - if (el.match(perm, value)) { - if (el.denies) return false; - else return true; - } - } - - return false; - } - @Override public boolean hasPermission(Permission perm) { - for (var el : predicates) { - if (el.match(perm)) { - if (el.denies) return false; - else return true; - } - } - - return false; - } - - public PermissionsProvider addFromStream(InputStream stream) throws IOException { - var reader = new BufferedReader(new InputStreamReader(stream)); - String line; - - while ((line = reader.readLine()) != null) { - var i = line.indexOf('#'); - if (i >= 0) line = line.substring(0, i); - - line = line.trim(); - - if (line.isEmpty()) continue; - - add(line); - } - - return this; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java deleted file mode 100644 index 53112b3..0000000 --- a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.topchetoeu.jscript.utils.permissions; - -import me.topchetoeu.jscript.runtime.Extensions; -import me.topchetoeu.jscript.runtime.Key; - -public interface PermissionsProvider { - public static final Key KEY = new Key<>(); - public static final PermissionsProvider ALL_PERMS = (perm, value) -> true; - - boolean hasPermission(Permission perm, String value); - - default boolean hasPermission(Permission perm) { - return hasPermission(perm, null); - } - - default boolean hasPermission(String perm, String value, Matcher matcher) { - return hasPermission(new Permission(perm, matcher), value); - } - default boolean hasPermission(String perm, Matcher matcher) { - return hasPermission(new Permission(perm, matcher)); - } - - public static PermissionsProvider get(Extensions exts) { - return (perm, value) -> { - if (exts.hasNotNull(KEY)) return exts.get(KEY).hasPermission(perm); - else return true; - }; - } -} \ No newline at end of file diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js new file mode 100644 index 0000000..c807527 --- /dev/null +++ b/src/main/resources/lib/index.js @@ -0,0 +1,313 @@ +const target = arguments[0]; +const primordials = arguments[1]; + +const makeSymbol = primordials.symbol.makeSymbol; +const getSymbol = primordials.symbol.getSymbol; +const getSymbolKey = primordials.symbol.getSymbolKey; +const getSymbolDescription = primordials.symbol.getSymbolDescription; + +const parseInt = primordials.number.parseInt; +const parseFloat = primordials.number.parseFloat; +const isNaN = primordials.number.isNaN; +const NaN = primordials.number.NaN; +const Infinity = primordials.number.Infinity; + +const fromCharCode = primordials.string.fromCharCode; +const fromCodePoint = primordials.string.fromCodePoint; +const stringBuild = primordials.string.stringBuild; + +const defineProperty = primordials.object.defineProperty; +const defineField = primordials.object.defineField; +const getOwnMember = primordials.object.getMember; +const getOwnSymbolMember = primordials.object.getOwnSymbolMember; +const getOwnMembers = primordials.object.getOwnMembers; +const getOwnSymbolMembers = primordials.object.getOwnSymbolMembers; +const getPrototype = primordials.object.getPrototype; +const setPrototype = primordials.object.setPrototype; + +const invokeType = primordials.function.invokeType; +const setConstructable = primordials.function.setConstructable; +const setCallable = primordials.function.setCallable; +const invoke = primordials.function.invoke; + +const setGlobalPrototype = primordials.setGlobalPrototype; +const compile = primordials.compile; + +const json = primordials.json; + +const valueKey = makeSymbol("Primitive.value"); +const undefined = ({}).definitelyDefined; + +target.undefined = undefined; + +const unwrapThis = (self, type, constr, name, arg, defaultVal) => { + if (arg == null) arg = "this"; + if (typeof self === type) return self; + if (self instanceof constr && valueKey in self) self = self[valueKey]; + if (typeof self === type) return self; + if (defaultVal !== undefined) return defaultVal; + + throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name); +} + +const wrapIndex = (i, len) => {}; + +const Symbol = (name = "") => makeSymbol(name); + +defineField(Symbol, "for", true, false, true, function(name) { + return getSymbol(name + ""); +}); +defineField(Symbol, "keyFor", true, false, true, function(symbol) { + return getSymbolKey(unwrapThis(symbol, "symbol", Symbol, "Symbol.keyFor")); +}); + +defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); +defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator")); +defineField(Symbol, "match", false, false, false, Symbol("Symbol.match")); +defineField(Symbol, "matchAll", false, false, false, Symbol("Symbol.matchAll")); +defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace")); +defineField(Symbol, "search", false, false, false, Symbol("Symbol.search")); +defineField(Symbol, "split", false, false, false, Symbol("Symbol.split")); +defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag")); +defineField(Symbol, "prototype", false, false, false, {}); + +defineProperty(Symbol.prototype, "description", false, true, function () { + return getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); +}, undefined); +defineField(Symbol.prototype, "toString", true, false, true, function() { + return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; +}); +defineField(Symbol.prototype, "valueOf", true, false, true, function() { + return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); +}); + +target.Symbol = Symbol; + +const Number = function(value) { + if (invokeType(arguments) === "call") { + if (arguments.length === 0) return 0; + else return +value; + } + + this[valueKey] = target.Number(value); +}; + +defineField(Number, "isFinite", true, false, true, function(value) { + value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); + + if (value === undefined || isNaN(value)) return false; + if (value === Infinity || value === -Infinity) return false; + + return true; +}); +defineField(Number, "isInteger", true, false, true, function(value) { + value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); + if (value === undefined) return false; + return parseInt(value) === value; +}); +defineField(Number, "isNaN", true, false, true, function(value) { + return isNaN(value); +}); +defineField(Number, "isSafeInteger", true, false, true, function(value) { + value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); + if (value === undefined || parseInt(value) !== value) return false; + return value >= -9007199254740991 && value <= 9007199254740991; +}); +defineField(Number, "parseFloat", true, false, true, function(value) { + value = 0 + value; + return parseFloat(value); +}); +defineField(Number, "parseInt", true, false, true, function(value, radix) { + value = 0 + value; + radix = +radix; + if (isNaN(radix)) radix = 10; + + return parseInt(value, radix); +}); + +defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); +defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); +defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); +defineField(Number, "POSITIVE_INFINITY", false, false, false, +Infinity); +defineField(Number, "NEGATIVE_INFINITY", false, false, false, -Infinity); +defineField(Number, "NaN", false, false, false, NaN); +defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); +defineField(Number, "MIN_VALUE", false, false, false, 5e-324); +defineField(Number, "prototype", false, false, false, {}); + +defineField(Number.prototype, "toString", true, false, true); +defineField(Number.prototype, "toString", true, false, true, function() { + return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); +}); +defineField(Number.prototype, "valueOf", true, false, true, function() { + return unwrapThis(this, "number", Number, "Number.prototype.toString"); +}); + +target.Number = Number; + +const String = function(value) { + if (invokeType(arguments) === "call") { + if (arguments.length === 0) return ""; + else return value + ""; + } + + this[valueKey] = String(value); +}; + +defineField(String, "fromCharCode", true, false, true, function() { + const res = []; + res[arguments.length] = 0; + + for (let i = 0; i < arguments.length; i++) { + res[i] = fromCharCode(+arguments[i]); + } + + return stringBuild(res); +}); +defineField(String, "fromCodePoint", true, false, true, function() { + const res = []; + res[arguments.length] = 0; + + for (let i = 0; i < arguments.length; i++) { + res[i] = fromCodePoint(+arguments[i]); + } + + return stringBuild(res); +}); + +defineField(String, "prototype", false, false, false, {}); + +defineField(String.prototype, "at", true, false, true, function(index) { + throw "Not implemented :/"; + return unwrapThis(this, "string", String, "String.prototype.at")[index]; +}); +defineField(String.prototype, "toString", true, false, true, function() { + return unwrapThis(this, "string", String, "String.prototype.toString"); +}); +defineField(String.prototype, "valueOf", true, false, true, function() { + return unwrapThis(this, "string", String, "String.prototype.valueOf"); +}); + +target.String = String; + +const Boolean = function(value) { + if (invokeType(arguments) === "call") { + if (arguments.length === 0) return false; + else return !!value; + } + + this[valueKey] = Boolean(value); +}; + +defineField(Boolean, "prototype", false, false, false, {}); + +defineField(Boolean.prototype, "toString", true, false, true, function() { + return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); +}); +defineField(Boolean.prototype, "valueOf", true, false, true, function() { + return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); +}); + +target.Boolean = Boolean; + +const Object = function(value) { + if (typeof value === 'object' && value !== null) return value; + + if (typeof value === 'string') return new String(value); + if (typeof value === 'number') return new Number(value); + if (typeof value === 'boolean') return new Boolean(value); + if (typeof value === 'symbol') { + const res = {}; + setPrototype(res, Symbol.prototype); + res[valueKey] = value; + return res; + } + + const target = this; + if (target == null || typeof target !== 'object') target = {}; + + this[valueKey] = Object(value); +}; + +defineField(Object, "prototype", false, false, false, setPrototype({}, null)); + +defineField(Object.prototype, "toString", true, false, true, function() { + if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; + else if (typeof this === "number" || this instanceof Number) return "[object Number]"; + else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; + else if (typeof this === "string" || this instanceof String) return "[object String]"; + else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; + else if (typeof this === "function") return "[object Function]"; + else return "[object Object]"; +}); +defineField(Object.prototype, "valueOf", true, false, true, function() { + return this; +}); + +target.Boolean = Boolean; + +const Function = function() { + const parts = ["return function annonymous("]; + + for (let i = 0; i < arguments.length - 1; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = arguments[i]; + } + parts[parts.length] = "){\n"; + parts[parts.length] = String(arguments[arguments.length - 1]); + parts[parts.length] = "\n}"; + + print(parts); + + const res = compile(stringBuild(parts))(); + return res; +}; + +defineField(Function, "compile", true, false, true, (src = "", options = {}) => { + if (options.globals == null) options.globals = []; + if (options.wrap == null) options.wrap = true; + + const parts = []; + + if (options.wrap) parts[parts.length] = "return (function() {\n"; + if (options.globals.length > 0) { + parts[parts.length] = "var "; + + for (let i = 0; i < options.globals.length; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = options.globals[i]; + } + + parts[parts.length] = ";((g=arguments[0])=>{"; + + for (let i = 0; i < options.globals.length; i++) { + const name = options.globals[i]; + parts[parts.length] = name + "=g[" + json.stringify(name) + "];"; + } + + parts[parts.length] = "})()\n"; + } + + parts[parts.length] = src; + if (options.wrap) parts[parts.length] = "\n})(arguments[0])"; + + const res = compile(stringBuild(parts)); + return res; +}); +defineField(Function, "prototype", false, false, false, setPrototype({}, null)); + +defineField(Function.prototype, "toString", true, false, true, function() { + if (this.name !== "") return "function " + this.name + "(...) { ... }"; + else return "function (...) { ... }"; +}); +defineField(Function.prototype, "valueOf", true, false, true, function() { + return this; +}); + +target.Function = Function; + +setGlobalPrototype("string", String.prototype); +setGlobalPrototype("number", Number.prototype); +setGlobalPrototype("boolean", Boolean.prototype); +setGlobalPrototype("symbol", Symbol.prototype); +setGlobalPrototype("object", Object.prototype); \ No newline at end of file diff --git a/src/test/java/me/topchetoeu/jscript/TestHelloWorld.java b/src/main/test/java/me/topchetoeu/jscript/TestHelloWorld.java similarity index 100% rename from src/test/java/me/topchetoeu/jscript/TestHelloWorld.java rename to src/main/test/java/me/topchetoeu/jscript/TestHelloWorld.java