major rewrite: clean up a lot of code and lay ground for ES6 support
This commit is contained in:
parent
d4f6b24c28
commit
a9a19824fe
24
build.gradle
24
build.gradle
@ -2,11 +2,27 @@ plugins {
|
||||
id "application"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2'
|
||||
// Genuinely fuck Java
|
||||
compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(11)
|
||||
withSourcesJar()
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
|
||||
configure([tasks.compileJava]) {
|
||||
sourceCompatibility = 17 // for the IDE support
|
||||
options.release = 11
|
||||
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -1,3 +1,5 @@
|
||||
#! my special comment lol
|
||||
|
||||
(function(target, primordials) {
|
||||
var makeSymbol = primordials.symbol.makeSymbol;
|
||||
var getSymbol = primordials.symbol.getSymbol;
|
||||
|
@ -3,12 +3,13 @@ package me.topchetoeu.jscript.common;
|
||||
public class FunctionBody {
|
||||
public final FunctionBody[] children;
|
||||
public final Instruction[] instructions;
|
||||
public final int localsN, argsN;
|
||||
public final int localsN, capturesN, argsN;
|
||||
|
||||
public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) {
|
||||
public FunctionBody(int localsN, int capturesN, int argsN, Instruction[] instructions, FunctionBody[] children) {
|
||||
this.children = children;
|
||||
this.argsN = argsN;
|
||||
this.localsN = localsN;
|
||||
this.capturesN = capturesN;
|
||||
this.instructions = instructions;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
// import java.io.DataInputStream;
|
||||
// import java.io.DataOutputStream;
|
||||
// import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
@ -35,20 +35,27 @@ public class Instruction {
|
||||
LOAD_FUNC(0x30),
|
||||
LOAD_ARR(0x31),
|
||||
LOAD_OBJ(0x32),
|
||||
STORE_SELF_FUNC(0x33),
|
||||
LOAD_GLOB(0x33),
|
||||
LOAD_REGEX(0x34),
|
||||
|
||||
LOAD_VAR(0x40),
|
||||
LOAD_MEMBER(0x41),
|
||||
LOAD_GLOB(0x42),
|
||||
STORE_VAR(0x43),
|
||||
STORE_MEMBER(0x44),
|
||||
LOAD_ARGS(0x42),
|
||||
LOAD_THIS(0x43),
|
||||
STORE_VAR(0x48),
|
||||
STORE_MEMBER(0x49),
|
||||
|
||||
MAKE_VAR(0x50),
|
||||
DEF_PROP(0x51),
|
||||
KEYS(0x52),
|
||||
TYPEOF(0x53),
|
||||
OPERATION(0x54);
|
||||
DEF_PROP(0x50),
|
||||
KEYS(0x51),
|
||||
TYPEOF(0x52),
|
||||
OPERATION(0x53),
|
||||
|
||||
GLOB_GET(0x60),
|
||||
GLOB_SET(0x61),
|
||||
GLOB_DEF(0x62),
|
||||
|
||||
STACK_ALLOC(0x70),
|
||||
STACK_FREE(0x71);
|
||||
|
||||
private static final HashMap<Integer, Type> types = new HashMap<>();
|
||||
public final int numeric;
|
||||
@ -108,123 +115,128 @@ public class Instruction {
|
||||
return params[i].equals(arg);
|
||||
}
|
||||
|
||||
public void write(DataOutputStream writer) throws IOException {
|
||||
var rawType = type.numeric;
|
||||
// public void write(DataOutputStream writer) throws IOException {
|
||||
// var rawType = type.numeric;
|
||||
|
||||
switch (type) {
|
||||
case KEYS:
|
||||
case PUSH_BOOL:
|
||||
case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break;
|
||||
case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break;
|
||||
case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
|
||||
default:
|
||||
}
|
||||
// switch (type) {
|
||||
// case KEYS:
|
||||
// case PUSH_BOOL:
|
||||
// case STORE_MEMBER:
|
||||
// case GLOB_SET:
|
||||
// rawType |= (boolean)get(0) ? 128 : 0; break;
|
||||
// case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
|
||||
// default:
|
||||
// }
|
||||
|
||||
writer.writeByte(rawType);
|
||||
// writer.writeByte(rawType);
|
||||
|
||||
switch (type) {
|
||||
case CALL:
|
||||
case CALL_NEW:
|
||||
case CALL_MEMBER:
|
||||
writer.writeInt(get(0));
|
||||
writer.writeUTF(get(1));
|
||||
break;
|
||||
case DUP: writer.writeInt(get(0)); break;
|
||||
case JMP: writer.writeInt(get(0)); break;
|
||||
case JMP_IF: writer.writeInt(get(0)); break;
|
||||
case JMP_IFN: writer.writeInt(get(0)); break;
|
||||
case LOAD_ARR: writer.writeInt(get(0)); break;
|
||||
case LOAD_FUNC: {
|
||||
writer.writeInt(params.length - 1);
|
||||
// switch (type) {
|
||||
// case CALL:
|
||||
// case CALL_NEW:
|
||||
// case CALL_MEMBER:
|
||||
// writer.writeInt(get(0));
|
||||
// writer.writeUTF(get(1));
|
||||
// break;
|
||||
// case DUP: writer.writeInt(get(0)); break;
|
||||
// case JMP: writer.writeInt(get(0)); break;
|
||||
// case JMP_IF: writer.writeInt(get(0)); break;
|
||||
// case JMP_IFN: writer.writeInt(get(0)); break;
|
||||
// case LOAD_ARR: writer.writeInt(get(0)); break;
|
||||
// case LOAD_FUNC: {
|
||||
// writer.writeInt(params.length - 1);
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
writer.writeInt(get(i + 1));
|
||||
}
|
||||
// for (var i = 0; i < params.length; i++) {
|
||||
// writer.writeInt(get(i + 1));
|
||||
// }
|
||||
|
||||
writer.writeInt(get(0));
|
||||
writer.writeUTF(get(0));
|
||||
break;
|
||||
}
|
||||
case LOAD_REGEX: writer.writeUTF(get(0)); break;
|
||||
case LOAD_VAR: writer.writeInt(get(0)); break;
|
||||
case MAKE_VAR: writer.writeUTF(get(0)); break;
|
||||
case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
|
||||
case PUSH_NUMBER: writer.writeDouble(get(0)); break;
|
||||
case PUSH_STRING: writer.writeUTF(get(0)); break;
|
||||
case STORE_SELF_FUNC: writer.writeInt(get(0)); break;
|
||||
case STORE_VAR: writer.writeInt(get(0)); break;
|
||||
case THROW_SYNTAX: writer.writeUTF(get(0));
|
||||
case TRY_START:
|
||||
writer.writeInt(get(0));
|
||||
writer.writeInt(get(1));
|
||||
writer.writeInt(get(2));
|
||||
break;
|
||||
case TYPEOF:
|
||||
if (params.length > 0) writer.writeUTF(get(0));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
// writer.writeInt(get(0));
|
||||
// writer.writeUTF(get(0));
|
||||
// break;
|
||||
// }
|
||||
// case LOAD_REGEX: writer.writeUTF(get(0)); break;
|
||||
// case LOAD_VAR: writer.writeInt(get(0)); break;
|
||||
// case GLOB_DEF: writer.writeUTF(get(0)); break;
|
||||
// case GLOB_GET: writer.writeUTF(get(0)); break;
|
||||
// case GLOB_SET:
|
||||
// writer.writeUTF(get(0));
|
||||
// break;
|
||||
// case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
|
||||
// case PUSH_NUMBER: writer.writeDouble(get(0)); break;
|
||||
// case PUSH_STRING: writer.writeUTF(get(0)); break;
|
||||
// case STORE_VAR: writer.writeInt(get(0)); break;
|
||||
// case THROW_SYNTAX: writer.writeUTF(get(0));
|
||||
// case TRY_START:
|
||||
// writer.writeInt(get(0));
|
||||
// writer.writeInt(get(1));
|
||||
// writer.writeInt(get(2));
|
||||
// break;
|
||||
// case TYPEOF:
|
||||
// if (params.length > 0) writer.writeUTF(get(0));
|
||||
// break;
|
||||
// default:
|
||||
// }
|
||||
// }
|
||||
|
||||
private Instruction(Type type, Object ...params) {
|
||||
this.type = type;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public static Instruction read(DataInputStream stream) throws IOException {
|
||||
var rawType = stream.readUnsignedByte();
|
||||
var type = Type.fromNumeric(rawType & 127);
|
||||
var flag = (rawType & 128) != 0;
|
||||
// public static Instruction read(DataInputStream stream) throws IOException {
|
||||
// var rawType = stream.readUnsignedByte();
|
||||
// var type = Type.fromNumeric(rawType & 127);
|
||||
// var flag = (rawType & 128) != 0;
|
||||
|
||||
switch (type) {
|
||||
case CALL: return call(stream.readInt(), stream.readUTF());
|
||||
case CALL_NEW: return callNew(stream.readInt(), stream.readUTF());
|
||||
case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF());
|
||||
case DEF_PROP: return defProp();
|
||||
case DELETE: return delete();
|
||||
case DISCARD: return discard();
|
||||
case DUP: return dup(stream.readInt());
|
||||
case JMP: return jmp(stream.readInt());
|
||||
case JMP_IF: return jmpIf(stream.readInt());
|
||||
case JMP_IFN: return jmpIfNot(stream.readInt());
|
||||
case KEYS: return keys(flag);
|
||||
case LOAD_ARR: return loadArr(stream.readInt());
|
||||
case LOAD_FUNC: {
|
||||
var captures = new int[stream.readInt()];
|
||||
// switch (type) {
|
||||
// case CALL: return call(stream.readInt(), stream.readUTF());
|
||||
// case CALL_NEW: return callNew(stream.readInt(), stream.readUTF());
|
||||
// case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF());
|
||||
// case DEF_PROP: return defProp();
|
||||
// case DELETE: return delete();
|
||||
// case DISCARD: return discard();
|
||||
// case DUP: return dup(stream.readInt());
|
||||
// case JMP: return jmp(stream.readInt());
|
||||
// case JMP_IF: return jmpIf(stream.readInt());
|
||||
// case JMP_IFN: return jmpIfNot(stream.readInt());
|
||||
// case KEYS: return keys(flag);
|
||||
// case LOAD_ARR: return loadArr(stream.readInt());
|
||||
// case LOAD_FUNC: {
|
||||
// var captures = new int[stream.readInt()];
|
||||
|
||||
for (var i = 0; i < captures.length; i++) {
|
||||
captures[i] = stream.readInt();
|
||||
}
|
||||
// for (var i = 0; i < captures.length; i++) {
|
||||
// captures[i] = stream.readInt();
|
||||
// }
|
||||
|
||||
return loadFunc(stream.readInt(), stream.readUTF(), captures);
|
||||
}
|
||||
case LOAD_GLOB: return loadGlob();
|
||||
case LOAD_MEMBER: return loadMember();
|
||||
case LOAD_OBJ: return loadObj();
|
||||
case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
|
||||
case LOAD_VAR: return loadVar(stream.readInt());
|
||||
case MAKE_VAR: return makeVar(stream.readUTF());
|
||||
case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
|
||||
case PUSH_NULL: return pushNull();
|
||||
case PUSH_UNDEFINED: return pushUndefined();
|
||||
case PUSH_BOOL: return pushValue(flag);
|
||||
case PUSH_NUMBER: return pushValue(stream.readDouble());
|
||||
case PUSH_STRING: return pushValue(stream.readUTF());
|
||||
case RETURN: return ret();
|
||||
case STORE_MEMBER: return storeMember(flag);
|
||||
case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt());
|
||||
case STORE_VAR: return storeVar(stream.readInt(), flag);
|
||||
case THROW: return throwInstr();
|
||||
case THROW_SYNTAX: return throwSyntax(stream.readUTF());
|
||||
case TRY_END: return tryEnd();
|
||||
case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt());
|
||||
case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
|
||||
case NOP:
|
||||
if (flag) return null;
|
||||
else return nop();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
// return loadFunc(stream.readInt(), stream.readUTF(), captures);
|
||||
// }
|
||||
// case LOAD_GLOB: return loadGlob();
|
||||
// case LOAD_MEMBER: return loadMember();
|
||||
// case LOAD_OBJ: return loadObj();
|
||||
// case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
|
||||
// case LOAD_VAR: return loadVar(stream.readInt());
|
||||
// case GLOB_DEF: return globDef(stream.readUTF());
|
||||
// case GLOB_GET: return globGet(stream.readUTF());
|
||||
// case GLOB_SET: return globSet(stream.readUTF(), flag);
|
||||
// case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
|
||||
// case PUSH_NULL: return pushNull();
|
||||
// case PUSH_UNDEFINED: return pushUndefined();
|
||||
// case PUSH_BOOL: return pushValue(flag);
|
||||
// case PUSH_NUMBER: return pushValue(stream.readDouble());
|
||||
// case PUSH_STRING: return pushValue(stream.readUTF());
|
||||
// case RETURN: return ret();
|
||||
// case STORE_MEMBER: return storeMember(flag);
|
||||
// case STORE_VAR: return storeVar(stream.readInt(), flag);
|
||||
// case THROW: return throwInstr();
|
||||
// case THROW_SYNTAX: return throwSyntax(stream.readUTF());
|
||||
// case TRY_END: return tryEnd();
|
||||
// case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt(), stream.readInt());
|
||||
// case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
|
||||
// case NOP:
|
||||
// if (flag) return null;
|
||||
// else return nop();
|
||||
// default: return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
|
||||
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
|
||||
@ -299,12 +311,26 @@ public class Instruction {
|
||||
return new Instruction(Type.PUSH_STRING, val);
|
||||
}
|
||||
|
||||
public static Instruction makeVar(String name) {
|
||||
return new Instruction(Type.MAKE_VAR, name);
|
||||
public static Instruction globDef(String name) {
|
||||
return new Instruction(Type.GLOB_GET, name);
|
||||
}
|
||||
public static Instruction loadVar(Object i) {
|
||||
|
||||
public static Instruction globGet(String name) {
|
||||
return new Instruction(Type.GLOB_GET, name);
|
||||
}
|
||||
public static Instruction globSet(String name, boolean keep, boolean define) {
|
||||
return new Instruction(Type.GLOB_SET, name, keep, define);
|
||||
}
|
||||
|
||||
public static Instruction loadVar(int i) {
|
||||
return new Instruction(Type.LOAD_VAR, i);
|
||||
}
|
||||
public static Instruction loadThis() {
|
||||
return new Instruction(Type.LOAD_THIS);
|
||||
}
|
||||
public static Instruction loadArgs() {
|
||||
return new Instruction(Type.LOAD_ARGS);
|
||||
}
|
||||
public static Instruction loadGlob() {
|
||||
return new Instruction(Type.LOAD_GLOB);
|
||||
}
|
||||
@ -337,13 +363,10 @@ public class Instruction {
|
||||
return new Instruction(Type.DUP, count);
|
||||
}
|
||||
|
||||
public static Instruction storeSelfFunc(int i) {
|
||||
return new Instruction(Type.STORE_SELF_FUNC, i);
|
||||
}
|
||||
public static Instruction storeVar(Object i) {
|
||||
public static Instruction storeVar(int i) {
|
||||
return new Instruction(Type.STORE_VAR, i, false);
|
||||
}
|
||||
public static Instruction storeVar(Object i, boolean keep) {
|
||||
public static Instruction storeVar(int i, boolean keep) {
|
||||
return new Instruction(Type.STORE_VAR, i, keep);
|
||||
}
|
||||
public static Instruction storeMember() {
|
||||
@ -375,8 +398,14 @@ public class Instruction {
|
||||
return new Instruction(Type.OPERATION, op);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public static Instruction stackAlloc(int i) {
|
||||
return new Instruction(Type.STACK_ALLOC, i);
|
||||
}
|
||||
public static Instruction stackFree(int i) {
|
||||
return new Instruction(Type.STACK_FREE, i);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
var res = type.toString();
|
||||
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
|
@ -160,13 +160,17 @@ public class Environment {
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> Environment init(Key<T> key, T val) {
|
||||
public <T> T init(Key<T> key, T val) {
|
||||
if (!has(key)) this.add(key, val);
|
||||
return this;
|
||||
return val;
|
||||
}
|
||||
public <T> Environment init(Key<T> key, Supplier<T> val) {
|
||||
if (!has(key)) this.add(key, val.get());
|
||||
return this;
|
||||
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() {
|
||||
|
@ -69,8 +69,7 @@ public class JSONElement {
|
||||
return (boolean)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@Override public String toString() {
|
||||
if (isMap()) return "{...}";
|
||||
if (isList()) return "[...]";
|
||||
if (isString()) return (String)value;
|
||||
|
@ -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, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
|
||||
|
||||
@Override
|
||||
public int size() { return elements.size(); }
|
||||
@Override
|
||||
public boolean isEmpty() { return elements.isEmpty(); }
|
||||
@Override
|
||||
public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||
@Override
|
||||
public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||
@Override
|
||||
public JSONElement get(Object key) { return elements.get(key); }
|
||||
@Override
|
||||
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||
@Override
|
||||
public JSONElement remove(Object key) { return elements.remove(key); }
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||
@Override public int size() { return elements.size(); }
|
||||
@Override public boolean isEmpty() { return elements.isEmpty(); }
|
||||
@Override public boolean containsKey(Object key) { return elements.containsKey(key); }
|
||||
@Override public boolean containsValue(Object value) { return elements.containsValue(value); }
|
||||
@Override public JSONElement get(Object key) { return elements.get(key); }
|
||||
@Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
|
||||
@Override public JSONElement remove(Object key) { return elements.remove(key); }
|
||||
@Override public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
|
||||
|
||||
@Override
|
||||
public void clear() { elements.clear(); }
|
||||
@Override public void clear() { elements.clear(); }
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() { return elements.keySet(); }
|
||||
@Override
|
||||
public Collection<JSONElement> values() { return elements.values(); }
|
||||
@Override
|
||||
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||
@Override public Set<String> keySet() { return elements.keySet(); }
|
||||
@Override public Collection<JSONElement> values() { return elements.values(); }
|
||||
@Override public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
|
||||
|
||||
public JSONMap() { }
|
||||
public JSONMap(Map<String, JSONElement> els) {
|
||||
|
@ -14,7 +14,7 @@ import java.util.stream.Collectors;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.scope.Scope;
|
||||
|
||||
public class FunctionMap {
|
||||
public static class FunctionMapBuilder {
|
||||
@ -53,8 +53,8 @@ public class FunctionMap {
|
||||
public FunctionMap build(String[] localNames, String[] captureNames) {
|
||||
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
|
||||
}
|
||||
public FunctionMap build(LocalScopeRecord scope) {
|
||||
return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures());
|
||||
public FunctionMap build(Scope scope) {
|
||||
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||
}
|
||||
public FunctionMap build() {
|
||||
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||
|
@ -4,7 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class Location implements Comparable<Location> {
|
||||
public static final Location INTERNAL = Location.of("jscript://native");
|
||||
public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1);
|
||||
|
||||
public abstract int line();
|
||||
public abstract int start();
|
||||
@ -39,10 +39,10 @@ public abstract class Location implements Comparable<Location> {
|
||||
};
|
||||
}
|
||||
|
||||
@Override public final int hashCode() {
|
||||
@Override public int hashCode() {
|
||||
return Objects.hash(line(), start(), filename());
|
||||
}
|
||||
@Override public final boolean equals(Object obj) {
|
||||
@Override public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Location)) return false;
|
||||
var other = (Location)obj;
|
||||
|
@ -12,28 +12,35 @@ public class ParseRes<T> {
|
||||
}
|
||||
|
||||
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, String error, T result, int readN) {
|
||||
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, result, i);
|
||||
return new ParseRes<>(state, null, null, result, i);
|
||||
}
|
||||
public ParseRes<T> addN(int n) {
|
||||
if (!state.isSuccess()) return this;
|
||||
return new ParseRes<>(state, null, result, this.n + n);
|
||||
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, error, null, 0);
|
||||
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(); }
|
||||
@ -41,19 +48,13 @@ public class ParseRes<T> {
|
||||
public boolean isError() { return state.isError(); }
|
||||
|
||||
public static <T> ParseRes<T> failed() {
|
||||
return new ParseRes<T>(State.FAILED, null, null, 0);
|
||||
return new ParseRes<T>(State.FAILED, null, 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 <T2> ParseRes<T2> chainError(Location loc, String error) {
|
||||
if (loc != null) error = loc + ": " + error;
|
||||
if (!this.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
|
||||
return new ParseRes<>(State.ERROR, this.error, null, 0);
|
||||
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, val, i);
|
||||
return new ParseRes<>(State.SUCCESS, null, null, val, i);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
|
@ -19,9 +19,48 @@ public class Parsing {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++;
|
||||
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;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package me.topchetoeu.jscript.common.parsing;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SourceLocation extends Location {
|
||||
private int[] lineStarts;
|
||||
private int line;
|
||||
@ -10,16 +12,16 @@ public class SourceLocation extends Location {
|
||||
private void update() {
|
||||
if (lineStarts == null) return;
|
||||
|
||||
int start = 0;
|
||||
int end = lineStarts.length - 1;
|
||||
int a = 0;
|
||||
int b = lineStarts.length;
|
||||
|
||||
while (true) {
|
||||
if (start + 1 >= end) break;
|
||||
var mid = -((-start - end) >> 1);
|
||||
if (a + 1 >= b) break;
|
||||
var mid = -((-a - b) >> 1);
|
||||
var el = lineStarts[mid];
|
||||
|
||||
if (el < offset) start = mid;
|
||||
else if (el > offset) end = mid;
|
||||
if (el < offset) a = mid;
|
||||
else if (el > offset) b = mid;
|
||||
else {
|
||||
this.line = mid;
|
||||
this.start = 0;
|
||||
@ -28,8 +30,8 @@ public class SourceLocation extends Location {
|
||||
}
|
||||
}
|
||||
|
||||
this.line = start;
|
||||
this.start = offset - lineStarts[start];
|
||||
this.line = a;
|
||||
this.start = offset - lineStarts[a];
|
||||
this.lineStarts = null;
|
||||
return;
|
||||
}
|
||||
@ -44,6 +46,18 @@ public class SourceLocation extends Location {
|
||||
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;
|
||||
|
@ -1,23 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Vector;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.topchetoeu.jscript.common.FunctionBody;
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
||||
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.compilation.scope.Scope;
|
||||
|
||||
public class CompileResult {
|
||||
public final Vector<Instruction> instructions = new Vector<>();
|
||||
public final List<CompileResult> children = new LinkedList<>();
|
||||
public final FunctionMapBuilder map = FunctionMap.builder();
|
||||
public final LocalScopeRecord scope;
|
||||
public final class CompileResult {
|
||||
public final List<Supplier<Instruction>> instructions;
|
||||
public final List<CompileResult> children;
|
||||
public final FunctionMapBuilder map;
|
||||
public final Environment env;
|
||||
public int length = 0;
|
||||
public final Scope scope;
|
||||
|
||||
public int temp() {
|
||||
instructions.add(null);
|
||||
@ -25,16 +29,24 @@ public class CompileResult {
|
||||
}
|
||||
|
||||
public CompileResult add(Instruction instr) {
|
||||
instructions.add(() -> instr);
|
||||
return this;
|
||||
}
|
||||
public CompileResult add(Supplier<Instruction> instr) {
|
||||
instructions.add(instr);
|
||||
return this;
|
||||
}
|
||||
public CompileResult set(int i, Instruction instr) {
|
||||
instructions.set(i, () -> instr);
|
||||
return this;
|
||||
}
|
||||
public CompileResult set(int i, Supplier<Instruction>instr) {
|
||||
instructions.set(i, instr);
|
||||
return this;
|
||||
}
|
||||
public Instruction get(int i) {
|
||||
return instructions.get(i);
|
||||
}
|
||||
// public Instruction get(int i) {
|
||||
// return instructions.get(i);
|
||||
// }
|
||||
public int size() { return instructions.size(); }
|
||||
|
||||
public void setDebug(Location loc, BreakpointType type) {
|
||||
@ -61,6 +73,13 @@ public class CompileResult {
|
||||
return child;
|
||||
}
|
||||
|
||||
public Instruction[] instructions() {
|
||||
var res = new Instruction[instructions.size()];
|
||||
var i = 0;
|
||||
for (var suppl : instructions) res[i++] = suppl.get();
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionMap map() {
|
||||
return map.build(scope);
|
||||
}
|
||||
@ -69,10 +88,32 @@ public class CompileResult {
|
||||
|
||||
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
|
||||
|
||||
return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren);
|
||||
var instrRes = new Instruction[instructions.size()];
|
||||
var i = 0;
|
||||
for (var suppl : instructions) instrRes[i++] = suppl.get();
|
||||
|
||||
return new FunctionBody(
|
||||
scope.localsCount() + scope.allocCount(), scope.capturesCount(), length,
|
||||
instrRes, builtChildren
|
||||
);
|
||||
}
|
||||
|
||||
public CompileResult(LocalScopeRecord scope) {
|
||||
public CompileResult subtarget() {
|
||||
return new CompileResult(new LocalScope(scope), this);
|
||||
}
|
||||
|
||||
public CompileResult(Environment env, Scope scope) {
|
||||
this.scope = scope;
|
||||
instructions = new ArrayList<>();
|
||||
children = new LinkedList<>();
|
||||
map = FunctionMap.builder();
|
||||
this.env = env;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
@ -10,36 +9,25 @@ 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.values.FunctionNode;
|
||||
|
||||
|
||||
public class CompoundNode extends Node {
|
||||
public final Node[] 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 resolve(CompileResult target) {
|
||||
for (var stm : statements) stm.resolve(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declare(CompileResult target) {
|
||||
for (var stm : statements) stm.declare(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||
List<Node> statements = new ArrayList<Node>();
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||
List<Node> statements = new Vector<Node>();
|
||||
if (separateFuncs) for (var stm : this.statements) {
|
||||
if (stm instanceof FunctionNode && ((FunctionNode)stm).statement) {
|
||||
for (var stm : this.statements) {
|
||||
if (stm instanceof FunctionStatementNode) {
|
||||
stm.compile(target, false);
|
||||
}
|
||||
else statements.add(stm);
|
||||
}
|
||||
else statements = List.of(this.statements);
|
||||
|
||||
var polluted = false;
|
||||
|
||||
@ -60,9 +48,8 @@ public class CompoundNode extends Node {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompoundNode(Location loc, boolean separateFuncs, Node ...statements) {
|
||||
public CompoundNode(Location loc, Node ...statements) {
|
||||
super(loc);
|
||||
this.separateFuncs = separateFuncs;
|
||||
this.statements = statements;
|
||||
}
|
||||
|
||||
@ -75,11 +62,18 @@ public class CompoundNode extends Node {
|
||||
if (!src.is(i + n, ",")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var res = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the comma");
|
||||
n += res.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;
|
||||
|
||||
return ParseRes.res(new CompoundNode(loc, false, prev, res.result), n);
|
||||
if (prev instanceof CompoundNode) {
|
||||
var children = new ArrayList<Node>();
|
||||
children.addAll(List.of(((CompoundNode)prev).statements));
|
||||
children.add(curr.result);
|
||||
|
||||
return ParseRes.res(new CompoundNode(loc, children.toArray(Node[]::new)), n);
|
||||
}
|
||||
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
|
||||
}
|
||||
public static ParseRes<CompoundNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
@ -109,6 +103,6 @@ public class CompoundNode extends Node {
|
||||
statements.add(res.result);
|
||||
}
|
||||
|
||||
return ParseRes.res(new CompoundNode(loc, true, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n);
|
||||
return ParseRes.res(new CompoundNode(loc, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
public class ExpressionNode {
|
||||
|
||||
}
|
130
src/java/me/topchetoeu/jscript/compilation/FunctionNode.java
Normal file
130
src/java/me/topchetoeu/jscript/compilation/FunctionNode.java
Normal file
@ -0,0 +1,130 @@
|
||||
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.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.LocalScope;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public abstract class FunctionNode extends Node {
|
||||
public final CompoundNode body;
|
||||
public final String[] args;
|
||||
public final Location end;
|
||||
|
||||
public abstract String name();
|
||||
|
||||
// @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.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
protected void compileLoadFunc(CompileResult target, int[] captures, String name) {
|
||||
target.add(Instruction.loadFunc(target.children.size(), name, captures));
|
||||
}
|
||||
|
||||
private CompileResult compileBody(CompileResult target, String name, boolean storeSelf, 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 funcScope = new FunctionScope(storeSelf ? name : null, args, target.scope);
|
||||
var subtarget = new CompileResult(target.env, new LocalScope(funcScope));
|
||||
|
||||
// compileStoreSelf(subtarget, pollute, bp);
|
||||
|
||||
body.resolve(subtarget);
|
||||
body.compile(subtarget, false);
|
||||
|
||||
subtarget.length = args.length;
|
||||
subtarget.scope.end();
|
||||
funcScope.end();
|
||||
subtarget.add(Instruction.ret()).setLocation(end);
|
||||
|
||||
if (pollute) compileLoadFunc(target, funcScope.getCaptureIndices(), name);
|
||||
|
||||
return target.addChild(subtarget);
|
||||
}
|
||||
|
||||
public void compile(CompileResult target, boolean pollute, boolean storeSelf, String name, BreakpointType bp) {
|
||||
if (this.name() != null) name = this.name();
|
||||
|
||||
compileBody(target, name, storeSelf, pollute, bp);
|
||||
}
|
||||
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, String[] args, CompoundNode body) {
|
||||
super(loc);
|
||||
|
||||
this.end = end;
|
||||
this.args = args;
|
||||
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 args = JavaScript.parseParamList(src, i + n);
|
||||
if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += args.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),
|
||||
args.result.toArray(String[]::new), body.result, name.result
|
||||
), n);
|
||||
else return ParseRes.res(new FunctionValueNode(
|
||||
loc, src.loc(i + n - 1),
|
||||
args.result.toArray(String[]::new), body.result, name.result
|
||||
), n);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
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(name, false, end);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
compile(target, true, false, this.name, bp);
|
||||
target.add(VariableNode.toSet(target, end, this.name, pollute, true));
|
||||
}
|
||||
|
||||
public FunctionStatementNode(Location loc, Location end, String[] args, CompoundNode body, String name) {
|
||||
super(loc, end, args, body);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
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) {
|
||||
compile(target, pollute, true, name, bp);
|
||||
}
|
||||
|
||||
public FunctionValueNode(Location loc, Location end, String[] args, CompoundNode body, String name) {
|
||||
super(loc, end, args, body);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ 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.ForOfNode;
|
||||
import me.topchetoeu.jscript.compilation.control.ForNode;
|
||||
import me.topchetoeu.jscript.compilation.control.IfNode;
|
||||
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||
@ -24,12 +23,14 @@ 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.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.compilation.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionNode;
|
||||
import me.topchetoeu.jscript.compilation.values.GlobalThisNode;
|
||||
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;
|
||||
@ -41,7 +42,6 @@ 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.compilation.values.operations.VariableIndexNode;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class JavaScript {
|
||||
@ -104,8 +104,8 @@ public class JavaScript {
|
||||
if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n);
|
||||
if (id.result.equals("undefined")) return ParseRes.res(new DiscardNode(loc, null), n);
|
||||
if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n);
|
||||
if (id.result.equals("this")) return ParseRes.res(new VariableIndexNode(loc, 0), n);
|
||||
if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexNode(loc, 1), n);
|
||||
if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n);
|
||||
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
|
||||
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n);
|
||||
|
||||
return ParseRes.failed();
|
||||
@ -187,7 +187,7 @@ public class JavaScript {
|
||||
SwitchNode::parse,
|
||||
ForNode::parse,
|
||||
ForInNode::parse,
|
||||
ForOfNode::parse,
|
||||
// ForOfNode::parse,
|
||||
DoWhileNode::parse,
|
||||
TryNode::parse,
|
||||
CompoundNode::parse,
|
||||
@ -257,7 +257,7 @@ public class JavaScript {
|
||||
|
||||
var res = parseStatement(src, i);
|
||||
|
||||
if (res.isError()) throw new SyntaxException(src.loc(i), res.error);
|
||||
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;
|
||||
@ -272,22 +272,17 @@ public class JavaScript {
|
||||
return !JavaScript.reserved.contains(name);
|
||||
}
|
||||
|
||||
public static CompileResult compile(Node ...statements) {
|
||||
var target = new CompileResult(new LocalScopeRecord());
|
||||
var stm = new CompoundNode(null, true, statements);
|
||||
|
||||
target.scope.define("this");
|
||||
target.scope.define("arguments");
|
||||
public static CompileResult compile(Environment env, Node ...statements) {
|
||||
var target = new CompileResult(env, new LocalScope(new GlobalScope()));
|
||||
var stm = new CompoundNode(null, statements);
|
||||
|
||||
try {
|
||||
stm.resolve(target);
|
||||
stm.compile(target, true);
|
||||
FunctionNode.checkBreakAndCont(target, 0);
|
||||
// FunctionNode.checkBreakAndCont(target, 0);
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
target = new CompileResult(new LocalScopeRecord());
|
||||
|
||||
target.scope.define("this");
|
||||
target.scope.define("arguments");
|
||||
target = new CompileResult(env, new LocalScope(new GlobalScope()));
|
||||
|
||||
target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
|
||||
}
|
||||
@ -298,6 +293,24 @@ public class JavaScript {
|
||||
}
|
||||
|
||||
public static CompileResult compile(Environment env, Filename filename, String raw) {
|
||||
return JavaScript.compile(JavaScript.parse(env, filename, 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);
|
||||
}
|
||||
}
|
||||
|
85
src/java/me/topchetoeu/jscript/compilation/LabelContext.java
Normal file
85
src/java/me/topchetoeu/jscript/compilation/LabelContext.java
Normal file
@ -0,0 +1,85 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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 Supplier<Instruction> getJump(int offset) {
|
||||
var res = get();
|
||||
if (res == null) return null;
|
||||
else return () -> Instruction.jmp(res.getAsInt() - offset);
|
||||
}
|
||||
public Supplier<Instruction> getJump(int offset, String name) {
|
||||
var res = get(name);
|
||||
if (res == null) return null;
|
||||
else return () -> Instruction.jmp(res.getAsInt() - offset);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -4,10 +4,9 @@ import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public abstract class Node {
|
||||
private Location _loc;
|
||||
private Location loc;
|
||||
|
||||
public boolean pure() { return false; }
|
||||
public void declare(CompileResult target) { }
|
||||
public void resolve(CompileResult target) {}
|
||||
|
||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||
int start = target.size();
|
||||
@ -18,10 +17,10 @@ public abstract class Node {
|
||||
compile(target, pollute, BreakpointType.NONE);
|
||||
}
|
||||
|
||||
public Location loc() { return _loc; }
|
||||
public void setLoc(Location loc) { _loc = loc; }
|
||||
public Location loc() { return loc; }
|
||||
public void setLoc(Location loc) { this.loc = loc; }
|
||||
|
||||
protected Node(Location loc) {
|
||||
this._loc = loc;
|
||||
this.loc = loc;
|
||||
}
|
||||
}
|
93
src/java/me/topchetoeu/jscript/compilation/NodeChildren.java
Normal file
93
src/java/me/topchetoeu/jscript/compilation/NodeChildren.java
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
29
src/java/me/topchetoeu/jscript/compilation/Path.java
Normal file
29
src/java/me/topchetoeu/jscript/compilation/Path.java
Normal file
@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Path<T extends Node> {
|
||||
public final Path<?> parent;
|
||||
public final Node node;
|
||||
|
||||
public Path<?> getParent(Predicate<Path<?>> predicate) {
|
||||
for (Path<?> it = this; it != null; it = it.parent) {
|
||||
if (predicate.test(it)) return it;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path<?> getParent(Class<? extends Node> type, Predicate<Path<?>> predicate) {
|
||||
for (Path<?> it = this; it != null; it = it.parent) {
|
||||
if (type.isInstance(it.node) && predicate.test(it)) return it;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path(Path<?> parent, Node node) {
|
||||
this.parent = parent;
|
||||
this.node = node;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class ThrowSyntaxNode extends Node {
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.throwSyntax(name));
|
||||
}
|
||||
|
||||
public ThrowSyntaxNode(SyntaxException e) {
|
||||
super(e.loc);
|
||||
this.name = e.msg;
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ 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.values.FunctionNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class VariableDeclareNode extends Node {
|
||||
public static class Pair {
|
||||
@ -26,23 +26,26 @@ public class VariableDeclareNode extends Node {
|
||||
|
||||
public final List<Pair> values;
|
||||
|
||||
@Override
|
||||
public void declare(CompileResult target) {
|
||||
for (var key : values) {
|
||||
target.scope.define(key.name);
|
||||
@Override public void resolve(CompileResult target) {
|
||||
for (var entry : values) {
|
||||
target.scope.define(entry.name, false, entry.location);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@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) {
|
||||
FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.storeVar(key));
|
||||
target.add(VariableNode.toSet(target, entry.location, entry.name, false, true));
|
||||
}
|
||||
else {
|
||||
target.add(() -> {
|
||||
var i = target.scope.get(entry.name, true);
|
||||
|
||||
if (i == null) return Instruction.globDef(entry.name);
|
||||
else return Instruction.nop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,13 +7,22 @@ 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) {
|
||||
target.add(Instruction.nop("break", label));
|
||||
var res = LabelContext.getBreak(target.env).getJump(target.size());
|
||||
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);
|
||||
|
||||
// target.add(Instruction.nop("break", label));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
|
@ -7,13 +7,22 @@ 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) {
|
||||
target.add(Instruction.nop("cont", label));
|
||||
var res = LabelContext.getCont(target.env).getJump(target.size());
|
||||
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);
|
||||
|
||||
// () -> Instruction.nop("cont", label));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
|
@ -16,8 +16,7 @@ public class DeleteNode extends Node {
|
||||
public final Node key;
|
||||
public final Node value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
value.compile(target, true);
|
||||
key.compile(target, true);
|
||||
|
||||
|
@ -7,28 +7,35 @@ 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 declare(CompileResult target) {
|
||||
body.declare(target);
|
||||
@Override public void resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@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();
|
||||
var end = new DeferredIntSupplier();
|
||||
var mid = new DeferredIntSupplier();
|
||||
|
||||
WhileNode.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
target.add(Instruction.jmpIf(start - end));
|
||||
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);
|
||||
|
||||
// WhileNode.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
target.add(Instruction.jmpIf(start - endI));
|
||||
}
|
||||
|
||||
public DoWhileNode(Location loc, String label, Node condition, Node body) {
|
||||
@ -42,7 +49,7 @@ public class DoWhileNode extends Node {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var labelRes = WhileNode.parseLabel(src, i + n);
|
||||
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||
n += labelRes.n;
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
|
||||
|
@ -8,8 +8,11 @@ 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;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class ForInNode extends Node {
|
||||
public final String varName;
|
||||
@ -18,16 +21,12 @@ public class ForInNode extends Node {
|
||||
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 resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
if (isDeclaration) target.scope.define(varName, false, loc());
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
var key = target.scope.getKey(varName);
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
|
||||
object.compile(target, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.keys(true));
|
||||
|
||||
@ -39,17 +38,28 @@ public class ForInNode extends Node {
|
||||
|
||||
target.add(Instruction.pushValue("value")).setLocation(varLocation);
|
||||
target.add(Instruction.loadMember()).setLocation(varLocation);
|
||||
target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
||||
target.add(VariableNode.toSet(target, loc(), varName, pollute, isDeclaration));
|
||||
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
||||
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int end = target.size();
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
int endI = target.size();
|
||||
|
||||
target.add(Instruction.jmp(start - end));
|
||||
// WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - endI));
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
||||
target.set(mid, Instruction.jmpIf(endI - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
@ -67,7 +77,7 @@ public class ForInNode extends Node {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var label = WhileNode.parseLabel(src, i + n);
|
||||
var label = JavaScript.parseLabel(src, i + n);
|
||||
n += label.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
|
@ -7,7 +7,9 @@ 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;
|
||||
import me.topchetoeu.jscript.compilation.VariableDeclareNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
|
||||
@ -16,27 +18,39 @@ public class ForNode extends Node {
|
||||
public final Node declaration, assignment, condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void declare(CompileResult target) {
|
||||
declaration.declare(target);
|
||||
body.declare(target);
|
||||
@Override public void resolve(CompileResult target) {
|
||||
declaration.resolve(target);
|
||||
body.resolve(target);
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@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();
|
||||
|
||||
var end = new DeferredIntSupplier();
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||
|
||||
body.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
|
||||
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
// int beforeAssign = target.size();
|
||||
assignment.compile(target, false, BreakpointType.STEP_OVER);
|
||||
int end = target.size();
|
||||
int endI = target.size();
|
||||
end.set(endI);
|
||||
|
||||
WhileNode.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
// WhileNode.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||
target.add(Instruction.jmp(start - endI));
|
||||
target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
@ -74,7 +88,7 @@ public class ForNode extends Node {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var labelRes = WhileNode.parseLabel(src, i + n);
|
||||
var labelRes = JavaScript.parseLabel(src, i + n);
|
||||
n += labelRes.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
|
@ -17,17 +17,15 @@ public class ForOfNode extends Node {
|
||||
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 resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
if (isDeclaration) target.scope.resolve(varName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@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 (key instanceof String) target.add(Instruction.globDef((String)key));
|
||||
|
||||
iterable.compile(target, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.dup());
|
||||
@ -79,7 +77,7 @@ public class ForOfNode extends Node {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var label = WhileNode.parseLabel(src, i + n);
|
||||
var label = JavaScript.parseLabel(src, i + n);
|
||||
n += label.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
|
@ -7,47 +7,87 @@ 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 declare(CompileResult target) {
|
||||
body.declare(target);
|
||||
if (elseBody != null) elseBody.declare(target);
|
||||
@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 i = target.temp();
|
||||
body.compile(target, pollute, breakpoint);
|
||||
int start = target.temp();
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||
|
||||
body.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
|
||||
|
||||
LabelContext.getBreak(target.env).pop(label);
|
||||
|
||||
int endI = target.size();
|
||||
target.set(i, Instruction.jmpIfNot(endI - i));
|
||||
end.set(endI);
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(endI - start));
|
||||
}
|
||||
else {
|
||||
int start = target.temp();
|
||||
body.compile(target, pollute, breakpoint);
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||
|
||||
var bodyTarget = target.subtarget();
|
||||
bodyTarget.add(() -> Instruction.stackAlloc(bodyTarget.scope.allocCount()));
|
||||
|
||||
body.compile(bodyTarget, false, BreakpointType.STEP_OVER);
|
||||
|
||||
bodyTarget.scope.end();
|
||||
bodyTarget.add(Instruction.stackFree(bodyTarget.scope.allocCount()));
|
||||
|
||||
int mid = target.temp();
|
||||
elseBody.compile(target, pollute, breakpoint);
|
||||
int end = target.size();
|
||||
|
||||
var elseTarget = target.subtarget();
|
||||
elseTarget.add(() -> Instruction.stackAlloc(elseTarget.scope.allocCount()));
|
||||
|
||||
body.compile(elseTarget, false, BreakpointType.STEP_OVER);
|
||||
|
||||
elseTarget.scope.end();
|
||||
elseTarget.add(Instruction.stackFree(elseTarget.scope.allocCount()));
|
||||
|
||||
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(end - mid));
|
||||
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) {
|
||||
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) {
|
||||
@ -71,12 +111,16 @@ public class IfNode extends Node {
|
||||
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), 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);
|
||||
@ -97,14 +141,13 @@ public class IfNode extends Node {
|
||||
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), n);
|
||||
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), n);
|
||||
return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class ReturnNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@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());
|
||||
|
@ -6,13 +6,14 @@ 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.Instruction.Type;
|
||||
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 {
|
||||
@ -30,9 +31,10 @@ public class SwitchNode extends Node {
|
||||
public final SwitchCase[] cases;
|
||||
public final Node[] body;
|
||||
public final int defaultI;
|
||||
public final String label;
|
||||
|
||||
@Override public void declare(CompileResult target) {
|
||||
for (var stm : body) stm.declare(target);
|
||||
@Override public void resolve(CompileResult target) {
|
||||
for (var stm : body) stm.resolve(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
@ -41,43 +43,55 @@ public class SwitchNode extends Node {
|
||||
|
||||
value.compile(target, true, BreakpointType.STEP_OVER);
|
||||
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||
|
||||
// TODO: create a jump map
|
||||
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);
|
||||
subtarget.add(Instruction.dup());
|
||||
ccase.value.compile(subtarget, true);
|
||||
subtarget.add(Instruction.operation(Operation.EQUALS));
|
||||
caseToStatement.put(subtarget.temp(), ccase.statementI);
|
||||
}
|
||||
|
||||
int start = target.temp();
|
||||
int start = subtarget.temp();
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||
for (var stm : body) {
|
||||
statementToIndex.put(statementToIndex.size(), target.size());
|
||||
stm.compile(target, false, BreakpointType.STEP_OVER);
|
||||
statementToIndex.put(statementToIndex.size(), subtarget.size());
|
||||
stm.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||
}
|
||||
LabelContext.getBreak(target.env).pop(label);
|
||||
|
||||
int end = target.size();
|
||||
target.add(Instruction.discard());
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start));
|
||||
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
||||
int endI = subtarget.size();
|
||||
end.set(endI);
|
||||
subtarget.add(Instruction.discard());
|
||||
if (pollute) subtarget.add(Instruction.pushUndefined());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start));
|
||||
else subtarget.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()));
|
||||
if (i == null) i = endI;
|
||||
subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SwitchNode(Location loc, Node value, int defaultI, SwitchCase[] cases, Node[] body) {
|
||||
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;
|
||||
@ -90,14 +104,14 @@ public class SwitchNode extends Node {
|
||||
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
|
||||
n += 4;
|
||||
|
||||
var valRes = JavaScript.parseExpression(src, i + n, 0);
|
||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'case'");
|
||||
n += valRes.n;
|
||||
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(valRes.result, n);
|
||||
return ParseRes.res(val.result, n);
|
||||
}
|
||||
private static ParseRes<Void> parseDefaultCase(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
@ -116,15 +130,19 @@ public class SwitchNode extends Node {
|
||||
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 valRes = JavaScript.parseExpression(src, i + n, 0);
|
||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a switch value");
|
||||
n += valRes.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");
|
||||
@ -177,7 +195,7 @@ public class SwitchNode extends Node {
|
||||
}
|
||||
|
||||
return ParseRes.res(new SwitchNode(
|
||||
loc, valRes.result, defaultI,
|
||||
loc, label.result, val.result, defaultI,
|
||||
cases.toArray(SwitchCase[]::new),
|
||||
statements.toArray(Node[]::new)
|
||||
), n);
|
||||
|
@ -12,8 +12,7 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class ThrowNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.throwInstr()).setLocation(loc());
|
||||
}
|
||||
|
@ -8,60 +8,98 @@ 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 TryNode extends Node {
|
||||
public final Node tryBody;
|
||||
public final Node catchBody;
|
||||
public final Node finallyBody;
|
||||
public final String name;
|
||||
public final String captureName;
|
||||
public final String label;
|
||||
|
||||
@Override public void declare(CompileResult target) {
|
||||
tryBody.declare(target);
|
||||
if (catchBody != null) catchBody.declare(target);
|
||||
if (finallyBody != null) finallyBody.declare(target);
|
||||
@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;
|
||||
|
||||
tryBody.compile(target, false);
|
||||
target.add(Instruction.tryEnd());
|
||||
LabelContext.getBreak(target.env).push(loc(), label, endSuppl);
|
||||
|
||||
{
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||
|
||||
tryBody.compile(subtarget, false);
|
||||
subtarget.add(Instruction.tryEnd());
|
||||
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
|
||||
}
|
||||
|
||||
if (catchBody != null) {
|
||||
catchStart = target.size() - start;
|
||||
target.scope.define(name, true);
|
||||
catchBody.compile(target, false);
|
||||
target.scope.undefine();
|
||||
target.add(Instruction.tryEnd());
|
||||
|
||||
var subtarget = target.subtarget();
|
||||
var decN = captureName != null ? 1 : 0;
|
||||
|
||||
if (captureName != null) subtarget.scope.defineStrict(captureName, false, catchBody.loc());
|
||||
|
||||
var _subtarget = subtarget;
|
||||
|
||||
subtarget.add(() -> Instruction.stackAlloc(_subtarget.scope.allocCount() - decN));
|
||||
|
||||
catchBody.compile(subtarget, false);
|
||||
|
||||
subtarget.add(Instruction.tryEnd());
|
||||
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount() - decN));
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
finallyStart = target.size() - start;
|
||||
finallyBody.compile(target, false);
|
||||
target.add(Instruction.tryEnd());
|
||||
|
||||
var subtarget = target.subtarget();
|
||||
finallyBody.compile(subtarget, false);
|
||||
subtarget.add(Instruction.tryEnd());
|
||||
}
|
||||
|
||||
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, Node tryBody, Node catchBody, Node finallyBody, String name) {
|
||||
public TryNode(Location loc, String label, Node tryBody, Node catchBody, Node finallyBody, String captureName) {
|
||||
super(loc);
|
||||
this.tryBody = tryBody;
|
||||
this.catchBody = catchBody;
|
||||
this.finallyBody = finallyBody;
|
||||
this.name = name;
|
||||
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;
|
||||
|
||||
@ -70,7 +108,7 @@ public class TryNode extends Node {
|
||||
n += tryBody.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
String name = null;
|
||||
String capture = null;
|
||||
Node catchBody = null, finallyBody = null;
|
||||
|
||||
if (Parsing.isIdentifier(src, i + n, "catch")) {
|
||||
@ -80,7 +118,7 @@ public class TryNode extends Node {
|
||||
n++;
|
||||
var nameRes = Parsing.parseIdentifier(src, i + n);
|
||||
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
|
||||
name = nameRes.result;
|
||||
capture = nameRes.result;
|
||||
n += nameRes.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
@ -108,6 +146,6 @@ public class TryNode extends Node {
|
||||
|
||||
if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally");
|
||||
|
||||
return ParseRes.res(new TryNode(loc, tryBody.result, catchBody, finallyBody, name), n);
|
||||
return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n);
|
||||
}
|
||||
}
|
||||
|
@ -2,53 +2,51 @@ package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.Instruction.Type;
|
||||
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 WhileNode extends Node {
|
||||
public final Node condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public void declare(CompileResult target) {
|
||||
body.declare(target);
|
||||
@Override public void resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@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);
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
|
||||
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int end = target.size();
|
||||
subtarget.scope.end();
|
||||
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
var endI = target.size();
|
||||
end.set(endI + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||
// replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end.getAsInt()));
|
||||
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public WhileNode(Location loc, String label, Node condition, Node body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
@ -56,24 +54,24 @@ public class WhileNode extends Node {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public static ParseRes<WhileNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var labelRes = WhileNode.parseLabel(src, i + n);
|
||||
n += labelRes.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();
|
||||
@ -83,18 +81,18 @@ public class WhileNode extends Node {
|
||||
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 while condition.");
|
||||
n += condRes.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 res = JavaScript.parseStatement(src, i + n);
|
||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a while body.");
|
||||
n += res.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, labelRes.result, condRes.result, res.result), n);
|
||||
return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n);
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,12 @@ import java.util.HashMap;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public class FunctionScope extends Scope {
|
||||
private final VariableList captures = new VariableList();
|
||||
private final VariableList locals = new VariableList(captures);
|
||||
private final VariableList captures = new VariableList().setIndexMap(v -> ~v);
|
||||
private final VariableList specials = new VariableList();
|
||||
private final VariableList locals = new VariableList(specials);
|
||||
private HashMap<VariableDescriptor, VariableDescriptor> childToParent = new HashMap<>();
|
||||
public final String selfName;
|
||||
public final VariableDescriptor selfVar;
|
||||
|
||||
private void removeCapture(String name) {
|
||||
var res = captures.remove(name);
|
||||
@ -28,10 +31,14 @@ public class FunctionScope extends Scope {
|
||||
}
|
||||
|
||||
@Override public VariableDescriptor get(String name, boolean capture) {
|
||||
if (specials.has(name)) return specials.get(name);
|
||||
if (locals.has(name)) return locals.get(name);
|
||||
if (captures.has(name)) return captures.get(name);
|
||||
if (selfName != null && selfName.equals(name)) return selfVar;
|
||||
|
||||
var parentVar = parent.get(name, true);
|
||||
if (parentVar == null) return null;
|
||||
|
||||
var childVar = captures.add(parentVar);
|
||||
|
||||
childToParent.put(childVar, parentVar);
|
||||
@ -40,6 +47,7 @@ public class FunctionScope extends Scope {
|
||||
}
|
||||
|
||||
@Override public boolean has(String name) {
|
||||
if (specials.has(name)) return true;
|
||||
if (locals.has(name)) return true;
|
||||
if (captures.has(name)) return true;
|
||||
if (parent != null) return parent.has(name);
|
||||
@ -47,9 +55,24 @@ public class FunctionScope extends Scope {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
@Override public boolean end() {
|
||||
if (!super.end()) return false;
|
||||
|
||||
captures.freeze();
|
||||
locals.freeze();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public int localsCount() {
|
||||
return locals.size() + specials.size();
|
||||
}
|
||||
@Override public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
@Override public int allocCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int offset() {
|
||||
return captures.size() + locals.size();
|
||||
}
|
||||
@ -66,6 +89,22 @@ public class FunctionScope extends Scope {
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionScope() { super(); }
|
||||
public FunctionScope(Scope parent) { super(parent); }
|
||||
public FunctionScope(String selfName, String[] args) {
|
||||
super();
|
||||
this.selfName = selfName;
|
||||
|
||||
if (selfName != null) this.selfVar = VariableDescriptor.of(selfName, true, -1);
|
||||
else this.selfVar = null;
|
||||
|
||||
for (var arg : args) specials.add(arg, false);
|
||||
}
|
||||
public FunctionScope(String selfName, String[] args, Scope parent) {
|
||||
super(parent);
|
||||
this.selfName = selfName;
|
||||
|
||||
if (selfName != null) this.selfVar = VariableDescriptor.of(selfName, true, -1);
|
||||
else this.selfVar = null;
|
||||
|
||||
for (var arg : args) specials.add(arg, false);
|
||||
}
|
||||
}
|
||||
|
@ -19,5 +19,15 @@ public final class GlobalScope extends Scope {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public int localsCount() {
|
||||
return 0;
|
||||
}
|
||||
@Override public int capturesCount() {
|
||||
return 0;
|
||||
}
|
||||
@Override public int allocCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public GlobalScope() { super(); }
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public class LocalScope extends Scope {
|
||||
public final class LocalScope extends Scope {
|
||||
private final VariableList locals = new VariableList();
|
||||
|
||||
@Override public int offset() {
|
||||
@ -43,6 +43,18 @@ public class LocalScope extends Scope {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public int localsCount() {
|
||||
if (parent == null) return 0;
|
||||
else return parent.localsCount();
|
||||
}
|
||||
@Override public int capturesCount() {
|
||||
if (parent == null) return 0;
|
||||
else return parent.capturesCount();
|
||||
}
|
||||
@Override public int allocCount() {
|
||||
return locals.size();
|
||||
}
|
||||
|
||||
public Iterable<VariableDescriptor> all() {
|
||||
return () -> locals.iterator();
|
||||
}
|
||||
|
@ -41,13 +41,17 @@ public abstract class Scope {
|
||||
*/
|
||||
public abstract int offset();
|
||||
|
||||
public abstract int localsCount();
|
||||
public abstract int capturesCount();
|
||||
public abstract int allocCount();
|
||||
|
||||
public boolean end() {
|
||||
if (!active) return false;
|
||||
|
||||
this.active = false;
|
||||
if (this.parent != null) {
|
||||
assert this.parent.child == this;
|
||||
this.parent.child = this;
|
||||
this.parent.child = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -4,22 +4,49 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
public class VariableList implements Iterable<VariableDescriptor> {
|
||||
public final class VariableList implements Iterable<VariableDescriptor> {
|
||||
private class ListVar extends VariableDescriptor {
|
||||
private ListVar next;
|
||||
private ListVar prev;
|
||||
private boolean frozen;
|
||||
private int index;
|
||||
|
||||
@Override public int index() {
|
||||
throw new RuntimeException("The index of a variable may not be retrieved until the scope has been finalized");
|
||||
// var res = 0;
|
||||
// if (offset != null) res = offset.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());
|
||||
}
|
||||
}
|
||||
|
||||
// for (var it = prev; it != null; it = it.prev) {
|
||||
// res++;
|
||||
// }
|
||||
var res = 0;
|
||||
if (offset != null) res = offset.getAsInt();
|
||||
|
||||
// return res;
|
||||
for (var it = prev; it != null; it = it.prev) {
|
||||
res++;
|
||||
}
|
||||
|
||||
return indexConverter == null ? res : indexConverter.applyAsInt(res);
|
||||
}
|
||||
|
||||
public ListVar freeze() {
|
||||
if (frozen) return this;
|
||||
this.frozen = true;
|
||||
|
||||
if (prev == null) return this;
|
||||
assert prev.frozen;
|
||||
|
||||
this.index = prev.index + 1;
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListVar(String name, boolean readonly, ListVar next, ListVar prev) {
|
||||
@ -38,6 +65,7 @@ public class VariableList implements Iterable<VariableDescriptor> {
|
||||
private ArrayList<VariableDescriptor> frozenList = null;
|
||||
|
||||
private final IntSupplier offset;
|
||||
private IntUnaryOperator indexConverter = null;
|
||||
|
||||
public boolean frozen() {
|
||||
if (frozenMap != null) {
|
||||
@ -60,26 +88,52 @@ public class VariableList implements Iterable<VariableDescriptor> {
|
||||
|
||||
public VariableDescriptor add(VariableDescriptor val) {
|
||||
return add(val.name, val.readonly);
|
||||
}
|
||||
public VariableDescriptor add(String name, boolean readonly) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
if (map.containsKey(name)) return map.get(name);
|
||||
}
|
||||
public VariableDescriptor add(String name, boolean readonly) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
if (map.containsKey(name)) return map.get(name);
|
||||
|
||||
var res = new ListVar(name, readonly, null, last);
|
||||
last.next = res;
|
||||
last = res;
|
||||
map.put(name, res);
|
||||
var res = new ListVar(name, readonly, null, last);
|
||||
|
||||
return res;
|
||||
}
|
||||
if (last != null) {
|
||||
assert first != null;
|
||||
|
||||
last.next = res;
|
||||
res.prev = last;
|
||||
|
||||
last = res;
|
||||
}
|
||||
else {
|
||||
first = last = res;
|
||||
}
|
||||
|
||||
map.put(name, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
public VariableDescriptor remove(String name) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
|
||||
var el = map.get(name);
|
||||
if (el == null) return null;
|
||||
|
||||
el.prev.next = el.next;
|
||||
el.next.prev = el.prev;
|
||||
if (el.prev != null) {
|
||||
assert el != first;
|
||||
el.prev.next = el.next;
|
||||
}
|
||||
else {
|
||||
assert el == first;
|
||||
first = first.next;
|
||||
}
|
||||
|
||||
if (el.next != null) {
|
||||
assert el != last;
|
||||
el.next.prev = el.prev;
|
||||
}
|
||||
else {
|
||||
assert el == last;
|
||||
last = last.prev;
|
||||
}
|
||||
|
||||
el.next = null;
|
||||
el.prev = null;
|
||||
@ -88,7 +142,8 @@ public class VariableList implements Iterable<VariableDescriptor> {
|
||||
}
|
||||
|
||||
public VariableDescriptor get(String name) {
|
||||
return map.get(name);
|
||||
if (frozen()) return frozenMap.get(name);
|
||||
else return map.get(name);
|
||||
}
|
||||
public VariableDescriptor get(int i) {
|
||||
if (frozen()) {
|
||||
@ -126,12 +181,17 @@ public class VariableList implements Iterable<VariableDescriptor> {
|
||||
frozenMap = new HashMap<>();
|
||||
frozenList = new ArrayList<>();
|
||||
|
||||
var i = 0;
|
||||
if (offset != null) i = offset.getAsInt();
|
||||
for (var it = first; it != null; ) {
|
||||
frozenMap.put(it.name, it);
|
||||
frozenList.add(it);
|
||||
|
||||
for (var it = first; it != null; it = it.next) {
|
||||
frozenMap.put(it.name, VariableDescriptor.of(it.name, it.readonly, i++));
|
||||
var tmp = it;
|
||||
it = it.next;
|
||||
tmp.freeze();
|
||||
}
|
||||
|
||||
map = null;
|
||||
first = last = null;
|
||||
}
|
||||
|
||||
@Override public Iterator<VariableDescriptor> iterator() {
|
||||
@ -161,6 +221,11 @@ public class VariableList implements Iterable<VariableDescriptor> {
|
||||
return res;
|
||||
}
|
||||
|
||||
public VariableList setIndexMap(IntUnaryOperator map) {
|
||||
indexConverter = map;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VariableList(IntSupplier offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
public ArgumentsNode(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
@ -11,17 +11,10 @@ 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 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));
|
||||
|
||||
|
@ -1,150 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.Instruction.Type;
|
||||
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.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class FunctionNode extends Node {
|
||||
public final CompoundNode 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, String name, 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(), name, 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;
|
||||
|
||||
compileBody(target, name, pollute || hasVar, bp);
|
||||
|
||||
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 FunctionNode(Location loc, Location end, String varName, String[] args, boolean statement, CompoundNode body) {
|
||||
super(loc);
|
||||
|
||||
this.end = end;
|
||||
this.varName = varName;
|
||||
this.statement = statement;
|
||||
|
||||
this.args = args;
|
||||
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 nameRes = Parsing.parseIdentifier(src, i + n);
|
||||
if (!nameRes.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
|
||||
n += nameRes.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var args = JavaScript.parseParamList(src, i + n);
|
||||
if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += args.n;
|
||||
|
||||
var res = CompoundNode.parse(src, i + n);
|
||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a compound statement for function.");
|
||||
n += res.n;
|
||||
|
||||
return ParseRes.res(new FunctionNode(
|
||||
loc, src.loc(i + n - 1),
|
||||
nameRes.result, args.result.toArray(String[]::new),
|
||||
statement, res.result
|
||||
), n);
|
||||
}
|
||||
}
|
@ -5,11 +5,9 @@ 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 boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
public class GlobalThisNode extends Node {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadGlob());
|
||||
}
|
||||
|
||||
|
@ -11,16 +11,19 @@ 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 FunctionNode func;
|
||||
|
||||
public ObjProp(String name, String access, FunctionNode func) {
|
||||
public final FunctionValueNode func;
|
||||
|
||||
public ObjProp(String name, String access, FunctionValueNode func) {
|
||||
this.name = name;
|
||||
this.access = access;
|
||||
this.func = func;
|
||||
@ -31,14 +34,6 @@ public class ObjectNode extends Node {
|
||||
public final Map<String, FunctionNode> getters;
|
||||
public final Map<String, FunctionNode> 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());
|
||||
|
||||
@ -114,7 +109,7 @@ public class ObjectNode extends Node {
|
||||
|
||||
return ParseRes.res(new ObjProp(
|
||||
name.result, access.result,
|
||||
new FunctionNode(loc, end, access + " " + name.result.toString(), params.result.toArray(String[]::new), false, body.result)
|
||||
new FunctionValueNode(loc, end, params.result.toArray(String[]::new), body.result, access + " " + name.result.toString())
|
||||
), n);
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,7 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class RegexNode extends Node {
|
||||
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) {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.loadRegex(pattern, flags));
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
|
||||
public class ThisNode extends Node {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadThis());
|
||||
}
|
||||
|
||||
public ThisNode(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
@ -11,22 +13,61 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.VariableAssignNode;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class VariableNode extends Node implements AssignableNode {
|
||||
public final String name;
|
||||
|
||||
@Override public boolean pure() { return false; }
|
||||
// @Override public EvalResult evaluate(CompileResult target) {
|
||||
// var i = target.scope.getKey(name);
|
||||
|
||||
@Override
|
||||
public Node toAssign(Node val, Operation operation) {
|
||||
// if (i instanceof String) return EvalResult.NONE;
|
||||
// else return EvalResult.UNKNOWN;
|
||||
// }
|
||||
|
||||
@Override public Node toAssign(Node val, Operation operation) {
|
||||
return new VariableAssignNode(loc(), name, val, operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
var i = target.scope.getKey(name);
|
||||
target.add(Instruction.loadVar(i));
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
var i = target.scope.get(name, true);
|
||||
|
||||
if (i == null) {
|
||||
target.add((Supplier<Instruction>)() -> {
|
||||
if (target.scope.has(name)) throw new SyntaxException(loc(), String.format("Cannot access '%s' before initialization", name));
|
||||
return Instruction.globGet(name);
|
||||
});
|
||||
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
else if (pollute) {
|
||||
target.add(Instruction.loadVar(i.index()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Supplier<Instruction> toGet(CompileResult target, Location loc, String name, Supplier<Instruction> onGlobal) {
|
||||
var i = target.scope.get(name, true);
|
||||
|
||||
if (i == null) return () -> {
|
||||
if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
|
||||
else return onGlobal.get();
|
||||
};
|
||||
else return () -> Instruction.loadVar(i.index());
|
||||
}
|
||||
public static Supplier<Instruction> toGet(CompileResult target, Location loc, String name) {
|
||||
return toGet(target, loc, name, () -> Instruction.globGet(name));
|
||||
}
|
||||
|
||||
|
||||
public static Supplier<Instruction> toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) {
|
||||
var i = target.scope.get(name, true);
|
||||
|
||||
if (i == null) return () -> {
|
||||
if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
|
||||
else return Instruction.globSet(name, keep, define);
|
||||
};
|
||||
else return () -> Instruction.storeVar(i.index(), keep);
|
||||
|
||||
}
|
||||
|
||||
public VariableNode(Location loc, String name) {
|
||||
|
@ -8,8 +8,6 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class BoolNode extends Node {
|
||||
public final boolean value;
|
||||
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushValue(value));
|
||||
}
|
||||
|
@ -6,10 +6,8 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
public class NullNode extends Node {
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.pushNull());
|
||||
if (pollute) target.add(Instruction.pushNull());
|
||||
}
|
||||
|
||||
public NullNode(Location loc) { super(loc); }
|
||||
|
@ -11,8 +11,6 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class NumberNode extends Node {
|
||||
public final double value;
|
||||
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushValue(value));
|
||||
}
|
||||
|
@ -11,8 +11,6 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class StringNode extends Node {
|
||||
public final String value;
|
||||
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushValue(value));
|
||||
}
|
||||
|
@ -13,8 +13,10 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ThisNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
@ -51,12 +53,18 @@ public class CallNode extends Node {
|
||||
else if (func instanceof VariableNode) {
|
||||
res = ((VariableNode)func).name;
|
||||
}
|
||||
else if (func instanceof VariableIndexNode) {
|
||||
var i = ((VariableIndexNode)func).index;
|
||||
|
||||
if (i == 0) res = "this";
|
||||
else if (i == 1) res = "arguments";
|
||||
else if (func instanceof ThisNode) {
|
||||
res = "this";
|
||||
}
|
||||
else if (func instanceof ArgumentsNode) {
|
||||
res = "arguments";
|
||||
}
|
||||
// else if (func instanceof VariableIndexNode) {
|
||||
// var i = ((VariableIndexNode)func).index;
|
||||
|
||||
// if (i == 0) res = "this";
|
||||
// else if (i == 1) res = "arguments";
|
||||
// }
|
||||
else if (func instanceof ArrayNode) {
|
||||
var els = new ArrayList<String>();
|
||||
|
||||
|
@ -9,10 +9,18 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
|
||||
public class DiscardNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override public boolean pure() { return value.pure(); }
|
||||
// @Override public EvalResult evaluate(CompileResult target) {
|
||||
// if (value == null) return EvalResult.FALSY;
|
||||
// var res = value.evaluate(target);
|
||||
|
||||
// if (res.isPure) return EvalResult.FALSY;
|
||||
// else if (res.never) return EvalResult.NEVER;
|
||||
// else return EvalResult.FALSY_IMPURE;
|
||||
// }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (value != null) value.compile(target, false);
|
||||
|
@ -13,8 +13,7 @@ public class IndexAssignNode extends Node {
|
||||
public final Node value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (operation != null) {
|
||||
object.compile(target, true);
|
||||
index.compile(target, true);
|
||||
|
@ -17,8 +17,7 @@ public class IndexNode extends Node implements AssignableNode {
|
||||
public final Node object;
|
||||
public final Node index;
|
||||
|
||||
@Override
|
||||
public Node toAssign(Node val, Operation operation) {
|
||||
@Override public Node toAssign(Node val, Operation operation) {
|
||||
return new IndexAssignNode(loc(), object, index, val, operation);
|
||||
}
|
||||
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
|
||||
@ -29,8 +28,7 @@ public class IndexNode extends Node implements AssignableNode {
|
||||
target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
compile(target, false, pollute);
|
||||
}
|
||||
|
||||
|
@ -12,10 +12,15 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class LazyAndNode extends Node {
|
||||
public final Node first, second;
|
||||
|
||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||
// @Override public EvalResult evaluate(CompileResult target) {
|
||||
// var firstRes = first.evaluate(target);
|
||||
// if (firstRes.falsy) return firstRes;
|
||||
// if (!firstRes.isPure) return firstRes;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
// return second.evaluate(target);
|
||||
// }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
first.compile(target, true);
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
int start = target.temp();
|
||||
|
@ -9,13 +9,19 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
|
||||
public class LazyOrNode extends Node {
|
||||
public final Node first, second;
|
||||
|
||||
@Override public boolean pure() { return first.pure() && second.pure(); }
|
||||
// @Override public EvalResult evaluate(CompileResult target) {
|
||||
// var firstRes = first.evaluate(target);
|
||||
// if (firstRes.truthy) return firstRes;
|
||||
// if (!firstRes.isPure) return firstRes;
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
// return second.evaluate(target);
|
||||
// }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
first.compile(target, true);
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
int start = target.temp();
|
||||
|
@ -93,14 +93,6 @@ public class OperationNode extends Node {
|
||||
public final Node[] 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);
|
||||
|
@ -8,26 +8,32 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class TypeofNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
// Not really pure, since a variable from the global scope could be accessed,
|
||||
// which could lead to code execution, that would get omitted
|
||||
@Override public boolean pure() { return true; }
|
||||
// @Override public EvalResult evaluate(CompileResult target) {
|
||||
// if (value instanceof VariableNode) {
|
||||
// var i = target.scope.getKey(((VariableNode)value).name);
|
||||
// if (i instanceof String) return EvalResult.NONE;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
if (value instanceof VariableNode) {
|
||||
var i = target.scope.getKey(((VariableNode)value).name);
|
||||
if (i instanceof String) {
|
||||
target.add(Instruction.typeof((String)i));
|
||||
return;
|
||||
}
|
||||
// return EvalResult.UNKNOWN;
|
||||
// }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (value instanceof VariableNode varNode) {
|
||||
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name)));
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
value.compile(target, pollute);
|
||||
target.add(Instruction.typeof());
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
public TypeofNode(Location loc, Node value) {
|
||||
|
@ -4,28 +4,25 @@ import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.FunctionNode;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class VariableAssignNode extends Node {
|
||||
public final String name;
|
||||
public final Node value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public boolean pure() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
var i = target.scope.getKey(name);
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (operation != null) {
|
||||
target.add(Instruction.loadVar(i));
|
||||
target.add(VariableNode.toGet(target, loc(), name));
|
||||
FunctionNode.compileWithName(value, target, true, name);
|
||||
target.add(Instruction.operation(operation));
|
||||
target.add(Instruction.storeVar(i, pollute));
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||
}
|
||||
else {
|
||||
FunctionNode.compileWithName(value, target, true, name);
|
||||
target.add(Instruction.storeVar(i, pollute));
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values.operations;
|
||||
|
||||
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 VariableIndexNode extends Node {
|
||||
public final int index;
|
||||
|
||||
@Override public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadVar(index));
|
||||
}
|
||||
|
||||
public VariableIndexNode(Location loc, int i) {
|
||||
super(loc);
|
||||
this.index = i;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package me.topchetoeu.jscript.common;
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import me.topchetoeu.jscript.common.FunctionBody;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||
@ -7,7 +8,7 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
|
||||
public interface Compiler {
|
||||
@ -30,7 +31,7 @@ public interface Compiler {
|
||||
});
|
||||
}
|
||||
|
||||
private static void registerFunc(Environment env, FunctionBody body, CompileResult res) {
|
||||
static void registerFunc(Environment env, FunctionBody body, CompileResult res) {
|
||||
var map = res.map();
|
||||
|
||||
DebugContext.get(env).onFunctionLoad(body, map);
|
||||
@ -41,6 +42,6 @@ public interface Compiler {
|
||||
}
|
||||
|
||||
public static CodeFunction compileFunc(Environment env, Filename filename, String raw) {
|
||||
return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new ValueVariable[0]);
|
||||
return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new Value[0][]);
|
||||
}
|
||||
}
|
@ -18,8 +18,7 @@ public class Engine implements EventLoop {
|
||||
this.micro = micro;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Task<?> other) {
|
||||
@Override public int compareTo(Task<?> other) {
|
||||
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package me.topchetoeu.jscript.runtime;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.topchetoeu.jscript.common.Compiler;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||
|
@ -1,7 +1,9 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
@ -11,8 +13,6 @@ import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.runtime.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
@ -97,14 +97,24 @@ public class Frame {
|
||||
}
|
||||
}
|
||||
|
||||
public final LocalScope scope;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
/**
|
||||
* A list of one-element arrays of values. This is so that we can pass captures to other functions
|
||||
*/
|
||||
public final Value[][] captures;
|
||||
public final List<Value[]> locals = new ArrayList<>();
|
||||
public final Value self;
|
||||
public final Value argsVal;
|
||||
public final Value[] args;
|
||||
public final boolean isNew;
|
||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||
public final CodeFunction function;
|
||||
public final Environment env;
|
||||
|
||||
public Value[] getVar(int i) {
|
||||
if (i < 0) return captures[~i];
|
||||
else return locals.get(i);
|
||||
}
|
||||
|
||||
public Value[] stack = new Value[32];
|
||||
public int stackPtr = 0;
|
||||
public int codePtr = 0;
|
||||
@ -178,6 +188,7 @@ public class Frame {
|
||||
}
|
||||
}
|
||||
catch (EngineException e) { error = e; }
|
||||
// catch (RuntimeException e) { error = EngineException.ofError("InternalError", e.getMessage()); }
|
||||
}
|
||||
|
||||
while (!tryStack.empty()) {
|
||||
@ -201,12 +212,12 @@ public class Frame {
|
||||
if (newCtx != tryCtx) {
|
||||
switch (newCtx.state) {
|
||||
case CATCH:
|
||||
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
|
||||
if (tryCtx.state != TryState.CATCH) locals.add(new Value[] { error.value });
|
||||
codePtr = tryCtx.catchStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
break;
|
||||
case FINALLY:
|
||||
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1);
|
||||
codePtr = tryCtx.finallyStart;
|
||||
stackPtr = tryCtx.restoreStackPtr;
|
||||
default:
|
||||
@ -222,7 +233,7 @@ public class Frame {
|
||||
}
|
||||
else {
|
||||
popTryFlag = false;
|
||||
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1);
|
||||
|
||||
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
|
||||
codePtr = tryCtx.finallyStart;
|
||||
@ -318,41 +329,45 @@ public class Frame {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object proxy of the local scope
|
||||
* Gets an object proxy of the local locals
|
||||
*/
|
||||
public ObjectValue getLocalScope() {
|
||||
var names = new String[scope.locals.length];
|
||||
var map = DebugContext.get(env).getMapOrEmpty(function);
|
||||
throw new RuntimeException("Not supported");
|
||||
|
||||
for (int i = 0; i < scope.locals.length; i++) {
|
||||
var name = "local_" + (i - 2);
|
||||
// var names = new String[locals.locals.length];
|
||||
// var map = DebugContext.get(env).getMapOrEmpty(function);
|
||||
|
||||
if (i == 0) name = "this";
|
||||
else if (i == 1) name = "arguments";
|
||||
else if (i < map.localNames.length) name = map.localNames[i];
|
||||
// for (int i = 0; i < locals.locals.length; i++) {
|
||||
// var name = "local_" + (i - 2);
|
||||
|
||||
names[i] = name;
|
||||
}
|
||||
// if (i == 0) name = "this";
|
||||
// else if (i == 1) name = "arguments";
|
||||
// else if (i < map.localNames.length) name = map.localNames[i];
|
||||
|
||||
return new ScopeValue(scope.locals, names);
|
||||
// names[i] = name;
|
||||
// }
|
||||
|
||||
// return new ScopeValue(locals, names);
|
||||
}
|
||||
/**
|
||||
* Gets an object proxy of the capture scope
|
||||
* Gets an object proxy of the capture locals
|
||||
*/
|
||||
public ObjectValue getCaptureScope() {
|
||||
var names = new String[scope.captures.length];
|
||||
// throw new RuntimeException("Not supported");
|
||||
|
||||
var names = new String[captures.length];
|
||||
var map = DebugContext.get(env).getMapOrEmpty(function);
|
||||
|
||||
for (int i = 0; i < scope.captures.length; i++) {
|
||||
for (int i = 0; i < captures.length; i++) {
|
||||
var name = "capture_" + (i - 2);
|
||||
if (i < map.captureNames.length) name = map.captureNames[i];
|
||||
names[i] = name;
|
||||
}
|
||||
|
||||
return new ScopeValue(scope.captures, names);
|
||||
return new ScopeValue(captures, names);
|
||||
}
|
||||
/**
|
||||
* Gets an array proxy of the local scope
|
||||
* Gets an array proxy of the local locals
|
||||
*/
|
||||
public ObjectValue getValStackScope() {
|
||||
return new ObjectValue() {
|
||||
@ -393,13 +408,25 @@ public class Frame {
|
||||
|
||||
public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) {
|
||||
this.env = env;
|
||||
this.args = args;
|
||||
this.isNew = isNew;
|
||||
this.scope = new LocalScope(func.body.localsN, func.captures);
|
||||
this.scope.get(0).set(thisArg);
|
||||
this.scope.get(1).value = new ArgumentsValue(this, args);
|
||||
|
||||
this.thisArg = thisArg;
|
||||
this.function = func;
|
||||
this.isNew = isNew;
|
||||
|
||||
this.self = thisArg;
|
||||
this.args = args;
|
||||
this.argsVal = new ArgumentsValue(this, args);
|
||||
this.captures = func.captures;
|
||||
|
||||
for (var i = 0; i < func.body.argsN; i++) {
|
||||
this.locals.add(new Value[] { args[i] });
|
||||
}
|
||||
|
||||
for (var i = 0; i < func.body.localsN; i++) {
|
||||
this.locals.add(new Value[] { Value.UNDEFINED });
|
||||
}
|
||||
|
||||
// this.locals = new LocalScope(func.body.localsN, func.captures);
|
||||
// this.locals.get(0).set(thisArg);
|
||||
// this.locals.get(1).value = new ArgumentsValue(this, args);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
@ -60,12 +58,6 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value execMakeVar(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
GlobalScope.get(env).define(env, false, name);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
|
||||
var setterVal = frame.pop();
|
||||
var getterVal = frame.pop();
|
||||
@ -146,12 +138,11 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadVar(Environment env, Instruction instr, Frame frame) {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(GlobalScope.get(env).get(env, (String)i));
|
||||
else frame.push(frame.scope.get((int)i).get(env));
|
||||
int i = instr.get(0);
|
||||
|
||||
frame.push(frame.getVar(i)[0]);
|
||||
frame.codePtr++;
|
||||
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadObj(Environment env, Instruction instr, Frame frame) {
|
||||
@ -162,7 +153,7 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) {
|
||||
frame.push(GlobalScope.get(env).object);
|
||||
frame.push(Value.global(env));
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
@ -176,10 +167,10 @@ public class InstructionRunner {
|
||||
private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) {
|
||||
int id = instr.get(0);
|
||||
String name = instr.get(1);
|
||||
var captures = new ValueVariable[instr.params.length - 2];
|
||||
var captures = new Value[instr.params.length - 2][];
|
||||
|
||||
for (var i = 2; i < instr.params.length; i++) {
|
||||
captures[i - 2] = frame.scope.get(instr.get(i));
|
||||
captures[i - 2] = frame.getVar(instr.get(i));
|
||||
}
|
||||
|
||||
var func = new CodeFunction(env, name, frame.function.body.children[id], captures);
|
||||
@ -231,20 +222,14 @@ public class InstructionRunner {
|
||||
}
|
||||
private static Value execStoreVar(Environment env, Instruction instr, Frame frame) {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) GlobalScope.get(env).set(env, (String)i, val);
|
||||
else frame.scope.get((int)i).set(env, val);
|
||||
int i = instr.get(0);
|
||||
|
||||
frame.getVar(i)[0] = val;
|
||||
frame.codePtr++;
|
||||
|
||||
return null;
|
||||
}
|
||||
private static Value execStoreSelfFunc(Environment env, Instruction instr, Frame frame) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(env, frame.function);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static Value execJmp(Environment env, Instruction instr, Frame frame) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
@ -271,12 +256,7 @@ public class InstructionRunner {
|
||||
String name = instr.get(0);
|
||||
Value obj;
|
||||
|
||||
if (name != null) {
|
||||
if (GlobalScope.get(env).has(env, name)) {
|
||||
obj = GlobalScope.get(env).get(env, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
if (name != null) obj = Value.global(env).getMember(env, name);
|
||||
else obj = frame.pop();
|
||||
|
||||
frame.push(obj.type());
|
||||
@ -309,6 +289,73 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
|
||||
if (!Value.global(env).hasMember(env, name, false)) {
|
||||
if (!Value.global(env).defineOwnMember(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
var res = Value.global(env).getMemberOrNull(env, name);
|
||||
|
||||
if (res == null) throw EngineException.ofSyntax(name + " is not defined");
|
||||
else frame.push(res);
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
var keep = (boolean)instr.get(1);
|
||||
var define = (boolean)instr.get(2);
|
||||
|
||||
var val = keep ? frame.peek() : frame.pop();
|
||||
var res = false;
|
||||
|
||||
if (define) res = Value.global(env).setMember(env, name, val);
|
||||
else res = Value.global(env).setMemberIfExists(env, name, val);
|
||||
|
||||
if (!res) throw EngineException.ofError("Couldn't set variable " + name);
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) {
|
||||
frame.push(frame.argsVal);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadThis(Environment env, Instruction instr, Frame frame) {
|
||||
frame.push(frame.self);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) {
|
||||
int n = instr.get(0);
|
||||
|
||||
for (var i = 0; i < n; i++) frame.locals.add(new Value[] { Value.UNDEFINED });
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execStackFree(Environment env, Instruction instr, Frame frame) {
|
||||
int n = instr.get(0);
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
frame.locals.remove(frame.locals.size() - 1);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Value exec(Environment env, Instruction instr, Frame frame) {
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(env, instr, frame);
|
||||
@ -335,12 +382,12 @@ public class InstructionRunner {
|
||||
case LOAD_MEMBER: return execLoadMember(env, instr, frame);
|
||||
case LOAD_REGEX: return execLoadRegEx(env, instr, frame);
|
||||
case LOAD_GLOB: return execLoadGlob(env, instr, frame);
|
||||
case LOAD_ARGS: return execLoadArgs(env, instr, frame);
|
||||
case LOAD_THIS: return execLoadThis(env, instr, frame);
|
||||
|
||||
case DISCARD: return execDiscard(env, instr, frame);
|
||||
case STORE_MEMBER: return execStoreMember(env, instr, frame);
|
||||
case STORE_VAR: return execStoreVar(env, instr, frame);
|
||||
case STORE_SELF_FUNC: return execStoreSelfFunc(env, instr, frame);
|
||||
case MAKE_VAR: return execMakeVar(env, instr, frame);
|
||||
|
||||
case KEYS: return execKeys(env, instr, frame);
|
||||
case DEF_PROP: return execDefProp(env, instr, frame);
|
||||
@ -353,6 +400,13 @@ public class InstructionRunner {
|
||||
|
||||
case OPERATION: return execOperation(env, instr, frame);
|
||||
|
||||
case GLOB_DEF: return exexGlobDef(env, instr, frame);
|
||||
case GLOB_GET: return exexGlobGet(env, instr, frame);
|
||||
case GLOB_SET: return exexGlobSet(env, instr, frame);
|
||||
|
||||
case STACK_ALLOC: return execStackAlloc(env, instr, frame);
|
||||
case STACK_FREE: return execStackFree(env, instr, frame);
|
||||
|
||||
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import java.nio.file.Path;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import me.topchetoeu.jscript.common.Compiler;
|
||||
import me.topchetoeu.jscript.common.Metadata;
|
||||
import me.topchetoeu.jscript.common.Reading;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
@ -16,7 +15,6 @@ import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
@ -324,18 +322,17 @@ public class SimpleRepl {
|
||||
// environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs));
|
||||
// environment.add(Compiler.KEY, new JSCompiler(environment));
|
||||
environment.add(EventLoop.KEY, engine);
|
||||
environment.add(GlobalScope.KEY, new GlobalScope());
|
||||
environment.add(DebugContext.KEY, new DebugContext());
|
||||
// environment.add(EventLoop.KEY, engine);
|
||||
environment.add(Compiler.KEY, Compiler.DEFAULT);
|
||||
|
||||
var glob = GlobalScope.get(environment);
|
||||
var glob = Value.global(environment);
|
||||
|
||||
glob.define(null, false, new NativeFunction("exit", args -> {
|
||||
glob.defineOwnMember(null, "exit", new NativeFunction("exit", args -> {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new InterruptException();
|
||||
}));
|
||||
glob.define(null, false, new NativeFunction("log", args -> {
|
||||
glob.defineOwnMember(null, "print", new NativeFunction("print", args -> {
|
||||
for (var el : args.args) {
|
||||
if (el instanceof StringValue) System.out.print(((StringValue)el).value);
|
||||
else System.out.print(el.toReadable(args.env));
|
||||
@ -356,7 +353,7 @@ public class SimpleRepl {
|
||||
EventLoop.get(environment).pushMsg(
|
||||
false, environment,
|
||||
Filename.parse("jscript://init.js"), Reading.resourceToString("lib/index.js"),
|
||||
Value.UNDEFINED, GlobalScope.get(environment).object, primordials(environment)
|
||||
Value.UNDEFINED, Value.global(environment), primordials(environment)
|
||||
).get();
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,8 @@ public class EngineException extends RuntimeException {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(proto);
|
||||
|
||||
if (msg == null) msg = "";
|
||||
|
||||
if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name)));
|
||||
res.defineOwnMember(Environment.empty(), "message", FieldMember.of(new StringValue(msg)));
|
||||
return res;
|
||||
|
@ -7,7 +7,7 @@ public class SyntaxException extends RuntimeException {
|
||||
public final String msg;
|
||||
|
||||
public SyntaxException(Location loc, String msg) {
|
||||
super(String.format("Syntax error (at %s): %s", loc, msg));
|
||||
super(String.format("Syntax error %s: %s", loc, msg));
|
||||
this.loc = loc;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
@ -1,74 +0,0 @@
|
||||
package me.topchetoeu.jscript.runtime.scope;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||
|
||||
public class GlobalScope {
|
||||
public static final Key<GlobalScope> KEY = Key.of();
|
||||
|
||||
public final ObjectValue object;
|
||||
|
||||
public boolean has(Environment ext, String name) {
|
||||
return object.hasMember(ext, new StringValue(name), false);
|
||||
}
|
||||
|
||||
public GlobalScope child() {
|
||||
var res = new GlobalScope();
|
||||
res.object.setPrototype(null, this.object);
|
||||
return res;
|
||||
}
|
||||
|
||||
public void define(Environment ext, String name, Variable variable) {
|
||||
object.defineOwnMember(ext, name, variable.toField(true, true));
|
||||
}
|
||||
public void define(Environment ext, boolean readonly, String name, Value val) {
|
||||
object.defineOwnMember(ext, name, FieldMember.of(val, !readonly));
|
||||
}
|
||||
public void define(Environment ext, boolean readonly, String ...names) {
|
||||
for (var name : names) define(ext, name, new ValueVariable(readonly, Value.UNDEFINED));
|
||||
}
|
||||
public void define(Environment ext, boolean readonly, FunctionValue val) {
|
||||
define(ext, readonly, val.name, val);
|
||||
}
|
||||
|
||||
public Value get(Environment env, String name) {
|
||||
if (!object.hasMember(env, name, false)) throw EngineException.ofSyntax(name + " is not defined");
|
||||
else return object.getMember(env, name);
|
||||
}
|
||||
public void set(Environment ext, String name, Value val) {
|
||||
if (!object.hasMember(ext, name, false)) throw EngineException.ofSyntax(name + " is not defined");
|
||||
if (!object.setMember(ext, name, val)) throw EngineException.ofSyntax("Assignment to constant variable");
|
||||
}
|
||||
|
||||
public Set<String> keys() {
|
||||
var res = new HashSet<String>();
|
||||
|
||||
for (var key : keys()) {
|
||||
if (key instanceof String) res.add((String)key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public GlobalScope() {
|
||||
this.object = new ObjectValue();
|
||||
this.object.setPrototype(null, null);
|
||||
}
|
||||
public GlobalScope(ObjectValue val) {
|
||||
this.object = val;
|
||||
}
|
||||
|
||||
public static GlobalScope get(Environment ext) {
|
||||
if (ext.has(KEY)) return ext.get(KEY);
|
||||
else return new GlobalScope();
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package me.topchetoeu.jscript.runtime.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScope {
|
||||
public final ValueVariable[] captures;
|
||||
public final ValueVariable[] locals;
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
||||
|
||||
public int size() {
|
||||
return captures.length + locals.length;
|
||||
}
|
||||
|
||||
public LocalScope(int n, ValueVariable[] captures) {
|
||||
locals = new ValueVariable[n];
|
||||
this.captures = captures;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
locals[i] = new ValueVariable(false, null);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package me.topchetoeu.jscript.runtime.scope;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
|
||||
public class ValueVariable implements Variable {
|
||||
public boolean readonly;
|
||||
public Value value;
|
||||
|
||||
@Override public boolean readonly() { return readonly; }
|
||||
@Override public final Value get(Environment env) { return get(); }
|
||||
@Override public final boolean set(Environment env, Value val) { return set(val); }
|
||||
|
||||
public Value get() { return value; }
|
||||
public boolean set(Value val) {
|
||||
if (readonly) return false;
|
||||
this.value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
public ValueVariable(boolean readonly, Value val) {
|
||||
this.readonly = readonly;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package me.topchetoeu.jscript.runtime.scope;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
|
||||
public interface Variable {
|
||||
Value get(Environment env);
|
||||
default boolean readonly() { return true; }
|
||||
default boolean set(Environment env, Value val) { return false; }
|
||||
|
||||
default FieldMember toField(boolean configurable, boolean enumerable) {
|
||||
var self = this;
|
||||
|
||||
return new FieldMember(!readonly(), configurable, enumerable) {
|
||||
@Override public Value get(Environment env, Value _self) {
|
||||
return self.get(env);
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value _self) {
|
||||
return self.set(env, val);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -64,6 +64,7 @@ public abstract class Value {
|
||||
public static final Key<ObjectValue> SYNTAX_ERR_PROTO = Key.of();
|
||||
public static final Key<ObjectValue> TYPE_ERR_PROTO = Key.of();
|
||||
public static final Key<ObjectValue> RANGE_ERR_PROTO = Key.of();
|
||||
public static final Key<ObjectValue> GLOBAL = Key.of();
|
||||
|
||||
public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined"));
|
||||
public static final VoidValue NULL = new VoidValue("null", new StringValue("object"));
|
||||
@ -278,13 +279,31 @@ public abstract class Value {
|
||||
return deleteOwnMember(env, new KeyCache(key));
|
||||
}
|
||||
|
||||
public final Value getMember(Environment env, KeyCache key) {
|
||||
public final Value getMemberOrNull(Environment env, KeyCache key) {
|
||||
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
|
||||
var member = obj.getOwnMember(env, key);
|
||||
if (member != null) return member.get(env, obj);
|
||||
}
|
||||
|
||||
return Value.UNDEFINED;
|
||||
return null;
|
||||
}
|
||||
public final Value getMemberOrNull(Environment env, Value key) {
|
||||
return getMemberOrNull(env, new KeyCache(key));
|
||||
}
|
||||
public final Value getMemberOrNull(Environment env, String key) {
|
||||
return getMemberOrNull(env, new KeyCache(key));
|
||||
}
|
||||
public final Value getMemberOrNull(Environment env, int key) {
|
||||
return getMemberOrNull(env, new KeyCache(key));
|
||||
}
|
||||
public final Value getMemberOrNull(Environment env, double key) {
|
||||
return getMemberOrNull(env, new KeyCache(key));
|
||||
}
|
||||
|
||||
public final Value getMember(Environment env, KeyCache key) {
|
||||
var res = getMemberOrNull(env, key);
|
||||
if (res != null) return res;
|
||||
else return Value.UNDEFINED;
|
||||
}
|
||||
public final Value getMember(Environment env, Value key) {
|
||||
return getMember(env, new KeyCache(key));
|
||||
@ -330,6 +349,33 @@ public abstract class Value {
|
||||
return setMember(env, new KeyCache(key), val);
|
||||
}
|
||||
|
||||
public final boolean setMemberIfExists(Environment env, KeyCache key, Value val) {
|
||||
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
|
||||
var member = obj.getOwnMember(env, key);
|
||||
if (member != null) {
|
||||
if (member.set(env, val, obj)) {
|
||||
if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public final boolean setMemberIfExists(Environment env, Value key, Value val) {
|
||||
return setMemberIfExists(env, new KeyCache(key), val);
|
||||
}
|
||||
public final boolean setMemberIfExists(Environment env, String key, Value val) {
|
||||
return setMemberIfExists(env, new KeyCache(key), val);
|
||||
}
|
||||
public final boolean setMemberIfExists(Environment env, int key, Value val) {
|
||||
return setMemberIfExists(env, new KeyCache(key), val);
|
||||
}
|
||||
public final boolean setMemberIfExists(Environment env, double key, Value val) {
|
||||
return setMemberIfExists(env, new KeyCache(key), val);
|
||||
}
|
||||
|
||||
public final boolean hasMember(Environment env, KeyCache key, boolean own) {
|
||||
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
|
||||
if (obj.getOwnMember(env, key) != null) return true;
|
||||
@ -478,13 +524,11 @@ public abstract class Value {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
@Override public boolean hasNext() {
|
||||
loadNext();
|
||||
return supplier != null;
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
@Override public Object next() {
|
||||
loadNext();
|
||||
var res = value;
|
||||
value = null;
|
||||
@ -632,4 +676,8 @@ public abstract class Value {
|
||||
return prefix + " internal error " + str.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static final ObjectValue global(Environment env) {
|
||||
return env.initFrom(GLOBAL, () -> new ObjectValue());
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,11 @@ package me.topchetoeu.jscript.runtime.values.functions;
|
||||
import me.topchetoeu.jscript.common.FunctionBody;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.Frame;
|
||||
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
public final FunctionBody body;
|
||||
public final ValueVariable[] captures;
|
||||
public final Value[][] captures;
|
||||
public Environment env;
|
||||
|
||||
@Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) {
|
||||
@ -26,7 +25,7 @@ public class CodeFunction extends FunctionValue {
|
||||
}
|
||||
}
|
||||
|
||||
public CodeFunction(Environment env, String name, FunctionBody body, ValueVariable[] captures) {
|
||||
public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) {
|
||||
super(name, body.argsN);
|
||||
this.captures = captures;
|
||||
this.env = env;
|
||||
|
@ -201,12 +201,10 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
|
||||
return new Iterator<>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
@Override public boolean hasNext() {
|
||||
return i < size();
|
||||
}
|
||||
@Override
|
||||
public Value next() {
|
||||
@Override public Value next() {
|
||||
if (!hasNext()) return null;
|
||||
return get(i++);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.topchetoeu.jscript.runtime.values.objects;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
|
||||
@ -15,17 +14,18 @@ public class ScopeValue extends ObjectValue {
|
||||
}
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return variables[i].get(env);
|
||||
return variables[i][0];
|
||||
}
|
||||
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
return variables[i].set(env, val);
|
||||
variables[i][0] = val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public final ValueVariable[] variables;
|
||||
public final Value[][] variables;
|
||||
|
||||
public ScopeValue(ValueVariable[] variables, String[] names) {
|
||||
public ScopeValue(Value[][] variables, String[] names) {
|
||||
this.variables = variables;
|
||||
for (var i = 0; i < names.length && i < variables.length; i++) {
|
||||
defineOwnMember(Environment.empty(), i, new VariableField(i));
|
||||
|
Loading…
Reference in New Issue
Block a user