ES6 Support Groundwork + Fixes #26
@ -3,12 +3,13 @@ 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, argsN;
|
public final int localsN, capturesN, length;
|
||||||
|
|
||||||
public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) {
|
public FunctionBody(int localsN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) {
|
||||||
this.children = children;
|
this.children = children;
|
||||||
this.argsN = argsN;
|
this.length = length;
|
||||||
this.localsN = localsN;
|
this.localsN = localsN;
|
||||||
|
this.capturesN = capturesN;
|
||||||
this.instructions = instructions;
|
this.instructions = instructions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,56 +1,71 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
package me.topchetoeu.jscript.common;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class Instruction {
|
public class Instruction {
|
||||||
public static enum Type {
|
public static enum Type {
|
||||||
NOP(0),
|
RETURN(0x00),
|
||||||
RETURN(1),
|
NOP(0x01),
|
||||||
THROW(2),
|
THROW(0x02),
|
||||||
THROW_SYNTAX(3),
|
THROW_SYNTAX(0x03),
|
||||||
DELETE(4),
|
DELETE(0x04),
|
||||||
TRY_START(5),
|
TRY_START(0x05),
|
||||||
TRY_END(6),
|
TRY_END(0x06),
|
||||||
|
|
||||||
CALL(7),
|
CALL(0x10),
|
||||||
CALL_NEW(8),
|
CALL_MEMBER(0x11),
|
||||||
JMP_IF(9),
|
CALL_NEW(0x12),
|
||||||
JMP_IFN(10),
|
JMP_IF(0x13),
|
||||||
JMP(11),
|
JMP_IFN(0x14),
|
||||||
|
JMP(0x15),
|
||||||
|
|
||||||
PUSH_UNDEFINED(12),
|
PUSH_UNDEFINED(0x20),
|
||||||
PUSH_NULL(13),
|
PUSH_NULL(0x21),
|
||||||
PUSH_BOOL(14),
|
PUSH_BOOL(0x22),
|
||||||
PUSH_NUMBER(15),
|
PUSH_NUMBER(0x23),
|
||||||
PUSH_STRING(16),
|
PUSH_STRING(0x24),
|
||||||
|
DUP(0x25),
|
||||||
|
DISCARD(0x26),
|
||||||
|
|
||||||
LOAD_VAR(17),
|
LOAD_FUNC(0x30),
|
||||||
LOAD_MEMBER(18),
|
LOAD_ARR(0x31),
|
||||||
LOAD_GLOB(20),
|
LOAD_OBJ(0x32),
|
||||||
|
LOAD_GLOB(0x33),
|
||||||
|
LOAD_INTRINSICS(0x34),
|
||||||
|
LOAD_REGEX(0x35),
|
||||||
|
|
||||||
LOAD_FUNC(21),
|
LOAD_VAR(0x40),
|
||||||
LOAD_ARR(22),
|
LOAD_MEMBER(0x41),
|
||||||
LOAD_OBJ(23),
|
LOAD_MEMBER_INT(0x42),
|
||||||
STORE_SELF_FUNC(24),
|
LOAD_MEMBER_STR(0x43),
|
||||||
LOAD_REGEX(25),
|
|
||||||
|
|
||||||
DUP(26),
|
LOAD_ARGS(0x44),
|
||||||
|
LOAD_REST_ARGS(0x45),
|
||||||
|
LOAD_CALLEE(0x46),
|
||||||
|
LOAD_THIS(0x47),
|
||||||
|
|
||||||
STORE_VAR(27),
|
STORE_VAR(0x48),
|
||||||
STORE_MEMBER(28),
|
STORE_MEMBER(0x49),
|
||||||
DISCARD(29),
|
STORE_MEMBER_INT(0x4A),
|
||||||
|
STORE_MEMBER_STR(0x4B),
|
||||||
|
|
||||||
MAKE_VAR(30),
|
DEF_PROP(0x50),
|
||||||
DEF_PROP(31),
|
KEYS(0x51),
|
||||||
KEYS(32),
|
TYPEOF(0x52),
|
||||||
|
OPERATION(0x53),
|
||||||
|
|
||||||
TYPEOF(33),
|
GLOB_GET(0x60),
|
||||||
OPERATION(34);
|
GLOB_SET(0x61),
|
||||||
|
GLOB_DEF(0x62),
|
||||||
|
|
||||||
|
STACK_ALLOC(0x70),
|
||||||
|
STACK_REALLOC(0x71),
|
||||||
|
STACK_FREE(0x72);
|
||||||
|
|
||||||
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;
|
||||||
@ -110,117 +125,128 @@ public class Instruction {
|
|||||||
return params[i].equals(arg);
|
return params[i].equals(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(DataOutputStream writer) throws IOException {
|
// public void write(DataOutputStream writer) throws IOException {
|
||||||
var rawType = type.numeric;
|
// var rawType = type.numeric;
|
||||||
|
|
||||||
switch (type) {
|
// switch (type) {
|
||||||
case KEYS:
|
// case KEYS:
|
||||||
case PUSH_BOOL:
|
// case PUSH_BOOL:
|
||||||
case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break;
|
// case STORE_MEMBER:
|
||||||
case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break;
|
// case GLOB_SET:
|
||||||
case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
|
// rawType |= (boolean)get(0) ? 128 : 0; break;
|
||||||
default:
|
// case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
|
||||||
}
|
// default:
|
||||||
|
// }
|
||||||
|
|
||||||
writer.writeByte(rawType);
|
// writer.writeByte(rawType);
|
||||||
|
|
||||||
switch (type) {
|
// switch (type) {
|
||||||
case CALL: writer.writeInt(get(0)); break;
|
// case CALL:
|
||||||
case CALL_NEW: writer.writeInt(get(0)); break;
|
// case CALL_NEW:
|
||||||
case DUP: writer.writeInt(get(0)); break;
|
// case CALL_MEMBER:
|
||||||
case JMP: writer.writeInt(get(0)); break;
|
// writer.writeInt(get(0));
|
||||||
case JMP_IF: writer.writeInt(get(0)); break;
|
// writer.writeUTF(get(1));
|
||||||
case JMP_IFN: writer.writeInt(get(0)); break;
|
// break;
|
||||||
case LOAD_ARR: writer.writeInt(get(0)); break;
|
// case DUP: writer.writeInt(get(0)); break;
|
||||||
case LOAD_FUNC: {
|
// case JMP: writer.writeInt(get(0)); break;
|
||||||
writer.writeInt(params.length - 1);
|
// 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++) {
|
// for (var i = 0; i < params.length; i++) {
|
||||||
writer.writeInt(get(i + 1));
|
// writer.writeInt(get(i + 1));
|
||||||
}
|
// }
|
||||||
|
|
||||||
writer.writeInt(get(0));
|
// writer.writeInt(get(0));
|
||||||
break;
|
// writer.writeUTF(get(0));
|
||||||
}
|
// break;
|
||||||
case LOAD_REGEX: writer.writeUTF(get(0)); break;
|
// }
|
||||||
case LOAD_VAR: writer.writeInt(get(0)); break;
|
// case LOAD_REGEX: writer.writeUTF(get(0)); break;
|
||||||
case MAKE_VAR: writer.writeUTF(get(0)); break;
|
// case LOAD_VAR: writer.writeInt(get(0)); break;
|
||||||
case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
|
// case GLOB_DEF: writer.writeUTF(get(0)); break;
|
||||||
case PUSH_NUMBER: writer.writeDouble(get(0)); break;
|
// case GLOB_GET: writer.writeUTF(get(0)); break;
|
||||||
case PUSH_STRING: writer.writeUTF(get(0)); break;
|
// case GLOB_SET:
|
||||||
case STORE_SELF_FUNC: writer.writeInt(get(0)); break;
|
// writer.writeUTF(get(0));
|
||||||
case STORE_VAR: writer.writeInt(get(0)); break;
|
// break;
|
||||||
case THROW_SYNTAX: writer.writeUTF(get(0));
|
// case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
|
||||||
case TRY_START:
|
// case PUSH_NUMBER: writer.writeDouble(get(0)); break;
|
||||||
writer.writeInt(get(0));
|
// case PUSH_STRING: writer.writeUTF(get(0)); break;
|
||||||
writer.writeInt(get(1));
|
// case STORE_VAR: writer.writeInt(get(0)); break;
|
||||||
writer.writeInt(get(2));
|
// case THROW_SYNTAX: writer.writeUTF(get(0));
|
||||||
break;
|
// case TRY_START:
|
||||||
case TYPEOF:
|
// writer.writeInt(get(0));
|
||||||
if (params.length > 0) writer.writeUTF(get(0));
|
// writer.writeInt(get(1));
|
||||||
break;
|
// writer.writeInt(get(2));
|
||||||
default:
|
// break;
|
||||||
}
|
// case TYPEOF:
|
||||||
}
|
// if (params.length > 0) writer.writeUTF(get(0));
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
private Instruction(Type type, Object ...params) {
|
private Instruction(Type type, Object ...params) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction read(DataInputStream stream) throws IOException {
|
// public static Instruction read(DataInputStream stream) throws IOException {
|
||||||
var rawType = stream.readUnsignedByte();
|
// var rawType = stream.readUnsignedByte();
|
||||||
var type = Type.fromNumeric(rawType & 127);
|
// var type = Type.fromNumeric(rawType & 127);
|
||||||
var flag = (rawType & 128) != 0;
|
// var flag = (rawType & 128) != 0;
|
||||||
|
|
||||||
switch (type) {
|
// switch (type) {
|
||||||
case CALL: return call(stream.readInt());
|
// case CALL: return call(stream.readInt(), stream.readUTF());
|
||||||
case CALL_NEW: return callNew(stream.readInt());
|
// case CALL_NEW: return callNew(stream.readInt(), stream.readUTF());
|
||||||
case DEF_PROP: return defProp();
|
// case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF());
|
||||||
case DELETE: return delete();
|
// case DEF_PROP: return defProp();
|
||||||
case DISCARD: return discard();
|
// case DELETE: return delete();
|
||||||
case DUP: return dup(stream.readInt());
|
// case DISCARD: return discard();
|
||||||
case JMP: return jmp(stream.readInt());
|
// case DUP: return dup(stream.readInt());
|
||||||
case JMP_IF: return jmpIf(stream.readInt());
|
// case JMP: return jmp(stream.readInt());
|
||||||
case JMP_IFN: return jmpIfNot(stream.readInt());
|
// case JMP_IF: return jmpIf(stream.readInt());
|
||||||
case KEYS: return keys(flag);
|
// case JMP_IFN: return jmpIfNot(stream.readInt());
|
||||||
case LOAD_ARR: return loadArr(stream.readInt());
|
// case KEYS: return keys(flag);
|
||||||
case LOAD_FUNC: {
|
// case LOAD_ARR: return loadArr(stream.readInt());
|
||||||
var captures = new int[stream.readInt()];
|
// case LOAD_FUNC: {
|
||||||
|
// var captures = new int[stream.readInt()];
|
||||||
|
|
||||||
for (var i = 0; i < captures.length; i++) {
|
// for (var i = 0; i < captures.length; i++) {
|
||||||
captures[i] = stream.readInt();
|
// captures[i] = stream.readInt();
|
||||||
}
|
// }
|
||||||
|
|
||||||
return loadFunc(stream.readInt(), captures);
|
// return loadFunc(stream.readInt(), stream.readUTF(), captures);
|
||||||
}
|
// }
|
||||||
case LOAD_GLOB: return loadGlob();
|
// case LOAD_GLOB: return loadGlob();
|
||||||
case LOAD_MEMBER: return loadMember();
|
// case LOAD_MEMBER: return loadMember();
|
||||||
case LOAD_OBJ: return loadObj();
|
// case LOAD_OBJ: return loadObj();
|
||||||
case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
|
// case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
|
||||||
case LOAD_VAR: return loadVar(stream.readInt());
|
// case LOAD_VAR: return loadVar(stream.readInt());
|
||||||
case MAKE_VAR: return makeVar(stream.readUTF());
|
// case GLOB_DEF: return globDef(stream.readUTF());
|
||||||
case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
|
// case GLOB_GET: return globGet(stream.readUTF());
|
||||||
case PUSH_NULL: return pushNull();
|
// case GLOB_SET: return globSet(stream.readUTF(), flag);
|
||||||
case PUSH_UNDEFINED: return pushUndefined();
|
// case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
|
||||||
case PUSH_BOOL: return pushValue(flag);
|
// case PUSH_NULL: return pushNull();
|
||||||
case PUSH_NUMBER: return pushValue(stream.readDouble());
|
// case PUSH_UNDEFINED: return pushUndefined();
|
||||||
case PUSH_STRING: return pushValue(stream.readUTF());
|
// case PUSH_BOOL: return pushValue(flag);
|
||||||
case RETURN: return ret();
|
// case PUSH_NUMBER: return pushValue(stream.readDouble());
|
||||||
case STORE_MEMBER: return storeMember(flag);
|
// case PUSH_STRING: return pushValue(stream.readUTF());
|
||||||
case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt());
|
// case RETURN: return ret();
|
||||||
case STORE_VAR: return storeVar(stream.readInt(), flag);
|
// case STORE_MEMBER: return storeMember(flag);
|
||||||
case THROW: return throwInstr();
|
// case STORE_VAR: return storeVar(stream.readInt(), flag);
|
||||||
case THROW_SYNTAX: return throwSyntax(stream.readUTF());
|
// case THROW: return throwInstr();
|
||||||
case TRY_END: return tryEnd();
|
// case THROW_SYNTAX: return throwSyntax(stream.readUTF());
|
||||||
case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt());
|
// case TRY_END: return tryEnd();
|
||||||
case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
|
// case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt(), stream.readInt());
|
||||||
case NOP:
|
// case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
|
||||||
if (flag) return null;
|
// case NOP:
|
||||||
else return nop();
|
// if (flag) return null;
|
||||||
default: return null;
|
// else return nop();
|
||||||
}
|
// default: return null;
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
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);
|
||||||
@ -237,6 +263,9 @@ public class Instruction {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
public static Instruction throwSyntax(Location loc, String err) {
|
||||||
|
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
|
||||||
|
}
|
||||||
public static Instruction delete() {
|
public static Instruction delete() {
|
||||||
return new Instruction(Type.DELETE);
|
return new Instruction(Type.DELETE);
|
||||||
}
|
}
|
||||||
@ -251,12 +280,25 @@ public class Instruction {
|
|||||||
return new Instruction(Type.NOP, params);
|
return new Instruction(Type.NOP, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Instruction call(int argn, String name) {
|
||||||
|
return new Instruction(Type.CALL, argn, name);
|
||||||
|
}
|
||||||
public static Instruction call(int argn) {
|
public static Instruction call(int argn) {
|
||||||
return new Instruction(Type.CALL, argn);
|
return call(argn, "");
|
||||||
|
}
|
||||||
|
public static Instruction callMember(int argn, String name) {
|
||||||
|
return new Instruction(Type.CALL_MEMBER, argn, name);
|
||||||
|
}
|
||||||
|
public static Instruction callMember(int argn) {
|
||||||
|
return new Instruction(Type.CALL_MEMBER, argn, "");
|
||||||
|
}
|
||||||
|
public static Instruction callNew(int argn, String name) {
|
||||||
|
return new Instruction(Type.CALL_NEW, argn, name);
|
||||||
}
|
}
|
||||||
public static Instruction callNew(int argn) {
|
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);
|
||||||
}
|
}
|
||||||
@ -267,6 +309,17 @@ public class Instruction {
|
|||||||
return new Instruction(Type.JMP_IFN, offset);
|
return new Instruction(Type.JMP_IFN, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IntFunction<Instruction> jmp(IntSupplier pos) {
|
||||||
|
return i -> new Instruction(Type.JMP, pos.getAsInt() - i);
|
||||||
|
}
|
||||||
|
public static IntFunction<Instruction> jmpIf(IntSupplier pos) {
|
||||||
|
return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i);
|
||||||
|
}
|
||||||
|
public static IntFunction<Instruction> jmpIfNot(IntSupplier pos) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@ -283,26 +336,61 @@ public class Instruction {
|
|||||||
return new Instruction(Type.PUSH_STRING, val);
|
return new Instruction(Type.PUSH_STRING, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction makeVar(String name) {
|
public static Instruction globDef(String name) {
|
||||||
return new Instruction(Type.MAKE_VAR, name);
|
return new Instruction(Type.GLOB_DEF, name);
|
||||||
}
|
}
|
||||||
public static Instruction loadVar(Object i) {
|
|
||||||
|
public static Instruction globGet(String name) {
|
||||||
|
return new Instruction(Type.GLOB_GET, name);
|
||||||
|
}
|
||||||
|
public static Instruction globSet(String name, boolean keep, boolean define) {
|
||||||
|
return new Instruction(Type.GLOB_SET, name, keep, define);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction loadVar(int i) {
|
||||||
return new Instruction(Type.LOAD_VAR, i);
|
return new Instruction(Type.LOAD_VAR, i);
|
||||||
}
|
}
|
||||||
|
public static Instruction loadThis() {
|
||||||
|
return new Instruction(Type.LOAD_THIS);
|
||||||
|
}
|
||||||
|
public static Instruction loadArgs(boolean real) {
|
||||||
|
return new Instruction(Type.LOAD_ARGS, real);
|
||||||
|
}
|
||||||
|
public static Instruction loadRestArgs(int offset) {
|
||||||
|
return new Instruction(Type.LOAD_REST_ARGS, offset);
|
||||||
|
}
|
||||||
|
public static Instruction loadCallee() {
|
||||||
|
return new Instruction(Type.LOAD_CALLEE);
|
||||||
|
}
|
||||||
public static Instruction loadGlob() {
|
public static Instruction loadGlob() {
|
||||||
return new Instruction(Type.LOAD_GLOB);
|
return new Instruction(Type.LOAD_GLOB);
|
||||||
}
|
}
|
||||||
|
public static Instruction loadIntrinsics(String key) {
|
||||||
|
return new Instruction(Type.LOAD_INTRINSICS, key);
|
||||||
|
}
|
||||||
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) {
|
||||||
|
return new Instruction(Type.LOAD_MEMBER_INT, member);
|
||||||
|
}
|
||||||
|
public static Instruction loadMember(String member) {
|
||||||
|
return new Instruction(Type.LOAD_MEMBER_STR, member);
|
||||||
|
}
|
||||||
|
|
||||||
public static Instruction loadRegex(String pattern, String flags) {
|
public static Instruction loadRegex(String pattern, String flags) {
|
||||||
return new Instruction(Type.LOAD_REGEX, pattern, flags);
|
return new Instruction(Type.LOAD_REGEX, pattern, flags);
|
||||||
}
|
}
|
||||||
public static Instruction loadFunc(int id, int[] captures) {
|
public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, String name, int[] captures) {
|
||||||
var args = new Object[1 + captures.length];
|
if (name == null) name = "";
|
||||||
|
|
||||||
|
var args = new Object[5 + captures.length];
|
||||||
args[0] = id;
|
args[0] = id;
|
||||||
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
|
args[1] = name;
|
||||||
|
args[2] = callable;
|
||||||
|
args[3] = constructible;
|
||||||
|
args[4] = captureThis;
|
||||||
|
for (var i = 0; i < captures.length; i++) args[i + 5] = captures[i];
|
||||||
return new Instruction(Type.LOAD_FUNC, args);
|
return new Instruction(Type.LOAD_FUNC, args);
|
||||||
}
|
}
|
||||||
public static Instruction loadObj() {
|
public static Instruction loadObj() {
|
||||||
@ -318,21 +406,34 @@ public class Instruction {
|
|||||||
return new Instruction(Type.DUP, count);
|
return new Instruction(Type.DUP, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instruction storeSelfFunc(int i) {
|
public static Instruction storeVar(int i) {
|
||||||
return new Instruction(Type.STORE_SELF_FUNC, i);
|
|
||||||
}
|
|
||||||
public static Instruction storeVar(Object i) {
|
|
||||||
return new Instruction(Type.STORE_VAR, i, false);
|
return new Instruction(Type.STORE_VAR, i, false);
|
||||||
}
|
}
|
||||||
public static Instruction storeVar(Object i, boolean keep) {
|
public static Instruction storeVar(int i, boolean keep) {
|
||||||
return new Instruction(Type.STORE_VAR, i, keep);
|
return new Instruction(Type.STORE_VAR, i, keep);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
return new Instruction(Type.STORE_MEMBER_STR, key, false);
|
||||||
|
}
|
||||||
|
public static Instruction storeMember(String key, boolean keep) {
|
||||||
|
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instruction storeMember(int key) {
|
||||||
|
return new Instruction(Type.STORE_MEMBER_INT, key, false);
|
||||||
|
}
|
||||||
|
public static Instruction storeMember(int key, boolean keep) {
|
||||||
|
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
|
||||||
|
}
|
||||||
|
|
||||||
public static Instruction discard() {
|
public static Instruction discard() {
|
||||||
return new Instruction(Type.DISCARD);
|
return new Instruction(Type.DISCARD);
|
||||||
}
|
}
|
||||||
@ -356,8 +457,17 @@ public class Instruction {
|
|||||||
return new Instruction(Type.OPERATION, op);
|
return new Instruction(Type.OPERATION, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static Instruction stackAlloc(int n) {
|
||||||
public String toString() {
|
return new Instruction(Type.STACK_ALLOC, n);
|
||||||
|
}
|
||||||
|
public static Instruction stackRealloc(int n) {
|
||||||
|
return new Instruction(Type.STACK_REALLOC, n);
|
||||||
|
}
|
||||||
|
public static Instruction stackFree(int n) {
|
||||||
|
return new Instruction(Type.STACK_FREE, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() {
|
||||||
var res = type.toString();
|
var res = type.toString();
|
||||||
|
|
||||||
for (int i = 0; i < params.length; i++) {
|
for (int i = 0; i < params.length; i++) {
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class Location implements Comparable<Location> {
|
|
||||||
public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native"));
|
|
||||||
private int line;
|
|
||||||
private int start;
|
|
||||||
private Filename filename;
|
|
||||||
|
|
||||||
public int line() { return line; }
|
|
||||||
public int start() { return start; }
|
|
||||||
public Filename filename() { return filename; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
var res = new ArrayList<String>();
|
|
||||||
|
|
||||||
if (filename != null) res.add(filename.toString());
|
|
||||||
if (line >= 0) res.add(line + "");
|
|
||||||
if (start >= 0) res.add(start + "");
|
|
||||||
|
|
||||||
return String.join(":", res);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Location add(int n, boolean clone) {
|
|
||||||
if (clone) return new Location(line, start + n, filename);
|
|
||||||
this.start += n;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public Location add(int n) {
|
|
||||||
return add(n, false);
|
|
||||||
}
|
|
||||||
public Location nextLine() {
|
|
||||||
line++;
|
|
||||||
start = 0;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public Location clone() {
|
|
||||||
return new Location(line, start, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + line;
|
|
||||||
result = prime * result + start;
|
|
||||||
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) return true;
|
|
||||||
if (obj == null) return false;
|
|
||||||
if (getClass() != obj.getClass()) return false;
|
|
||||||
Location other = (Location) obj;
|
|
||||||
if (line != other.line) return false;
|
|
||||||
if (start != other.start) return false;
|
|
||||||
if (filename == null && other.filename != null) return false;
|
|
||||||
else if (!filename.equals(other.filename)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(Location other) {
|
|
||||||
int a = filename.toString().compareTo(other.filename.toString());
|
|
||||||
int b = Integer.compare(line, other.line);
|
|
||||||
int c = Integer.compare(start, other.start);
|
|
||||||
|
|
||||||
if (a != 0) return a;
|
|
||||||
if (b != 0) return b;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Location(int line, int start, Filename filename) {
|
|
||||||
this.line = line;
|
|
||||||
this.start = start;
|
|
||||||
this.filename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Location parse(String raw) {
|
|
||||||
int i0 = -1, i1 = -1;
|
|
||||||
for (var i = raw.length() - 1; i >= 0; i--) {
|
|
||||||
if (raw.charAt(i) == ':') {
|
|
||||||
if (i1 == -1) i1 = i;
|
|
||||||
else if (i0 == -1) {
|
|
||||||
i0 = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Location(
|
|
||||||
Integer.parseInt(raw.substring(i0 + 1, i1)),
|
|
||||||
Integer.parseInt(raw.substring(i1 + 1)),
|
|
||||||
Filename.parse(raw.substring(0, i0))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
|
||||||
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
public class RefTracker {
|
|
||||||
public static void onDestroy(Object obj, Runnable runnable) {
|
|
||||||
var queue = new ReferenceQueue<>();
|
|
||||||
var ref = new WeakReference<>(obj, queue);
|
|
||||||
obj = null;
|
|
||||||
|
|
||||||
var th = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
queue.remove();
|
|
||||||
ref.get();
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) { return; }
|
|
||||||
});
|
|
||||||
th.setDaemon(true);
|
|
||||||
th.start();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
|
||||||
|
|
||||||
public interface ResultRunnable<T> {
|
|
||||||
T run();
|
|
||||||
}
|
|
@ -0,0 +1,187 @@
|
|||||||
|
package me.topchetoeu.jscript.common.environment;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class Environment {
|
||||||
|
public final Environment parent;
|
||||||
|
private final Map<Key<Object>, Object> map = new HashMap<>();
|
||||||
|
private final Set<Key<Object>> hidden = new HashSet<>();
|
||||||
|
|
||||||
|
private final Map<MultiKey<Object>, Set<Object>> multi = new HashMap<>();
|
||||||
|
private final Map<MultiKey<Object>, Set<Object>> multiHidden = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> Set<T> getAll(MultiKey<T> key, boolean forceClone) {
|
||||||
|
Set<T> parent = null, child = null;
|
||||||
|
boolean cloned = false;
|
||||||
|
|
||||||
|
if (this.parent != null && !hidden.contains(key)) {
|
||||||
|
parent = this.parent.getAll(key, false);
|
||||||
|
if (parent.size() == 0) parent = null;
|
||||||
|
else if (multiHidden.containsKey(key)) {
|
||||||
|
parent = new HashSet<>(parent);
|
||||||
|
parent.removeAll(multiHidden.get(key));
|
||||||
|
cloned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (multi.containsKey(key)) {
|
||||||
|
child = (Set<T>)multi.get(key);
|
||||||
|
if (child.size() == 0) child = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forceClone) {
|
||||||
|
if (parent == null && child == null) return Set.of();
|
||||||
|
if (parent == null && child != null) return child;
|
||||||
|
if (parent != null && child == null) return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cloned) parent = new HashSet<>();
|
||||||
|
parent.addAll(child);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
private <T> T getMulti(MultiKey<T> key) {
|
||||||
|
return key.of(getAll(key, false));
|
||||||
|
}
|
||||||
|
private boolean hasMulti(MultiKey<?> key) {
|
||||||
|
return getAll(key, false).size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private <T> Environment addMulti(MultiKey<T> key, T value) {
|
||||||
|
if (!multi.containsKey(key)) {
|
||||||
|
if (hidden.contains(key)) {
|
||||||
|
multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true));
|
||||||
|
hidden.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
multi.put((MultiKey)key, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
multi.get(key).add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(Key<T> key) {
|
||||||
|
if (key instanceof MultiKey) return getMulti((MultiKey<T>)key);
|
||||||
|
|
||||||
|
if (map.containsKey(key)) return (T)map.get(key);
|
||||||
|
else if (!hidden.contains(key) && parent != null) return parent.get(key);
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
public boolean has(Key<?> key) {
|
||||||
|
if (key instanceof MultiKey) return hasMulti((MultiKey<?>)key);
|
||||||
|
|
||||||
|
if (map.containsKey(key)) return true;
|
||||||
|
else if (!hidden.contains(key) && parent != null) return parent.has(key);
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNotNull(Key<?> key) {
|
||||||
|
return get(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(Key<T> key, T defaultVal) {
|
||||||
|
if (has(key)) return get(key);
|
||||||
|
else return defaultVal;
|
||||||
|
}
|
||||||
|
public <T> T get(Key<T> key, Supplier<T> defaultVal) {
|
||||||
|
if (has(key)) return get(key);
|
||||||
|
else return defaultVal.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> Environment add(Key<T> key, T val) {
|
||||||
|
if (key instanceof MultiKey) return add(key, val);
|
||||||
|
|
||||||
|
map.put((Key<Object>)key, val);
|
||||||
|
hidden.remove(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Environment add(Key<Void> key) {
|
||||||
|
return add(key, null);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
|
||||||
|
for (var pair : map.entrySet()) {
|
||||||
|
if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) {
|
||||||
|
for (var val : (Iterable<?>)pair.getValue()) {
|
||||||
|
addMulti((MultiKey<Object>)pair.getKey(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else add((Key<Object>)pair.getKey(), pair.getValue());
|
||||||
|
}
|
||||||
|
map.putAll((Map)map);
|
||||||
|
hidden.removeAll(map.keySet());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Environment addAll(Map<Key<?>, ?> map) {
|
||||||
|
return addAll(map, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Environment remove(Key<?> key) {
|
||||||
|
map.remove(key);
|
||||||
|
multi.remove(key);
|
||||||
|
multiHidden.remove(key);
|
||||||
|
hidden.add((Key<Object>)key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public <T> Environment remove(MultiKey<T> key, T val) {
|
||||||
|
if (multi.containsKey(key)) {
|
||||||
|
multi.get(key).remove(val);
|
||||||
|
multiHidden.get(key).add(val);
|
||||||
|
|
||||||
|
if (multi.get(key).size() == 0) {
|
||||||
|
multi.remove(key);
|
||||||
|
multiHidden.remove(key);
|
||||||
|
hidden.add((Key)key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T init(Key<T> key, T val) {
|
||||||
|
if (!has(key)) this.add(key, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
public <T> T initFrom(Key<T> key, Supplier<T> val) {
|
||||||
|
if (!has(key)) {
|
||||||
|
var res = val.get();
|
||||||
|
this.add(key, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
else return get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment child() {
|
||||||
|
return new Environment(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment(Environment parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
public Environment() {
|
||||||
|
this.parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Environment wrap(Environment env) {
|
||||||
|
if (env == null) return empty();
|
||||||
|
else return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Environment empty() {
|
||||||
|
return new Environment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int nextId() {
|
||||||
|
return new Random().nextInt();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package me.topchetoeu.jscript.common.environment;
|
||||||
|
|
||||||
|
public interface Key<T> {
|
||||||
|
public static <T> Key<T> of() {
|
||||||
|
return new Key<>() { };
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package me.topchetoeu.jscript.common.environment;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface MultiKey<T> extends Key<T> {
|
||||||
|
public T of(Set<T> values);
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common.events;
|
|
||||||
|
|
||||||
public class DataNotifier<T> {
|
|
||||||
private Notifier notifier = new Notifier();
|
|
||||||
private boolean isErr;
|
|
||||||
private T val;
|
|
||||||
private RuntimeException err;
|
|
||||||
|
|
||||||
public void error(RuntimeException t) {
|
|
||||||
err = t;
|
|
||||||
isErr = true;
|
|
||||||
notifier.next();
|
|
||||||
}
|
|
||||||
public void next(T val) {
|
|
||||||
this.val = val;
|
|
||||||
isErr = false;
|
|
||||||
notifier.next();
|
|
||||||
}
|
|
||||||
public T await() {
|
|
||||||
notifier.await();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (isErr) throw err;
|
|
||||||
else return val;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.err = null;
|
|
||||||
this.val = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.common.events;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
|
|
||||||
|
|
||||||
public class Notifier {
|
|
||||||
private boolean ok = false;
|
|
||||||
|
|
||||||
public synchronized void next() {
|
|
||||||
ok = true;
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
public synchronized void await() {
|
|
||||||
try {
|
|
||||||
while (!ok) wait();
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) { throw new InterruptException(e); }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +1,72 @@
|
|||||||
package me.topchetoeu.jscript.common.json;
|
package me.topchetoeu.jscript.common.json;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Filename;
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
import me.topchetoeu.jscript.compilation.parsing.Operator;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.compilation.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.parsing.Token;
|
|
||||||
import me.topchetoeu.jscript.runtime.Extensions;
|
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.runtime.values.ArrayValue;
|
|
||||||
import me.topchetoeu.jscript.runtime.values.ObjectValue;
|
|
||||||
import me.topchetoeu.jscript.runtime.values.Values;
|
|
||||||
|
|
||||||
public class JSON {
|
public class JSON {
|
||||||
public static Object toJs(JSONElement val) {
|
public static ParseRes<JSONElement> parseString(Source src, int i) {
|
||||||
if (val.isBoolean()) return val.bool();
|
var res = Parsing.parseString(src, i);
|
||||||
if (val.isString()) return val.string();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
if (val.isNumber()) return val.number();
|
return ParseRes.res(JSONElement.string(res.result), res.n);
|
||||||
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
|
|
||||||
if (val.isMap()) {
|
|
||||||
var res = new ObjectValue();
|
|
||||||
for (var el : val.map().entrySet()) {
|
|
||||||
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
if (val.isNull()) return Values.NULL;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
private static JSONElement fromJs(Extensions ext, Object val, HashSet<Object> prev) {
|
public static ParseRes<JSONElement> parseNumber(Source src, int i) {
|
||||||
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
|
var res = Parsing.parseNumber(src, i, true);
|
||||||
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
|
if (!res.isSuccess()) return res.chainError();
|
||||||
if (val instanceof String) return JSONElement.string((String)val);
|
else return ParseRes.res(JSONElement.number(res.result), res.n);
|
||||||
if (val == Values.NULL) return JSONElement.NULL;
|
|
||||||
if (val instanceof ArrayValue) {
|
|
||||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
|
||||||
prev.add(val);
|
|
||||||
|
|
||||||
var res = new JSONList();
|
|
||||||
|
|
||||||
for (var el : ((ArrayValue)val).toArray()) {
|
|
||||||
var jsonEl = fromJs(ext, el, prev);
|
|
||||||
if (jsonEl == null) jsonEl = JSONElement.NULL;
|
|
||||||
res.add(jsonEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
prev.remove(val);
|
|
||||||
return JSONElement.of(res);
|
|
||||||
}
|
|
||||||
if (val instanceof ObjectValue) {
|
|
||||||
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
|
|
||||||
prev.add(val);
|
|
||||||
|
|
||||||
var res = new JSONMap();
|
|
||||||
|
|
||||||
for (var el : Values.getMembers(ext, val, false, false)) {
|
|
||||||
var jsonEl = fromJs(ext, Values.getMember(ext, val, el), prev);
|
|
||||||
if (jsonEl == null) continue;
|
|
||||||
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
prev.remove(val);
|
|
||||||
return JSONElement.of(res);
|
|
||||||
}
|
|
||||||
if (val == null) return null;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
public static JSONElement fromJs(Extensions ext, Object val) {
|
public static ParseRes<JSONElement> parseLiteral(Source src, int i) {
|
||||||
return fromJs(ext, val, new HashSet<>());
|
var id = Parsing.parseIdentifier(src, i);
|
||||||
}
|
|
||||||
|
|
||||||
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
|
||||||
return Parsing.parseIdentifier(tokens, i);
|
|
||||||
}
|
|
||||||
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
|
|
||||||
var res = Parsing.parseString(filename, tokens, i);
|
|
||||||
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
|
|
||||||
else return res.transform();
|
|
||||||
}
|
|
||||||
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
|
|
||||||
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
|
|
||||||
if (minus) i++;
|
|
||||||
|
|
||||||
var res = Parsing.parseNumber(filename, tokens, i);
|
|
||||||
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
|
|
||||||
else return res.transform();
|
|
||||||
}
|
|
||||||
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
|
|
||||||
var id = parseIdentifier(tokens, i);
|
|
||||||
|
|
||||||
if (!id.isSuccess()) return ParseRes.failed();
|
if (!id.isSuccess()) return ParseRes.failed();
|
||||||
else if (id.result.equals("true")) return ParseRes.res(true, 1);
|
else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n);
|
||||||
else if (id.result.equals("false")) return ParseRes.res(false, 1);
|
else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n);
|
||||||
|
else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n);
|
||||||
else return ParseRes.failed();
|
else return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
|
public static ParseRes<JSONElement> parseValue(Source src, int i) {
|
||||||
return ParseRes.any(
|
return ParseRes.first(src, i,
|
||||||
parseString(filename, tokens, i),
|
JSON::parseString,
|
||||||
parseNumber(filename, tokens, i),
|
JSON::parseNumber,
|
||||||
parseBool(filename, tokens, i),
|
JSON::parseLiteral,
|
||||||
parseMap(filename, tokens, i),
|
JSON::parseMap,
|
||||||
parseList(filename, tokens, i)
|
JSON::parseList
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
|
public static ParseRes<JSONMap> parseMap(Source src, int i) {
|
||||||
int n = 0;
|
var n = Parsing.skipEmpty(src, i);
|
||||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
|
|
||||||
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
var values = new JSONMap();
|
var values = new JSONMap();
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(Map.of()), n + 1);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
var name = parseString(src, i + n);
|
||||||
n++;
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index");
|
||||||
break;
|
n += name.n;
|
||||||
}
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var name = ParseRes.any(
|
if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon");
|
||||||
parseIdentifier(tokens, i + n),
|
|
||||||
parseString(filename, tokens, i + n),
|
|
||||||
parseNumber(filename, tokens, i + n)
|
|
||||||
);
|
|
||||||
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
|
|
||||||
else n += name.n;
|
|
||||||
|
|
||||||
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
|
|
||||||
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
|
|
||||||
}
|
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
var res = parseValue(filename, tokens, i + n);
|
var res = parseValue(src, i + n);
|
||||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
|
||||||
else n += res.n;
|
values.put(name.result.toString(), res.result);
|
||||||
|
n += res.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
values.put(name.result.toString(), JSONElement.of(res.result));
|
if (src.is(i + n, ",")) n++;
|
||||||
|
else if (src.is(i + n, "}")) {
|
||||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
|
||||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
|
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -149,26 +74,23 @@ public class JSON {
|
|||||||
|
|
||||||
return ParseRes.res(values, n);
|
return ParseRes.res(values, n);
|
||||||
}
|
}
|
||||||
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
|
public static ParseRes<JSONList> parseList(Source src, int i) {
|
||||||
int n = 0;
|
var n = Parsing.skipEmpty(src, i);
|
||||||
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) 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);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
var res = parseValue(src, i + n);
|
||||||
n++;
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
|
||||||
break;
|
values.add(res.result);
|
||||||
}
|
n += res.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var res = parseValue(filename, tokens, i + n);
|
if (src.is(i + n, ",")) n++;
|
||||||
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
|
else if (src.is(i + n, "]")) {
|
||||||
else n += res.n;
|
|
||||||
|
|
||||||
values.add(JSONElement.of(res.result));
|
|
||||||
|
|
||||||
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
|
|
||||||
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
|
|
||||||
n++;
|
n++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -178,14 +100,21 @@ public class JSON {
|
|||||||
}
|
}
|
||||||
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(filename, Parsing.tokenize(filename, raw), 0);
|
|
||||||
|
var res = parseValue(new Source(null, filename, raw), 0);
|
||||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
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()) return Double.toString(el.number());
|
if (el.isNumber()) {
|
||||||
|
var d = el.number();
|
||||||
|
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
|
||||||
|
if (d == Double.POSITIVE_INFINITY) return "Infinity";
|
||||||
|
if (Double.isNaN(d)) return "NaN";
|
||||||
|
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
|
||||||
|
}
|
||||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||||
if (el.isNull()) return "null";
|
if (el.isNull()) return "null";
|
||||||
if (el.isString()) {
|
if (el.isString()) {
|
||||||
|
@ -69,8 +69,7 @@ public class JSONElement {
|
|||||||
return (boolean)value;
|
return (boolean)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String toString() {
|
||||||
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;
|
||||||
|
@ -116,32 +116,20 @@ public class JSONMap implements Map<String, JSONElement> {
|
|||||||
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
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 JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||||
|
|
||||||
@Override
|
@Override public int size() { return elements.size(); }
|
||||||
public int size() { return elements.size(); }
|
@Override public boolean isEmpty() { return elements.isEmpty(); }
|
||||||
@Override
|
@Override public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||||
public boolean isEmpty() { return elements.isEmpty(); }
|
@Override public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||||
@Override
|
@Override public JSONElement get(Object key) { return elements.get(key); }
|
||||||
public boolean containsKey(Object key) { return elements.containsKey(key); }
|
@Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||||
@Override
|
@Override public JSONElement remove(Object key) { return elements.remove(key); }
|
||||||
public boolean containsValue(Object value) { return elements.containsValue(value); }
|
@Override public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||||
@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
|
@Override public void clear() { elements.clear(); }
|
||||||
public void clear() { elements.clear(); }
|
|
||||||
|
|
||||||
@Override
|
@Override public Set<String> keySet() { return elements.keySet(); }
|
||||||
public Set<String> keySet() { return elements.keySet(); }
|
@Override public Collection<JSONElement> values() { return elements.values(); }
|
||||||
@Override
|
@Override public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||||
public Collection<JSONElement> values() { return elements.values(); }
|
|
||||||
@Override
|
|
||||||
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
|
||||||
|
|
||||||
public JSONMap() { }
|
public JSONMap() { }
|
||||||
public JSONMap(Map<String, JSONElement> els) {
|
public JSONMap(Map<String, JSONElement> els) {
|
||||||
|
@ -11,11 +11,10 @@ import java.util.TreeSet;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Filename;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
import me.topchetoeu.jscript.utils.mapping.SourceMap;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Scope;
|
||||||
|
|
||||||
public class FunctionMap {
|
public class FunctionMap {
|
||||||
public static class FunctionMapBuilder {
|
public static class FunctionMapBuilder {
|
||||||
@ -54,8 +53,8 @@ public class FunctionMap {
|
|||||||
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(LocalScopeRecord scope) {
|
public FunctionMap build(Scope scope) {
|
||||||
return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures());
|
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||||
}
|
}
|
||||||
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]);
|
||||||
@ -104,7 +103,7 @@ public class FunctionMap {
|
|||||||
|
|
||||||
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(new Location(line, column, candidate.getKey()));
|
var val = correctBreakpoint(Location.of(candidate.getKey(), line, column));
|
||||||
if (val == null) continue;
|
if (val == null) continue;
|
||||||
res.add(val);
|
res.add(val);
|
||||||
}
|
}
|
||||||
@ -131,27 +130,27 @@ public class FunctionMap {
|
|||||||
return pcToLoc.lastEntry().getValue();
|
return pcToLoc.lastEntry().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionMap apply(SourceMap map) {
|
// public static FunctionMap apply(FunctionMap funcMap, SourceMap map) {
|
||||||
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
// var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames);
|
||||||
|
|
||||||
for (var el : pcToLoc.entrySet()) {
|
// for (var el : funcMap.pcToLoc.entrySet()) {
|
||||||
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
|
// res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
|
||||||
}
|
// }
|
||||||
|
|
||||||
res.bps.putAll(bps);
|
// res.bps.putAll(bps);
|
||||||
|
|
||||||
for (var el : bpLocs.entrySet()) {
|
// for (var el : bpLocs.entrySet()) {
|
||||||
for (var loc : el.getValue()) {
|
// for (var loc : el.getValue()) {
|
||||||
loc = map.toCompiled(loc);
|
// loc = map.toCompiled(loc);
|
||||||
if (loc == null) continue;
|
// if (loc == null) continue;
|
||||||
|
|
||||||
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
|
// if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
|
||||||
res.bpLocs.get(loc.filename()).add(loc);
|
// res.bpLocs.get(loc.filename()).add(loc);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return res;
|
// return res;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public FunctionMap clone() {
|
public FunctionMap clone() {
|
||||||
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package me.topchetoeu.jscript.common;
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
108
src/main/java/me/topchetoeu/jscript/common/parsing/Location.java
Normal file
108
src/main/java/me/topchetoeu/jscript/common/parsing/Location.java
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public abstract class Location implements Comparable<Location> {
|
||||||
|
public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1);
|
||||||
|
|
||||||
|
public abstract int line();
|
||||||
|
public abstract int start();
|
||||||
|
public abstract Filename filename();
|
||||||
|
|
||||||
|
public final String toString() {
|
||||||
|
var res = new ArrayList<String>();
|
||||||
|
|
||||||
|
if (filename() != null) res.add(filename().toString());
|
||||||
|
if (line() >= 0) res.add(line() + 1 + "");
|
||||||
|
if (start() >= 0) res.add(start() + 1 + "");
|
||||||
|
|
||||||
|
return String.join(":", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Location add(int n) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Location() {
|
||||||
|
@Override public Filename filename() { return self.filename(); }
|
||||||
|
@Override public int start() { return self.start() + n; }
|
||||||
|
@Override public int line() { return self.line(); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public final Location nextLine() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Location() {
|
||||||
|
@Override public Filename filename() { return self.filename(); }
|
||||||
|
@Override public int start() { return 0; }
|
||||||
|
@Override public int line() { return self.line() + 1; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return Objects.hash(line(), start(), filename());
|
||||||
|
}
|
||||||
|
@Override public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Location)) return false;
|
||||||
|
var other = (Location)obj;
|
||||||
|
|
||||||
|
if (!Objects.equals(this.start(), other.start())) return false;
|
||||||
|
if (!Objects.equals(this.line(), other.line())) return false;
|
||||||
|
if (!Objects.equals(this.filename(), other.filename())) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int compareTo(Location other) {
|
||||||
|
int a = filename().toString().compareTo(other.filename().toString());
|
||||||
|
int b = Integer.compare(line(), other.line());
|
||||||
|
int c = Integer.compare(start(), other.start());
|
||||||
|
|
||||||
|
if (a != 0) return a;
|
||||||
|
if (b != 0) return b;
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location of(Filename filename, int line, int start) {
|
||||||
|
return new Location() {
|
||||||
|
@Override public Filename filename() { return filename; }
|
||||||
|
@Override public int start() { return start; }
|
||||||
|
@Override public int line() { return line; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location of(String raw) {
|
||||||
|
var i0 = raw.lastIndexOf(':');
|
||||||
|
if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1);
|
||||||
|
|
||||||
|
var i1 = raw.lastIndexOf(':', i0);
|
||||||
|
if (i0 < 0) {
|
||||||
|
try {
|
||||||
|
return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
return Location.of(Filename.parse(raw), -1, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int start, line;
|
||||||
|
|
||||||
|
try {
|
||||||
|
start = Integer.parseInt(raw.substring(i1 + 1));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
return Location.of(Filename.parse(raw), -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
line = Integer.parseInt(raw.substring(i0 + 1, i1));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Location.of(Filename.parse(raw.substring(0, i0)), start, line);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
|
public class ParseRes<T> {
|
||||||
|
public static enum State {
|
||||||
|
SUCCESS,
|
||||||
|
FAILED,
|
||||||
|
ERROR;
|
||||||
|
|
||||||
|
public boolean isSuccess() { return this == SUCCESS; }
|
||||||
|
public boolean isFailed() { return this == FAILED; }
|
||||||
|
public boolean isError() { return this == ERROR; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ParseRes.State state;
|
||||||
|
public final Location errorLocation;
|
||||||
|
public final String error;
|
||||||
|
public final T result;
|
||||||
|
public final int n;
|
||||||
|
|
||||||
|
private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) {
|
||||||
|
this.result = result;
|
||||||
|
this.n = readN;
|
||||||
|
this.state = state;
|
||||||
|
this.error = error;
|
||||||
|
this.errorLocation = errorLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseRes<T> setN(int i) {
|
||||||
|
if (!state.isSuccess()) return this;
|
||||||
|
return new ParseRes<>(state, null, null, result, i);
|
||||||
|
}
|
||||||
|
public ParseRes<T> addN(int n) {
|
||||||
|
if (!state.isSuccess()) return this;
|
||||||
|
return new ParseRes<>(state, null, null, result, this.n + n);
|
||||||
|
}
|
||||||
|
public <T2> ParseRes<T2> chainError() {
|
||||||
|
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
||||||
|
return new ParseRes<>(state, errorLocation, error, null, 0);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T2> ParseRes<T2> chainError(Location loc, String error) {
|
||||||
|
if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
||||||
|
return (ParseRes<T2>) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() { return state.isSuccess(); }
|
||||||
|
public boolean isFailed() { return state.isFailed(); }
|
||||||
|
public boolean isError() { return state.isError(); }
|
||||||
|
|
||||||
|
public static <T> ParseRes<T> failed() {
|
||||||
|
return new ParseRes<T>(State.FAILED, null, null, null, 0);
|
||||||
|
}
|
||||||
|
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||||
|
return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
||||||
|
}
|
||||||
|
public static <T> ParseRes<T> res(T val, int i) {
|
||||||
|
return new ParseRes<>(State.SUCCESS, null, null, val, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static <T> ParseRes<T> first(Source src, int i, Parser ...parsers) {
|
||||||
|
int n = Parsing.skipEmpty(src, i);
|
||||||
|
ParseRes<T> error = ParseRes.failed();
|
||||||
|
|
||||||
|
for (var parser : parsers) {
|
||||||
|
var res = parser.parse(src, i + n);
|
||||||
|
if (res.isSuccess()) return res.addN(n);
|
||||||
|
if (res.isError() && error.isFailed()) error = res.chainError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
|
public interface Parser<T> {
|
||||||
|
public ParseRes<T> parse(Source src, int i);
|
||||||
|
}
|
420
src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java
Normal file
420
src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class Parsing {
|
||||||
|
public static boolean isDigit(Character c) {
|
||||||
|
return c != null && c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
public static boolean isAny(char c, String alphabet) {
|
||||||
|
return alphabet.contains(Character.toString(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int fromHex(char c) {
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int skipEmpty(Source src, int i) {
|
||||||
|
return skipEmpty(src, i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int skipEmpty(Source src, int i, boolean noComments) {
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if (i == 0 && src.is(0, "#!")) {
|
||||||
|
while (!src.is(n, '\n')) n++;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSingle = false;
|
||||||
|
var isMulti = false;
|
||||||
|
|
||||||
|
while (i + n < src.size()) {
|
||||||
|
if (isSingle) {
|
||||||
|
if (src.is(i + n, '\n')) {
|
||||||
|
n++;
|
||||||
|
isSingle = false;
|
||||||
|
}
|
||||||
|
else n++;
|
||||||
|
}
|
||||||
|
else if (isMulti) {
|
||||||
|
if (src.is(i + n, "*/")) {
|
||||||
|
n += 2;
|
||||||
|
isMulti = false;
|
||||||
|
}
|
||||||
|
else n++;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, "//")) {
|
||||||
|
n += 2;
|
||||||
|
isSingle = true;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, "/*")) {
|
||||||
|
n += 2;
|
||||||
|
isMulti = true;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, Character::isWhitespace)) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<Character> parseChar(Source src, int i) {
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if (src.is(i + n, '\\')) {
|
||||||
|
n++;
|
||||||
|
char c = src.at(i + n++);
|
||||||
|
|
||||||
|
if (c == 'b') return ParseRes.res('\b', n);
|
||||||
|
else if (c == 't') return ParseRes.res('\t', n);
|
||||||
|
else if (c == 'n') return ParseRes.res('\n', n);
|
||||||
|
else if (c == 'f') return ParseRes.res('\f', n);
|
||||||
|
else if (c == 'r') return ParseRes.res('\r', n);
|
||||||
|
else if (c == '0') {
|
||||||
|
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
|
||||||
|
else return ParseRes.res('\0', n);
|
||||||
|
}
|
||||||
|
else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
|
||||||
|
else if (c == 'x') {
|
||||||
|
var newC = 0;
|
||||||
|
|
||||||
|
for (var j = 0; j < 2; j++) {
|
||||||
|
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence.");
|
||||||
|
|
||||||
|
int val = fromHex(src.at(i + n));
|
||||||
|
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
newC = (newC << 4) | val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res((char)newC, n);
|
||||||
|
}
|
||||||
|
else if (c == 'u') {
|
||||||
|
var newC = 0;
|
||||||
|
|
||||||
|
for (var j = 0; j < 4; j++) {
|
||||||
|
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
|
||||||
|
|
||||||
|
int val = fromHex(src.at(i + n));
|
||||||
|
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
newC = (newC << 4) | val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res((char)newC, n);
|
||||||
|
}
|
||||||
|
else if (c == '\n') return ParseRes.res(null, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(src.at(i + n), n + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<String> parseIdentifier(Source src, int i) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
var res = new StringBuilder();
|
||||||
|
var first = true;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (i + n > src.size()) break;
|
||||||
|
char c = src.at(i + n, '\0');
|
||||||
|
|
||||||
|
if (first && Parsing.isDigit(c)) break;
|
||||||
|
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
|
||||||
|
res.append(c);
|
||||||
|
n++;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.length() <= 0) return ParseRes.failed();
|
||||||
|
else return ParseRes.res(res.toString(), n);
|
||||||
|
}
|
||||||
|
public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
var res = new StringBuilder();
|
||||||
|
var first = true;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (i + n > src.size()) break;
|
||||||
|
char c = src.at(i + n, '\0');
|
||||||
|
|
||||||
|
if (first && Parsing.isDigit(c)) break;
|
||||||
|
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
|
||||||
|
res.append(c);
|
||||||
|
n++;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.length() <= 0) return ParseRes.failed();
|
||||||
|
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
|
||||||
|
else return ParseRes.failed();
|
||||||
|
}
|
||||||
|
public static boolean isIdentifier(Source src, int i, String test) {
|
||||||
|
return parseIdentifier(src, i, test).isSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<String> parseOperator(Source src, int i, String op) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
|
||||||
|
else return ParseRes.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<Double> parseHex(Source src, int i) {
|
||||||
|
int n = 0;
|
||||||
|
double res = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int digit = Parsing.fromHex(src.at(i + n, '\0'));
|
||||||
|
if (digit < 0) {
|
||||||
|
if (n <= 0) return ParseRes.failed();
|
||||||
|
else return ParseRes.res(res, n);
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
|
||||||
|
res *= 16;
|
||||||
|
res += digit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static ParseRes<Double> parseOct(Source src, int i) {
|
||||||
|
int n = 0;
|
||||||
|
double res = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int digit = src.at(i + n, '\0') - '0';
|
||||||
|
if (digit < 0 || digit > 9) break;
|
||||||
|
if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit);
|
||||||
|
|
||||||
|
if (digit < 0) {
|
||||||
|
if (n <= 0) return ParseRes.failed();
|
||||||
|
else return ParseRes.res(res, n);
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
|
||||||
|
res *= 8;
|
||||||
|
res += digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(res, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<String> parseString(Source src, int i) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
|
char quote;
|
||||||
|
|
||||||
|
if (src.is(i + n, '\'')) quote = '\'';
|
||||||
|
else if (src.is(i + n, '"')) quote = '"';
|
||||||
|
else return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var res = new StringBuilder();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
|
||||||
|
if (src.is(i + n, quote)) {
|
||||||
|
n++;
|
||||||
|
return ParseRes.res(res.toString(), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var charRes = parseChar(src, i + n);
|
||||||
|
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
|
||||||
|
n += charRes.n;
|
||||||
|
|
||||||
|
if (charRes.result != null) res.append(charRes.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
|
double whole = 0;
|
||||||
|
double fract = 0;
|
||||||
|
long exponent = 0;
|
||||||
|
boolean parsedAny = false;
|
||||||
|
boolean negative = false;
|
||||||
|
|
||||||
|
if (withMinus && src.is(i + n, "-")) {
|
||||||
|
negative = true;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
|
||||||
|
n += 2;
|
||||||
|
|
||||||
|
var res = parseHex(src, i + n);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
if (negative) return ParseRes.res(-res.result, n);
|
||||||
|
else return ParseRes.res(res.result, n);
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
|
||||||
|
n += 2;
|
||||||
|
|
||||||
|
var res = parseOct(src, i + n);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
if (negative) return ParseRes.res(-res.result, n);
|
||||||
|
else return ParseRes.res(res.result, n);
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, '0')) {
|
||||||
|
n++;
|
||||||
|
parsedAny = true;
|
||||||
|
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
|
parsedAny = true;
|
||||||
|
whole *= 10;
|
||||||
|
whole += src.at(i + n++) - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, '.')) {
|
||||||
|
parsedAny = true;
|
||||||
|
n++;
|
||||||
|
|
||||||
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
|
fract += src.at(i + n++) - '0';
|
||||||
|
fract /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
|
||||||
|
n++;
|
||||||
|
parsedAny = true;
|
||||||
|
boolean expNegative = false;
|
||||||
|
boolean parsedE = false;
|
||||||
|
|
||||||
|
if (src.is(i + n, '-')) {
|
||||||
|
expNegative = true;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, '+')) n++;
|
||||||
|
|
||||||
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
|
parsedE = true;
|
||||||
|
exponent *= 10;
|
||||||
|
|
||||||
|
if (expNegative) exponent -= src.at(i + n++) - '0';
|
||||||
|
else exponent += src.at(i + n++) - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedAny) {
|
||||||
|
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||||
|
return ParseRes.failed();
|
||||||
|
}
|
||||||
|
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
|
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
|
}
|
||||||
|
public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
|
double whole = 0;
|
||||||
|
double fract = 0;
|
||||||
|
long exponent = 0;
|
||||||
|
boolean parsedAny = false;
|
||||||
|
boolean negative = false;
|
||||||
|
|
||||||
|
if (withMinus && src.is(i + n, "-")) {
|
||||||
|
negative = true;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
|
parsedAny = true;
|
||||||
|
whole *= 10;
|
||||||
|
whole += src.at(i + n++) - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, '.')) {
|
||||||
|
parsedAny = true;
|
||||||
|
n++;
|
||||||
|
|
||||||
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
|
fract += src.at(i + n++) - '0';
|
||||||
|
fract /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
|
||||||
|
n++;
|
||||||
|
parsedAny = true;
|
||||||
|
boolean expNegative = false;
|
||||||
|
boolean parsedE = false;
|
||||||
|
|
||||||
|
if (src.is(i + n, '-')) {
|
||||||
|
expNegative = true;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, '+')) n++;
|
||||||
|
|
||||||
|
while (src.is(i + n, Parsing::isDigit)) {
|
||||||
|
parsedE = true;
|
||||||
|
exponent *= 10;
|
||||||
|
|
||||||
|
if (expNegative) exponent -= src.at(i + n++) - '0';
|
||||||
|
else exponent += src.at(i + n++) - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedAny) {
|
||||||
|
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||||
|
return ParseRes.failed();
|
||||||
|
}
|
||||||
|
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
|
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
|
||||||
|
}
|
||||||
|
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
|
||||||
|
var n = skipEmpty(src, i);
|
||||||
|
|
||||||
|
double result = 0;
|
||||||
|
boolean parsedAny = false;
|
||||||
|
boolean negative = false;
|
||||||
|
|
||||||
|
if (withMinus && src.is(i + n, "-")) {
|
||||||
|
negative = true;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
|
||||||
|
n += 2;
|
||||||
|
|
||||||
|
var res = parseHex(src, i);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
if (negative) return ParseRes.res(-res.result, n);
|
||||||
|
else return ParseRes.res(res.result, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n)));
|
||||||
|
if (digit < 0) break;
|
||||||
|
|
||||||
|
parsedAny = true;
|
||||||
|
result += digit;
|
||||||
|
result *= alphabet.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedAny) {
|
||||||
|
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
|
||||||
|
return ParseRes.failed();
|
||||||
|
}
|
||||||
|
else if (negative) return ParseRes.res(-result, n);
|
||||||
|
else return ParseRes.res(-result, n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
|
||||||
|
public class Source {
|
||||||
|
public final Environment env;
|
||||||
|
public final Filename filename;
|
||||||
|
public final String src;
|
||||||
|
|
||||||
|
private int[] lineStarts;
|
||||||
|
|
||||||
|
public Location loc(int offset) {
|
||||||
|
return new SourceLocation(filename, lineStarts, offset);
|
||||||
|
}
|
||||||
|
public boolean is(int i, char c) {
|
||||||
|
return i >= 0 && i < src.length() && src.charAt(i) == c;
|
||||||
|
}
|
||||||
|
public boolean is(int i, String src) {
|
||||||
|
if (i < 0 || i + src.length() > size()) return false;
|
||||||
|
|
||||||
|
for (int j = 0; j < src.length(); j++) {
|
||||||
|
if (at(i + j) != src.charAt(j)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public boolean is(int i, Predicate<Character> predicate) {
|
||||||
|
if (i < 0 || i >= src.length()) return false;
|
||||||
|
return predicate.test(at(i));
|
||||||
|
}
|
||||||
|
public char at(int i) {
|
||||||
|
return src.charAt(i);
|
||||||
|
}
|
||||||
|
public char at(int i, char defaultVal) {
|
||||||
|
if (i < 0 || i >= src.length()) return defaultVal;
|
||||||
|
else return src.charAt(i);
|
||||||
|
}
|
||||||
|
public int size() {
|
||||||
|
return src.length();
|
||||||
|
}
|
||||||
|
public String slice(int start, int end) {
|
||||||
|
return src.substring(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Source(Environment env, Filename filename, String src) {
|
||||||
|
if (env == null) this.env = new Environment();
|
||||||
|
else this.env = env;
|
||||||
|
|
||||||
|
this.filename = filename;
|
||||||
|
this.src = src;
|
||||||
|
|
||||||
|
int n = 1;
|
||||||
|
lineStarts = new int[16];
|
||||||
|
lineStarts[0] = 0;
|
||||||
|
|
||||||
|
for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) {
|
||||||
|
if (n >= lineStarts.length) {
|
||||||
|
var newArr = new int[lineStarts.length * 2];
|
||||||
|
System.arraycopy(lineStarts, 0, newArr, 0, n);
|
||||||
|
lineStarts = newArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStarts[n++] = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newArr = new int[n];
|
||||||
|
System.arraycopy(lineStarts, 0, newArr, 0, n);
|
||||||
|
lineStarts = newArr;
|
||||||
|
}
|
||||||
|
public Source(String src) {
|
||||||
|
this(null, null, src);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package me.topchetoeu.jscript.common.parsing;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class SourceLocation extends Location {
|
||||||
|
private int[] lineStarts;
|
||||||
|
private int line;
|
||||||
|
private int start;
|
||||||
|
private final Filename filename;
|
||||||
|
private final int offset;
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
if (lineStarts == null) return;
|
||||||
|
|
||||||
|
int a = 0;
|
||||||
|
int b = lineStarts.length;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (a + 1 >= b) break;
|
||||||
|
var mid = -((-a - b) >> 1);
|
||||||
|
var el = lineStarts[mid];
|
||||||
|
|
||||||
|
if (el < offset) a = mid;
|
||||||
|
else if (el > offset) b = mid;
|
||||||
|
else {
|
||||||
|
this.line = mid;
|
||||||
|
this.start = 0;
|
||||||
|
this.lineStarts = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.line = a;
|
||||||
|
this.start = offset - lineStarts[a];
|
||||||
|
this.lineStarts = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Filename filename() { return filename; }
|
||||||
|
@Override public int line() {
|
||||||
|
update();
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
@Override public int start() {
|
||||||
|
update();
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return Objects.hash(offset);
|
||||||
|
}
|
||||||
|
@Override public int compareTo(Location other) {
|
||||||
|
if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset);
|
||||||
|
else return super.compareTo(other);
|
||||||
|
}
|
||||||
|
@Override public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof SourceLocation other) return this.offset == other.offset;
|
||||||
|
else return super.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceLocation(Filename filename, int[] lineStarts, int offset) {
|
||||||
|
this.filename = filename;
|
||||||
|
this.lineStarts = lineStarts;
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
|
||||||
|
public interface AssignableNode {
|
||||||
|
public abstract Node toAssign(Node val, Operation operation);
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
|
|
||||||
public abstract class AssignableStatement extends Statement {
|
|
||||||
public abstract Statement toAssign(Statement val, Operation operation);
|
|
||||||
|
|
||||||
protected AssignableStatement(Location loc) {
|
|
||||||
super(loc);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +1,40 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Vector;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.FunctionBody;
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
||||||
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
|
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
|
||||||
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Scope;
|
||||||
|
|
||||||
public class CompileResult {
|
public final class CompileResult {
|
||||||
public final Vector<Instruction> instructions = new Vector<>();
|
public static final class ChildData {
|
||||||
public final List<CompileResult> children = new LinkedList<>();
|
public final int id;
|
||||||
public final FunctionMapBuilder map = FunctionMap.builder();
|
public final CompileResult result;
|
||||||
public final LocalScopeRecord scope;
|
|
||||||
public int length = 0;
|
public ChildData(int id, CompileResult result) {
|
||||||
|
this.result = result;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<IntFunction<Instruction>> instructions;
|
||||||
|
public final List<CompileResult> children;
|
||||||
|
public final FunctionMapBuilder map;
|
||||||
|
public final Environment env;
|
||||||
|
public int length;
|
||||||
|
public Runnable buildTask = () -> {
|
||||||
|
throw new IllegalStateException("Compile result is not ready to be built");
|
||||||
|
};
|
||||||
|
public final Scope scope;
|
||||||
|
|
||||||
public int temp() {
|
public int temp() {
|
||||||
instructions.add(null);
|
instructions.add(null);
|
||||||
@ -25,15 +42,20 @@ public class CompileResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult add(Instruction instr) {
|
public CompileResult add(Instruction instr) {
|
||||||
|
instructions.add(i -> instr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public CompileResult add(IntFunction<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, _i -> instr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public Instruction get(int i) {
|
public CompileResult set(int i, IntFunction<Instruction>instr) {
|
||||||
return instructions.get(i);
|
instructions.set(i, instr);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
public int size() { return instructions.size(); }
|
public int size() { return instructions.size(); }
|
||||||
|
|
||||||
@ -56,9 +78,19 @@ public class CompileResult {
|
|||||||
setLocationAndDebug(instructions.size() - 1, loc, type);
|
setLocationAndDebug(instructions.size() - 1, loc, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult addChild(CompileResult child) {
|
public int addChild(CompileResult res) {
|
||||||
this.children.add(child);
|
this.children.add(res);
|
||||||
return child;
|
return this.children.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instruction[] instructions() {
|
||||||
|
var res = new Instruction[instructions.size()];
|
||||||
|
var i = 0;
|
||||||
|
for (var suppl : instructions) {
|
||||||
|
res[i] = suppl.apply(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionMap map() {
|
public FunctionMap map() {
|
||||||
@ -69,10 +101,38 @@ public class CompileResult {
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren);
|
var instrRes = new Instruction[instructions.size()];
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (var suppl : instructions) {
|
||||||
|
instrRes[i] = suppl.apply(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FunctionBody(
|
||||||
|
scope.localsCount() + scope.allocCount(), scope.capturesCount(),
|
||||||
|
length, instrRes, builtChildren
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompileResult(LocalScopeRecord scope) {
|
public CompileResult subtarget() {
|
||||||
|
return new CompileResult(new Scope(scope), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompileResult(Environment env, Scope scope, int length, Consumer<CompileResult> task) {
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
|
this.instructions = new ArrayList<>();
|
||||||
|
this.children = new LinkedList<>();
|
||||||
|
this.map = FunctionMap.builder();
|
||||||
|
this.env = env;
|
||||||
|
this.length = length;
|
||||||
|
this.buildTask = () -> task.accept(this);
|
||||||
|
}
|
||||||
|
private CompileResult(Scope scope, CompileResult parent) {
|
||||||
|
this.scope = scope;
|
||||||
|
this.instructions = parent.instructions;
|
||||||
|
this.children = parent.children;
|
||||||
|
this.map = parent.map;
|
||||||
|
this.env = parent.env;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
|
||||||
|
|
||||||
|
public class CompoundNode extends Node {
|
||||||
|
public final Node[] statements;
|
||||||
|
public final boolean hasScope;
|
||||||
|
public Location end;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
for (var stm : statements) stm.resolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compile(CompileResult target, boolean pollute, boolean singleEntry, BreakpointType type) {
|
||||||
|
List<Node> statements = new ArrayList<Node>();
|
||||||
|
|
||||||
|
var subtarget = hasScope ? target.subtarget() : target;
|
||||||
|
if (hasScope) {
|
||||||
|
subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||||
|
subtarget.scope.singleEntry = singleEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var stm : this.statements) {
|
||||||
|
if (stm instanceof FunctionStatementNode func) {
|
||||||
|
func.compile(subtarget, false);
|
||||||
|
}
|
||||||
|
else statements.add(stm);
|
||||||
|
}
|
||||||
|
|
||||||
|
var polluted = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < statements.size(); i++) {
|
||||||
|
var stm = statements.get(i);
|
||||||
|
|
||||||
|
if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||||
|
else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasScope) {
|
||||||
|
subtarget.scope.end();
|
||||||
|
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!polluted && pollute) {
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
|
compile(target, pollute, true, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundNode setEnd(Location loc) {
|
||||||
|
this.end = loc;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundNode(Location loc, boolean hasScope, Node ...statements) {
|
||||||
|
super(loc);
|
||||||
|
this.hasScope = hasScope;
|
||||||
|
this.statements = statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void compileMultiEntry(Node node, CompileResult target, boolean pollute, BreakpointType type) {
|
||||||
|
if (node instanceof CompoundNode comp) {
|
||||||
|
comp.compile(target, pollute, false, type);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.compile(target, pollute, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
|
||||||
|
if (precedence > 1) return ParseRes.failed();
|
||||||
|
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ",")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var curr = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
|
||||||
|
n += curr.n;
|
||||||
|
|
||||||
|
if (prev instanceof CompoundNode comp) {
|
||||||
|
var children = new ArrayList<Node>();
|
||||||
|
children.addAll(List.of(comp.statements));
|
||||||
|
children.add(curr.result);
|
||||||
|
|
||||||
|
return ParseRes.res(new CompoundNode(loc, comp.hasScope, children.toArray(Node[]::new)), n);
|
||||||
|
}
|
||||||
|
else return ParseRes.res(new CompoundNode(loc, false, prev, curr.result), n);
|
||||||
|
}
|
||||||
|
public static ParseRes<CompoundNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var statements = new ArrayList<Node>();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (src.is(i + n, ";")) {
|
||||||
|
n++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
statements.add(res.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new CompoundNode(loc, true, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
|
||||||
|
|
||||||
public class CompoundStatement extends Statement {
|
|
||||||
public final Statement[] statements;
|
|
||||||
public final boolean separateFuncs;
|
|
||||||
public Location end;
|
|
||||||
|
|
||||||
@Override public boolean pure() {
|
|
||||||
for (var stm : statements) {
|
|
||||||
if (!stm.pure()) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
for (var stm : statements) stm.declare(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
|
||||||
List<Statement> statements = new Vector<Statement>();
|
|
||||||
if (separateFuncs) for (var stm : this.statements) {
|
|
||||||
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
|
|
||||||
stm.compile(target, false);
|
|
||||||
}
|
|
||||||
else statements.add(stm);
|
|
||||||
}
|
|
||||||
else statements = List.of(this.statements);
|
|
||||||
|
|
||||||
var polluted = false;
|
|
||||||
|
|
||||||
for (var i = 0; i < statements.size(); i++) {
|
|
||||||
var stm = statements.get(i);
|
|
||||||
|
|
||||||
if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!polluted && pollute) {
|
|
||||||
target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompoundStatement setEnd(Location loc) {
|
|
||||||
this.end = loc;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
|
|
||||||
super(loc);
|
|
||||||
this.separateFuncs = separateFuncs;
|
|
||||||
this.statements = statements;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
public final class DeferredIntSupplier implements IntSupplier {
|
||||||
|
private int value;
|
||||||
|
private boolean set;
|
||||||
|
|
||||||
|
public void set(int val) {
|
||||||
|
if (set) throw new RuntimeException("A deferred int supplier may be set only once");
|
||||||
|
value = val;
|
||||||
|
set = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getAsInt() {
|
||||||
|
if (!set) throw new RuntimeException("Deferred int supplier accessed too early");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||||
|
|
||||||
|
public class FunctionArrowNode extends FunctionNode {
|
||||||
|
@Override public String name() { return null; }
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
var id = target.addChild(compileBody(target, name, null));
|
||||||
|
target.add(_i -> Instruction.loadFunc(id, true, false, true, null, captures(id, target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) {
|
||||||
|
super(loc, end, params, expToBody(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final CompoundNode expToBody(Node node) {
|
||||||
|
if (node instanceof CompoundNode res) return res;
|
||||||
|
else return new CompoundNode(node.loc(), false, new ReturnNode(node.loc(), node));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<FunctionArrowNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
Parameters params;
|
||||||
|
|
||||||
|
if (src.is(i + n, "(")) {
|
||||||
|
var paramsRes = JavaScript.parseParameters(src, i + n);
|
||||||
|
if (!paramsRes.isSuccess()) return paramsRes.chainError();
|
||||||
|
n += paramsRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
params = paramsRes.result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var singleParam = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!singleParam.isSuccess()) return ParseRes.failed();
|
||||||
|
|
||||||
|
var paramLoc = src.loc(i + n);
|
||||||
|
n += singleParam.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
params = new Parameters(List.of(new Parameter(paramLoc, singleParam.result, null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!src.is(i + n, "=>")) return ParseRes.failed();
|
||||||
|
n += 2;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "{")) {
|
||||||
|
var body = CompoundNode.parse(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var body = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public abstract class FunctionNode extends Node {
|
||||||
|
public final CompoundNode body;
|
||||||
|
public final Parameters params;
|
||||||
|
public final Location end;
|
||||||
|
|
||||||
|
public abstract String name();
|
||||||
|
|
||||||
|
protected final int[] captures(int id, CompileResult target) {
|
||||||
|
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) {
|
||||||
|
var name = this.name() != null ? this.name() : _name;
|
||||||
|
|
||||||
|
env = env.child()
|
||||||
|
.remove(LabelContext.BREAK_CTX)
|
||||||
|
.remove(LabelContext.CONTINUE_CTX);
|
||||||
|
|
||||||
|
return new CompileResult(env, scope, params.params.size(), target -> {
|
||||||
|
// if (params.params.size() > 0) target.add(Instruction.loadArgs(true));
|
||||||
|
|
||||||
|
// if (hasArgs) {
|
||||||
|
// var argsVar = scope.defineStrict(new Variable("arguments", true), loc());
|
||||||
|
// target.add(_i -> Instruction.storeVar(argsVar.index(), params.params.size() > 0));
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (params.params.size() > 0) {
|
||||||
|
target.add(Instruction.loadArgs(true));
|
||||||
|
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1));
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (var param : params.params) {
|
||||||
|
if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed");
|
||||||
|
if (!JavaScript.checkVarName(param.name)) {
|
||||||
|
throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name));
|
||||||
|
}
|
||||||
|
var varI = scope.define(new Variable(param.name, false), param.loc);
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember(i++));
|
||||||
|
|
||||||
|
if (param.node != null) {
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
target.add(Instruction.jmpIfNot(end));
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
param.node.compile(target, true);
|
||||||
|
|
||||||
|
end.set(target.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
target.add(_i -> Instruction.storeVar(varI.index()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.restName != null) {
|
||||||
|
if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
|
||||||
|
var restVar = scope.define(new Variable(params.restName, false), params.restLocation);
|
||||||
|
target.add(Instruction.loadRestArgs(params.params.size()));
|
||||||
|
target.add(_i -> Instruction.storeVar(restVar.index()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfName != null && !scope.has(name, false)) {
|
||||||
|
var i = scope.defineSpecial(new Variable(selfName, true), end);
|
||||||
|
|
||||||
|
target.add(Instruction.loadCallee());
|
||||||
|
target.add(_i -> Instruction.storeVar(i.index(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
body.resolve(target);
|
||||||
|
body.compile(target, lastReturn, BreakpointType.NONE);
|
||||||
|
|
||||||
|
scope.end();
|
||||||
|
|
||||||
|
for (var child : target.children) child.buildTask.run();
|
||||||
|
|
||||||
|
scope.finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public final CompileResult compileBody(CompileResult parent, String name, String selfName) {
|
||||||
|
return compileBody(parent.env, new FunctionScope(parent.scope), false, name, selfName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
|
||||||
|
public void compile(CompileResult target, boolean pollute, String name) {
|
||||||
|
compile(target, pollute, name, BreakpointType.NONE);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
|
||||||
|
compile(target, pollute, (String)null, bp);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, pollute, (String)null, BreakpointType.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) {
|
||||||
|
super(loc);
|
||||||
|
|
||||||
|
this.end = end;
|
||||||
|
this.params = params;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
|
||||||
|
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name);
|
||||||
|
else stm.compile(target, pollute);
|
||||||
|
}
|
||||||
|
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp);
|
||||||
|
else stm.compile(target, pollute, bp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed();
|
||||||
|
n += 8;
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var params = JavaScript.parseParameters(src, i + n);
|
||||||
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
||||||
|
n += params.n;
|
||||||
|
|
||||||
|
var body = CompoundNode.parse(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
if (statement) return ParseRes.res(new FunctionStatementNode(
|
||||||
|
loc, src.loc(i + n - 1),
|
||||||
|
params.result, body.result, name.result
|
||||||
|
), n);
|
||||||
|
else return ParseRes.res(new FunctionValueNode(
|
||||||
|
loc, src.loc(i + n - 1),
|
||||||
|
params.result, body.result, name.result
|
||||||
|
), n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public class FunctionStatementNode extends FunctionNode {
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Override public String name() { return name; }
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
target.scope.define(new Variable(name, false), end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
var id = target.addChild(compileBody(target, name, null));
|
||||||
|
target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target)));
|
||||||
|
target.add(VariableNode.toSet(target, end, this.name, pollute, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
|
||||||
|
super(loc, end, params, body);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
|
public class FunctionValueNode extends FunctionNode {
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Override public String name() { return name; }
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
var id = target.addChild(compileBody(target, name, null));
|
||||||
|
target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
|
||||||
|
super(loc, end, params, body);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
353
src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java
Normal file
353
src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.BreakNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ContinueNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.DebugNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.DeleteNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.DoWhileNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ForInNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ForNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ForOfNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.IfNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.SwitchNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.ThrowNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.TryNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.control.WhileNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.RegexNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ThisNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.NullNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.CallNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.ChangeNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.OperationNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public final class JavaScript {
|
||||||
|
public static enum DeclarationType {
|
||||||
|
VAR(false, false),
|
||||||
|
CONST(true, true),
|
||||||
|
LET(true, false);
|
||||||
|
|
||||||
|
public final boolean strict, readonly;
|
||||||
|
|
||||||
|
private DeclarationType(boolean strict, boolean readonly) {
|
||||||
|
this.strict = strict;
|
||||||
|
this.readonly = readonly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Set<String> reserved = Set.of(
|
||||||
|
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
|
||||||
|
"finally", "for", "do", "while", "switch", "case", "default", "new",
|
||||||
|
"function", "var", "return", "throw", "typeof", "delete", "break",
|
||||||
|
"continue", "debugger", "implements", "interface", "package", "private",
|
||||||
|
"protected", "public", "static", "arguments"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseParens(Source src, int i) {
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||||
|
if (!openParen.isSuccess()) return openParen.chainError();
|
||||||
|
n += openParen.n;
|
||||||
|
|
||||||
|
var res = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||||
|
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
|
||||||
|
n += closeParen.n;
|
||||||
|
|
||||||
|
return ParseRes.res(res.result, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseSimple(Source src, int i, boolean statement) {
|
||||||
|
return ParseRes.first(src, i,
|
||||||
|
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
|
||||||
|
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
|
||||||
|
JavaScript::parseLiteral,
|
||||||
|
StringNode::parse,
|
||||||
|
RegexNode::parse,
|
||||||
|
NumberNode::parse,
|
||||||
|
ChangeNode::parsePrefixDecrease,
|
||||||
|
ChangeNode::parsePrefixIncrease,
|
||||||
|
OperationNode::parsePrefix,
|
||||||
|
ArrayNode::parse,
|
||||||
|
FunctionArrowNode::parse,
|
||||||
|
JavaScript::parseParens,
|
||||||
|
CallNode::parseNew,
|
||||||
|
TypeofNode::parse,
|
||||||
|
DiscardNode::parse,
|
||||||
|
DeleteNode::parse,
|
||||||
|
VariableNode::parse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseLiteral(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var id = Parsing.parseIdentifier(src, i);
|
||||||
|
if (!id.isSuccess()) return id.chainError();
|
||||||
|
n += id.n;
|
||||||
|
|
||||||
|
if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n);
|
||||||
|
if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n);
|
||||||
|
if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n);
|
||||||
|
if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n);
|
||||||
|
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
|
||||||
|
|
||||||
|
return ParseRes.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseExpression(Source src, int i, int precedence, boolean statement) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
Node prev = null;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (prev == null) {
|
||||||
|
var res = parseSimple(src, i + n, statement);
|
||||||
|
if (res.isSuccess()) {
|
||||||
|
n += res.n;
|
||||||
|
prev = res.result;
|
||||||
|
}
|
||||||
|
else if (res.isError()) return res.chainError();
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var _prev = prev;
|
||||||
|
ParseRes<Node> res = ParseRes.first(src, i + n,
|
||||||
|
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
|
||||||
|
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
|
||||||
|
(s, j) -> ChangeNode.parsePostfixIncrease(s, j, _prev, precedence),
|
||||||
|
(s, j) -> ChangeNode.parsePostfixDecrease(s, j, _prev, precedence),
|
||||||
|
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
|
||||||
|
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
|
||||||
|
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
|
||||||
|
(s, j) -> IndexNode.parseIndex(s, j, _prev, precedence),
|
||||||
|
(s, j) -> CallNode.parseCall(s, j, _prev, precedence),
|
||||||
|
(s, j) -> CompoundNode.parseComma(s, j, _prev, precedence)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.isSuccess()) {
|
||||||
|
n += res.n;
|
||||||
|
prev = res.result;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (res.isError()) return res.chainError();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == null) return ParseRes.failed();
|
||||||
|
else return ParseRes.res(prev, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseExpression(Source src, int i, int precedence) {
|
||||||
|
return parseExpression(src, i, precedence, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseExpressionStatement(Source src, int i) {
|
||||||
|
var res = parseExpression(src, i, 0, true);
|
||||||
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + res.n);
|
||||||
|
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
|
||||||
|
|
||||||
|
return res.addN(end.n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parseStatement(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1);
|
||||||
|
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
|
||||||
|
|
||||||
|
ParseRes<? extends Node> res = ParseRes.first(src, i + n,
|
||||||
|
VariableDeclareNode::parse,
|
||||||
|
ReturnNode::parse,
|
||||||
|
ThrowNode::parse,
|
||||||
|
ContinueNode::parse,
|
||||||
|
BreakNode::parse,
|
||||||
|
DebugNode::parse,
|
||||||
|
IfNode::parse,
|
||||||
|
WhileNode::parse,
|
||||||
|
SwitchNode::parse,
|
||||||
|
ForNode::parse,
|
||||||
|
ForInNode::parse,
|
||||||
|
ForOfNode::parse,
|
||||||
|
DoWhileNode::parse,
|
||||||
|
TryNode::parse,
|
||||||
|
CompoundNode::parse,
|
||||||
|
(s, j) -> FunctionNode.parseFunction(s, j, true),
|
||||||
|
JavaScript::parseExpressionStatement
|
||||||
|
);
|
||||||
|
return res.addN(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
if (i >= src.size()) return ParseRes.res(true, n + 1);
|
||||||
|
|
||||||
|
for (var j = i; j < i + n; j++) {
|
||||||
|
if (src.is(j, '\n')) return ParseRes.res(true, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, ';')) return ParseRes.res(true, n + 1);
|
||||||
|
if (src.is(i + n, '}')) return ParseRes.res(true, n);
|
||||||
|
|
||||||
|
return ParseRes.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<Parameters> parseParameters(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||||
|
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
|
||||||
|
n += openParen.n;
|
||||||
|
|
||||||
|
var params = new ArrayList<Parameter>();
|
||||||
|
|
||||||
|
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||||
|
n += closeParen.n;
|
||||||
|
|
||||||
|
if (!closeParen.isSuccess()) {
|
||||||
|
while (true) {
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "...")) {
|
||||||
|
n += 3;
|
||||||
|
var restLoc = src.loc(i);
|
||||||
|
|
||||||
|
var restName = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!restName.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
|
||||||
|
n += restName.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(new Parameters(params, restName.result, restLoc), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var paramLoc = src.loc(i);
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "=")) {
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var val = parseExpression(src, i + n, 2);
|
||||||
|
if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter default value");
|
||||||
|
n += val.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
params.add(new Parameter(paramLoc, name.result, val.result));
|
||||||
|
}
|
||||||
|
else params.add(new Parameter(paramLoc, name.result, null));
|
||||||
|
|
||||||
|
if (src.is(i + n, ",")) {
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, ")")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new Parameters(params), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
|
||||||
|
var res = Parsing.parseIdentifier(src, i);
|
||||||
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
|
||||||
|
if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n);
|
||||||
|
if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n);
|
||||||
|
if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n);
|
||||||
|
|
||||||
|
return ParseRes.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Node[] parse(Environment env, Filename filename, String raw) {
|
||||||
|
var src = new Source(env, filename, raw);
|
||||||
|
var list = new ArrayList<Node>();
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (i >= src.size()) break;
|
||||||
|
|
||||||
|
var res = parseStatement(src, i);
|
||||||
|
|
||||||
|
if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
|
||||||
|
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
|
||||||
|
|
||||||
|
i += res.n;
|
||||||
|
|
||||||
|
list.add(res.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.toArray(Node[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkVarName(String name) {
|
||||||
|
return !JavaScript.reserved.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompileResult compile(Environment env, Node ...statements) {
|
||||||
|
var func = new FunctionValueNode(null, null, new Parameters(List.of()), new CompoundNode(null, true, statements), null);
|
||||||
|
var res = func.compileBody(env, new FunctionScope(true), true, null, null);
|
||||||
|
res.buildTask.run();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompileResult compile(Environment env, Filename filename, String raw) {
|
||||||
|
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
|
||||||
|
}
|
||||||
|
public static CompileResult compile(Filename filename, String raw) {
|
||||||
|
var env = new Environment();
|
||||||
|
return JavaScript.compile(env, JavaScript.parse(env, filename, raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<String> parseLabel(Source src, int i) {
|
||||||
|
int n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
var nameRes = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!nameRes.isSuccess()) return nameRes.chainError();
|
||||||
|
n += nameRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(nameRes.result, n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
import me.topchetoeu.jscript.common.environment.Key;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class LabelContext {
|
||||||
|
public static final Key<LabelContext> BREAK_CTX = Key.of();
|
||||||
|
public static final Key<LabelContext> CONTINUE_CTX = Key.of();
|
||||||
|
|
||||||
|
private final LinkedList<IntSupplier> list = new LinkedList<>();
|
||||||
|
private final HashMap<String, IntSupplier> map = new HashMap<>();
|
||||||
|
|
||||||
|
public IntSupplier get() {
|
||||||
|
return list.peekLast();
|
||||||
|
}
|
||||||
|
public IntSupplier get(String name) {
|
||||||
|
return map.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntFunction<Instruction> getJump() {
|
||||||
|
var res = get();
|
||||||
|
if (res == null) return null;
|
||||||
|
else return i -> Instruction.jmp(res.getAsInt() - i);
|
||||||
|
}
|
||||||
|
public IntFunction<Instruction> getJump(String name) {
|
||||||
|
var res = get(name);
|
||||||
|
if (res == null) return null;
|
||||||
|
else return i -> Instruction.jmp(res.getAsInt() - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(IntSupplier jumpTarget) {
|
||||||
|
list.add(jumpTarget);
|
||||||
|
}
|
||||||
|
public void push(Location loc, String name, IntSupplier jumpTarget) {
|
||||||
|
if (name == null) return;
|
||||||
|
if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name));
|
||||||
|
map.put(name, jumpTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) {
|
||||||
|
push(jumpTarget);
|
||||||
|
push(loc, name, jumpTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pop() {
|
||||||
|
list.removeLast();
|
||||||
|
}
|
||||||
|
public void pop(String name) {
|
||||||
|
if (name == null) return;
|
||||||
|
map.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popLoop(String name) {
|
||||||
|
pop();
|
||||||
|
pop(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LabelContext getBreak(Environment env) {
|
||||||
|
return env.initFrom(BREAK_CTX, () -> new LabelContext());
|
||||||
|
}
|
||||||
|
public static LabelContext getCont(Environment env) {
|
||||||
|
return env.initFrom(CONTINUE_CTX, () -> new LabelContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) {
|
||||||
|
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
|
||||||
|
LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget);
|
||||||
|
}
|
||||||
|
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) {
|
||||||
|
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
|
||||||
|
LabelContext.getCont(env).pushLoop(loc, name, contTarget);
|
||||||
|
}
|
||||||
|
public static void popLoop(Environment env, String name) {
|
||||||
|
LabelContext.getBreak(env).popLoop(name);
|
||||||
|
LabelContext.getCont(env).popLoop(name);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
public abstract class Statement {
|
public abstract class Node {
|
||||||
private Location _loc;
|
private Location loc;
|
||||||
|
|
||||||
public boolean pure() { return false; }
|
public void resolve(CompileResult target) {}
|
||||||
public void declare(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();
|
||||||
@ -18,10 +17,10 @@ public abstract class Statement {
|
|||||||
compile(target, pollute, BreakpointType.NONE);
|
compile(target, pollute, BreakpointType.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Location loc() { return _loc; }
|
public Location loc() { return loc; }
|
||||||
public void setLoc(Location loc) { _loc = loc; }
|
public void setLoc(Location loc) { this.loc = loc; }
|
||||||
|
|
||||||
protected Statement(Location loc) {
|
protected Node(Location loc) {
|
||||||
this._loc = loc;
|
this.loc = loc;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public final class NodeChildren implements Iterable<Node> {
|
||||||
|
public static final class Slot {
|
||||||
|
private Node node;
|
||||||
|
private final Function<Node, Node> replacer;
|
||||||
|
|
||||||
|
public final void replace(Node node) {
|
||||||
|
this.node = this.replacer.apply(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slot(Node nodes, Function<Node, Node> replacer) {
|
||||||
|
this.node = nodes;
|
||||||
|
this.replacer = replacer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Slot[] slots;
|
||||||
|
|
||||||
|
private NodeChildren(Slot[] slots) {
|
||||||
|
this.slots = slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Iterator<Node> iterator() {
|
||||||
|
return new Iterator<Node>() {
|
||||||
|
private int i = 0;
|
||||||
|
private Slot[] arr = slots;
|
||||||
|
|
||||||
|
@Override public boolean hasNext() {
|
||||||
|
if (arr == null) return false;
|
||||||
|
else if (i >= arr.length) {
|
||||||
|
arr = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
@Override public Node next() {
|
||||||
|
if (!hasNext()) return null;
|
||||||
|
return arr[i++].node;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public Iterable<Slot> slots() {
|
||||||
|
return () -> new Iterator<Slot>() {
|
||||||
|
private int i = 0;
|
||||||
|
private Slot[] arr = slots;
|
||||||
|
|
||||||
|
@Override public boolean hasNext() {
|
||||||
|
if (arr == null) return false;
|
||||||
|
else if (i >= arr.length) {
|
||||||
|
arr = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
@Override public Slot next() {
|
||||||
|
if (!hasNext()) return null;
|
||||||
|
return arr[i++];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final ArrayList<Slot> slots = new ArrayList<>();
|
||||||
|
|
||||||
|
public final Builder add(Slot ...children) {
|
||||||
|
for (var child : children) {
|
||||||
|
this.slots.add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public final Builder add(Iterable<Slot> children) {
|
||||||
|
for (var child : children) {
|
||||||
|
this.slots.add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public final Builder add(Node child, Function<Node, Node> replacer) {
|
||||||
|
slots.add(new Slot(child, replacer));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final NodeChildren build() {
|
||||||
|
return new NodeChildren(slots.toArray(Slot[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
|
public final class Parameter {
|
||||||
|
public final Location loc;
|
||||||
|
public final String name;
|
||||||
|
public final Node node;
|
||||||
|
|
||||||
|
public Parameter(Location loc, String name, Node node) {
|
||||||
|
this.name = name;
|
||||||
|
this.node = node;
|
||||||
|
this.loc = loc;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
|
public final class Parameters {
|
||||||
|
public final int length;
|
||||||
|
public final List<Parameter> params;
|
||||||
|
public final String restName;
|
||||||
|
public final Location restLocation;
|
||||||
|
|
||||||
|
public Parameters(List<Parameter> params, String restName, Location restLocation) {
|
||||||
|
var len = params.size();
|
||||||
|
|
||||||
|
for (var i = params.size() - 1; i >= 0; i--) {
|
||||||
|
if (params.get(i).node == null) break;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.params = params;
|
||||||
|
this.length = len;
|
||||||
|
this.restName = restName;
|
||||||
|
this.restLocation = restLocation;
|
||||||
|
}
|
||||||
|
public Parameters(List<Parameter> params) {
|
||||||
|
this(params, null, null);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
|
||||||
|
|
||||||
public class ThrowSyntaxStatement extends Statement {
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.throwSyntax(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThrowSyntaxStatement(SyntaxException e) {
|
|
||||||
super(e.loc);
|
|
||||||
this.name = e.msg;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,123 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public class VariableDeclareNode extends Node {
|
||||||
|
public static class Pair {
|
||||||
|
public final String name;
|
||||||
|
public final Node value;
|
||||||
|
public final Location location;
|
||||||
|
|
||||||
|
public Pair(String name, Node value, Location location) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<Pair> values;
|
||||||
|
public final DeclarationType declType;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
if (!declType.strict) {
|
||||||
|
for (var entry : values) {
|
||||||
|
target.scope.define(new Variable(entry.name, false), entry.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
for (var entry : values) {
|
||||||
|
if (entry.name == null) continue;
|
||||||
|
if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location);
|
||||||
|
|
||||||
|
if (entry.value != null) {
|
||||||
|
FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
|
||||||
|
target.add(VariableNode.toSet(target, entry.location, entry.name, false, true));
|
||||||
|
}
|
||||||
|
else target.add(_i -> {
|
||||||
|
var i = target.scope.get(entry.name, false);
|
||||||
|
|
||||||
|
if (i == null) return Instruction.globDef(entry.name);
|
||||||
|
else return Instruction.nop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableDeclareNode(Location loc, DeclarationType declType, List<Pair> values) {
|
||||||
|
super(loc);
|
||||||
|
this.values = values;
|
||||||
|
this.declType = declType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<VariableDeclareNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var declType = JavaScript.parseDeclarationType(src, i + n);
|
||||||
|
if (!declType.isSuccess()) return declType.chainError();
|
||||||
|
n += declType.n;
|
||||||
|
|
||||||
|
var res = new ArrayList<Pair>();
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var nameLoc = src.loc(i + n);
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
|
||||||
|
n += name.n;
|
||||||
|
|
||||||
|
if (!JavaScript.checkVarName(name.result)) {
|
||||||
|
return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Node val = null;
|
||||||
|
var endN = n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "=")) {
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='");
|
||||||
|
|
||||||
|
n += valRes.n;
|
||||||
|
endN = n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
val = valRes.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.add(new Pair(name.result, val, nameLoc));
|
||||||
|
|
||||||
|
if (src.is(i + n, ",")) {
|
||||||
|
n++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = JavaScript.parseStatementEnd(src, i + endN);
|
||||||
|
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n + endN - n;
|
||||||
|
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
|
||||||
|
|
||||||
public class VariableDeclareStatement extends Statement {
|
|
||||||
public static class Pair {
|
|
||||||
public final String name;
|
|
||||||
public final Statement value;
|
|
||||||
public final Location location;
|
|
||||||
|
|
||||||
public Pair(String name, Statement value, Location location) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
this.location = location;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final List<Pair> values;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
for (var key : values) {
|
|
||||||
target.scope.define(key.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
for (var entry : values) {
|
|
||||||
if (entry.name == null) continue;
|
|
||||||
var key = target.scope.getKey(entry.name);
|
|
||||||
|
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
|
||||||
|
|
||||||
if (entry.value != null) {
|
|
||||||
FunctionStatement.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
|
|
||||||
target.add(Instruction.storeVar(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
|
||||||
super(loc);
|
|
||||||
this.values = values;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class BreakNode extends Node {
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
var res = LabelContext.getBreak(target.env).getJump();
|
||||||
|
if (res == null) {
|
||||||
|
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
||||||
|
else throw new SyntaxException(loc(), "Illegal break statement");
|
||||||
|
}
|
||||||
|
target.add(res);
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreakNode(Location loc, String label) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<BreakNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed();
|
||||||
|
n += 5;
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new BreakNode(loc, null), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var label = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
|
||||||
|
n += label.n;
|
||||||
|
|
||||||
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new BreakNode(loc, label.result), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class BreakStatement extends Statement {
|
|
||||||
public final String label;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.nop("break", label));
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public BreakStatement(Location loc, String label) {
|
|
||||||
super(loc);
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class ContinueNode extends Node {
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
var res = LabelContext.getCont(target.env).getJump();
|
||||||
|
if (res == null) {
|
||||||
|
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
||||||
|
else throw new SyntaxException(loc(), "Illegal continue statement");
|
||||||
|
}
|
||||||
|
target.add(res);
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContinueNode(Location loc, String label) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ContinueNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed();
|
||||||
|
n += 8;
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new ContinueNode(loc, null), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var label = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
|
||||||
|
n += label.n;
|
||||||
|
|
||||||
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new ContinueNode(loc, label.result), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ContinueStatement extends Statement {
|
|
||||||
public final String label;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.nop("cont", label));
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContinueStatement(Location loc, String label) {
|
|
||||||
super(loc);
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class DebugNode extends Node {
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.debug());
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugNode(Location loc) {
|
||||||
|
super(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<DebugNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed();
|
||||||
|
n += 8;
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new DebugNode(loc), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class DebugStatement extends Statement {
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.debug());
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugStatement(Location loc) {
|
|
||||||
super(loc);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,53 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||||
|
|
||||||
|
public class DeleteNode extends Node {
|
||||||
|
public final Node key;
|
||||||
|
public final Node value;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
value.compile(target, true);
|
||||||
|
key.compile(target, true);
|
||||||
|
|
||||||
|
target.add(Instruction.delete());
|
||||||
|
if (pollute) target.add(Instruction.pushValue(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<? extends Node> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed();
|
||||||
|
n += 6;
|
||||||
|
|
||||||
|
var valRes = JavaScript.parseExpression(src, i + n, 15);
|
||||||
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'");
|
||||||
|
n += valRes.n;
|
||||||
|
|
||||||
|
if (valRes.result instanceof IndexNode) {
|
||||||
|
var index = (IndexNode)valRes.result;
|
||||||
|
return ParseRes.res(new DeleteNode(loc, index.index, index.object), n);
|
||||||
|
}
|
||||||
|
else if (valRes.result instanceof VariableNode) {
|
||||||
|
return ParseRes.error(src.loc(i + n), "A variable may not be deleted");
|
||||||
|
}
|
||||||
|
else return ParseRes.res(new BoolNode(loc, true), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeleteNode(Location loc, Node key, Node value) {
|
||||||
|
super(loc);
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class DeleteStatement extends Statement {
|
|
||||||
public final Statement key;
|
|
||||||
public final Statement value;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
value.compile(target, true);
|
|
||||||
key.compile(target, true);
|
|
||||||
|
|
||||||
target.add(Instruction.delete());
|
|
||||||
if (pollute) target.add(Instruction.pushValue(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
|
||||||
super(loc);
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,84 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class DoWhileNode extends Node {
|
||||||
|
public final Node condition, body;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
body.resolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
int start = target.size();
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
var mid = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
|
mid.set(target.size());
|
||||||
|
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
int endI = target.size();
|
||||||
|
end.set(endI + 1);
|
||||||
|
|
||||||
|
target.add(Instruction.jmpIf(start - endI));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoWhileNode(Location loc, String label, Node condition, Node body) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
this.condition = condition;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<DoWhileNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += labelRes.n;
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
|
||||||
|
n += 2;
|
||||||
|
|
||||||
|
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
|
||||||
|
n += bodyRes.n;
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
||||||
|
n += 5;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var condRes = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition.");
|
||||||
|
n += condRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class DoWhileStatement extends Statement {
|
|
||||||
public final Statement condition, body;
|
|
||||||
public final String label;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
body.declare(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
int start = target.size();
|
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
int mid = target.size();
|
|
||||||
condition.compile(target, true, BreakpointType.STEP_OVER);
|
|
||||||
int end = target.size();
|
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
|
||||||
target.add(Instruction.jmpIf(start - end));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
|
|
||||||
super(loc);
|
|
||||||
this.label = label;
|
|
||||||
this.condition = condition;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,114 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public class ForInNode extends Node {
|
||||||
|
public final String varName;
|
||||||
|
public final DeclarationType declType;
|
||||||
|
public final Node object, body;
|
||||||
|
public final String label;
|
||||||
|
public final Location varLocation;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
body.resolve(target);
|
||||||
|
if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), loc());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation);
|
||||||
|
|
||||||
|
object.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
target.add(Instruction.keys(true));
|
||||||
|
|
||||||
|
int start = target.size();
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
int mid = target.temp();
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember("value")).setLocation(varLocation);
|
||||||
|
target.add(VariableNode.toSet(target, loc(), varName, pollute, declType != null && declType.strict));
|
||||||
|
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
|
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
|
int endI = target.size();
|
||||||
|
|
||||||
|
target.add(Instruction.jmp(start - endI));
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
target.set(mid, Instruction.jmpIf(endI - mid + 1));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForInNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) {
|
||||||
|
super(loc);
|
||||||
|
this.varLocation = varLocation;
|
||||||
|
this.label = label;
|
||||||
|
this.declType = declType;
|
||||||
|
this.varName = varName;
|
||||||
|
this.object = object;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ForInNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += label.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
||||||
|
n += 3;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var declType = JavaScript.parseDeclarationType(src, i + n);
|
||||||
|
n += declType.n;
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-in loop");
|
||||||
|
var nameLoc = src.loc(i + n);
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration");
|
||||||
|
n += 2;
|
||||||
|
|
||||||
|
var obj = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
|
||||||
|
n += obj.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
|
||||||
|
n += bodyRes.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new ForInNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,69 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ForInStatement extends Statement {
|
|
||||||
public final String varName;
|
|
||||||
public final boolean isDeclaration;
|
|
||||||
public final Statement varValue, object, body;
|
|
||||||
public final String label;
|
|
||||||
public final Location varLocation;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
body.declare(target);
|
|
||||||
if (isDeclaration) target.scope.define(varName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
var key = target.scope.getKey(varName);
|
|
||||||
|
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
|
||||||
|
|
||||||
if (varValue != null) {
|
|
||||||
varValue.compile(target, true);
|
|
||||||
target.add(Instruction.storeVar(target.scope.getKey(varName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
object.compile(target, true, BreakpointType.STEP_OVER);
|
|
||||||
target.add(Instruction.keys(true));
|
|
||||||
|
|
||||||
int start = target.size();
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushUndefined());
|
|
||||||
target.add(Instruction.operation(Operation.EQUALS));
|
|
||||||
int mid = target.temp();
|
|
||||||
|
|
||||||
target.add(Instruction.pushValue("value")).setLocation(varLocation);
|
|
||||||
target.add(Instruction.loadMember()).setLocation(varLocation);
|
|
||||||
target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
int end = target.size();
|
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end));
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
|
||||||
super(loc);
|
|
||||||
this.varLocation = varLocation;
|
|
||||||
this.label = label;
|
|
||||||
this.isDeclaration = isDecl;
|
|
||||||
this.varName = varName;
|
|
||||||
this.varValue = varValue;
|
|
||||||
this.object = object;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,132 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.VariableDeclareNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
|
||||||
|
|
||||||
|
public class ForNode extends Node {
|
||||||
|
public final Node declaration, assignment, condition, body;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
declaration.resolve(target);
|
||||||
|
body.resolve(target);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
var subtarget = target.subtarget();
|
||||||
|
subtarget.scope.singleEntry = false;
|
||||||
|
subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||||
|
|
||||||
|
declaration.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
int start = subtarget.size();
|
||||||
|
CompoundNode.compileMultiEntry(condition, subtarget, true, BreakpointType.STEP_OVER);
|
||||||
|
int mid = subtarget.temp();
|
||||||
|
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.pushLoop(subtarget.env, loc(), label, end, start);
|
||||||
|
CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.popLoop(subtarget.env, label);
|
||||||
|
|
||||||
|
subtarget.add(_i -> Instruction.stackRealloc(subtarget.scope.allocCount()));
|
||||||
|
|
||||||
|
CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER);
|
||||||
|
int endI = subtarget.size();
|
||||||
|
|
||||||
|
end.set(endI);
|
||||||
|
|
||||||
|
subtarget.add(Instruction.jmp(start - endI));
|
||||||
|
subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||||
|
if (pollute) subtarget.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
subtarget.scope.end();
|
||||||
|
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
this.declaration = declaration;
|
||||||
|
this.condition = condition;
|
||||||
|
this.assignment = assignment;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<Node> parseSemicolon(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ";")) return ParseRes.failed();
|
||||||
|
else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1);
|
||||||
|
}
|
||||||
|
private static ParseRes<Node> parseCondition(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
var res = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
n += res.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon");
|
||||||
|
else return ParseRes.res(res.result, n + 1);
|
||||||
|
}
|
||||||
|
private static ParseRes<? extends Node> parseUpdater(Source src, int i) {
|
||||||
|
return JavaScript.parseExpression(src, i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ForNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += labelRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
||||||
|
n += 3;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
ParseRes<Node> decl = ParseRes.first(src, i + n,
|
||||||
|
ForNode::parseSemicolon,
|
||||||
|
VariableDeclareNode::parse,
|
||||||
|
ForNode::parseCondition
|
||||||
|
);
|
||||||
|
if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression");
|
||||||
|
n += decl.n;
|
||||||
|
|
||||||
|
ParseRes<Node> cond = ParseRes.first(src, i + n,
|
||||||
|
ForNode::parseSemicolon,
|
||||||
|
ForNode::parseCondition
|
||||||
|
);
|
||||||
|
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition");
|
||||||
|
n += cond.n;
|
||||||
|
|
||||||
|
var update = parseUpdater(src, i + n);
|
||||||
|
if (update.isError()) return update.chainError();
|
||||||
|
n += update.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var body = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public class ForOfNode extends Node {
|
||||||
|
public final String varName;
|
||||||
|
public final DeclarationType declType;
|
||||||
|
public final Node iterable, body;
|
||||||
|
public final String label;
|
||||||
|
public final Location varLocation;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
body.resolve(target);
|
||||||
|
if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), varLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation);
|
||||||
|
|
||||||
|
iterable.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.loadIntrinsics("it_key"));
|
||||||
|
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
||||||
|
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
||||||
|
|
||||||
|
int start = target.size();
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.loadMember("next")).setLocation(iterable.loc());
|
||||||
|
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.loadMember("done")).setLocation(iterable.loc());
|
||||||
|
int mid = target.temp();
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember("value")).setLocation(varLocation);
|
||||||
|
target.add(VariableNode.toSet(target, varLocation, varName, false, declType != null && declType.strict));
|
||||||
|
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
|
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
|
int endI = target.size();
|
||||||
|
end.set(endI);
|
||||||
|
|
||||||
|
target.add(Instruction.jmp(start - endI));
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
target.set(mid, Instruction.jmpIf(endI - mid + 1));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForOfNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) {
|
||||||
|
super(loc);
|
||||||
|
this.varLocation = varLocation;
|
||||||
|
this.label = label;
|
||||||
|
this.declType = declType;
|
||||||
|
this.varName = varName;
|
||||||
|
this.iterable = object;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ForOfNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += label.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
||||||
|
n += 3;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var declType = JavaScript.parseDeclarationType(src, i + n);
|
||||||
|
n += declType.n;
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-of loop");
|
||||||
|
var nameLoc = src.loc(i + n);
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration");
|
||||||
|
n += 2;
|
||||||
|
|
||||||
|
var obj = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
|
||||||
|
n += obj.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body");
|
||||||
|
n += bodyRes.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new ForOfNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ForOfStatement extends Statement {
|
|
||||||
public final String varName;
|
|
||||||
public final boolean isDeclaration;
|
|
||||||
public final Statement iterable, body;
|
|
||||||
public final String label;
|
|
||||||
public final Location varLocation;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
body.declare(target);
|
|
||||||
if (isDeclaration) target.scope.define(varName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
var key = target.scope.getKey(varName);
|
|
||||||
|
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
|
||||||
|
|
||||||
iterable.compile(target, true, BreakpointType.STEP_OVER);
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.loadVar("Symbol"));
|
|
||||||
target.add(Instruction.pushValue("iterator"));
|
|
||||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
|
||||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
|
||||||
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
|
||||||
|
|
||||||
int start = target.size();
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushValue("next"));
|
|
||||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
|
||||||
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushValue("done"));
|
|
||||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
|
||||||
int mid = target.temp();
|
|
||||||
|
|
||||||
target.add(Instruction.pushValue("value"));
|
|
||||||
target.add(Instruction.loadMember()).setLocation(varLocation);
|
|
||||||
target.add(Instruction.storeVar(key)).setLocationAndDebug(iterable.loc(), BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
int end = target.size();
|
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end));
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForOfStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement object, Statement body) {
|
|
||||||
super(loc);
|
|
||||||
this.varLocation = varLocation;
|
|
||||||
this.label = label;
|
|
||||||
this.isDeclaration = isDecl;
|
|
||||||
this.varName = varName;
|
|
||||||
this.iterable = object;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ForStatement extends Statement {
|
|
||||||
public final Statement declaration, assignment, condition, body;
|
|
||||||
public final String label;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
declaration.declare(target);
|
|
||||||
body.declare(target);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
declaration.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
int start = target.size();
|
|
||||||
condition.compile(target, true, BreakpointType.STEP_OVER);
|
|
||||||
int mid = target.temp();
|
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
int beforeAssign = target.size();
|
|
||||||
assignment.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
int end = target.size();
|
|
||||||
|
|
||||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end));
|
|
||||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
|
|
||||||
super(loc);
|
|
||||||
this.label = label;
|
|
||||||
this.declaration = declaration;
|
|
||||||
this.condition = condition;
|
|
||||||
this.assignment = assignment;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,131 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class IfNode extends Node {
|
||||||
|
public final Node condition, body, elseBody;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
body.resolve(target);
|
||||||
|
if (elseBody != null) elseBody.resolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
|
||||||
|
condition.compile(target, true, breakpoint);
|
||||||
|
|
||||||
|
if (elseBody == null) {
|
||||||
|
int start = target.temp();
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
|
int endI = target.size();
|
||||||
|
end.set(endI);
|
||||||
|
|
||||||
|
target.set(start, Instruction.jmpIfNot(endI - start));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int start = target.temp();
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||||
|
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
int mid = target.temp();
|
||||||
|
|
||||||
|
elseBody.compile(target, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
|
int endI = target.size();
|
||||||
|
end.set(endI);
|
||||||
|
|
||||||
|
target.set(start, Instruction.jmpIfNot(mid - start + 1));
|
||||||
|
target.set(mid, Instruction.jmp(endI - mid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, pollute, BreakpointType.STEP_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) {
|
||||||
|
super(loc);
|
||||||
|
this.condition = condition;
|
||||||
|
this.body = body;
|
||||||
|
this.elseBody = elseBody;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<IfNode> parseTernary(Source src, int i, Node prev, int precedence) {
|
||||||
|
if (precedence > 2) return ParseRes.failed();
|
||||||
|
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "?")) return ParseRes.failed();
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var a = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator.");
|
||||||
|
n += a.n;
|
||||||
|
n += Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var b = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator.");
|
||||||
|
n += b.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n);
|
||||||
|
}
|
||||||
|
public static ParseRes<IfNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += label.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed();
|
||||||
|
n += 2;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var condRes = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition.");
|
||||||
|
n += condRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var res = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body.");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
var elseKw = Parsing.parseIdentifier(src, i + n, "else");
|
||||||
|
if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n);
|
||||||
|
n += elseKw.n;
|
||||||
|
|
||||||
|
var elseRes = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body.");
|
||||||
|
n += elseRes.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class IfStatement extends Statement {
|
|
||||||
public final Statement condition, body, elseBody;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
body.declare(target);
|
|
||||||
if (elseBody != null) elseBody.declare(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
|
|
||||||
condition.compile(target, true, breakpoint);
|
|
||||||
|
|
||||||
if (elseBody == null) {
|
|
||||||
int i = target.temp();
|
|
||||||
body.compile(target, pollute, breakpoint);
|
|
||||||
int endI = target.size();
|
|
||||||
target.set(i, Instruction.jmpIfNot(endI - i));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int start = target.temp();
|
|
||||||
body.compile(target, pollute, breakpoint);
|
|
||||||
int mid = target.temp();
|
|
||||||
elseBody.compile(target, pollute, breakpoint);
|
|
||||||
int end = target.size();
|
|
||||||
|
|
||||||
target.set(start, Instruction.jmpIfNot(mid - start + 1));
|
|
||||||
target.set(mid, Instruction.jmp(end - mid));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
|
||||||
compile(target, pollute, BreakpointType.STEP_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
|
|
||||||
super(loc);
|
|
||||||
this.condition = condition;
|
|
||||||
this.body = body;
|
|
||||||
this.elseBody = elseBody;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,50 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class ReturnNode extends Node {
|
||||||
|
public final Node value;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (value == null) target.add(Instruction.pushUndefined());
|
||||||
|
else value.compile(target, true);
|
||||||
|
target.add(Instruction.ret()).setLocation(loc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReturnNode(Location loc, Node value) {
|
||||||
|
super(loc);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ReturnNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed();
|
||||||
|
n += 6;
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new ReturnNode(loc, null), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (val.isError()) return val.chainError();
|
||||||
|
n += val.n;
|
||||||
|
|
||||||
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new ReturnNode(loc, val.result), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected end of statement or a return value");
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ReturnStatement extends Statement {
|
|
||||||
public final Statement value;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
if (value == null) target.add(Instruction.pushUndefined());
|
|
||||||
else value.compile(target, true);
|
|
||||||
target.add(Instruction.ret()).setLocation(loc());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReturnStatement(Location loc, Statement value) {
|
|
||||||
super(loc);
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,197 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class SwitchNode extends Node {
|
||||||
|
public static class SwitchCase {
|
||||||
|
public final Node value;
|
||||||
|
public final int statementI;
|
||||||
|
|
||||||
|
public SwitchCase(Node value, int statementI) {
|
||||||
|
this.value = value;
|
||||||
|
this.statementI = statementI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Node value;
|
||||||
|
public final SwitchCase[] cases;
|
||||||
|
public final Node[] body;
|
||||||
|
public final int defaultI;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
for (var stm : body) stm.resolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
var caseToStatement = new HashMap<Integer, Integer>();
|
||||||
|
var statementToIndex = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
|
value.compile(target, true, BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
var subtarget = target.subtarget();
|
||||||
|
subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||||
|
|
||||||
|
// TODO: create a jump map
|
||||||
|
for (var ccase : cases) {
|
||||||
|
subtarget.add(Instruction.dup());
|
||||||
|
ccase.value.compile(subtarget, true);
|
||||||
|
subtarget.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
caseToStatement.put(subtarget.temp(), ccase.statementI);
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = subtarget.temp();
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||||
|
for (var stm : body) {
|
||||||
|
statementToIndex.put(statementToIndex.size(), subtarget.size());
|
||||||
|
stm.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||||
|
}
|
||||||
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
|
subtarget.scope.end();
|
||||||
|
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
|
||||||
|
|
||||||
|
int endI = subtarget.size();
|
||||||
|
end.set(endI);
|
||||||
|
subtarget.add(Instruction.discard());
|
||||||
|
if (pollute) subtarget.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start));
|
||||||
|
else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
||||||
|
|
||||||
|
for (var el : caseToStatement.entrySet()) {
|
||||||
|
var i = statementToIndex.get(el.getValue());
|
||||||
|
if (i == null) i = endI;
|
||||||
|
subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
this.value = value;
|
||||||
|
this.defaultI = defaultI;
|
||||||
|
this.cases = cases;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<Node> parseSwitchCase(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
|
||||||
|
n += 4;
|
||||||
|
|
||||||
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'");
|
||||||
|
n += val.n;
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(val.result, n);
|
||||||
|
}
|
||||||
|
private static ParseRes<Void> parseDefaultCase(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed();
|
||||||
|
n += 7;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(null, n);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static ParseRes<SwitchNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += label.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed();
|
||||||
|
n += 6;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value");
|
||||||
|
n += val.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value");
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value");
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var statements = new ArrayList<Node>();
|
||||||
|
var cases = new ArrayList<SwitchCase>();
|
||||||
|
var defaultI = -1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (src.is(i + n, ";")) {
|
||||||
|
n++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseRes<Node> caseRes = ParseRes.first(src, i + n,
|
||||||
|
SwitchNode::parseDefaultCase,
|
||||||
|
SwitchNode::parseSwitchCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parsing::parseStatement
|
||||||
|
|
||||||
|
if (caseRes.isSuccess()) {
|
||||||
|
n += caseRes.n;
|
||||||
|
|
||||||
|
if (caseRes.result == null) defaultI = statements.size();
|
||||||
|
else cases.add(new SwitchCase(caseRes.result, statements.size()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (caseRes.isError()) return caseRes.chainError();
|
||||||
|
|
||||||
|
var stm = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (stm.isSuccess()) {
|
||||||
|
n += stm.n;
|
||||||
|
statements.add(stm.result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new SwitchNode(
|
||||||
|
loc, label.result, val.result, defaultI,
|
||||||
|
cases.toArray(SwitchCase[]::new),
|
||||||
|
statements.toArray(Node[]::new)
|
||||||
|
), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,83 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.Type;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class SwitchStatement extends Statement {
|
|
||||||
public static class SwitchCase {
|
|
||||||
public final Statement value;
|
|
||||||
public final int statementI;
|
|
||||||
|
|
||||||
public SwitchCase(Statement value, int statementI) {
|
|
||||||
this.value = value;
|
|
||||||
this.statementI = statementI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Statement value;
|
|
||||||
public final SwitchCase[] cases;
|
|
||||||
public final Statement[] body;
|
|
||||||
public final int defaultI;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
for (var stm : body) stm.declare(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
var caseToStatement = new HashMap<Integer, Integer>();
|
|
||||||
var statementToIndex = new HashMap<Integer, Integer>();
|
|
||||||
|
|
||||||
value.compile(target, true, BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
for (var ccase : cases) {
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
ccase.value.compile(target, true);
|
|
||||||
target.add(Instruction.operation(Operation.EQUALS));
|
|
||||||
caseToStatement.put(target.temp(), ccase.statementI);
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = target.temp();
|
|
||||||
|
|
||||||
for (var stm : body) {
|
|
||||||
statementToIndex.put(statementToIndex.size(), target.size());
|
|
||||||
stm.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
}
|
|
||||||
|
|
||||||
int end = target.size();
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
|
|
||||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start));
|
|
||||||
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
|
||||||
|
|
||||||
for (int i = start; i < end; i++) {
|
|
||||||
var instr = target.get(i);
|
|
||||||
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
|
|
||||||
target.set(i, Instruction.jmp(end - i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var el : caseToStatement.entrySet()) {
|
|
||||||
var i = statementToIndex.get(el.getValue());
|
|
||||||
if (i == null) i = end;
|
|
||||||
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
|
|
||||||
super(loc);
|
|
||||||
this.value = value;
|
|
||||||
this.defaultI = defaultI;
|
|
||||||
this.cases = cases;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,49 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class ThrowNode extends Node {
|
||||||
|
public final Node value;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
value.compile(target, true);
|
||||||
|
target.add(Instruction.throwInstr()).setLocation(loc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThrowNode(Location loc, Node value) {
|
||||||
|
super(loc);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ThrowNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed();
|
||||||
|
n += 5;
|
||||||
|
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new ThrowNode(loc, null), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value");
|
||||||
|
n += val.n;
|
||||||
|
|
||||||
|
end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) {
|
||||||
|
n += end.n;
|
||||||
|
return ParseRes.res(new ThrowNode(loc, val.result), n);
|
||||||
|
}
|
||||||
|
else return end.chainError(src.loc(i + n), "Expected end of statement");
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ThrowStatement extends Statement {
|
|
||||||
public final Statement value;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
value.compile(target, true);
|
|
||||||
target.add(Instruction.throwInstr()).setLocation(loc());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThrowStatement(Location loc, Statement value) {
|
|
||||||
super(loc);
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,135 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
|
||||||
|
public class TryNode extends Node {
|
||||||
|
public final CompoundNode tryBody;
|
||||||
|
public final CompoundNode catchBody;
|
||||||
|
public final CompoundNode finallyBody;
|
||||||
|
public final String captureName;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
tryBody.resolve(target);
|
||||||
|
catchBody.resolve(target);
|
||||||
|
finallyBody.resolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
|
||||||
|
int replace = target.temp();
|
||||||
|
var endSuppl = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
int start = replace + 1, catchStart = -1, finallyStart = -1;
|
||||||
|
|
||||||
|
LabelContext.getBreak(target.env).push(loc(), label, endSuppl);
|
||||||
|
|
||||||
|
tryBody.compile(target, false);
|
||||||
|
target.add(Instruction.tryEnd());
|
||||||
|
|
||||||
|
if (catchBody != null) {
|
||||||
|
catchStart = target.size() - start;
|
||||||
|
|
||||||
|
if (captureName != null) {
|
||||||
|
var subtarget = target.subtarget();
|
||||||
|
subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc());
|
||||||
|
catchBody.compile(subtarget, false);
|
||||||
|
subtarget.scope.end();
|
||||||
|
}
|
||||||
|
else catchBody.compile(target, false);
|
||||||
|
|
||||||
|
target.add(Instruction.tryEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finallyBody != null) {
|
||||||
|
finallyStart = target.size() - start;
|
||||||
|
finallyBody.compile(target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelContext.getBreak(target.env).pop(label);
|
||||||
|
|
||||||
|
endSuppl.set(target.size());
|
||||||
|
|
||||||
|
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
|
||||||
|
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
|
||||||
|
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) {
|
||||||
|
super(loc);
|
||||||
|
this.tryBody = tryBody;
|
||||||
|
this.catchBody = catchBody;
|
||||||
|
this.finallyBody = finallyBody;
|
||||||
|
this.captureName = captureName;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<TryNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += labelRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed();
|
||||||
|
n += 3;
|
||||||
|
|
||||||
|
var tryBody = CompoundNode.parse(src, i + n);
|
||||||
|
if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body");
|
||||||
|
n += tryBody.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
String capture = null;
|
||||||
|
CompoundNode catchBody = null, finallyBody = null;
|
||||||
|
|
||||||
|
if (Parsing.isIdentifier(src, i + n, "catch")) {
|
||||||
|
n += 5;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
if (src.is(i + n, "(")) {
|
||||||
|
n++;
|
||||||
|
var nameRes = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
|
||||||
|
capture = nameRes.result;
|
||||||
|
n += nameRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name");
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyRes = CompoundNode.parse(src, i + n);
|
||||||
|
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body");
|
||||||
|
n += bodyRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
catchBody = bodyRes.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Parsing.isIdentifier(src, i + n, "finally")) {
|
||||||
|
n += 7;
|
||||||
|
|
||||||
|
var bodyRes = CompoundNode.parse(src, i + n);
|
||||||
|
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body");
|
||||||
|
n += bodyRes.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
finallyBody = bodyRes.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally");
|
||||||
|
|
||||||
|
return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class TryStatement extends Statement {
|
|
||||||
public final Statement tryBody;
|
|
||||||
public final Statement catchBody;
|
|
||||||
public final Statement finallyBody;
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
tryBody.declare(target);
|
|
||||||
if (catchBody != null) catchBody.declare(target);
|
|
||||||
if (finallyBody != null) finallyBody.declare(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
|
|
||||||
int replace = target.temp();
|
|
||||||
|
|
||||||
int start = replace + 1, catchStart = -1, finallyStart = -1;
|
|
||||||
|
|
||||||
tryBody.compile(target, false);
|
|
||||||
target.add(Instruction.tryEnd());
|
|
||||||
|
|
||||||
if (catchBody != null) {
|
|
||||||
catchStart = target.size() - start;
|
|
||||||
target.scope.define(name, true);
|
|
||||||
catchBody.compile(target, false);
|
|
||||||
target.scope.undefine();
|
|
||||||
target.add(Instruction.tryEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finallyBody != null) {
|
|
||||||
finallyStart = target.size() - start;
|
|
||||||
finallyBody.compile(target, false);
|
|
||||||
target.add(Instruction.tryEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
|
|
||||||
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
|
||||||
super(loc);
|
|
||||||
this.tryBody = tryBody;
|
|
||||||
this.catchBody = catchBody;
|
|
||||||
this.finallyBody = finallyBody;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.control;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class WhileNode extends Node {
|
||||||
|
public final Node condition, body;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
@Override public void resolve(CompileResult target) {
|
||||||
|
body.resolve(target);
|
||||||
|
}
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
int start = target.size();
|
||||||
|
condition.compile(target, true);
|
||||||
|
int mid = target.temp();
|
||||||
|
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
|
||||||
|
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||||
|
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
|
||||||
|
LabelContext.popLoop(target.env, label);
|
||||||
|
|
||||||
|
var endI = target.size();
|
||||||
|
end.set(endI + 1);
|
||||||
|
|
||||||
|
target.add(Instruction.jmp(start - end.getAsInt()));
|
||||||
|
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1));
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhileNode(Location loc, String label, Node condition, Node body) {
|
||||||
|
super(loc);
|
||||||
|
this.label = label;
|
||||||
|
this.condition = condition;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<WhileNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var label = JavaScript.parseLabel(src, i + n);
|
||||||
|
n += label.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
||||||
|
n += 5;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var cond = JavaScript.parseExpression(src, i + n, 0);
|
||||||
|
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition.");
|
||||||
|
n += cond.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition.");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var body = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body.");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.control;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.Type;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class WhileStatement extends Statement {
|
|
||||||
public final Statement condition, body;
|
|
||||||
public final String label;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
body.declare(target);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
int start = target.size();
|
|
||||||
condition.compile(target, true);
|
|
||||||
int mid = target.temp();
|
|
||||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
|
||||||
|
|
||||||
int end = target.size();
|
|
||||||
|
|
||||||
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
|
||||||
|
|
||||||
target.add(Instruction.jmp(start - end));
|
|
||||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
|
|
||||||
super(loc);
|
|
||||||
this.label = label;
|
|
||||||
this.condition = condition;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) {
|
|
||||||
for (int i = start; i < end; i++) {
|
|
||||||
var instr = target.get(i);
|
|
||||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
|
||||||
target.set(i, Instruction.jmp(continuePoint - i));
|
|
||||||
}
|
|
||||||
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
|
||||||
target.set(i, Instruction.jmp(breakPoint - i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.parsing;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
|
|
||||||
public enum Operator {
|
|
||||||
MULTIPLY("*", Operation.MULTIPLY, 13),
|
|
||||||
DIVIDE("/", Operation.DIVIDE, 12),
|
|
||||||
MODULO("%", Operation.MODULO, 12),
|
|
||||||
SUBTRACT("-", Operation.SUBTRACT, 11),
|
|
||||||
ADD("+", Operation.ADD, 11),
|
|
||||||
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
|
|
||||||
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
|
|
||||||
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
|
|
||||||
GREATER(">", Operation.GREATER, 9),
|
|
||||||
LESS("<", Operation.LESS, 9),
|
|
||||||
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
|
|
||||||
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
|
|
||||||
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
|
|
||||||
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
|
|
||||||
EQUALS("==", Operation.LOOSE_EQUALS, 8),
|
|
||||||
LOOSE_EQUALS("===", Operation.EQUALS, 8),
|
|
||||||
AND("&", Operation.AND, 7),
|
|
||||||
XOR("^", Operation.XOR, 6),
|
|
||||||
OR("|", Operation.OR, 5),
|
|
||||||
LAZY_AND("&&", 4),
|
|
||||||
LAZY_OR("||", 3),
|
|
||||||
ASSIGN_SHIFT_LEFT("<<=", 2, true),
|
|
||||||
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
|
|
||||||
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
|
|
||||||
ASSIGN_AND("&=", 2, true),
|
|
||||||
ASSIGN_OR("|=", 2, true),
|
|
||||||
ASSIGN_XOR("^=", 2, true),
|
|
||||||
ASSIGN_MODULO("%=", 2, true),
|
|
||||||
ASSIGN_DIVIDE("/=", 2, true),
|
|
||||||
ASSIGN_MULTIPLY("*=", 2, true),
|
|
||||||
ASSIGN_SUBTRACT("-=", 2, true),
|
|
||||||
ASSIGN_ADD("+=", 2, true),
|
|
||||||
ASSIGN("=", 2, true),
|
|
||||||
SEMICOLON(";"),
|
|
||||||
COLON(":"),
|
|
||||||
PAREN_OPEN("("),
|
|
||||||
PAREN_CLOSE(")"),
|
|
||||||
BRACKET_OPEN("["),
|
|
||||||
BRACKET_CLOSE("]"),
|
|
||||||
BRACE_OPEN("{"),
|
|
||||||
BRACE_CLOSE("}"),
|
|
||||||
DOT("."),
|
|
||||||
COMMA(","),
|
|
||||||
NOT("!"),
|
|
||||||
QUESTION("?"),
|
|
||||||
INVERSE("~"),
|
|
||||||
INCREASE("++"),
|
|
||||||
DECREASE("--");
|
|
||||||
|
|
||||||
public final String readable;
|
|
||||||
public final Operation operation;
|
|
||||||
public final int precedence;
|
|
||||||
public final boolean reverse;
|
|
||||||
private static final Map<String, Operator> ops = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (var el : Operator.values()) {
|
|
||||||
ops.put(el.readable, el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAssign() { return precedence == 2; }
|
|
||||||
|
|
||||||
public static Operator parse(String val) {
|
|
||||||
return ops.get(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Operator() {
|
|
||||||
this.readable = null;
|
|
||||||
this.operation = null;
|
|
||||||
this.precedence = -1;
|
|
||||||
this.reverse = false;
|
|
||||||
}
|
|
||||||
private Operator(String value) {
|
|
||||||
this.readable = value;
|
|
||||||
this.operation = null;
|
|
||||||
this.precedence = -1;
|
|
||||||
this.reverse = false;
|
|
||||||
}
|
|
||||||
private Operator(String value, int precedence) {
|
|
||||||
this.readable = value;
|
|
||||||
this.operation = null;
|
|
||||||
this.precedence = precedence;
|
|
||||||
this.reverse = false;
|
|
||||||
}
|
|
||||||
private Operator(String value, int precedence, boolean reverse) {
|
|
||||||
this.readable = value;
|
|
||||||
this.operation = null;
|
|
||||||
this.precedence = precedence;
|
|
||||||
this.reverse = reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Operator(String value, Operation funcName, int precedence) {
|
|
||||||
this.readable = value;
|
|
||||||
this.operation = funcName;
|
|
||||||
this.precedence = precedence;
|
|
||||||
this.reverse = false;
|
|
||||||
}
|
|
||||||
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
|
|
||||||
this.readable = value;
|
|
||||||
this.operation = funcName;
|
|
||||||
this.precedence = precedence;
|
|
||||||
this.reverse = reverse;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.parsing;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser;
|
|
||||||
|
|
||||||
public class ParseRes<T> {
|
|
||||||
public static enum State {
|
|
||||||
SUCCESS,
|
|
||||||
FAILED,
|
|
||||||
ERROR;
|
|
||||||
|
|
||||||
public boolean isSuccess() { return this == SUCCESS; }
|
|
||||||
public boolean isFailed() { return this == FAILED; }
|
|
||||||
public boolean isError() { return this == ERROR; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public final ParseRes.State state;
|
|
||||||
public final String error;
|
|
||||||
public final T result;
|
|
||||||
public final int n;
|
|
||||||
|
|
||||||
private ParseRes(ParseRes.State state, String error, T result, int readN) {
|
|
||||||
this.result = result;
|
|
||||||
this.n = readN;
|
|
||||||
this.state = state;
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParseRes<T> setN(int i) {
|
|
||||||
if (!state.isSuccess()) return this;
|
|
||||||
return new ParseRes<>(state, null, result, i);
|
|
||||||
}
|
|
||||||
public ParseRes<T> addN(int i) {
|
|
||||||
if (!state.isSuccess()) return this;
|
|
||||||
return new ParseRes<>(state, null, result, this.n + i);
|
|
||||||
}
|
|
||||||
public <T2> ParseRes<T2> transform() {
|
|
||||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
|
||||||
return new ParseRes<>(state, error, null, 0);
|
|
||||||
}
|
|
||||||
public TestRes toTest() {
|
|
||||||
if (isSuccess()) return TestRes.res(n);
|
|
||||||
else if (isError()) return TestRes.error(null, error);
|
|
||||||
else return TestRes.failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSuccess() { return state.isSuccess(); }
|
|
||||||
public boolean isFailed() { return state.isFailed(); }
|
|
||||||
public boolean isError() { return state.isError(); }
|
|
||||||
|
|
||||||
public static <T> ParseRes<T> failed() {
|
|
||||||
return new ParseRes<T>(State.FAILED, null, null, 0);
|
|
||||||
}
|
|
||||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
|
||||||
if (loc != null) error = loc + ": " + error;
|
|
||||||
return new ParseRes<>(State.ERROR, error, null, 0);
|
|
||||||
}
|
|
||||||
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
|
|
||||||
if (loc != null) error = loc + ": " + error;
|
|
||||||
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
|
|
||||||
return new ParseRes<>(State.ERROR, other.error, null, 0);
|
|
||||||
}
|
|
||||||
public static <T> ParseRes<T> res(T val, int i) {
|
|
||||||
return new ParseRes<>(State.SUCCESS, null, val, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
|
|
||||||
return any(List.of(parsers));
|
|
||||||
}
|
|
||||||
public static <T> ParseRes<? extends T> any(List<ParseRes<? extends T>> parsers) {
|
|
||||||
ParseRes<? extends T> best = null;
|
|
||||||
ParseRes<? extends T> error = ParseRes.failed();
|
|
||||||
|
|
||||||
for (var parser : parsers) {
|
|
||||||
if (parser.isSuccess()) {
|
|
||||||
if (best == null || best.n < parser.n) best = parser;
|
|
||||||
}
|
|
||||||
else if (parser.isError() && error.isFailed()) error = parser.transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (best != null) return best;
|
|
||||||
else return error;
|
|
||||||
}
|
|
||||||
@SafeVarargs
|
|
||||||
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
|
|
||||||
ParseRes<? extends T> error = ParseRes.failed();
|
|
||||||
|
|
||||||
for (var parser : parsers) {
|
|
||||||
var res = parser.parse(null, tokens, 0);
|
|
||||||
if (res.isSuccess()) return res;
|
|
||||||
else if (res.isError() && error.isFailed()) error = res.transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.parsing;
|
|
||||||
|
|
||||||
public class RawToken {
|
|
||||||
public final String value;
|
|
||||||
public final TokenType type;
|
|
||||||
public final int line;
|
|
||||||
public final int start;
|
|
||||||
|
|
||||||
public RawToken(String value, TokenType type, int line, int start) {
|
|
||||||
this.value = value;
|
|
||||||
this.type = type;
|
|
||||||
this.line = line;
|
|
||||||
this.start = start;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.parsing;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
|
|
||||||
|
|
||||||
public class TestRes {
|
|
||||||
public final State state;
|
|
||||||
public final String error;
|
|
||||||
public final int i;
|
|
||||||
|
|
||||||
private TestRes(ParseRes.State state, String error, int i) {
|
|
||||||
this.i = i;
|
|
||||||
this.state = state;
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestRes add(int n) {
|
|
||||||
return new TestRes(state, null, this.i + n);
|
|
||||||
}
|
|
||||||
public <T> ParseRes<T> transform() {
|
|
||||||
if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed.");
|
|
||||||
else if (isError()) return ParseRes.error(null, error);
|
|
||||||
else return ParseRes.failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSuccess() { return state.isSuccess(); }
|
|
||||||
public boolean isFailed() { return state.isFailed(); }
|
|
||||||
public boolean isError() { return state.isError(); }
|
|
||||||
|
|
||||||
public static TestRes failed() {
|
|
||||||
return new TestRes(State.FAILED, null, 0);
|
|
||||||
}
|
|
||||||
public static TestRes error(Location loc, String error) {
|
|
||||||
if (loc != null) error = loc + ": " + error;
|
|
||||||
return new TestRes(State.ERROR, error, 0);
|
|
||||||
}
|
|
||||||
public static TestRes error(Location loc, String error, TestRes other) {
|
|
||||||
if (loc != null) error = loc + ": " + error;
|
|
||||||
if (!other.isError()) return new TestRes(State.ERROR, error, 0);
|
|
||||||
return new TestRes(State.ERROR, other.error, 0);
|
|
||||||
}
|
|
||||||
public static TestRes res(int i) {
|
|
||||||
return new TestRes(State.SUCCESS, null, i);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.parsing;
|
|
||||||
|
|
||||||
public class Token {
|
|
||||||
public final Object value;
|
|
||||||
public final String rawValue;
|
|
||||||
public final boolean isString;
|
|
||||||
public final boolean isRegex;
|
|
||||||
public final int line;
|
|
||||||
public final int start;
|
|
||||||
|
|
||||||
private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) {
|
|
||||||
this.value = value;
|
|
||||||
this.rawValue = rawValue;
|
|
||||||
this.line = line;
|
|
||||||
this.start = start;
|
|
||||||
this.isString = isString;
|
|
||||||
this.isRegex = isRegex;
|
|
||||||
}
|
|
||||||
private Token(int line, int start, Object value, String rawValue) {
|
|
||||||
this.value = value;
|
|
||||||
this.rawValue = rawValue;
|
|
||||||
this.line = line;
|
|
||||||
this.start = start;
|
|
||||||
this.isString = false;
|
|
||||||
this.isRegex = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isString() { return isString; }
|
|
||||||
public boolean isRegex() { return isRegex; }
|
|
||||||
public boolean isNumber() { return value instanceof Number; }
|
|
||||||
public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; }
|
|
||||||
public boolean isOperator() { return value instanceof Operator; }
|
|
||||||
|
|
||||||
public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); }
|
|
||||||
public boolean isOperator(Operator op) { return value.equals(op); }
|
|
||||||
|
|
||||||
public String string() { return (String)value; }
|
|
||||||
public String regex() { return (String)value; }
|
|
||||||
public double number() { return (double)value; }
|
|
||||||
public String identifier() { return (String)value; }
|
|
||||||
public Operator operator() { return (Operator)value; }
|
|
||||||
|
|
||||||
public static Token regex(int line, int start, String val, String rawValue) {
|
|
||||||
return new Token(line, start, val, rawValue, false, true);
|
|
||||||
}
|
|
||||||
public static Token string(int line, int start, String val, String rawValue) {
|
|
||||||
return new Token(line, start, val, rawValue, true, false);
|
|
||||||
}
|
|
||||||
public static Token number(int line, int start, double val, String rawValue) {
|
|
||||||
return new Token(line, start, val, rawValue);
|
|
||||||
}
|
|
||||||
public static Token identifier(int line, int start, String val) {
|
|
||||||
return new Token(line, start, val, val);
|
|
||||||
}
|
|
||||||
public static Token operator(int line, int start, Operator val) {
|
|
||||||
return new Token(line, start, val, val.readable);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.parsing;
|
|
||||||
|
|
||||||
enum TokenType {
|
|
||||||
REGEX,
|
|
||||||
STRING,
|
|
||||||
NUMBER,
|
|
||||||
LITERAL,
|
|
||||||
OPERATOR,
|
|
||||||
}
|
|
@ -0,0 +1,139 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.scope;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class FunctionScope extends Scope {
|
||||||
|
private final VariableList captures = new VariableList().setIndexMap(v -> ~v);
|
||||||
|
private final VariableList specials = new VariableList();
|
||||||
|
private final VariableList locals = new VariableList(specials);
|
||||||
|
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
|
||||||
|
private final HashSet<String> blacklistNames = new HashSet<>();
|
||||||
|
|
||||||
|
private final Scope captureParent;
|
||||||
|
|
||||||
|
public final boolean passtrough;
|
||||||
|
|
||||||
|
private void removeCapture(String name) {
|
||||||
|
var res = captures.remove(name);
|
||||||
|
if (res != null) {
|
||||||
|
childToParent.remove(res);
|
||||||
|
res.setIndexSupplier(() -> { throw new SyntaxException(null, res.name + " has been shadowed"); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Variable define(Variable var, Location loc) {
|
||||||
|
checkNotEnded();
|
||||||
|
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||||
|
|
||||||
|
if (passtrough) {
|
||||||
|
blacklistNames.add(var.name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCapture(var.name);
|
||||||
|
return locals.add(var);
|
||||||
|
}
|
||||||
|
@Override public Variable defineStrict(Variable var, Location loc) {
|
||||||
|
checkNotEnded();
|
||||||
|
if (locals.has(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||||
|
if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||||
|
|
||||||
|
var res = super.defineStrict(var, loc);
|
||||||
|
removeCapture(var.name);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public Variable defineSpecial(Variable var, Location loc) {
|
||||||
|
return specials.add(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean flattenVariable(Variable variable, boolean capturable) {
|
||||||
|
// if (!ended()) throw new IllegalStateException("Tried to flatten a variable before the scope has ended");
|
||||||
|
this.locals.overlay(variable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Variable get(String name, boolean capture) {
|
||||||
|
var superRes = super.get(name, capture);
|
||||||
|
if (superRes != null) return superRes;
|
||||||
|
|
||||||
|
if (specials.has(name)) return addCaptured(specials.get(name), capture);
|
||||||
|
if (locals.has(name)) return addCaptured(locals.get(name), capture);
|
||||||
|
if (captures.has(name)) return addCaptured(captures.get(name), capture);
|
||||||
|
|
||||||
|
if (captureParent == null) return null;
|
||||||
|
|
||||||
|
var parentVar = captureParent.get(name, true);
|
||||||
|
if (parentVar == null) return null;
|
||||||
|
|
||||||
|
var childVar = captures.add(parentVar.clone());
|
||||||
|
|
||||||
|
childToParent.put(childVar, parentVar);
|
||||||
|
|
||||||
|
return childVar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean has(String name, boolean capture) {
|
||||||
|
if (specials.has(name)) return true;
|
||||||
|
if (locals.has(name)) return true;
|
||||||
|
|
||||||
|
if (capture) {
|
||||||
|
if (captures.has(name)) return true;
|
||||||
|
if (captureParent != null) return captureParent.has(name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean finish() {
|
||||||
|
if (!super.finish()) return false;
|
||||||
|
|
||||||
|
captures.freeze();
|
||||||
|
locals.freeze();
|
||||||
|
specials.freeze();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int allocCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@Override public int capturesCount() {
|
||||||
|
return captures.size();
|
||||||
|
}
|
||||||
|
@Override public int localsCount() {
|
||||||
|
return locals.size() + specials.size() + super.allocCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int offset() {
|
||||||
|
return specials.size() + locals.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getCaptureIndices() {
|
||||||
|
var res = new int[captures.size()];
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (var el : captures.all()) {
|
||||||
|
assert childToParent.containsKey(el);
|
||||||
|
res[i] = childToParent.get(el).index();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionScope(Scope parent) {
|
||||||
|
super();
|
||||||
|
if (parent.finished()) throw new RuntimeException("Parent is finished");
|
||||||
|
this.captureParent = parent;
|
||||||
|
this.passtrough = false;
|
||||||
|
}
|
||||||
|
public FunctionScope(boolean passtrough) {
|
||||||
|
super();
|
||||||
|
this.captureParent = null;
|
||||||
|
this.passtrough = passtrough;
|
||||||
|
}
|
||||||
|
}
|
@ -1,77 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.scope;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class LocalScopeRecord implements ScopeRecord {
|
|
||||||
public final LocalScopeRecord parent;
|
|
||||||
|
|
||||||
private final ArrayList<String> captures = new ArrayList<>();
|
|
||||||
private final ArrayList<String> locals = new ArrayList<>();
|
|
||||||
|
|
||||||
public String[] captures() {
|
|
||||||
return captures.toArray(String[]::new);
|
|
||||||
}
|
|
||||||
public String[] locals() {
|
|
||||||
return locals.toArray(String[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalScopeRecord child() {
|
|
||||||
return new LocalScopeRecord(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int localsCount() {
|
|
||||||
return locals.size();
|
|
||||||
}
|
|
||||||
public int capturesCount() {
|
|
||||||
return captures.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getCaptures() {
|
|
||||||
var buff = new int[captures.size()];
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
for (var name : captures) {
|
|
||||||
var index = parent.getKey(name);
|
|
||||||
if (index instanceof Integer) buff[i++] = (int)index;
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = new int[i];
|
|
||||||
System.arraycopy(buff, 0, res, 0, i);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getKey(String name) {
|
|
||||||
var capI = captures.indexOf(name);
|
|
||||||
var locI = locals.lastIndexOf(name);
|
|
||||||
if (locI >= 0) return locI;
|
|
||||||
if (capI >= 0) return ~capI;
|
|
||||||
if (parent != null) {
|
|
||||||
var res = parent.getKey(name);
|
|
||||||
if (res != null && res instanceof Integer) {
|
|
||||||
captures.add(name);
|
|
||||||
return -captures.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
public Object define(String name, boolean force) {
|
|
||||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
|
||||||
locals.add(name);
|
|
||||||
return locals.size() - 1;
|
|
||||||
}
|
|
||||||
public Object define(String name) {
|
|
||||||
return define(name, false);
|
|
||||||
}
|
|
||||||
public void undefine() {
|
|
||||||
locals.remove(locals.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalScopeRecord() {
|
|
||||||
this.parent = null;
|
|
||||||
}
|
|
||||||
public LocalScopeRecord(LocalScopeRecord parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
}
|
|
197
src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java
Normal file
197
src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.scope;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class Scope {
|
||||||
|
protected final VariableList variables = new VariableList(this::parentOffset);
|
||||||
|
|
||||||
|
private boolean ended = false;
|
||||||
|
private boolean finished = false;
|
||||||
|
private Scope child;
|
||||||
|
private List<Scope> prevChildren = new LinkedList<>();
|
||||||
|
|
||||||
|
public final Scope parent;
|
||||||
|
public final HashSet<Variable> captured = new HashSet<>();
|
||||||
|
|
||||||
|
protected final Variable addCaptured(Variable var, boolean captured) {
|
||||||
|
if (captured) this.captured.add(var);
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wether or not the scope is going to be entered multiple times.
|
||||||
|
* If set to true, captured variables will be kept as allocations, otherwise will be converted to locals
|
||||||
|
*/
|
||||||
|
public boolean singleEntry = true;
|
||||||
|
|
||||||
|
private final int parentOffset() {
|
||||||
|
if (parent != null) return parent.offset();
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
|
||||||
|
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws if the scope is ended
|
||||||
|
*/
|
||||||
|
protected final void checkNotEnded() {
|
||||||
|
if (ended) throw new IllegalStateException("Cannot define in an ended scope");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an ES5-style variable
|
||||||
|
*
|
||||||
|
* @returns The index supplier of the variable if it is a local, or null if it is a global
|
||||||
|
* @throws SyntaxException If an ES2015-style variable with the same name exists anywhere from the current function to the current scope
|
||||||
|
* @throws RuntimeException If the scope is finalized or has an active child
|
||||||
|
*/
|
||||||
|
public Variable define(Variable var, Location loc) {
|
||||||
|
checkNotEnded();
|
||||||
|
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||||
|
if (parent != null) return parent.define(var, loc);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an ES2015-style variable
|
||||||
|
* @param readonly True if const, false if let
|
||||||
|
* @return The index supplier of the variable
|
||||||
|
* @throws SyntaxException If any variable with the same name exists in the current scope
|
||||||
|
* @throws RuntimeException If the scope is finalized or has an active child
|
||||||
|
*/
|
||||||
|
public Variable defineStrict(Variable var, Location loc) {
|
||||||
|
checkNotEnded();
|
||||||
|
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||||
|
|
||||||
|
variables.add(var);
|
||||||
|
return var.setIndexSupplier(() -> variables.indexOfKey(var.name));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets the index supplier of the given variable name, or null if it is a global
|
||||||
|
*
|
||||||
|
* @param capture If true, the variable is being captured by a function
|
||||||
|
*/
|
||||||
|
public Variable get(String name, boolean capture) {
|
||||||
|
var res = variables.get(name);
|
||||||
|
if (res != null) return addCaptured(res, capture);
|
||||||
|
if (parent != null) return parent.get(name, capture);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if the given variable name is accessible
|
||||||
|
*
|
||||||
|
* @param capture If true, will check beyond this function's scope
|
||||||
|
*/
|
||||||
|
public boolean has(String name, boolean capture) {
|
||||||
|
if (variables.has(name)) return true;
|
||||||
|
if (parent != null) return parent.has(name, capture);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets the index offset from this scope to its children
|
||||||
|
*/
|
||||||
|
public int offset() {
|
||||||
|
if (parent != null) return parent.offset() + variables.size();
|
||||||
|
else return variables.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds this variable to the current function's locals record. Capturable indicates whether or not the variable
|
||||||
|
* should still be capturable, or be put in an array (still not implemented)
|
||||||
|
*
|
||||||
|
* @return Whether or not the request was actually fuliflled
|
||||||
|
*/
|
||||||
|
public boolean flattenVariable(Variable variable, boolean capturable) {
|
||||||
|
if (singleEntry || !capturable) {
|
||||||
|
if (parent == null) return false;
|
||||||
|
return parent.flattenVariable(variable, capturable);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
variables.overlay(variable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int localsCount() { return 0; }
|
||||||
|
public int capturesCount() { return 0; }
|
||||||
|
public int allocCount() { return variables.size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends this scope. This will make it possible for another child to take its place
|
||||||
|
*/
|
||||||
|
public boolean end() {
|
||||||
|
if (ended) return false;
|
||||||
|
|
||||||
|
this.ended = true;
|
||||||
|
|
||||||
|
if (this.parent != null) {
|
||||||
|
assert this.parent.child == this;
|
||||||
|
this.parent.child = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalizes this scope. The scope will become immutable after this call
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean finish() {
|
||||||
|
if (finished) return false;
|
||||||
|
if (parent != null && parent.finished) throw new IllegalStateException("Tried to finish a child after the parent was finished");
|
||||||
|
|
||||||
|
for (var child : prevChildren) child.finish();
|
||||||
|
|
||||||
|
var captured = new HashSet<Variable>();
|
||||||
|
var normal = new HashSet<Variable>();
|
||||||
|
|
||||||
|
for (var v : variables.all()) {
|
||||||
|
if (this.captured.contains(v)) {
|
||||||
|
if (singleEntry) captured.add(v);
|
||||||
|
}
|
||||||
|
else normal.add(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var v : captured) variables.remove(v);
|
||||||
|
for (var v : normal) variables.remove(v);
|
||||||
|
|
||||||
|
for (var v : captured) flattenVariable(v, true);
|
||||||
|
for (var v : normal) flattenVariable(v, false);
|
||||||
|
|
||||||
|
|
||||||
|
this.variables.freeze();
|
||||||
|
this.finished = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean ended() { return ended; }
|
||||||
|
public final boolean finished() { return finished; }
|
||||||
|
public final Scope child() { return child; }
|
||||||
|
|
||||||
|
public Scope() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
public Scope(Scope parent) {
|
||||||
|
if (parent != null) {
|
||||||
|
if (parent.ended) throw new RuntimeException("Parent is not active");
|
||||||
|
if (parent.finished) throw new RuntimeException("Parent is finished");
|
||||||
|
if (parent.child != null) throw new RuntimeException("Parent has an active child");
|
||||||
|
|
||||||
|
this.parent = parent;
|
||||||
|
this.parent.child = this;
|
||||||
|
this.parent.prevChildren.add(this);
|
||||||
|
}
|
||||||
|
else this.parent = null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.scope;
|
|
||||||
|
|
||||||
public interface ScopeRecord {
|
|
||||||
public Object getKey(String name);
|
|
||||||
public Object define(String name);
|
|
||||||
public LocalScopeRecord child();
|
|
||||||
}
|
|
@ -0,0 +1,41 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.scope;
|
||||||
|
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
public final class Variable {
|
||||||
|
private IntSupplier indexSupplier;
|
||||||
|
private boolean frozen;
|
||||||
|
|
||||||
|
public final boolean readonly;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public final int index() {
|
||||||
|
if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized");
|
||||||
|
return indexSupplier.getAsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void freeze() {
|
||||||
|
this.frozen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Variable setIndexSupplier(IntSupplier index) {
|
||||||
|
this.indexSupplier = index;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public final IntSupplier indexSupplier() {
|
||||||
|
return indexSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Variable clone() {
|
||||||
|
return new Variable(name, readonly).setIndexSupplier(indexSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable(String name, boolean readonly) {
|
||||||
|
this.name = name;
|
||||||
|
this.readonly = readonly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Variable of(String name, boolean readonly, int i) {
|
||||||
|
return new Variable(name, readonly).setIndexSupplier(() -> i);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,233 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.scope;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
import java.util.function.IntUnaryOperator;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
public final class VariableList {
|
||||||
|
private final class Node implements IntSupplier {
|
||||||
|
public Variable var;
|
||||||
|
public Node next;
|
||||||
|
public Node prev;
|
||||||
|
public boolean frozen;
|
||||||
|
public int index;
|
||||||
|
|
||||||
|
@Override public int getAsInt() {
|
||||||
|
if (frozen) {
|
||||||
|
if (offset == null) {
|
||||||
|
return indexConverter == null ? index : indexConverter.applyAsInt(index);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return indexConverter == null ?
|
||||||
|
index + offset.getAsInt() :
|
||||||
|
indexConverter.applyAsInt(index + offset.getAsInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = 0;
|
||||||
|
if (offset != null) res = offset.getAsInt();
|
||||||
|
|
||||||
|
for (var it = prev; it != null; it = it.prev) {
|
||||||
|
res++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexConverter == null ? res : indexConverter.applyAsInt(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void freeze() {
|
||||||
|
if (frozen) return;
|
||||||
|
this.frozen = true;
|
||||||
|
this.next = null;
|
||||||
|
this.var.freeze();
|
||||||
|
if (prev == null) return;
|
||||||
|
|
||||||
|
this.index = prev.index + 1;
|
||||||
|
this.next = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node(Variable var, Node next, Node prev) {
|
||||||
|
this.var = var;
|
||||||
|
this.next = next;
|
||||||
|
this.prev = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node first, last;
|
||||||
|
|
||||||
|
private final HashMap<String, Node> map = new HashMap<>();
|
||||||
|
private ArrayList<Node> frozenList = null;
|
||||||
|
private HashMap<Variable, Node> varMap = new HashMap<>();
|
||||||
|
|
||||||
|
private final IntSupplier offset;
|
||||||
|
private IntUnaryOperator indexConverter = null;
|
||||||
|
|
||||||
|
public boolean frozen() {
|
||||||
|
if (frozenList != null) {
|
||||||
|
assert frozenList != null;
|
||||||
|
assert map != null;
|
||||||
|
assert first == null;
|
||||||
|
assert last == null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert frozenList == null;
|
||||||
|
assert map != null;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable add(Variable val, boolean overlay) {
|
||||||
|
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||||
|
if (!overlay && map.containsKey(val.name)) {
|
||||||
|
var node = this.map.get(val.name);
|
||||||
|
val.setIndexSupplier(node);
|
||||||
|
return node.var;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = new Node(val, null, last);
|
||||||
|
|
||||||
|
if (last != null) {
|
||||||
|
assert first != null;
|
||||||
|
|
||||||
|
last.next = node;
|
||||||
|
node.prev = last;
|
||||||
|
|
||||||
|
last = node;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
first = last = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.put(val.name, node);
|
||||||
|
varMap.put(val, node);
|
||||||
|
val.setIndexSupplier(node);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable add(Variable val) {
|
||||||
|
return this.add(val, false);
|
||||||
|
}
|
||||||
|
public Variable overlay(Variable val) {
|
||||||
|
return this.add(val, true);
|
||||||
|
}
|
||||||
|
public Variable remove(String key) {
|
||||||
|
var res = map.get(key);
|
||||||
|
if (res != null) return remove(res.var);
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
public Variable remove(Variable var) {
|
||||||
|
if (var == null) return null;
|
||||||
|
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||||
|
|
||||||
|
var node = varMap.get(var);
|
||||||
|
if (node == null) return null;
|
||||||
|
|
||||||
|
if (node.prev != null) {
|
||||||
|
assert node != first;
|
||||||
|
node.prev.next = node.next;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert node == first;
|
||||||
|
first = first.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.next != null) {
|
||||||
|
assert node != last;
|
||||||
|
node.next.prev = node.prev;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert node == last;
|
||||||
|
last = last.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.next = null;
|
||||||
|
node.prev = null;
|
||||||
|
|
||||||
|
map.remove(node.var.name);
|
||||||
|
varMap.remove(node.var);
|
||||||
|
|
||||||
|
return node.var;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable get(String name) {
|
||||||
|
var res = map.get(name);
|
||||||
|
if (res != null) return res.var;
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
public int indexOfKey(String name) {
|
||||||
|
return map.get(name).getAsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean has(String name) {
|
||||||
|
return this.map.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
if (frozen()) return frozenList.size();
|
||||||
|
else return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void freeze() {
|
||||||
|
if (frozen()) return;
|
||||||
|
|
||||||
|
frozenList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (var node = first; node != null; ) {
|
||||||
|
frozenList.add(node);
|
||||||
|
|
||||||
|
var tmp = node;
|
||||||
|
node = node.next;
|
||||||
|
tmp.freeze();
|
||||||
|
}
|
||||||
|
|
||||||
|
first = last = null;
|
||||||
|
varMap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Variable> all() {
|
||||||
|
if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator();
|
||||||
|
else return () -> new Iterator<Variable>() {
|
||||||
|
private Node curr = first;
|
||||||
|
|
||||||
|
@Override public boolean hasNext() {
|
||||||
|
return curr != null;
|
||||||
|
}
|
||||||
|
@Override public Variable next() {
|
||||||
|
if (curr == null) return null;
|
||||||
|
|
||||||
|
var res = curr;
|
||||||
|
curr = curr.next;
|
||||||
|
return res.var;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public Iterable<String> keys() {
|
||||||
|
return () -> StreamSupport.stream(all().spliterator(), false).map(v -> v.name).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableList setIndexMap(IntUnaryOperator map) {
|
||||||
|
indexConverter = map;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableList(IntSupplier offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
public VariableList(int offset) {
|
||||||
|
this.offset = () -> offset;
|
||||||
|
}
|
||||||
|
public VariableList(VariableList prev) {
|
||||||
|
this.offset = prev::size;
|
||||||
|
}
|
||||||
|
public VariableList() {
|
||||||
|
this.offset = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
|
||||||
|
public class ArgumentsNode extends Node {
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (pollute) target.add(Instruction.loadArgs(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgumentsNode(Location loc) {
|
||||||
|
super(loc);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
|
||||||
|
public class ArrayNode extends Node {
|
||||||
|
public final Node[] statements;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadArr(statements.length));
|
||||||
|
|
||||||
|
if (statements.length > 0) target.add(Instruction.dup(statements.length));
|
||||||
|
|
||||||
|
for (var i = 0; i < statements.length; i++) {
|
||||||
|
var el = statements[i];
|
||||||
|
if (el != null) {
|
||||||
|
el.compile(target, true);
|
||||||
|
target.add(Instruction.storeMember(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayNode(Location loc, Node[] statements) {
|
||||||
|
super(loc);
|
||||||
|
this.statements = statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ArrayNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "[")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var values = new ArrayList<Node>();
|
||||||
|
|
||||||
|
loop: while (true) {
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
if (src.is(i + n, "]")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (src.is(i + n, ",")) {
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
values.add(null);
|
||||||
|
|
||||||
|
if (src.is(i + n, "]")) {
|
||||||
|
n++;
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element.");
|
||||||
|
n += res.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
values.add(res.result);
|
||||||
|
|
||||||
|
if (src.is(i + n, ",")) n++;
|
||||||
|
else if (src.is(i + n, "]")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new ArrayNode(loc, values.toArray(Node[]::new)), n);
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ArrayStatement extends Statement {
|
|
||||||
public final Statement[] statements;
|
|
||||||
|
|
||||||
@Override public boolean pure() {
|
|
||||||
for (var stm : statements) {
|
|
||||||
if (!stm.pure()) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.loadArr(statements.length));
|
|
||||||
|
|
||||||
for (var i = 0; i < statements.length; i++) {
|
|
||||||
var el = statements[i];
|
|
||||||
if (el != null) {
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushValue(i));
|
|
||||||
el.compile(target, true);
|
|
||||||
target.add(Instruction.storeMember());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayStatement(Location loc, Statement[] statements) {
|
|
||||||
super(loc);
|
|
||||||
this.statements = statements;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class CallStatement extends Statement {
|
|
||||||
public final Statement func;
|
|
||||||
public final Statement[] args;
|
|
||||||
public final boolean isNew;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
|
||||||
if (isNew) func.compile(target, true);
|
|
||||||
else if (func instanceof IndexStatement) {
|
|
||||||
((IndexStatement)func).compile(target, true, true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
target.add(Instruction.pushUndefined());
|
|
||||||
func.compile(target, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var arg : args) arg.compile(target, true);
|
|
||||||
|
|
||||||
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
|
|
||||||
else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type);
|
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
compile(target, pollute, BreakpointType.STEP_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
|
|
||||||
super(loc);
|
|
||||||
this.isNew = isNew;
|
|
||||||
this.func = func;
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ChangeStatement extends Statement {
|
|
||||||
public final AssignableStatement value;
|
|
||||||
public final double addAmount;
|
|
||||||
public final boolean postfix;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true);
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
else if (postfix) {
|
|
||||||
target.add(Instruction.pushValue(addAmount));
|
|
||||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
|
||||||
super(loc);
|
|
||||||
this.value = value;
|
|
||||||
this.addAmount = addAmount;
|
|
||||||
this.postfix = postfix;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ConstantStatement extends Statement {
|
|
||||||
public final Object value;
|
|
||||||
public final boolean isNull;
|
|
||||||
|
|
||||||
@Override public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
if (pollute) {
|
|
||||||
if (isNull) target.add(Instruction.pushNull());
|
|
||||||
else if (value instanceof Double) target.add(Instruction.pushValue((Double)value));
|
|
||||||
else if (value instanceof String) target.add(Instruction.pushValue((String)value));
|
|
||||||
else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value));
|
|
||||||
else target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConstantStatement(Location loc, Object val, boolean isNull) {
|
|
||||||
super(loc);
|
|
||||||
this.value = val;
|
|
||||||
this.isNull = isNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConstantStatement(Location loc, boolean val) {
|
|
||||||
this(loc, val, false);
|
|
||||||
}
|
|
||||||
public ConstantStatement(Location loc, String val) {
|
|
||||||
this(loc, val, false);
|
|
||||||
}
|
|
||||||
public ConstantStatement(Location loc, double val) {
|
|
||||||
this(loc, val, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ConstantStatement ofUndefined(Location loc) {
|
|
||||||
return new ConstantStatement(loc, null, false);
|
|
||||||
}
|
|
||||||
public static ConstantStatement ofNull(Location loc) {
|
|
||||||
return new ConstantStatement(loc, null, true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class DiscardStatement extends Statement {
|
|
||||||
public final Statement value;
|
|
||||||
|
|
||||||
@Override public boolean pure() { return value.pure(); }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
value.compile(target, false);
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiscardStatement(Location loc, Statement val) {
|
|
||||||
super(loc);
|
|
||||||
this.value = val;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.Type;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
|
||||||
|
|
||||||
public class FunctionStatement extends Statement {
|
|
||||||
public final CompoundStatement body;
|
|
||||||
public final String varName;
|
|
||||||
public final String[] args;
|
|
||||||
public final boolean statement;
|
|
||||||
public final Location end;
|
|
||||||
|
|
||||||
@Override public boolean pure() { return varName == null && statement; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void declare(CompileResult target) {
|
|
||||||
if (varName != null && statement) target.scope.define(varName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void checkBreakAndCont(CompileResult target, int start) {
|
|
||||||
for (int i = start; i < target.size(); i++) {
|
|
||||||
if (target.get(i).type == Type.NOP) {
|
|
||||||
if (target.get(i).is(0, "break") ) {
|
|
||||||
throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop.");
|
|
||||||
}
|
|
||||||
if (target.get(i).is(0, "cont")) {
|
|
||||||
throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) {
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
for (var j = 0; j < i; j++) {
|
|
||||||
if (args[i].equals(args[j])) {
|
|
||||||
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var subtarget = new CompileResult(target.scope.child());
|
|
||||||
|
|
||||||
subtarget.scope.define("this");
|
|
||||||
var argsVar = subtarget.scope.define("arguments");
|
|
||||||
|
|
||||||
if (args.length > 0) {
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
subtarget.add(Instruction.loadVar(argsVar));
|
|
||||||
subtarget.add(Instruction.pushValue(i));
|
|
||||||
subtarget.add(Instruction.loadMember());
|
|
||||||
subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!statement && this.varName != null) {
|
|
||||||
subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.declare(subtarget);
|
|
||||||
body.compile(subtarget, false);
|
|
||||||
subtarget.length = args.length;
|
|
||||||
subtarget.add(Instruction.ret()).setLocation(end);
|
|
||||||
checkBreakAndCont(subtarget, 0);
|
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures()));
|
|
||||||
return target.addChild(subtarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
|
||||||
if (this.varName != null) name = this.varName;
|
|
||||||
|
|
||||||
var hasVar = this.varName != null && statement;
|
|
||||||
var hasName = name != null;
|
|
||||||
|
|
||||||
compileBody(target, pollute || hasVar || hasName, bp);
|
|
||||||
|
|
||||||
if (hasName) {
|
|
||||||
if (pollute || hasVar) target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushValue("name"));
|
|
||||||
target.add(Instruction.pushValue(name));
|
|
||||||
target.add(Instruction.storeMember());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasVar) {
|
|
||||||
var key = target.scope.getKey(this.varName);
|
|
||||||
|
|
||||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
|
||||||
target.add(Instruction.storeVar(target.scope.getKey(this.varName), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void compile(CompileResult target, boolean pollute, String name) {
|
|
||||||
compile(target, pollute, name, BreakpointType.NONE);
|
|
||||||
}
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
|
|
||||||
compile(target, pollute, (String)null, bp);
|
|
||||||
}
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
|
||||||
compile(target, pollute, (String)null, BreakpointType.NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
|
|
||||||
super(loc);
|
|
||||||
|
|
||||||
this.end = end;
|
|
||||||
this.varName = varName;
|
|
||||||
this.statement = statement;
|
|
||||||
|
|
||||||
this.args = args;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name) {
|
|
||||||
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name);
|
|
||||||
else stm.compile(target, pollute);
|
|
||||||
}
|
|
||||||
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
|
||||||
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp);
|
|
||||||
else stm.compile(target, pollute, bp);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
|
||||||
|
public class GlobalThisNode extends Node {
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
if (pollute) target.add(Instruction.loadGlob());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalThisNode(Location loc) {
|
||||||
|
super(loc);
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class GlobalThisStatement extends Statement {
|
|
||||||
@Override public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
if (pollute) target.add(Instruction.loadGlob());
|
|
||||||
}
|
|
||||||
|
|
||||||
public GlobalThisStatement(Location loc) {
|
|
||||||
super(loc);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class IndexAssignStatement extends Statement {
|
|
||||||
public final Statement object;
|
|
||||||
public final Statement index;
|
|
||||||
public final Statement value;
|
|
||||||
public final Operation operation;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
if (operation != null) {
|
|
||||||
object.compile(target, true);
|
|
||||||
index.compile(target, true);
|
|
||||||
target.add(Instruction.dup(2));
|
|
||||||
|
|
||||||
target.add(Instruction.loadMember());
|
|
||||||
value.compile(target, true);
|
|
||||||
target.add(Instruction.operation(operation));
|
|
||||||
|
|
||||||
target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
object.compile(target, true);
|
|
||||||
index.compile(target, true);
|
|
||||||
value.compile(target, true);
|
|
||||||
|
|
||||||
target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN);;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
|
|
||||||
super(loc);
|
|
||||||
this.object = object;
|
|
||||||
this.index = index;
|
|
||||||
this.value = value;
|
|
||||||
this.operation = operation;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class IndexStatement extends AssignableStatement {
|
|
||||||
public final Statement object;
|
|
||||||
public final Statement index;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Statement toAssign(Statement val, Operation operation) {
|
|
||||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
|
||||||
}
|
|
||||||
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
|
|
||||||
object.compile(target, true);
|
|
||||||
if (dupObj) target.add(Instruction.dup());
|
|
||||||
|
|
||||||
index.compile(target, true);
|
|
||||||
target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
compile(target, false, pollute);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexStatement(Location loc, Statement object, Statement index) {
|
|
||||||
super(loc);
|
|
||||||
this.object = object;
|
|
||||||
this.index = index;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class LazyAndStatement extends Statement {
|
|
||||||
public final Statement first, second;
|
|
||||||
|
|
||||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
first.compile(target, true);
|
|
||||||
if (pollute) target.add(Instruction.dup());
|
|
||||||
int start = target.temp();
|
|
||||||
if (pollute) target.add(Instruction.discard());
|
|
||||||
second.compile(target, pollute);
|
|
||||||
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
|
||||||
super(loc);
|
|
||||||
this.first = first;
|
|
||||||
this.second = second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class LazyOrStatement extends Statement {
|
|
||||||
public final Statement first, second;
|
|
||||||
|
|
||||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
first.compile(target, true);
|
|
||||||
if (pollute) target.add(Instruction.dup());
|
|
||||||
int start = target.temp();
|
|
||||||
if (pollute) target.add(Instruction.discard());
|
|
||||||
second.compile(target, pollute);
|
|
||||||
target.set(start, Instruction.jmpIf(target.size() - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
|
||||||
super(loc);
|
|
||||||
this.first = first;
|
|
||||||
this.second = second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,179 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.FunctionNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.FunctionValueNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
|
||||||
|
public class ObjectNode extends Node {
|
||||||
|
public static class ObjProp {
|
||||||
|
public final String name;
|
||||||
|
public final String access;
|
||||||
|
public final FunctionValueNode func;
|
||||||
|
|
||||||
|
public ObjProp(String name, String access, FunctionValueNode func) {
|
||||||
|
this.name = name;
|
||||||
|
this.access = access;
|
||||||
|
this.func = func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Map<String, Node> map;
|
||||||
|
public final Map<String, FunctionNode> getters;
|
||||||
|
public final Map<String, FunctionNode> setters;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadObj());
|
||||||
|
|
||||||
|
for (var el : map.entrySet()) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
var val = el.getValue();
|
||||||
|
FunctionNode.compileWithName(val, target, true, el.getKey().toString());
|
||||||
|
target.add(Instruction.storeMember(el.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = new ArrayList<Object>();
|
||||||
|
keys.addAll(getters.keySet());
|
||||||
|
keys.addAll(setters.keySet());
|
||||||
|
|
||||||
|
for (var key : keys) {
|
||||||
|
target.add(Instruction.pushValue((String)key));
|
||||||
|
|
||||||
|
if (getters.containsKey(key)) getters.get(key).compile(target, true);
|
||||||
|
else target.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
if (setters.containsKey(key)) setters.get(key).compile(target, true);
|
||||||
|
else target.add(Instruction.pushUndefined());
|
||||||
|
|
||||||
|
target.add(Instruction.defProp());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode(Location loc, Map<String, Node> map, Map<String, FunctionNode> getters, Map<String, FunctionNode> setters) {
|
||||||
|
super(loc);
|
||||||
|
this.map = map;
|
||||||
|
this.getters = getters;
|
||||||
|
this.setters = setters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<String> parsePropName(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
var res = ParseRes.first(src, i + n,
|
||||||
|
Parsing::parseIdentifier,
|
||||||
|
Parsing::parseString,
|
||||||
|
(s, j) -> Parsing.parseNumber(s, j, false)
|
||||||
|
);
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
return ParseRes.res(res.result.toString(), n);
|
||||||
|
}
|
||||||
|
private static ParseRes<ObjectNode.ObjProp> parseObjectProp(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var access = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!access.isSuccess()) return ParseRes.failed();
|
||||||
|
if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed();
|
||||||
|
n += access.n;
|
||||||
|
|
||||||
|
var name = parsePropName(src, i + n);
|
||||||
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
||||||
|
n += name.n;
|
||||||
|
|
||||||
|
var params = JavaScript.parseParameters(src, i + n);
|
||||||
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||||
|
n += params.n;
|
||||||
|
|
||||||
|
var body = CompoundNode.parse(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
var end = src.loc(i + n - 1);
|
||||||
|
|
||||||
|
return ParseRes.res(new ObjProp(
|
||||||
|
name.result, access.result,
|
||||||
|
new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString())
|
||||||
|
), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ObjectNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var values = new LinkedHashMap<String, Node>();
|
||||||
|
var getters = new LinkedHashMap<String, FunctionNode>();
|
||||||
|
var setters = new LinkedHashMap<String, FunctionNode>();
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var prop = parseObjectProp(src, i + n);
|
||||||
|
|
||||||
|
if (prop.isSuccess()) {
|
||||||
|
n += prop.n;
|
||||||
|
|
||||||
|
if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func);
|
||||||
|
else getters.put(prop.result.name, prop.result.func);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var name = parsePropName(src, i + n);
|
||||||
|
if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name");
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list");
|
||||||
|
n += valRes.n;
|
||||||
|
|
||||||
|
values.put(name.result, valRes.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
if (src.is(i + n, ",")) {
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class ObjectStatement extends Statement {
|
|
||||||
public final Map<String, Statement> map;
|
|
||||||
public final Map<String, FunctionStatement> getters;
|
|
||||||
public final Map<String, FunctionStatement> setters;
|
|
||||||
|
|
||||||
@Override public boolean pure() {
|
|
||||||
for (var el : map.values()) {
|
|
||||||
if (!el.pure()) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.loadObj());
|
|
||||||
|
|
||||||
for (var el : map.entrySet()) {
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushValue(el.getKey()));
|
|
||||||
var val = el.getValue();
|
|
||||||
FunctionStatement.compileWithName(val, target, true, el.getKey().toString());
|
|
||||||
target.add(Instruction.storeMember());
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys = new ArrayList<Object>();
|
|
||||||
keys.addAll(getters.keySet());
|
|
||||||
keys.addAll(setters.keySet());
|
|
||||||
|
|
||||||
for (var key : keys) {
|
|
||||||
target.add(Instruction.pushValue((String)key));
|
|
||||||
|
|
||||||
if (getters.containsKey(key)) getters.get(key).compile(target, true);
|
|
||||||
else target.add(Instruction.pushUndefined());
|
|
||||||
|
|
||||||
if (setters.containsKey(key)) setters.get(key).compile(target, true);
|
|
||||||
else target.add(Instruction.pushUndefined());
|
|
||||||
|
|
||||||
target.add(Instruction.defProp());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectStatement(Location loc, Map<String, Statement> map, Map<String, FunctionStatement> getters, Map<String, FunctionStatement> setters) {
|
|
||||||
super(loc);
|
|
||||||
this.map = map;
|
|
||||||
this.getters = getters;
|
|
||||||
this.setters = setters;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class OperationStatement extends Statement {
|
|
||||||
public final Statement[] args;
|
|
||||||
public final Operation operation;
|
|
||||||
|
|
||||||
@Override public boolean pure() {
|
|
||||||
for (var el : args) {
|
|
||||||
if (!el.pure()) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
for (var arg : args) {
|
|
||||||
arg.compile(target, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.operation(operation));
|
|
||||||
else target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
|
|
||||||
public OperationStatement(Location loc, Operation operation, Statement ...args) {
|
|
||||||
super(loc);
|
|
||||||
this.operation = operation;
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,76 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
|
||||||
|
public class RegexNode extends Node {
|
||||||
|
public final String pattern, flags;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadRegex(pattern, flags));
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ParseRes<RegexNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
if (!src.is(i + n, '/')) return ParseRes.failed();
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var source = new StringBuilder();
|
||||||
|
var flags = new StringBuilder();
|
||||||
|
|
||||||
|
var inBrackets = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (src.is(i + n, '[')) {
|
||||||
|
n++;
|
||||||
|
inBrackets = true;
|
||||||
|
source.append(src.at(i + n));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, ']')) {
|
||||||
|
n++;
|
||||||
|
inBrackets = false;
|
||||||
|
source.append(src.at(i + n));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, '/') && !inBrackets) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var charRes = Parsing.parseChar(src, i + n);
|
||||||
|
if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed");
|
||||||
|
source.append(charRes.result);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
char c = src.at(i + n, '\0');
|
||||||
|
|
||||||
|
if (src.is(i + n, v -> Parsing.isAny(c, "dgimsuy"))) {
|
||||||
|
if (flags.indexOf(c + "") >= 0) return ParseRes.error(src.loc(i + n), "The flags of a regular expression may not be repeated");
|
||||||
|
flags.append(c);
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new RegexNode(loc, source.toString(), flags.toString()), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegexNode(Location loc, String pattern, String flags) {
|
||||||
|
super(loc);
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Location;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
|
||||||
|
|
||||||
public class RegexStatement extends Statement {
|
|
||||||
public final String pattern, flags;
|
|
||||||
|
|
||||||
// Not really pure, since a function is called, but can be ignored.
|
|
||||||
@Override public boolean pure() { return true; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void compile(CompileResult target, boolean pollute) {
|
|
||||||
target.add(Instruction.loadRegex(pattern, flags));
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegexStatement(Location loc, String pattern, String flags) {
|
|
||||||
super(loc);
|
|
||||||
this.pattern = pattern;
|
|
||||||
this.flags = flags;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user