change indentation to tabs
This commit is contained in:
parent
caf44a50e5
commit
db45eb529d
@ -1,58 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
|
||||||
|
|
||||||
public class Buffer {
|
|
||||||
private byte[] data;
|
|
||||||
private int length;
|
|
||||||
|
|
||||||
public void write(int i, byte[] val) {
|
|
||||||
if (i + val.length > data.length) {
|
|
||||||
var newCap = i + val.length + 1;
|
|
||||||
if (newCap < data.length * 2) newCap = data.length * 2;
|
|
||||||
|
|
||||||
var tmp = new byte[newCap];
|
|
||||||
System.arraycopy(this.data, 0, tmp, 0, length);
|
|
||||||
this.data = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.arraycopy(val, 0, data, i, val.length);
|
|
||||||
if (i + val.length > length) length = i + val.length;
|
|
||||||
}
|
|
||||||
public int read(int i, byte[] buff) {
|
|
||||||
int n = buff.length;
|
|
||||||
if (i + n > length) n = length - i;
|
|
||||||
System.arraycopy(data, i, buff, 0, n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
data = new byte[128];
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(byte b) {
|
|
||||||
write(length, new byte[] { b });
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] data() {
|
|
||||||
var res = new byte[length];
|
|
||||||
System.arraycopy(this.data, 0, res, 0, length);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
public int length() {
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Buffer(byte[] data) {
|
|
||||||
this.data = new byte[data.length];
|
|
||||||
this.length = data.length;
|
|
||||||
System.arraycopy(data, 0, this.data, 0, data.length);
|
|
||||||
}
|
|
||||||
public Buffer(int capacity) {
|
|
||||||
this.data = new byte[capacity];
|
|
||||||
this.length = 0;
|
|
||||||
}
|
|
||||||
public Buffer() {
|
|
||||||
this.data = new byte[128];
|
|
||||||
this.length = 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +1,16 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
public class FunctionBody {
|
public class FunctionBody {
|
||||||
public final FunctionBody[] children;
|
public final FunctionBody[] children;
|
||||||
public final Instruction[] instructions;
|
public final Instruction[] instructions;
|
||||||
public final int localsN, capturablesN, capturesN, length;
|
public final int localsN, capturablesN, capturesN, length;
|
||||||
|
|
||||||
public FunctionBody(int localsN, int capturablesN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) {
|
public FunctionBody(int localsN, int capturablesN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) {
|
||||||
this.children = children;
|
this.children = children;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.localsN = localsN;
|
this.localsN = localsN;
|
||||||
this.capturablesN = capturablesN;
|
this.capturablesN = capturablesN;
|
||||||
this.capturesN = capturesN;
|
this.capturesN = capturesN;
|
||||||
this.instructions = instructions;
|
this.instructions = instructions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,233 +7,115 @@ import java.util.function.IntSupplier;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public class Instruction {
|
public class Instruction {
|
||||||
public static enum Type {
|
public static enum Type {
|
||||||
RETURN(0x00),
|
RETURN(0x00),
|
||||||
NOP(0x01),
|
NOP(0x01),
|
||||||
THROW(0x02),
|
THROW(0x02),
|
||||||
THROW_SYNTAX(0x03),
|
THROW_SYNTAX(0x03),
|
||||||
DELETE(0x04),
|
DELETE(0x04),
|
||||||
TRY_START(0x05),
|
TRY_START(0x05),
|
||||||
TRY_END(0x06),
|
TRY_END(0x06),
|
||||||
|
|
||||||
CALL(0x10),
|
CALL(0x10),
|
||||||
CALL_NEW(0x12),
|
CALL_NEW(0x12),
|
||||||
JMP_IF(0x18),
|
JMP_IF(0x18),
|
||||||
JMP_IFN(0x19),
|
JMP_IFN(0x19),
|
||||||
JMP(0x1A),
|
JMP(0x1A),
|
||||||
|
|
||||||
PUSH_UNDEFINED(0x20),
|
PUSH_UNDEFINED(0x20),
|
||||||
PUSH_NULL(0x21),
|
PUSH_NULL(0x21),
|
||||||
PUSH_BOOL(0x22),
|
PUSH_BOOL(0x22),
|
||||||
PUSH_NUMBER(0x23),
|
PUSH_NUMBER(0x23),
|
||||||
PUSH_STRING(0x24),
|
PUSH_STRING(0x24),
|
||||||
DUP(0x25),
|
DUP(0x25),
|
||||||
DISCARD(0x26),
|
DISCARD(0x26),
|
||||||
|
|
||||||
LOAD_FUNC(0x30),
|
LOAD_FUNC(0x30),
|
||||||
LOAD_ARR(0x31),
|
LOAD_ARR(0x31),
|
||||||
LOAD_OBJ(0x32),
|
LOAD_OBJ(0x32),
|
||||||
LOAD_REGEX(0x33),
|
LOAD_REGEX(0x33),
|
||||||
|
|
||||||
LOAD_GLOB(0x38),
|
LOAD_GLOB(0x38),
|
||||||
LOAD_INTRINSICS(0x39),
|
LOAD_INTRINSICS(0x39),
|
||||||
LOAD_ARG(0x3A),
|
LOAD_ARG(0x3A),
|
||||||
LOAD_ARGS_N(0x3B),
|
LOAD_ARGS_N(0x3B),
|
||||||
LOAD_ARGS(0x3C),
|
LOAD_ARGS(0x3C),
|
||||||
LOAD_CALLED(0x3D),
|
LOAD_CALLED(0x3D),
|
||||||
LOAD_THIS(0x3E),
|
LOAD_THIS(0x3E),
|
||||||
LOAD_ERROR(0x3F),
|
LOAD_ERROR(0x3F),
|
||||||
|
|
||||||
LOAD_VAR(0x40),
|
LOAD_VAR(0x40),
|
||||||
LOAD_MEMBER(0x41),
|
LOAD_MEMBER(0x41),
|
||||||
LOAD_MEMBER_INT(0x42),
|
LOAD_MEMBER_INT(0x42),
|
||||||
LOAD_MEMBER_STR(0x43),
|
LOAD_MEMBER_STR(0x43),
|
||||||
|
|
||||||
STORE_VAR(0x48),
|
STORE_VAR(0x48),
|
||||||
STORE_MEMBER(0x49),
|
STORE_MEMBER(0x49),
|
||||||
STORE_MEMBER_INT(0x4A),
|
STORE_MEMBER_INT(0x4A),
|
||||||
STORE_MEMBER_STR(0x4B),
|
STORE_MEMBER_STR(0x4B),
|
||||||
|
|
||||||
DEF_PROP(0x50),
|
DEF_PROP(0x50),
|
||||||
DEF_FIELD(0x51),
|
DEF_FIELD(0x51),
|
||||||
KEYS(0x52),
|
KEYS(0x52),
|
||||||
TYPEOF(0x53),
|
TYPEOF(0x53),
|
||||||
OPERATION(0x54),
|
OPERATION(0x54),
|
||||||
|
|
||||||
GLOB_GET(0x60),
|
GLOB_GET(0x60),
|
||||||
GLOB_SET(0x61),
|
GLOB_SET(0x61),
|
||||||
GLOB_DEF(0x62);
|
GLOB_DEF(0x62);
|
||||||
|
|
||||||
private static final HashMap<Integer, Type> types = new HashMap<>();
|
private static final HashMap<Integer, Type> types = new HashMap<>();
|
||||||
public final int numeric;
|
public final int numeric;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (var val : Type.values()) types.put(val.numeric, val);
|
for (var val : Type.values()) types.put(val.numeric, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Type(int numeric) {
|
private Type(int numeric) {
|
||||||
this.numeric = numeric;
|
this.numeric = numeric;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type fromNumeric(int i) {
|
public static Type fromNumeric(int i) {
|
||||||
return types.get(i);
|
return types.get(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static enum BreakpointType {
|
public static enum BreakpointType {
|
||||||
/**
|
/**
|
||||||
* A debugger should never stop at such instruction, unless a breakpoint has been set on it
|
* A debugger should never stop at such instruction, unless a breakpoint has been set on it
|
||||||
*/
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
/**
|
/**
|
||||||
* Debuggers should pause at instructions marked with this breakpoint type
|
* Debuggers should pause at instructions marked with this breakpoint type
|
||||||
* after any step command
|
* after any step command
|
||||||
*/
|
*/
|
||||||
STEP_OVER,
|
STEP_OVER,
|
||||||
/**
|
/**
|
||||||
* Debuggers should pause at instructions marked with this breakpoint type
|
* Debuggers should pause at instructions marked with this breakpoint type
|
||||||
* only after a step-in command
|
* only after a step-in command
|
||||||
*/
|
*/
|
||||||
STEP_IN;
|
STEP_IN;
|
||||||
|
|
||||||
public boolean shouldStepIn() {
|
public boolean shouldStepIn() {
|
||||||
return this != NONE;
|
return this != NONE;
|
||||||
}
|
}
|
||||||
public boolean shouldStepOver() {
|
public boolean shouldStepOver() {
|
||||||
return this == STEP_OVER;
|
return this == STEP_OVER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Type type;
|
public final Type type;
|
||||||
public final Object[] params;
|
public final Object[] params;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T get(int i) {
|
public <T> T get(int i) {
|
||||||
if (i >= params.length || i < 0) return null;
|
if (i >= params.length || i < 0) return null;
|
||||||
return (T)params[i];
|
return (T)params[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// public void write(DataOutputStream writer) throws IOException {
|
private Instruction(Type type, Object ...params) {
|
||||||
// var rawType = type.numeric;
|
this.type = type;
|
||||||
|
this.params = params;
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the start of a protected context
|
* Signals the start of a protected context
|
||||||
@ -241,257 +123,254 @@ public class Instruction {
|
|||||||
* @param finallyStart The point to witch to jump after either the try or catch bodies have exited
|
* @param finallyStart The point to witch to jump after either the try or catch bodies have exited
|
||||||
* @param end The point to which to jump after exiting the whole protected context
|
* @param end The point to which to jump after exiting the whole protected context
|
||||||
*/
|
*/
|
||||||
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
|
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
|
||||||
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
|
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Signifies that the current protected section (try, catch or finally) has ended
|
* Signifies that the current protected section (try, catch or finally) has ended
|
||||||
*/
|
*/
|
||||||
public static Instruction tryEnd() {
|
public static Instruction tryEnd() {
|
||||||
return new Instruction(Type.TRY_END);
|
return new Instruction(Type.TRY_END);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Throws the top stack value
|
* Throws the top stack value
|
||||||
*/
|
*/
|
||||||
public static Instruction throwInstr() {
|
public static Instruction throwInstr() {
|
||||||
return new Instruction(Type.THROW);
|
return new Instruction(Type.THROW);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Converts the given exception to a runtime syntax error and throws it
|
* Converts the given exception to a runtime syntax error and throws it
|
||||||
*/
|
*/
|
||||||
public static Instruction throwSyntax(SyntaxException err) {
|
public static Instruction throwSyntax(SyntaxException err) {
|
||||||
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
|
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Converts the given exception to a runtime syntax error and throws it
|
* Converts the given exception to a runtime syntax error and throws it
|
||||||
*/
|
*/
|
||||||
public static Instruction throwSyntax(String err) {
|
public static Instruction throwSyntax(String err) {
|
||||||
return new Instruction(Type.THROW_SYNTAX, err);
|
return new Instruction(Type.THROW_SYNTAX, err);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Converts the given exception to a runtime syntax error and throws it
|
* Converts the given exception to a runtime syntax error and throws it
|
||||||
*/
|
*/
|
||||||
public static Instruction throwSyntax(Location loc, String err) {
|
public static Instruction throwSyntax(Location loc, String err) {
|
||||||
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
|
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Performs a JS object property deletion.
|
* Performs a JS object property deletion.
|
||||||
* Operands:
|
* Operands:
|
||||||
* 1. Object to manipulate
|
* 1. Object to manipulate
|
||||||
* 2. Key to delete
|
* 2. Key to delete
|
||||||
*/
|
*/
|
||||||
public static Instruction delete() {
|
public static Instruction delete() {
|
||||||
return new Instruction(Type.DELETE);
|
return new Instruction(Type.DELETE);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns the top stack value
|
* Returns the top stack value
|
||||||
*/
|
*/
|
||||||
public static Instruction ret() {
|
public static Instruction ret() {
|
||||||
return new Instruction(Type.RETURN);
|
return new Instruction(Type.RETURN);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* A special NOP instruction telling any debugger to pause
|
* A special NOP instruction telling any debugger to pause
|
||||||
*/
|
*/
|
||||||
public static Instruction debug() {
|
public static Instruction debug() {
|
||||||
return new Instruction(Type.NOP, "debug");
|
return new Instruction(Type.NOP, "debug");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does nothing. May be used for metadata or implementation-specific instructions that don't alter the behavior
|
* Does nothing. May be used for metadata or implementation-specific instructions that don't alter the behavior
|
||||||
*/
|
*/
|
||||||
public static Instruction nop(Object ...params) {
|
public static Instruction nop(Object ...params) {
|
||||||
return new Instruction(Type.NOP, params);
|
return new Instruction(Type.NOP, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction call(int argn, boolean hasSelf) {
|
public static Instruction call(int argn, boolean hasSelf) {
|
||||||
return new Instruction(Type.CALL, argn, hasSelf);
|
return new Instruction(Type.CALL, argn, hasSelf);
|
||||||
}
|
}
|
||||||
public static Instruction callNew(int argn) {
|
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) {
|
public static Instruction jmp(int offset) {
|
||||||
return new Instruction(Type.JMP, offset);
|
return new Instruction(Type.JMP, offset);
|
||||||
}
|
}
|
||||||
public static Instruction jmpIf(int offset) {
|
public static Instruction jmpIf(int offset) {
|
||||||
return new Instruction(Type.JMP_IF, offset);
|
return new Instruction(Type.JMP_IF, offset);
|
||||||
}
|
}
|
||||||
public static Instruction jmpIfNot(int offset) {
|
public static Instruction jmpIfNot(int offset) {
|
||||||
return new Instruction(Type.JMP_IFN, offset);
|
return new Instruction(Type.JMP_IFN, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntFunction<Instruction> jmp(IntSupplier pos) {
|
public static IntFunction<Instruction> jmp(IntSupplier pos) {
|
||||||
return i -> new Instruction(Type.JMP, pos.getAsInt() - i);
|
return i -> new Instruction(Type.JMP, pos.getAsInt() - i);
|
||||||
}
|
}
|
||||||
public static IntFunction<Instruction> jmpIf(IntSupplier pos) {
|
public static IntFunction<Instruction> jmpIf(IntSupplier pos) {
|
||||||
return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i);
|
return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i);
|
||||||
}
|
}
|
||||||
public static IntFunction<Instruction> jmpIfNot(IntSupplier pos) {
|
public static IntFunction<Instruction> jmpIfNot(IntSupplier pos) {
|
||||||
return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i);
|
return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction pushUndefined() {
|
public static Instruction pushUndefined() {
|
||||||
return new Instruction(Type.PUSH_UNDEFINED);
|
return new Instruction(Type.PUSH_UNDEFINED);
|
||||||
}
|
}
|
||||||
public static Instruction pushNull() {
|
public static Instruction pushNull() {
|
||||||
return new Instruction(Type.PUSH_NULL);
|
return new Instruction(Type.PUSH_NULL);
|
||||||
}
|
}
|
||||||
public static Instruction pushValue(boolean val) {
|
public static Instruction pushValue(boolean val) {
|
||||||
return new Instruction(Type.PUSH_BOOL, val);
|
return new Instruction(Type.PUSH_BOOL, val);
|
||||||
}
|
}
|
||||||
public static Instruction pushValue(double val) {
|
public static Instruction pushValue(double val) {
|
||||||
return new Instruction(Type.PUSH_NUMBER, val);
|
return new Instruction(Type.PUSH_NUMBER, val);
|
||||||
}
|
}
|
||||||
public static Instruction pushValue(String val) {
|
public static Instruction pushValue(String val) {
|
||||||
return new Instruction(Type.PUSH_STRING, val);
|
return new Instruction(Type.PUSH_STRING, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction globDef(String name) {
|
public static Instruction globDef(String name) {
|
||||||
return new Instruction(Type.GLOB_DEF, name);
|
return new Instruction(Type.GLOB_DEF, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction globGet(String name, boolean force) {
|
public static Instruction globGet(String name, boolean force) {
|
||||||
return new Instruction(Type.GLOB_GET, name, force);
|
return new Instruction(Type.GLOB_GET, name, force);
|
||||||
}
|
}
|
||||||
public static Instruction globSet(String name, boolean keep, boolean define) {
|
public static Instruction globSet(String name, boolean keep, boolean define) {
|
||||||
return new Instruction(Type.GLOB_SET, name, keep, define);
|
return new Instruction(Type.GLOB_SET, name, keep, define);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction loadVar(int i) {
|
public static Instruction loadVar(int i) {
|
||||||
return new Instruction(Type.LOAD_VAR, i);
|
return new Instruction(Type.LOAD_VAR, i);
|
||||||
}
|
}
|
||||||
public static Instruction loadThis() {
|
public static Instruction loadThis() {
|
||||||
return new Instruction(Type.LOAD_THIS);
|
return new Instruction(Type.LOAD_THIS);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Loads the given argument
|
* Loads the given argument
|
||||||
* @param i The index of the argument to load. If -1, will get the index from the stack instead
|
* @param i The index of the argument to load. If -1, will get the index from the stack instead
|
||||||
*/
|
*/
|
||||||
public static Instruction loadArg(int i) {
|
public static Instruction loadArg(int i) {
|
||||||
return new Instruction(Type.LOAD_ARG, i);
|
return new Instruction(Type.LOAD_ARG, i);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Pushes the amount of arguments to the stack
|
* Pushes the amount of arguments to the stack
|
||||||
*/
|
*/
|
||||||
public static Instruction loadArgsN() {
|
public static Instruction loadArgsN() {
|
||||||
return new Instruction(Type.LOAD_ARGS_N);
|
return new Instruction(Type.LOAD_ARGS_N);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Pushes the arguments object to the stack
|
* Pushes the arguments object to the stack
|
||||||
*/
|
*/
|
||||||
public static Instruction loadArgs() {
|
public static Instruction loadArgs() {
|
||||||
return new Instruction(Type.LOAD_ARGS);
|
return new Instruction(Type.LOAD_ARGS);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Loads a reference to the function being called
|
* Loads a reference to the function being called
|
||||||
*/
|
*/
|
||||||
public static Instruction loadCalled() {
|
public static Instruction loadCalled() {
|
||||||
return new Instruction(Type.LOAD_CALLED);
|
return new Instruction(Type.LOAD_CALLED);
|
||||||
}
|
}
|
||||||
public static Instruction loadGlob() {
|
public static Instruction loadGlob() {
|
||||||
return new Instruction(Type.LOAD_GLOB);
|
return new Instruction(Type.LOAD_GLOB);
|
||||||
}
|
}
|
||||||
public static Instruction loadIntrinsics(String key) {
|
public static Instruction loadIntrinsics(String key) {
|
||||||
return new Instruction(Type.LOAD_INTRINSICS, key);
|
return new Instruction(Type.LOAD_INTRINSICS, key);
|
||||||
}
|
}
|
||||||
public static Instruction loadError() {
|
public static Instruction loadError() {
|
||||||
return new Instruction(Type.LOAD_ERROR);
|
return new Instruction(Type.LOAD_ERROR);
|
||||||
}
|
}
|
||||||
public static Instruction loadMember() {
|
public static Instruction loadMember() {
|
||||||
return new Instruction(Type.LOAD_MEMBER);
|
return new Instruction(Type.LOAD_MEMBER);
|
||||||
}
|
}
|
||||||
public static Instruction loadMember(int member) {
|
public static Instruction loadMember(int member) {
|
||||||
return new Instruction(Type.LOAD_MEMBER_INT, member);
|
return new Instruction(Type.LOAD_MEMBER_INT, member);
|
||||||
}
|
}
|
||||||
public static Instruction loadMember(String member) {
|
public static Instruction loadMember(String member) {
|
||||||
return new Instruction(Type.LOAD_MEMBER_STR, member);
|
return new Instruction(Type.LOAD_MEMBER_STR, member);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction loadRegex(String pattern, String flags) {
|
public static Instruction loadRegex(String pattern, String flags) {
|
||||||
return new Instruction(Type.LOAD_REGEX, pattern, flags);
|
return new Instruction(Type.LOAD_REGEX, pattern, flags);
|
||||||
}
|
}
|
||||||
// TODO: make this capturing a concern of the compiler
|
// TODO: make this capturing a concern of the compiler
|
||||||
public static Instruction loadFunc(int id, String name, int[] captures) {
|
public static Instruction loadFunc(int id, String name, int[] captures) {
|
||||||
var args = new Object[2 + captures.length];
|
var args = new Object[2 + captures.length];
|
||||||
args[0] = id;
|
args[0] = id;
|
||||||
args[1] = name;
|
args[1] = name;
|
||||||
for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i];
|
for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i];
|
||||||
return new Instruction(Type.LOAD_FUNC, args);
|
return new Instruction(Type.LOAD_FUNC, args);
|
||||||
}
|
}
|
||||||
public static Instruction loadObj() {
|
public static Instruction loadObj() {
|
||||||
return new Instruction(Type.LOAD_OBJ);
|
return new Instruction(Type.LOAD_OBJ);
|
||||||
}
|
}
|
||||||
public static Instruction loadArr(int count) {
|
public static Instruction loadArr(int count) {
|
||||||
return new Instruction(Type.LOAD_ARR, count);
|
return new Instruction(Type.LOAD_ARR, count);
|
||||||
}
|
}
|
||||||
public static Instruction dup() {
|
public static Instruction dup() {
|
||||||
return new Instruction(Type.DUP, 1, 0);
|
return new Instruction(Type.DUP, 1, 0);
|
||||||
}
|
}
|
||||||
public static Instruction dup(int count, int offset) {
|
public static Instruction dup(int count, int offset) {
|
||||||
return new Instruction(Type.DUP, count, offset);
|
return new Instruction(Type.DUP, count, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public static Instruction storeVar(int i) {
|
public static Instruction storeVar(int i, boolean keep, boolean initialize) {
|
||||||
// return new Instruction(Type.STORE_VAR, i, false);
|
return new Instruction(Type.STORE_VAR, i, keep, initialize);
|
||||||
// }
|
}
|
||||||
public static Instruction storeVar(int i, boolean keep, boolean initialize) {
|
|
||||||
return new Instruction(Type.STORE_VAR, i, keep, initialize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Instruction storeMember() {
|
public static Instruction storeMember() {
|
||||||
return new Instruction(Type.STORE_MEMBER, false);
|
return new Instruction(Type.STORE_MEMBER, false);
|
||||||
}
|
}
|
||||||
public static Instruction storeMember(boolean keep) {
|
public static Instruction storeMember(boolean keep) {
|
||||||
return new Instruction(Type.STORE_MEMBER, keep);
|
return new Instruction(Type.STORE_MEMBER, keep);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction storeMember(String key) {
|
public static Instruction storeMember(String key) {
|
||||||
return new Instruction(Type.STORE_MEMBER_STR, key, false);
|
return new Instruction(Type.STORE_MEMBER_STR, key, false);
|
||||||
}
|
}
|
||||||
public static Instruction storeMember(String key, boolean keep) {
|
public static Instruction storeMember(String key, boolean keep) {
|
||||||
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
|
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction storeMember(int key) {
|
public static Instruction storeMember(int key) {
|
||||||
return new Instruction(Type.STORE_MEMBER_INT, key, false);
|
return new Instruction(Type.STORE_MEMBER_INT, key, false);
|
||||||
}
|
}
|
||||||
public static Instruction storeMember(int key, boolean keep) {
|
public static Instruction storeMember(int key, boolean keep) {
|
||||||
return new Instruction(Type.STORE_MEMBER_INT, key, keep);
|
return new Instruction(Type.STORE_MEMBER_INT, key, keep);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction discard() {
|
public static Instruction discard() {
|
||||||
return new Instruction(Type.DISCARD);
|
return new Instruction(Type.DISCARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction typeof() {
|
public static Instruction typeof() {
|
||||||
return new Instruction(Type.TYPEOF);
|
return new Instruction(Type.TYPEOF);
|
||||||
}
|
}
|
||||||
public static Instruction typeof(String varName) {
|
public static Instruction typeof(String varName) {
|
||||||
return new Instruction(Type.TYPEOF, varName);
|
return new Instruction(Type.TYPEOF, varName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction keys(boolean own, boolean onlyEnumerable) {
|
public static Instruction keys(boolean own, boolean onlyEnumerable) {
|
||||||
return new Instruction(Type.KEYS, own, onlyEnumerable);
|
return new Instruction(Type.KEYS, own, onlyEnumerable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction defProp(boolean setter) {
|
public static Instruction defProp(boolean setter) {
|
||||||
return new Instruction(Type.DEF_PROP, setter);
|
return new Instruction(Type.DEF_PROP, setter);
|
||||||
}
|
}
|
||||||
public static Instruction defField() {
|
public static Instruction defField() {
|
||||||
return new Instruction(Type.DEF_FIELD);
|
return new Instruction(Type.DEF_FIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction operation(Operation op) {
|
public static Instruction operation(Operation op) {
|
||||||
return new Instruction(Type.OPERATION, op);
|
return new Instruction(Type.OPERATION, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override public String toString() {
|
||||||
var res = type.toString();
|
var res = type.toString();
|
||||||
|
|
||||||
for (int i = 0; i < params.length; i++) {
|
for (int i = 0; i < params.length; i++) {
|
||||||
res += " " + params[i];
|
res += " " + params[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,27 +3,27 @@ package me.topchetoeu.jscript.common;
|
|||||||
import me.topchetoeu.jscript.common.json.JSON;
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
|
||||||
public class Metadata {
|
public class Metadata {
|
||||||
private static final String VERSION;
|
private static final String VERSION;
|
||||||
private static final String AUTHOR;
|
private static final String AUTHOR;
|
||||||
private static final String NAME;
|
private static final String NAME;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map();
|
var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map();
|
||||||
VERSION = data.string("version");
|
VERSION = data.string("version");
|
||||||
AUTHOR = data.string("author");
|
AUTHOR = data.string("author");
|
||||||
NAME = data.string("name");
|
NAME = data.string("name");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String version() {
|
public static String version() {
|
||||||
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
|
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
|
||||||
else return VERSION;
|
else return VERSION;
|
||||||
}
|
}
|
||||||
public static String author() {
|
public static String author() {
|
||||||
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
|
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
|
||||||
else return AUTHOR;
|
else return AUTHOR;
|
||||||
}
|
}
|
||||||
public static String name() {
|
public static String name() {
|
||||||
if (NAME.equals("$" + "{NAME}")) return "some-product";
|
if (NAME.equals("$" + "{NAME}")) return "some-product";
|
||||||
else return NAME;
|
else return NAME;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,52 +3,52 @@ package me.topchetoeu.jscript.common;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public enum Operation {
|
public enum Operation {
|
||||||
INSTANCEOF(1, 2),
|
INSTANCEOF(1, 2),
|
||||||
IN(2, 2),
|
IN(2, 2),
|
||||||
|
|
||||||
MULTIPLY(3, 2),
|
MULTIPLY(3, 2),
|
||||||
DIVIDE(4, 2),
|
DIVIDE(4, 2),
|
||||||
MODULO(5, 2),
|
MODULO(5, 2),
|
||||||
ADD(6, 2),
|
ADD(6, 2),
|
||||||
SUBTRACT(7, 2),
|
SUBTRACT(7, 2),
|
||||||
|
|
||||||
USHIFT_RIGHT(8, 2),
|
USHIFT_RIGHT(8, 2),
|
||||||
SHIFT_RIGHT(9, 2),
|
SHIFT_RIGHT(9, 2),
|
||||||
SHIFT_LEFT(10, 2),
|
SHIFT_LEFT(10, 2),
|
||||||
|
|
||||||
GREATER(11, 2),
|
GREATER(11, 2),
|
||||||
LESS(12, 2),
|
LESS(12, 2),
|
||||||
GREATER_EQUALS(13, 2),
|
GREATER_EQUALS(13, 2),
|
||||||
LESS_EQUALS(14, 2),
|
LESS_EQUALS(14, 2),
|
||||||
LOOSE_EQUALS(15, 2),
|
LOOSE_EQUALS(15, 2),
|
||||||
LOOSE_NOT_EQUALS(16, 2),
|
LOOSE_NOT_EQUALS(16, 2),
|
||||||
EQUALS(17, 2),
|
EQUALS(17, 2),
|
||||||
NOT_EQUALS(18, 2),
|
NOT_EQUALS(18, 2),
|
||||||
|
|
||||||
AND(19, 2),
|
AND(19, 2),
|
||||||
OR(20, 2),
|
OR(20, 2),
|
||||||
XOR(21, 2),
|
XOR(21, 2),
|
||||||
|
|
||||||
NEG(23, 1),
|
NEG(23, 1),
|
||||||
POS(24, 1),
|
POS(24, 1),
|
||||||
NOT(25, 1),
|
NOT(25, 1),
|
||||||
INVERSE(26, 1);
|
INVERSE(26, 1);
|
||||||
|
|
||||||
private static final HashMap<Integer, Operation> operations = new HashMap<>();
|
private static final HashMap<Integer, Operation> operations = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (var val : Operation.values()) operations.put(val.numeric, val);
|
for (var val : Operation.values()) operations.put(val.numeric, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int numeric;
|
public final int numeric;
|
||||||
public final int operands;
|
public final int operands;
|
||||||
|
|
||||||
private Operation(int numeric, int n) {
|
private Operation(int numeric, int n) {
|
||||||
this.numeric = numeric;
|
this.numeric = numeric;
|
||||||
this.operands = n;
|
this.operands = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operation fromNumeric(int i) {
|
public static Operation fromNumeric(int i) {
|
||||||
return operations.get(i);
|
return operations.get(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,76 +10,76 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Reading {
|
public class Reading {
|
||||||
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
|
||||||
public static synchronized String readline() throws IOException {
|
public static synchronized String readline() throws IOException {
|
||||||
return reader.readLine();
|
return reader.readLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] streamToBytes(InputStream in) {
|
public static byte[] streamToBytes(InputStream in) {
|
||||||
try {
|
try {
|
||||||
List<byte[]> bufs = null;
|
List<byte[]> bufs = null;
|
||||||
byte[] result = null;
|
byte[] result = null;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
var buf = new byte[8192];
|
var buf = new byte[8192];
|
||||||
int nread = 0;
|
int nread = 0;
|
||||||
|
|
||||||
// read to EOF which may read more or less than buffer size
|
// read to EOF which may read more or less than buffer size
|
||||||
while ((n = in.read(buf, nread, buf.length - nread)) > 0) {
|
while ((n = in.read(buf, nread, buf.length - nread)) > 0) {
|
||||||
nread += n;
|
nread += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nread > 0) {
|
if (nread > 0) {
|
||||||
if (Integer.MAX_VALUE - 8 - total < nread) throw new OutOfMemoryError("Required array size too large");
|
if (Integer.MAX_VALUE - 8 - total < nread) throw new OutOfMemoryError("Required array size too large");
|
||||||
if (nread < buf.length) buf = Arrays.copyOfRange(buf, 0, nread);
|
if (nread < buf.length) buf = Arrays.copyOfRange(buf, 0, nread);
|
||||||
total += nread;
|
total += nread;
|
||||||
|
|
||||||
if (result == null) result = buf;
|
if (result == null) result = buf;
|
||||||
else {
|
else {
|
||||||
if (bufs == null) {
|
if (bufs == null) {
|
||||||
bufs = new ArrayList<>();
|
bufs = new ArrayList<>();
|
||||||
bufs.add(result);
|
bufs.add(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
bufs.add(buf);
|
bufs.add(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if the last call to read returned -1 or the number of bytes
|
// if the last call to read returned -1 or the number of bytes
|
||||||
// requested have been read then break
|
// requested have been read then break
|
||||||
} while (n >= 0);
|
} while (n >= 0);
|
||||||
|
|
||||||
if (bufs == null) {
|
if (bufs == null) {
|
||||||
if (result == null) return new byte[0];
|
if (result == null) return new byte[0];
|
||||||
return result.length == total ? result : Arrays.copyOf(result, total);
|
return result.length == total ? result : Arrays.copyOf(result, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = new byte[total];
|
result = new byte[total];
|
||||||
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
int remaining = total;
|
int remaining = total;
|
||||||
|
|
||||||
for (byte[] b : bufs) {
|
for (byte[] b : bufs) {
|
||||||
int count = Math.min(b.length, remaining);
|
int count = Math.min(b.length, remaining);
|
||||||
System.arraycopy(b, 0, result, offset, count);
|
System.arraycopy(b, 0, result, offset, count);
|
||||||
offset += count;
|
offset += count;
|
||||||
remaining -= count;
|
remaining -= count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||||
}
|
}
|
||||||
public static String streamToString(InputStream in) {
|
public static String streamToString(InputStream in) {
|
||||||
return new String(streamToBytes(in));
|
return new String(streamToBytes(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InputStream resourceToStream(String name) {
|
public static InputStream resourceToStream(String name) {
|
||||||
return Reading.class.getResourceAsStream("/" + name);
|
return Reading.class.getResourceAsStream("/" + name);
|
||||||
}
|
}
|
||||||
public static String resourceToString(String name) {
|
public static String resourceToString(String name) {
|
||||||
return streamToString(resourceToStream(name));
|
return streamToString(resourceToStream(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@ package me.topchetoeu.jscript.common;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public class SyntaxException extends RuntimeException {
|
public class SyntaxException extends RuntimeException {
|
||||||
public final Location loc;
|
public final Location loc;
|
||||||
public final String msg;
|
public final String msg;
|
||||||
|
|
||||||
public SyntaxException(Location loc, String msg) {
|
public SyntaxException(Location loc, String msg) {
|
||||||
super(String.format("Syntax error %s: %s", loc, msg));
|
super(String.format("Syntax error %s: %s", loc, msg));
|
||||||
this.loc = loc;
|
this.loc = loc;
|
||||||
this.msg = msg;
|
this.msg = msg;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,91 +7,91 @@ import java.util.Set;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class Environment {
|
public class Environment {
|
||||||
public final Environment parent;
|
public final Environment parent;
|
||||||
private final Map<Key<Object>, Object> map = new HashMap<>();
|
private final Map<Key<Object>, Object> map = new HashMap<>();
|
||||||
private final Set<Key<Object>> hidden = new HashSet<>();
|
private final Set<Key<Object>> hidden = new HashSet<>();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T get(Key<T> key) {
|
public <T> T get(Key<T> key) {
|
||||||
if (map.containsKey(key)) return (T)map.get(key);
|
if (map.containsKey(key)) return (T)map.get(key);
|
||||||
else if (!hidden.contains(key) && parent != null) return parent.get(key);
|
else if (!hidden.contains(key) && parent != null) return parent.get(key);
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
public boolean has(Key<?> key) {
|
public boolean has(Key<?> key) {
|
||||||
if (map.containsKey(key)) return true;
|
if (map.containsKey(key)) return true;
|
||||||
else if (!hidden.contains(key) && parent != null) return parent.has(key);
|
else if (!hidden.contains(key) && parent != null) return parent.has(key);
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasNotNull(Key<?> key) {
|
public boolean hasNotNull(Key<?> key) {
|
||||||
return get(key) != null;
|
return get(key) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T get(Key<T> key, T defaultVal) {
|
public <T> T get(Key<T> key, T defaultVal) {
|
||||||
if (has(key)) return get(key);
|
if (has(key)) return get(key);
|
||||||
else return defaultVal;
|
else return defaultVal;
|
||||||
}
|
}
|
||||||
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
|
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
|
||||||
if (has(key)) return get(key);
|
if (has(key)) return get(key);
|
||||||
else return defaultVal.get();
|
else return defaultVal.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> Environment add(Key<T> key, T val) {
|
public <T> Environment add(Key<T> key, T val) {
|
||||||
map.put((Key<Object>)key, val);
|
map.put((Key<Object>)key, val);
|
||||||
hidden.remove(key);
|
hidden.remove(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public Environment add(Key<Void> key) {
|
public Environment add(Key<Void> key) {
|
||||||
return add(key, null);
|
return add(key, null);
|
||||||
}
|
}
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
|
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
|
||||||
map.putAll((Map)map);
|
map.putAll((Map)map);
|
||||||
hidden.removeAll(map.keySet());
|
hidden.removeAll(map.keySet());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public Environment addAll(Map<Key<?>, ?> map) {
|
public Environment addAll(Map<Key<?>, ?> map) {
|
||||||
return addAll(map, true);
|
return addAll(map, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Environment remove(Key<?> key) {
|
public Environment remove(Key<?> key) {
|
||||||
map.remove(key);
|
map.remove(key);
|
||||||
hidden.add((Key<Object>)key);
|
hidden.add((Key<Object>)key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T init(Key<T> key, T val) {
|
public <T> T init(Key<T> key, T val) {
|
||||||
if (!has(key)) this.add(key, val);
|
if (!has(key)) this.add(key, val);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
public <T> T initFrom(Key<T> key, Supplier<T> val) {
|
public <T> T initFrom(Key<T> key, Supplier<T> val) {
|
||||||
if (!has(key)) {
|
if (!has(key)) {
|
||||||
var res = val.get();
|
var res = val.get();
|
||||||
this.add(key, res);
|
this.add(key, res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
else return get(key);
|
else return get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Environment child() {
|
public Environment child() {
|
||||||
return new Environment(this);
|
return new Environment(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Environment(Environment parent) {
|
public Environment(Environment parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
public Environment() {
|
public Environment() {
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Environment wrap(Environment env) {
|
public static Environment wrap(Environment env) {
|
||||||
if (env == null) return empty();
|
if (env == null) return empty();
|
||||||
else return env;
|
else return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Environment empty() {
|
public static Environment empty() {
|
||||||
return new Environment();
|
return new Environment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
package me.topchetoeu.jscript.common.environment;
|
package me.topchetoeu.jscript.common.environment;
|
||||||
|
|
||||||
public final class Key<T> {
|
public final class Key<T> { }
|
||||||
public static <T> Key<T> of() {
|
|
||||||
return new Key<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -11,162 +11,162 @@ import me.topchetoeu.jscript.common.parsing.Parsing;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
|
||||||
public class JSON {
|
public class JSON {
|
||||||
public static ParseRes<JSONElement> parseString(Source src, int i) {
|
public static ParseRes<JSONElement> parseString(Source src, int i) {
|
||||||
var res = Parsing.parseString(src, i);
|
var res = Parsing.parseString(src, i);
|
||||||
if (!res.isSuccess()) return res.chainError();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
return ParseRes.res(JSONElement.string(res.result), res.n);
|
return ParseRes.res(JSONElement.string(res.result), res.n);
|
||||||
}
|
}
|
||||||
public static ParseRes<JSONElement> parseNumber(Source src, int i) {
|
public static ParseRes<JSONElement> parseNumber(Source src, int i) {
|
||||||
var res = Parsing.parseNumber(src, i, true);
|
var res = Parsing.parseNumber(src, i, true);
|
||||||
if (!res.isSuccess()) return res.chainError();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
else return ParseRes.res(JSONElement.number(res.result), res.n);
|
else return ParseRes.res(JSONElement.number(res.result), res.n);
|
||||||
}
|
}
|
||||||
public static ParseRes<JSONElement> parseLiteral(Source src, int i) {
|
public static ParseRes<JSONElement> parseLiteral(Source src, int i) {
|
||||||
var id = Parsing.parseIdentifier(src, i);
|
var id = Parsing.parseIdentifier(src, i);
|
||||||
|
|
||||||
if (!id.isSuccess()) return ParseRes.failed();
|
if (!id.isSuccess()) return ParseRes.failed();
|
||||||
else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n);
|
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("false")) return ParseRes.res(JSONElement.bool(false), id.n);
|
||||||
else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n);
|
else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n);
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<JSONElement> parseValue(Source src, int i) {
|
public static ParseRes<JSONElement> parseValue(Source src, int i) {
|
||||||
return ParseRes.first(src, i,
|
return ParseRes.first(src, i,
|
||||||
JSON::parseString,
|
JSON::parseString,
|
||||||
JSON::parseNumber,
|
JSON::parseNumber,
|
||||||
JSON::parseLiteral,
|
JSON::parseLiteral,
|
||||||
JSON::parseMap,
|
JSON::parseMap,
|
||||||
JSON::parseList
|
JSON::parseList
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<JSONMap> parseMap(Source src, int i) {
|
public static ParseRes<JSONMap> parseMap(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var values = new JSONMap();
|
var values = new JSONMap();
|
||||||
|
|
||||||
if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(new HashMap<>()), n + 1);
|
if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(new HashMap<>()), n + 1);
|
||||||
while (true) {
|
while (true) {
|
||||||
var name = parseString(src, i + n);
|
var name = parseString(src, i + n);
|
||||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index");
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index");
|
||||||
n += name.n;
|
n += name.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon");
|
if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var res = parseValue(src, i + n);
|
var res = parseValue(src, i + n);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
|
||||||
values.put(name.result.toString(), res.result);
|
values.put(name.result.toString(), res.result);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (src.is(i + n, ",")) n++;
|
if (src.is(i + n, ",")) n++;
|
||||||
else if (src.is(i + n, "}")) {
|
else if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(values, n);
|
return ParseRes.res(values, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<JSONList> parseList(Source src, int i) {
|
public static ParseRes<JSONList> parseList(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n++, "[]")) return ParseRes.failed();
|
if (!src.is(i + n++, "[]")) return ParseRes.failed();
|
||||||
|
|
||||||
var values = new JSONList();
|
var values = new JSONList();
|
||||||
|
|
||||||
if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1);
|
if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1);
|
||||||
while (true) {
|
while (true) {
|
||||||
var res = parseValue(src, i + n);
|
var res = parseValue(src, i + n);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
|
||||||
values.add(res.result);
|
values.add(res.result);
|
||||||
n += res.n;
|
n += res.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (src.is(i + n, ",")) n++;
|
if (src.is(i + n, ",")) n++;
|
||||||
else if (src.is(i + n, "]")) {
|
else if (src.is(i + n, "]")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(values, n);
|
return ParseRes.res(values, n);
|
||||||
}
|
}
|
||||||
public static JSONElement parse(Filename filename, String raw) {
|
public static JSONElement parse(Filename filename, String raw) {
|
||||||
if (filename == null) filename = new Filename("jscript", "json");
|
if (filename == null) filename = new Filename("jscript", "json");
|
||||||
|
|
||||||
var res = parseValue(new Source(null, filename, raw), 0);
|
var res = parseValue(new Source(null, filename, raw), 0);
|
||||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given");
|
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given");
|
||||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||||
else return JSONElement.of(res.result);
|
else return JSONElement.of(res.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String stringify(JSONElement el) {
|
public static String stringify(JSONElement el) {
|
||||||
if (el.isNumber()) {
|
if (el.isNumber()) {
|
||||||
var d = el.number();
|
var d = el.number();
|
||||||
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
|
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
|
||||||
if (d == Double.POSITIVE_INFINITY) return "Infinity";
|
if (d == Double.POSITIVE_INFINITY) return "Infinity";
|
||||||
if (Double.isNaN(d)) return "NaN";
|
if (Double.isNaN(d)) return "NaN";
|
||||||
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
|
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
|
||||||
}
|
}
|
||||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||||
if (el.isNull()) return "null";
|
if (el.isNull()) return "null";
|
||||||
if (el.isString()) {
|
if (el.isString()) {
|
||||||
var res = new StringBuilder("\"");
|
var res = new StringBuilder("\"");
|
||||||
var alphabet = "0123456789ABCDEF".toCharArray();
|
var alphabet = "0123456789ABCDEF".toCharArray();
|
||||||
|
|
||||||
for (var c : el.string().toCharArray()) {
|
for (var c : el.string().toCharArray()) {
|
||||||
if (c < 32 || c >= 127) {
|
if (c < 32 || c >= 127) {
|
||||||
res
|
res
|
||||||
.append("\\u")
|
.append("\\u")
|
||||||
.append(alphabet[(c >> 12) & 0xF])
|
.append(alphabet[(c >> 12) & 0xF])
|
||||||
.append(alphabet[(c >> 8) & 0xF])
|
.append(alphabet[(c >> 8) & 0xF])
|
||||||
.append(alphabet[(c >> 4) & 0xF])
|
.append(alphabet[(c >> 4) & 0xF])
|
||||||
.append(alphabet[(c >> 0) & 0xF]);
|
.append(alphabet[(c >> 0) & 0xF]);
|
||||||
}
|
}
|
||||||
else if (c == '\\')
|
else if (c == '\\')
|
||||||
res.append("\\\\");
|
res.append("\\\\");
|
||||||
else if (c == '"')
|
else if (c == '"')
|
||||||
res.append("\\\"");
|
res.append("\\\"");
|
||||||
else res.append(c);
|
else res.append(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.append('"').toString();
|
return res.append('"').toString();
|
||||||
}
|
}
|
||||||
if (el.isList()) {
|
if (el.isList()) {
|
||||||
var res = new StringBuilder().append("[");
|
var res = new StringBuilder().append("[");
|
||||||
for (int i = 0; i < el.list().size(); i++) {
|
for (int i = 0; i < el.list().size(); i++) {
|
||||||
if (i != 0) res.append(",");
|
if (i != 0) res.append(",");
|
||||||
res.append(stringify(el.list().get(i)));
|
res.append(stringify(el.list().get(i)));
|
||||||
}
|
}
|
||||||
res.append("]");
|
res.append("]");
|
||||||
return res.toString();
|
return res.toString();
|
||||||
}
|
}
|
||||||
if (el.isMap()) {
|
if (el.isMap()) {
|
||||||
var res = new StringBuilder().append("{");
|
var res = new StringBuilder().append("{");
|
||||||
var entries = el.map().entrySet().stream().collect(Collectors.toList());
|
var entries = el.map().entrySet().stream().collect(Collectors.toList());
|
||||||
|
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
if (i != 0) res.append(",");
|
if (i != 0) res.append(",");
|
||||||
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
|
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
|
||||||
res.append(":");
|
res.append(":");
|
||||||
res.append(stringify(entries.get(i).getValue()));
|
res.append(stringify(entries.get(i).getValue()));
|
||||||
}
|
}
|
||||||
res.append("}");
|
res.append("}");
|
||||||
return res.toString();
|
return res.toString();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public static String stringify(JSONMap map) {
|
public static String stringify(JSONMap map) {
|
||||||
return stringify(JSONElement.of(map));
|
return stringify(JSONElement.of(map));
|
||||||
}
|
}
|
||||||
public static String stringify(JSONList list) {
|
public static String stringify(JSONList list) {
|
||||||
return stringify(JSONElement.of(list));
|
return stringify(JSONElement.of(list));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,87 +1,87 @@
|
|||||||
package me.topchetoeu.jscript.common.json;
|
package me.topchetoeu.jscript.common.json;
|
||||||
|
|
||||||
public class JSONElement {
|
public class JSONElement {
|
||||||
public static enum Type {
|
public static enum Type {
|
||||||
STRING,
|
STRING,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
NULL,
|
NULL,
|
||||||
LIST,
|
LIST,
|
||||||
MAP,
|
MAP,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
|
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
|
||||||
|
|
||||||
public static JSONElement map(JSONMap val) {
|
public static JSONElement map(JSONMap val) {
|
||||||
return new JSONElement(Type.MAP, val);
|
return new JSONElement(Type.MAP, val);
|
||||||
}
|
}
|
||||||
public static JSONElement list(JSONList val) {
|
public static JSONElement list(JSONList val) {
|
||||||
return new JSONElement(Type.LIST, val);
|
return new JSONElement(Type.LIST, val);
|
||||||
}
|
}
|
||||||
public static JSONElement string(String val) {
|
public static JSONElement string(String val) {
|
||||||
return new JSONElement(Type.STRING, val);
|
return new JSONElement(Type.STRING, val);
|
||||||
}
|
}
|
||||||
public static JSONElement number(double val) {
|
public static JSONElement number(double val) {
|
||||||
return new JSONElement(Type.NUMBER, val);
|
return new JSONElement(Type.NUMBER, val);
|
||||||
}
|
}
|
||||||
public static JSONElement bool(boolean val) {
|
public static JSONElement bool(boolean val) {
|
||||||
return new JSONElement(Type.BOOLEAN, val);
|
return new JSONElement(Type.BOOLEAN, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONElement of(Object val) {
|
public static JSONElement of(Object val) {
|
||||||
if (val instanceof JSONMap) return map((JSONMap)val);
|
if (val instanceof JSONMap) return map((JSONMap)val);
|
||||||
else if (val instanceof JSONList) return list((JSONList)val);
|
else if (val instanceof JSONList) return list((JSONList)val);
|
||||||
else if (val instanceof String) return string((String)val);
|
else if (val instanceof String) return string((String)val);
|
||||||
else if (val instanceof Boolean) return bool((Boolean)val);
|
else if (val instanceof Boolean) return bool((Boolean)val);
|
||||||
else if (val instanceof Number) return number(((Number)val).doubleValue());
|
else if (val instanceof Number) return number(((Number)val).doubleValue());
|
||||||
else if (val == null) return NULL;
|
else if (val == null) return NULL;
|
||||||
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap");
|
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap");
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Type type;
|
public final Type type;
|
||||||
private final Object value;
|
private final Object value;
|
||||||
|
|
||||||
public boolean isMap() { return type == Type.MAP; }
|
public boolean isMap() { return type == Type.MAP; }
|
||||||
public boolean isList() { return type == Type.LIST; }
|
public boolean isList() { return type == Type.LIST; }
|
||||||
public boolean isString() { return type == Type.STRING; }
|
public boolean isString() { return type == Type.STRING; }
|
||||||
public boolean isNumber() { return type == Type.NUMBER; }
|
public boolean isNumber() { return type == Type.NUMBER; }
|
||||||
public boolean isBoolean() { return type == Type.BOOLEAN; }
|
public boolean isBoolean() { return type == Type.BOOLEAN; }
|
||||||
public boolean isNull() { return type == Type.NULL; }
|
public boolean isNull() { return type == Type.NULL; }
|
||||||
|
|
||||||
public JSONMap map() {
|
public JSONMap map() {
|
||||||
if (!isMap()) throw new IllegalStateException("Element is not a map");
|
if (!isMap()) throw new IllegalStateException("Element is not a map");
|
||||||
return (JSONMap)value;
|
return (JSONMap)value;
|
||||||
}
|
}
|
||||||
public JSONList list() {
|
public JSONList list() {
|
||||||
if (!isList()) throw new IllegalStateException("Element is not a map");
|
if (!isList()) throw new IllegalStateException("Element is not a map");
|
||||||
return (JSONList)value;
|
return (JSONList)value;
|
||||||
}
|
}
|
||||||
public String string() {
|
public String string() {
|
||||||
if (!isString()) throw new IllegalStateException("Element is not a string");
|
if (!isString()) throw new IllegalStateException("Element is not a string");
|
||||||
return (String)value;
|
return (String)value;
|
||||||
}
|
}
|
||||||
public double number() {
|
public double number() {
|
||||||
if (!isNumber()) throw new IllegalStateException("Element is not a number");
|
if (!isNumber()) throw new IllegalStateException("Element is not a number");
|
||||||
return (double)value;
|
return (double)value;
|
||||||
}
|
}
|
||||||
public boolean bool() {
|
public boolean bool() {
|
||||||
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean");
|
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean");
|
||||||
return (boolean)value;
|
return (boolean)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override public String toString() {
|
||||||
if (isMap()) return "{...}";
|
if (isMap()) return "{...}";
|
||||||
if (isList()) return "[...]";
|
if (isList()) return "[...]";
|
||||||
if (isString()) return (String)value;
|
if (isString()) return (String)value;
|
||||||
if (isString()) return (String)value;
|
if (isString()) return (String)value;
|
||||||
if (isNumber()) return (double)value + "";
|
if (isNumber()) return (double)value + "";
|
||||||
if (isBoolean()) return (boolean)value + "";
|
if (isBoolean()) return (boolean)value + "";
|
||||||
if (isNull()) return "null";
|
if (isNull()) return "null";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONElement(Type type, Object val) {
|
private JSONElement(Type type, Object val) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.value = val;
|
this.value = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,21 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class JSONList extends ArrayList<JSONElement> {
|
public class JSONList extends ArrayList<JSONElement> {
|
||||||
public JSONList() {}
|
public JSONList() {}
|
||||||
public JSONList(JSONElement ...els) {
|
public JSONList(JSONElement ...els) {
|
||||||
super(Arrays.asList(els));
|
super(Arrays.asList(els));
|
||||||
}
|
}
|
||||||
public JSONList(Collection<JSONElement> els) {
|
public JSONList(Collection<JSONElement> els) {
|
||||||
super(els);
|
super(els);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||||
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
|
||||||
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
|
||||||
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||||
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
|
||||||
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
|
||||||
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,133 +6,131 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class JSONMap implements Map<String, JSONElement> {
|
public class JSONMap implements Map<String, JSONElement> {
|
||||||
private Map<String, JSONElement> elements = new HashMap<>();
|
private Map<String, JSONElement> elements = new HashMap<>();
|
||||||
|
|
||||||
public JSONElement get(String path) {
|
public JSONElement get(String path) {
|
||||||
var curr = this;
|
JSONElement curr = JSONElement.map(this);
|
||||||
var segs = path.split("\\.");
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
while (true) {
|
for (var seg : path.split("\\.")) {
|
||||||
var tmp = curr.elements.get(segs[i++]);
|
if (!curr.isMap()) return null;
|
||||||
if (i == segs.length) return tmp;
|
curr = curr.map().elements.get(seg);
|
||||||
if (!tmp.isMap()) return null;
|
}
|
||||||
curr = tmp.map();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMap(String path) {
|
return curr;
|
||||||
var el = get(path);
|
}
|
||||||
return el != null && el.isMap();
|
|
||||||
}
|
|
||||||
public boolean isList(String path) {
|
|
||||||
var el = get(path);
|
|
||||||
return el != null && el.isList();
|
|
||||||
}
|
|
||||||
public boolean isString(String path) {
|
|
||||||
var el = get(path);
|
|
||||||
return el != null && el.isString();
|
|
||||||
}
|
|
||||||
public boolean isNumber(String path) {
|
|
||||||
var el = get(path);
|
|
||||||
return el != null && el.isNumber();
|
|
||||||
}
|
|
||||||
public boolean isBoolean(String path) {
|
|
||||||
var el = get(path);
|
|
||||||
return el != null && el.isBoolean();
|
|
||||||
}
|
|
||||||
public boolean isNull(String path) {
|
|
||||||
var el = get(path);
|
|
||||||
return el != null && el.isNull();
|
|
||||||
}
|
|
||||||
public boolean contains(String path) {
|
|
||||||
return get(path) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONMap map(String path) {
|
public boolean isMap(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
return el != null && el.isMap();
|
||||||
return el.map();
|
}
|
||||||
}
|
public boolean isList(String path) {
|
||||||
public JSONMap map(String path, JSONMap defaultVal) {
|
var el = get(path);
|
||||||
var el = get(path);
|
return el != null && el.isList();
|
||||||
if (el == null) return defaultVal;
|
}
|
||||||
if (el.isMap()) return el.map();
|
public boolean isString(String path) {
|
||||||
return defaultVal;
|
var el = get(path);
|
||||||
}
|
return el != null && el.isString();
|
||||||
|
}
|
||||||
|
public boolean isNumber(String path) {
|
||||||
|
var el = get(path);
|
||||||
|
return el != null && el.isNumber();
|
||||||
|
}
|
||||||
|
public boolean isBoolean(String path) {
|
||||||
|
var el = get(path);
|
||||||
|
return el != null && el.isBoolean();
|
||||||
|
}
|
||||||
|
public boolean isNull(String path) {
|
||||||
|
var el = get(path);
|
||||||
|
return el != null && el.isNull();
|
||||||
|
}
|
||||||
|
public boolean contains(String path) {
|
||||||
|
return get(path) != null;
|
||||||
|
}
|
||||||
|
|
||||||
public JSONList list(String path) {
|
public JSONMap map(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||||
return el.list();
|
return el.map();
|
||||||
}
|
}
|
||||||
public JSONList list(String path, JSONList defaultVal) {
|
public JSONMap map(String path, JSONMap defaultVal) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) return defaultVal;
|
if (el == null) return defaultVal;
|
||||||
if (el.isList()) return el.list();
|
if (el.isMap()) return el.map();
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String string(String path) {
|
public JSONList list(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||||
return el.string();
|
return el.list();
|
||||||
}
|
}
|
||||||
public String string(String path, String defaultVal) {
|
public JSONList list(String path, JSONList defaultVal) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) return defaultVal;
|
if (el == null) return defaultVal;
|
||||||
if (el.isString()) return el.string();
|
if (el.isList()) return el.list();
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double number(String path) {
|
public String string(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||||
return el.number();
|
return el.string();
|
||||||
}
|
}
|
||||||
public double number(String path, double defaultVal) {
|
public String string(String path, String defaultVal) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) return defaultVal;
|
if (el == null) return defaultVal;
|
||||||
if (el.isNumber()) return el.number();
|
if (el.isString()) return el.string();
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean bool(String path) {
|
public double number(String path) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||||
return el.bool();
|
return el.number();
|
||||||
}
|
}
|
||||||
public boolean bool(String path, boolean defaultVal) {
|
public double number(String path, double defaultVal) {
|
||||||
var el = get(path);
|
var el = get(path);
|
||||||
if (el == null) return defaultVal;
|
if (el == null) return defaultVal;
|
||||||
if (el.isBoolean()) return el.bool();
|
if (el.isNumber()) return el.number();
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
|
public boolean bool(String path) {
|
||||||
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
|
var el = get(path);
|
||||||
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
|
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||||
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
|
return el.bool();
|
||||||
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
}
|
||||||
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
public boolean bool(String path, boolean defaultVal) {
|
||||||
|
var el = get(path);
|
||||||
|
if (el == null) return defaultVal;
|
||||||
|
if (el.isBoolean()) return el.bool();
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public int size() { return elements.size(); }
|
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
|
||||||
@Override public boolean isEmpty() { return elements.isEmpty(); }
|
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||||
@Override public boolean containsKey(Object key) { return elements.containsKey(key); }
|
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||||
@Override public boolean containsValue(Object value) { return elements.containsValue(value); }
|
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||||
@Override public JSONElement get(Object key) { return elements.get(key); }
|
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||||
@Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||||
@Override public JSONElement remove(Object key) { return elements.remove(key); }
|
|
||||||
@Override public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
|
||||||
|
|
||||||
@Override public void clear() { elements.clear(); }
|
@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<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||||
|
|
||||||
@Override public Set<String> keySet() { return elements.keySet(); }
|
@Override public void clear() { elements.clear(); }
|
||||||
@Override public Collection<JSONElement> values() { return elements.values(); }
|
|
||||||
@Override public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
|
||||||
|
|
||||||
public JSONMap() { }
|
@Override public Set<String> keySet() { return elements.keySet(); }
|
||||||
public JSONMap(Map<String, JSONElement> els) {
|
@Override public Collection<JSONElement> values() { return elements.values(); }
|
||||||
this.elements = new HashMap<>(els);
|
@Override public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||||
}
|
|
||||||
|
public JSONMap() { }
|
||||||
|
public JSONMap(Map<String, JSONElement> els) {
|
||||||
|
this.elements = new HashMap<>(els);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common.mapping;
|
|
||||||
|
|
||||||
public enum ConvertType {
|
|
||||||
Exact,
|
|
||||||
Lower,
|
|
||||||
Upper,
|
|
||||||
Both,
|
|
||||||
}
|
|
@ -17,172 +17,150 @@ import me.topchetoeu.jscript.common.parsing.Filename;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public class FunctionMap {
|
public class FunctionMap {
|
||||||
public static class FunctionMapBuilder {
|
public static class FunctionMapBuilder {
|
||||||
private final TreeMap<Integer, Location> sourceMap = new TreeMap<>();
|
private final TreeMap<Integer, Location> sourceMap = new TreeMap<>();
|
||||||
private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>();
|
private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>();
|
||||||
|
|
||||||
public Location toLocation(int pc) {
|
public Location toLocation(int pc) {
|
||||||
return sourceMap.headMap(pc, true).firstEntry().getValue();
|
return sourceMap.headMap(pc, true).firstEntry().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionMapBuilder setDebug(Location loc, BreakpointType type) {
|
public FunctionMapBuilder setDebug(Location loc, BreakpointType type) {
|
||||||
if (loc == null || type == null || type == BreakpointType.NONE) return this;
|
if (loc == null || type == null || type == BreakpointType.NONE) return this;
|
||||||
breakpoints.put(loc, type);
|
breakpoints.put(loc, type);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public FunctionMapBuilder setLocation(int i, Location loc) {
|
public FunctionMapBuilder setLocation(int i, Location loc) {
|
||||||
if (loc == null || i < 0) return this;
|
if (loc == null || i < 0) return this;
|
||||||
sourceMap.put(i, loc);
|
sourceMap.put(i, loc);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) {
|
public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) {
|
||||||
setDebug(loc, type);
|
setDebug(loc, type);
|
||||||
setLocation(i, loc);
|
setLocation(i, loc);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Location first() {
|
public Location first() {
|
||||||
if (sourceMap.size() == 0) return null;
|
if (sourceMap.size() == 0) return null;
|
||||||
return sourceMap.firstEntry().getValue();
|
return sourceMap.firstEntry().getValue();
|
||||||
}
|
}
|
||||||
public Location last() {
|
public Location last() {
|
||||||
if (sourceMap.size() == 0) return null;
|
if (sourceMap.size() == 0) return null;
|
||||||
return sourceMap.lastEntry().getValue();
|
return sourceMap.lastEntry().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionMap build(String[] localNames, String[] captureNames) {
|
public FunctionMap build(String[] localNames, String[] captureNames) {
|
||||||
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
|
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
|
||||||
}
|
}
|
||||||
public FunctionMap build() {
|
public FunctionMap build() {
|
||||||
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FunctionMapBuilder() { }
|
private FunctionMapBuilder() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final FunctionMap EMPTY = new FunctionMap();
|
public static final FunctionMap EMPTY = new FunctionMap();
|
||||||
|
|
||||||
private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
|
private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
|
||||||
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
|
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
|
||||||
|
|
||||||
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
|
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
|
||||||
|
|
||||||
public final String[] localNames, captureNames;
|
public final String[] localNames, captureNames;
|
||||||
|
|
||||||
public Location toLocation(int pc, boolean approxiamte) {
|
public Location toLocation(int pc, boolean approxiamte) {
|
||||||
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
|
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
|
||||||
var res = pcToLoc.get(pc);
|
var res = pcToLoc.get(pc);
|
||||||
if (!approxiamte || res != null) return res;
|
if (!approxiamte || res != null) return res;
|
||||||
var entry = pcToLoc.headMap(pc, true).lastEntry();
|
var entry = pcToLoc.headMap(pc, true).lastEntry();
|
||||||
if (entry == null) return null;
|
if (entry == null) return null;
|
||||||
else return entry.getValue();
|
else return entry.getValue();
|
||||||
}
|
}
|
||||||
public Location toLocation(int pc) {
|
public Location toLocation(int pc) {
|
||||||
return toLocation(pc, false);
|
return toLocation(pc, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BreakpointType getBreakpoint(int pc) {
|
public BreakpointType getBreakpoint(int pc) {
|
||||||
return bps.getOrDefault(pc, BreakpointType.NONE);
|
return bps.getOrDefault(pc, BreakpointType.NONE);
|
||||||
}
|
}
|
||||||
public Location correctBreakpoint(Location loc) {
|
public Location correctBreakpoint(Location loc) {
|
||||||
var set = bpLocs.get(loc.filename());
|
var set = bpLocs.get(loc.filename());
|
||||||
if (set == null) return null;
|
if (set == null) return null;
|
||||||
else return set.ceiling(loc);
|
else return set.ceiling(loc);
|
||||||
}
|
}
|
||||||
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
|
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
|
||||||
var candidates = new HashMap<Filename, TreeSet<Location>>();
|
var candidates = new HashMap<Filename, TreeSet<Location>>();
|
||||||
|
|
||||||
for (var name : bpLocs.keySet()) {
|
for (var name : bpLocs.keySet()) {
|
||||||
if (filename.matcher(name.toString()).matches()) {
|
if (filename.matcher(name.toString()).matches()) {
|
||||||
candidates.put(name, bpLocs.get(name));
|
candidates.put(name, bpLocs.get(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = new ArrayList<Location>(candidates.size());
|
var res = new ArrayList<Location>(candidates.size());
|
||||||
for (var candidate : candidates.entrySet()) {
|
for (var candidate : candidates.entrySet()) {
|
||||||
var val = correctBreakpoint(Location.of(candidate.getKey(), line, column));
|
var val = correctBreakpoint(Location.of(candidate.getKey(), line, column));
|
||||||
if (val == null) continue;
|
if (val == null) continue;
|
||||||
res.add(val);
|
res.add(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
public List<Location> breakpoints(Location start, Location end) {
|
public List<Location> breakpoints(Location start, Location end) {
|
||||||
if (!Objects.equals(start.filename(), end.filename())) return Arrays.asList();
|
if (!Objects.equals(start.filename(), end.filename())) return Arrays.asList();
|
||||||
NavigableSet<Location> set = bpLocs.get(start.filename());
|
NavigableSet<Location> set = bpLocs.get(start.filename());
|
||||||
if (set == null) return Arrays.asList();
|
if (set == null) return Arrays.asList();
|
||||||
|
|
||||||
if (start != null) set = set.tailSet(start, true);
|
if (start != null) set = set.tailSet(start, true);
|
||||||
if (end != null) set = set.headSet(end, true);
|
if (end != null) set = set.headSet(end, true);
|
||||||
|
|
||||||
return set.stream().collect(Collectors.toList());
|
return set.stream().collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Location start() {
|
public Location start() {
|
||||||
if (pcToLoc.size() == 0) return null;
|
if (pcToLoc.size() == 0) return null;
|
||||||
return pcToLoc.firstEntry().getValue();
|
return pcToLoc.firstEntry().getValue();
|
||||||
}
|
}
|
||||||
public Location end() {
|
public Location end() {
|
||||||
if (pcToLoc.size() == 0) return null;
|
if (pcToLoc.size() == 0) return null;
|
||||||
return pcToLoc.lastEntry().getValue();
|
return pcToLoc.lastEntry().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// public static FunctionMap apply(FunctionMap funcMap, SourceMap map) {
|
public FunctionMap clone() {
|
||||||
// var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames);
|
var res = new FunctionMap(new HashMap<>(), new HashMap<>(), localNames, captureNames);
|
||||||
|
res.pcToLoc.putAll(this.pcToLoc);
|
||||||
|
res.bps.putAll(bps);
|
||||||
|
res.bpLocs.putAll(bpLocs);
|
||||||
|
res.pcToLoc.putAll(pcToLoc);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
// for (var el : funcMap.pcToLoc.entrySet()) {
|
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) {
|
||||||
// res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
|
var locToPc = new HashMap<Location, Integer>();
|
||||||
// }
|
|
||||||
|
|
||||||
// res.bps.putAll(bps);
|
for (var el : map.entrySet()) {
|
||||||
|
pcToLoc.put(el.getKey(), el.getValue());
|
||||||
|
locToPc.putIfAbsent(el.getValue(), el.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
// for (var el : bpLocs.entrySet()) {
|
for (var el : breakpoints.entrySet()) {
|
||||||
// for (var loc : el.getValue()) {
|
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
|
||||||
// loc = map.toCompiled(loc);
|
bps.put(locToPc.get(el.getKey()), el.getValue());
|
||||||
// if (loc == null) continue;
|
|
||||||
|
|
||||||
// if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
|
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
|
||||||
// res.bpLocs.get(loc.filename()).add(loc);
|
bpLocs.get(el.getKey().filename()).add(el.getKey());
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// return res;
|
this.localNames = localNames;
|
||||||
// }
|
this.captureNames = captureNames;
|
||||||
|
}
|
||||||
|
private FunctionMap() {
|
||||||
|
localNames = new String[0];
|
||||||
|
captureNames = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
public FunctionMap clone() {
|
public static FunctionMapBuilder builder() {
|
||||||
var res = new FunctionMap(new HashMap<>(), new HashMap<>(), localNames, captureNames);
|
return new FunctionMapBuilder();
|
||||||
res.pcToLoc.putAll(this.pcToLoc);
|
}
|
||||||
res.bps.putAll(bps);
|
|
||||||
res.bpLocs.putAll(bpLocs);
|
|
||||||
res.pcToLoc.putAll(pcToLoc);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) {
|
|
||||||
var locToPc = new HashMap<Location, Integer>();
|
|
||||||
|
|
||||||
for (var el : map.entrySet()) {
|
|
||||||
pcToLoc.put(el.getKey(), el.getValue());
|
|
||||||
locToPc.putIfAbsent(el.getValue(), el.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var el : breakpoints.entrySet()) {
|
|
||||||
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
|
|
||||||
bps.put(locToPc.get(el.getKey()), el.getValue());
|
|
||||||
|
|
||||||
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
|
|
||||||
bpLocs.get(el.getKey().filename()).add(el.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.localNames = localNames;
|
|
||||||
this.captureNames = captureNames;
|
|
||||||
}
|
|
||||||
private FunctionMap() {
|
|
||||||
localNames = new String[0];
|
|
||||||
captureNames = new String[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FunctionMapBuilder builder() {
|
|
||||||
return new FunctionMapBuilder();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,57 +3,51 @@ package me.topchetoeu.jscript.common.parsing;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class Filename {
|
public class Filename {
|
||||||
public final String protocol;
|
public final String protocol;
|
||||||
public final String path;
|
public final String path;
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override public String toString() {
|
||||||
return protocol + "://" + path;
|
return protocol + "://" + path;
|
||||||
}
|
}
|
||||||
@Override public int hashCode() {
|
@Override public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + protocol.hashCode();
|
result = prime * result + protocol.hashCode();
|
||||||
result = prime * result + path.hashCode();
|
result = prime * result + path.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@Override public boolean equals(Object obj) {
|
@Override public boolean equals(Object obj) {
|
||||||
if (this == obj) return true;
|
if (this == obj) return true;
|
||||||
if (obj == null) return false;
|
if (obj == null) return false;
|
||||||
if (getClass() != obj.getClass()) return false;
|
if (getClass() != obj.getClass()) return false;
|
||||||
|
|
||||||
var other = (Filename) obj;
|
var other = (Filename) obj;
|
||||||
|
|
||||||
if (protocol == null) {
|
if (protocol == null) {
|
||||||
if (other.protocol != null) return false;
|
if (other.protocol != null) return false;
|
||||||
}
|
}
|
||||||
else if (!protocol.equals(other.protocol)) return false;
|
else if (!protocol.equals(other.protocol)) return false;
|
||||||
|
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
if (other.path != null) return false;
|
if (other.path != null) return false;
|
||||||
}
|
}
|
||||||
else if (!path.equals(other.path)) return false;
|
else if (!path.equals(other.path)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Filename(String protocol, String path) {
|
public Filename(String protocol, String path) {
|
||||||
path = path.trim();
|
path = path.trim();
|
||||||
protocol = protocol.trim();
|
protocol = protocol.trim();
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Filename parse(String val) {
|
public static Filename parse(String val) {
|
||||||
var i = val.indexOf("://");
|
var i = val.indexOf("://");
|
||||||
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
|
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
|
||||||
else return new Filename("file", val.trim());
|
else return new Filename("file", val.trim());
|
||||||
}
|
}
|
||||||
// public static Path normalize(String path) {
|
public static Filename fromFile(File file) {
|
||||||
// // File file = new File("/" + path.trim().replace("\\", "/"));
|
return new Filename("file", file.getAbsolutePath());
|
||||||
// // String normalizedPath = new File("/" + path.trim().replace("\\", "/")).getAbsolutePath().replaceFirst("^/", "").replace("\\", "/");
|
}
|
||||||
// // return normalizedPath;
|
|
||||||
// return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
|
|
||||||
// }
|
|
||||||
public static Filename fromFile(File file) {
|
|
||||||
return new Filename("file", file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,105 +4,105 @@ import java.util.ArrayList;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public abstract class Location implements Comparable<Location> {
|
public abstract class Location implements Comparable<Location> {
|
||||||
public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1);
|
public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1);
|
||||||
|
|
||||||
public abstract int line();
|
public abstract int line();
|
||||||
public abstract int start();
|
public abstract int start();
|
||||||
public abstract Filename filename();
|
public abstract Filename filename();
|
||||||
|
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
var res = new ArrayList<String>();
|
var res = new ArrayList<String>();
|
||||||
|
|
||||||
if (filename() != null) res.add(filename().toString());
|
if (filename() != null) res.add(filename().toString());
|
||||||
if (line() >= 0) res.add(line() + 1 + "");
|
if (line() >= 0) res.add(line() + 1 + "");
|
||||||
if (start() >= 0) res.add(start() + 1 + "");
|
if (start() >= 0) res.add(start() + 1 + "");
|
||||||
|
|
||||||
return String.join(":", res);
|
return String.join(":", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Location add(int n) {
|
public final Location add(int n) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
return new Location() {
|
return new Location() {
|
||||||
@Override public Filename filename() { return self.filename(); }
|
@Override public Filename filename() { return self.filename(); }
|
||||||
@Override public int start() { return self.start() + n; }
|
@Override public int start() { return self.start() + n; }
|
||||||
@Override public int line() { return self.line(); }
|
@Override public int line() { return self.line(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public final Location nextLine() {
|
public final Location nextLine() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
return new Location() {
|
return new Location() {
|
||||||
@Override public Filename filename() { return self.filename(); }
|
@Override public Filename filename() { return self.filename(); }
|
||||||
@Override public int start() { return 0; }
|
@Override public int start() { return 0; }
|
||||||
@Override public int line() { return self.line() + 1; }
|
@Override public int line() { return self.line() + 1; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override public int hashCode() {
|
||||||
return Objects.hash(line(), start(), filename());
|
return Objects.hash(line(), start(), filename());
|
||||||
}
|
}
|
||||||
@Override public boolean equals(Object obj) {
|
@Override public boolean equals(Object obj) {
|
||||||
if (this == obj) return true;
|
if (this == obj) return true;
|
||||||
if (!(obj instanceof Location)) return false;
|
if (!(obj instanceof Location)) return false;
|
||||||
var other = (Location)obj;
|
var other = (Location)obj;
|
||||||
|
|
||||||
if (!Objects.equals(this.start(), other.start())) return false;
|
if (!Objects.equals(this.start(), other.start())) return false;
|
||||||
if (!Objects.equals(this.line(), other.line())) return false;
|
if (!Objects.equals(this.line(), other.line())) return false;
|
||||||
if (!Objects.equals(this.filename(), other.filename())) return false;
|
if (!Objects.equals(this.filename(), other.filename())) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int compareTo(Location other) {
|
@Override public int compareTo(Location other) {
|
||||||
int a = filename().toString().compareTo(other.filename().toString());
|
int a = filename().toString().compareTo(other.filename().toString());
|
||||||
int b = Integer.compare(line(), other.line());
|
int b = Integer.compare(line(), other.line());
|
||||||
int c = Integer.compare(start(), other.start());
|
int c = Integer.compare(start(), other.start());
|
||||||
|
|
||||||
if (a != 0) return a;
|
if (a != 0) return a;
|
||||||
if (b != 0) return b;
|
if (b != 0) return b;
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Location of(Filename filename, int line, int start) {
|
public static Location of(Filename filename, int line, int start) {
|
||||||
return new Location() {
|
return new Location() {
|
||||||
@Override public Filename filename() { return filename; }
|
@Override public Filename filename() { return filename; }
|
||||||
@Override public int start() { return start; }
|
@Override public int start() { return start; }
|
||||||
@Override public int line() { return line; }
|
@Override public int line() { return line; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Location of(String raw) {
|
public static Location of(String raw) {
|
||||||
var i0 = raw.lastIndexOf(':');
|
var i0 = raw.lastIndexOf(':');
|
||||||
if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1);
|
if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1);
|
||||||
|
|
||||||
var i1 = raw.lastIndexOf(':', i0);
|
var i1 = raw.lastIndexOf(':', i0);
|
||||||
if (i0 < 0) {
|
if (i0 < 0) {
|
||||||
try {
|
try {
|
||||||
return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1);
|
return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1);
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
return Location.of(Filename.parse(raw), -1, -1);
|
return Location.of(Filename.parse(raw), -1, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int start, line;
|
int start, line;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
start = Integer.parseInt(raw.substring(i1 + 1));
|
start = Integer.parseInt(raw.substring(i1 + 1));
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
return Location.of(Filename.parse(raw), -1, -1);
|
return Location.of(Filename.parse(raw), -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
line = Integer.parseInt(raw.substring(i0 + 1, i1));
|
line = Integer.parseInt(raw.substring(i0 + 1, i1));
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1);
|
return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Location.of(Filename.parse(raw.substring(0, i0)), start, line);
|
return Location.of(Filename.parse(raw.substring(0, i0)), start, line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +1,80 @@
|
|||||||
package me.topchetoeu.jscript.common.parsing;
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
public class ParseRes<T> {
|
public class ParseRes<T> {
|
||||||
public static enum State {
|
public static enum State {
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
FAILED,
|
FAILED,
|
||||||
ERROR;
|
ERROR;
|
||||||
|
|
||||||
public boolean isSuccess() { return this == SUCCESS; }
|
public boolean isSuccess() { return this == SUCCESS; }
|
||||||
public boolean isFailed() { return this == FAILED; }
|
public boolean isFailed() { return this == FAILED; }
|
||||||
public boolean isError() { return this == ERROR; }
|
public boolean isError() { return this == ERROR; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public final ParseRes.State state;
|
public final ParseRes.State state;
|
||||||
public final Location errorLocation;
|
public final Location errorLocation;
|
||||||
public final String error;
|
public final String error;
|
||||||
public final T result;
|
public final T result;
|
||||||
public final int n;
|
public final int n;
|
||||||
|
|
||||||
private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) {
|
private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.n = readN;
|
this.n = readN;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.error = error;
|
this.error = error;
|
||||||
this.errorLocation = errorLocation;
|
this.errorLocation = errorLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParseRes<T> setN(int i) {
|
public ParseRes<T> setN(int i) {
|
||||||
if (!state.isSuccess()) return this;
|
if (!state.isSuccess()) return this;
|
||||||
return new ParseRes<>(state, null, null, result, i);
|
return new ParseRes<>(state, null, null, result, i);
|
||||||
}
|
}
|
||||||
public ParseRes<T> addN(int n) {
|
public ParseRes<T> addN(int n) {
|
||||||
if (!state.isSuccess()) return this;
|
if (!state.isSuccess()) return this;
|
||||||
return new ParseRes<>(state, null, null, result, this.n + n);
|
return new ParseRes<>(state, null, null, result, this.n + n);
|
||||||
}
|
}
|
||||||
public <T2> ParseRes<T2> chainError() {
|
public <T2> ParseRes<T2> chainError() {
|
||||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed");
|
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed");
|
||||||
return new ParseRes<>(state, errorLocation, error, null, 0);
|
return new ParseRes<>(state, errorLocation, error, null, 0);
|
||||||
}
|
}
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T2> ParseRes<T2> chainError(ParseRes<?> other) {
|
public <T2> ParseRes<T2> chainError(ParseRes<?> other) {
|
||||||
if (!this.isError()) return other.chainError();
|
if (!this.isError()) return other.chainError();
|
||||||
return (ParseRes<T2>) this;
|
return (ParseRes<T2>) this;
|
||||||
}
|
}
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T2> ParseRes<T2> chainError(Location loc, String error) {
|
public <T2> ParseRes<T2> chainError(Location loc, String error) {
|
||||||
if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
||||||
return (ParseRes<T2>) this;
|
return (ParseRes<T2>) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSuccess() { return state.isSuccess(); }
|
public boolean isSuccess() { return state.isSuccess(); }
|
||||||
public boolean isFailed() { return state.isFailed(); }
|
public boolean isFailed() { return state.isFailed(); }
|
||||||
public boolean isError() { return state.isError(); }
|
public boolean isError() { return state.isError(); }
|
||||||
|
|
||||||
public static <T> ParseRes<T> failed() {
|
public static <T> ParseRes<T> failed() {
|
||||||
return new ParseRes<T>(State.FAILED, null, null, null, 0);
|
return new ParseRes<T>(State.FAILED, null, null, null, 0);
|
||||||
}
|
}
|
||||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||||
// TODO: differentiate definitive and probable errors
|
// TODO: differentiate definitive and probable errors
|
||||||
return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
||||||
}
|
}
|
||||||
public static <T> ParseRes<T> res(T val, int i) {
|
public static <T> ParseRes<T> res(T val, int i) {
|
||||||
return new ParseRes<>(State.SUCCESS, null, null, val, i);
|
return new ParseRes<>(State.SUCCESS, null, null, val, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
public static <T> ParseRes<T> first(Source src, int i, Parser ...parsers) {
|
public static <T> ParseRes<T> first(Source src, int i, Parser ...parsers) {
|
||||||
int n = Parsing.skipEmpty(src, i);
|
int n = Parsing.skipEmpty(src, i);
|
||||||
ParseRes<T> error = ParseRes.failed();
|
ParseRes<T> error = ParseRes.failed();
|
||||||
|
|
||||||
for (var parser : parsers) {
|
for (var parser : parsers) {
|
||||||
var res = parser.parse(src, i + n);
|
var res = parser.parse(src, i + n);
|
||||||
if (res.isSuccess()) return res.addN(n);
|
if (res.isSuccess()) return res.addN(n);
|
||||||
if (res.isError() && error.isFailed()) error = res.chainError();
|
if (res.isError() && error.isFailed()) error = res.chainError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
package me.topchetoeu.jscript.common.parsing;
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
public interface Parser<T> {
|
public interface Parser<T> {
|
||||||
public ParseRes<T> parse(Source src, int i);
|
public ParseRes<T> parse(Source src, int i);
|
||||||
}
|
}
|
@ -4,418 +4,418 @@ import me.topchetoeu.jscript.common.SyntaxException;
|
|||||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
|
||||||
public class Parsing {
|
public class Parsing {
|
||||||
public static boolean isDigit(Character c) {
|
public static boolean isDigit(Character c) {
|
||||||
return c != null && c >= '0' && c <= '9';
|
return c != null && c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
public static boolean isAny(char c, String alphabet) {
|
public static boolean isAny(char c, String alphabet) {
|
||||||
return alphabet.contains(Character.toString(c));
|
return alphabet.contains(Character.toString(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int fromHex(char 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 >= 'a' && c <= 'f') return c - 'a' + 10;
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
if (c >= '0' && c <= '9') return c - '0';
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int skipEmpty(Source src, int i) {
|
public static int skipEmpty(Source src, int i) {
|
||||||
return skipEmpty(src, i, true);
|
return skipEmpty(src, i, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int skipEmpty(Source src, int i, boolean noComments) {
|
public static int skipEmpty(Source src, int i, boolean noComments) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
if (i == 0 && src.is(0, "#!")) {
|
if (i == 0 && src.is(0, "#!")) {
|
||||||
while (!src.is(n, '\n')) n++;
|
while (!src.is(n, '\n')) n++;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSingle = false;
|
var isSingle = false;
|
||||||
var isMulti = false;
|
var isMulti = false;
|
||||||
|
|
||||||
while (i + n < src.size()) {
|
while (i + n < src.size()) {
|
||||||
if (isSingle) {
|
if (isSingle) {
|
||||||
if (src.is(i + n, '\n')) {
|
if (src.is(i + n, '\n')) {
|
||||||
n++;
|
n++;
|
||||||
isSingle = false;
|
isSingle = false;
|
||||||
}
|
}
|
||||||
else n++;
|
else n++;
|
||||||
}
|
}
|
||||||
else if (isMulti) {
|
else if (isMulti) {
|
||||||
if (src.is(i + n, "*/")) {
|
if (src.is(i + n, "*/")) {
|
||||||
n += 2;
|
n += 2;
|
||||||
isMulti = false;
|
isMulti = false;
|
||||||
}
|
}
|
||||||
else n++;
|
else n++;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, "//")) {
|
else if (src.is(i + n, "//")) {
|
||||||
n += 2;
|
n += 2;
|
||||||
isSingle = true;
|
isSingle = true;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, "/*")) {
|
else if (src.is(i + n, "/*")) {
|
||||||
n += 2;
|
n += 2;
|
||||||
isMulti = true;
|
isMulti = true;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, Character::isWhitespace)) {
|
else if (src.is(i + n, Character::isWhitespace)) {
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
else break;
|
else break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Character> parseChar(Source src, int i) {
|
public static ParseRes<Character> parseChar(Source src, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
if (src.is(i + n, '\\')) {
|
if (src.is(i + n, '\\')) {
|
||||||
n++;
|
n++;
|
||||||
char c = src.at(i + n++);
|
char c = src.at(i + n++);
|
||||||
|
|
||||||
if (c == 'b') return ParseRes.res('\b', n);
|
if (c == 'b') return ParseRes.res('\b', n);
|
||||||
else if (c == 't') return ParseRes.res('\t', n);
|
else if (c == 't') return ParseRes.res('\t', n);
|
||||||
else if (c == 'n') return ParseRes.res('\n', n);
|
else if (c == 'n') return ParseRes.res('\n', n);
|
||||||
else if (c == 'f') return ParseRes.res('\f', n);
|
else if (c == 'f') return ParseRes.res('\f', n);
|
||||||
else if (c == 'r') return ParseRes.res('\r', n);
|
else if (c == 'r') return ParseRes.res('\r', n);
|
||||||
else if (c == '0') {
|
else if (c == '0') {
|
||||||
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
|
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 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 >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
|
||||||
else if (c == 'x') {
|
else if (c == 'x') {
|
||||||
var newC = 0;
|
var newC = 0;
|
||||||
|
|
||||||
for (var j = 0; j < 2; j++) {
|
for (var j = 0; j < 2; j++) {
|
||||||
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence");
|
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence");
|
||||||
|
|
||||||
int val = fromHex(src.at(i + n));
|
int val = fromHex(src.at(i + n));
|
||||||
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence");
|
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
newC = (newC << 4) | val;
|
newC = (newC << 4) | val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res((char)newC, n);
|
return ParseRes.res((char)newC, n);
|
||||||
}
|
}
|
||||||
else if (c == 'u') {
|
else if (c == 'u') {
|
||||||
var newC = 0;
|
var newC = 0;
|
||||||
|
|
||||||
for (var j = 0; j < 4; j++) {
|
for (var j = 0; j < 4; j++) {
|
||||||
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
|
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
|
||||||
|
|
||||||
int val = fromHex(src.at(i + n));
|
int val = fromHex(src.at(i + n));
|
||||||
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
|
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
newC = (newC << 4) | val;
|
newC = (newC << 4) | val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res((char)newC, n);
|
return ParseRes.res((char)newC, n);
|
||||||
}
|
}
|
||||||
else if (c == '\n') return ParseRes.res(null, n);
|
else if (c == '\n') return ParseRes.res(null, n);
|
||||||
else n--;
|
else n--;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(src.at(i + n), n + 1);
|
return ParseRes.res(src.at(i + n), n + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<String> parseIdentifier(Source src, int i) {
|
public static ParseRes<String> parseIdentifier(Source src, int i) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
var res = new StringBuilder();
|
var res = new StringBuilder();
|
||||||
var first = true;
|
var first = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (i + n > src.size()) break;
|
if (i + n > src.size()) break;
|
||||||
char c = src.at(i + n, '\0');
|
char c = src.at(i + n, '\0');
|
||||||
|
|
||||||
if (first && Parsing.isDigit(c)) break;
|
if (first && Parsing.isDigit(c)) break;
|
||||||
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
|
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
|
||||||
res.append(c);
|
res.append(c);
|
||||||
n++;
|
n++;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.length() <= 0) return ParseRes.failed();
|
if (res.length() <= 0) return ParseRes.failed();
|
||||||
else return ParseRes.res(res.toString(), n);
|
else return ParseRes.res(res.toString(), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
|
public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
var res = new StringBuilder();
|
var res = new StringBuilder();
|
||||||
var first = true;
|
var first = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (i + n > src.size()) break;
|
if (i + n > src.size()) break;
|
||||||
char c = src.at(i + n, '\0');
|
char c = src.at(i + n, '\0');
|
||||||
|
|
||||||
if (first && Parsing.isDigit(c)) break;
|
if (first && Parsing.isDigit(c)) break;
|
||||||
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
|
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
|
||||||
res.append(c);
|
res.append(c);
|
||||||
n++;
|
n++;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.length() <= 0) return ParseRes.failed();
|
if (res.length() <= 0) return ParseRes.failed();
|
||||||
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
|
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
public static boolean isIdentifier(Source src, int i, String test) {
|
public static boolean isIdentifier(Source src, int i, String test) {
|
||||||
return parseIdentifier(src, i, test).isSuccess();
|
return parseIdentifier(src, i, test).isSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<String> parseOperator(Source src, int i, String op) {
|
public static ParseRes<String> parseOperator(Source src, int i, String op) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
|
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParseRes<Double> parseHex(Source src, int i) {
|
private static ParseRes<Double> parseHex(Source src, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
double res = 0;
|
double res = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int digit = Parsing.fromHex(src.at(i + n, '\0'));
|
int digit = Parsing.fromHex(src.at(i + n, '\0'));
|
||||||
if (digit < 0) {
|
if (digit < 0) {
|
||||||
if (n <= 0) return ParseRes.failed();
|
if (n <= 0) return ParseRes.failed();
|
||||||
else return ParseRes.res(res, n);
|
else return ParseRes.res(res, n);
|
||||||
}
|
}
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
res *= 16;
|
res *= 16;
|
||||||
res += digit;
|
res += digit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static ParseRes<Double> parseOct(Source src, int i) {
|
private static ParseRes<Double> parseOct(Source src, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
double res = 0;
|
double res = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int digit = src.at(i + n, '\0') - '0';
|
int digit = src.at(i + n, '\0') - '0';
|
||||||
if (digit < 0 || digit > 9) break;
|
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 > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit);
|
||||||
|
|
||||||
if (digit < 0) {
|
if (digit < 0) {
|
||||||
if (n <= 0) return ParseRes.failed();
|
if (n <= 0) return ParseRes.failed();
|
||||||
else return ParseRes.res(res, n);
|
else return ParseRes.res(res, n);
|
||||||
}
|
}
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
res *= 8;
|
res *= 8;
|
||||||
res += digit;
|
res += digit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(res, n);
|
return ParseRes.res(res, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<String> parseString(Source src, int i) {
|
public static ParseRes<String> parseString(Source src, int i) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
char quote;
|
char quote;
|
||||||
|
|
||||||
if (src.is(i + n, '\'')) quote = '\'';
|
if (src.is(i + n, '\'')) quote = '\'';
|
||||||
else if (src.is(i + n, '"')) quote = '"';
|
else if (src.is(i + n, '"')) quote = '"';
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var res = new StringBuilder();
|
var res = new StringBuilder();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
|
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
|
||||||
if (src.is(i + n, quote)) {
|
if (src.is(i + n, quote)) {
|
||||||
n++;
|
n++;
|
||||||
return ParseRes.res(res.toString(), n);
|
return ParseRes.res(res.toString(), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
var charRes = parseChar(src, i + n);
|
var charRes = parseChar(src, i + n);
|
||||||
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
|
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
|
||||||
n += charRes.n;
|
n += charRes.n;
|
||||||
|
|
||||||
if (charRes.result != null) res.append(charRes.result);
|
if (charRes.result != null) res.append(charRes.result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
|
public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
double whole = 0;
|
double whole = 0;
|
||||||
double fract = 0;
|
double fract = 0;
|
||||||
long exponent = 0;
|
long exponent = 0;
|
||||||
boolean parsedAny = false;
|
boolean parsedAny = false;
|
||||||
boolean negative = false;
|
boolean negative = false;
|
||||||
|
|
||||||
if (withMinus && src.is(i + n, "-")) {
|
if (withMinus && src.is(i + n, "-")) {
|
||||||
negative = true;
|
negative = true;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
|
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = parseHex(src, i + n);
|
var res = parseHex(src, i + n);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
if (negative) return ParseRes.res(-res.result, n);
|
if (negative) return ParseRes.res(-res.result, n);
|
||||||
else 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")) {
|
else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = parseOct(src, i + n);
|
var res = parseOct(src, i + n);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
if (negative) return ParseRes.res(-res.result, n);
|
if (negative) return ParseRes.res(-res.result, n);
|
||||||
else return ParseRes.res(res.result, n);
|
else return ParseRes.res(res.result, n);
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, '0')) {
|
else if (src.is(i + n, '0')) {
|
||||||
n++;
|
n++;
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed");
|
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)) {
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
whole *= 10;
|
whole *= 10;
|
||||||
whole += src.at(i + n++) - '0';
|
whole += src.at(i + n++) - '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.is(i + n, '.')) {
|
if (src.is(i + n, '.')) {
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
while (src.is(i + n, Parsing::isDigit)) {
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
fract += src.at(i + n++) - '0';
|
fract += src.at(i + n++) - '0';
|
||||||
fract /= 10;
|
fract /= 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
|
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
|
||||||
n++;
|
n++;
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
boolean expNegative = false;
|
boolean expNegative = false;
|
||||||
boolean parsedE = false;
|
boolean parsedE = false;
|
||||||
|
|
||||||
if (src.is(i + n, '-')) {
|
if (src.is(i + n, '-')) {
|
||||||
expNegative = true;
|
expNegative = true;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, '+')) n++;
|
else if (src.is(i + n, '+')) n++;
|
||||||
|
|
||||||
while (src.is(i + n, Parsing::isDigit)) {
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
parsedE = true;
|
parsedE = true;
|
||||||
exponent *= 10;
|
exponent *= 10;
|
||||||
|
|
||||||
if (expNegative) exponent -= src.at(i + n++) - '0';
|
if (expNegative) exponent -= src.at(i + n++) - '0';
|
||||||
else 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 (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parsedAny) {
|
if (!parsedAny) {
|
||||||
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) {
|
public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
double whole = 0;
|
double whole = 0;
|
||||||
double fract = 0;
|
double fract = 0;
|
||||||
long exponent = 0;
|
long exponent = 0;
|
||||||
boolean parsedAny = false;
|
boolean parsedAny = false;
|
||||||
boolean negative = false;
|
boolean negative = false;
|
||||||
|
|
||||||
if (withMinus && src.is(i + n, "-")) {
|
if (withMinus && src.is(i + n, "-")) {
|
||||||
negative = true;
|
negative = true;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (src.is(i + n, Parsing::isDigit)) {
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
whole *= 10;
|
whole *= 10;
|
||||||
whole += src.at(i + n++) - '0';
|
whole += src.at(i + n++) - '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.is(i + n, '.')) {
|
if (src.is(i + n, '.')) {
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
while (src.is(i + n, Parsing::isDigit)) {
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
fract += src.at(i + n++) - '0';
|
fract += src.at(i + n++) - '0';
|
||||||
fract /= 10;
|
fract /= 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
|
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
|
||||||
n++;
|
n++;
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
boolean expNegative = false;
|
boolean expNegative = false;
|
||||||
boolean parsedE = false;
|
boolean parsedE = false;
|
||||||
|
|
||||||
if (src.is(i + n, '-')) {
|
if (src.is(i + n, '-')) {
|
||||||
expNegative = true;
|
expNegative = true;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, '+')) n++;
|
else if (src.is(i + n, '+')) n++;
|
||||||
|
|
||||||
while (src.is(i + n, Parsing::isDigit)) {
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
parsedE = true;
|
parsedE = true;
|
||||||
exponent *= 10;
|
exponent *= 10;
|
||||||
|
|
||||||
if (expNegative) exponent -= src.at(i + n++) - '0';
|
if (expNegative) exponent -= src.at(i + n++) - '0';
|
||||||
else 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 (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parsedAny) {
|
if (!parsedAny) {
|
||||||
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
|
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
|
||||||
var n = skipEmpty(src, i);
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
double result = 0;
|
double result = 0;
|
||||||
boolean parsedAny = false;
|
boolean parsedAny = false;
|
||||||
boolean negative = false;
|
boolean negative = false;
|
||||||
|
|
||||||
if (withMinus && src.is(i + n, "-")) {
|
if (withMinus && src.is(i + n, "-")) {
|
||||||
negative = true;
|
negative = true;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
|
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = parseHex(src, i);
|
var res = parseHex(src, i);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
if (negative) return ParseRes.res(-res.result, n);
|
if (negative) return ParseRes.res(-res.result, n);
|
||||||
else return ParseRes.res(res.result, n);
|
else return ParseRes.res(res.result, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n)));
|
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n)));
|
||||||
if (digit < 0) break;
|
if (digit < 0) break;
|
||||||
|
|
||||||
parsedAny = true;
|
parsedAny = true;
|
||||||
result += digit;
|
result += digit;
|
||||||
result *= alphabet.length();
|
result *= alphabet.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parsedAny) {
|
if (!parsedAny) {
|
||||||
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
else if (negative) return ParseRes.res(-result, n);
|
else if (negative) return ParseRes.res(-result, n);
|
||||||
else return ParseRes.res(-result, n);
|
else return ParseRes.res(-result, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,71 +5,71 @@ import java.util.function.Predicate;
|
|||||||
import me.topchetoeu.jscript.common.environment.Environment;
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
|
||||||
public class Source {
|
public class Source {
|
||||||
public final Environment env;
|
public final Environment env;
|
||||||
public final Filename filename;
|
public final Filename filename;
|
||||||
public final String src;
|
public final String src;
|
||||||
|
|
||||||
private int[] lineStarts;
|
private int[] lineStarts;
|
||||||
|
|
||||||
public Location loc(int offset) {
|
public Location loc(int offset) {
|
||||||
return new SourceLocation(filename, lineStarts, offset);
|
return new SourceLocation(filename, lineStarts, offset);
|
||||||
}
|
}
|
||||||
public boolean is(int i, char c) {
|
public boolean is(int i, char c) {
|
||||||
return i >= 0 && i < src.length() && src.charAt(i) == c;
|
return i >= 0 && i < src.length() && src.charAt(i) == c;
|
||||||
}
|
}
|
||||||
public boolean is(int i, String src) {
|
public boolean is(int i, String src) {
|
||||||
if (i < 0 || i + src.length() > size()) return false;
|
if (i < 0 || i + src.length() > size()) return false;
|
||||||
|
|
||||||
for (int j = 0; j < src.length(); j++) {
|
for (int j = 0; j < src.length(); j++) {
|
||||||
if (at(i + j) != src.charAt(j)) return false;
|
if (at(i + j) != src.charAt(j)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public boolean is(int i, Predicate<Character> predicate) {
|
public boolean is(int i, Predicate<Character> predicate) {
|
||||||
if (i < 0 || i >= src.length()) return false;
|
if (i < 0 || i >= src.length()) return false;
|
||||||
return predicate.test(at(i));
|
return predicate.test(at(i));
|
||||||
}
|
}
|
||||||
public char at(int i) {
|
public char at(int i) {
|
||||||
return src.charAt(i);
|
return src.charAt(i);
|
||||||
}
|
}
|
||||||
public char at(int i, char defaultVal) {
|
public char at(int i, char defaultVal) {
|
||||||
if (i < 0 || i >= src.length()) return defaultVal;
|
if (i < 0 || i >= src.length()) return defaultVal;
|
||||||
else return src.charAt(i);
|
else return src.charAt(i);
|
||||||
}
|
}
|
||||||
public int size() {
|
public int size() {
|
||||||
return src.length();
|
return src.length();
|
||||||
}
|
}
|
||||||
public String slice(int start, int end) {
|
public String slice(int start, int end) {
|
||||||
return src.substring(start, end);
|
return src.substring(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Source(Environment env, Filename filename, String src) {
|
public Source(Environment env, Filename filename, String src) {
|
||||||
if (env == null) this.env = new Environment();
|
if (env == null) this.env = new Environment();
|
||||||
else this.env = env;
|
else this.env = env;
|
||||||
|
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.src = src;
|
this.src = src;
|
||||||
|
|
||||||
int n = 1;
|
int n = 1;
|
||||||
lineStarts = new int[16];
|
lineStarts = new int[16];
|
||||||
lineStarts[0] = 0;
|
lineStarts[0] = 0;
|
||||||
|
|
||||||
for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) {
|
for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) {
|
||||||
if (n >= lineStarts.length) {
|
if (n >= lineStarts.length) {
|
||||||
var newArr = new int[lineStarts.length * 2];
|
var newArr = new int[lineStarts.length * 2];
|
||||||
System.arraycopy(lineStarts, 0, newArr, 0, n);
|
System.arraycopy(lineStarts, 0, newArr, 0, n);
|
||||||
lineStarts = newArr;
|
lineStarts = newArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
lineStarts[n++] = i + 1;
|
lineStarts[n++] = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newArr = new int[n];
|
var newArr = new int[n];
|
||||||
System.arraycopy(lineStarts, 0, newArr, 0, n);
|
System.arraycopy(lineStarts, 0, newArr, 0, n);
|
||||||
lineStarts = newArr;
|
lineStarts = newArr;
|
||||||
}
|
}
|
||||||
public Source(String src) {
|
public Source(String src) {
|
||||||
this(null, null, src);
|
this(null, null, src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,64 +3,64 @@ package me.topchetoeu.jscript.common.parsing;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class SourceLocation extends Location {
|
public class SourceLocation extends Location {
|
||||||
private int[] lineStarts;
|
private int[] lineStarts;
|
||||||
private int line;
|
private int line;
|
||||||
private int start;
|
private int start;
|
||||||
private final Filename filename;
|
private final Filename filename;
|
||||||
private final int offset;
|
private final int offset;
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
if (lineStarts == null) return;
|
if (lineStarts == null) return;
|
||||||
|
|
||||||
int a = 0;
|
int a = 0;
|
||||||
int b = lineStarts.length;
|
int b = lineStarts.length;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (a + 1 >= b) break;
|
if (a + 1 >= b) break;
|
||||||
var mid = -((-a - b) >> 1);
|
var mid = -((-a - b) >> 1);
|
||||||
var el = lineStarts[mid];
|
var el = lineStarts[mid];
|
||||||
|
|
||||||
if (el < offset) a = mid;
|
if (el < offset) a = mid;
|
||||||
else if (el > offset) b = mid;
|
else if (el > offset) b = mid;
|
||||||
else {
|
else {
|
||||||
this.line = mid;
|
this.line = mid;
|
||||||
this.start = 0;
|
this.start = 0;
|
||||||
this.lineStarts = null;
|
this.lineStarts = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.line = a;
|
this.line = a;
|
||||||
this.start = offset - lineStarts[a];
|
this.start = offset - lineStarts[a];
|
||||||
this.lineStarts = null;
|
this.lineStarts = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Filename filename() { return filename; }
|
@Override public Filename filename() { return filename; }
|
||||||
@Override public int line() {
|
@Override public int line() {
|
||||||
update();
|
update();
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@Override public int start() {
|
@Override public int start() {
|
||||||
update();
|
update();
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override public int hashCode() {
|
||||||
return Objects.hash(offset);
|
return Objects.hash(offset);
|
||||||
}
|
}
|
||||||
@Override public int compareTo(Location other) {
|
@Override public int compareTo(Location other) {
|
||||||
if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset);
|
if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset);
|
||||||
else return super.compareTo(other);
|
else return super.compareTo(other);
|
||||||
}
|
}
|
||||||
@Override public boolean equals(Object obj) {
|
@Override public boolean equals(Object obj) {
|
||||||
if (obj instanceof SourceLocation other) return this.offset == other.offset;
|
if (obj instanceof SourceLocation other) return this.offset == other.offset;
|
||||||
else return super.equals(obj);
|
else return super.equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceLocation(Filename filename, int[] lineStarts, int offset) {
|
public SourceLocation(Filename filename, int[] lineStarts, int offset) {
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.lineStarts = lineStarts;
|
this.lineStarts = lineStarts;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,69 +20,69 @@ import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
|||||||
public final class CompileResult {
|
public final class CompileResult {
|
||||||
public static final Key<Void> DEBUG_LOG = new Key<>();
|
public static final Key<Void> DEBUG_LOG = new Key<>();
|
||||||
|
|
||||||
public final List<Instruction> instructions;
|
public final List<Instruction> instructions;
|
||||||
public final List<CompileResult> children;
|
public final List<CompileResult> children;
|
||||||
public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
|
public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
|
||||||
public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
|
public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
|
||||||
public final FunctionMapBuilder map;
|
public final FunctionMapBuilder map;
|
||||||
public final Environment env;
|
public final Environment env;
|
||||||
public int length;
|
public int length;
|
||||||
public final FunctionScope scope;
|
public final FunctionScope scope;
|
||||||
|
|
||||||
public int temp() {
|
public int temp() {
|
||||||
instructions.add(null);
|
instructions.add(null);
|
||||||
return instructions.size() - 1;
|
return instructions.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult add(Instruction instr) {
|
public CompileResult add(Instruction instr) {
|
||||||
instructions.add(instr);
|
instructions.add(instr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public CompileResult set(int i, Instruction instr) {
|
public CompileResult set(int i, Instruction instr) {
|
||||||
instructions.set(i, instr);
|
instructions.set(i, instr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() { return instructions.size(); }
|
public int size() { return instructions.size(); }
|
||||||
|
|
||||||
public void setDebug(Location loc, BreakpointType type) {
|
public void setDebug(Location loc, BreakpointType type) {
|
||||||
map.setDebug(loc, type);
|
map.setDebug(loc, type);
|
||||||
}
|
}
|
||||||
public void setLocation(int i, Location loc) {
|
public void setLocation(int i, Location loc) {
|
||||||
map.setLocation(i, loc);
|
map.setLocation(i, loc);
|
||||||
}
|
}
|
||||||
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
|
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
|
||||||
map.setLocationAndDebug(i, loc, type);
|
map.setLocationAndDebug(i, loc, type);
|
||||||
}
|
}
|
||||||
public void setDebug(BreakpointType type) {
|
public void setDebug(BreakpointType type) {
|
||||||
setDebug(map.last(), type);
|
setDebug(map.last(), type);
|
||||||
}
|
}
|
||||||
public void setLocation(Location type) {
|
public void setLocation(Location type) {
|
||||||
setLocation(instructions.size() - 1, type);
|
setLocation(instructions.size() - 1, type);
|
||||||
}
|
}
|
||||||
public void setLocationAndDebug(Location loc, BreakpointType type) {
|
public void setLocationAndDebug(Location loc, BreakpointType type) {
|
||||||
setLocationAndDebug(instructions.size() - 1, loc, type);
|
setLocationAndDebug(instructions.size() - 1, loc, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult addChild(FunctionNode node, CompileResult res) {
|
public CompileResult addChild(FunctionNode node, CompileResult res) {
|
||||||
this.children.add(res);
|
this.children.add(res);
|
||||||
this.childrenMap.put(node, res);
|
this.childrenMap.put(node, res);
|
||||||
this.childrenIndices.put(node, this.children.size() - 1);
|
this.childrenIndices.put(node, this.children.size() - 1);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instruction[] instructions() {
|
public Instruction[] instructions() {
|
||||||
return instructions.toArray(new Instruction[0]);
|
return instructions.toArray(new Instruction[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionMap map() {
|
public FunctionMap map() {
|
||||||
return map.build(scope.localNames(), scope.captureNames());
|
return map.build(scope.localNames(), scope.captureNames());
|
||||||
}
|
}
|
||||||
public FunctionBody body() {
|
public FunctionBody body() {
|
||||||
var builtChildren = new FunctionBody[children.size()];
|
var builtChildren = new FunctionBody[children.size()];
|
||||||
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
|
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
|
||||||
|
|
||||||
var instrRes = instructions();
|
var instrRes = instructions();
|
||||||
|
|
||||||
if (env.has(DEBUG_LOG)) {
|
if (env.has(DEBUG_LOG)) {
|
||||||
System.out.println("================= BODY =================");
|
System.out.println("================= BODY =================");
|
||||||
@ -93,43 +93,43 @@ public final class CompileResult {
|
|||||||
for (var instr : instrRes) System.out.println(instr);
|
for (var instr : instrRes) System.out.println(instr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FunctionBody(
|
return new FunctionBody(
|
||||||
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
|
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
|
||||||
length, instrRes, builtChildren
|
length, instrRes, builtChildren
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult subtarget() {
|
public CompileResult subtarget() {
|
||||||
return new CompileResult(env, new FunctionScope(scope), this);
|
return new CompileResult(env, new FunctionScope(scope), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult setEnvironment(Environment env) {
|
public CompileResult setEnvironment(Environment env) {
|
||||||
return new CompileResult(env, scope, this);
|
return new CompileResult(env, scope, this);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns a compile result with a child of the environment that relates to the given key.
|
* Returns a compile result with a child of the environment that relates to the given key.
|
||||||
* In essence, this is used to create a compile result which is back at the root environment of the compilation
|
* In essence, this is used to create a compile result which is back at the root environment of the compilation
|
||||||
*/
|
*/
|
||||||
public CompileResult rootEnvironment(Key<Environment> env) {
|
public CompileResult rootEnvironment(Key<Environment> env) {
|
||||||
return new CompileResult(this.env.get(env).child(), scope, this);
|
return new CompileResult(this.env.get(env).child(), scope, this);
|
||||||
}
|
}
|
||||||
public CompileResult subEnvironment() {
|
public CompileResult subEnvironment() {
|
||||||
return new CompileResult(env.child(), scope, this);
|
return new CompileResult(env.child(), scope, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult(Environment env, FunctionScope scope, int length) {
|
public CompileResult(Environment env, FunctionScope scope, int length) {
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.instructions = new ArrayList<>();
|
this.instructions = new ArrayList<>();
|
||||||
this.children = new LinkedList<>();
|
this.children = new LinkedList<>();
|
||||||
this.map = FunctionMap.builder();
|
this.map = FunctionMap.builder();
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
}
|
}
|
||||||
private CompileResult(Environment env, FunctionScope scope, CompileResult parent) {
|
private CompileResult(Environment env, FunctionScope scope, CompileResult parent) {
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.instructions = parent.instructions;
|
this.instructions = parent.instructions;
|
||||||
this.children = parent.children;
|
this.children = parent.children;
|
||||||
this.map = parent.map;
|
this.map = parent.map;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,100 +13,100 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
|||||||
|
|
||||||
|
|
||||||
public class CompoundNode extends Node {
|
public class CompoundNode extends Node {
|
||||||
public final Node[] statements;
|
public final Node[] statements;
|
||||||
public Location end;
|
public Location end;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
for (var stm : statements) stm.resolve(target);
|
for (var stm : statements) stm.resolve(target);
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
for (var stm : statements) stm.compileFunctions(target);
|
for (var stm : statements) stm.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
List<Node> statements = new ArrayList<Node>();
|
List<Node> statements = new ArrayList<Node>();
|
||||||
|
|
||||||
for (var stm : this.statements) {
|
for (var stm : this.statements) {
|
||||||
if (stm instanceof FunctionStatementNode func) {
|
if (stm instanceof FunctionStatementNode func) {
|
||||||
func.compile(target, false);
|
func.compile(target, false);
|
||||||
}
|
}
|
||||||
else statements.add(stm);
|
else statements.add(stm);
|
||||||
}
|
}
|
||||||
|
|
||||||
var polluted = false;
|
var polluted = false;
|
||||||
|
|
||||||
for (var i = 0; i < statements.size(); i++) {
|
for (var i = 0; i < statements.size(); i++) {
|
||||||
var stm = statements.get(i);
|
var stm = statements.get(i);
|
||||||
|
|
||||||
if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER);
|
if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
|
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!polluted && pollute) {
|
if (!polluted && pollute) {
|
||||||
target.add(Instruction.pushUndefined());
|
target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompoundNode setEnd(Location loc) {
|
public CompoundNode setEnd(Location loc) {
|
||||||
this.end = loc;
|
this.end = loc;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompoundNode(Location loc, Node ...statements) {
|
public CompoundNode(Location loc, Node ...statements) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.statements = statements;
|
this.statements = statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 1) return ParseRes.failed();
|
if (precedence > 1) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ",")) return ParseRes.failed();
|
if (!src.is(i + n, ",")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var curr = JavaScript.parseExpression(src, i + n, 2);
|
var curr = JavaScript.parseExpression(src, i + n, 2);
|
||||||
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
|
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
|
||||||
n += curr.n;
|
n += curr.n;
|
||||||
|
|
||||||
if (prev instanceof CompoundNode comp) {
|
if (prev instanceof CompoundNode comp) {
|
||||||
var children = new ArrayList<Node>();
|
var children = new ArrayList<Node>();
|
||||||
children.addAll(Arrays.asList(comp.statements));
|
children.addAll(Arrays.asList(comp.statements));
|
||||||
children.add(curr.result);
|
children.add(curr.result);
|
||||||
|
|
||||||
return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n);
|
return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n);
|
||||||
}
|
}
|
||||||
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
|
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<CompoundNode> parse(Source src, int i) {
|
public static ParseRes<CompoundNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var statements = new ArrayList<Node>();
|
var statements = new ArrayList<Node>();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (src.is(i + n, "}")) {
|
if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (src.is(i + n, ";")) {
|
if (src.is(i + n, ";")) {
|
||||||
n++;
|
n++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = JavaScript.parseStatement(src, i + n);
|
var res = JavaScript.parseStatement(src, i + n);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
statements.add(res.result);
|
statements.add(res.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
|
return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,17 @@ package me.topchetoeu.jscript.compilation;
|
|||||||
import java.util.function.IntSupplier;
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
public final class DeferredIntSupplier implements IntSupplier {
|
public final class DeferredIntSupplier implements IntSupplier {
|
||||||
private int value;
|
private int value;
|
||||||
private boolean set;
|
private boolean set;
|
||||||
|
|
||||||
public void set(int val) {
|
public void set(int val) {
|
||||||
if (set) throw new RuntimeException("A deferred int supplier may be set only once");
|
if (set) throw new RuntimeException("A deferred int supplier may be set only once");
|
||||||
value = val;
|
value = val;
|
||||||
set = true;
|
set = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int getAsInt() {
|
@Override public int getAsInt() {
|
||||||
if (!set) throw new RuntimeException("Deferred int supplier accessed too early");
|
if (!set) throw new RuntimeException("Deferred int supplier accessed too early");
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,38 +13,38 @@ import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
|||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
public abstract class FunctionNode extends Node {
|
public abstract class FunctionNode extends Node {
|
||||||
public final CompoundNode body;
|
public final CompoundNode body;
|
||||||
public final List<VariableNode> params;
|
public final List<VariableNode> params;
|
||||||
public final Location end;
|
public final Location end;
|
||||||
|
|
||||||
public abstract String name();
|
public abstract String name();
|
||||||
public final String name(String fallback) {
|
public final String name(String fallback) {
|
||||||
return this.name() != null ? this.name() : fallback;
|
return this.name() != null ? this.name() : fallback;
|
||||||
|
}
|
||||||
|
protected final int[] captures(CompileResult target) {
|
||||||
|
return target.childrenMap.get(this).scope.getCaptureIndices();
|
||||||
}
|
}
|
||||||
protected final int[] captures(CompileResult target) {
|
|
||||||
return target.childrenMap.get(this).scope.getCaptureIndices();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final Environment rootEnv(Environment env) {
|
protected final Environment rootEnv(Environment env) {
|
||||||
return env.get(JavaScript.COMPILE_ROOT);
|
return env.get(JavaScript.COMPILE_ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) { }
|
@Override public void resolve(CompileResult target) { }
|
||||||
|
|
||||||
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String selfName) {
|
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String selfName) {
|
||||||
var target = new CompileResult(env, scope, params.size());
|
var target = new CompileResult(env, scope, params.size());
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
body.resolve(target);
|
body.resolve(target);
|
||||||
|
|
||||||
for (var param : params) scope.define(param.name);
|
for (var param : params) scope.define(param.name);
|
||||||
|
|
||||||
// if (selfName != null && !scope.has(selfName, false)) {
|
var hasSelf = false;
|
||||||
// var i = scope.defineSpecial(new Variable(selfName, true), end);
|
|
||||||
|
|
||||||
// t.add(Instruction.loadCalled());
|
if (selfName != null && !scope.has(selfName, false)) {
|
||||||
// t.add(_i -> i.index().toInit());
|
hasSelf = true;
|
||||||
// }
|
scope.define(selfName);
|
||||||
|
}
|
||||||
|
|
||||||
body.compileFunctions(target);
|
body.compileFunctions(target);
|
||||||
|
|
||||||
@ -52,70 +52,74 @@ public abstract class FunctionNode extends Node {
|
|||||||
target.add(Instruction.loadArg(i++)).setLocation(param.loc());
|
target.add(Instruction.loadArg(i++)).setLocation(param.loc());
|
||||||
target.add(scope.define(param.name).index().toSet(false)).setLocation(param.loc());
|
target.add(scope.define(param.name).index().toSet(false)).setLocation(param.loc());
|
||||||
}
|
}
|
||||||
|
if (hasSelf) {
|
||||||
|
target.add(Instruction.loadCalled());
|
||||||
|
target.add(scope.define(selfName).index().toSet(false));
|
||||||
|
}
|
||||||
|
|
||||||
body.compile(target, lastReturn, BreakpointType.NONE);
|
body.compile(target, lastReturn, BreakpointType.NONE);
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
public final CompileResult compileBody(CompileResult parent, String selfName) {
|
public final CompileResult compileBody(CompileResult parent, String selfName) {
|
||||||
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, selfName);
|
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, selfName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
|
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
|
||||||
public void compile(CompileResult target, boolean pollute, String name) {
|
public void compile(CompileResult target, boolean pollute, String name) {
|
||||||
compile(target, pollute, name, BreakpointType.NONE);
|
compile(target, pollute, name, BreakpointType.NONE);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
|
||||||
compile(target, pollute, (String)null, bp);
|
compile(target, pollute, (String)null, bp);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
compile(target, pollute, (String)null, BreakpointType.NONE);
|
compile(target, pollute, (String)null, BreakpointType.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionNode(Location loc, Location end, List<VariableNode> params, CompoundNode body) {
|
public FunctionNode(Location loc, Location end, List<VariableNode> params, CompoundNode body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
|
|
||||||
this.end = end;
|
this.end = end;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
|
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
|
||||||
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name);
|
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name);
|
||||||
else stm.compile(target, pollute);
|
else stm.compile(target, pollute);
|
||||||
}
|
}
|
||||||
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
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);
|
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp);
|
||||||
else stm.compile(target, pollute, bp);
|
else stm.compile(target, pollute, bp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) {
|
public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed();
|
||||||
n += 8;
|
n += 8;
|
||||||
|
|
||||||
var name = Parsing.parseIdentifier(src, i + n);
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
|
if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
|
||||||
n += name.n;
|
n += name.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var params = JavaScript.parseParameters(src, i + n);
|
var params = JavaScript.parseParameters(src, i + n);
|
||||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
||||||
n += params.n;
|
n += params.n;
|
||||||
|
|
||||||
var body = CompoundNode.parse(src, i + n);
|
var body = CompoundNode.parse(src, i + n);
|
||||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
|
||||||
n += body.n;
|
n += body.n;
|
||||||
|
|
||||||
if (statement) return ParseRes.res(new FunctionStatementNode(
|
if (statement) return ParseRes.res(new FunctionStatementNode(
|
||||||
loc, src.loc(i + n - 1),
|
loc, src.loc(i + n - 1),
|
||||||
params.result, body.result, name.result
|
params.result, body.result, name.result
|
||||||
), n);
|
), n);
|
||||||
else return ParseRes.res(new FunctionValueNode(
|
else return ParseRes.res(new FunctionValueNode(
|
||||||
loc, src.loc(i + n - 1),
|
loc, src.loc(i + n - 1),
|
||||||
params.result, body.result, name.result
|
params.result, body.result, name.result
|
||||||
), n);
|
), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,25 +9,25 @@ import me.topchetoeu.jscript.compilation.scope.Variable;
|
|||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
public class FunctionStatementNode extends FunctionNode {
|
public class FunctionStatementNode extends FunctionNode {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@Override public String name() { return name; }
|
@Override public String name() { return name; }
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
target.scope.define(new Variable(name, false));
|
target.scope.define(new Variable(name, false));
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
target.addChild(this, compileBody(target, name()));
|
target.addChild(this, compileBody(target, name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
||||||
target.add(VariableNode.toSet(target, end, this.name, false, true)).setLocation(loc());
|
target.add(VariableNode.toSet(target, end, this.name, false, true)).setLocation(loc());
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionStatementNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
|
public FunctionStatementNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
|
||||||
super(loc, end, params, body);
|
super(loc, end, params, body);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,21 +8,21 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
public class FunctionValueNode extends FunctionNode {
|
public class FunctionValueNode extends FunctionNode {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@Override public String name() { return name; }
|
@Override public String name() { return name; }
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
target.addChild(this, compileBody(target, name()));
|
target.addChild(this, compileBody(target, name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionValueNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
|
public FunctionValueNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
|
||||||
super(loc, end, params, body);
|
super(loc, end, params, body);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,242 +47,242 @@ import me.topchetoeu.jscript.compilation.values.operations.PostfixNode;
|
|||||||
import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
|
import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
|
||||||
|
|
||||||
public final class JavaScript {
|
public final class JavaScript {
|
||||||
public static enum DeclarationType {
|
public static enum DeclarationType {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
VAR;
|
VAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Key<Environment> COMPILE_ROOT = Key.of();
|
public static final Key<Environment> COMPILE_ROOT = new Key<>();
|
||||||
|
|
||||||
static final Set<String> reserved = new HashSet<>(Arrays.asList(
|
static final Set<String> reserved = new HashSet<>(Arrays.asList(
|
||||||
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
|
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
|
||||||
"finally", "for", "do", "while", "switch", "case", "default", "new",
|
"finally", "for", "do", "while", "switch", "case", "default", "new",
|
||||||
"function", "var", "return", "throw", "typeof", "delete", "break",
|
"function", "var", "return", "throw", "typeof", "delete", "break",
|
||||||
"continue", "debugger", "implements", "interface", "package", "private",
|
"continue", "debugger", "implements", "interface", "package", "private",
|
||||||
"protected", "public", "static", "arguments", "class", "extends"
|
"protected", "public", "static", "arguments", "class", "extends"
|
||||||
));
|
));
|
||||||
|
|
||||||
public static ParseRes<? extends Node> parseParens(Source src, int i) {
|
public static ParseRes<? extends Node> parseParens(Source src, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
var openParen = Parsing.parseOperator(src, i + n, "(");
|
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||||
if (!openParen.isSuccess()) return openParen.chainError();
|
if (!openParen.isSuccess()) return openParen.chainError();
|
||||||
n += openParen.n;
|
n += openParen.n;
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 0);
|
var res = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||||
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
|
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
|
||||||
n += closeParen.n;
|
n += closeParen.n;
|
||||||
|
|
||||||
return ParseRes.res(res.result, n);
|
return ParseRes.res(res.result, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Node> parseSimple(Source src, int i, boolean statement) {
|
public static ParseRes<? extends Node> parseSimple(Source src, int i, boolean statement) {
|
||||||
return ParseRes.first(src, i,
|
return ParseRes.first(src, i,
|
||||||
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
|
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
|
||||||
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
|
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
|
||||||
JavaScript::parseLiteral,
|
JavaScript::parseLiteral,
|
||||||
StringNode::parse,
|
StringNode::parse,
|
||||||
RegexNode::parse,
|
RegexNode::parse,
|
||||||
NumberNode::parse,
|
NumberNode::parse,
|
||||||
ChangeNode::parsePrefixDecrease,
|
ChangeNode::parsePrefixDecrease,
|
||||||
ChangeNode::parsePrefixIncrease,
|
ChangeNode::parsePrefixIncrease,
|
||||||
OperationNode::parsePrefix,
|
OperationNode::parsePrefix,
|
||||||
ArrayNode::parse,
|
ArrayNode::parse,
|
||||||
JavaScript::parseParens,
|
JavaScript::parseParens,
|
||||||
CallNode::parseNew,
|
CallNode::parseNew,
|
||||||
TypeofNode::parse,
|
TypeofNode::parse,
|
||||||
DiscardNode::parse,
|
DiscardNode::parse,
|
||||||
DeleteNode::parse,
|
DeleteNode::parse,
|
||||||
VariableNode::parse
|
VariableNode::parse
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Node> parseLiteral(Source src, int i) {
|
public static ParseRes<? extends Node> parseLiteral(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var id = Parsing.parseIdentifier(src, i);
|
var id = Parsing.parseIdentifier(src, i);
|
||||||
if (!id.isSuccess()) return id.chainError();
|
if (!id.isSuccess()) return id.chainError();
|
||||||
n += id.n;
|
n += id.n;
|
||||||
|
|
||||||
if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), 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("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("null")) return ParseRes.res(new NullNode(loc), n);
|
||||||
if (id.result.equals("this")) return ParseRes.res(new ThisNode(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);
|
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
|
||||||
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n);
|
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n);
|
||||||
|
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Node> parseExpression(Source src, int i, int precedence, boolean statement) {
|
public static ParseRes<Node> parseExpression(Source src, int i, int precedence, boolean statement) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
Node prev = null;
|
Node prev = null;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (prev == null) {
|
if (prev == null) {
|
||||||
var res = parseSimple(src, i + n, statement);
|
var res = parseSimple(src, i + n, statement);
|
||||||
if (res.isSuccess()) {
|
if (res.isSuccess()) {
|
||||||
n += res.n;
|
n += res.n;
|
||||||
prev = res.result;
|
prev = res.result;
|
||||||
}
|
}
|
||||||
else if (res.isError()) return res.chainError();
|
else if (res.isError()) return res.chainError();
|
||||||
else break;
|
else break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var _prev = prev;
|
var _prev = prev;
|
||||||
ParseRes<Node> res = ParseRes.first(src, i + n,
|
ParseRes<Node> res = ParseRes.first(src, i + n,
|
||||||
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
|
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
|
||||||
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
|
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
|
||||||
(s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
|
(s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
|
||||||
(s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
|
(s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
|
||||||
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
|
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
|
||||||
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
|
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
|
||||||
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
|
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
|
||||||
(s, j) -> IndexNode.parseIndex(s, j, _prev, precedence),
|
(s, j) -> IndexNode.parseIndex(s, j, _prev, precedence),
|
||||||
(s, j) -> CallNode.parseCall(s, j, _prev, precedence),
|
(s, j) -> CallNode.parseCall(s, j, _prev, precedence),
|
||||||
(s, j) -> CompoundNode.parseComma(s, j, _prev, precedence)
|
(s, j) -> CompoundNode.parseComma(s, j, _prev, precedence)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.isSuccess()) {
|
if (res.isSuccess()) {
|
||||||
n += res.n;
|
n += res.n;
|
||||||
prev = res.result;
|
prev = res.result;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (res.isError()) return res.chainError();
|
else if (res.isError()) return res.chainError();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev == null) return ParseRes.failed();
|
if (prev == null) return ParseRes.failed();
|
||||||
else return ParseRes.res(prev, n);
|
else return ParseRes.res(prev, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Node> parseExpression(Source src, int i, int precedence) {
|
public static ParseRes<Node> parseExpression(Source src, int i, int precedence) {
|
||||||
return parseExpression(src, i, precedence, false);
|
return parseExpression(src, i, precedence, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Node> parseExpressionStatement(Source src, int i) {
|
public static ParseRes<Node> parseExpressionStatement(Source src, int i) {
|
||||||
var res = parseExpression(src, i, 0, true);
|
var res = parseExpression(src, i, 0, true);
|
||||||
if (!res.isSuccess()) return res.chainError();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + res.n);
|
var end = JavaScript.parseStatementEnd(src, i + res.n);
|
||||||
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
|
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
|
||||||
|
|
||||||
return res.addN(end.n);
|
return res.addN(end.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Node> parseStatement(Source src, int i) {
|
public static ParseRes<Node> parseStatement(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, 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 (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.");
|
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
|
||||||
|
|
||||||
ParseRes<Node> res = ParseRes.first(src, i + n,
|
ParseRes<Node> res = ParseRes.first(src, i + n,
|
||||||
VariableDeclareNode::parse,
|
VariableDeclareNode::parse,
|
||||||
ReturnNode::parse,
|
ReturnNode::parse,
|
||||||
ThrowNode::parse,
|
ThrowNode::parse,
|
||||||
ContinueNode::parse,
|
ContinueNode::parse,
|
||||||
BreakNode::parse,
|
BreakNode::parse,
|
||||||
DebugNode::parse,
|
DebugNode::parse,
|
||||||
IfNode::parse,
|
IfNode::parse,
|
||||||
WhileNode::parse,
|
WhileNode::parse,
|
||||||
SwitchNode::parse,
|
SwitchNode::parse,
|
||||||
ForNode::parse,
|
ForNode::parse,
|
||||||
ForInNode::parse,
|
ForInNode::parse,
|
||||||
DoWhileNode::parse,
|
DoWhileNode::parse,
|
||||||
TryNode::parse,
|
TryNode::parse,
|
||||||
CompoundNode::parse,
|
CompoundNode::parse,
|
||||||
(s, j) -> FunctionNode.parseFunction(s, j, true),
|
(s, j) -> FunctionNode.parseFunction(s, j, true),
|
||||||
JavaScript::parseExpressionStatement
|
JavaScript::parseExpressionStatement
|
||||||
);
|
);
|
||||||
return res.addN(n);
|
return res.addN(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
|
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
if (i + n >= src.size()) return ParseRes.res(true, n);
|
if (i + n >= src.size()) return ParseRes.res(true, n);
|
||||||
|
|
||||||
for (var j = i; j < i + n; j++) {
|
for (var j = i; j < i + n; j++) {
|
||||||
if (src.is(j, '\n')) return ParseRes.res(true, n);
|
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 + 1);
|
||||||
if (src.is(i + n, '}')) return ParseRes.res(true, n);
|
if (src.is(i + n, '}')) return ParseRes.res(true, n);
|
||||||
|
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<Boolean> parseDeclarationType(Source src, int i) {
|
public static ParseRes<Boolean> parseDeclarationType(Source src, int i) {
|
||||||
var res = Parsing.parseIdentifier(src, i);
|
var res = Parsing.parseIdentifier(src, i);
|
||||||
if (!res.isSuccess()) return res.chainError();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
|
||||||
if (res.result.equals("var")) return ParseRes.res(true, res.n);
|
if (res.result.equals("var")) return ParseRes.res(true, res.n);
|
||||||
|
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Node[] parse(Environment env, Filename filename, String raw) {
|
public static Node[] parse(Environment env, Filename filename, String raw) {
|
||||||
var src = new Source(env, filename, raw);
|
var src = new Source(env, filename, raw);
|
||||||
var list = new ArrayList<Node>();
|
var list = new ArrayList<Node>();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (i >= src.size()) break;
|
if (i >= src.size()) break;
|
||||||
|
|
||||||
var res = parseStatement(src, i);
|
var res = parseStatement(src, i);
|
||||||
|
|
||||||
if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
|
if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
|
||||||
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
|
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
|
||||||
|
|
||||||
i += res.n;
|
i += res.n;
|
||||||
i += Parsing.skipEmpty(src, i);
|
i += Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
list.add(res.result);
|
list.add(res.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.toArray(new Node[0]);
|
return list.toArray(new Node[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkVarName(String name) {
|
public static boolean checkVarName(String name) {
|
||||||
return !JavaScript.reserved.contains(name);
|
return !JavaScript.reserved.contains(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompileResult compile(Environment env, Node ...statements) {
|
public static CompileResult compile(Environment env, Node ...statements) {
|
||||||
env = env.child();
|
env = env.child();
|
||||||
env.add(COMPILE_ROOT, env);
|
env.add(COMPILE_ROOT, env);
|
||||||
|
|
||||||
var func = new FunctionValueNode(null, null, Arrays.asList(), new CompoundNode(null, statements), null);
|
var func = new FunctionValueNode(null, null, Arrays.asList(), new CompoundNode(null, statements), null);
|
||||||
var res = func.compileBody(env, new FunctionScope(true), true, null);
|
var res = func.compileBody(env, new FunctionScope(true), true, null);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompileResult compile(Environment env, Filename filename, String raw) {
|
public static CompileResult compile(Environment env, Filename filename, String raw) {
|
||||||
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
|
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
|
||||||
}
|
}
|
||||||
public static CompileResult compile(Filename filename, String raw) {
|
public static CompileResult compile(Filename filename, String raw) {
|
||||||
var env = new Environment();
|
var env = new Environment();
|
||||||
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
|
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<String> parseLabel(Source src, int i) {
|
public static ParseRes<String> parseLabel(Source src, int i) {
|
||||||
int n = Parsing.skipEmpty(src, i);
|
int n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
var nameRes = Parsing.parseIdentifier(src, i + n);
|
var nameRes = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!nameRes.isSuccess()) return nameRes.chainError();
|
if (!nameRes.isSuccess()) return nameRes.chainError();
|
||||||
n += nameRes.n;
|
n += nameRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.failed();
|
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
return ParseRes.res(nameRes.result, n);
|
return ParseRes.res(nameRes.result, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<List<VariableNode>> parseParameters(Source src, int i) {
|
public static ParseRes<List<VariableNode>> parseParameters(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
@ -13,20 +13,20 @@ import me.topchetoeu.jscript.common.environment.Key;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public class LabelContext {
|
public class LabelContext {
|
||||||
public static final Key<LabelContext> BREAK_CTX = Key.of();
|
public static final Key<LabelContext> BREAK_CTX = new Key<>();
|
||||||
public static final Key<LabelContext> CONTINUE_CTX = Key.of();
|
public static final Key<LabelContext> CONTINUE_CTX = new Key<>();
|
||||||
|
|
||||||
private final LinkedList<IntSupplier> list = new LinkedList<>();
|
private final LinkedList<IntSupplier> list = new LinkedList<>();
|
||||||
private final HashMap<String, IntSupplier> map = new HashMap<>();
|
private final HashMap<String, IntSupplier> map = new HashMap<>();
|
||||||
|
|
||||||
private final Stack<ArrayList<Runnable>> deferredAdders = new Stack<>();
|
private final Stack<ArrayList<Runnable>> deferredAdders = new Stack<>();
|
||||||
|
|
||||||
public IntSupplier get() {
|
public IntSupplier get() {
|
||||||
return list.peekLast();
|
return list.peekLast();
|
||||||
}
|
}
|
||||||
public IntSupplier get(String name) {
|
public IntSupplier get(String name) {
|
||||||
return map.get(name);
|
return map.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flushAdders() {
|
public void flushAdders() {
|
||||||
for (var adder : deferredAdders.peek()) {
|
for (var adder : deferredAdders.peek()) {
|
||||||
@ -36,71 +36,71 @@ public class LabelContext {
|
|||||||
deferredAdders.pop();
|
deferredAdders.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean jump(CompileResult target) {
|
public boolean jump(CompileResult target) {
|
||||||
var res = get();
|
var res = get();
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
var tmp = target.temp();
|
var tmp = target.temp();
|
||||||
this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
|
this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
public boolean jump(CompileResult target, String name) {
|
public boolean jump(CompileResult target, String name) {
|
||||||
var res = get(name);
|
var res = get(name);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
var tmp = target.temp();
|
var tmp = target.temp();
|
||||||
this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
|
this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void push(IntSupplier jumpTarget) {
|
public void push(IntSupplier jumpTarget) {
|
||||||
list.add(jumpTarget);
|
list.add(jumpTarget);
|
||||||
}
|
}
|
||||||
public void push(Location loc, String name, IntSupplier jumpTarget) {
|
public void push(Location loc, String name, IntSupplier jumpTarget) {
|
||||||
if (name == null) return;
|
if (name == null) return;
|
||||||
if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name));
|
if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name));
|
||||||
map.put(name, jumpTarget);
|
map.put(name, jumpTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) {
|
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) {
|
||||||
push(jumpTarget);
|
push(jumpTarget);
|
||||||
push(loc, name, jumpTarget);
|
push(loc, name, jumpTarget);
|
||||||
deferredAdders.push(new ArrayList<>());
|
deferredAdders.push(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pop() {
|
public void pop() {
|
||||||
list.removeLast();
|
list.removeLast();
|
||||||
}
|
}
|
||||||
public void pop(String name) {
|
public void pop(String name) {
|
||||||
if (name == null) return;
|
if (name == null) return;
|
||||||
map.remove(name);
|
map.remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void popLoop(String name) {
|
public void popLoop(String name) {
|
||||||
pop();
|
pop();
|
||||||
pop(name);
|
pop(name);
|
||||||
flushAdders();
|
flushAdders();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LabelContext getBreak(Environment env) {
|
public static LabelContext getBreak(Environment env) {
|
||||||
return env.initFrom(BREAK_CTX, () -> new LabelContext());
|
return env.initFrom(BREAK_CTX, () -> new LabelContext());
|
||||||
}
|
}
|
||||||
public static LabelContext getCont(Environment env) {
|
public static LabelContext getCont(Environment env) {
|
||||||
return env.initFrom(CONTINUE_CTX, () -> new LabelContext());
|
return env.initFrom(CONTINUE_CTX, () -> new LabelContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) {
|
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) {
|
||||||
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
|
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
|
||||||
LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget);
|
LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget);
|
||||||
}
|
}
|
||||||
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) {
|
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) {
|
||||||
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
|
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
|
||||||
LabelContext.getCont(env).pushLoop(loc, name, contTarget);
|
LabelContext.getCont(env).pushLoop(loc, name, contTarget);
|
||||||
}
|
}
|
||||||
public static void popLoop(Environment env, String name) {
|
public static void popLoop(Environment env, String name) {
|
||||||
LabelContext.getBreak(env).popLoop(name);
|
LabelContext.getBreak(env).popLoop(name);
|
||||||
LabelContext.getCont(env).popLoop(name);
|
LabelContext.getCont(env).popLoop(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,25 +4,25 @@ import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public abstract class Node {
|
public abstract class Node {
|
||||||
private Location loc;
|
private Location loc;
|
||||||
|
|
||||||
public void resolve(CompileResult target) {}
|
public void resolve(CompileResult target) {}
|
||||||
|
|
||||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
compile(target, pollute);
|
compile(target, pollute);
|
||||||
if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
|
if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
|
||||||
}
|
}
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
public void compile(CompileResult target, boolean pollute) {
|
||||||
compile(target, pollute, BreakpointType.NONE);
|
compile(target, pollute, BreakpointType.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void compileFunctions(CompileResult target);
|
public abstract void compileFunctions(CompileResult target);
|
||||||
|
|
||||||
public Location loc() { return loc; }
|
public Location loc() { return loc; }
|
||||||
public void setLoc(Location loc) { this.loc = loc; }
|
public void setLoc(Location loc) { this.loc = loc; }
|
||||||
|
|
||||||
protected Node(Location loc) {
|
protected Node(Location loc) {
|
||||||
this.loc = loc;
|
this.loc = loc;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,89 +5,89 @@ import java.util.Iterator;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public final class NodeChildren implements Iterable<Node> {
|
public final class NodeChildren implements Iterable<Node> {
|
||||||
public static final class Slot {
|
public static final class Slot {
|
||||||
private Node node;
|
private Node node;
|
||||||
private final Function<Node, Node> replacer;
|
private final Function<Node, Node> replacer;
|
||||||
|
|
||||||
public final void replace(Node node) {
|
public final void replace(Node node) {
|
||||||
this.node = this.replacer.apply(node);
|
this.node = this.replacer.apply(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Slot(Node nodes, Function<Node, Node> replacer) {
|
public Slot(Node nodes, Function<Node, Node> replacer) {
|
||||||
this.node = nodes;
|
this.node = nodes;
|
||||||
this.replacer = replacer;
|
this.replacer = replacer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Slot[] slots;
|
private final Slot[] slots;
|
||||||
|
|
||||||
private NodeChildren(Slot[] slots) {
|
private NodeChildren(Slot[] slots) {
|
||||||
this.slots = slots;
|
this.slots = slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Iterator<Node> iterator() {
|
@Override public Iterator<Node> iterator() {
|
||||||
return new Iterator<Node>() {
|
return new Iterator<Node>() {
|
||||||
private int i = 0;
|
private int i = 0;
|
||||||
private Slot[] arr = slots;
|
private Slot[] arr = slots;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@Override public boolean hasNext() {
|
||||||
if (arr == null) return false;
|
if (arr == null) return false;
|
||||||
else if (i >= arr.length) {
|
else if (i >= arr.length) {
|
||||||
arr = null;
|
arr = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else return true;
|
else return true;
|
||||||
}
|
}
|
||||||
@Override public Node next() {
|
@Override public Node next() {
|
||||||
if (!hasNext()) return null;
|
if (!hasNext()) return null;
|
||||||
return arr[i++].node;
|
return arr[i++].node;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public Iterable<Slot> slots() {
|
public Iterable<Slot> slots() {
|
||||||
return () -> new Iterator<Slot>() {
|
return () -> new Iterator<Slot>() {
|
||||||
private int i = 0;
|
private int i = 0;
|
||||||
private Slot[] arr = slots;
|
private Slot[] arr = slots;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@Override public boolean hasNext() {
|
||||||
if (arr == null) return false;
|
if (arr == null) return false;
|
||||||
else if (i >= arr.length) {
|
else if (i >= arr.length) {
|
||||||
arr = null;
|
arr = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else return true;
|
else return true;
|
||||||
}
|
}
|
||||||
@Override public Slot next() {
|
@Override public Slot next() {
|
||||||
if (!hasNext()) return null;
|
if (!hasNext()) return null;
|
||||||
return arr[i++];
|
return arr[i++];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
private final ArrayList<Slot> slots = new ArrayList<>();
|
private final ArrayList<Slot> slots = new ArrayList<>();
|
||||||
|
|
||||||
public final Builder add(Slot ...children) {
|
public final Builder add(Slot ...children) {
|
||||||
for (var child : children) {
|
for (var child : children) {
|
||||||
this.slots.add(child);
|
this.slots.add(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public final Builder add(Iterable<Slot> children) {
|
public final Builder add(Iterable<Slot> children) {
|
||||||
for (var child : children) {
|
for (var child : children) {
|
||||||
this.slots.add(child);
|
this.slots.add(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public final Builder add(Node child, Function<Node, Node> replacer) {
|
public final Builder add(Node child, Function<Node, Node> replacer) {
|
||||||
slots.add(new Slot(child, replacer));
|
slots.add(new Slot(child, replacer));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final NodeChildren build() {
|
public final NodeChildren build() {
|
||||||
return new NodeChildren(slots.toArray(new Slot[0]));
|
return new NodeChildren(slots.toArray(new Slot[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,13 +3,13 @@ package me.topchetoeu.jscript.compilation;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public final class Parameter {
|
public final class Parameter {
|
||||||
public final Location loc;
|
public final Location loc;
|
||||||
public final String name;
|
public final String name;
|
||||||
public final Node node;
|
public final Node node;
|
||||||
|
|
||||||
public Parameter(Location loc, String name, Node node) {
|
public Parameter(Location loc, String name, Node node) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.loc = loc;
|
this.loc = loc;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,89 +14,89 @@ import me.topchetoeu.jscript.compilation.values.VariableNode;
|
|||||||
|
|
||||||
public class VariableDeclareNode extends Node {
|
public class VariableDeclareNode extends Node {
|
||||||
@Desugar
|
@Desugar
|
||||||
public static record Pair(VariableNode var, Node value) { }
|
public static record Pair(VariableNode var, Node value) { }
|
||||||
|
|
||||||
public final List<Pair> values;
|
public final List<Pair> values;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
for (var entry : values) {
|
for (var entry : values) {
|
||||||
target.scope.define(entry.var.name);
|
target.scope.define(entry.var.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
for (var pair : values) {
|
for (var pair : values) {
|
||||||
if (pair.value != null) pair.value.compileFunctions(target);
|
if (pair.value != null) pair.value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
for (var entry : values) {
|
for (var entry : values) {
|
||||||
if (entry.value != null) {
|
if (entry.value != null) {
|
||||||
entry.value.compile(target, true);
|
entry.value.compile(target, true);
|
||||||
target.add(VariableNode.toSet(target, loc(), entry.var.name, false, true)).setLocation(loc());
|
target.add(VariableNode.toSet(target, loc(), entry.var.name, false, true)).setLocation(loc());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableDeclareNode(Location loc, List<Pair> values) {
|
public VariableDeclareNode(Location loc, List<Pair> values) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.values = values;
|
this.values = values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<VariableDeclareNode> parse(Source src, int i) {
|
public static ParseRes<VariableDeclareNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var declType = JavaScript.parseDeclarationType(src, i + n);
|
var declType = JavaScript.parseDeclarationType(src, i + n);
|
||||||
if (!declType.isSuccess()) return declType.chainError();
|
if (!declType.isSuccess()) return declType.chainError();
|
||||||
n += declType.n;
|
n += declType.n;
|
||||||
|
|
||||||
var res = new ArrayList<Pair>();
|
var res = new ArrayList<Pair>();
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new VariableDeclareNode(loc, res), n);
|
return ParseRes.res(new VariableDeclareNode(loc, res), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var nameLoc = src.loc(i + n);
|
var nameLoc = src.loc(i + n);
|
||||||
|
|
||||||
var name = Parsing.parseIdentifier(src, i + n);
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
|
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
|
||||||
n += name.n;
|
n += name.n;
|
||||||
|
|
||||||
Node val = null;
|
Node val = null;
|
||||||
var endN = n;
|
var endN = n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (src.is(i + n, "=")) {
|
if (src.is(i + n, "=")) {
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='");
|
||||||
|
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
endN = n;
|
endN = n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
val = valRes.result;
|
val = valRes.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.add(new Pair(new VariableNode(nameLoc, name.result), val));
|
res.add(new Pair(new VariableNode(nameLoc, name.result), val));
|
||||||
|
|
||||||
if (src.is(i + n, ",")) {
|
if (src.is(i + n, ",")) {
|
||||||
n++;
|
n++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
end = JavaScript.parseStatementEnd(src, i + endN);
|
end = JavaScript.parseStatementEnd(src, i + endN);
|
||||||
|
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n + endN - n;
|
n += end.n + endN - n;
|
||||||
return ParseRes.res(new VariableDeclareNode(loc, res), n);
|
return ParseRes.res(new VariableDeclareNode(loc, res), n);
|
||||||
}
|
}
|
||||||
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
|
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,47 +12,47 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class BreakNode extends Node {
|
public class BreakNode extends Node {
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (!LabelContext.getBreak(target.env).jump(target)) {
|
if (!LabelContext.getBreak(target.env).jump(target)) {
|
||||||
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
||||||
else throw new SyntaxException(loc(), "Illegal break statement");
|
else throw new SyntaxException(loc(), "Illegal break statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BreakNode(Location loc, String label) {
|
public BreakNode(Location loc, String label) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<BreakNode> parse(Source src, int i) {
|
public static ParseRes<BreakNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed();
|
||||||
n += 5;
|
n += 5;
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new BreakNode(loc, null), n);
|
return ParseRes.res(new BreakNode(loc, null), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
var label = Parsing.parseIdentifier(src, i + 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");
|
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
|
||||||
n += label.n;
|
n += label.n;
|
||||||
|
|
||||||
end = JavaScript.parseStatementEnd(src, i + n);
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new BreakNode(loc, label.result), n);
|
return ParseRes.res(new BreakNode(loc, label.result), n);
|
||||||
}
|
}
|
||||||
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,47 +12,47 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class ContinueNode extends Node {
|
public class ContinueNode extends Node {
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (!LabelContext.getCont(target.env).jump(target)) {
|
if (!LabelContext.getCont(target.env).jump(target)) {
|
||||||
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
||||||
else throw new SyntaxException(loc(), "Illegal continue statement");
|
else throw new SyntaxException(loc(), "Illegal continue statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContinueNode(Location loc, String label) {
|
public ContinueNode(Location loc, String label) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ContinueNode> parse(Source src, int i) {
|
public static ParseRes<ContinueNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed();
|
||||||
n += 8;
|
n += 8;
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new ContinueNode(loc, null), n);
|
return ParseRes.res(new ContinueNode(loc, null), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
var label = Parsing.parseIdentifier(src, i + 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");
|
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
|
||||||
n += label.n;
|
n += label.n;
|
||||||
|
|
||||||
end = JavaScript.parseStatementEnd(src, i + n);
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new ContinueNode(loc, label.result), n);
|
return ParseRes.res(new ContinueNode(loc, label.result), n);
|
||||||
}
|
}
|
||||||
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,28 +13,28 @@ public class DebugNode extends Node {
|
|||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
target.add(Instruction.debug());
|
target.add(Instruction.debug());
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebugNode(Location loc) {
|
public DebugNode(Location loc) {
|
||||||
super(loc);
|
super(loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<DebugNode> parse(Source src, int i) {
|
public static ParseRes<DebugNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed();
|
||||||
n += 8;
|
n += 8;
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new DebugNode(loc), n);
|
return ParseRes.res(new DebugNode(loc), n);
|
||||||
}
|
}
|
||||||
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,45 +13,45 @@ import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
|
|||||||
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||||
|
|
||||||
public class DeleteNode extends Node {
|
public class DeleteNode extends Node {
|
||||||
public final Node key;
|
public final Node key;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
key.compileFunctions(target);
|
key.compileFunctions(target);
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
key.compile(target, true);
|
key.compile(target, true);
|
||||||
|
|
||||||
target.add(Instruction.delete());
|
target.add(Instruction.delete());
|
||||||
if (pollute) target.add(Instruction.pushValue(true));
|
if (pollute) target.add(Instruction.pushValue(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<? extends Node> parse(Source src, int i) {
|
public static ParseRes<? extends Node> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed();
|
||||||
n += 6;
|
n += 6;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 15);
|
var valRes = JavaScript.parseExpression(src, i + n, 15);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
if (valRes.result instanceof IndexNode) {
|
if (valRes.result instanceof IndexNode) {
|
||||||
var index = (IndexNode)valRes.result;
|
var index = (IndexNode)valRes.result;
|
||||||
return ParseRes.res(new DeleteNode(loc, index.index, index.object), n);
|
return ParseRes.res(new DeleteNode(loc, index.index, index.object), n);
|
||||||
}
|
}
|
||||||
else if (valRes.result instanceof VariableNode) {
|
else if (valRes.result instanceof VariableNode) {
|
||||||
return ParseRes.error(src.loc(i + n), "A variable may not be deleted");
|
return ParseRes.error(src.loc(i + n), "A variable may not be deleted");
|
||||||
}
|
}
|
||||||
else return ParseRes.res(new BoolNode(loc, true), n);
|
else return ParseRes.res(new BoolNode(loc, true), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeleteNode(Location loc, Node key, Node value) {
|
public DeleteNode(Location loc, Node key, Node value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,78 +13,78 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class DoWhileNode extends Node {
|
public class DoWhileNode extends Node {
|
||||||
public final Node condition, body;
|
public final Node condition, body;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
condition.compileFunctions(target);
|
condition.compileFunctions(target);
|
||||||
body.compileFunctions(target);
|
body.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
body.resolve(target);
|
body.resolve(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
var mid = new DeferredIntSupplier();
|
var mid = new DeferredIntSupplier();
|
||||||
|
|
||||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
mid.set(target.size());
|
mid.set(target.size());
|
||||||
condition.compile(target, true, BreakpointType.STEP_OVER);
|
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
end.set(endI + 1);
|
end.set(endI + 1);
|
||||||
|
|
||||||
LabelContext.popLoop(target.env, label);
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
target.add(Instruction.jmpIf(start - endI));
|
target.add(Instruction.jmpIf(start - endI));
|
||||||
}
|
}
|
||||||
|
|
||||||
public DoWhileNode(Location loc, String label, Node condition, Node body) {
|
public DoWhileNode(Location loc, String label, Node condition, Node body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<DoWhileNode> parse(Source src, int i) {
|
public static ParseRes<DoWhileNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var labelRes = JavaScript.parseLabel(src, i + n);
|
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||||
n += labelRes.n;
|
n += labelRes.n;
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var bodyRes = JavaScript.parseStatement(src, i + n);
|
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||||
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
|
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
|
||||||
n += bodyRes.n;
|
n += bodyRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
||||||
n += 5;
|
n += 5;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var condRes = JavaScript.parseExpression(src, i + n, 0);
|
var condRes = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition.");
|
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition.");
|
||||||
n += condRes.n;
|
n += condRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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.");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition.");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), 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");
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,100 +15,100 @@ import me.topchetoeu.jscript.compilation.values.VariableNode;
|
|||||||
|
|
||||||
public class ForInNode extends Node {
|
public class ForInNode extends Node {
|
||||||
public final boolean isDecl;
|
public final boolean isDecl;
|
||||||
public final VariableNode binding;
|
public final VariableNode binding;
|
||||||
public final Node object, body;
|
public final Node object, body;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
body.resolve(target);
|
body.resolve(target);
|
||||||
binding.resolve(target);
|
binding.resolve(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
object.compileFunctions(target);
|
object.compileFunctions(target);
|
||||||
body.compileFunctions(target);
|
body.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
object.compile(target, true, BreakpointType.STEP_OVER);
|
object.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
target.add(Instruction.keys(false, true));
|
target.add(Instruction.keys(false, true));
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup());
|
||||||
int mid = target.temp();
|
int mid = target.temp();
|
||||||
|
|
||||||
target.add(Instruction.loadMember("value")).setLocation(binding.loc());
|
target.add(Instruction.loadMember("value")).setLocation(binding.loc());
|
||||||
target.add(VariableNode.toSet(target, loc(), binding.name, false, true)).setLocation(binding.loc());
|
target.add(VariableNode.toSet(target, loc(), binding.name, false, true)).setLocation(binding.loc());
|
||||||
|
|
||||||
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - endI));
|
target.add(Instruction.jmp(start - endI));
|
||||||
target.add(Instruction.discard());
|
target.add(Instruction.discard());
|
||||||
target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||||
|
|
||||||
end.set(endI);
|
end.set(endI);
|
||||||
LabelContext.popLoop(target.env, label);
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForInNode(Location loc, String label, VariableNode binding, boolean isDecl, Node object, Node body) {
|
public ForInNode(Location loc, String label, VariableNode binding, boolean isDecl, Node object, Node body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.isDecl = isDecl;
|
this.isDecl = isDecl;
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ForInNode> parse(Source src, int i) {
|
public static ParseRes<ForInNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var label = JavaScript.parseLabel(src, i + n);
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
n += label.n;
|
n += label.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
||||||
n += 3;
|
n += 3;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
|
||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var varKw = JavaScript.parseDeclarationType(src, i + n);
|
var varKw = JavaScript.parseDeclarationType(src, i + n);
|
||||||
n += varKw.n;
|
n += varKw.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var bindingLoc = src.loc(i + n);
|
var bindingLoc = src.loc(i + n);
|
||||||
|
|
||||||
var name = Parsing.parseIdentifier(src, i + n);
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a variable name");
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a variable name");
|
||||||
n += name.n;
|
n += name.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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");
|
if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var obj = JavaScript.parseExpression(src, i + n, 0);
|
var obj = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
|
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
|
||||||
n += obj.n;
|
n += obj.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var bodyRes = JavaScript.parseStatement(src, i + n);
|
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||||
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
|
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
|
||||||
n += bodyRes.n;
|
n += bodyRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new ForInNode(loc, label.result, new VariableNode(bindingLoc, name.result), varKw.isSuccess(), obj.result, bodyRes.result), n);
|
return ParseRes.res(new ForInNode(loc, label.result, new VariableNode(bindingLoc, name.result), varKw.isSuccess(), obj.result, bodyRes.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,117 +15,117 @@ import me.topchetoeu.jscript.compilation.VariableDeclareNode;
|
|||||||
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
|
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
|
||||||
|
|
||||||
public class ForNode extends Node {
|
public class ForNode extends Node {
|
||||||
public final Node declaration, assignment, condition, body;
|
public final Node declaration, assignment, condition, body;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
declaration.resolve(target);
|
declaration.resolve(target);
|
||||||
body.resolve(target);
|
body.resolve(target);
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
if (declaration != null) declaration.compileFunctions(target);
|
if (declaration != null) declaration.compileFunctions(target);
|
||||||
if (assignment != null) assignment.compileFunctions(target);
|
if (assignment != null) assignment.compileFunctions(target);
|
||||||
if (condition != null) condition.compileFunctions(target);
|
if (condition != null) condition.compileFunctions(target);
|
||||||
body.compileFunctions(target);
|
body.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (declaration != null) declaration.compile(target, false, BreakpointType.STEP_OVER);
|
if (declaration != null) declaration.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
int mid = -1;
|
int mid = -1;
|
||||||
if (condition != null) {
|
if (condition != null) {
|
||||||
condition.compile(target, true, BreakpointType.STEP_OVER);
|
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
mid = target.temp();
|
mid = target.temp();
|
||||||
}
|
}
|
||||||
|
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
if (assignment != null) assignment.compile(target, false, BreakpointType.STEP_OVER);
|
if (assignment != null) assignment.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
|
|
||||||
end.set(endI);
|
end.set(endI);
|
||||||
LabelContext.popLoop(target.env, label);
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - endI));
|
target.add(Instruction.jmp(start - endI));
|
||||||
if (mid >= 0) target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
if (mid >= 0) target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {
|
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.declaration = declaration;
|
this.declaration = declaration;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.assignment = assignment;
|
this.assignment = assignment;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParseRes<Node> parseSemicolon(Source src, int i) {
|
private static ParseRes<Node> parseSemicolon(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n, ";")) return ParseRes.failed();
|
if (!src.is(i + n, ";")) return ParseRes.failed();
|
||||||
else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1);
|
else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1);
|
||||||
}
|
}
|
||||||
private static ParseRes<Node> parseCondition(Source src, int i) {
|
private static ParseRes<Node> parseCondition(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 0);
|
var res = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!res.isSuccess()) return res.chainError();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
n += res.n;
|
n += res.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon");
|
if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon");
|
||||||
else return ParseRes.res(res.result, n + 1);
|
else return ParseRes.res(res.result, n + 1);
|
||||||
}
|
}
|
||||||
private static ParseRes<? extends Node> parseUpdater(Source src, int i) {
|
private static ParseRes<? extends Node> parseUpdater(Source src, int i) {
|
||||||
return JavaScript.parseExpression(src, i, 0);
|
return JavaScript.parseExpression(src, i, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ForNode> parse(Source src, int i) {
|
public static ParseRes<ForNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var labelRes = JavaScript.parseLabel(src, i + n);
|
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||||
n += labelRes.n;
|
n += labelRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
||||||
n += 3;
|
n += 3;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'");
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
ParseRes<Node> decl = ParseRes.first(src, i + n,
|
ParseRes<Node> decl = ParseRes.first(src, i + n,
|
||||||
ForNode::parseSemicolon,
|
ForNode::parseSemicolon,
|
||||||
VariableDeclareNode::parse,
|
VariableDeclareNode::parse,
|
||||||
ForNode::parseCondition
|
ForNode::parseCondition
|
||||||
);
|
);
|
||||||
if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression");
|
if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression");
|
||||||
n += decl.n;
|
n += decl.n;
|
||||||
|
|
||||||
ParseRes<Node> cond = ParseRes.first(src, i + n,
|
ParseRes<Node> cond = ParseRes.first(src, i + n,
|
||||||
ForNode::parseSemicolon,
|
ForNode::parseSemicolon,
|
||||||
ForNode::parseCondition
|
ForNode::parseCondition
|
||||||
);
|
);
|
||||||
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition");
|
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition");
|
||||||
n += cond.n;
|
n += cond.n;
|
||||||
|
|
||||||
var update = parseUpdater(src, i + n);
|
var update = parseUpdater(src, i + n);
|
||||||
if (update.isError()) return update.chainError();
|
if (update.isError()) return update.chainError();
|
||||||
n += update.n;
|
n += update.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var body = JavaScript.parseStatement(src, i + n);
|
var body = JavaScript.parseStatement(src, i + n);
|
||||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
|
||||||
n += body.n;
|
n += body.n;
|
||||||
|
|
||||||
return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n);
|
return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,123 +13,123 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class IfNode extends Node {
|
public class IfNode extends Node {
|
||||||
public final Node condition, body, elseBody;
|
public final Node condition, body, elseBody;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
body.resolve(target);
|
body.resolve(target);
|
||||||
if (elseBody != null) elseBody.resolve(target);
|
if (elseBody != null) elseBody.resolve(target);
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
condition.compileFunctions(target);
|
condition.compileFunctions(target);
|
||||||
body.compileFunctions(target);
|
body.compileFunctions(target);
|
||||||
if (elseBody != null) elseBody.compileFunctions(target);
|
if (elseBody != null) elseBody.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
|
||||||
condition.compile(target, true, breakpoint);
|
condition.compile(target, true, breakpoint);
|
||||||
|
|
||||||
if (elseBody == null) {
|
if (elseBody == null) {
|
||||||
int start = target.temp();
|
int start = target.temp();
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||||
body.compile(target, pollute, BreakpointType.STEP_OVER);
|
body.compile(target, pollute, BreakpointType.STEP_OVER);
|
||||||
LabelContext.getBreak(target.env).pop(label);
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
end.set(endI);
|
end.set(endI);
|
||||||
|
|
||||||
target.set(start, Instruction.jmpIfNot(endI - start));
|
target.set(start, Instruction.jmpIfNot(endI - start));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int start = target.temp();
|
int start = target.temp();
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||||
body.compile(target, pollute, BreakpointType.STEP_OVER);
|
body.compile(target, pollute, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
int mid = target.temp();
|
int mid = target.temp();
|
||||||
|
|
||||||
elseBody.compile(target, pollute, BreakpointType.STEP_OVER);
|
elseBody.compile(target, pollute, BreakpointType.STEP_OVER);
|
||||||
LabelContext.getBreak(target.env).pop(label);
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
end.set(endI);
|
end.set(endI);
|
||||||
|
|
||||||
target.set(start, Instruction.jmpIfNot(mid - start + 1));
|
target.set(start, Instruction.jmpIfNot(mid - start + 1));
|
||||||
target.set(mid, Instruction.jmp(endI - mid));
|
target.set(mid, Instruction.jmp(endI - mid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
compile(target, pollute, BreakpointType.STEP_IN);
|
compile(target, pollute, BreakpointType.STEP_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) {
|
public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
this.elseBody = elseBody;
|
this.elseBody = elseBody;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<IfNode> parseTernary(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<IfNode> parseTernary(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 2) return ParseRes.failed();
|
if (precedence > 2) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n, "?")) return ParseRes.failed();
|
if (!src.is(i + n, "?")) return ParseRes.failed();
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var a = JavaScript.parseExpression(src, i + n, 2);
|
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.");
|
if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator.");
|
||||||
n += a.n;
|
n += a.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.failed();
|
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var b = JavaScript.parseExpression(src, i + n, 2);
|
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.");
|
if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator.");
|
||||||
n += b.n;
|
n += b.n;
|
||||||
|
|
||||||
return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n);
|
return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<IfNode> parse(Source src, int i) {
|
public static ParseRes<IfNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var label = JavaScript.parseLabel(src, i + n);
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
n += label.n;
|
n += label.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed();
|
||||||
n += 2;
|
n += 2;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'.");
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'.");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var condRes = JavaScript.parseExpression(src, i + n, 0);
|
var condRes = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition.");
|
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition.");
|
||||||
n += condRes.n;
|
n += condRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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.");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition.");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var res = JavaScript.parseStatement(src, i + n);
|
var res = JavaScript.parseStatement(src, i + n);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body.");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body.");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
var elseKw = Parsing.parseIdentifier(src, i + n, "else");
|
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);
|
if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n);
|
||||||
n += elseKw.n;
|
n += elseKw.n;
|
||||||
|
|
||||||
var elseRes = JavaScript.parseStatement(src, i + n);
|
var elseRes = JavaScript.parseStatement(src, i + n);
|
||||||
if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body.");
|
if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body.");
|
||||||
n += elseRes.n;
|
n += elseRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
|
return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,44 +10,44 @@ import me.topchetoeu.jscript.compilation.JavaScript;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class ReturnNode extends Node {
|
public class ReturnNode extends Node {
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
if (value != null) value.compileFunctions(target);
|
if (value != null) value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (value == null) target.add(Instruction.pushUndefined());
|
if (value == null) target.add(Instruction.pushUndefined());
|
||||||
else value.compile(target, true);
|
else value.compile(target, true);
|
||||||
target.add(Instruction.ret()).setLocation(loc());
|
target.add(Instruction.ret()).setLocation(loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReturnNode(Location loc, Node value) {
|
public ReturnNode(Location loc, Node value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ReturnNode> parse(Source src, int i) {
|
public static ParseRes<ReturnNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed();
|
||||||
n += 6;
|
n += 6;
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new ReturnNode(loc, null), n);
|
return ParseRes.res(new ReturnNode(loc, null), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
var val = JavaScript.parseExpression(src, i + n, 0);
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (val.isError()) return val.chainError();
|
if (val.isError()) return val.chainError();
|
||||||
n += val.n;
|
n += val.n;
|
||||||
|
|
||||||
end = JavaScript.parseStatementEnd(src, i + n);
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new ReturnNode(loc, val.result), 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");
|
else return end.chainError(src.loc(i + n), "Expected end of statement or a return value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,25 +17,25 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class SwitchNode extends Node {
|
public class SwitchNode extends Node {
|
||||||
public static class SwitchCase {
|
public static class SwitchCase {
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final int statementI;
|
public final int statementI;
|
||||||
|
|
||||||
public SwitchCase(Node value, int statementI) {
|
public SwitchCase(Node value, int statementI) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.statementI = statementI;
|
this.statementI = statementI;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final SwitchCase[] cases;
|
public final SwitchCase[] cases;
|
||||||
public final Node[] body;
|
public final Node[] body;
|
||||||
public final int defaultI;
|
public final int defaultI;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
for (var stm : body) stm.resolve(target);
|
for (var stm : body) stm.resolve(target);
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
for (var _case : cases) {
|
for (var _case : cases) {
|
||||||
@ -45,154 +45,154 @@ public class SwitchNode extends Node {
|
|||||||
stm.compileFunctions(target);
|
stm.compileFunctions(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
var caseToStatement = new HashMap<Integer, Integer>();
|
var caseToStatement = new HashMap<Integer, Integer>();
|
||||||
var statementToIndex = new HashMap<Integer, Integer>();
|
var statementToIndex = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
value.compile(target, true, BreakpointType.STEP_OVER);
|
value.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
// TODO: create a jump map
|
// TODO: create a jump map
|
||||||
for (var ccase : cases) {
|
for (var ccase : cases) {
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup());
|
||||||
ccase.value.compile(target, true);
|
ccase.value.compile(target, true);
|
||||||
target.add(Instruction.operation(Operation.EQUALS));
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
caseToStatement.put(target.temp(), ccase.statementI);
|
caseToStatement.put(target.temp(), ccase.statementI);
|
||||||
}
|
}
|
||||||
|
|
||||||
int start = target.temp();
|
int start = target.temp();
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
LabelContext.getBreak(target.env).pushLoop(loc(), label, end);
|
LabelContext.getBreak(target.env).pushLoop(loc(), label, end);
|
||||||
for (var stm : body) {
|
for (var stm : body) {
|
||||||
statementToIndex.put(statementToIndex.size(), target.size());
|
statementToIndex.put(statementToIndex.size(), target.size());
|
||||||
stm.compile(target, false, BreakpointType.STEP_OVER);
|
stm.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
int endI = target.size();
|
int endI = target.size();
|
||||||
end.set(endI);
|
end.set(endI);
|
||||||
LabelContext.getBreak(target.env).popLoop(label);
|
LabelContext.getBreak(target.env).popLoop(label);
|
||||||
|
|
||||||
target.add(Instruction.discard());
|
target.add(Instruction.discard());
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(endI - start));
|
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(endI - start));
|
||||||
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
||||||
|
|
||||||
for (var el : caseToStatement.entrySet()) {
|
for (var el : caseToStatement.entrySet()) {
|
||||||
var i = statementToIndex.get(el.getValue());
|
var i = statementToIndex.get(el.getValue());
|
||||||
if (i == null) i = endI;
|
if (i == null) i = endI;
|
||||||
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) {
|
public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.defaultI = defaultI;
|
this.defaultI = defaultI;
|
||||||
this.cases = cases;
|
this.cases = cases;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParseRes<Node> parseSwitchCase(Source src, int i) {
|
private static ParseRes<Node> parseSwitchCase(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
|
||||||
n += 4;
|
n += 4;
|
||||||
|
|
||||||
var val = JavaScript.parseExpression(src, i + n, 0);
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'");
|
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'");
|
||||||
n += val.n;
|
n += val.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value");
|
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
return ParseRes.res(val.result, n);
|
return ParseRes.res(val.result, n);
|
||||||
}
|
}
|
||||||
private static ParseRes<Void> parseDefaultCase(Source src, int i) {
|
private static ParseRes<Void> parseDefaultCase(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed();
|
||||||
n += 7;
|
n += 7;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'");
|
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
return ParseRes.res(null, n);
|
return ParseRes.res(null, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<SwitchNode> parse(Source src, int i) {
|
public static ParseRes<SwitchNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var label = JavaScript.parseLabel(src, i + n);
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
n += label.n;
|
n += label.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed();
|
||||||
n += 6;
|
n += 6;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var val = JavaScript.parseExpression(src, i + n, 0);
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value");
|
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value");
|
||||||
n += val.n;
|
n += val.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value");
|
||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + 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");
|
if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value");
|
||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var statements = new ArrayList<Node>();
|
var statements = new ArrayList<Node>();
|
||||||
var cases = new ArrayList<SwitchCase>();
|
var cases = new ArrayList<SwitchCase>();
|
||||||
var defaultI = -1;
|
var defaultI = -1;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (src.is(i + n, "}")) {
|
if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (src.is(i + n, ";")) {
|
if (src.is(i + n, ";")) {
|
||||||
n++;
|
n++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseRes<Node> caseRes = ParseRes.first(src, i + n,
|
ParseRes<Node> caseRes = ParseRes.first(src, i + n,
|
||||||
SwitchNode::parseDefaultCase,
|
SwitchNode::parseDefaultCase,
|
||||||
SwitchNode::parseSwitchCase
|
SwitchNode::parseSwitchCase
|
||||||
);
|
);
|
||||||
|
|
||||||
if (caseRes.isSuccess()) {
|
if (caseRes.isSuccess()) {
|
||||||
n += caseRes.n;
|
n += caseRes.n;
|
||||||
|
|
||||||
if (caseRes.result == null) defaultI = statements.size();
|
if (caseRes.result == null) defaultI = statements.size();
|
||||||
else cases.add(new SwitchCase(caseRes.result, statements.size()));
|
else cases.add(new SwitchCase(caseRes.result, statements.size()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (caseRes.isError()) return caseRes.chainError();
|
if (caseRes.isError()) return caseRes.chainError();
|
||||||
|
|
||||||
var stm = JavaScript.parseStatement(src, i + n);
|
var stm = JavaScript.parseStatement(src, i + n);
|
||||||
if (stm.isSuccess()) {
|
if (stm.isSuccess()) {
|
||||||
n += stm.n;
|
n += stm.n;
|
||||||
statements.add(stm.result);
|
statements.add(stm.result);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'");
|
else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new SwitchNode(
|
return ParseRes.res(new SwitchNode(
|
||||||
loc, label.result, val.result, defaultI,
|
loc, label.result, val.result, defaultI,
|
||||||
cases.toArray(new SwitchCase[0]),
|
cases.toArray(new SwitchCase[0]),
|
||||||
statements.toArray(new Node[0])
|
statements.toArray(new Node[0])
|
||||||
), n);
|
), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,43 +10,43 @@ import me.topchetoeu.jscript.compilation.JavaScript;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class ThrowNode extends Node {
|
public class ThrowNode extends Node {
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
target.add(Instruction.throwInstr()).setLocation(loc());
|
target.add(Instruction.throwInstr()).setLocation(loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ThrowNode(Location loc, Node value) {
|
public ThrowNode(Location loc, Node value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ThrowNode> parse(Source src, int i) {
|
public static ParseRes<ThrowNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed();
|
||||||
n += 5;
|
n += 5;
|
||||||
|
|
||||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new ThrowNode(loc, null), n);
|
return ParseRes.res(new ThrowNode(loc, null), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
var val = JavaScript.parseExpression(src, i + n, 0);
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value");
|
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value");
|
||||||
n += val.n;
|
n += val.n;
|
||||||
|
|
||||||
end = JavaScript.parseStatementEnd(src, i + n);
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
if (end.isSuccess()) {
|
if (end.isSuccess()) {
|
||||||
n += end.n;
|
n += end.n;
|
||||||
return ParseRes.res(new ThrowNode(loc, val.result), n);
|
return ParseRes.res(new ThrowNode(loc, val.result), n);
|
||||||
}
|
}
|
||||||
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,126 +14,126 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class TryNode extends Node {
|
public class TryNode extends Node {
|
||||||
public final CompoundNode tryBody;
|
public final CompoundNode tryBody;
|
||||||
public final CompoundNode catchBody;
|
public final CompoundNode catchBody;
|
||||||
public final CompoundNode finallyBody;
|
public final CompoundNode finallyBody;
|
||||||
public final String captureName;
|
public final String captureName;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
tryBody.resolve(target);
|
tryBody.resolve(target);
|
||||||
if (catchBody != null) catchBody.resolve(target);
|
if (catchBody != null) catchBody.resolve(target);
|
||||||
if (finallyBody != null) finallyBody.resolve(target);
|
if (finallyBody != null) finallyBody.resolve(target);
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
tryBody.compileFunctions(target);
|
tryBody.compileFunctions(target);
|
||||||
if (catchBody != null) catchBody.compileFunctions(target);
|
if (catchBody != null) catchBody.compileFunctions(target);
|
||||||
if (finallyBody != null) finallyBody.compileFunctions(target);
|
if (finallyBody != null) finallyBody.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
|
||||||
int replace = target.temp();
|
int replace = target.temp();
|
||||||
var endSuppl = new DeferredIntSupplier();
|
var endSuppl = new DeferredIntSupplier();
|
||||||
|
|
||||||
int start = replace + 1, catchStart = -1, finallyStart = -1;
|
int start = replace + 1, catchStart = -1, finallyStart = -1;
|
||||||
|
|
||||||
LabelContext.getBreak(target.env).push(loc(), label, endSuppl);
|
LabelContext.getBreak(target.env).push(loc(), label, endSuppl);
|
||||||
|
|
||||||
tryBody.compile(target, false);
|
tryBody.compile(target, false);
|
||||||
target.add(Instruction.tryEnd());
|
target.add(Instruction.tryEnd());
|
||||||
|
|
||||||
if (catchBody != null) {
|
if (catchBody != null) {
|
||||||
catchStart = target.size() - start;
|
catchStart = target.size() - start;
|
||||||
|
|
||||||
if (captureName != null) {
|
if (captureName != null) {
|
||||||
var catchVar = target.scope.defineCatch(captureName);
|
var catchVar = target.scope.defineCatch(captureName);
|
||||||
target.add(Instruction.loadError()).setLocation(catchBody.loc());
|
target.add(Instruction.loadError()).setLocation(catchBody.loc());
|
||||||
target.add(catchVar.index().toSet(false)).setLocation(catchBody.loc());
|
target.add(catchVar.index().toSet(false)).setLocation(catchBody.loc());
|
||||||
catchBody.compile(target, false);
|
catchBody.compile(target, false);
|
||||||
target.scope.undefineCatch();
|
target.scope.undefineCatch();
|
||||||
}
|
}
|
||||||
else catchBody.compile(target, false);
|
else catchBody.compile(target, false);
|
||||||
|
|
||||||
target.add(Instruction.tryEnd());
|
target.add(Instruction.tryEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finallyBody != null) {
|
if (finallyBody != null) {
|
||||||
finallyStart = target.size() - start;
|
finallyStart = target.size() - start;
|
||||||
finallyBody.compile(target, false);
|
finallyBody.compile(target, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelContext.getBreak(target.env).pop(label);
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
endSuppl.set(target.size());
|
endSuppl.set(target.size());
|
||||||
|
|
||||||
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
|
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
|
||||||
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
|
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) {
|
public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.tryBody = tryBody;
|
this.tryBody = tryBody;
|
||||||
this.catchBody = catchBody;
|
this.catchBody = catchBody;
|
||||||
this.finallyBody = finallyBody;
|
this.finallyBody = finallyBody;
|
||||||
this.captureName = captureName;
|
this.captureName = captureName;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<TryNode> parse(Source src, int i) {
|
public static ParseRes<TryNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var labelRes = JavaScript.parseLabel(src, i + n);
|
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||||
n += labelRes.n;
|
n += labelRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed();
|
||||||
n += 3;
|
n += 3;
|
||||||
|
|
||||||
var tryBody = CompoundNode.parse(src, i + n);
|
var tryBody = CompoundNode.parse(src, i + n);
|
||||||
if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body");
|
if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body");
|
||||||
n += tryBody.n;
|
n += tryBody.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
String capture = null;
|
String capture = null;
|
||||||
CompoundNode catchBody = null, finallyBody = null;
|
CompoundNode catchBody = null, finallyBody = null;
|
||||||
|
|
||||||
if (Parsing.isIdentifier(src, i + n, "catch")) {
|
if (Parsing.isIdentifier(src, i + n, "catch")) {
|
||||||
n += 5;
|
n += 5;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
if (src.is(i + n, "(")) {
|
if (src.is(i + n, "(")) {
|
||||||
n++;
|
n++;
|
||||||
var nameRes = Parsing.parseIdentifier(src, i + n);
|
var nameRes = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
|
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
|
||||||
capture = nameRes.result;
|
capture = nameRes.result;
|
||||||
n += nameRes.n;
|
n += nameRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name");
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodyRes = CompoundNode.parse(src, i + n);
|
var bodyRes = CompoundNode.parse(src, i + n);
|
||||||
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body");
|
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body");
|
||||||
n += bodyRes.n;
|
n += bodyRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
catchBody = bodyRes.result;
|
catchBody = bodyRes.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Parsing.isIdentifier(src, i + n, "finally")) {
|
if (Parsing.isIdentifier(src, i + n, "finally")) {
|
||||||
n += 7;
|
n += 7;
|
||||||
|
|
||||||
var bodyRes = CompoundNode.parse(src, i + n);
|
var bodyRes = CompoundNode.parse(src, i + n);
|
||||||
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body");
|
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body");
|
||||||
n += bodyRes.n;
|
n += bodyRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
finallyBody = bodyRes.result;
|
finallyBody = bodyRes.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally");
|
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);
|
return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,70 +13,70 @@ import me.topchetoeu.jscript.compilation.LabelContext;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class WhileNode extends Node {
|
public class WhileNode extends Node {
|
||||||
public final Node condition, body;
|
public final Node condition, body;
|
||||||
public final String label;
|
public final String label;
|
||||||
|
|
||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
body.resolve(target);
|
body.resolve(target);
|
||||||
}
|
}
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
condition.compileFunctions(target);
|
condition.compileFunctions(target);
|
||||||
body.compileFunctions(target);
|
body.compileFunctions(target);
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
int start = target.size();
|
int start = target.size();
|
||||||
condition.compile(target, true);
|
condition.compile(target, true);
|
||||||
int mid = target.temp();
|
int mid = target.temp();
|
||||||
|
|
||||||
var end = new DeferredIntSupplier();
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
|
||||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
var endI = target.size();
|
var endI = target.size();
|
||||||
end.set(endI + 1);
|
end.set(endI + 1);
|
||||||
LabelContext.popLoop(target.env, label);
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end.getAsInt()));
|
target.add(Instruction.jmp(start - end.getAsInt()));
|
||||||
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1));
|
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1));
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public WhileNode(Location loc, String label, Node condition, Node body) {
|
public WhileNode(Location loc, String label, Node condition, Node body) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<WhileNode> parse(Source src, int i) {
|
public static ParseRes<WhileNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var label = JavaScript.parseLabel(src, i + n);
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
n += label.n;
|
n += label.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
||||||
n += 5;
|
n += 5;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var cond = JavaScript.parseExpression(src, i + n, 0);
|
var cond = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition.");
|
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition.");
|
||||||
n += cond.n;
|
n += cond.n;
|
||||||
n += Parsing.skipEmpty(src, i + 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.");
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition.");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var body = JavaScript.parseStatement(src, i + n);
|
var body = JavaScript.parseStatement(src, i + n);
|
||||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body.");
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body.");
|
||||||
n += body.n;
|
n += body.n;
|
||||||
|
|
||||||
return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n);
|
return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,49 +11,49 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||||
|
|
||||||
public class FieldMemberNode implements Member {
|
public class FieldMemberNode implements Member {
|
||||||
public final Location loc;
|
public final Location loc;
|
||||||
public final Node key;
|
public final Node key;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public Location loc() { return loc; }
|
@Override public Location loc() { return loc; }
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
key.compileFunctions(target);
|
key.compileFunctions(target);
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.dup());
|
if (pollute) target.add(Instruction.dup());
|
||||||
key.compile(target, true);
|
key.compile(target, true);
|
||||||
|
|
||||||
if (value == null) target.add(Instruction.pushUndefined());
|
if (value == null) target.add(Instruction.pushUndefined());
|
||||||
else value.compile(target, true);
|
else value.compile(target, true);
|
||||||
|
|
||||||
target.add(Instruction.defField());
|
target.add(Instruction.defField());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldMemberNode(Location loc, Node key, Node value) {
|
public FieldMemberNode(Location loc, Node key, Node value) {
|
||||||
this.loc = loc;
|
this.loc = loc;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<FieldMemberNode> parse(Source src, int i) {
|
public static ParseRes<FieldMemberNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var name = ObjectNode.parsePropName(src, i + n);
|
var name = ObjectNode.parsePropName(src, i + n);
|
||||||
if (!name.isSuccess()) return name.chainError();
|
if (!name.isSuccess()) return name.chainError();
|
||||||
n += name.n;
|
n += name.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.failed();
|
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
||||||
n += value.n;
|
n += value.n;
|
||||||
|
|
||||||
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,8 +4,8 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
|
||||||
public interface Member {
|
public interface Member {
|
||||||
Location loc();
|
Location loc();
|
||||||
|
|
||||||
void compileFunctions(CompileResult target);
|
void compileFunctions(CompileResult target);
|
||||||
void compile(CompileResult target, boolean pollute);
|
void compile(CompileResult target, boolean pollute);
|
||||||
}
|
}
|
||||||
|
@ -18,66 +18,66 @@ import me.topchetoeu.jscript.compilation.values.VariableNode;
|
|||||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
|
|
||||||
public final class PropertyMemberNode extends FunctionNode implements Member {
|
public final class PropertyMemberNode extends FunctionNode implements Member {
|
||||||
public final Node key;
|
public final Node key;
|
||||||
public final VariableNode argument;
|
public final VariableNode argument;
|
||||||
|
|
||||||
@Override public String name() {
|
@Override public String name() {
|
||||||
if (key instanceof StringNode str) {
|
if (key instanceof StringNode str) {
|
||||||
if (isGetter()) return "get " + str.value;
|
if (isGetter()) return "get " + str.value;
|
||||||
else return "set " + str.value;
|
else return "set " + str.value;
|
||||||
}
|
}
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGetter() { return argument == null; }
|
public boolean isGetter() { return argument == null; }
|
||||||
public boolean isSetter() { return argument != null; }
|
public boolean isSetter() { return argument != null; }
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
key.compileFunctions(target);
|
key.compileFunctions(target);
|
||||||
target.addChild(this, compileBody(target, null));
|
target.addChild(this, compileBody(target, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
if (pollute) target.add(Instruction.dup());
|
if (pollute) target.add(Instruction.dup());
|
||||||
key.compile(target, true);
|
key.compile(target, true);
|
||||||
|
|
||||||
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
||||||
target.add(Instruction.defProp(isSetter()));
|
target.add(Instruction.defProp(isSetter()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PropertyMemberNode(Location loc, Location end, Node key, VariableNode argument, CompoundNode body) {
|
public PropertyMemberNode(Location loc, Location end, Node key, VariableNode argument, CompoundNode body) {
|
||||||
super(loc, end, argument == null ? Arrays.asList() : Arrays.asList(argument), body);
|
super(loc, end, argument == null ? Arrays.asList() : Arrays.asList(argument), body);
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.argument = argument;
|
this.argument = argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<PropertyMemberNode> parse(Source src, int i) {
|
public static ParseRes<PropertyMemberNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var access = Parsing.parseIdentifier(src, i + n);
|
var access = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!access.isSuccess()) return ParseRes.failed();
|
if (!access.isSuccess()) return ParseRes.failed();
|
||||||
if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed();
|
if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed();
|
||||||
n += access.n;
|
n += access.n;
|
||||||
|
|
||||||
var name = ObjectNode.parsePropName(src, i + n);
|
var name = ObjectNode.parsePropName(src, i + n);
|
||||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
||||||
n += name.n;
|
n += name.n;
|
||||||
|
|
||||||
var params = JavaScript.parseParameters(src, i + n);
|
var params = JavaScript.parseParameters(src, i + n);
|
||||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||||
if (access.result.equals("get") && params.result.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
|
if (access.result.equals("get") && params.result.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
|
||||||
if (access.result.equals("set") && params.result.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
|
if (access.result.equals("set") && params.result.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
|
||||||
n += params.n;
|
n += params.n;
|
||||||
|
|
||||||
var body = CompoundNode.parse(src, i + 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.");
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
|
||||||
n += body.n;
|
n += body.n;
|
||||||
|
|
||||||
var end = src.loc(i + n - 1);
|
var end = src.loc(i + n - 1);
|
||||||
|
|
||||||
return ParseRes.res(new PropertyMemberNode(
|
return ParseRes.res(new PropertyMemberNode(
|
||||||
loc, end, name.result, access.result.equals("get") ? null : params.result.get(0), body.result
|
loc, end, name.result, access.result.equals("get") ? null : params.result.get(0), body.result
|
||||||
), n);
|
), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,21 +7,21 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
|||||||
* Represents all nodes that can be assign targets
|
* Represents all nodes that can be assign targets
|
||||||
*/
|
*/
|
||||||
public interface AssignTarget extends AssignTargetLike {
|
public interface AssignTarget extends AssignTargetLike {
|
||||||
Location loc();
|
Location loc();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to perform calculations before the assigned value is calculated
|
* Called to perform calculations before the assigned value is calculated
|
||||||
*/
|
*/
|
||||||
default void beforeAssign(CompileResult target) {}
|
default void beforeAssign(CompileResult target) {}
|
||||||
/**
|
/**
|
||||||
* Called to perform the actual assignemnt. Between the `beforeAssign` and this call a single value will have been pushed to the stack
|
* Called to perform the actual assignemnt. Between the `beforeAssign` and this call a single value will have been pushed to the stack
|
||||||
* @param pollute Whether or not to leave the original value on the stack
|
* @param pollute Whether or not to leave the original value on the stack
|
||||||
*/
|
*/
|
||||||
void afterAssign(CompileResult target, boolean pollute);
|
void afterAssign(CompileResult target, boolean pollute);
|
||||||
|
|
||||||
default void assign(CompileResult target, boolean pollute) {
|
default void assign(CompileResult target, boolean pollute) {
|
||||||
afterAssign(target, pollute);
|
afterAssign(target, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override default AssignTarget toAssignTarget() { return this; }
|
@Override default AssignTarget toAssignTarget() { return this; }
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,5 @@ package me.topchetoeu.jscript.compilation.patterns;
|
|||||||
* Represents all nodes that can be converted to assign targets
|
* Represents all nodes that can be converted to assign targets
|
||||||
*/
|
*/
|
||||||
public interface AssignTargetLike {
|
public interface AssignTargetLike {
|
||||||
AssignTarget toAssignTarget();
|
AssignTarget toAssignTarget();
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ package me.topchetoeu.jscript.compilation.patterns;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
|
||||||
public interface ChangeTarget extends AssignTarget {
|
public interface ChangeTarget extends AssignTarget {
|
||||||
void beforeChange(CompileResult target);
|
void beforeChange(CompileResult target);
|
||||||
}
|
}
|
||||||
|
@ -4,24 +4,24 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public final class FunctionScope {
|
public final class FunctionScope {
|
||||||
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS);
|
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS);
|
||||||
protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this.locals);
|
protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this.locals);
|
||||||
private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
|
private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
|
||||||
|
|
||||||
private final HashMap<String, Variable> localsMap = new HashMap<>();
|
private final HashMap<String, Variable> localsMap = new HashMap<>();
|
||||||
private final HashMap<String, Variable> capturesMap = new HashMap<>();
|
private final HashMap<String, Variable> capturesMap = new HashMap<>();
|
||||||
private final ArrayList<Variable> catchesMap = new ArrayList<>();
|
private final ArrayList<Variable> catchesMap = new ArrayList<>();
|
||||||
|
|
||||||
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
|
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
|
||||||
private final HashMap<Variable, Variable> parentToChild = new HashMap<>();
|
private final HashMap<Variable, Variable> parentToChild = new HashMap<>();
|
||||||
|
|
||||||
public final FunctionScope parent;
|
public final FunctionScope parent;
|
||||||
public final boolean passthrough;
|
public final boolean passthrough;
|
||||||
|
|
||||||
private Variable addCaptured(Variable var, boolean captured) {
|
private Variable addCaptured(Variable var, boolean captured) {
|
||||||
if (captured && !this.capturables.has(var)) this.capturables.add(var);
|
if (captured && !this.capturables.has(var)) this.capturables.add(var);
|
||||||
return var;
|
return var;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Variable getCatchVar(String name) {
|
private Variable getCatchVar(String name) {
|
||||||
for (var el : catchesMap) {
|
for (var el : catchesMap) {
|
||||||
@ -31,28 +31,28 @@ public final class FunctionScope {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns If a variable with the same name exists, the old variable. Otherwise, the given variable
|
* @returns If a variable with the same name exists, the old variable. Otherwise, the given variable
|
||||||
*/
|
*/
|
||||||
public Variable define(Variable var) {
|
public Variable define(Variable var) {
|
||||||
if (passthrough) return null;
|
if (passthrough) return null;
|
||||||
else {
|
else {
|
||||||
var catchVar = getCatchVar(var.name);
|
var catchVar = getCatchVar(var.name);
|
||||||
if (catchVar != null) return catchVar;
|
if (catchVar != null) return catchVar;
|
||||||
if (localsMap.containsKey(var.name)) return localsMap.get(var.name);
|
if (localsMap.containsKey(var.name)) return localsMap.get(var.name);
|
||||||
if (capturesMap.containsKey(var.name)) throw new RuntimeException("HEY!");
|
if (capturesMap.containsKey(var.name)) throw new RuntimeException("HEY!");
|
||||||
|
|
||||||
localsMap.put(var.name, var);
|
localsMap.put(var.name, var);
|
||||||
return locals.add(var);
|
return locals.add(var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns A variable with the given name, or null if a global variable
|
* @returns A variable with the given name, or null if a global variable
|
||||||
*/
|
*/
|
||||||
public Variable define(String name) {
|
public Variable define(String name) {
|
||||||
return define(new Variable(name, false));
|
return define(new Variable(name, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a catch variable and returns it
|
* Creates a catch variable and returns it
|
||||||
@ -71,41 +71,41 @@ public final class FunctionScope {
|
|||||||
this.catchesMap.remove(this.catchesMap.size() - 1);
|
this.catchesMap.remove(this.catchesMap.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the index supplier of the given variable name, or null if it is a global
|
* 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
|
* @param capture If true, the variable is being captured by a function
|
||||||
*/
|
*/
|
||||||
public Variable get(String name, boolean capture) {
|
public Variable get(String name, boolean capture) {
|
||||||
var catchVar = getCatchVar(name);
|
var catchVar = getCatchVar(name);
|
||||||
|
|
||||||
if (catchVar != null) return addCaptured(catchVar, capture);
|
if (catchVar != null) return addCaptured(catchVar, capture);
|
||||||
if (localsMap.containsKey(name)) return addCaptured(localsMap.get(name), capture);
|
if (localsMap.containsKey(name)) return addCaptured(localsMap.get(name), capture);
|
||||||
if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture);
|
if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture);
|
||||||
|
|
||||||
if (parent == null) return null;
|
if (parent == null) return null;
|
||||||
|
|
||||||
var parentVar = parent.get(name, true);
|
var parentVar = parent.get(name, true);
|
||||||
if (parentVar == null) return null;
|
if (parentVar == null) return null;
|
||||||
|
|
||||||
var childVar = captures.add(parentVar.clone().setIndexSupplier(null));
|
var childVar = captures.add(parentVar.clone().setIndexSupplier(null));
|
||||||
capturesMap.put(childVar.name, childVar);
|
capturesMap.put(childVar.name, childVar);
|
||||||
childToParent.put(childVar, parentVar);
|
childToParent.put(childVar, parentVar);
|
||||||
parentToChild.put(parentVar, childVar);
|
parentToChild.put(parentVar, childVar);
|
||||||
|
|
||||||
return childVar;
|
return childVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the variable given is contained in this function, just returns the variable itself.
|
* If the variable given is contained in this function, just returns the variable itself.
|
||||||
* However, this function is important to handle cases in which you might want to access
|
* However, this function is important to handle cases in which you might want to access
|
||||||
* a captured variable. In such cases, this function will return a capture to the given variable.
|
* a captured variable. In such cases, this function will return a capture to the given variable.
|
||||||
*
|
*
|
||||||
* @param capture Whether or not to execute this capturing logic
|
* @param capture Whether or not to execute this capturing logic
|
||||||
*/
|
*/
|
||||||
public Variable get(Variable var, boolean capture) {
|
public Variable get(Variable var, boolean capture) {
|
||||||
if (captures.has(var)) return addCaptured(var, capture);
|
if (captures.has(var)) return addCaptured(var, capture);
|
||||||
if (locals.has(var)) return addCaptured(var, capture);
|
if (locals.has(var)) return addCaptured(var, capture);
|
||||||
|
|
||||||
if (capture) {
|
if (capture) {
|
||||||
if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture);
|
if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture);
|
||||||
@ -122,54 +122,53 @@ public final class FunctionScope {
|
|||||||
return childVar;
|
return childVar;
|
||||||
}
|
}
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given variable name is accessible
|
* Checks if the given variable name is accessible
|
||||||
*
|
*
|
||||||
* @param capture If true, will check beyond this function's scope
|
* @param capture If true, will check beyond this function's scope
|
||||||
*/
|
*/
|
||||||
public boolean has(String name, boolean capture) {
|
public boolean has(String name, boolean capture) {
|
||||||
if (localsMap.containsKey(name)) return true;
|
if (localsMap.containsKey(name)) return true;
|
||||||
// if (specialVarMap.containsKey(name)) return true;
|
|
||||||
|
|
||||||
if (capture) {
|
if (capture) {
|
||||||
if (capturesMap.containsKey(name)) return true;
|
if (capturesMap.containsKey(name)) return true;
|
||||||
if (parent != null) return parent.has(name, true);
|
if (parent != null) return parent.has(name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int localsCount() {
|
public int localsCount() {
|
||||||
return locals.size();
|
return locals.size();
|
||||||
}
|
}
|
||||||
public int capturesCount() {
|
public int capturesCount() {
|
||||||
return captures.size();
|
return captures.size();
|
||||||
}
|
}
|
||||||
public int capturablesCount() {
|
public int capturablesCount() {
|
||||||
return capturables.size();
|
return capturables.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] getCaptureIndices() {
|
public int[] getCaptureIndices() {
|
||||||
var res = new int[captures.size()];
|
var res = new int[captures.size()];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
for (var el : captures) {
|
for (var el : captures) {
|
||||||
assert childToParent.containsKey(el);
|
assert childToParent.containsKey(el);
|
||||||
res[i] = childToParent.get(el).index().toCaptureIndex();
|
res[i] = childToParent.get(el).index().toCaptureIndex();
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterable<Variable> capturables() {
|
public Iterable<Variable> capturables() {
|
||||||
return capturables;
|
return capturables;
|
||||||
}
|
}
|
||||||
public Iterable<Variable> locals() {
|
public Iterable<Variable> locals() {
|
||||||
return locals;
|
return locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] captureNames() {
|
public String[] captureNames() {
|
||||||
var res = new String[this.captures.size()];
|
var res = new String[this.captures.size()];
|
||||||
@ -195,12 +194,12 @@ public final class FunctionScope {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionScope(FunctionScope parent) {
|
public FunctionScope(FunctionScope parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.passthrough = false;
|
this.passthrough = false;
|
||||||
}
|
}
|
||||||
public FunctionScope(boolean passthrough) {
|
public FunctionScope(boolean passthrough) {
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
this.passthrough = passthrough;
|
this.passthrough = passthrough;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,33 +3,33 @@ package me.topchetoeu.jscript.compilation.scope;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public final class Variable {
|
public final class Variable {
|
||||||
private Supplier<VariableIndex> indexSupplier;
|
private Supplier<VariableIndex> indexSupplier;
|
||||||
|
|
||||||
public final boolean readonly;
|
public final boolean readonly;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
public final VariableIndex index() {
|
public final VariableIndex index() {
|
||||||
return indexSupplier.get();
|
return indexSupplier.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
|
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
|
||||||
this.indexSupplier = index;
|
this.indexSupplier = index;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public final Supplier<VariableIndex> indexSupplier() {
|
public final Supplier<VariableIndex> indexSupplier() {
|
||||||
return indexSupplier;
|
return indexSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Variable clone() {
|
public final Variable clone() {
|
||||||
return new Variable(name, readonly).setIndexSupplier(indexSupplier);
|
return new Variable(name, readonly).setIndexSupplier(indexSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable(String name, boolean readonly) {
|
public Variable(String name, boolean readonly) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.readonly = readonly;
|
this.readonly = readonly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Variable of(String name, boolean readonly, VariableIndex index) {
|
public static Variable of(String name, boolean readonly, VariableIndex index) {
|
||||||
return new Variable(name, readonly).setIndexSupplier(() -> index);
|
return new Variable(name, readonly).setIndexSupplier(() -> index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,51 +3,51 @@ package me.topchetoeu.jscript.compilation.scope;
|
|||||||
import me.topchetoeu.jscript.common.Instruction;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
|
||||||
public final class VariableIndex {
|
public final class VariableIndex {
|
||||||
public static enum IndexType {
|
public static enum IndexType {
|
||||||
/**
|
/**
|
||||||
* A simple variable that is only ever used within the function
|
* A simple variable that is only ever used within the function
|
||||||
*/
|
*/
|
||||||
LOCALS,
|
LOCALS,
|
||||||
/**
|
/**
|
||||||
* A variable that has the ability to be captured by children functions
|
* A variable that has the ability to be captured by children functions
|
||||||
*/
|
*/
|
||||||
CAPTURABLES,
|
CAPTURABLES,
|
||||||
/**
|
/**
|
||||||
* A variable that has been captured from the parent function
|
* A variable that has been captured from the parent function
|
||||||
*/
|
*/
|
||||||
CAPTURES,
|
CAPTURES,
|
||||||
}
|
}
|
||||||
|
|
||||||
public final IndexType type;
|
public final IndexType type;
|
||||||
public final int index;
|
public final int index;
|
||||||
|
|
||||||
public final int toCaptureIndex() {
|
public final int toCaptureIndex() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CAPTURES: return ~index;
|
case CAPTURES: return ~index;
|
||||||
case CAPTURABLES: return index;
|
case CAPTURABLES: return index;
|
||||||
default: throw new UnsupportedOperationException("Index type " + type + " may not be captured");
|
default: throw new UnsupportedOperationException("Index type " + type + " may not be captured");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Instruction toGet() {
|
public final Instruction toGet() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CAPTURES: return Instruction.loadVar(~index);
|
case CAPTURES: return Instruction.loadVar(~index);
|
||||||
case CAPTURABLES: return Instruction.loadVar(index);
|
case CAPTURABLES: return Instruction.loadVar(index);
|
||||||
case LOCALS: return Instruction.loadVar(index);
|
case LOCALS: return Instruction.loadVar(index);
|
||||||
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public final Instruction toSet(boolean keep) {
|
public final Instruction toSet(boolean keep) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CAPTURES: return Instruction.storeVar(index, keep, false);
|
case CAPTURES: return Instruction.storeVar(index, keep, false);
|
||||||
case CAPTURABLES: return Instruction.storeVar(index, keep, false);
|
case CAPTURABLES: return Instruction.storeVar(index, keep, false);
|
||||||
case LOCALS: return Instruction.storeVar(index, keep, false);
|
case LOCALS: return Instruction.storeVar(index, keep, false);
|
||||||
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableIndex(VariableIndex.IndexType type, int index) {
|
public VariableIndex(VariableIndex.IndexType type, int index) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,14 +8,14 @@ import java.util.function.Supplier;
|
|||||||
import me.topchetoeu.jscript.compilation.scope.VariableIndex.IndexType;
|
import me.topchetoeu.jscript.compilation.scope.VariableIndex.IndexType;
|
||||||
|
|
||||||
public final class VariableList implements Iterable<Variable> {
|
public final class VariableList implements Iterable<Variable> {
|
||||||
private final class VariableNode implements Supplier<VariableIndex> {
|
private final class VariableNode implements Supplier<VariableIndex> {
|
||||||
public Variable var;
|
public Variable var;
|
||||||
public VariableNode next;
|
public VariableNode next;
|
||||||
public VariableNode prev;
|
public VariableNode prev;
|
||||||
public int index;
|
public int index;
|
||||||
public int indexIteration = -1;
|
public int indexIteration = -1;
|
||||||
|
|
||||||
public VariableList list() { return VariableList.this; }
|
public VariableList list() { return VariableList.this; }
|
||||||
|
|
||||||
private int getIndex() {
|
private int getIndex() {
|
||||||
if (this.indexIteration != VariableList.this.indexIteration) {
|
if (this.indexIteration != VariableList.this.indexIteration) {
|
||||||
@ -28,23 +28,23 @@ public final class VariableList implements Iterable<Variable> {
|
|||||||
return this.index;
|
return this.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public VariableIndex get() {
|
@Override public VariableIndex get() {
|
||||||
if (offset == null) return new VariableIndex(indexType, this.getIndex());
|
if (offset == null) return new VariableIndex(indexType, this.getIndex());
|
||||||
else return new VariableIndex(indexType, offset.getAsInt() + this.getIndex());
|
else return new VariableIndex(indexType, offset.getAsInt() + this.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
|
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
|
||||||
this.var = var;
|
this.var = var;
|
||||||
this.next = next;
|
this.next = next;
|
||||||
this.prev = prev;
|
this.prev = prev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private VariableNode first, last;
|
private VariableNode first, last;
|
||||||
|
|
||||||
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
|
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
|
||||||
|
|
||||||
private final IntSupplier offset;
|
private final IntSupplier offset;
|
||||||
/**
|
/**
|
||||||
* Increased when indices need recalculation. VariableNode will check if
|
* Increased when indices need recalculation. VariableNode will check if
|
||||||
* its internal indexIteration is up to date with this, and if not, will
|
* its internal indexIteration is up to date with this, and if not, will
|
||||||
@ -52,140 +52,140 @@ public final class VariableList implements Iterable<Variable> {
|
|||||||
*/
|
*/
|
||||||
private int indexIteration = 0;
|
private int indexIteration = 0;
|
||||||
|
|
||||||
public final VariableIndex.IndexType indexType;
|
public final VariableIndex.IndexType indexType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given variable to this list. If it already exists, does nothing
|
* Adds the given variable to this list. If it already exists, does nothing
|
||||||
* @return val
|
* @return val
|
||||||
*/
|
*/
|
||||||
public Variable add(Variable val) {
|
public Variable add(Variable val) {
|
||||||
if (this.varMap.containsKey(val)) return val;
|
if (this.varMap.containsKey(val)) return val;
|
||||||
this.indexIteration++;
|
this.indexIteration++;
|
||||||
|
|
||||||
if (val.indexSupplier() instanceof VariableNode prevNode) {
|
if (val.indexSupplier() instanceof VariableNode prevNode) {
|
||||||
prevNode.list().remove(val);
|
prevNode.list().remove(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = new VariableNode(val, null, last);
|
var node = new VariableNode(val, null, last);
|
||||||
|
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
assert first != null;
|
assert first != null;
|
||||||
|
|
||||||
last.next = node;
|
last.next = node;
|
||||||
node.prev = last;
|
node.prev = last;
|
||||||
|
|
||||||
last = node;
|
last = node;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
first = last = node;
|
first = last = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
varMap.put(val, node);
|
varMap.put(val, node);
|
||||||
val.setIndexSupplier(node);
|
val.setIndexSupplier(node);
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the variable is not in the list, does nothing. Otherwise, removes the variable from the list
|
* If the variable is not in the list, does nothing. Otherwise, removes the variable from the list
|
||||||
* @return null if nothing was done, else the deleted variable (should be var)
|
* @return null if nothing was done, else the deleted variable (should be var)
|
||||||
*/
|
*/
|
||||||
public Variable remove(Variable var) {
|
public Variable remove(Variable var) {
|
||||||
if (var == null) return null;
|
if (var == null) return null;
|
||||||
|
|
||||||
var node = varMap.get(var);
|
var node = varMap.get(var);
|
||||||
if (node == null) return null;
|
if (node == null) return null;
|
||||||
|
|
||||||
this.indexIteration++;
|
this.indexIteration++;
|
||||||
|
|
||||||
if (node.prev != null) {
|
if (node.prev != null) {
|
||||||
assert node != first;
|
assert node != first;
|
||||||
node.prev.next = node.next;
|
node.prev.next = node.next;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert node == first;
|
assert node == first;
|
||||||
first = first.next;
|
first = first.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.next != null) {
|
if (node.next != null) {
|
||||||
assert node != last;
|
assert node != last;
|
||||||
node.next.prev = node.prev;
|
node.next.prev = node.prev;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert node == last;
|
assert node == last;
|
||||||
last = last.prev;
|
last = last.prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.next = null;
|
node.next = null;
|
||||||
node.prev = null;
|
node.prev = null;
|
||||||
|
|
||||||
varMap.remove(node.var);
|
varMap.remove(node.var);
|
||||||
node.var.setIndexSupplier(null);
|
node.var.setIndexSupplier(null);
|
||||||
|
|
||||||
return node.var;
|
return node.var;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the list has the given variable
|
* Checks if the list has the given variable
|
||||||
*/
|
*/
|
||||||
public boolean has(Variable var) {
|
public boolean has(Variable var) {
|
||||||
return varMap.containsKey(var);
|
return varMap.containsKey(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an indexer for the given variable
|
* Returns an indexer for the given variable
|
||||||
*/
|
*/
|
||||||
public Supplier<VariableIndex> indexer(Variable var) {
|
public Supplier<VariableIndex> indexer(Variable var) {
|
||||||
return varMap.get(var);
|
return varMap.get(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() {
|
public int size() {
|
||||||
return varMap.size();
|
return varMap.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<Variable> iterator() {
|
public Iterator<Variable> iterator() {
|
||||||
return new Iterator<Variable>() {
|
return new Iterator<Variable>() {
|
||||||
private VariableNode curr = first;
|
private VariableNode curr = first;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@Override public boolean hasNext() {
|
||||||
return curr != null;
|
return curr != null;
|
||||||
}
|
}
|
||||||
@Override public Variable next() {
|
@Override public Variable next() {
|
||||||
if (curr == null) return null;
|
if (curr == null) return null;
|
||||||
|
|
||||||
var res = curr;
|
var res = curr;
|
||||||
curr = curr.next;
|
curr = curr.next;
|
||||||
return res.var;
|
return res.var;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param offset Will offset the indices by the given amount from the supplier
|
* @param offset Will offset the indices by the given amount from the supplier
|
||||||
*/
|
*/
|
||||||
public VariableList(IndexType type, IntSupplier offset) {
|
public VariableList(IndexType type, IntSupplier offset) {
|
||||||
this.indexType = type;
|
this.indexType = type;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param offset Will offset the indices by the given amount
|
* @param offset Will offset the indices by the given amount
|
||||||
*/
|
*/
|
||||||
public VariableList(IndexType type, int offset) {
|
public VariableList(IndexType type, int offset) {
|
||||||
this.indexType = type;
|
this.indexType = type;
|
||||||
this.offset = () -> offset;
|
this.offset = () -> offset;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param offset Will offset the indices by the size of the given list
|
* @param offset Will offset the indices by the size of the given list
|
||||||
*/
|
*/
|
||||||
public VariableList(IndexType type, VariableList prev) {
|
public VariableList(IndexType type, VariableList prev) {
|
||||||
this.indexType = type;
|
this.indexType = type;
|
||||||
this.offset = () -> {
|
this.offset = () -> {
|
||||||
if (prev.offset != null) return prev.offset.getAsInt() + prev.size();
|
if (prev.offset != null) return prev.offset.getAsInt() + prev.size();
|
||||||
else return prev.size();
|
else return prev.size();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public VariableList(IndexType type) {
|
public VariableList(IndexType type) {
|
||||||
this.indexType = type;
|
this.indexType = type;
|
||||||
this.offset = null;
|
this.offset = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,11 @@ public class ArgumentsNode extends Node {
|
|||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.loadArgs());
|
if (pollute) target.add(Instruction.loadArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgumentsNode(Location loc) {
|
public ArgumentsNode(Location loc) {
|
||||||
super(loc);
|
super(loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,75 +13,75 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
|
|
||||||
|
|
||||||
public class ArrayNode extends Node {
|
public class ArrayNode extends Node {
|
||||||
public final Node[] statements;
|
public final Node[] statements;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
for (var stm : statements) {
|
for (var stm : statements) {
|
||||||
if (stm != null) stm.compileFunctions(target);
|
if (stm != null) stm.compileFunctions(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
target.add(Instruction.loadArr(statements.length));
|
target.add(Instruction.loadArr(statements.length));
|
||||||
|
|
||||||
for (var i = 0; i < statements.length; i++) {
|
for (var i = 0; i < statements.length; i++) {
|
||||||
var el = statements[i];
|
var el = statements[i];
|
||||||
if (el != null) {
|
if (el != null) {
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup());
|
||||||
el.compile(target, true);
|
el.compile(target, true);
|
||||||
target.add(Instruction.storeMember(i));
|
target.add(Instruction.storeMember(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayNode(Location loc, Node[] statements) {
|
public ArrayNode(Location loc, Node[] statements) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.statements = statements;
|
this.statements = statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ArrayNode> parse(Source src, int i) {
|
public static ParseRes<ArrayNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "[")) return ParseRes.failed();
|
if (!src.is(i + n, "[")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var values = new ArrayList<Node>();
|
var values = new ArrayList<Node>();
|
||||||
|
|
||||||
loop: while (true) {
|
loop: while (true) {
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
if (src.is(i + n, "]")) {
|
if (src.is(i + n, "]")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (src.is(i + n, ",")) {
|
while (src.is(i + n, ",")) {
|
||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
values.add(null);
|
values.add(null);
|
||||||
|
|
||||||
if (src.is(i + n, "]")) {
|
if (src.is(i + n, "]")) {
|
||||||
n++;
|
n++;
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 2);
|
var res = JavaScript.parseExpression(src, i + n, 2);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element.");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element.");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
values.add(res.result);
|
values.add(res.result);
|
||||||
|
|
||||||
if (src.is(i + n, ",")) n++;
|
if (src.is(i + n, ",")) n++;
|
||||||
else if (src.is(i + n, "]")) {
|
else if (src.is(i + n, "]")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new ArrayNode(loc, values.toArray(new Node[0])), n);
|
return ParseRes.res(new ArrayNode(loc, values.toArray(new Node[0])), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,11 @@ public class GlobalThisNode extends Node {
|
|||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.loadGlob());
|
if (pollute) target.add(Instruction.loadGlob());
|
||||||
}
|
}
|
||||||
|
|
||||||
public GlobalThisNode(Location loc) {
|
public GlobalThisNode(Location loc) {
|
||||||
super(loc);
|
super(loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,97 +18,97 @@ import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
|||||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
|
|
||||||
public class ObjectNode extends Node {
|
public class ObjectNode extends Node {
|
||||||
public final List<Member> members;
|
public final List<Member> members;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
for (var member : members) member.compileFunctions(target);
|
for (var member : members) member.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
target.add(Instruction.loadObj());
|
target.add(Instruction.loadObj());
|
||||||
for (var el : members) el.compile(target, true);
|
for (var el : members) el.compile(target, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectNode(Location loc, List<Member> map) {
|
public ObjectNode(Location loc, List<Member> map) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.members = map;
|
this.members = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParseRes<Node> parseComputePropName(Source src, int i) {
|
private static ParseRes<Node> parseComputePropName(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
if (!src.is(i + n, "[")) return ParseRes.failed();
|
if (!src.is(i + n, "[")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var val = JavaScript.parseExpression(src, i, 0);
|
var val = JavaScript.parseExpression(src, i, 0);
|
||||||
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property");
|
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property");
|
||||||
n += val.n;
|
n += val.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket after compute property");
|
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket after compute property");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
return ParseRes.res(val.result, n);
|
return ParseRes.res(val.result, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<Node> parsePropName(Source src, int i) {
|
public static ParseRes<Node> parsePropName(Source src, int i) {
|
||||||
return ParseRes.first(src, i,
|
return ParseRes.first(src, i,
|
||||||
(s, j) -> {
|
(s, j) -> {
|
||||||
var m = Parsing.skipEmpty(s, j);
|
var m = Parsing.skipEmpty(s, j);
|
||||||
var l = s.loc(j + m);
|
var l = s.loc(j + m);
|
||||||
|
|
||||||
var r = Parsing.parseIdentifier(s, j + m);
|
var r = Parsing.parseIdentifier(s, j + m);
|
||||||
if (r.isSuccess()) return ParseRes.res(new StringNode(l, r.result), r.n);
|
if (r.isSuccess()) return ParseRes.res(new StringNode(l, r.result), r.n);
|
||||||
else return r.chainError();
|
else return r.chainError();
|
||||||
},
|
},
|
||||||
StringNode::parse,
|
StringNode::parse,
|
||||||
NumberNode::parse,
|
NumberNode::parse,
|
||||||
ObjectNode::parseComputePropName
|
ObjectNode::parseComputePropName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ObjectNode> parse(Source src, int i) {
|
public static ParseRes<ObjectNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var members = new LinkedList<Member>();
|
var members = new LinkedList<Member>();
|
||||||
|
|
||||||
if (src.is(i + n, "}")) {
|
if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
return ParseRes.res(new ObjectNode(loc, members), n);
|
return ParseRes.res(new ObjectNode(loc, members), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ParseRes<Member> prop = ParseRes.first(src, i + n,
|
ParseRes<Member> prop = ParseRes.first(src, i + n,
|
||||||
PropertyMemberNode::parse,
|
PropertyMemberNode::parse,
|
||||||
FieldMemberNode::parse
|
FieldMemberNode::parse
|
||||||
);
|
);
|
||||||
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal");
|
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal");
|
||||||
n += prop.n;
|
n += prop.n;
|
||||||
|
|
||||||
members.add(prop.result);
|
members.add(prop.result);
|
||||||
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
if (src.is(i + n, ",")) {
|
if (src.is(i + n, ",")) {
|
||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (src.is(i + n, "}")) {
|
if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, "}")) {
|
else if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new ObjectNode(loc, members), n);
|
return ParseRes.res(new ObjectNode(loc, members), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,29 +9,29 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class RegexNode extends Node {
|
public class RegexNode extends Node {
|
||||||
public final String pattern, flags;
|
public final String pattern, flags;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
target.add(Instruction.loadRegex(pattern, flags));
|
target.add(Instruction.loadRegex(pattern, flags));
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<RegexNode> parse(Source src, int i) {
|
public static ParseRes<RegexNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n, '/')) return ParseRes.failed();
|
if (!src.is(i + n, '/')) return ParseRes.failed();
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var source = new StringBuilder();
|
var source = new StringBuilder();
|
||||||
var flags = new StringBuilder();
|
var flags = new StringBuilder();
|
||||||
|
|
||||||
var inBrackets = false;
|
var inBrackets = false;
|
||||||
|
|
||||||
loop: while (true) {
|
loop: while (true) {
|
||||||
switch (src.at(i + n)) {
|
switch (src.at(i + n)) {
|
||||||
case '[':
|
case '[':
|
||||||
inBrackets = true;
|
inBrackets = true;
|
||||||
@ -60,26 +60,26 @@ public class RegexNode extends Node {
|
|||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
char c = src.at(i + n, '\0');
|
char c = src.at(i + n, '\0');
|
||||||
|
|
||||||
if (src.is(i + n, v -> Parsing.isAny(c, "dgimsuy"))) {
|
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");
|
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);
|
flags.append(c);
|
||||||
}
|
}
|
||||||
else break;
|
else break;
|
||||||
|
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new RegexNode(loc, source.toString(), flags.toString()), n);
|
return ParseRes.res(new RegexNode(loc, source.toString(), flags.toString()), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegexNode(Location loc, String pattern, String flags) {
|
public RegexNode(Location loc, String pattern, String flags) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,11 @@ public class ThisNode extends Node {
|
|||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.loadThis());
|
if (pollute) target.add(Instruction.loadThis());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ThisNode(Location loc) {
|
public ThisNode(Location loc) {
|
||||||
super(loc);
|
super(loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,63 +11,63 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
|
|
||||||
public class VariableNode extends Node implements ChangeTarget {
|
public class VariableNode extends Node implements ChangeTarget {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String assignName() { return name; }
|
public String assignName() { return name; }
|
||||||
|
|
||||||
@Override public void beforeChange(CompileResult target) {
|
@Override public void beforeChange(CompileResult target) {
|
||||||
target.add(VariableNode.toGet(target, loc(), name));
|
target.add(VariableNode.toGet(target, loc(), name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
target.add(toGet(target, loc(), name, true, false)).setLocation(loc());
|
target.add(toGet(target, loc(), name, true, false)).setLocation(loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) {
|
public static Instruction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) {
|
||||||
var i = target.scope.get(name, false);
|
var i = target.scope.get(name, false);
|
||||||
|
|
||||||
if (i != null) {
|
if (i != null) {
|
||||||
if (keep) return i.index().toGet();
|
if (keep) return i.index().toGet();
|
||||||
else return Instruction.nop();
|
else return Instruction.nop();
|
||||||
}
|
}
|
||||||
else return Instruction.globGet(name, forceGet);
|
else return Instruction.globGet(name, forceGet);
|
||||||
}
|
}
|
||||||
public static Instruction toGet(CompileResult target, Location loc, String name) {
|
public static Instruction toGet(CompileResult target, Location loc, String name) {
|
||||||
return toGet(target, loc, name, true, false);
|
return toGet(target, loc, name, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction toSet(CompileResult target, Location loc, String name, boolean keep, boolean init) {
|
public static Instruction toSet(CompileResult target, Location loc, String name, boolean keep, boolean init) {
|
||||||
var i = target.scope.get(name, false);
|
var i = target.scope.get(name, false);
|
||||||
|
|
||||||
if (i != null) return i.index().toSet(keep);
|
if (i != null) return i.index().toSet(keep);
|
||||||
else return Instruction.globSet(name, keep, init);
|
else return Instruction.globSet(name, keep, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableNode(Location loc, String name) {
|
public VariableNode(Location loc, String name) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<VariableNode> parse(Source src, int i) {
|
public static ParseRes<VariableNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var literal = Parsing.parseIdentifier(src, i);
|
var literal = Parsing.parseIdentifier(src, i);
|
||||||
if (!literal.isSuccess()) return literal.chainError();
|
if (!literal.isSuccess()) return literal.chainError();
|
||||||
n += literal.n;
|
n += literal.n;
|
||||||
|
|
||||||
if (!JavaScript.checkVarName(literal.result)) {
|
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("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported");
|
||||||
return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result));
|
return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new VariableNode(loc, literal.result), n);
|
return ParseRes.res(new VariableNode(loc, literal.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,17 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class BoolNode extends Node {
|
public class BoolNode extends Node {
|
||||||
public final boolean value;
|
public final boolean value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.pushValue(value));
|
if (pollute) target.add(Instruction.pushValue(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoolNode(Location loc, boolean value) {
|
public BoolNode(Location loc, boolean value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,9 @@ public class NullNode extends Node {
|
|||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.pushNull());
|
if (pollute) target.add(Instruction.pushNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
public NullNode(Location loc) { super(loc); }
|
public NullNode(Location loc) { super(loc); }
|
||||||
}
|
}
|
||||||
|
@ -9,35 +9,35 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class NumberNode extends Node {
|
public class NumberNode extends Node {
|
||||||
public final double value;
|
public final double value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.pushValue(value));
|
if (pollute) target.add(Instruction.pushValue(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NumberNode(Location loc, double value) {
|
public NumberNode(Location loc, double value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double power(double a, long b) {
|
public static double power(double a, long b) {
|
||||||
if (b == 0) return 1;
|
if (b == 0) return 1;
|
||||||
if (b == 1) return a;
|
if (b == 1) return a;
|
||||||
if (b < 0) return 1 / power(a, -b);
|
if (b < 0) return 1 / power(a, -b);
|
||||||
|
|
||||||
if ((b & 1) == 0) return power(a * a, b / 2);
|
if ((b & 1) == 0) return power(a * a, b / 2);
|
||||||
else return a * power(a * a, b / 2);
|
else return a * power(a * a, b / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<NumberNode> parse(Source src, int i) {
|
public static ParseRes<NumberNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var res = Parsing.parseNumber(src, i + n, false);
|
var res = Parsing.parseNumber(src, i + n, false);
|
||||||
if (res.isSuccess()) return ParseRes.res(new NumberNode(loc, res.result), n + res.n);
|
if (res.isSuccess()) return ParseRes.res(new NumberNode(loc, res.result), n + res.n);
|
||||||
else return res.chainError();
|
else return res.chainError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,26 +9,26 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class StringNode extends Node {
|
public class StringNode extends Node {
|
||||||
public final String value;
|
public final String value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (pollute) target.add(Instruction.pushValue(value));
|
if (pollute) target.add(Instruction.pushValue(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringNode(Location loc, String value) {
|
public StringNode(Location loc, String value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<StringNode> parse(Source src, int i) {
|
public static ParseRes<StringNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var res = Parsing.parseString(src, i + n);
|
var res = Parsing.parseString(src, i + n);
|
||||||
if (res.isSuccess()) return ParseRes.res(new StringNode(loc, res.result), n + res.n);
|
if (res.isSuccess()) return ParseRes.res(new StringNode(loc, res.result), n + res.n);
|
||||||
else return res.chainError();
|
else return res.chainError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,43 +9,43 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||||
|
|
||||||
public class AssignNode extends Node implements AssignTarget {
|
public class AssignNode extends Node implements AssignTarget {
|
||||||
public final AssignTarget assignable;
|
public final AssignTarget assignable;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
((Node)assignable).compileFunctions(target);
|
((Node)assignable).compileFunctions(target);
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here");
|
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here");
|
||||||
|
|
||||||
assignable.beforeAssign(target);
|
assignable.beforeAssign(target);
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
assignable.afterAssign(target, pollute);
|
assignable.afterAssign(target, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed");
|
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed");
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.dup(2, 0));
|
if (pollute) target.add(Instruction.dup(2, 0));
|
||||||
else target.add(Instruction.dup());
|
else target.add(Instruction.dup());
|
||||||
target.add(Instruction.pushUndefined());
|
target.add(Instruction.pushUndefined());
|
||||||
target.add(Instruction.operation(Operation.EQUALS));
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
var start = target.temp();
|
var start = target.temp();
|
||||||
target.add(Instruction.discard());
|
target.add(Instruction.discard());
|
||||||
|
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
|
|
||||||
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||||
|
|
||||||
assignable.assign(target, false);
|
assignable.assign(target, false);
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssignNode(Location loc, AssignTarget assignable, Node value) {
|
public AssignNode(Location loc, AssignTarget assignable, Node value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.assignable = assignable;
|
this.assignable = assignable;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,103 +13,103 @@ import me.topchetoeu.jscript.compilation.JavaScript;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class CallNode extends Node {
|
public class CallNode extends Node {
|
||||||
public final Node func;
|
public final Node func;
|
||||||
public final Node[] args;
|
public final Node[] args;
|
||||||
public final boolean isNew;
|
public final boolean isNew;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
func.compileFunctions(target);
|
func.compileFunctions(target);
|
||||||
for (var arg : args) arg.compileFunctions(target);
|
for (var arg : args) arg.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
if (!isNew && func instanceof IndexNode indexNode) {
|
if (!isNew && func instanceof IndexNode indexNode) {
|
||||||
var obj = indexNode.object;
|
var obj = indexNode.object;
|
||||||
var index = indexNode.index;
|
var index = indexNode.index;
|
||||||
|
|
||||||
obj.compile(target, true);
|
obj.compile(target, true);
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup());
|
||||||
IndexNode.indexLoad(target, index, true);
|
IndexNode.indexLoad(target, index, true);
|
||||||
|
|
||||||
for (var arg : args) arg.compile(target, true);
|
for (var arg : args) arg.compile(target, true);
|
||||||
|
|
||||||
target.add(Instruction.call(args.length, true));
|
target.add(Instruction.call(args.length, true));
|
||||||
|
|
||||||
target.setLocationAndDebug(loc(), type);
|
target.setLocationAndDebug(loc(), type);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
func.compile(target, true);
|
func.compile(target, true);
|
||||||
for (var arg : args) arg.compile(target, true);
|
for (var arg : args) arg.compile(target, true);
|
||||||
|
|
||||||
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
|
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
|
||||||
else target.add(Instruction.call(args.length, false)).setLocationAndDebug(loc(), type);
|
else target.add(Instruction.call(args.length, false)).setLocationAndDebug(loc(), type);
|
||||||
}
|
}
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
compile(target, pollute, BreakpointType.STEP_IN);
|
compile(target, pollute, BreakpointType.STEP_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallNode(Location loc, boolean isNew, Node func, Node ...args) {
|
public CallNode(Location loc, boolean isNew, Node func, Node ...args) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.isNew = isNew;
|
this.isNew = isNew;
|
||||||
this.func = func;
|
this.func = func;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<CallNode> parseCall(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<CallNode> parseCall(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 17) return ParseRes.failed();
|
if (precedence > 17) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "(")) return ParseRes.failed();
|
if (!src.is(i + n, "(")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var args = new ArrayList<Node>();
|
var args = new ArrayList<Node>();
|
||||||
boolean prevArg = false;
|
boolean prevArg = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var argRes = JavaScript.parseExpression(src, i + n, 2);
|
var argRes = JavaScript.parseExpression(src, i + n, 2);
|
||||||
n += argRes.n;
|
n += argRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (argRes.isSuccess()) {
|
if (argRes.isSuccess()) {
|
||||||
args.add(argRes.result);
|
args.add(argRes.result);
|
||||||
prevArg = true;
|
prevArg = true;
|
||||||
}
|
}
|
||||||
else if (argRes.isError()) return argRes.chainError();
|
else if (argRes.isError()) return argRes.chainError();
|
||||||
|
|
||||||
if (prevArg && src.is(i + n, ",")) {
|
if (prevArg && src.is(i + n, ",")) {
|
||||||
prevArg = false;
|
prevArg = false;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
else if (src.is(i + n, ")")) {
|
else if (src.is(i + n, ")")) {
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren");
|
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");
|
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(new Node[0])), n);
|
return ParseRes.res(new CallNode(loc, false, prev, args.toArray(new Node[0])), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<CallNode> parseNew(Source src, int i) {
|
public static ParseRes<CallNode> parseNew(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed();
|
||||||
n += 3;
|
n += 3;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 18);
|
var valRes = JavaScript.parseExpression(src, i + n, 18);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword.");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword.");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
var callRes = CallNode.parseCall(src, i + n, valRes.result, 0);
|
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.isFailed()) return ParseRes.res(new CallNode(loc, true, valRes.result), n);
|
||||||
if (callRes.isError()) return callRes.chainError();
|
if (callRes.isError()) return callRes.chainError();
|
||||||
n += callRes.n;
|
n += callRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new CallNode(loc, true, callRes.result.func, callRes.result.args), n);
|
return ParseRes.res(new CallNode(loc, true, callRes.result.func, callRes.result.args), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,53 +13,53 @@ import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
|||||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
|
||||||
public class ChangeNode extends Node {
|
public class ChangeNode extends Node {
|
||||||
public final ChangeTarget changable;
|
public final ChangeTarget changable;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final Operation op;
|
public final Operation op;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
((Node)changable).compileFunctions(target);
|
((Node)changable).compileFunctions(target);
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
changable.beforeChange(target);
|
changable.beforeChange(target);
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
target.add(Instruction.operation(op));
|
target.add(Instruction.operation(op));
|
||||||
changable.afterAssign(target, pollute);
|
changable.afterAssign(target, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) {
|
public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.changable = changable;
|
this.changable = changable;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.op = op;
|
this.op = op;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ChangeNode> parsePrefixIncrease(Source src, int i) {
|
public static ParseRes<ChangeNode> parsePrefixIncrease(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "++")) return ParseRes.failed();
|
if (!src.is(i + n, "++")) return ParseRes.failed();
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 15);
|
var res = JavaScript.parseExpression(src, i + n, 15);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||||
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||||
|
|
||||||
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n);
|
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
|
public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "--")) return ParseRes.failed();
|
if (!src.is(i + n, "--")) return ParseRes.failed();
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 15);
|
var res = JavaScript.parseExpression(src, i + n, 15);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||||
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||||
|
|
||||||
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n);
|
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,33 +11,33 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
|
|
||||||
|
|
||||||
public class DiscardNode extends Node {
|
public class DiscardNode extends Node {
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
if (value != null) value.compileFunctions(target);
|
if (value != null) value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (value != null) value.compile(target, false);
|
if (value != null) value.compile(target, false);
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DiscardNode(Location loc, Node val) {
|
public DiscardNode(Location loc, Node val) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = val;
|
this.value = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<DiscardNode> parse(Source src, int i) {
|
public static ParseRes<DiscardNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed();
|
||||||
n += 4;
|
n += 4;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 14);
|
var valRes = JavaScript.parseExpression(src, i + n, 14);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword.");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword.");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new DiscardNode(loc, valRes.result), n);
|
return ParseRes.res(new DiscardNode(loc, valRes.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,141 +14,141 @@ import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
|||||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
|
|
||||||
public class IndexNode extends Node implements ChangeTarget {
|
public class IndexNode extends Node implements ChangeTarget {
|
||||||
public final Node object;
|
public final Node object;
|
||||||
public final Node index;
|
public final Node index;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
object.compileFunctions(target);
|
object.compileFunctions(target);
|
||||||
index.compileFunctions(target);
|
index.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void beforeAssign(CompileResult target) {
|
@Override public void beforeAssign(CompileResult target) {
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
indexStorePushKey(target, index);
|
indexStorePushKey(target, index);
|
||||||
}
|
}
|
||||||
@Override public void beforeChange(CompileResult target) {
|
@Override public void beforeChange(CompileResult target) {
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
|
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup());
|
||||||
target.add(Instruction.loadMember((int)num.value));
|
target.add(Instruction.loadMember((int)num.value));
|
||||||
}
|
}
|
||||||
else if (index instanceof StringNode str) {
|
else if (index instanceof StringNode str) {
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.dup());
|
||||||
target.add(Instruction.loadMember(str.value));
|
target.add(Instruction.loadMember(str.value));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
index.compile(target, true);
|
index.compile(target, true);
|
||||||
|
|
||||||
target.add(Instruction.dup(1, 1));
|
target.add(Instruction.dup(1, 1));
|
||||||
target.add(Instruction.dup(1, 1));
|
target.add(Instruction.dup(1, 1));
|
||||||
target.add(Instruction.loadMember());
|
target.add(Instruction.loadMember());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void assign(CompileResult target, boolean pollute) {
|
@Override public void assign(CompileResult target, boolean pollute) {
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
target.add(Instruction.dup(1, 1));
|
target.add(Instruction.dup(1, 1));
|
||||||
indexStorePushKey(target, index);
|
indexStorePushKey(target, index);
|
||||||
indexStore(target, index, pollute);
|
indexStore(target, index, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
indexStore(target, index, pollute);
|
indexStore(target, index, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
|
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
if (dupObj) target.add(Instruction.dup());
|
if (dupObj) target.add(Instruction.dup());
|
||||||
|
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
target.add(Instruction.loadMember((int)num.value));
|
target.add(Instruction.loadMember((int)num.value));
|
||||||
}
|
}
|
||||||
else if (index instanceof StringNode str) {
|
else if (index instanceof StringNode str) {
|
||||||
target.add(Instruction.loadMember(str.value));
|
target.add(Instruction.loadMember(str.value));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
index.compile(target, true);
|
index.compile(target, true);
|
||||||
target.add(Instruction.loadMember());
|
target.add(Instruction.loadMember());
|
||||||
}
|
}
|
||||||
|
|
||||||
target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
compile(target, false, pollute);
|
compile(target, false, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndexNode(Location loc, Node object, Node index) {
|
public IndexNode(Location loc, Node object, Node index) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<IndexNode> parseIndex(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<IndexNode> parseIndex(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 18) return ParseRes.failed();
|
if (precedence > 18) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "[")) return ParseRes.failed();
|
if (!src.is(i + n, "[")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 0);
|
var valRes = JavaScript.parseExpression(src, i + n, 0);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket");
|
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket");
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
return ParseRes.res(new IndexNode(loc, prev, valRes.result), n);
|
return ParseRes.res(new IndexNode(loc, prev, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<IndexNode> parseMember(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<IndexNode> parseMember(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 18) return ParseRes.failed();
|
if (precedence > 18) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, ".")) return ParseRes.failed();
|
if (!src.is(i + n, ".")) return ParseRes.failed();
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var literal = Parsing.parseIdentifier(src, i + n);
|
var literal = Parsing.parseIdentifier(src, i + n);
|
||||||
if (!literal.isSuccess()) return literal.chainError(src.loc(i + n), "Expected an identifier after member access.");
|
if (!literal.isSuccess()) return literal.chainError(src.loc(i + n), "Expected an identifier after member access.");
|
||||||
n += literal.n;
|
n += literal.n;
|
||||||
|
|
||||||
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
|
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void indexStorePushKey(CompileResult target, Node index) {
|
public static void indexStorePushKey(CompileResult target, Node index) {
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) return;
|
if (index instanceof NumberNode num && (int)num.value == num.value) return;
|
||||||
if (index instanceof StringNode) return;
|
if (index instanceof StringNode) return;
|
||||||
index.compile(target, true);
|
index.compile(target, true);
|
||||||
}
|
}
|
||||||
public static void indexStore(CompileResult target, Node index, boolean pollute) {
|
public static void indexStore(CompileResult target, Node index, boolean pollute) {
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
target.add(Instruction.storeMember((int)num.value, pollute));
|
target.add(Instruction.storeMember((int)num.value, pollute));
|
||||||
}
|
}
|
||||||
else if (index instanceof StringNode str) {
|
else if (index instanceof StringNode str) {
|
||||||
target.add(Instruction.storeMember(str.value, pollute));
|
target.add(Instruction.storeMember(str.value, pollute));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
target.add(Instruction.storeMember(pollute));
|
target.add(Instruction.storeMember(pollute));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static void indexLoad(CompileResult target, Node index, boolean pollute) {
|
public static void indexLoad(CompileResult target, Node index, boolean pollute) {
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
target.add(Instruction.loadMember((int)num.value));
|
target.add(Instruction.loadMember((int)num.value));
|
||||||
}
|
}
|
||||||
else if (index instanceof StringNode str) {
|
else if (index instanceof StringNode str) {
|
||||||
target.add(Instruction.loadMember(str.value));
|
target.add(Instruction.loadMember(str.value));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
index.compile(target, true);
|
index.compile(target, true);
|
||||||
target.add(Instruction.loadMember());
|
target.add(Instruction.loadMember());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,41 +10,41 @@ import me.topchetoeu.jscript.compilation.JavaScript;
|
|||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
public class LazyAndNode extends Node {
|
public class LazyAndNode extends Node {
|
||||||
public final Node first, second;
|
public final Node first, second;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
first.compileFunctions(target);
|
first.compileFunctions(target);
|
||||||
second.compileFunctions(target);
|
second.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
first.compile(target, true);
|
first.compile(target, true);
|
||||||
if (pollute) target.add(Instruction.dup());
|
if (pollute) target.add(Instruction.dup());
|
||||||
int start = target.temp();
|
int start = target.temp();
|
||||||
if (pollute) target.add(Instruction.discard());
|
if (pollute) target.add(Instruction.discard());
|
||||||
second.compile(target, pollute);
|
second.compile(target, pollute);
|
||||||
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LazyAndNode(Location loc, Node first, Node second) {
|
public LazyAndNode(Location loc, Node first, Node second) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.first = first;
|
this.first = first;
|
||||||
this.second = second;
|
this.second = second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ParseRes<LazyAndNode> parse(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<LazyAndNode> parse(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence < 4) return ParseRes.failed();
|
if (precedence < 4) return ParseRes.failed();
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n, "&&")) return ParseRes.failed();
|
if (!src.is(i + n, "&&")) return ParseRes.failed();
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 4);
|
var res = JavaScript.parseExpression(src, i + n, 4);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator.");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator.");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
return ParseRes.res(new LazyAndNode(loc, prev, res.result), n);
|
return ParseRes.res(new LazyAndNode(loc, prev, res.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,41 +11,41 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
|
|
||||||
|
|
||||||
public class LazyOrNode extends Node {
|
public class LazyOrNode extends Node {
|
||||||
public final Node first, second;
|
public final Node first, second;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
first.compileFunctions(target);
|
first.compileFunctions(target);
|
||||||
second.compileFunctions(target);
|
second.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
first.compile(target, true);
|
first.compile(target, true);
|
||||||
if (pollute) target.add(Instruction.dup());
|
if (pollute) target.add(Instruction.dup());
|
||||||
int start = target.temp();
|
int start = target.temp();
|
||||||
if (pollute) target.add(Instruction.discard());
|
if (pollute) target.add(Instruction.discard());
|
||||||
second.compile(target, pollute);
|
second.compile(target, pollute);
|
||||||
target.set(start, Instruction.jmpIf(target.size() - start));
|
target.set(start, Instruction.jmpIf(target.size() - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LazyOrNode(Location loc, Node first, Node second) {
|
public LazyOrNode(Location loc, Node first, Node second) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.first = first;
|
this.first = first;
|
||||||
this.second = second;
|
this.second = second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ParseRes<LazyOrNode> parse(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<LazyOrNode> parse(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence < 3) return ParseRes.failed();
|
if (precedence < 3) return ParseRes.failed();
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
if (!src.is(i + n, "||")) return ParseRes.failed();
|
if (!src.is(i + n, "||")) return ParseRes.failed();
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 4);
|
var res = JavaScript.parseExpression(src, i + n, 4);
|
||||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator.");
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator.");
|
||||||
n += res.n;
|
n += res.n;
|
||||||
|
|
||||||
return ParseRes.res(new LazyOrNode(loc, prev, res.result), n);
|
return ParseRes.res(new LazyOrNode(loc, prev, res.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,220 +18,220 @@ import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
|
|||||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
|
|
||||||
public class OperationNode extends Node {
|
public class OperationNode extends Node {
|
||||||
private static interface OperatorFactory {
|
private static interface OperatorFactory {
|
||||||
String token();
|
String token();
|
||||||
int precedence();
|
int precedence();
|
||||||
ParseRes<Node> construct(Source src, int i, Node prev);
|
ParseRes<Node> construct(Source src, int i, Node prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NormalOperatorFactory implements OperatorFactory {
|
private static class NormalOperatorFactory implements OperatorFactory {
|
||||||
public final String token;
|
public final String token;
|
||||||
public final int precedence;
|
public final int precedence;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
|
|
||||||
@Override public int precedence() { return precedence; }
|
@Override public int precedence() { return precedence; }
|
||||||
@Override public String token() { return token; }
|
@Override public String token() { return token; }
|
||||||
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
||||||
var loc = src.loc(i);
|
var loc = src.loc(i);
|
||||||
|
|
||||||
var other = JavaScript.parseExpression(src, i, precedence + 1);
|
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));
|
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);
|
return ParseRes.res(new OperationNode(loc, operation, prev, (Node)other.result), other.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NormalOperatorFactory(String token, int precedence, Operation operation) {
|
public NormalOperatorFactory(String token, int precedence, Operation operation) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.precedence = precedence;
|
this.precedence = precedence;
|
||||||
this.operation = operation;
|
this.operation = operation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static class AssignmentOperatorFactory implements OperatorFactory {
|
private static class AssignmentOperatorFactory implements OperatorFactory {
|
||||||
public final String token;
|
public final String token;
|
||||||
public final int precedence;
|
public final int precedence;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
|
|
||||||
@Override public int precedence() { return precedence; }
|
@Override public int precedence() { return precedence; }
|
||||||
@Override public String token() { return token; }
|
@Override public String token() { return token; }
|
||||||
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
||||||
var loc = src.loc(i);
|
var loc = src.loc(i);
|
||||||
|
|
||||||
if (operation == null) {
|
if (operation == null) {
|
||||||
if (!(prev instanceof AssignTargetLike target)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
|
if (!(prev instanceof AssignTargetLike target)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
|
||||||
|
|
||||||
var other = JavaScript.parseExpression(src, i, precedence);
|
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));
|
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||||
|
|
||||||
return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), other.n);
|
return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), other.n);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!(prev instanceof ChangeTarget target)) return ParseRes.error(loc, String.format("Expected a changeable expression before '%s'", token));
|
if (!(prev instanceof ChangeTarget target)) return ParseRes.error(loc, String.format("Expected a changeable expression before '%s'", token));
|
||||||
|
|
||||||
var other = JavaScript.parseExpression(src, i, precedence);
|
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));
|
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||||
|
|
||||||
return ParseRes.res(new ChangeNode(loc, target, other.result, operation), other.n);
|
return ParseRes.res(new ChangeNode(loc, target, other.result, operation), other.n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.precedence = precedence;
|
this.precedence = precedence;
|
||||||
this.operation = operation;
|
this.operation = operation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static class LazyAndFactory implements OperatorFactory {
|
private static class LazyAndFactory implements OperatorFactory {
|
||||||
@Override public int precedence() { return 4; }
|
@Override public int precedence() { return 4; }
|
||||||
@Override public String token() { return "&&"; }
|
@Override public String token() { return "&&"; }
|
||||||
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
||||||
var loc = src.loc(i);
|
var loc = src.loc(i);
|
||||||
|
|
||||||
var other = JavaScript.parseExpression(src, i, 5);
|
var other = JavaScript.parseExpression(src, i, 5);
|
||||||
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '&&'");
|
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);
|
return ParseRes.res(new LazyAndNode(loc, prev, (Node)other.result), other.n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static class LazyOrFactory implements OperatorFactory {
|
private static class LazyOrFactory implements OperatorFactory {
|
||||||
@Override public int precedence() { return 5; }
|
@Override public int precedence() { return 5; }
|
||||||
@Override public String token() { return "||"; }
|
@Override public String token() { return "||"; }
|
||||||
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
||||||
var loc = src.loc(i);
|
var loc = src.loc(i);
|
||||||
|
|
||||||
var other = JavaScript.parseExpression(src, i, 6);
|
var other = JavaScript.parseExpression(src, i, 6);
|
||||||
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '||'");
|
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);
|
return ParseRes.res(new LazyOrNode(loc, prev, (Node)other.result), other.n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Node[] args;
|
public final Node[] args;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
for (var arg : args) arg.compileFunctions(target);
|
for (var arg : args) arg.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
for (var arg : args) {
|
for (var arg : args) {
|
||||||
arg.compile(target, true);
|
arg.compile(target, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
target.add(Instruction.operation(operation));
|
target.add(Instruction.operation(operation));
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OperationNode(Location loc, Operation operation, Node ...args) {
|
public OperationNode(Location loc, Operation operation, Node ...args) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.operation = operation;
|
this.operation = operation;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, OperatorFactory> factories = Arrays.asList(
|
private static final Map<String, OperatorFactory> factories = Arrays.asList(
|
||||||
new NormalOperatorFactory("*", 13, Operation.MULTIPLY),
|
new NormalOperatorFactory("*", 13, Operation.MULTIPLY),
|
||||||
new NormalOperatorFactory("/", 12, Operation.DIVIDE),
|
new NormalOperatorFactory("/", 12, Operation.DIVIDE),
|
||||||
new NormalOperatorFactory("%", 12, Operation.MODULO),
|
new NormalOperatorFactory("%", 12, Operation.MODULO),
|
||||||
new NormalOperatorFactory("-", 11, Operation.SUBTRACT),
|
new NormalOperatorFactory("-", 11, Operation.SUBTRACT),
|
||||||
new NormalOperatorFactory("+", 11, Operation.ADD),
|
new NormalOperatorFactory("+", 11, Operation.ADD),
|
||||||
new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT),
|
new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT),
|
||||||
new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT),
|
new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT),
|
||||||
new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT),
|
new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT),
|
||||||
new NormalOperatorFactory(">", 9, Operation.GREATER),
|
new NormalOperatorFactory(">", 9, Operation.GREATER),
|
||||||
new NormalOperatorFactory("<", 9, Operation.LESS),
|
new NormalOperatorFactory("<", 9, Operation.LESS),
|
||||||
new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS),
|
new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS),
|
||||||
new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS),
|
new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS),
|
||||||
new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS),
|
new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS),
|
||||||
new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS),
|
new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS),
|
||||||
new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS),
|
new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS),
|
||||||
new NormalOperatorFactory("===", 8, Operation.EQUALS),
|
new NormalOperatorFactory("===", 8, Operation.EQUALS),
|
||||||
new NormalOperatorFactory("&", 7, Operation.AND),
|
new NormalOperatorFactory("&", 7, Operation.AND),
|
||||||
new NormalOperatorFactory("^", 6, Operation.XOR),
|
new NormalOperatorFactory("^", 6, Operation.XOR),
|
||||||
new NormalOperatorFactory("|", 5, Operation.OR),
|
new NormalOperatorFactory("|", 5, Operation.OR),
|
||||||
|
|
||||||
new AssignmentOperatorFactory("=", 2, null),
|
new AssignmentOperatorFactory("=", 2, null),
|
||||||
new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY),
|
new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY),
|
||||||
new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE),
|
new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE),
|
||||||
new AssignmentOperatorFactory("%=", 2, Operation.MODULO),
|
new AssignmentOperatorFactory("%=", 2, Operation.MODULO),
|
||||||
new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT),
|
new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT),
|
||||||
new AssignmentOperatorFactory("+=", 2, Operation.ADD),
|
new AssignmentOperatorFactory("+=", 2, Operation.ADD),
|
||||||
new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT),
|
new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT),
|
||||||
new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT),
|
new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT),
|
||||||
new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT),
|
new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT),
|
||||||
new AssignmentOperatorFactory("&=", 2, Operation.AND),
|
new AssignmentOperatorFactory("&=", 2, Operation.AND),
|
||||||
new AssignmentOperatorFactory("^=", 2, Operation.XOR),
|
new AssignmentOperatorFactory("^=", 2, Operation.XOR),
|
||||||
new AssignmentOperatorFactory("|=", 2, Operation.OR),
|
new AssignmentOperatorFactory("|=", 2, Operation.OR),
|
||||||
|
|
||||||
new LazyAndFactory(),
|
new LazyAndFactory(),
|
||||||
new LazyOrFactory()
|
new LazyOrFactory()
|
||||||
).stream().collect(Collectors.toMap(v -> v.token(), v -> v));
|
).stream().collect(Collectors.toMap(v -> v.token(), v -> v));
|
||||||
|
|
||||||
private static final List<String> operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList());
|
private static final List<String> operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList());
|
||||||
|
|
||||||
public static ParseRes<OperationNode> parsePrefix(Source src, int i) {
|
public static ParseRes<OperationNode> parsePrefix(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
Operation operation = null;
|
Operation operation = null;
|
||||||
String op;
|
String op;
|
||||||
|
|
||||||
if (src.is(i + n, op = "+")) operation = Operation.POS;
|
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.NEG;
|
||||||
else if (src.is(i + n, op = "~")) operation = Operation.INVERSE;
|
else if (src.is(i + n, op = "~")) operation = Operation.INVERSE;
|
||||||
else if (src.is(i + n, op = "!")) operation = Operation.NOT;
|
else if (src.is(i + n, op = "!")) operation = Operation.NOT;
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
|
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var res = JavaScript.parseExpression(src, i + n, 14);
|
var res = JavaScript.parseExpression(src, i + n, 14);
|
||||||
|
|
||||||
if (res.isSuccess()) return ParseRes.res(new OperationNode(loc, operation, res.result), n + res.n);
|
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));
|
else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op));
|
||||||
}
|
}
|
||||||
public static ParseRes<OperationNode> parseInstanceof(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<OperationNode> parseInstanceof(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 9) return ParseRes.failed();
|
if (precedence > 9) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var kw = Parsing.parseIdentifier(src, i + n, "instanceof");
|
var kw = Parsing.parseIdentifier(src, i + n, "instanceof");
|
||||||
if (!kw.isSuccess()) return kw.chainError();
|
if (!kw.isSuccess()) return kw.chainError();
|
||||||
n += kw.n;
|
n += kw.n;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 10);
|
var valRes = JavaScript.parseExpression(src, i + n, 10);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'.");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'.");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new OperationNode(loc, Operation.INSTANCEOF, prev, valRes.result), n);
|
return ParseRes.res(new OperationNode(loc, Operation.INSTANCEOF, prev, valRes.result), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<OperationNode> parseIn(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<OperationNode> parseIn(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 9) return ParseRes.failed();
|
if (precedence > 9) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
var kw = Parsing.parseIdentifier(src, i + n, "in");
|
var kw = Parsing.parseIdentifier(src, i + n, "in");
|
||||||
if (!kw.isSuccess()) return kw.chainError();
|
if (!kw.isSuccess()) return kw.chainError();
|
||||||
n += kw.n;
|
n += kw.n;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 10);
|
var valRes = JavaScript.parseExpression(src, i + n, 10);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'.");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'.");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new OperationNode(loc, Operation.IN, valRes.result, prev), n);
|
return ParseRes.res(new OperationNode(loc, Operation.IN, valRes.result, prev), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<? extends Node> parseOperator(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<? extends Node> parseOperator(Source src, int i, Node prev, int precedence) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
for (var token : operatorsByLength) {
|
for (var token : operatorsByLength) {
|
||||||
var factory = factories.get(token);
|
var factory = factories.get(token);
|
||||||
|
|
||||||
if (!src.is(i + n, token)) continue;
|
if (!src.is(i + n, token)) continue;
|
||||||
if (factory.precedence() < precedence) return ParseRes.failed();
|
if (factory.precedence() < precedence) return ParseRes.failed();
|
||||||
|
|
||||||
n += token.length();
|
n += token.length();
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var res = factory.construct(src, i + n, prev);
|
var res = factory.construct(src, i + n, prev);
|
||||||
return res.addN(n);
|
return res.addN(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,41 +16,41 @@ public class PostfixNode extends ChangeNode {
|
|||||||
((Node)changable).compileFunctions(target);
|
((Node)changable).compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
super.compile(target, pollute);
|
super.compile(target, pollute);
|
||||||
|
|
||||||
if (pollute) {
|
if (pollute) {
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
target.add(Instruction.operation(Operation.ADD));
|
target.add(Instruction.operation(Operation.ADD));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PostfixNode(Location loc, ChangeTarget value, double addAmount) {
|
public PostfixNode(Location loc, ChangeTarget value, double addAmount) {
|
||||||
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<ChangeNode> parsePostfixIncrease(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<ChangeNode> parsePostfixIncrease(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 15) return ParseRes.failed();
|
if (precedence > 15) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "++")) return ParseRes.failed();
|
if (!src.is(i + n, "++")) return ParseRes.failed();
|
||||||
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n);
|
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 15) return ParseRes.failed();
|
if (precedence > 15) return ParseRes.failed();
|
||||||
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "--")) return ParseRes.failed();
|
if (!src.is(i + n, "--")) return ParseRes.failed();
|
||||||
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n);
|
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,41 +12,41 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
public class TypeofNode extends Node {
|
public class TypeofNode extends Node {
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (value instanceof VariableNode varNode) {
|
if (value instanceof VariableNode varNode) {
|
||||||
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true, true));
|
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true, true));
|
||||||
if (pollute) target.add(Instruction.typeof());
|
if (pollute) target.add(Instruction.typeof());
|
||||||
else target.add(Instruction.discard());
|
else target.add(Instruction.discard());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value.compile(target, pollute);
|
value.compile(target, pollute);
|
||||||
if (pollute) target.add(Instruction.typeof());
|
if (pollute) target.add(Instruction.typeof());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeofNode(Location loc, Node value) {
|
public TypeofNode(Location loc, Node value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<TypeofNode> parse(Source src, int i) {
|
public static ParseRes<TypeofNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed();
|
if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed();
|
||||||
n += 6;
|
n += 6;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 15);
|
var valRes = JavaScript.parseExpression(src, i + n, 15);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword.");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword.");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
return ParseRes.res(new TypeofNode(loc, valRes.result), n);
|
return ParseRes.res(new TypeofNode(loc, valRes.result), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,31 +9,31 @@ import me.topchetoeu.jscript.compilation.Node;
|
|||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
public class VariableAssignNode extends Node {
|
public class VariableAssignNode extends Node {
|
||||||
public final String name;
|
public final String name;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final Operation operation;
|
public final Operation operation;
|
||||||
|
|
||||||
@Override public void compileFunctions(CompileResult target) {
|
@Override public void compileFunctions(CompileResult target) {
|
||||||
value.compileFunctions(target);
|
value.compileFunctions(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (operation != null) {
|
if (operation != null) {
|
||||||
target.add(VariableNode.toGet(target, loc(), name));
|
target.add(VariableNode.toGet(target, loc(), name));
|
||||||
FunctionNode.compileWithName(value, target, true, name);
|
FunctionNode.compileWithName(value, target, true, name);
|
||||||
target.add(Instruction.operation(operation));
|
target.add(Instruction.operation(operation));
|
||||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
FunctionNode.compileWithName(value, target, true, name);
|
FunctionNode.compileWithName(value, target, true, name);
|
||||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableAssignNode(Location loc, String name, Node val, Operation operation) {
|
public VariableAssignNode(Location loc, String name, Node val, Operation operation) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.value = val;
|
this.value = val;
|
||||||
this.operation = operation;
|
this.operation = operation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
|||||||
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
|
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
|
||||||
|
|
||||||
public class ArgumentsValue extends ArrayValue {
|
public class ArgumentsValue extends ArrayValue {
|
||||||
public final Frame frame;
|
public final Frame frame;
|
||||||
|
|
||||||
public ArgumentsValue(Frame frame, Value... args) {
|
public ArgumentsValue(Frame frame, Value... args) {
|
||||||
super(args);
|
super(args);
|
||||||
this.frame = frame;
|
this.frame = frame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,42 +13,42 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
|||||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||||
|
|
||||||
public interface Compiler {
|
public interface Compiler {
|
||||||
public static final Compiler DEFAULT = (env, filename, raw) -> {
|
public static final Compiler DEFAULT = (env, filename, raw) -> {
|
||||||
try {
|
try {
|
||||||
var res = JavaScript.compile(env, filename, raw);
|
var res = JavaScript.compile(env, filename, raw);
|
||||||
var body = res.body();
|
var body = res.body();
|
||||||
DebugContext.get(env).onSource(filename, raw);
|
DebugContext.get(env).onSource(filename, raw);
|
||||||
registerFunc(env, body, res);
|
registerFunc(env, body, res);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
catch (SyntaxException e) {
|
catch (SyntaxException e) {
|
||||||
var res = EngineException.ofSyntax(e.msg);
|
var res = EngineException.ofSyntax(e.msg);
|
||||||
res.add(env, e.loc.filename() + "", e.loc);
|
res.add(env, e.loc.filename() + "", e.loc);
|
||||||
throw res;
|
throw res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public Key<Compiler> KEY = Key.of();
|
public Key<Compiler> KEY = new Key<>();
|
||||||
|
|
||||||
public FunctionBody compile(Environment env, Filename filename, String source);
|
public FunctionBody compile(Environment env, Filename filename, String source);
|
||||||
|
|
||||||
public static Compiler get(Environment ext) {
|
public static Compiler get(Environment ext) {
|
||||||
return ext.get(KEY, (env, filename, src) -> {
|
return ext.get(KEY, (env, filename, src) -> {
|
||||||
throw EngineException.ofError("No compiler attached to engine");
|
throw EngineException.ofError("No compiler attached to engine");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerFunc(Environment env, FunctionBody body, CompileResult res) {
|
static void registerFunc(Environment env, FunctionBody body, CompileResult res) {
|
||||||
var map = res.map();
|
var map = res.map();
|
||||||
|
|
||||||
DebugContext.get(env).onFunctionLoad(body, map);
|
DebugContext.get(env).onFunctionLoad(body, map);
|
||||||
|
|
||||||
for (var i = 0; i < body.children.length; i++) {
|
for (var i = 0; i < body.children.length; i++) {
|
||||||
registerFunc(env, body.children[i], res.children.get(i));
|
registerFunc(env, body.children[i], res.children.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CodeFunction compileFunc(Environment env, Filename filename, String raw) {
|
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][]);
|
return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new Value[0][]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,68 +7,68 @@ import java.util.concurrent.PriorityBlockingQueue;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public final class Engine implements EventLoop {
|
public final class Engine implements EventLoop {
|
||||||
private static class Task<T> implements Comparable<Task<?>> {
|
private static class Task<T> implements Comparable<Task<?>> {
|
||||||
public final Supplier<?> runnable;
|
public final Supplier<?> runnable;
|
||||||
public final CompletableFuture<T> notifier = new CompletableFuture<T>();
|
public final CompletableFuture<T> notifier = new CompletableFuture<T>();
|
||||||
public final boolean micro;
|
public final boolean micro;
|
||||||
|
|
||||||
public Task(Supplier<T> runnable, boolean micro) {
|
public Task(Supplier<T> runnable, boolean micro) {
|
||||||
this.runnable = runnable;
|
this.runnable = runnable;
|
||||||
this.micro = micro;
|
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);
|
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PriorityBlockingQueue<Task<?>> tasks = new PriorityBlockingQueue<>();
|
private PriorityBlockingQueue<Task<?>> tasks = new PriorityBlockingQueue<>();
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
|
||||||
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
|
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
|
||||||
var msg = new Task<T>(runnable, micro);
|
var msg = new Task<T>(runnable, micro);
|
||||||
tasks.add(msg);
|
tasks.add(msg);
|
||||||
return msg.notifier;
|
return msg.notifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void run(boolean untilEmpty) {
|
public void run(boolean untilEmpty) {
|
||||||
while (!untilEmpty || !tasks.isEmpty()) {
|
while (!untilEmpty || !tasks.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
var task = tasks.take();
|
var task = tasks.take();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
((Task<Object>)task).notifier.complete(task.runnable.get());
|
((Task<Object>)task).notifier.complete(task.runnable.get());
|
||||||
}
|
}
|
||||||
catch (CancellationException e) { throw e; }
|
catch (CancellationException e) { throw e; }
|
||||||
catch (RuntimeException e) { task.notifier.completeExceptionally(e); }
|
catch (RuntimeException e) { task.notifier.completeExceptionally(e); }
|
||||||
}
|
}
|
||||||
catch (InterruptedException | CancellationException e) {
|
catch (InterruptedException | CancellationException e) {
|
||||||
for (var msg : tasks) msg.notifier.cancel(false);
|
for (var msg : tasks) msg.notifier.cancel(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thread thread() {
|
public Thread thread() {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
public Thread start() {
|
public Thread start() {
|
||||||
if (thread == null) {
|
if (thread == null) {
|
||||||
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
|
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (thread != null) thread.interrupt();
|
if (thread != null) thread.interrupt();
|
||||||
thread = null;
|
thread = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean inLoopThread() {
|
public boolean inLoopThread() {
|
||||||
return Thread.currentThread() == thread;
|
return Thread.currentThread() == thread;
|
||||||
}
|
}
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return this.thread != null;
|
return this.thread != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,26 +11,26 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
|||||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||||
|
|
||||||
public interface EventLoop {
|
public interface EventLoop {
|
||||||
public static final Key<EventLoop> KEY = Key.of();
|
public static final Key<EventLoop> KEY = new Key<>();
|
||||||
|
|
||||||
public static EventLoop get(Environment ext) {
|
public static EventLoop get(Environment ext) {
|
||||||
if (ext.hasNotNull(KEY)) return ext.get(KEY);
|
if (ext.hasNotNull(KEY)) return ext.get(KEY);
|
||||||
else return new EventLoop() {
|
else return new EventLoop() {
|
||||||
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
|
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
|
||||||
throw EngineException.ofError("No event loop attached to environment.");
|
throw EngineException.ofError("No event loop attached to environment.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro);
|
public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro);
|
||||||
public default Future<Void> pushMsg(Runnable runnable, boolean micro) {
|
public default Future<Void> pushMsg(Runnable runnable, boolean micro) {
|
||||||
return pushMsg(() -> { runnable.run(); return null; }, micro);
|
return pushMsg(() -> { runnable.run(); return null; }, micro);
|
||||||
}
|
}
|
||||||
|
|
||||||
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
|
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
|
||||||
return pushMsg(() -> func.apply(env, thisArg, args), micro);
|
return pushMsg(() -> func.apply(env, thisArg, args), micro);
|
||||||
}
|
}
|
||||||
public default Future<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
|
public default Future<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
|
||||||
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).apply(env, thisArg, args), micro);
|
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).apply(env, thisArg, args), micro);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,371 +16,371 @@ import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.IntValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.IntValue;
|
||||||
|
|
||||||
public final class Frame {
|
public final class Frame {
|
||||||
public static final Key<Frame> KEY = Key.of();
|
public static final Key<Frame> KEY = new Key<>();
|
||||||
public static final EngineException STACK_OVERFLOW;
|
public static final EngineException STACK_OVERFLOW;
|
||||||
static {
|
static {
|
||||||
STACK_OVERFLOW = EngineException.ofRange("Stack overflow!");
|
STACK_OVERFLOW = EngineException.ofRange("Stack overflow!");
|
||||||
STACK_OVERFLOW.value.freeze();
|
STACK_OVERFLOW.value.freeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static enum TryState {
|
public static enum TryState {
|
||||||
TRY,
|
TRY,
|
||||||
CATCH,
|
CATCH,
|
||||||
FINALLY,
|
FINALLY,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TryCtx {
|
public static class TryCtx {
|
||||||
public final int start, end, catchStart, finallyStart;
|
public final int start, end, catchStart, finallyStart;
|
||||||
public final int restoreStackPtr;
|
public final int restoreStackPtr;
|
||||||
public final TryState state;
|
public final TryState state;
|
||||||
public final EngineException error;
|
public final EngineException error;
|
||||||
public final PendingResult result;
|
public final PendingResult result;
|
||||||
|
|
||||||
public boolean hasCatch() { return catchStart >= 0; }
|
public boolean hasCatch() { return catchStart >= 0; }
|
||||||
public boolean hasFinally() { return finallyStart >= 0; }
|
public boolean hasFinally() { return finallyStart >= 0; }
|
||||||
|
|
||||||
public boolean inBounds(int ptr) {
|
public boolean inBounds(int ptr) {
|
||||||
return ptr >= start && ptr < end;
|
return ptr >= start && ptr < end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCause(EngineException target) {
|
public void setCause(EngineException target) {
|
||||||
if (error != null) target.setCause(error);
|
if (error != null) target.setCause(error);
|
||||||
}
|
}
|
||||||
public TryCtx _catch(EngineException e) {
|
public TryCtx _catch(EngineException e) {
|
||||||
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
|
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
|
||||||
}
|
}
|
||||||
public TryCtx _finally(PendingResult res) {
|
public TryCtx _finally(PendingResult res) {
|
||||||
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
|
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
|
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
|
||||||
this.catchStart = catchStart;
|
this.catchStart = catchStart;
|
||||||
this.finallyStart = finallyStart;
|
this.finallyStart = finallyStart;
|
||||||
this.restoreStackPtr = stackPtr;
|
this.restoreStackPtr = stackPtr;
|
||||||
this.result = res == null ? PendingResult.ofNone() : res;
|
this.result = res == null ? PendingResult.ofNone() : res;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
this.error = err;
|
this.error = err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PendingResult {
|
private static class PendingResult {
|
||||||
public final boolean isReturn, isJump, isThrow;
|
public final boolean isReturn, isJump, isThrow;
|
||||||
public final Value value;
|
public final Value value;
|
||||||
public final EngineException error;
|
public final EngineException error;
|
||||||
public final int ptr;
|
public final int ptr;
|
||||||
public final Instruction instruction;
|
public final Instruction instruction;
|
||||||
|
|
||||||
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Value value, EngineException error, int ptr) {
|
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Value value, EngineException error, int ptr) {
|
||||||
this.instruction = instr;
|
this.instruction = instr;
|
||||||
this.isReturn = isReturn;
|
this.isReturn = isReturn;
|
||||||
this.isJump = isJump;
|
this.isJump = isJump;
|
||||||
this.isThrow = isThrow;
|
this.isThrow = isThrow;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.error = error;
|
this.error = error;
|
||||||
this.ptr = ptr;
|
this.ptr = ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PendingResult ofNone() {
|
public static PendingResult ofNone() {
|
||||||
return new PendingResult(null, false, false, false, null, null, 0);
|
return new PendingResult(null, false, false, false, null, null, 0);
|
||||||
}
|
}
|
||||||
public static PendingResult ofReturn(Value value, Instruction instr) {
|
public static PendingResult ofReturn(Value value, Instruction instr) {
|
||||||
return new PendingResult(instr, true, false, false, value, null, 0);
|
return new PendingResult(instr, true, false, false, value, null, 0);
|
||||||
}
|
}
|
||||||
public static PendingResult ofThrow(EngineException error, Instruction instr) {
|
public static PendingResult ofThrow(EngineException error, Instruction instr) {
|
||||||
return new PendingResult(instr, false, false, true, null, error, 0);
|
return new PendingResult(instr, false, false, true, null, error, 0);
|
||||||
}
|
}
|
||||||
public static PendingResult ofJump(int codePtr, Instruction instr) {
|
public static PendingResult ofJump(int codePtr, Instruction instr) {
|
||||||
return new PendingResult(instr, false, true, false, null, null, codePtr);
|
return new PendingResult(instr, false, true, false, null, null, codePtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of captures from the parent function
|
* An array of captures from the parent function
|
||||||
*/
|
*/
|
||||||
public final Value[][] captures;
|
public final Value[][] captures;
|
||||||
/**
|
/**
|
||||||
* An array of non-capture variables
|
* An array of non-capture variables
|
||||||
*/
|
*/
|
||||||
public final Value[] locals;
|
public final Value[] locals;
|
||||||
/**
|
/**
|
||||||
* An array of children-captured variables
|
* An array of children-captured variables
|
||||||
*/
|
*/
|
||||||
public final Value[][] capturables;
|
public final Value[][] capturables;
|
||||||
|
|
||||||
public final Value self;
|
public final Value self;
|
||||||
public final Value[] args;
|
public final Value[] args;
|
||||||
public final Value argsVal;
|
public final Value argsVal;
|
||||||
public final Value argsLen;
|
public final Value argsLen;
|
||||||
|
|
||||||
public final boolean isNew;
|
public final boolean isNew;
|
||||||
|
|
||||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||||
public final CodeFunction function;
|
public final CodeFunction function;
|
||||||
public final Environment env;
|
public final Environment env;
|
||||||
private final DebugContext dbg;
|
private final DebugContext dbg;
|
||||||
|
|
||||||
public Value getVar(int i) {
|
public Value getVar(int i) {
|
||||||
if (i < 0) return captures[~i][0];
|
if (i < 0) return captures[~i][0];
|
||||||
else if (i < locals.length) return locals[i];
|
else if (i < locals.length) return locals[i];
|
||||||
else return capturables[i - locals.length][0];
|
else return capturables[i - locals.length][0];
|
||||||
}
|
}
|
||||||
public Value setVar(int i, Value val) {
|
public Value setVar(int i, Value val) {
|
||||||
if (i < 0) return captures[~i][0] = val;
|
if (i < 0) return captures[~i][0] = val;
|
||||||
else if (i < locals.length) return locals[i] = val;
|
else if (i < locals.length) return locals[i] = val;
|
||||||
else return capturables[i - locals.length][0] = val;
|
else return capturables[i - locals.length][0] = val;
|
||||||
}
|
}
|
||||||
public Value[] captureVar(int i) {
|
public Value[] captureVar(int i) {
|
||||||
if (i < 0) return captures[~i];
|
if (i < 0) return captures[~i];
|
||||||
if (i >= locals.length) return capturables[i - locals.length];
|
if (i >= locals.length) return capturables[i - locals.length];
|
||||||
else throw new RuntimeException("Illegal capture");
|
else throw new RuntimeException("Illegal capture");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value[] stack = new Value[32];
|
public Value[] stack = new Value[32];
|
||||||
public int stackPtr = 0;
|
public int stackPtr = 0;
|
||||||
public int codePtr = 0;
|
public int codePtr = 0;
|
||||||
public boolean jumpFlag = false;
|
public boolean jumpFlag = false;
|
||||||
public boolean popTryFlag = false;
|
public boolean popTryFlag = false;
|
||||||
|
|
||||||
public void addTry(int start, int end, int catchStart, int finallyStart) {
|
public void addTry(int start, int end, int catchStart, int finallyStart) {
|
||||||
var err = tryStack.empty() ? null : tryStack.peek().error;
|
var err = tryStack.empty() ? null : tryStack.peek().error;
|
||||||
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
|
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
|
||||||
|
|
||||||
tryStack.add(res);
|
tryStack.add(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value peek() {
|
public Value peek() {
|
||||||
return peek(0);
|
return peek(0);
|
||||||
}
|
}
|
||||||
public Value peek(int offset) {
|
public Value peek(int offset) {
|
||||||
return stack[stackPtr - 1 - offset];
|
return stack[stackPtr - 1 - offset];
|
||||||
}
|
}
|
||||||
public Value pop() {
|
public Value pop() {
|
||||||
return stack[--stackPtr];
|
return stack[--stackPtr];
|
||||||
}
|
}
|
||||||
public Value[] take(int n) {
|
public Value[] take(int n) {
|
||||||
Value[] res = new Value[n];
|
Value[] res = new Value[n];
|
||||||
System.arraycopy(stack, stackPtr - n, res, 0, n);
|
System.arraycopy(stack, stackPtr - n, res, 0, n);
|
||||||
stackPtr -= n;
|
stackPtr -= n;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
public void push(Value val) {
|
public void push(Value val) {
|
||||||
if (stack.length <= stackPtr) {
|
if (stack.length <= stackPtr) {
|
||||||
var newStack = new Value[stack.length * 2];
|
var newStack = new Value[stack.length * 2];
|
||||||
System.arraycopy(stack, 0, newStack, 0, stack.length);
|
System.arraycopy(stack, 0, newStack, 0, stack.length);
|
||||||
stack = newStack;
|
stack = newStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
stack[stackPtr++] = val;
|
stack[stackPtr++] = val;
|
||||||
}
|
}
|
||||||
public void replace(Value val) {
|
public void replace(Value val) {
|
||||||
stack[stackPtr - 1] = val;
|
stack[stackPtr - 1] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for the love of christ don't touch this
|
// 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
|
* 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) {
|
public final Value next(Value value, Value returnValue, EngineException error) {
|
||||||
if (value != null) push(value);
|
if (value != null) push(value);
|
||||||
|
|
||||||
Instruction instr = null;
|
Instruction instr = null;
|
||||||
if (codePtr != function.body.instructions.length) instr = function.body.instructions[codePtr];
|
if (codePtr != function.body.instructions.length) instr = function.body.instructions[codePtr];
|
||||||
|
|
||||||
if (returnValue == null && error == null) {
|
if (returnValue == null && error == null) {
|
||||||
try {
|
try {
|
||||||
if (Thread.interrupted()) throw new CancellationException();
|
if (Thread.interrupted()) throw new CancellationException();
|
||||||
|
|
||||||
if (instr == null) {
|
if (instr == null) {
|
||||||
if (stackPtr > 0) returnValue = stack[stackPtr - 1];
|
if (stackPtr > 0) returnValue = stack[stackPtr - 1];
|
||||||
else returnValue = Value.UNDEFINED;
|
else returnValue = Value.UNDEFINED;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dbg.onInstruction(env, this, instr);
|
dbg.onInstruction(env, this, instr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.jumpFlag = this.popTryFlag = false;
|
this.jumpFlag = this.popTryFlag = false;
|
||||||
returnValue = InstructionRunner.exec(env, instr, this);
|
returnValue = InstructionRunner.exec(env, instr, this);
|
||||||
}
|
}
|
||||||
catch (EngineException e) {
|
catch (EngineException e) {
|
||||||
error = e.add(env, function.name, dbg.getMapOrEmpty(function).toLocation(codePtr, true));
|
error = e.add(env, function.name, dbg.getMapOrEmpty(function).toLocation(codePtr, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (StackOverflowError e) { throw STACK_OVERFLOW; }
|
catch (StackOverflowError e) { throw STACK_OVERFLOW; }
|
||||||
catch (EngineException e) { error = e; }
|
catch (EngineException e) { error = e; }
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true));
|
System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!tryStack.empty()) {
|
while (!tryStack.empty()) {
|
||||||
var tryCtx = tryStack.peek();
|
var tryCtx = tryStack.peek();
|
||||||
TryCtx newCtx = null;
|
TryCtx newCtx = null;
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
tryCtx.setCause(error);
|
tryCtx.setCause(error);
|
||||||
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
||||||
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
|
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
|
||||||
}
|
}
|
||||||
else if (returnValue != null) {
|
else if (returnValue != null) {
|
||||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
|
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
|
||||||
}
|
}
|
||||||
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
||||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
|
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
|
||||||
}
|
}
|
||||||
else if (!this.popTryFlag) newCtx = tryCtx;
|
else if (!this.popTryFlag) newCtx = tryCtx;
|
||||||
|
|
||||||
if (newCtx != null) {
|
if (newCtx != null) {
|
||||||
if (newCtx != tryCtx) {
|
if (newCtx != tryCtx) {
|
||||||
switch (newCtx.state) {
|
switch (newCtx.state) {
|
||||||
case CATCH:
|
case CATCH:
|
||||||
codePtr = tryCtx.catchStart;
|
codePtr = tryCtx.catchStart;
|
||||||
stackPtr = tryCtx.restoreStackPtr;
|
stackPtr = tryCtx.restoreStackPtr;
|
||||||
break;
|
break;
|
||||||
case FINALLY:
|
case FINALLY:
|
||||||
codePtr = tryCtx.finallyStart;
|
codePtr = tryCtx.finallyStart;
|
||||||
stackPtr = tryCtx.restoreStackPtr;
|
stackPtr = tryCtx.restoreStackPtr;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
tryStack.pop();
|
tryStack.pop();
|
||||||
tryStack.push(newCtx);
|
tryStack.push(newCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
error = null;
|
error = null;
|
||||||
returnValue = null;
|
returnValue = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
popTryFlag = false;
|
popTryFlag = false;
|
||||||
|
|
||||||
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
|
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
|
||||||
codePtr = tryCtx.finallyStart;
|
codePtr = tryCtx.finallyStart;
|
||||||
stackPtr = tryCtx.restoreStackPtr;
|
stackPtr = tryCtx.restoreStackPtr;
|
||||||
tryStack.pop();
|
tryStack.pop();
|
||||||
tryStack.push(tryCtx._finally(null));
|
tryStack.push(tryCtx._finally(null));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tryStack.pop();
|
tryStack.pop();
|
||||||
codePtr = tryCtx.end;
|
codePtr = tryCtx.end;
|
||||||
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
|
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
|
||||||
if (!jumpFlag && returnValue == null && error == null) {
|
if (!jumpFlag && returnValue == null && error == null) {
|
||||||
if (tryCtx.result.isJump) {
|
if (tryCtx.result.isJump) {
|
||||||
codePtr = tryCtx.result.ptr;
|
codePtr = tryCtx.result.ptr;
|
||||||
jumpFlag = true;
|
jumpFlag = true;
|
||||||
}
|
}
|
||||||
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
||||||
if (error == null && tryCtx.result.isThrow) {
|
if (error == null && tryCtx.result.isThrow) {
|
||||||
error = tryCtx.result.error;
|
error = tryCtx.result.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (error != null) tryCtx.setCause(error);
|
if (error != null) tryCtx.setCause(error);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnValue != null) {
|
if (returnValue != null) {
|
||||||
dbg.onInstruction(env, this, instr, returnValue, null, false);
|
dbg.onInstruction(env, this, instr, returnValue, null, false);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
var caught = false;
|
var caught = false;
|
||||||
|
|
||||||
for (var frame : dbg.getStackFrames()) {
|
for (var frame : dbg.getStackFrames()) {
|
||||||
for (var tryCtx : frame.tryStack) {
|
for (var tryCtx : frame.tryStack) {
|
||||||
if (tryCtx.state == TryState.TRY) caught = true;
|
if (tryCtx.state == TryState.TRY) caught = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg.onInstruction(env, this, instr, null, error, caught);
|
dbg.onInstruction(env, this, instr, null, error, caught);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the next instruction in the frame
|
* Executes the next instruction in the frame
|
||||||
*/
|
*/
|
||||||
public final Value next() {
|
public final Value next() {
|
||||||
return next(null, null, null);
|
return next(null, null, null);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Induces a value on the stack (as if it were returned by the last function call)
|
* Induces a value on the stack (as if it were returned by the last function call)
|
||||||
* and executes the next instruction in the frame.
|
* and executes the next instruction in the frame.
|
||||||
*
|
*
|
||||||
* @param value The value to induce
|
* @param value The value to induce
|
||||||
*/
|
*/
|
||||||
public final Value next(Value value) {
|
public final Value next(Value value) {
|
||||||
return next(value, null, null);
|
return next(value, null, null);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Induces a thrown error and executes the next instruction.
|
* Induces a thrown error and executes the next instruction.
|
||||||
* Note that this is different than just throwing the error outside the
|
* Note that this is different than just throwing the error outside the
|
||||||
* function, as the function executed could have a try-catch which
|
* function, as the function executed could have a try-catch which
|
||||||
* would otherwise handle the error
|
* would otherwise handle the error
|
||||||
*
|
*
|
||||||
* @param error The error to induce
|
* @param error The error to induce
|
||||||
*/
|
*/
|
||||||
public final Value induceError(EngineException error) {
|
public final Value induceError(EngineException error) {
|
||||||
return next(null, null, error);
|
return next(null, null, error);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Induces a return, as if there was a return statement before
|
* Induces a return, as if there was a return statement before
|
||||||
* the currently executed instruction and executes the next instruction.
|
* the currently executed instruction and executes the next instruction.
|
||||||
* Note that this is different than just returning the value outside the
|
* Note that this is different than just returning the value outside the
|
||||||
* function, as the function executed could have a try-catch which
|
* function, as the function executed could have a try-catch which
|
||||||
* would otherwise handle the error
|
* would otherwise handle the error
|
||||||
*
|
*
|
||||||
* @param value The retunr value to induce
|
* @param value The retunr value to induce
|
||||||
*/
|
*/
|
||||||
public final Value induceReturn(Value value) {
|
public final Value induceReturn(Value value) {
|
||||||
return next(null, value, null);
|
return next(null, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPush() {
|
public void onPush() {
|
||||||
DebugContext.get(env).onFramePush(env, this);
|
DebugContext.get(env).onFramePush(env, this);
|
||||||
}
|
}
|
||||||
public void onPop() {
|
public void onPop() {
|
||||||
DebugContext.get(env).onFramePop(env, this);
|
DebugContext.get(env).onFramePop(env, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an array proxy of the local locals
|
* Gets an array proxy of the local locals
|
||||||
*/
|
*/
|
||||||
public ObjectValue getValStackScope() {
|
public ObjectValue getValStackScope() {
|
||||||
return new ArrayLikeValue() {
|
return new ArrayLikeValue() {
|
||||||
@Override public Value get(int i) { return stack[i]; }
|
@Override public Value get(int i) { return stack[i]; }
|
||||||
@Override public boolean set(Environment env, int i, Value val) { return false; }
|
@Override public boolean set(Environment env, int i, Value val) { return false; }
|
||||||
@Override public boolean has(int i) { return i >= 0 && i < size(); }
|
@Override public boolean has(int i) { return i >= 0 && i < size(); }
|
||||||
@Override public boolean remove(int i) { return false; }
|
@Override public boolean remove(int i) { return false; }
|
||||||
|
|
||||||
@Override public int size() { return stackPtr; }
|
@Override public int size() { return stackPtr; }
|
||||||
@Override public boolean setSize(int val) { return false; }
|
@Override public boolean setSize(int val) { return false; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Frame(Environment env, boolean isNew, Value self, Value[] args, CodeFunction func) {
|
public Frame(Environment env, boolean isNew, Value self, Value[] args, CodeFunction func) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.dbg = DebugContext.get(env);
|
this.dbg = DebugContext.get(env);
|
||||||
this.function = func;
|
this.function = func;
|
||||||
this.isNew = isNew;
|
this.isNew = isNew;
|
||||||
|
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.argsVal = new ArgumentsValue(this, args);
|
this.argsVal = new ArgumentsValue(this, args);
|
||||||
this.argsLen = new IntValue(args.length);
|
this.argsLen = new IntValue(args.length);
|
||||||
this.captures = func.captures;
|
this.captures = func.captures;
|
||||||
|
|
||||||
this.locals = new Value[func.body.localsN];
|
this.locals = new Value[func.body.localsN];
|
||||||
Arrays.fill(this.locals, Value.UNDEFINED);
|
Arrays.fill(this.locals, Value.UNDEFINED);
|
||||||
this.capturables = new Value[func.body.capturablesN][1];
|
this.capturables = new Value[func.body.capturablesN][1];
|
||||||
for (var i = 0; i < this.capturables.length; i++) this.capturables[i][0] = Value.UNDEFINED;
|
for (var i = 0; i < this.capturables.length; i++) this.capturables[i][0] = Value.UNDEFINED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -17,70 +17,70 @@ import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
public class JSONConverter {
|
public class JSONConverter {
|
||||||
public static Value toJs(JSONElement val) {
|
public static Value toJs(JSONElement val) {
|
||||||
if (val.isBoolean()) return BoolValue.of(val.bool());
|
if (val.isBoolean()) return BoolValue.of(val.bool());
|
||||||
if (val.isString()) return StringValue.of(val.string());
|
if (val.isString()) return StringValue.of(val.string());
|
||||||
if (val.isNumber()) return NumberValue.of(val.number());
|
if (val.isNumber()) return NumberValue.of(val.number());
|
||||||
if (val.isList()) return ArrayValue.of(val.list().stream().map(JSONConverter::toJs).collect(Collectors.toList()));
|
if (val.isList()) return ArrayValue.of(val.list().stream().map(JSONConverter::toJs).collect(Collectors.toList()));
|
||||||
if (val.isMap()) {
|
if (val.isMap()) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
|
|
||||||
for (var el : val.map().entrySet()) {
|
for (var el : val.map().entrySet()) {
|
||||||
res.defineOwnMember(null, el.getKey(), toJs(el.getValue()));
|
res.defineOwnMember(null, el.getKey(), toJs(el.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
if (val.isNull()) return Value.NULL;
|
if (val.isNull()) return Value.NULL;
|
||||||
return Value.UNDEFINED;
|
return Value.UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONElement fromJs(Environment ext, Value val) {
|
public static JSONElement fromJs(Environment ext, Value val) {
|
||||||
var res = JSONConverter.fromJs(ext, val, new HashSet<>());
|
var res = JSONConverter.fromJs(ext, val, new HashSet<>());
|
||||||
if (res == null) return JSONElement.NULL;
|
if (res == null) return JSONElement.NULL;
|
||||||
else return res;
|
else return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONElement fromJs(Environment env, Value val, HashSet<Object> prev) {
|
public static JSONElement fromJs(Environment env, Value val, HashSet<Object> prev) {
|
||||||
if (val instanceof BoolValue) return JSONElement.bool(((BoolValue)val).value);
|
if (val instanceof BoolValue) return JSONElement.bool(((BoolValue)val).value);
|
||||||
if (val instanceof NumberValue) return JSONElement.number(((NumberValue)val).getDouble());
|
if (val instanceof NumberValue) return JSONElement.number(((NumberValue)val).getDouble());
|
||||||
if (val instanceof StringValue) return JSONElement.string(((StringValue)val).value);
|
if (val instanceof StringValue) return JSONElement.string(((StringValue)val).value);
|
||||||
if (val == Value.NULL) return JSONElement.NULL;
|
if (val == Value.NULL) return JSONElement.NULL;
|
||||||
if (val instanceof VoidValue) return null;
|
if (val instanceof VoidValue) return null;
|
||||||
|
|
||||||
if (val instanceof ArrayValue) {
|
if (val instanceof ArrayValue) {
|
||||||
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
|
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
|
||||||
prev.add(val);
|
prev.add(val);
|
||||||
|
|
||||||
var res = new JSONList();
|
var res = new JSONList();
|
||||||
|
|
||||||
for (var el : ((ArrayValue)val).toArray()) {
|
for (var el : ((ArrayValue)val).toArray()) {
|
||||||
var jsonEl = fromJs(env, el, prev);
|
var jsonEl = fromJs(env, el, prev);
|
||||||
if (jsonEl == null) continue;
|
if (jsonEl == null) continue;
|
||||||
res.add(jsonEl);
|
res.add(jsonEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
prev.remove(val);
|
prev.remove(val);
|
||||||
return JSONElement.of(res);
|
return JSONElement.of(res);
|
||||||
}
|
}
|
||||||
if (val instanceof ObjectValue) {
|
if (val instanceof ObjectValue) {
|
||||||
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
|
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
|
||||||
prev.add(val);
|
prev.add(val);
|
||||||
|
|
||||||
var res = new JSONMap();
|
var res = new JSONMap();
|
||||||
|
|
||||||
for (var key : val.getOwnMembers(env, true)) {
|
for (var key : val.getOwnMembers(env, true)) {
|
||||||
var el = fromJs(env, val.getMember(env, key), prev);
|
var el = fromJs(env, val.getMember(env, key), prev);
|
||||||
if (el == null) continue;
|
if (el == null) continue;
|
||||||
|
|
||||||
res.put(key, el);
|
res.put(key, el);
|
||||||
}
|
}
|
||||||
|
|
||||||
prev.remove(val);
|
prev.remove(val);
|
||||||
return JSONElement.of(res);
|
return JSONElement.of(res);
|
||||||
}
|
}
|
||||||
if (val == null) return null;
|
if (val == null) return null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,102 +17,102 @@ import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
|||||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||||
|
|
||||||
public class DebugContext {
|
public class DebugContext {
|
||||||
public static final Key<DebugContext> KEY = Key.of();
|
public static final Key<DebugContext> KEY = new Key<>();
|
||||||
public static final Key<Void> IGNORE = Key.of();
|
public static final Key<Void> IGNORE = new Key<>();
|
||||||
|
|
||||||
private HashMap<Filename, String> sources;
|
private HashMap<Filename, String> sources;
|
||||||
private WeakHashMap<FunctionBody, FunctionMap> maps;
|
private WeakHashMap<FunctionBody, FunctionMap> maps;
|
||||||
private DebugHandler debugger;
|
private DebugHandler debugger;
|
||||||
|
|
||||||
public boolean attachDebugger(DebugHandler debugger) {
|
public boolean attachDebugger(DebugHandler debugger) {
|
||||||
if (this.debugger != null) return false;
|
if (this.debugger != null) return false;
|
||||||
|
|
||||||
if (sources != null) {
|
if (sources != null) {
|
||||||
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
|
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
|
||||||
}
|
}
|
||||||
if (maps != null) {
|
if (maps != null) {
|
||||||
for (var map : maps.entrySet()) debugger.onFunctionLoad(map.getKey(), map.getValue());
|
for (var map : maps.entrySet()) debugger.onFunctionLoad(map.getKey(), map.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.debugger = debugger;
|
this.debugger = debugger;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public boolean detachDebugger(DebugHandler debugger) {
|
public boolean detachDebugger(DebugHandler debugger) {
|
||||||
if (this.debugger != debugger) return false;
|
if (this.debugger != debugger) return false;
|
||||||
return detachDebugger();
|
return detachDebugger();
|
||||||
}
|
}
|
||||||
public boolean detachDebugger() {
|
public boolean detachDebugger() {
|
||||||
this.debugger = null;
|
this.debugger = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebugHandler debugger() {
|
public DebugHandler debugger() {
|
||||||
return debugger;
|
return debugger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionMap getMap(FunctionBody func) {
|
public FunctionMap getMap(FunctionBody func) {
|
||||||
if (maps == null) return null;
|
if (maps == null) return null;
|
||||||
return maps.get(func);
|
return maps.get(func);
|
||||||
}
|
}
|
||||||
public FunctionMap getMap(FunctionValue func) {
|
public FunctionMap getMap(FunctionValue func) {
|
||||||
if (maps == null || !(func instanceof CodeFunction)) return null;
|
if (maps == null || !(func instanceof CodeFunction)) return null;
|
||||||
return getMap(((CodeFunction)func).body);
|
return getMap(((CodeFunction)func).body);
|
||||||
}
|
}
|
||||||
public FunctionMap getMapOrEmpty(FunctionBody func) {
|
public FunctionMap getMapOrEmpty(FunctionBody func) {
|
||||||
if (maps == null) return FunctionMap.EMPTY;
|
if (maps == null) return FunctionMap.EMPTY;
|
||||||
var res = maps.get(func);
|
var res = maps.get(func);
|
||||||
if (res == null) return FunctionMap.EMPTY;
|
if (res == null) return FunctionMap.EMPTY;
|
||||||
else return res;
|
else return res;
|
||||||
}
|
}
|
||||||
public FunctionMap getMapOrEmpty(FunctionValue func) {
|
public FunctionMap getMapOrEmpty(FunctionValue func) {
|
||||||
if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY;
|
if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY;
|
||||||
return getMapOrEmpty(((CodeFunction)func).body);
|
return getMapOrEmpty(((CodeFunction)func).body);
|
||||||
}
|
}
|
||||||
public List<Frame> getStackFrames() {
|
public List<Frame> getStackFrames() {
|
||||||
if (debugger == null) return Arrays.asList();
|
if (debugger == null) return Arrays.asList();
|
||||||
return this.debugger.getStackFrame();
|
return this.debugger.getStackFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFramePop(Environment env, Frame frame) {
|
public void onFramePop(Environment env, Frame frame) {
|
||||||
if (debugger != null) debugger.onFramePop(env, frame);
|
if (debugger != null) debugger.onFramePop(env, frame);
|
||||||
}
|
}
|
||||||
public void onFramePush(Environment env, Frame frame) {
|
public void onFramePush(Environment env, Frame frame) {
|
||||||
if (debugger != null) debugger.onFramePush(env, frame);
|
if (debugger != null) debugger.onFramePush(env, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean 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);
|
if (debugger != null) return debugger.onInstruction(env, frame, instruction, returnVal, error, caught);
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
public boolean onInstruction(Environment env, Frame frame, Instruction instruction) {
|
public boolean onInstruction(Environment env, Frame frame, Instruction instruction) {
|
||||||
if (debugger != null) return debugger.onInstruction(env, frame, instruction, null, null, false);
|
if (debugger != null) return debugger.onInstruction(env, frame, instruction, null, null, false);
|
||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
public void onSource(Filename filename, String source) {
|
public void onSource(Filename filename, String source) {
|
||||||
if (debugger != null) debugger.onSourceLoad(filename, source);
|
if (debugger != null) debugger.onSourceLoad(filename, source);
|
||||||
if (sources != null) sources.put(filename, source);
|
if (sources != null) sources.put(filename, source);
|
||||||
}
|
}
|
||||||
public void onFunctionLoad(FunctionBody func, FunctionMap map) {
|
public void onFunctionLoad(FunctionBody func, FunctionMap map) {
|
||||||
if (maps != null) maps.put(func, map);
|
if (maps != null) maps.put(func, map);
|
||||||
if (debugger != null) debugger.onFunctionLoad(func, map);
|
if (debugger != null) debugger.onFunctionLoad(func, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DebugContext(boolean enabled) {
|
private DebugContext(boolean enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
sources = new HashMap<>();
|
sources = new HashMap<>();
|
||||||
maps = new WeakHashMap<>();
|
maps = new WeakHashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebugContext() {
|
public DebugContext() {
|
||||||
this(true);
|
this(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean enabled(Environment exts) {
|
public static boolean enabled(Environment exts) {
|
||||||
return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE);
|
return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE);
|
||||||
}
|
}
|
||||||
public static DebugContext get(Environment exts) {
|
public static DebugContext get(Environment exts) {
|
||||||
if (enabled(exts)) return exts.get(KEY);
|
if (enabled(exts)) return exts.get(KEY);
|
||||||
else return new DebugContext(false);
|
else return new DebugContext(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,49 +11,49 @@ import me.topchetoeu.jscript.runtime.Frame;
|
|||||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||||
|
|
||||||
public interface DebugHandler {
|
public interface DebugHandler {
|
||||||
/**
|
/**
|
||||||
* Called when a script has been loaded
|
* Called when a script has been loaded
|
||||||
* @param filename The name of the source
|
* @param filename The name of the source
|
||||||
* @param source The name of the source
|
* @param source The name of the source
|
||||||
* @param breakpoints A set of all the breakpointable locations in this source
|
* @param breakpoints A set of all the breakpointable locations in this source
|
||||||
* @param map The source map associated with this file. null if this source map isn't mapped
|
* @param map The source map associated with this file. null if this source map isn't mapped
|
||||||
*/
|
*/
|
||||||
void onSourceLoad(Filename filename, String source);
|
void onSourceLoad(Filename filename, String source);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a function body has been loaded
|
* Called when a function body has been loaded
|
||||||
* @param body The body loaded
|
* @param body The body loaded
|
||||||
* @param map The map of the function
|
* @param map The map of the function
|
||||||
*/
|
*/
|
||||||
void onFunctionLoad(FunctionBody body, FunctionMap map);
|
void onFunctionLoad(FunctionBody body, FunctionMap map);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
|
* 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.
|
* This function might pause in order to await debugging commands.
|
||||||
* @param env The context of execution
|
* @param env The context of execution
|
||||||
* @param frame The frame in which execution is occuring
|
* @param frame The frame in which execution is occuring
|
||||||
* @param instruction The instruction which was or will be executed
|
* @param instruction The instruction which was or will be executed
|
||||||
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
|
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
|
||||||
* @param error The error that the instruction threw, null if none
|
* @param error The error that the instruction threw, null if none
|
||||||
* @param caught Whether or not the error has been caught
|
* @param caught Whether or not the error has been caught
|
||||||
* @return Whether or not the frame should restart (currently does nothing)
|
* @return Whether or not the frame should restart (currently does nothing)
|
||||||
*/
|
*/
|
||||||
boolean onInstruction(Environment env, 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.
|
* Called immediatly before a frame has been pushed on the frame stack.
|
||||||
* This function might pause in order to await debugging commands.
|
* This function might pause in order to await debugging commands.
|
||||||
* @param env The context of execution
|
* @param env The context of execution
|
||||||
* @param frame The code frame which was pushed
|
* @param frame The code frame which was pushed
|
||||||
*/
|
*/
|
||||||
void onFramePush(Environment env, Frame frame);
|
void onFramePush(Environment env, Frame frame);
|
||||||
/**
|
/**
|
||||||
* Called immediatly after a frame has been popped out of the frame stack.
|
* Called immediatly after a frame has been popped out of the frame stack.
|
||||||
* This function might pause in order to await debugging commands.
|
* This function might pause in order to await debugging commands.
|
||||||
* @param env The context of execution
|
* @param env The context of execution
|
||||||
* @param frame The code frame which was popped out
|
* @param frame The code frame which was popped out
|
||||||
*/
|
*/
|
||||||
void onFramePop(Environment env, Frame frame);
|
void onFramePop(Environment env, Frame frame);
|
||||||
|
|
||||||
List<Frame> getStackFrame();
|
List<Frame> getStackFrame();
|
||||||
}
|
}
|
||||||
|
@ -12,116 +12,116 @@ import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
||||||
|
|
||||||
public class EngineException extends RuntimeException {
|
public class EngineException extends RuntimeException {
|
||||||
public static class StackElement {
|
public static class StackElement {
|
||||||
public final Location location;
|
public final Location location;
|
||||||
public final String name;
|
public final String name;
|
||||||
public final Environment ext;
|
public final Environment ext;
|
||||||
|
|
||||||
public boolean visible() {
|
public boolean visible() {
|
||||||
return ext == null || !ext.get(Value.HIDE_STACK, false);
|
return ext == null || !ext.get(Value.HIDE_STACK, false);
|
||||||
}
|
}
|
||||||
public String toString() {
|
public String toString() {
|
||||||
var res = "";
|
var res = "";
|
||||||
var loc = location;
|
var loc = location;
|
||||||
|
|
||||||
if (loc != null) res += "at " + loc.toString() + " ";
|
if (loc != null) res += "at " + loc.toString() + " ";
|
||||||
if (name != null && !name.equals("")) res += "in " + name + " ";
|
if (name != null && !name.equals("")) res += "in " + name + " ";
|
||||||
|
|
||||||
return res.trim();
|
return res.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StackElement(Environment ext, Location location, String name) {
|
public StackElement(Environment ext, Location location, String name) {
|
||||||
if (name != null) name = name.trim();
|
if (name != null) name = name.trim();
|
||||||
if (name.equals("")) name = null;
|
if (name.equals("")) name = null;
|
||||||
|
|
||||||
if (ext == null) this.ext = null;
|
if (ext == null) this.ext = null;
|
||||||
else this.ext = ext;
|
else this.ext = ext;
|
||||||
|
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Value value;
|
public final Value value;
|
||||||
public EngineException cause;
|
public EngineException cause;
|
||||||
public Environment env = null;
|
public Environment env = null;
|
||||||
public final List<StackElement> stackTrace = new ArrayList<>();
|
public final List<StackElement> stackTrace = new ArrayList<>();
|
||||||
|
|
||||||
public EngineException add(Environment env, String name, Location location) {
|
public EngineException add(Environment env, String name, Location location) {
|
||||||
var el = new StackElement(env, location, name);
|
var el = new StackElement(env, location, name);
|
||||||
if (el.name == null && el.location == null) return this;
|
if (el.name == null && el.location == null) return this;
|
||||||
setEnvironment(env);
|
setEnvironment(env);
|
||||||
stackTrace.add(el);
|
stackTrace.add(el);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public EngineException setCause(EngineException cause) {
|
public EngineException setCause(EngineException cause) {
|
||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public EngineException setEnvironment(Environment env) {
|
public EngineException setEnvironment(Environment env) {
|
||||||
if (this.env == null) this.env = env;
|
if (this.env == null) this.env = env;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString(Environment env) {
|
public String toString(Environment env) {
|
||||||
var ss = new StringBuilder();
|
var ss = new StringBuilder();
|
||||||
try {
|
try {
|
||||||
ss.append(value.toString(env)).append('\n');
|
ss.append(value.toString(env)).append('\n');
|
||||||
}
|
}
|
||||||
catch (EngineException e) {
|
catch (EngineException e) {
|
||||||
var name = value.getMember(env, "name");
|
var name = value.getMember(env, "name");
|
||||||
var desc = value.getMember(env, "message");
|
var desc = value.getMember(env, "message");
|
||||||
|
|
||||||
if (name.isPrimitive() && desc.isPrimitive()) {
|
if (name.isPrimitive() && desc.isPrimitive()) {
|
||||||
if (name instanceof VoidValue) ss.append("Error: ");
|
if (name instanceof VoidValue) ss.append("Error: ");
|
||||||
else ss.append(name.toString(env) + ": ");
|
else ss.append(name.toString(env) + ": ");
|
||||||
|
|
||||||
if (desc instanceof VoidValue) ss.append("An error occurred");
|
if (desc instanceof VoidValue) ss.append("An error occurred");
|
||||||
else ss.append(desc.toString(env));
|
else ss.append(desc.toString(env));
|
||||||
|
|
||||||
ss.append("\n");
|
ss.append("\n");
|
||||||
}
|
}
|
||||||
else ss.append("[Error while stringifying]\n");
|
else ss.append("[Error while stringifying]\n");
|
||||||
}
|
}
|
||||||
for (var line : stackTrace) {
|
for (var line : stackTrace) {
|
||||||
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
|
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
|
||||||
}
|
}
|
||||||
if (cause != null) ss.append("Caused by ").append(cause.toString(env)).append('\n');
|
if (cause != null) ss.append("Caused by ").append(cause.toString(env)).append('\n');
|
||||||
ss.deleteCharAt(ss.length() - 1);
|
ss.deleteCharAt(ss.length() - 1);
|
||||||
return ss.toString();
|
return ss.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObjectValue err(String name, String msg, PrototypeProvider proto) {
|
private static ObjectValue err(String name, String msg, PrototypeProvider proto) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
res.setPrototype(proto);
|
res.setPrototype(proto);
|
||||||
|
|
||||||
if (msg == null) msg = "";
|
if (msg == null) msg = "";
|
||||||
|
|
||||||
if (name != null) res.defineOwnMember(Environment.empty(), "name", StringValue.of(name));
|
if (name != null) res.defineOwnMember(Environment.empty(), "name", StringValue.of(name));
|
||||||
res.defineOwnMember(Environment.empty(), "message", StringValue.of(msg));
|
res.defineOwnMember(Environment.empty(), "message", StringValue.of(msg));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EngineException(Value error) {
|
public EngineException(Value error) {
|
||||||
super(error.toReadable(Environment.empty()));
|
super(error.toReadable(Environment.empty()));
|
||||||
|
|
||||||
this.value = error;
|
this.value = error;
|
||||||
this.cause = null;
|
this.cause = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EngineException ofError(String name, String msg) {
|
public static EngineException ofError(String name, String msg) {
|
||||||
return new EngineException(err(name, msg, env -> env.get(Value.ERROR_PROTO)));
|
return new EngineException(err(name, msg, env -> env.get(Value.ERROR_PROTO)));
|
||||||
}
|
}
|
||||||
public static EngineException ofError(String msg) {
|
public static EngineException ofError(String msg) {
|
||||||
return new EngineException(err(null, msg, env -> env.get(Value.ERROR_PROTO)));
|
return new EngineException(err(null, msg, env -> env.get(Value.ERROR_PROTO)));
|
||||||
}
|
}
|
||||||
public static EngineException ofSyntax(String msg) {
|
public static EngineException ofSyntax(String msg) {
|
||||||
return new EngineException(err(null, msg, env -> env.get(Value.SYNTAX_ERR_PROTO)));
|
return new EngineException(err(null, msg, env -> env.get(Value.SYNTAX_ERR_PROTO)));
|
||||||
}
|
}
|
||||||
public static EngineException ofType(String msg) {
|
public static EngineException ofType(String msg) {
|
||||||
return new EngineException(err(null, msg, env -> env.get(Value.TYPE_ERR_PROTO)));
|
return new EngineException(err(null, msg, env -> env.get(Value.TYPE_ERR_PROTO)));
|
||||||
}
|
}
|
||||||
public static EngineException ofRange(String msg) {
|
public static EngineException ofRange(String msg) {
|
||||||
return new EngineException(err(null, msg, env -> env.get(Value.RANGE_ERR_PROTO)));
|
return new EngineException(err(null, msg, env -> env.get(Value.RANGE_ERR_PROTO)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,67 +6,67 @@ import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
public final class KeyCache {
|
public final class KeyCache {
|
||||||
public final Value value;
|
public final Value value;
|
||||||
private boolean isInt;
|
private boolean isInt;
|
||||||
private int intCache;
|
private int intCache;
|
||||||
private Double doubleCache;
|
private Double doubleCache;
|
||||||
private Boolean booleanCache;
|
private Boolean booleanCache;
|
||||||
private String stringCache;
|
private String stringCache;
|
||||||
|
|
||||||
public String toString(Environment env) {
|
public String toString(Environment env) {
|
||||||
if (stringCache != null) return stringCache;
|
if (stringCache != null) return stringCache;
|
||||||
else return stringCache = value.toString(env);
|
else return stringCache = value.toString(env);
|
||||||
}
|
}
|
||||||
public double toNumber(Environment env) {
|
public double toNumber(Environment env) {
|
||||||
if (doubleCache == null) {
|
if (doubleCache == null) {
|
||||||
var res = value.toNumber(env);
|
var res = value.toNumber(env);
|
||||||
isInt = res.isInt();
|
isInt = res.isInt();
|
||||||
intCache = res.getInt();
|
intCache = res.getInt();
|
||||||
doubleCache = res.getDouble();
|
doubleCache = res.getDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
return doubleCache;
|
return doubleCache;
|
||||||
}
|
}
|
||||||
public boolean isInt(Environment env) {
|
public boolean isInt(Environment env) {
|
||||||
if (doubleCache == null) toNumber(env);
|
if (doubleCache == null) toNumber(env);
|
||||||
return isInt;
|
return isInt;
|
||||||
}
|
}
|
||||||
public int toInt(Environment env) {
|
public int toInt(Environment env) {
|
||||||
if (doubleCache == null) toNumber(env);
|
if (doubleCache == null) toNumber(env);
|
||||||
return intCache;
|
return intCache;
|
||||||
}
|
}
|
||||||
public boolean toBoolean() {
|
public boolean toBoolean() {
|
||||||
if (booleanCache != null) return booleanCache;
|
if (booleanCache != null) return booleanCache;
|
||||||
else return booleanCache = value.toBoolean();
|
else return booleanCache = value.toBoolean();
|
||||||
}
|
}
|
||||||
public SymbolValue toSymbol() {
|
public SymbolValue toSymbol() {
|
||||||
if (value instanceof SymbolValue) return (SymbolValue)value;
|
if (value instanceof SymbolValue) return (SymbolValue)value;
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
public boolean isSymbol() {
|
public boolean isSymbol() {
|
||||||
return value instanceof SymbolValue;
|
return value instanceof SymbolValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyCache(Value value) {
|
public KeyCache(Value value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
public KeyCache(String value) {
|
public KeyCache(String value) {
|
||||||
this.value = StringValue.of(value);
|
this.value = StringValue.of(value);
|
||||||
this.stringCache = value;
|
this.stringCache = value;
|
||||||
this.booleanCache = !value.equals("");
|
this.booleanCache = !value.equals("");
|
||||||
}
|
}
|
||||||
public KeyCache(int value) {
|
public KeyCache(int value) {
|
||||||
this.value = NumberValue.of(value);
|
this.value = NumberValue.of(value);
|
||||||
this.isInt = true;
|
this.isInt = true;
|
||||||
this.intCache = value;
|
this.intCache = value;
|
||||||
this.doubleCache = (double)value;
|
this.doubleCache = (double)value;
|
||||||
this.booleanCache = value != 0;
|
this.booleanCache = value != 0;
|
||||||
}
|
}
|
||||||
public KeyCache(double value) {
|
public KeyCache(double value) {
|
||||||
this.value = NumberValue.of(value);
|
this.value = NumberValue.of(value);
|
||||||
this.isInt = (int)value == value;
|
this.isInt = (int)value == value;
|
||||||
this.intCache = (int)value;
|
this.intCache = (int)value;
|
||||||
this.doubleCache = value;
|
this.doubleCache = value;
|
||||||
this.booleanCache = value != 0;
|
this.booleanCache = value != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,168 +6,168 @@ import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
|
||||||
|
|
||||||
public interface Member {
|
public interface Member {
|
||||||
public static final class PropertyMember implements Member {
|
public static final class PropertyMember implements Member {
|
||||||
public final Value self;
|
public final Value self;
|
||||||
public FunctionValue getter;
|
public FunctionValue getter;
|
||||||
public FunctionValue setter;
|
public FunctionValue setter;
|
||||||
public boolean configurable;
|
public boolean configurable;
|
||||||
public boolean enumerable;
|
public boolean enumerable;
|
||||||
|
|
||||||
@Override public Value get(Environment env, Value self) {
|
@Override public Value get(Environment env, Value self) {
|
||||||
if (getter != null) return getter.apply(env, self);
|
if (getter != null) return getter.apply(env, self);
|
||||||
else return Value.UNDEFINED;
|
else return Value.UNDEFINED;
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
if (setter == null) return false;
|
if (setter == null) return false;
|
||||||
setter.apply(env, self, val);
|
setter.apply(env, self, val);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean configurable() { return configurable && self.getState().configurable; }
|
@Override public boolean configurable() { return configurable && self.getState().configurable; }
|
||||||
@Override public boolean enumerable() { return enumerable; }
|
@Override public boolean enumerable() { return enumerable; }
|
||||||
|
|
||||||
@Override public boolean redefine(Environment env, Member newMember, Value self) {
|
@Override public boolean redefine(Environment env, Member newMember, Value self) {
|
||||||
// If the given member isn't a property, we can't redefine
|
// If the given member isn't a property, we can't redefine
|
||||||
if (!(newMember instanceof PropertyMember prop)) return false;
|
if (!(newMember instanceof PropertyMember prop)) return false;
|
||||||
|
|
||||||
if (configurable()) {
|
if (configurable()) {
|
||||||
// We will overlay the getters and setters of the new member
|
// We will overlay the getters and setters of the new member
|
||||||
enumerable = prop.enumerable;
|
enumerable = prop.enumerable;
|
||||||
configurable = prop.configurable;
|
configurable = prop.configurable;
|
||||||
|
|
||||||
if (prop.getter != null) getter = prop.getter;
|
if (prop.getter != null) getter = prop.getter;
|
||||||
if (prop.setter != null) setter = prop.setter;
|
if (prop.setter != null) setter = prop.setter;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// We will pretend that a redefinition has occurred if the two members match exactly
|
// We will pretend that a redefinition has occurred if the two members match exactly
|
||||||
if (prop.configurable() != configurable()) return false;
|
if (prop.configurable() != configurable()) return false;
|
||||||
if (prop.enumerable != enumerable) return false;
|
if (prop.enumerable != enumerable) return false;
|
||||||
if (prop.getter != getter || prop.setter != setter) return false;
|
if (prop.getter != getter || prop.setter != setter) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public ObjectValue descriptor(Environment env, Value self) {
|
@Override public ObjectValue descriptor(Environment env, Value self) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
|
|
||||||
// Don't touch the ordering, as it's emulating V8
|
// Don't touch the ordering, as it's emulating V8
|
||||||
|
|
||||||
if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED);
|
if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED);
|
||||||
else res.defineOwnMember(env, "getter", getter);
|
else res.defineOwnMember(env, "getter", getter);
|
||||||
|
|
||||||
if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED);
|
if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED);
|
||||||
else res.defineOwnMember(env, "setter", setter);
|
else res.defineOwnMember(env, "setter", setter);
|
||||||
|
|
||||||
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
|
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
|
||||||
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
|
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PropertyMember(Value self, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
public PropertyMember(Value self, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.getter = getter;
|
this.getter = getter;
|
||||||
this.setter = setter;
|
this.setter = setter;
|
||||||
this.configurable = configurable;
|
this.configurable = configurable;
|
||||||
this.enumerable = enumerable;
|
this.enumerable = enumerable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class FieldMember implements Member {
|
public static abstract class FieldMember implements Member {
|
||||||
private static class SimpleFieldMember extends FieldMember {
|
private static class SimpleFieldMember extends FieldMember {
|
||||||
public Value value;
|
public Value value;
|
||||||
|
|
||||||
@Override public Value get(Environment env, Value self) { return value; }
|
@Override public Value get(Environment env, Value self) { return value; }
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
if (!writable) return false;
|
if (!writable) return false;
|
||||||
value = val;
|
value = val;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public SimpleFieldMember(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
|
public SimpleFieldMember(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
|
||||||
super(self, configurable, enumerable, writable);
|
super(self, configurable, enumerable, writable);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Value self;
|
public final Value self;
|
||||||
public boolean configurable;
|
public boolean configurable;
|
||||||
public boolean enumerable;
|
public boolean enumerable;
|
||||||
public boolean writable;
|
public boolean writable;
|
||||||
|
|
||||||
@Override public final boolean configurable() { return configurable && self.getState().configurable; }
|
@Override public final boolean configurable() { return configurable && self.getState().configurable; }
|
||||||
@Override public final boolean enumerable() { return enumerable; }
|
@Override public final boolean enumerable() { return enumerable; }
|
||||||
public final boolean writable() { return writable && self.getState().writable; }
|
public final boolean writable() { return writable && self.getState().writable; }
|
||||||
|
|
||||||
@Override public final boolean redefine(Environment env, Member newMember, Value self) {
|
@Override public final boolean redefine(Environment env, Member newMember, Value self) {
|
||||||
// If the given member isn't a field, we can't redefine
|
// If the given member isn't a field, we can't redefine
|
||||||
if (!(newMember instanceof FieldMember field)) return false;
|
if (!(newMember instanceof FieldMember field)) return false;
|
||||||
|
|
||||||
if (configurable()) {
|
if (configurable()) {
|
||||||
configurable = field.configurable;
|
configurable = field.configurable;
|
||||||
enumerable = field.enumerable;
|
enumerable = field.enumerable;
|
||||||
writable = field.enumerable;
|
writable = field.enumerable;
|
||||||
|
|
||||||
// We will try to set a new value. However, the underlying field might be immutably readonly
|
// We will try to set a new value. However, the underlying field might be immutably readonly
|
||||||
// In such case, we will silently fail, since this is not covered by the specification
|
// In such case, we will silently fail, since this is not covered by the specification
|
||||||
if (!set(env, field.get(env, self), self)) writable = false;
|
if (!set(env, field.get(env, self), self)) writable = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// New field settings must be an exact match
|
// New field settings must be an exact match
|
||||||
if (configurable() != field.configurable()) return false;
|
if (configurable() != field.configurable()) return false;
|
||||||
if (enumerable() != field.enumerable()) return false;
|
if (enumerable() != field.enumerable()) return false;
|
||||||
|
|
||||||
if (!writable()) {
|
if (!writable()) {
|
||||||
// If the field isn't writable, the redefinition should be an exact match
|
// If the field isn't writable, the redefinition should be an exact match
|
||||||
if (field.writable()) return false;
|
if (field.writable()) return false;
|
||||||
if (field.get(env, self).equals(this.get(env, self))) return false;
|
if (field.get(env, self).equals(this.get(env, self))) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Writable non-configurable fields may be made readonly or their values may be changed
|
// Writable non-configurable fields may be made readonly or their values may be changed
|
||||||
writable = field.writable;
|
writable = field.writable;
|
||||||
|
|
||||||
if (!set(env, field.get(env, self), self)) writable = false;
|
if (!set(env, field.get(env, self), self)) writable = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public ObjectValue descriptor(Environment env, Value self) {
|
@Override public ObjectValue descriptor(Environment env, Value self) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
res.defineOwnMember(env, "value", get(env, self));
|
res.defineOwnMember(env, "value", get(env, self));
|
||||||
res.defineOwnMember(env, "writable", BoolValue.of(writable));
|
res.defineOwnMember(env, "writable", BoolValue.of(writable));
|
||||||
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
|
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
|
||||||
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
|
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldMember(Value self, boolean configurable, boolean enumerable, boolean writable) {
|
public FieldMember(Value self, boolean configurable, boolean enumerable, boolean writable) {
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.configurable = configurable;
|
this.configurable = configurable;
|
||||||
this.enumerable = enumerable;
|
this.enumerable = enumerable;
|
||||||
this.writable = writable;
|
this.writable = writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FieldMember of(Value self, Value value) {
|
public static FieldMember of(Value self, Value value) {
|
||||||
return new SimpleFieldMember(self, value, true, true, true);
|
return new SimpleFieldMember(self, value, true, true, true);
|
||||||
}
|
}
|
||||||
public static FieldMember of(Value self, Value value, boolean writable) {
|
public static FieldMember of(Value self, Value value, boolean writable) {
|
||||||
return new SimpleFieldMember(self, value, true, true, writable);
|
return new SimpleFieldMember(self, value, true, true, writable);
|
||||||
}
|
}
|
||||||
public static FieldMember of(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
|
public static FieldMember of(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
|
||||||
return new SimpleFieldMember(self, value, configurable, enumerable, writable);
|
return new SimpleFieldMember(self, value, configurable, enumerable, writable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean configurable();
|
public boolean configurable();
|
||||||
public boolean enumerable();
|
public boolean enumerable();
|
||||||
public boolean redefine(Environment env, Member newMember, Value self);
|
public boolean redefine(Environment env, Member newMember, Value self);
|
||||||
public ObjectValue descriptor(Environment env, Value self);
|
public ObjectValue descriptor(Environment env, Value self);
|
||||||
|
|
||||||
public Value get(Environment env, Value self);
|
public Value get(Environment env, Value self);
|
||||||
public boolean set(Environment env, Value val, Value self);
|
public boolean set(Environment env, Value val, Value self);
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@ -6,41 +6,41 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.UserValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.UserValue;
|
||||||
|
|
||||||
public class Arguments {
|
public class Arguments {
|
||||||
public final Value self;
|
public final Value self;
|
||||||
public final Value[] args;
|
public final Value[] args;
|
||||||
public final Environment env;
|
public final Environment env;
|
||||||
public final boolean isNew;
|
public final boolean isNew;
|
||||||
|
|
||||||
public int n() {
|
public int n() {
|
||||||
return args.length;
|
return args.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean has(int i) {
|
public boolean has(int i) {
|
||||||
return i == -1 || i >= 0 && i < args.length;
|
return i == -1 || i >= 0 && i < args.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value self() {
|
public Value self() {
|
||||||
return get(-1);
|
return get(-1);
|
||||||
}
|
}
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T self(Class<T> clazz) {
|
public <T> T self(Class<T> clazz) {
|
||||||
if (self instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value;
|
if (self instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value;
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
public Value get(int i) {
|
public Value get(int i) {
|
||||||
if (i >= args.length || i < -1) return Value.UNDEFINED;
|
if (i >= args.length || i < -1) return Value.UNDEFINED;
|
||||||
else if (i == -1) return self;
|
else if (i == -1) return self;
|
||||||
else return args[i];
|
else return args[i];
|
||||||
}
|
}
|
||||||
public Value getOrDefault(int i, Value def) {
|
public Value getOrDefault(int i, Value def) {
|
||||||
if (i < -1 || i >= args.length) return def;
|
if (i < -1 || i >= args.length) return def;
|
||||||
else return get(i);
|
else return get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Arguments(Environment env, boolean isNew, Value thisArg, Value... args) {
|
public Arguments(Environment env, boolean isNew, Value thisArg, Value... args) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.self = thisArg;
|
this.self = thisArg;
|
||||||
this.isNew = isNew;
|
this.isNew = isNew;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,37 +6,37 @@ import me.topchetoeu.jscript.runtime.Frame;
|
|||||||
import me.topchetoeu.jscript.runtime.values.Value;
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
|
|
||||||
public final class CodeFunction extends FunctionValue {
|
public final class CodeFunction extends FunctionValue {
|
||||||
public final FunctionBody body;
|
public final FunctionBody body;
|
||||||
public final Value[][] captures;
|
public final Value[][] captures;
|
||||||
public Environment env;
|
public Environment env;
|
||||||
|
|
||||||
private Value onCall(Frame frame) {
|
private Value onCall(Frame frame) {
|
||||||
frame.onPush();
|
frame.onPush();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
var res = frame.next(null, null, null);
|
var res = frame.next(null, null, null);
|
||||||
if (res != null) return res;
|
if (res != null) return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
frame.onPop();
|
frame.onPop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) {
|
@Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) {
|
||||||
var frame = new Frame(env, isNew, self, args, this);
|
var frame = new Frame(env, isNew, self, args, this);
|
||||||
|
|
||||||
var res = onCall(frame);
|
var res = onCall(frame);
|
||||||
|
|
||||||
if (isNew) return frame.self;
|
if (isNew) return frame.self;
|
||||||
else return res;
|
else return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) {
|
public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) {
|
||||||
super(name, body.length);
|
super(name, body.length);
|
||||||
this.captures = captures;
|
this.captures = captures;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,103 +17,103 @@ import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
public abstract class FunctionValue extends ObjectValue {
|
public abstract class FunctionValue extends ObjectValue {
|
||||||
public String name = "";
|
public String name = "";
|
||||||
public int length;
|
public int length;
|
||||||
public Value prototype = new ObjectValue();
|
public Value prototype = new ObjectValue();
|
||||||
|
|
||||||
public boolean enableApply = true;
|
public boolean enableApply = true;
|
||||||
public boolean enableConstruct = true;
|
public boolean enableConstruct = true;
|
||||||
|
|
||||||
private final FieldMember nameField = new FieldMember(this, true, false, false) {
|
private final FieldMember nameField = new FieldMember(this, true, false, false) {
|
||||||
@Override public Value get(Environment env, Value self) {
|
@Override public Value get(Environment env, Value self) {
|
||||||
if (name == null) return StringValue.of("");
|
if (name == null) return StringValue.of("");
|
||||||
return StringValue.of(name);
|
return StringValue.of(name);
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
name = val.toString(env);
|
name = val.toString(env);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final FieldMember lengthField = new FieldMember(this, true, false, false) {
|
private final FieldMember lengthField = new FieldMember(this, true, false, false) {
|
||||||
@Override public Value get(Environment env, Value self) {
|
@Override public Value get(Environment env, Value self) {
|
||||||
return NumberValue.of(length);
|
return NumberValue.of(length);
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final FieldMember prototypeField = new FieldMember(this, false, false, true) {
|
private final FieldMember prototypeField = new FieldMember(this, false, false, true) {
|
||||||
@Override public Value get(Environment env, Value self) {
|
@Override public Value get(Environment env, Value self) {
|
||||||
return prototype;
|
return prototype;
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
prototype = val;
|
prototype = val;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
protected abstract Value onCall(Environment ext, boolean isNew, Value thisArg, Value ...args);
|
protected abstract Value onCall(Environment ext, boolean isNew, Value thisArg, Value ...args);
|
||||||
|
|
||||||
@Override public String toString() { return String.format("function %s(...)", name); }
|
@Override public String toString() { return String.format("function %s(...)", name); }
|
||||||
@Override public Value apply(Environment env, Value self, Value... args) {
|
@Override public Value apply(Environment env, Value self, Value... args) {
|
||||||
if (!enableApply) throw EngineException.ofType("Function cannot be applied");
|
if (!enableApply) throw EngineException.ofType("Function cannot be applied");
|
||||||
return onCall(env, false, self, args);
|
return onCall(env, false, self, args);
|
||||||
}
|
}
|
||||||
@Override public Value construct(Environment env, Value self, Value... args) {
|
@Override public Value construct(Environment env, Value self, Value... args) {
|
||||||
if (!enableConstruct) throw EngineException.ofType("Function cannot be constructed");
|
if (!enableConstruct) throw EngineException.ofType("Function cannot be constructed");
|
||||||
return onCall(env, true, self, args);
|
return onCall(env, true, self, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||||
switch (key.toString(env)) {
|
switch (key.toString(env)) {
|
||||||
case "length": return lengthField;
|
case "length": return lengthField;
|
||||||
case "name": return nameField;
|
case "name": return nameField;
|
||||||
case "prototype": return prototypeField;
|
case "prototype": return prototypeField;
|
||||||
default: return super.getOwnMember(env, key);
|
default: return super.getOwnMember(env, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||||
switch (key.toString(env)) {
|
switch (key.toString(env)) {
|
||||||
case "length":
|
case "length":
|
||||||
length = 0;
|
length = 0;
|
||||||
return true;
|
return true;
|
||||||
case "name":
|
case "name":
|
||||||
name = "";
|
name = "";
|
||||||
return true;
|
return true;
|
||||||
case "prototype":
|
case "prototype":
|
||||||
return false;
|
return false;
|
||||||
default: return super.deleteOwnMember(env, key);
|
default: return super.deleteOwnMember(env, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public StringValue type() { return StringValue.of("function"); }
|
@Override public StringValue type() { return StringValue.of("function"); }
|
||||||
|
|
||||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||||
var dbg = DebugContext.get(env);
|
var dbg = DebugContext.get(env);
|
||||||
var res = new StringBuilder(this.toString());
|
var res = new StringBuilder(this.toString());
|
||||||
var loc = dbg.getMapOrEmpty(this).start();
|
var loc = dbg.getMapOrEmpty(this).start();
|
||||||
|
|
||||||
if (loc != null) res.append(" @ " + loc);
|
if (loc != null) res.append(" @ " + loc);
|
||||||
|
|
||||||
var lines = new LinkedList<String>(super.toReadableLines(env, passed));
|
var lines = new LinkedList<String>(super.toReadableLines(env, passed));
|
||||||
if (lines.size() == 1 && lines.getFirst().equals("{}")) return Arrays.asList(res.toString());
|
if (lines.size() == 1 && lines.getFirst().equals("{}")) return Arrays.asList(res.toString());
|
||||||
lines.set(0, res.toString() + " " + lines.getFirst());
|
lines.set(0, res.toString() + " " + lines.getFirst());
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String val) {
|
public void setName(String val) {
|
||||||
if (this.name == null || this.name.equals("")) this.name = val;
|
if (this.name == null || this.name.equals("")) this.name = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionValue(String name, int length) {
|
public FunctionValue(String name, int length) {
|
||||||
setPrototype(FUNCTION_PROTO);
|
setPrototype(FUNCTION_PROTO);
|
||||||
|
|
||||||
if (name == null) name = "";
|
if (name == null) name = "";
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
prototype.defineOwnMember(null, "constructor", this);
|
prototype.defineOwnMember(null, "constructor", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,22 +4,22 @@ import me.topchetoeu.jscript.common.environment.Environment;
|
|||||||
import me.topchetoeu.jscript.runtime.values.Value;
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
|
|
||||||
public final class NativeFunction extends FunctionValue {
|
public final class NativeFunction extends FunctionValue {
|
||||||
public static interface NativeFunctionRunner {
|
public static interface NativeFunctionRunner {
|
||||||
Value run(Arguments args);
|
Value run(Arguments args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final NativeFunctionRunner action;
|
public final NativeFunctionRunner action;
|
||||||
|
|
||||||
@Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) {
|
@Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) {
|
||||||
return action.run(new Arguments(env, isNew, self, args));
|
return action.run(new Arguments(env, isNew, self, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
public NativeFunction(String name, NativeFunctionRunner action) {
|
||||||
super(name, 0);
|
super(name, 0);
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
public NativeFunction(NativeFunctionRunner action) {
|
public NativeFunction(NativeFunctionRunner action) {
|
||||||
super("", 0);
|
super("", 0);
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,182 +16,182 @@ import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.Value;
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
|
|
||||||
public abstract class ArrayLikeValue extends ObjectValue {
|
public abstract class ArrayLikeValue extends ObjectValue {
|
||||||
private static class IndexField extends FieldMember {
|
private static class IndexField extends FieldMember {
|
||||||
private int i;
|
private int i;
|
||||||
private ArrayLikeValue arr;
|
private ArrayLikeValue arr;
|
||||||
|
|
||||||
@Override public Value get(Environment env, Value self) {
|
@Override public Value get(Environment env, Value self) {
|
||||||
return arr.get(i);
|
return arr.get(i);
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
return arr.set(env, i, val);
|
return arr.set(env, i, val);
|
||||||
}
|
}
|
||||||
public IndexField(int i, ArrayLikeValue arr) {
|
public IndexField(int i, ArrayLikeValue arr) {
|
||||||
super(arr, true, true, true);
|
super(arr, true, true, true);
|
||||||
this.arr = arr;
|
this.arr = arr;
|
||||||
this.i = i;
|
this.i = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final FieldMember lengthField = new FieldMember(this, false, false, true) {
|
private final FieldMember lengthField = new FieldMember(this, false, false, true) {
|
||||||
@Override public Value get(Environment env, Value self) {
|
@Override public Value get(Environment env, Value self) {
|
||||||
return NumberValue.of(size());
|
return NumberValue.of(size());
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, Value val, Value self) {
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
var num = val.toNumber(env);
|
var num = val.toNumber(env);
|
||||||
if (!num.isInt()) throw EngineException.ofRange("Invalid array length");
|
if (!num.isInt()) throw EngineException.ofRange("Invalid array length");
|
||||||
|
|
||||||
var i = num.getInt();
|
var i = num.getInt();
|
||||||
if (i < 0) throw EngineException.ofRange("Invalid array length");
|
if (i < 0) throw EngineException.ofRange("Invalid array length");
|
||||||
|
|
||||||
return setSize(i);
|
return setSize(i);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract int size();
|
public abstract int size();
|
||||||
public abstract boolean setSize(int val);
|
public abstract boolean setSize(int val);
|
||||||
|
|
||||||
public abstract Value get(int i);
|
public abstract Value get(int i);
|
||||||
public abstract boolean set(Environment env, int i, Value val);
|
public abstract boolean set(Environment env, int i, Value val);
|
||||||
public abstract boolean has(int i);
|
public abstract boolean has(int i);
|
||||||
public abstract boolean remove(int i);
|
public abstract boolean remove(int i);
|
||||||
|
|
||||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||||
var res = super.getOwnMember(env, key);
|
var res = super.getOwnMember(env, key);
|
||||||
if (res != null) return res;
|
if (res != null) return res;
|
||||||
if (key.isSymbol()) return null;
|
if (key.isSymbol()) return null;
|
||||||
|
|
||||||
var num = key.toNumber(env);
|
var num = key.toNumber(env);
|
||||||
var i = key.toInt(env);
|
var i = key.toInt(env);
|
||||||
|
|
||||||
if (i == num && i >= 0 && i < size() && has(i)) return new IndexField(i, this);
|
if (i == num && i >= 0 && i < size() && has(i)) return new IndexField(i, this);
|
||||||
else if (key.toString(env).equals("length")) return lengthField;
|
else if (key.toString(env).equals("length")) return lengthField;
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
||||||
if (!(member instanceof FieldMember) || super.getOwnMember(env, key) != null) return super.defineOwnMember(env, key, member);
|
if (!(member instanceof FieldMember) || super.getOwnMember(env, key) != null) return super.defineOwnMember(env, key, member);
|
||||||
if (!getState().writable) return false;
|
if (!getState().writable) return false;
|
||||||
|
|
||||||
if (!key.isSymbol()) {
|
if (!key.isSymbol()) {
|
||||||
var num = key.toNumber(env);
|
var num = key.toNumber(env);
|
||||||
var i = key.toInt(env);
|
var i = key.toInt(env);
|
||||||
|
|
||||||
if (i == num) {
|
if (i == num) {
|
||||||
if (!getState().extendable && !has(i)) return false;
|
if (!getState().extendable && !has(i)) return false;
|
||||||
if (set(env, i, ((FieldMember)member).get(env, this))) return true;
|
if (set(env, i, ((FieldMember)member).get(env, this))) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.defineOwnMember(env, key, member);
|
return super.defineOwnMember(env, key, member);
|
||||||
}
|
}
|
||||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||||
if (!super.deleteOwnMember(env, key)) return false;
|
if (!super.deleteOwnMember(env, key)) return false;
|
||||||
if (key.isSymbol()) return true;
|
if (key.isSymbol()) return true;
|
||||||
|
|
||||||
var num = key.toNumber(env);
|
var num = key.toNumber(env);
|
||||||
var i = key.toInt(env);
|
var i = key.toInt(env);
|
||||||
|
|
||||||
if (i == num && i >= 0 && i < size()) return remove(i);
|
if (i == num && i >= 0 && i < size()) return remove(i);
|
||||||
else return true;
|
else return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||||
var res = new LinkedHashSet<String>();
|
var res = new LinkedHashSet<String>();
|
||||||
|
|
||||||
res.addAll(super.getOwnMembers(env, onlyEnumerable));
|
res.addAll(super.getOwnMembers(env, onlyEnumerable));
|
||||||
|
|
||||||
for (var i = 0; i < size(); i++) {
|
for (var i = 0; i < size(); i++) {
|
||||||
if (has(i)) res.add(i + "");
|
if (has(i)) res.add(i + "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!onlyEnumerable) res.add("length");
|
if (!onlyEnumerable) res.add("length");
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinkedList<String> toReadableBase(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
|
private LinkedList<String> toReadableBase(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
|
||||||
var stringified = new LinkedList<LinkedList<String>>();
|
var stringified = new LinkedList<LinkedList<String>>();
|
||||||
|
|
||||||
passed.add(this);
|
passed.add(this);
|
||||||
|
|
||||||
var emptyN = 0;
|
var emptyN = 0;
|
||||||
|
|
||||||
for (int i = 0; i < size(); i++) {
|
for (int i = 0; i < size(); i++) {
|
||||||
if (has(i)) {
|
if (has(i)) {
|
||||||
String emptyStr = null;
|
String emptyStr = null;
|
||||||
|
|
||||||
if (emptyN == 1) emptyStr = "<empty>";
|
if (emptyN == 1) emptyStr = "<empty>";
|
||||||
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
|
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
|
||||||
|
|
||||||
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr + ",")));
|
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr + ",")));
|
||||||
emptyN = 0;
|
emptyN = 0;
|
||||||
|
|
||||||
stringified.add(new LinkedList<>(get(i).toReadableLines(env, passed)));
|
stringified.add(new LinkedList<>(get(i).toReadableLines(env, passed)));
|
||||||
ignoredKeys.add(i + "");
|
ignoredKeys.add(i + "");
|
||||||
|
|
||||||
var entry = stringified.getLast();
|
var entry = stringified.getLast();
|
||||||
entry.set(entry.size() - 1, entry.getLast() + ",");
|
entry.set(entry.size() - 1, entry.getLast() + ",");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
emptyN++;
|
emptyN++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String emptyStr = null;
|
String emptyStr = null;
|
||||||
|
|
||||||
if (emptyN == 1) emptyStr = "<empty>";
|
if (emptyN == 1) emptyStr = "<empty>";
|
||||||
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
|
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
|
||||||
|
|
||||||
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr)));
|
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr)));
|
||||||
else if (stringified.size() > 0) {
|
else if (stringified.size() > 0) {
|
||||||
var lastEntry = stringified.getLast();
|
var lastEntry = stringified.getLast();
|
||||||
lastEntry.set(lastEntry.size() - 1, lastEntry.getLast().substring(0, lastEntry.getLast().length() - 1));
|
lastEntry.set(lastEntry.size() - 1, lastEntry.getLast().substring(0, lastEntry.getLast().length() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
passed.remove(this);
|
passed.remove(this);
|
||||||
|
|
||||||
if (stringified.size() == 0) return new LinkedList<>(Arrays.asList("[]"));
|
if (stringified.size() == 0) return new LinkedList<>(Arrays.asList("[]"));
|
||||||
var concat = new StringBuilder();
|
var concat = new StringBuilder();
|
||||||
for (var entry : stringified) {
|
for (var entry : stringified) {
|
||||||
// We make a one-liner only when all members are one-liners
|
// We make a one-liner only when all members are one-liners
|
||||||
if (entry.size() != 1) {
|
if (entry.size() != 1) {
|
||||||
concat = null;
|
concat = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (concat.length() != 0) concat.append(" ");
|
if (concat.length() != 0) concat.append(" ");
|
||||||
concat.append(entry.get(0));
|
concat.append(entry.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want too long one-liners
|
// We don't want too long one-liners
|
||||||
if (concat != null && concat.length() < 160) return new LinkedList<>(Arrays.asList("[" + concat.toString() + "]"));
|
if (concat != null && concat.length() < 160) return new LinkedList<>(Arrays.asList("[" + concat.toString() + "]"));
|
||||||
|
|
||||||
var res = new LinkedList<String>();
|
var res = new LinkedList<String>();
|
||||||
|
|
||||||
res.add("[");
|
res.add("[");
|
||||||
|
|
||||||
for (var entry : stringified) {
|
for (var entry : stringified) {
|
||||||
for (var line : entry) {
|
for (var line : entry) {
|
||||||
res.add(" " + line);
|
res.add(" " + line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
|
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
|
||||||
res.add("]");
|
res.add("]");
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||||
var ignored = new HashSet<String>();
|
var ignored = new HashSet<String>();
|
||||||
var lines = toReadableBase(env, passed, ignored);
|
var lines = toReadableBase(env, passed, ignored);
|
||||||
|
|
||||||
var superLines = new LinkedList<String>(super.toReadableLines(env, passed, ignored));
|
var superLines = new LinkedList<String>(super.toReadableLines(env, passed, ignored));
|
||||||
if (superLines.size() == 1 && superLines.getFirst().equals("{}")) return lines;
|
if (superLines.size() == 1 && superLines.getFirst().equals("{}")) return lines;
|
||||||
|
|
||||||
lines.set(lines.size() - 1, lines.getLast() + " " + superLines.getFirst());
|
lines.set(lines.size() - 1, lines.getLast() + " " + superLines.getFirst());
|
||||||
lines.addAll(superLines.subList(1, superLines.size()));
|
lines.addAll(superLines.subList(1, superLines.size()));
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,141 +10,141 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
||||||
|
|
||||||
public class ArrayValue extends ArrayLikeValue implements Iterable<Value> {
|
public class ArrayValue extends ArrayLikeValue implements Iterable<Value> {
|
||||||
private Value[] values;
|
private Value[] values;
|
||||||
private int size;
|
private int size;
|
||||||
|
|
||||||
private Value[] alloc(int index) {
|
private Value[] alloc(int index) {
|
||||||
index++;
|
index++;
|
||||||
if (index < values.length) return values;
|
if (index < values.length) return values;
|
||||||
if (index < values.length * 2) index = values.length * 2;
|
if (index < values.length * 2) index = values.length * 2;
|
||||||
|
|
||||||
var arr = new Value[index];
|
var arr = new Value[index];
|
||||||
System.arraycopy(values, 0, arr, 0, values.length);
|
System.arraycopy(values, 0, arr, 0, values.length);
|
||||||
return values = arr;
|
return values = arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() { return size; }
|
public int size() { return size; }
|
||||||
public boolean setSize(int val) {
|
public boolean setSize(int val) {
|
||||||
if (val < 0) return false;
|
if (val < 0) return false;
|
||||||
if (size > val) shrink(size - val);
|
if (size > val) shrink(size - val);
|
||||||
else {
|
else {
|
||||||
values = alloc(val);
|
values = alloc(val);
|
||||||
size = val;
|
size = val;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Value get(int i) {
|
@Override public Value get(int i) {
|
||||||
if (i < 0 || i >= size) return null;
|
if (i < 0 || i >= size) return null;
|
||||||
var res = values[i];
|
var res = values[i];
|
||||||
|
|
||||||
if (res == null) return Value.UNDEFINED;
|
if (res == null) return Value.UNDEFINED;
|
||||||
else return res;
|
else return res;
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, int i, Value val) {
|
@Override public boolean set(Environment env, int i, Value val) {
|
||||||
if (i < 0) return false;
|
if (i < 0) return false;
|
||||||
|
|
||||||
alloc(i)[i] = val;
|
alloc(i)[i] = val;
|
||||||
if (i >= size) size = i + 1;
|
if (i >= size) size = i + 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@Override public boolean has(int i) {
|
@Override public boolean has(int i) {
|
||||||
return i >= 0 && i < size && values[i] != null;
|
return i >= 0 && i < size && values[i] != null;
|
||||||
}
|
}
|
||||||
@Override public boolean remove(int i) {
|
@Override public boolean remove(int i) {
|
||||||
if (i < 0 || i >= values.length) return true;
|
if (i < 0 || i >= values.length) return true;
|
||||||
values[i] = null;
|
values[i] = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shrink(int n) {
|
public void shrink(int n) {
|
||||||
if (n >= values.length) {
|
if (n >= values.length) {
|
||||||
values = new Value[16];
|
values = new Value[16];
|
||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < n; i++) values[--size] = null;
|
for (int i = 0; i < n; i++) values[--size] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value[] toArray() {
|
public Value[] toArray() {
|
||||||
var res = new Value[size];
|
var res = new Value[size];
|
||||||
copyTo(res, 0, 0, size);
|
copyTo(res, 0, 0, size);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyTo(Value[] arr, int sourceStart, int destStart, int count) {
|
public void copyTo(Value[] arr, int sourceStart, int destStart, int count) {
|
||||||
var nullFill = Math.max(0, arr.length - size - destStart);
|
var nullFill = Math.max(0, arr.length - size - destStart);
|
||||||
count -= nullFill;
|
count -= nullFill;
|
||||||
|
|
||||||
System.arraycopy(values, sourceStart, arr, destStart, count);
|
System.arraycopy(values, sourceStart, arr, destStart, count);
|
||||||
Arrays.fill(arr, count, nullFill + count, null);
|
Arrays.fill(arr, count, nullFill + count, null);
|
||||||
}
|
}
|
||||||
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
|
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||||
if (arr == this) {
|
if (arr == this) {
|
||||||
move(sourceStart, destStart, count);
|
move(sourceStart, destStart, count);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.copyFrom(values, sourceStart, destStart, count);
|
arr.copyFrom(values, sourceStart, destStart, count);
|
||||||
}
|
}
|
||||||
public void copyFrom(Value[] arr, int sourceStart, int destStart, int count) {
|
public void copyFrom(Value[] arr, int sourceStart, int destStart, int count) {
|
||||||
alloc(destStart + count);
|
alloc(destStart + count);
|
||||||
System.arraycopy(arr, sourceStart, values, destStart, count);
|
System.arraycopy(arr, sourceStart, values, destStart, count);
|
||||||
if (size < destStart + count) size = destStart + count;
|
if (size < destStart + count) size = destStart + count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(int srcI, int dstI, int n) {
|
public void move(int srcI, int dstI, int n) {
|
||||||
values = alloc(dstI + n);
|
values = alloc(dstI + n);
|
||||||
System.arraycopy(values, srcI, values, dstI, n);
|
System.arraycopy(values, srcI, values, dstI, n);
|
||||||
if (dstI + n >= size) size = dstI + n;
|
if (dstI + n >= size) size = dstI + n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sort(Comparator<Value> comparator) {
|
public void sort(Comparator<Value> comparator) {
|
||||||
Arrays.sort(values, 0, size, (a, b) -> {
|
Arrays.sort(values, 0, size, (a, b) -> {
|
||||||
var _a = 0;
|
var _a = 0;
|
||||||
var _b = 0;
|
var _b = 0;
|
||||||
|
|
||||||
if (a == null) _a = 2;
|
if (a == null) _a = 2;
|
||||||
if (a instanceof VoidValue) _a = 1;
|
if (a instanceof VoidValue) _a = 1;
|
||||||
|
|
||||||
if (b == null) _b = 2;
|
if (b == null) _b = 2;
|
||||||
if (b instanceof VoidValue) _b = 1;
|
if (b instanceof VoidValue) _b = 1;
|
||||||
|
|
||||||
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
|
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
|
||||||
|
|
||||||
return comparator.compare(a, b);
|
return comparator.compare(a, b);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Iterator<Value> iterator() {
|
@Override public Iterator<Value> iterator() {
|
||||||
return new Iterator<>() {
|
return new Iterator<>() {
|
||||||
private int i = 0;
|
private int i = 0;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@Override public boolean hasNext() {
|
||||||
return i < size();
|
return i < size();
|
||||||
}
|
}
|
||||||
@Override public Value next() {
|
@Override public Value next() {
|
||||||
if (!hasNext()) return null;
|
if (!hasNext()) return null;
|
||||||
return get(i++);
|
return get(i++);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayValue() {
|
public ArrayValue() {
|
||||||
this(16);
|
this(16);
|
||||||
}
|
}
|
||||||
public ArrayValue(int cap) {
|
public ArrayValue(int cap) {
|
||||||
setPrototype(ARRAY_PROTO);
|
setPrototype(ARRAY_PROTO);
|
||||||
values = new Value[Math.min(cap, 16)];
|
values = new Value[Math.min(cap, 16)];
|
||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
public ArrayValue(Value ...values) {
|
public ArrayValue(Value ...values) {
|
||||||
this();
|
this();
|
||||||
copyFrom(values, 0, 0, values.length);
|
copyFrom(values, 0, 0, values.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayValue of(Collection<? extends Value> values) {
|
public static ArrayValue of(Collection<? extends Value> values) {
|
||||||
return new ArrayValue(values.toArray(new Value[0]));
|
return new ArrayValue(values.toArray(new Value[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,74 +8,74 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
public class ByteBufferValue extends ArrayLikeValue implements Iterable<Value> {
|
public class ByteBufferValue extends ArrayLikeValue implements Iterable<Value> {
|
||||||
public final byte[] values;
|
public final byte[] values;
|
||||||
|
|
||||||
public int size() { return values.length; }
|
public int size() { return values.length; }
|
||||||
public boolean setSize(int val) { return false; }
|
public boolean setSize(int val) { return false; }
|
||||||
|
|
||||||
@Override public Value get(int i) {
|
@Override public Value get(int i) {
|
||||||
if (i < 0 || i >= values.length) return null;
|
if (i < 0 || i >= values.length) return null;
|
||||||
return NumberValue.of(values[i]);
|
return NumberValue.of(values[i]);
|
||||||
}
|
}
|
||||||
@Override public boolean set(Environment env, int i, Value val) {
|
@Override public boolean set(Environment env, int i, Value val) {
|
||||||
if (i < 0 || i >= values.length) return false;
|
if (i < 0 || i >= values.length) return false;
|
||||||
values[i] = (byte)val.toNumber(env).getInt();
|
values[i] = (byte)val.toNumber(env).getInt();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@Override public boolean has(int i) {
|
@Override public boolean has(int i) {
|
||||||
return i >= 0 && i < values.length;
|
return i >= 0 && i < values.length;
|
||||||
}
|
}
|
||||||
@Override public boolean remove(int i) {
|
@Override public boolean remove(int i) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyTo(byte[] arr, int sourceStart, int destStart, int count) {
|
public void copyTo(byte[] arr, int sourceStart, int destStart, int count) {
|
||||||
System.arraycopy(values, sourceStart, arr, destStart, count);
|
System.arraycopy(values, sourceStart, arr, destStart, count);
|
||||||
}
|
}
|
||||||
public void copyTo(ByteBufferValue arr, int sourceStart, int destStart, int count) {
|
public void copyTo(ByteBufferValue arr, int sourceStart, int destStart, int count) {
|
||||||
arr.copyFrom(values, sourceStart, destStart, count);
|
arr.copyFrom(values, sourceStart, destStart, count);
|
||||||
}
|
}
|
||||||
public void copyFrom(byte[] arr, int sourceStart, int destStart, int count) {
|
public void copyFrom(byte[] arr, int sourceStart, int destStart, int count) {
|
||||||
System.arraycopy(arr, sourceStart, arr, destStart, count);
|
System.arraycopy(arr, sourceStart, arr, destStart, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(int srcI, int dstI, int n) {
|
public void move(int srcI, int dstI, int n) {
|
||||||
System.arraycopy(values, srcI, values, dstI, n);
|
System.arraycopy(values, srcI, values, dstI, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sort() {
|
public void sort() {
|
||||||
var buckets = new int[256];
|
var buckets = new int[256];
|
||||||
|
|
||||||
for (var i = 0; i < values.length; i++) {
|
for (var i = 0; i < values.length; i++) {
|
||||||
buckets[values[i] + 128]++;
|
buckets[values[i] + 128]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
|
|
||||||
for (var i = 0; i < values.length; i++) {
|
for (var i = 0; i < values.length; i++) {
|
||||||
Arrays.fill(values, offset, offset += buckets[i], (byte)(i - 128));
|
Arrays.fill(values, offset, offset += buckets[i], (byte)(i - 128));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Iterator<Value> iterator() {
|
@Override public Iterator<Value> iterator() {
|
||||||
return new Iterator<>() {
|
return new Iterator<>() {
|
||||||
private int i = 0;
|
private int i = 0;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@Override public boolean hasNext() {
|
||||||
return i < size();
|
return i < size();
|
||||||
}
|
}
|
||||||
@Override public Value next() {
|
@Override public Value next() {
|
||||||
if (!hasNext()) return null;
|
if (!hasNext()) return null;
|
||||||
return get(i++);
|
return get(i++);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBufferValue(int size) {
|
public ByteBufferValue(int size) {
|
||||||
this(new byte[size]);
|
this(new byte[size]);
|
||||||
}
|
}
|
||||||
public ByteBufferValue(byte[] buffer) {
|
public ByteBufferValue(byte[] buffer) {
|
||||||
setPrototype(BYTE_BUFF_PROTO);
|
setPrototype(BYTE_BUFF_PROTO);
|
||||||
this.values = buffer;
|
this.values = buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,207 +21,207 @@ import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
public class ObjectValue extends Value {
|
public class ObjectValue extends Value {
|
||||||
public static interface PrototypeProvider {
|
public static interface PrototypeProvider {
|
||||||
public ObjectValue get(Environment env);
|
public ObjectValue get(Environment env);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Property {
|
public static class Property {
|
||||||
public final FunctionValue getter;
|
public final FunctionValue getter;
|
||||||
public final FunctionValue setter;
|
public final FunctionValue setter;
|
||||||
|
|
||||||
public Property(FunctionValue getter, FunctionValue setter) {
|
public Property(FunctionValue getter, FunctionValue setter) {
|
||||||
this.getter = getter;
|
this.getter = getter;
|
||||||
this.setter = setter;
|
this.setter = setter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PrototypeProvider prototype;
|
protected PrototypeProvider prototype;
|
||||||
|
|
||||||
public LinkedHashMap<String, Member> members = new LinkedHashMap<>();
|
public LinkedHashMap<String, Member> members = new LinkedHashMap<>();
|
||||||
public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>();
|
public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>();
|
||||||
|
|
||||||
@Override public boolean isPrimitive() { return false; }
|
@Override public boolean isPrimitive() { return false; }
|
||||||
@Override public Value toPrimitive(Environment env) {
|
@Override public Value toPrimitive(Environment env) {
|
||||||
if (env != null) {
|
if (env != null) {
|
||||||
var valueOf = getMember(env, "valueOf");
|
var valueOf = getMember(env, "valueOf");
|
||||||
|
|
||||||
if (valueOf instanceof FunctionValue) {
|
if (valueOf instanceof FunctionValue) {
|
||||||
var res = valueOf.apply(env, this);
|
var res = valueOf.apply(env, this);
|
||||||
if (res.isPrimitive()) return res;
|
if (res.isPrimitive()) return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
var toString = getMember(env, "toString");
|
var toString = getMember(env, "toString");
|
||||||
if (toString instanceof FunctionValue) {
|
if (toString instanceof FunctionValue) {
|
||||||
var res = toString.apply(env, this);
|
var res = toString.apply(env, this);
|
||||||
if (res.isPrimitive()) return res;
|
if (res.isPrimitive()) return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||||
}
|
}
|
||||||
@Override public String toString(Environment env) { return toPrimitive(env).toString(env); }
|
@Override public String toString(Environment env) { return toPrimitive(env).toString(env); }
|
||||||
@Override public boolean toBoolean() { return true; }
|
@Override public boolean toBoolean() { return true; }
|
||||||
@Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
|
@Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
|
||||||
@Override public StringValue type() { return StringValue.of("object"); }
|
@Override public StringValue type() { return StringValue.of("object"); }
|
||||||
|
|
||||||
private State state = State.NORMAL;
|
private State state = State.NORMAL;
|
||||||
|
|
||||||
@Override public State getState() { return state; }
|
@Override public State getState() { return state; }
|
||||||
|
|
||||||
public final void preventExtensions() {
|
public final void preventExtensions() {
|
||||||
if (state == State.NORMAL) state = State.NON_EXTENDABLE;
|
if (state == State.NORMAL) state = State.NON_EXTENDABLE;
|
||||||
}
|
}
|
||||||
public final void seal() {
|
public final void seal() {
|
||||||
if (state == State.NORMAL || state == State.NON_EXTENDABLE) state = State.SEALED;
|
if (state == State.NORMAL || state == State.NON_EXTENDABLE) state = State.SEALED;
|
||||||
}
|
}
|
||||||
@Override public final void freeze() { state = State.FROZEN; }
|
@Override public final void freeze() { state = State.FROZEN; }
|
||||||
|
|
||||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||||
if (key.isSymbol()) {
|
if (key.isSymbol()) {
|
||||||
if (symbolMembers.size() > 0) return symbolMembers.get(key.toSymbol());
|
if (symbolMembers.size() > 0) return symbolMembers.get(key.toSymbol());
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
else if (members.size() > 0) return members.get(key.toString(env));
|
else if (members.size() > 0) return members.get(key.toString(env));
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
||||||
var old = getOwnMember(env, key);
|
var old = getOwnMember(env, key);
|
||||||
if (old != null && old.redefine(env, member, this)) return true;
|
if (old != null && old.redefine(env, member, this)) return true;
|
||||||
if (old != null && !old.configurable()) return false;
|
if (old != null && !old.configurable()) return false;
|
||||||
|
|
||||||
if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member);
|
if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member);
|
||||||
else members.put(key.toString(env), member);
|
else members.put(key.toString(env), member);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||||
if (!getState().extendable) return false;
|
if (!getState().extendable) return false;
|
||||||
|
|
||||||
var member = getOwnMember(env, key);
|
var member = getOwnMember(env, key);
|
||||||
if (member == null) return true;
|
if (member == null) return true;
|
||||||
if (!member.configurable()) return false;
|
if (!member.configurable()) return false;
|
||||||
|
|
||||||
if (key.isSymbol()) symbolMembers.remove(key.toSymbol());
|
if (key.isSymbol()) symbolMembers.remove(key.toSymbol());
|
||||||
else members.remove(key.toString(env));
|
else members.remove(key.toString(env));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||||
if (onlyEnumerable) {
|
if (onlyEnumerable) {
|
||||||
var res = new LinkedHashSet<String>();
|
var res = new LinkedHashSet<String>();
|
||||||
|
|
||||||
for (var el : members.entrySet()) {
|
for (var el : members.entrySet()) {
|
||||||
if (el.getValue().enumerable()) res.add(el.getKey());
|
if (el.getValue().enumerable()) res.add(el.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
else return members.keySet();
|
else return members.keySet();
|
||||||
}
|
}
|
||||||
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
|
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
|
||||||
if (onlyEnumerable) {
|
if (onlyEnumerable) {
|
||||||
var res = new LinkedHashSet<SymbolValue>();
|
var res = new LinkedHashSet<SymbolValue>();
|
||||||
|
|
||||||
for (var el : symbolMembers.entrySet()) {
|
for (var el : symbolMembers.entrySet()) {
|
||||||
if (el.getValue().enumerable()) res.add(el.getKey());
|
if (el.getValue().enumerable()) res.add(el.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
else return symbolMembers.keySet();
|
else return symbolMembers.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public ObjectValue getPrototype(Environment env) {
|
@Override public ObjectValue getPrototype(Environment env) {
|
||||||
if (prototype == null || env == null) return null;
|
if (prototype == null || env == null) return null;
|
||||||
else return prototype.get(env);
|
else return prototype.get(env);
|
||||||
}
|
}
|
||||||
@Override public final boolean setPrototype(Environment env, ObjectValue val) {
|
@Override public final boolean setPrototype(Environment env, ObjectValue val) {
|
||||||
return setPrototype(_env -> val);
|
return setPrototype(_env -> val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final LinkedList<String> memberToReadable(Environment env, String key, Member member, HashSet<ObjectValue> passed) {
|
private final LinkedList<String> memberToReadable(Environment env, String key, Member member, HashSet<ObjectValue> passed) {
|
||||||
if (member instanceof PropertyMember prop) {
|
if (member instanceof PropertyMember prop) {
|
||||||
if (prop.getter == null && prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [No accessors]"));
|
if (prop.getter == null && prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [No accessors]"));
|
||||||
else if (prop.getter == null) return new LinkedList<>(Arrays.asList(key + ": [Setter]"));
|
else if (prop.getter == null) return new LinkedList<>(Arrays.asList(key + ": [Setter]"));
|
||||||
else if (prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [Getter]"));
|
else if (prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [Getter]"));
|
||||||
else return new LinkedList<>(Arrays.asList(key + ": [Getter/Setter]"));
|
else return new LinkedList<>(Arrays.asList(key + ": [Getter/Setter]"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var res = new LinkedList<String>();
|
var res = new LinkedList<String>();
|
||||||
var first = true;
|
var first = true;
|
||||||
|
|
||||||
for (var line : member.get(env, this).toReadableLines(env, passed)) {
|
for (var line : member.get(env, this).toReadableLines(env, passed)) {
|
||||||
if (first) res.add(key + ": " + line);
|
if (first) res.add(key + ": " + line);
|
||||||
else res.add(line);
|
else res.add(line);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
|
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
|
||||||
passed.add(this);
|
passed.add(this);
|
||||||
|
|
||||||
var stringified = new LinkedList<LinkedList<String>>();
|
var stringified = new LinkedList<LinkedList<String>>();
|
||||||
|
|
||||||
for (var entry : getOwnSymbolMembers(env, true)) {
|
for (var entry : getOwnSymbolMembers(env, true)) {
|
||||||
var member = getOwnMember(env, entry);
|
var member = getOwnMember(env, entry);
|
||||||
stringified.add(memberToReadable(env, "[" + entry.value + "]", member, passed));
|
stringified.add(memberToReadable(env, "[" + entry.value + "]", member, passed));
|
||||||
}
|
}
|
||||||
for (var entry : getOwnMembers(env, true)) {
|
for (var entry : getOwnMembers(env, true)) {
|
||||||
if (ignoredKeys.contains(entry)) continue;
|
if (ignoredKeys.contains(entry)) continue;
|
||||||
|
|
||||||
var member = getOwnMember(env, entry);
|
var member = getOwnMember(env, entry);
|
||||||
stringified.add(memberToReadable(env, entry, member, passed));
|
stringified.add(memberToReadable(env, entry, member, passed));
|
||||||
}
|
}
|
||||||
|
|
||||||
passed.remove(this);
|
passed.remove(this);
|
||||||
|
|
||||||
if (stringified.size() == 0) return Arrays.asList("{}");
|
if (stringified.size() == 0) return Arrays.asList("{}");
|
||||||
var concat = new StringBuilder();
|
var concat = new StringBuilder();
|
||||||
for (var entry : stringified) {
|
for (var entry : stringified) {
|
||||||
// We make a one-liner only when all members are one-liners
|
// We make a one-liner only when all members are one-liners
|
||||||
if (entry.size() != 1) {
|
if (entry.size() != 1) {
|
||||||
concat = null;
|
concat = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (concat.length() != 0) concat.append(", ");
|
if (concat.length() != 0) concat.append(", ");
|
||||||
concat.append(entry.get(0));
|
concat.append(entry.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want too long one-liners
|
// We don't want too long one-liners
|
||||||
if (concat != null && concat.length() < 80) return Arrays.asList("{ " + concat.toString() + " }");
|
if (concat != null && concat.length() < 80) return Arrays.asList("{ " + concat.toString() + " }");
|
||||||
|
|
||||||
var res = new LinkedList<String>();
|
var res = new LinkedList<String>();
|
||||||
|
|
||||||
res.add("{");
|
res.add("{");
|
||||||
|
|
||||||
for (var entry : stringified) {
|
for (var entry : stringified) {
|
||||||
for (var line : entry) {
|
for (var line : entry) {
|
||||||
res.add(" " + line);
|
res.add(" " + line);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.set(res.size() - 1, res.getLast() + ",");
|
res.set(res.size() - 1, res.getLast() + ",");
|
||||||
}
|
}
|
||||||
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
|
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
|
||||||
res.add("}");
|
res.add("}");
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||||
return toReadableLines(env, passed, new HashSet<>());
|
return toReadableLines(env, passed, new HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean setPrototype(PrototypeProvider val) {
|
public final boolean setPrototype(PrototypeProvider val) {
|
||||||
if (!getState().extendable) return false;
|
if (!getState().extendable) return false;
|
||||||
prototype = val;
|
prototype = val;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public final boolean setPrototype(Key<ObjectValue> key) {
|
public final boolean setPrototype(Key<ObjectValue> key) {
|
||||||
if (!getState().extendable) return false;
|
if (!getState().extendable) return false;
|
||||||
prototype = env -> env.get(key);
|
prototype = env -> env.get(key);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user