Merge pull request #28 from TopchetoEU:TopchetoEU/destructing
TopchetoEU/destructing
This commit is contained in:
commit
b5b7781136
@ -56,9 +56,10 @@ public class Instruction {
|
||||
STORE_MEMBER_STR(0x4B),
|
||||
|
||||
DEF_PROP(0x50),
|
||||
KEYS(0x51),
|
||||
TYPEOF(0x52),
|
||||
OPERATION(0x53),
|
||||
DEF_FIELD(0x51),
|
||||
KEYS(0x52),
|
||||
TYPEOF(0x53),
|
||||
OPERATION(0x54),
|
||||
|
||||
GLOB_GET(0x60),
|
||||
GLOB_SET(0x61),
|
||||
@ -341,8 +342,8 @@ public class Instruction {
|
||||
return new Instruction(Type.GLOB_DEF, name);
|
||||
}
|
||||
|
||||
public static Instruction globGet(String name) {
|
||||
return new Instruction(Type.GLOB_GET, name);
|
||||
public static Instruction globGet(String name, boolean force) {
|
||||
return new Instruction(Type.GLOB_GET, name, force);
|
||||
}
|
||||
public static Instruction globSet(String name, boolean keep, boolean define) {
|
||||
return new Instruction(Type.GLOB_SET, name, keep, define);
|
||||
@ -404,10 +405,10 @@ public class Instruction {
|
||||
return new Instruction(Type.LOAD_ARR, count);
|
||||
}
|
||||
public static Instruction dup() {
|
||||
return new Instruction(Type.DUP, 1);
|
||||
return new Instruction(Type.DUP, 1, 0);
|
||||
}
|
||||
public static Instruction dup(int count) {
|
||||
return new Instruction(Type.DUP, count);
|
||||
public static Instruction dup(int count, int offset) {
|
||||
return new Instruction(Type.DUP, count, offset);
|
||||
}
|
||||
|
||||
public static Instruction storeVar(int i) {
|
||||
@ -435,7 +436,7 @@ public class Instruction {
|
||||
return new Instruction(Type.STORE_MEMBER_INT, key, false);
|
||||
}
|
||||
public static Instruction storeMember(int key, boolean keep) {
|
||||
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
|
||||
return new Instruction(Type.STORE_MEMBER_INT, key, keep);
|
||||
}
|
||||
|
||||
public static Instruction discard() {
|
||||
@ -449,12 +450,15 @@ public class Instruction {
|
||||
return new Instruction(Type.TYPEOF, varName);
|
||||
}
|
||||
|
||||
public static Instruction keys(boolean forInFormat) {
|
||||
return new Instruction(Type.KEYS, forInFormat);
|
||||
public static Instruction keys(boolean own, boolean onlyEnumerable) {
|
||||
return new Instruction(Type.KEYS, own, onlyEnumerable);
|
||||
}
|
||||
|
||||
public static Instruction defProp() {
|
||||
return new Instruction(Type.DEF_PROP);
|
||||
public static Instruction defProp(boolean setter) {
|
||||
return new Instruction(Type.DEF_PROP, setter);
|
||||
}
|
||||
public static Instruction defField() {
|
||||
return new Instruction(Type.DEF_FIELD);
|
||||
}
|
||||
|
||||
public static Instruction operation(Operation op) {
|
||||
|
@ -51,6 +51,7 @@ public class ParseRes<T> {
|
||||
return new ParseRes<T>(State.FAILED, null, null, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> error(Location loc, String error) {
|
||||
// TODO: differentiate definitive and probable errors
|
||||
return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
||||
}
|
||||
public static <T> ParseRes<T> res(T val, int i) {
|
||||
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
|
||||
public interface AssignableNode {
|
||||
public abstract Node toAssign(Node val, Operation operation);
|
||||
}
|
@ -13,7 +13,7 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
|
||||
public class CompoundNode extends Node {
|
||||
public final Node[] statements;
|
||||
public final boolean hasScope;
|
||||
public boolean hasScope;
|
||||
public Location end;
|
||||
|
||||
@Override public void resolve(CompileResult target) {
|
||||
|
@ -9,6 +9,7 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
|
||||
public class FunctionArrowNode extends FunctionNode {
|
||||
@Override public String name() { return null; }
|
||||
@ -34,7 +35,7 @@ public class FunctionArrowNode extends FunctionNode {
|
||||
Parameters params;
|
||||
|
||||
if (src.is(i + n, "(")) {
|
||||
var paramsRes = JavaScript.parseParameters(src, i + n);
|
||||
var paramsRes = Parameters.parseParameters(src, i + n);
|
||||
if (!paramsRes.isSuccess()) return paramsRes.chainError();
|
||||
n += paramsRes.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
@ -42,14 +43,12 @@ public class FunctionArrowNode extends FunctionNode {
|
||||
params = paramsRes.result;
|
||||
}
|
||||
else {
|
||||
var singleParam = Parsing.parseIdentifier(src, i + n);
|
||||
var singleParam = Pattern.parse(src, i + n, true);
|
||||
if (!singleParam.isSuccess()) return ParseRes.failed();
|
||||
|
||||
var paramLoc = src.loc(i + n);
|
||||
n += singleParam.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params = new Parameters(List.of(new Parameter(paramLoc, singleParam.result, null)));
|
||||
params = new Parameters(List.of(singleParam.result));
|
||||
}
|
||||
|
||||
if (!src.is(i + n, "=>")) return ParseRes.failed();
|
||||
|
@ -1,16 +1,15 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public abstract class FunctionNode extends Node {
|
||||
public final CompoundNode body;
|
||||
@ -33,40 +32,41 @@ public abstract class FunctionNode extends Node {
|
||||
return new CompileResult(env, scope, params.params.size(), target -> {
|
||||
if (params.params.size() > 0) {
|
||||
target.add(Instruction.loadArgs(true));
|
||||
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1));
|
||||
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0));
|
||||
var i = 0;
|
||||
|
||||
for (var param : params.params) {
|
||||
if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed");
|
||||
if (!JavaScript.checkVarName(param.name)) {
|
||||
throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name));
|
||||
}
|
||||
var varI = scope.define(new Variable(param.name, false), param.loc);
|
||||
|
||||
target.add(Instruction.loadMember(i++));
|
||||
param.destruct(target, DeclarationType.VAR, true);
|
||||
// if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed");
|
||||
// if (!JavaScript.checkVarName(param.name)) {
|
||||
// throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name));
|
||||
// }
|
||||
// var varI = scope.define(new Variable(param.name, false), param.loc);
|
||||
|
||||
if (param.node != null) {
|
||||
var end = new DeferredIntSupplier();
|
||||
// if (param.node != null) {
|
||||
// var end = new DeferredIntSupplier();
|
||||
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.pushUndefined());
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
target.add(Instruction.jmpIfNot(end));
|
||||
target.add(Instruction.discard());
|
||||
param.node.compile(target, true);
|
||||
// target.add(Instruction.dup());
|
||||
// target.add(Instruction.pushUndefined());
|
||||
// target.add(Instruction.operation(Operation.EQUALS));
|
||||
// target.add(Instruction.jmpIfNot(end));
|
||||
// target.add(Instruction.discard());
|
||||
// param.node.compile(target, true);
|
||||
|
||||
end.set(target.size());
|
||||
}
|
||||
// end.set(target.size());
|
||||
// }
|
||||
|
||||
target.add(_i -> varI.index().toSet(false));
|
||||
// target.add(_i -> varI.index().toSet(false));
|
||||
}
|
||||
}
|
||||
|
||||
if (params.restName != null) {
|
||||
if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
|
||||
var restVar = scope.define(new Variable(params.restName, false), params.restLocation);
|
||||
if (params.rest != null) {
|
||||
target.add(Instruction.loadRestArgs(params.params.size()));
|
||||
target.add(_i -> restVar.index().toSet(false));
|
||||
params.rest.destruct(target, DeclarationType.VAR, true);
|
||||
// if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
|
||||
// var restVar = scope.define(new Variable(params.restName, false), params.restLocation);
|
||||
// target.add(_i -> restVar.index().toSet(false));
|
||||
}
|
||||
|
||||
if (selfName != null && !scope.has(name, false)) {
|
||||
@ -107,6 +107,7 @@ public abstract class FunctionNode extends Node {
|
||||
this.end = end;
|
||||
this.params = params;
|
||||
this.body = body;
|
||||
this.body.hasScope = false;
|
||||
}
|
||||
|
||||
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
|
||||
@ -130,7 +131,7 @@ public abstract class FunctionNode extends Node {
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var params = JavaScript.parseParameters(src, i + n);
|
||||
var params = Parameters.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += params.n;
|
||||
|
||||
|
@ -39,6 +39,7 @@ import me.topchetoeu.jscript.compilation.values.operations.ChangeNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.OperationNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.PostfixNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
@ -140,8 +141,8 @@ public final class JavaScript {
|
||||
ParseRes<Node> res = ParseRes.first(src, i + n,
|
||||
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
|
||||
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
|
||||
(s, j) -> ChangeNode.parsePostfixIncrease(s, j, _prev, precedence),
|
||||
(s, j) -> ChangeNode.parsePostfixDecrease(s, j, _prev, precedence),
|
||||
(s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
|
||||
(s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
|
||||
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
|
||||
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
|
||||
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
|
||||
@ -221,71 +222,6 @@ public final class JavaScript {
|
||||
return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<Parameters> parseParameters(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += openParen.n;
|
||||
|
||||
var params = new ArrayList<Parameter>();
|
||||
|
||||
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||
n += closeParen.n;
|
||||
|
||||
if (!closeParen.isSuccess()) {
|
||||
while (true) {
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "...")) {
|
||||
n += 3;
|
||||
var restLoc = src.loc(i);
|
||||
|
||||
var restName = Parsing.parseIdentifier(src, i + n);
|
||||
if (!restName.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
|
||||
n += restName.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
|
||||
n++;
|
||||
|
||||
return ParseRes.res(new Parameters(params, restName.result, restLoc), n);
|
||||
}
|
||||
|
||||
var paramLoc = src.loc(i);
|
||||
|
||||
var name = Parsing.parseIdentifier(src, i + n);
|
||||
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "=")) {
|
||||
n++;
|
||||
|
||||
var val = parseExpression(src, i + n, 2);
|
||||
if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter default value");
|
||||
n += val.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params.add(new Parameter(paramLoc, name.result, val.result));
|
||||
}
|
||||
else params.add(new Parameter(paramLoc, name.result, null));
|
||||
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
}
|
||||
|
||||
if (src.is(i + n, ")")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(new Parameters(params), n);
|
||||
}
|
||||
|
||||
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
|
||||
var res = Parsing.parseIdentifier(src, i);
|
||||
if (!res.isSuccess()) return res.chainError();
|
||||
|
@ -1,29 +1,84 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
|
||||
|
||||
public final class Parameters {
|
||||
public final int length;
|
||||
public final List<Parameter> params;
|
||||
public final String restName;
|
||||
public final Location restLocation;
|
||||
public final List<Pattern> params;
|
||||
public final Pattern rest;
|
||||
|
||||
public Parameters(List<Parameter> params, String restName, Location restLocation) {
|
||||
public Parameters(List<Pattern> params, Pattern rest) {
|
||||
var len = params.size();
|
||||
|
||||
for (var i = params.size() - 1; i >= 0; i--) {
|
||||
if (params.get(i).node == null) break;
|
||||
if (!(params.get(i) instanceof AssignNode)) break;
|
||||
len--;
|
||||
}
|
||||
|
||||
this.params = params;
|
||||
this.length = len;
|
||||
this.restName = restName;
|
||||
this.restLocation = restLocation;
|
||||
this.rest = rest;
|
||||
}
|
||||
public Parameters(List<Parameter> params) {
|
||||
this(params, null, null);
|
||||
public Parameters(List<Pattern> params) {
|
||||
this(params, null);
|
||||
}
|
||||
|
||||
public static ParseRes<Parameters> parseParameters(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += openParen.n;
|
||||
|
||||
var params = new ArrayList<Pattern>();
|
||||
|
||||
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||
n += closeParen.n;
|
||||
|
||||
if (!closeParen.isSuccess()) {
|
||||
while (true) {
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "...")) {
|
||||
n += 3;
|
||||
|
||||
var rest = Pattern.parse(src, i + n, true);
|
||||
if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
|
||||
n += rest.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
|
||||
n++;
|
||||
|
||||
return ParseRes.res(new Parameters(params, rest.result), n);
|
||||
}
|
||||
|
||||
var param = Pattern.parse(src, i + n, true);
|
||||
if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
|
||||
n += param.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params.add(param.result);
|
||||
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
}
|
||||
|
||||
if (src.is(i + n, ")")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(new Parameters(params), n);
|
||||
}
|
||||
}
|
||||
|
@ -4,23 +4,21 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
|
||||
public class VariableDeclareNode extends Node {
|
||||
public static class Pair {
|
||||
public final String name;
|
||||
public final Pattern destructor;
|
||||
public final Node value;
|
||||
public final Location location;
|
||||
|
||||
public Pair(String name, Node value, Location location) {
|
||||
this.name = name;
|
||||
public Pair(Pattern destr, Node value, Location location) {
|
||||
this.destructor = destr;
|
||||
this.value = value;
|
||||
this.location = location;
|
||||
}
|
||||
@ -32,25 +30,22 @@ public class VariableDeclareNode extends Node {
|
||||
@Override public void resolve(CompileResult target) {
|
||||
if (!declType.strict) {
|
||||
for (var entry : values) {
|
||||
target.scope.define(new Variable(entry.name, false), entry.location);
|
||||
entry.destructor.destructDeclResolve(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
for (var entry : values) {
|
||||
if (entry.name == null) continue;
|
||||
if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location);
|
||||
|
||||
if (entry.value != null) {
|
||||
FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
|
||||
target.add(VariableNode.toSet(target, entry.location, entry.name, false, true));
|
||||
if (entry.value == null) {
|
||||
if (declType == DeclarationType.VAR) entry.destructor.declare(target, null);
|
||||
else entry.destructor.declare(target, declType);
|
||||
}
|
||||
else target.add(_i -> {
|
||||
var i = target.scope.get(entry.name, false);
|
||||
else {
|
||||
entry.value.compile(target, true);
|
||||
|
||||
if (i == null) return Instruction.globDef(entry.name);
|
||||
else return Instruction.nop();
|
||||
});
|
||||
if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true);
|
||||
else entry.destructor.destruct(target, declType, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
@ -80,13 +75,10 @@ public class VariableDeclareNode extends Node {
|
||||
|
||||
while (true) {
|
||||
var nameLoc = src.loc(i + n);
|
||||
var name = Parsing.parseIdentifier(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
|
||||
n += name.n;
|
||||
|
||||
if (!JavaScript.checkVarName(name.result)) {
|
||||
return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result));
|
||||
}
|
||||
var name = Pattern.parse(src, i + n, false);
|
||||
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor");
|
||||
n += name.n;
|
||||
|
||||
Node val = null;
|
||||
var endN = n;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
@ -33,12 +32,10 @@ public class ForInNode extends Node {
|
||||
if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation);
|
||||
|
||||
object.compile(target, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.keys(true));
|
||||
target.add(Instruction.keys(false, true));
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.pushUndefined());
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
int mid = target.temp();
|
||||
|
||||
target.add(Instruction.loadMember("value")).setLocation(varLocation);
|
||||
@ -55,7 +52,7 @@ public class ForInNode extends Node {
|
||||
|
||||
target.add(Instruction.jmp(start - endI));
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(endI - mid + 1));
|
||||
target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class AssignPattern implements Pattern {
|
||||
public final Location loc;
|
||||
public final Pattern assignable;
|
||||
public final Node value;
|
||||
|
||||
@Override public Location loc() { return loc; }
|
||||
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
assignable.destructDeclResolve(target);
|
||||
}
|
||||
|
||||
@Override public void declare(CompileResult target, DeclarationType decl) {
|
||||
throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration");
|
||||
}
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
// if (assignable instanceof AssignPattern other) throw new SyntaxException(other.loc(), "Unexpected destruction target");
|
||||
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.pushUndefined());
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
var start = target.temp();
|
||||
target.add(Instruction.discard());
|
||||
|
||||
value.compile(target, true);
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||
|
||||
assignable.destruct(target, decl, shouldDeclare);
|
||||
}
|
||||
|
||||
public AssignPattern(Location loc, Pattern assignable, Node value) {
|
||||
this.loc = loc;
|
||||
this.assignable = assignable;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ParseRes<AssignPattern> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var pattern = Pattern.parse(src, i + n, false);
|
||||
if (!pattern.isSuccess()) return pattern.chainError();
|
||||
n += pattern.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "=")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a default value");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new AssignPattern(loc, pattern.result, value.result), n);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
|
||||
/**
|
||||
* Represents all nodes that can be assign targets
|
||||
*/
|
||||
public interface AssignTarget extends AssignTargetLike {
|
||||
Location loc();
|
||||
|
||||
/**
|
||||
* Called to perform calculations before the assigned value is calculated
|
||||
*/
|
||||
default void beforeAssign(CompileResult target) {}
|
||||
/**
|
||||
* Called to perform the actual assignemnt. Between the `beforeAssign` and this call a single value will have been pushed to the stack
|
||||
* @param pollute Whether or not to leave the original value on the stack
|
||||
*/
|
||||
void afterAssign(CompileResult target, boolean pollute);
|
||||
|
||||
default void assign(CompileResult target, boolean pollute) {
|
||||
afterAssign(target, pollute);
|
||||
}
|
||||
|
||||
@Override default AssignTarget toAssignTarget() { return this; }
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
/**
|
||||
* Represents all nodes that can be assign targets
|
||||
*/
|
||||
public interface AssignTargetLike {
|
||||
AssignTarget toAssignTarget();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
|
||||
public interface ChangeTarget extends AssignTarget {
|
||||
void beforeChange(CompileResult target);
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
public interface NamedDestructor extends Pattern {
|
||||
String name();
|
||||
|
||||
// public static ParseRes<Destructor> parse(Source src, int i) {
|
||||
// var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
// ParseRes<Destructor> first = ParseRes.first(src, i + n,
|
||||
// AssignDestructorNode::parse,
|
||||
// VariableNode::parse
|
||||
// );
|
||||
// return first.addN(n);
|
||||
// }
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
|
||||
public class ObjectAssignable extends ObjectDestructor<AssignTarget> implements AssignTarget {
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
compile(target, t -> t.assign(target, false), pollute);
|
||||
}
|
||||
|
||||
public ObjectAssignable(Location loc, List<Member<AssignTarget>> members) {
|
||||
super(loc, members);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||
|
||||
public abstract class ObjectDestructor<T> extends Node {
|
||||
public static final class Member<T> {
|
||||
public final Node key;
|
||||
public final T consumable;
|
||||
|
||||
public Member(Node key, T consumer) {
|
||||
this.key = key;
|
||||
this.consumable = consumer;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<Member<T>> members;
|
||||
|
||||
public void consume(Consumer<T> consumer) {
|
||||
for (var el : members) {
|
||||
consumer.accept(el.consumable);
|
||||
}
|
||||
}
|
||||
public void compile(CompileResult target, Consumer<T> consumer, boolean pollute) {
|
||||
for (var el : members) {
|
||||
target.add(Instruction.dup());
|
||||
IndexNode.indexLoad(target, el.key, true);
|
||||
consumer.accept(el.consumable);
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
public ObjectDestructor(Location loc, List<Member<T>> members) {
|
||||
super(loc);
|
||||
this.members = members;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class ObjectPattern extends ObjectDestructor<Pattern> implements Pattern {
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
consume(t -> t.destructDeclResolve(target));
|
||||
}
|
||||
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
compile(target, t -> t.destruct(target, decl, shouldDeclare), false);
|
||||
}
|
||||
|
||||
@Override public void declare(CompileResult target, DeclarationType decl) {
|
||||
throw new SyntaxException(loc(), "Object pattern must be initialized");
|
||||
}
|
||||
|
||||
public ObjectPattern(Location loc, List<Member<Pattern>> members) {
|
||||
super(loc, members);
|
||||
}
|
||||
|
||||
private static ParseRes<Member<Pattern>> parseShorthand(Source src, int i) {
|
||||
ParseRes<Pattern> res = ParseRes.first(src, i,
|
||||
AssignPattern::parse,
|
||||
VariableNode::parse
|
||||
);
|
||||
|
||||
if (res.isSuccess()) {
|
||||
if (res.result instanceof AssignPattern assign) {
|
||||
if (assign.assignable instanceof VariableNode var) {
|
||||
return ParseRes.res(new Member<>(new StringNode(var.loc(), var.name), res.result), res.n);
|
||||
}
|
||||
}
|
||||
else if (res.result instanceof VariableNode var) {
|
||||
return ParseRes.res(new Member<>(new StringNode(var.loc(), var.name), res.result), res.n);
|
||||
}
|
||||
}
|
||||
|
||||
return res.chainError();
|
||||
}
|
||||
private static ParseRes<Member<Pattern>> parseKeyed(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
var key = ObjectNode.parsePropName(src, i + n);
|
||||
if (!key.isSuccess()) return key.chainError();
|
||||
n += key.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n , ":")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
ParseRes<Pattern> res = Pattern.parse(src, i + n, true);
|
||||
if (!res.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a pattern after colon");
|
||||
n += res.n;
|
||||
|
||||
return ParseRes.res(new Member<>(key.result, res.result), n);
|
||||
}
|
||||
|
||||
public static ParseRes<ObjectPattern> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var members = new LinkedList<Member<Pattern>>();
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
return ParseRes.res(new ObjectPattern(loc, members), n);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ParseRes<Member<Pattern>> prop = ParseRes.first(src, i + n,
|
||||
ObjectPattern::parseKeyed,
|
||||
ObjectPattern::parseShorthand
|
||||
);
|
||||
|
||||
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object pattern");
|
||||
n += prop.n;
|
||||
|
||||
members.add(prop.result);
|
||||
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||
}
|
||||
|
||||
return ParseRes.res(new ObjectPattern(loc, members), n);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
/**
|
||||
* Represents all nodes that can be a destructors (note that all destructors are assign targets, too)
|
||||
*/
|
||||
public interface Pattern extends PatternLike {
|
||||
Location loc();
|
||||
|
||||
/**
|
||||
* Called when the destructor has to declare
|
||||
* @param target
|
||||
*/
|
||||
void destructDeclResolve(CompileResult target);
|
||||
|
||||
/**
|
||||
* Called when a declaration-like is being destructed
|
||||
* @param decl The variable type the destructor must declare, if it is a named pne
|
||||
*/
|
||||
void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare);
|
||||
|
||||
/**
|
||||
* Run when destructing a declaration without an initializer
|
||||
*/
|
||||
void declare(CompileResult target, DeclarationType decl);
|
||||
|
||||
public static ParseRes<Pattern> parse(Source src, int i, boolean withDefault) {
|
||||
return withDefault ?
|
||||
ParseRes.first(src, i,
|
||||
AssignPattern::parse,
|
||||
ObjectPattern::parse,
|
||||
VariableNode::parse
|
||||
) :
|
||||
ParseRes.first(src, i,
|
||||
ObjectPattern::parse,
|
||||
VariableNode::parse
|
||||
);
|
||||
}
|
||||
|
||||
@Override default Pattern toPattern() { return this; }
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
public interface PatternLike {
|
||||
Pattern toPattern();
|
||||
}
|
@ -19,6 +19,13 @@ public class FunctionScope extends Scope {
|
||||
|
||||
public final boolean passtrough;
|
||||
|
||||
@Override public boolean hasNonStrict(String name) {
|
||||
if (functionVarMap.containsKey(name)) return true;
|
||||
if (blacklistNames.contains(name)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public Variable define(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
@ -32,13 +39,6 @@ public class FunctionScope extends Scope {
|
||||
return variables.add(var);
|
||||
}
|
||||
}
|
||||
@Override public Variable defineStrict(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (functionVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
|
||||
return super.defineStrict(var, loc);
|
||||
}
|
||||
public Variable defineSpecial(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
|
@ -4,6 +4,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class Scope {
|
||||
@ -41,15 +42,6 @@ public class Scope {
|
||||
return var;
|
||||
}
|
||||
|
||||
// private final int parentVarOffset() {
|
||||
// if (parent != null) return parent.variableOffset();
|
||||
// else return 0;
|
||||
// }
|
||||
// private final int parentCapOffset() {
|
||||
// if (parent != null) return parent.capturedOffset();
|
||||
// else return localsCount();
|
||||
// }
|
||||
|
||||
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
|
||||
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
|
||||
}
|
||||
@ -61,6 +53,16 @@ public class Scope {
|
||||
if (ended) throw new IllegalStateException("Cannot define in an ended scope");
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a nameless variable for holding intermediate temporary values
|
||||
*
|
||||
* @throws RuntimeException If the scope is finalized or has an active child
|
||||
*/
|
||||
public Variable defineTemp() {
|
||||
checkNotEnded();
|
||||
return this.variables.add(new Variable("<temp>", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an ES5-style variable
|
||||
*
|
||||
@ -76,6 +78,11 @@ public class Scope {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this scope's function parent has a non-strict variable of the given name
|
||||
*/
|
||||
public boolean hasNonStrict(String name) { return false; }
|
||||
|
||||
/**
|
||||
* Defines an ES2015-style variable
|
||||
* @param readonly True if const, false if let
|
||||
@ -86,6 +93,7 @@ public class Scope {
|
||||
public Variable defineStrict(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
|
||||
strictVarMap.put(var.name, var);
|
||||
return variables.add(var);
|
||||
@ -125,9 +133,6 @@ public class Scope {
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
// if (parent != null) return parent.variableOffset() + variables.size();
|
||||
// else return variables.size();
|
||||
}
|
||||
public final int capturablesOffset() {
|
||||
var res = 0;
|
||||
@ -138,8 +143,11 @@ public class Scope {
|
||||
}
|
||||
|
||||
return res;
|
||||
// if (parent != null) return parent.capturedOffset() + captured.size();
|
||||
// else return localsCount() + captured.size();
|
||||
}
|
||||
|
||||
public final Variable define(DeclarationType type, String name, Location loc) {
|
||||
if (type.strict) return defineStrict(new Variable(name, type.readonly), loc);
|
||||
else return define(new Variable(name, type.readonly), loc);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
|
@ -18,11 +18,10 @@ public class ArrayNode extends Node {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.loadArr(statements.length));
|
||||
|
||||
if (statements.length > 0) target.add(Instruction.dup(statements.length));
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var el = statements[i];
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup());
|
||||
el.compile(target, true);
|
||||
target.add(Instruction.storeMember(i));
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
@ -12,104 +12,342 @@ 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;
|
||||
import me.topchetoeu.jscript.compilation.Parameters;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ObjectAssignable;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ObjectDestructor.Member;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class ObjectNode extends Node implements AssignTargetLike {
|
||||
public static class PropertyMemberNode extends FunctionNode {
|
||||
public final Node key;
|
||||
public final Pattern argument;
|
||||
|
||||
public class ObjectNode extends Node {
|
||||
public static class ObjProp {
|
||||
public final String name;
|
||||
public final String access;
|
||||
public final FunctionValueNode func;
|
||||
@Override public String name() {
|
||||
if (key instanceof StringNode str) {
|
||||
if (isGetter()) return "get " + str.value;
|
||||
else return "set " + str.value;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
public ObjProp(String name, String access, FunctionValueNode func) {
|
||||
this.name = name;
|
||||
this.access = access;
|
||||
this.func = func;
|
||||
public boolean isGetter() { return argument == null; }
|
||||
public boolean isSetter() { return argument != null; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
key.compile(target, true);
|
||||
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target)));
|
||||
|
||||
target.add(Instruction.defProp(isSetter()));
|
||||
}
|
||||
|
||||
public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) {
|
||||
super(loc, end, argument == null ? new Parameters(List.of()) : new Parameters(List.of(argument)), body);
|
||||
this.key = key;
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
public static ParseRes<PropertyMemberNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var access = Parsing.parseIdentifier(src, i + n);
|
||||
if (!access.isSuccess()) return ParseRes.failed();
|
||||
if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed();
|
||||
n += access.n;
|
||||
|
||||
var name = parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
||||
n += name.n;
|
||||
|
||||
var params = Parameters.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||
if (access.result.equals("get") && params.result.params.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
|
||||
if (access.result.equals("set") && params.result.params.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
|
||||
if (params.result.rest != null) return ParseRes.error(params.result.rest.loc(), "Property members may not have rest arguments");
|
||||
n += params.n;
|
||||
|
||||
var body = CompoundNode.parse(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
|
||||
n += body.n;
|
||||
|
||||
var end = src.loc(i + n - 1);
|
||||
|
||||
return ParseRes.res(new PropertyMemberNode(
|
||||
loc, end, name.result, access.result.equals("get") ? null : params.result.params.get(0), body.result
|
||||
), n);
|
||||
}
|
||||
}
|
||||
public static class MethodMemberNode extends FunctionNode {
|
||||
public final Node key;
|
||||
|
||||
@Override public String name() {
|
||||
if (key instanceof StringNode str) return str.value;
|
||||
else return null;
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
key.compile(target, true);
|
||||
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target)));
|
||||
|
||||
target.add(Instruction.defField());
|
||||
}
|
||||
|
||||
public MethodMemberNode(Location loc, Location end, Node key, Parameters params, CompoundNode body) {
|
||||
super(loc, end, params, body);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public static ParseRes<MethodMemberNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var name = parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError();
|
||||
n += name.n;
|
||||
|
||||
var params = Parameters.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||
n += params.n;
|
||||
|
||||
var body = CompoundNode.parse(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
|
||||
n += body.n;
|
||||
|
||||
var end = src.loc(i + n - 1);
|
||||
|
||||
return ParseRes.res(new MethodMemberNode(
|
||||
loc, end, name.result, params.result, body.result
|
||||
), n);
|
||||
}
|
||||
}
|
||||
public static class FieldMemberNode extends Node {
|
||||
public final Node key;
|
||||
public final Node value;
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
key.compile(target, true);
|
||||
|
||||
if (value == null) target.add(Instruction.pushUndefined());
|
||||
else value.compile(target, true);
|
||||
|
||||
target.add(Instruction.defField());
|
||||
}
|
||||
|
||||
public FieldMemberNode(Location loc, Node key, Node value) {
|
||||
super(loc);
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ParseRes<FieldMemberNode> parseObject(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var name = parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError();
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||
}
|
||||
|
||||
public static ParseRes<FieldMemberNode> parseShorthand(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var var = VariableNode.parse(src, i + n);
|
||||
if (!var.isSuccess()) return var.chainError();
|
||||
n += var.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n);
|
||||
}
|
||||
|
||||
public static ParseRes<FieldMemberNode> parseClass(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var name = parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError();
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "=")) {
|
||||
var end = JavaScript.parseStatement(src, i + n);
|
||||
if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer");
|
||||
n += end.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, name.result, null), n);
|
||||
}
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||
}
|
||||
}
|
||||
public static class AssignShorthandNode extends Node {
|
||||
public final Node key;
|
||||
public final AssignTarget target;
|
||||
public final Node value;
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context");
|
||||
}
|
||||
|
||||
public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) {
|
||||
super(loc);
|
||||
this.key = key;
|
||||
this.target = target;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public AssignTarget target() {
|
||||
return new AssignNode(loc(), target, value);
|
||||
}
|
||||
|
||||
public static ParseRes<AssignShorthandNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var var = VariableNode.parse(src, i + n);
|
||||
if (!var.isSuccess()) return var.chainError();
|
||||
n += var.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "=")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n);
|
||||
}
|
||||
}
|
||||
|
||||
public final Map<String, Node> map;
|
||||
public final Map<String, FunctionNode> getters;
|
||||
public final Map<String, FunctionNode> setters;
|
||||
public final List<Node> members;
|
||||
|
||||
// TODO: Implement spreading into object
|
||||
|
||||
// private void compileRestObjBuilder(CompileResult target, int srcDupN) {
|
||||
// var subtarget = target.subtarget();
|
||||
// var src = subtarget.scope.defineTemp();
|
||||
// var dst = subtarget.scope.defineTemp();
|
||||
|
||||
// target.add(Instruction.loadObj());
|
||||
// target.add(_i -> src.index().toSet(true));
|
||||
// target.add(_i -> dst.index().toSet(destructors.size() > 0));
|
||||
|
||||
// target.add(Instruction.keys(true, true));
|
||||
// var start = target.size();
|
||||
|
||||
// target.add(Instruction.dup());
|
||||
// var mid = target.temp();
|
||||
|
||||
// target.add(_i -> src.index().toGet());
|
||||
// target.add(Instruction.dup(1, 1));
|
||||
// target.add(Instruction.loadMember());
|
||||
|
||||
// target.add(_i -> dst.index().toGet());
|
||||
// target.add(Instruction.dup(1, 1));
|
||||
// target.add(Instruction.storeMember());
|
||||
|
||||
// target.add(Instruction.discard());
|
||||
// var end = target.size();
|
||||
// target.add(Instruction.jmp(start - end));
|
||||
// target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||
|
||||
// target.add(Instruction.discard());
|
||||
|
||||
// target.add(Instruction.dup(srcDupN, 1));
|
||||
|
||||
// target.scope.end();
|
||||
// }
|
||||
|
||||
// @Override public void destruct(CompileResult target, DeclarationType decl) {
|
||||
// if (getters.size() > 0) throw new SyntaxException(getters.values().iterator().next().loc(), "Unexpected getter in destructor");
|
||||
// if (setters.size() > 0) throw new SyntaxException(setters.values().iterator().next().loc(), "Unexpected setter in destructor");
|
||||
// }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.loadObj());
|
||||
|
||||
for (var el : map.entrySet()) {
|
||||
for (var el : members) {
|
||||
target.add(Instruction.dup());
|
||||
var val = el.getValue();
|
||||
FunctionNode.compileWithName(val, target, true, el.getKey().toString());
|
||||
target.add(Instruction.storeMember(el.getKey()));
|
||||
el.compile(target, false);
|
||||
}
|
||||
|
||||
var keys = new ArrayList<Object>();
|
||||
keys.addAll(getters.keySet());
|
||||
keys.addAll(setters.keySet());
|
||||
|
||||
for (var key : keys) {
|
||||
target.add(Instruction.pushValue((String)key));
|
||||
|
||||
if (getters.containsKey(key)) getters.get(key).compile(target, true);
|
||||
else target.add(Instruction.pushUndefined());
|
||||
|
||||
if (setters.containsKey(key)) setters.get(key).compile(target, true);
|
||||
else target.add(Instruction.pushUndefined());
|
||||
|
||||
target.add(Instruction.defProp());
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
public ObjectNode(Location loc, Map<String, Node> map, Map<String, FunctionNode> getters, Map<String, FunctionNode> setters) {
|
||||
@Override public AssignTarget toAssignTarget() {
|
||||
var newMembers = new LinkedList<Member<AssignTarget>>();
|
||||
|
||||
for (var el : members) {
|
||||
if (el instanceof FieldMemberNode field) {
|
||||
if (field.value instanceof AssignTargetLike target) newMembers.add(new Member<>(field.key, target.toAssignTarget()));
|
||||
else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor");
|
||||
}
|
||||
else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new Member<>(shorthand.key, shorthand.target()));
|
||||
else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor");
|
||||
}
|
||||
|
||||
return new ObjectAssignable(loc(), newMembers);
|
||||
}
|
||||
|
||||
public ObjectNode(Location loc, List<Node> map) {
|
||||
super(loc);
|
||||
this.map = map;
|
||||
this.getters = getters;
|
||||
this.setters = setters;
|
||||
this.members = map;
|
||||
}
|
||||
|
||||
private static ParseRes<String> parsePropName(Source src, int i) {
|
||||
private static ParseRes<Node> parseComputePropName(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
if (!src.is(i + n, "[")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var res = ParseRes.first(src, i + n,
|
||||
Parsing::parseIdentifier,
|
||||
Parsing::parseString,
|
||||
(s, j) -> Parsing.parseNumber(s, j, false)
|
||||
var val = JavaScript.parseExpression(src, i, 0);
|
||||
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property");
|
||||
n += val.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket after compute property");
|
||||
n++;
|
||||
|
||||
return ParseRes.res(val.result, n);
|
||||
}
|
||||
public static ParseRes<Node> parsePropName(Source src, int i) {
|
||||
return ParseRes.first(src, i,
|
||||
(s, j) -> {
|
||||
var m = Parsing.skipEmpty(s, j);
|
||||
var l = s.loc(j + m);
|
||||
|
||||
var r = Parsing.parseIdentifier(s, j + m);
|
||||
if (r.isSuccess()) return ParseRes.res(new StringNode(l, r.result), r.n);
|
||||
else return r.chainError();
|
||||
},
|
||||
StringNode::parse,
|
||||
NumberNode::parse,
|
||||
ObjectNode::parseComputePropName
|
||||
);
|
||||
n += res.n;
|
||||
|
||||
if (!res.isSuccess()) return res.chainError();
|
||||
return ParseRes.res(res.result.toString(), n);
|
||||
}
|
||||
private static ParseRes<ObjectNode.ObjProp> parseObjectProp(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var access = Parsing.parseIdentifier(src, i + n);
|
||||
if (!access.isSuccess()) return ParseRes.failed();
|
||||
if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed();
|
||||
n += access.n;
|
||||
|
||||
var name = parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
||||
n += name.n;
|
||||
|
||||
var params = JavaScript.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||
n += params.n;
|
||||
|
||||
var body = CompoundNode.parse(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
|
||||
n += body.n;
|
||||
|
||||
var end = src.loc(i + n - 1);
|
||||
|
||||
return ParseRes.res(new ObjProp(
|
||||
name.result, access.result,
|
||||
new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString())
|
||||
), n);
|
||||
}
|
||||
|
||||
public static ParseRes<ObjectNode> parse(Source src, int i) {
|
||||
@ -120,39 +358,25 @@ public class ObjectNode extends Node {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var values = new LinkedHashMap<String, Node>();
|
||||
var getters = new LinkedHashMap<String, FunctionNode>();
|
||||
var setters = new LinkedHashMap<String, FunctionNode>();
|
||||
var members = new LinkedList<Node>();
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n);
|
||||
return ParseRes.res(new ObjectNode(loc, members), n);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var prop = parseObjectProp(src, i + n);
|
||||
ParseRes<Node> prop = ParseRes.first(src, i + n,
|
||||
MethodMemberNode::parse,
|
||||
PropertyMemberNode::parse,
|
||||
FieldMemberNode::parseObject,
|
||||
AssignShorthandNode::parse,
|
||||
FieldMemberNode::parseShorthand
|
||||
);
|
||||
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal");
|
||||
n += prop.n;
|
||||
|
||||
if (prop.isSuccess()) {
|
||||
n += prop.n;
|
||||
|
||||
if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func);
|
||||
else getters.put(prop.result.name, prop.result.func);
|
||||
}
|
||||
else {
|
||||
var name = parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name");
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
|
||||
n++;
|
||||
|
||||
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list");
|
||||
n += valRes.n;
|
||||
|
||||
values.put(name.result, valRes.result);
|
||||
}
|
||||
members.add(prop.result);
|
||||
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
if (src.is(i + n, ",")) {
|
||||
@ -173,7 +397,6 @@ public class ObjectNode extends Node {
|
||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||
}
|
||||
|
||||
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n);
|
||||
return ParseRes.res(new ObjectNode(loc, members), n);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,26 +1,72 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.VariableAssignNode;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class VariableNode extends Node implements AssignableNode {
|
||||
public class VariableNode extends Node implements Pattern, ChangeTarget {
|
||||
public final String name;
|
||||
|
||||
@Override public Node toAssign(Node val, Operation operation) {
|
||||
return new VariableAssignNode(loc(), name, val, operation);
|
||||
public String assignName() { return name; }
|
||||
|
||||
// @Override public void compileBeforeAssign(CompileResult target, boolean operator) {
|
||||
// if (operator) {
|
||||
// target.add(VariableNode.toGet(target, loc(), name));
|
||||
// }
|
||||
// }
|
||||
// @Override public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute) {
|
||||
// target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||
// }
|
||||
|
||||
@Override public void beforeChange(CompileResult target) {
|
||||
target.add(VariableNode.toGet(target, loc(), name));
|
||||
}
|
||||
|
||||
// @Override public void destructArg(CompileResult target) {
|
||||
// target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false));
|
||||
// }
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
target.scope.define(new Variable(name, false), loc());
|
||||
}
|
||||
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||
}
|
||||
|
||||
@Override public void declare(CompileResult target, DeclarationType decl) {
|
||||
if (decl != null) {
|
||||
if (decl.strict) target.scope.defineStrict(new Variable(name, decl.readonly), loc());
|
||||
else target.scope.define(new Variable(name, decl.readonly), loc());
|
||||
}
|
||||
else target.add(_i -> {
|
||||
var i = target.scope.get(name, false);
|
||||
|
||||
if (i == null) return Instruction.globDef(name);
|
||||
else return Instruction.nop();
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
if (!shouldDeclare || decl == null) {
|
||||
target.add(VariableNode.toSet(target, loc(), name, false, shouldDeclare));
|
||||
}
|
||||
else {
|
||||
if (decl == DeclarationType.VAR && target.scope.has(name, false)) throw new SyntaxException(loc(), "Duplicate parameter name not allowed");
|
||||
var v = target.scope.define(decl, name, loc());
|
||||
target.add(_i -> v.index().toSet(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
@ -29,7 +75,7 @@ public class VariableNode extends Node implements AssignableNode {
|
||||
if (i == null) {
|
||||
target.add(_i -> {
|
||||
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc(), String.format("Cannot access '%s' before initialization", name));
|
||||
return Instruction.globGet(name);
|
||||
return Instruction.globGet(name, false);
|
||||
});
|
||||
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
@ -37,18 +83,18 @@ public class VariableNode extends Node implements AssignableNode {
|
||||
else if (pollute) target.add(_i -> i.index().toGet());
|
||||
}
|
||||
|
||||
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, Supplier<Instruction> onGlobal) {
|
||||
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, boolean forceGet) {
|
||||
var i = target.scope.get(name, false);
|
||||
|
||||
if (i == null) return _i -> {
|
||||
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name));
|
||||
else return onGlobal.get();
|
||||
else return Instruction.globGet(name, forceGet);
|
||||
};
|
||||
else return _i -> i.index().toGet();
|
||||
}
|
||||
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name) {
|
||||
return toGet(target, loc, name, () -> Instruction.globGet(name));
|
||||
}
|
||||
return toGet(target, loc, name, false);
|
||||
}
|
||||
|
||||
|
||||
public static IntFunction<Instruction> toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) {
|
||||
@ -76,10 +122,8 @@ public class VariableNode extends Node implements AssignableNode {
|
||||
n += literal.n;
|
||||
|
||||
if (!JavaScript.checkVarName(literal.result)) {
|
||||
if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported.");
|
||||
if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported.");
|
||||
if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported.");
|
||||
return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result));
|
||||
if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported");
|
||||
return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result));
|
||||
}
|
||||
|
||||
return ParseRes.res(new VariableNode(loc, literal.result), n);
|
||||
|
@ -0,0 +1,46 @@
|
||||
package me.topchetoeu.jscript.compilation.values.operations;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public class AssignNode extends Node implements AssignTarget {
|
||||
public final AssignTarget assignable;
|
||||
public final Node value;
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here");
|
||||
|
||||
assignable.beforeAssign(target);
|
||||
value.compile(target, true);
|
||||
assignable.afterAssign(target, pollute);
|
||||
}
|
||||
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed");
|
||||
|
||||
if (pollute) target.add(Instruction.dup(2, 0));
|
||||
else target.add(Instruction.dup());
|
||||
target.add(Instruction.pushUndefined());
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
var start = target.temp();
|
||||
target.add(Instruction.discard());
|
||||
|
||||
value.compile(target, true);
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||
|
||||
assignable.assign(target, false);
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
public AssignNode(Location loc, AssignTarget assignable, Node value) {
|
||||
super(loc);
|
||||
this.assignable = assignable;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ public class CallNode extends Node {
|
||||
|
||||
shouldParen = true;
|
||||
|
||||
if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}";
|
||||
if (obj.members.size() > 0) res = "{}";
|
||||
else res = "{(intermediate value)}";
|
||||
}
|
||||
else if (func instanceof StringNode) {
|
||||
|
@ -6,31 +6,29 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
|
||||
public class ChangeNode extends Node {
|
||||
public final AssignableNode value;
|
||||
public final double addAmount;
|
||||
public final boolean postfix;
|
||||
public final ChangeTarget changable;
|
||||
public final Node value;
|
||||
public final Operation op;
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
value.toAssign(new NumberNode(loc(), -addAmount), Operation.SUBTRACT).compile(target, true);
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
else if (postfix) {
|
||||
target.add(Instruction.pushValue(addAmount));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
}
|
||||
changable.beforeChange(target);
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.operation(op));
|
||||
changable.afterAssign(target, pollute);
|
||||
}
|
||||
|
||||
public ChangeNode(Location loc, AssignableNode value, double addAmount, boolean postfix) {
|
||||
public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) {
|
||||
super(loc);
|
||||
this.changable = changable;
|
||||
this.value = value;
|
||||
this.addAmount = addAmount;
|
||||
this.postfix = postfix;
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public static ParseRes<ChangeNode> parsePrefixIncrease(Source src, int i) {
|
||||
@ -42,9 +40,9 @@ public class ChangeNode extends Node {
|
||||
|
||||
var res = JavaScript.parseExpression(src, i + n, 15);
|
||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||
else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||
|
||||
return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, 1, false), n + res.n);
|
||||
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n);
|
||||
}
|
||||
public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
@ -55,33 +53,8 @@ public class ChangeNode extends Node {
|
||||
|
||||
var res = JavaScript.parseExpression(src, i + n, 15);
|
||||
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||
else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||
else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
|
||||
|
||||
return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, -1, false), n + res.n);
|
||||
}
|
||||
|
||||
public static ParseRes<ChangeNode> parsePostfixIncrease(Source src, int i, Node prev, int precedence) {
|
||||
if (precedence > 15) return ParseRes.failed();
|
||||
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "++")) return ParseRes.failed();
|
||||
if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||
n += 2;
|
||||
|
||||
return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, 1, true), n);
|
||||
}
|
||||
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
||||
if (precedence > 15) return ParseRes.failed();
|
||||
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "--")) return ParseRes.failed();
|
||||
if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||
n += 2;
|
||||
|
||||
return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, -1, true), n);
|
||||
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n);
|
||||
}
|
||||
}
|
||||
|
@ -1,74 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values.operations;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class IndexAssignNode extends Node {
|
||||
public final Node object;
|
||||
public final Node index;
|
||||
public final Node value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (operation != null) {
|
||||
object.compile(target, true);
|
||||
|
||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||
target.add(Instruction.loadMember((int)num.value));
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.operation(operation));
|
||||
target.add(Instruction.storeMember((int)num.value, pollute));
|
||||
}
|
||||
else if (index instanceof StringNode str) {
|
||||
target.add(Instruction.loadMember(str.value));
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.operation(operation));
|
||||
target.add(Instruction.storeMember(str.value, pollute));
|
||||
}
|
||||
else {
|
||||
index.compile(target, true);
|
||||
target.add(Instruction.dup(2));
|
||||
|
||||
target.add(Instruction.loadMember());
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.operation(operation));
|
||||
|
||||
target.add(Instruction.storeMember(pollute));
|
||||
}
|
||||
target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);
|
||||
}
|
||||
else {
|
||||
object.compile(target, true);
|
||||
|
||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.storeMember((int)num.value, pollute));
|
||||
}
|
||||
else if (index instanceof StringNode str) {
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.storeMember(str.value, pollute));
|
||||
}
|
||||
else {
|
||||
index.compile(target, true);
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.storeMember(pollute));
|
||||
}
|
||||
|
||||
target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);;
|
||||
}
|
||||
}
|
||||
|
||||
public IndexAssignNode(Location loc, Node object, Node index, Node value, Operation operation) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.operation = operation;
|
||||
}
|
||||
}
|
@ -1,26 +1,61 @@
|
||||
package me.topchetoeu.jscript.compilation.values.operations;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class IndexNode extends Node implements AssignableNode {
|
||||
public class IndexNode extends Node implements ChangeTarget {
|
||||
public final Node object;
|
||||
public final Node index;
|
||||
|
||||
@Override public Node toAssign(Node val, Operation operation) {
|
||||
return new IndexAssignNode(loc(), object, index, val, operation);
|
||||
@Override public void beforeAssign(CompileResult target) {
|
||||
object.compile(target, true);
|
||||
|
||||
indexStorePushKey(target, index);
|
||||
}
|
||||
@Override public void beforeChange(CompileResult target) {
|
||||
object.compile(target, true);
|
||||
|
||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember((int)num.value));
|
||||
}
|
||||
else if (index instanceof StringNode str) {
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember(str.value));
|
||||
}
|
||||
else {
|
||||
index.compile(target, true);
|
||||
|
||||
target.add(Instruction.dup(1, 1));
|
||||
target.add(Instruction.dup(1, 1));
|
||||
target.add(Instruction.loadMember());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void assign(CompileResult target, boolean pollute) {
|
||||
object.compile(target, true);
|
||||
target.add(Instruction.dup(1, 1));
|
||||
indexStorePushKey(target, index);
|
||||
indexStore(target, index, pollute);
|
||||
}
|
||||
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
indexStore(target, index, pollute);
|
||||
}
|
||||
|
||||
// @Override public Node toAssign(Node val, Operation operation) {
|
||||
// return new IndexAssignNode(loc(), object, index, val, operation);
|
||||
// }
|
||||
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
|
||||
object.compile(target, true);
|
||||
if (dupObj) target.add(Instruction.dup());
|
||||
@ -84,4 +119,35 @@ public class IndexNode extends Node implements AssignableNode {
|
||||
|
||||
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
|
||||
}
|
||||
|
||||
public static void indexStorePushKey(CompileResult target, Node index) {
|
||||
if (index instanceof NumberNode num && (int)num.value == num.value) return;
|
||||
if (index instanceof StringNode) return;
|
||||
index.compile(target, true);
|
||||
}
|
||||
public static void indexStore(CompileResult target, Node index, boolean pollute) {
|
||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||
target.add(Instruction.storeMember((int)num.value, pollute));
|
||||
}
|
||||
else if (index instanceof StringNode str) {
|
||||
target.add(Instruction.storeMember(str.value, pollute));
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.storeMember(pollute));
|
||||
}
|
||||
}
|
||||
public static void indexLoad(CompileResult target, Node index, boolean pollute) {
|
||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||
target.add(Instruction.loadMember((int)num.value));
|
||||
}
|
||||
else if (index instanceof StringNode str) {
|
||||
target.add(Instruction.loadMember(str.value));
|
||||
}
|
||||
else {
|
||||
index.compile(target, true);
|
||||
target.add(Instruction.loadMember());
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
|
||||
public class OperationNode extends Node {
|
||||
private static interface OperatorFactory {
|
||||
@ -54,11 +55,22 @@ public class OperationNode extends Node {
|
||||
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
||||
var loc = src.loc(i);
|
||||
|
||||
if (!(prev instanceof AssignableNode)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
|
||||
if (operation == null) {
|
||||
if (!(prev instanceof AssignTargetLike target)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
|
||||
|
||||
var other = JavaScript.parseExpression(src, i, precedence);
|
||||
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||
return ParseRes.res(((AssignableNode)prev).toAssign(other.result, operation), other.n);
|
||||
var other = JavaScript.parseExpression(src, i, precedence);
|
||||
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||
|
||||
return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), other.n);
|
||||
}
|
||||
else {
|
||||
if (!(prev instanceof ChangeTarget target)) return ParseRes.error(loc, String.format("Expected a changeable expression before '%s'", token));
|
||||
|
||||
var other = JavaScript.parseExpression(src, i, precedence);
|
||||
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||
|
||||
return ParseRes.res(new ChangeNode(loc, target, other.result, operation), other.n);
|
||||
}
|
||||
}
|
||||
|
||||
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
||||
@ -207,7 +219,7 @@ public class OperationNode extends Node {
|
||||
var factory = factories.get(token);
|
||||
|
||||
if (!src.is(i + n, token)) continue;
|
||||
if (factory.precedence() < precedence) ParseRes.failed();
|
||||
if (factory.precedence() < precedence) return ParseRes.failed();
|
||||
|
||||
n += token.length();
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
@ -0,0 +1,52 @@
|
||||
package me.topchetoeu.jscript.compilation.values.operations;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
|
||||
public class PostfixNode extends ChangeNode {
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
super.compile(target, pollute);
|
||||
|
||||
if (pollute) {
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.operation(Operation.ADD));
|
||||
}
|
||||
}
|
||||
|
||||
public PostfixNode(Location loc, ChangeTarget value, double addAmount) {
|
||||
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
||||
}
|
||||
|
||||
public static ParseRes<ChangeNode> parsePostfixIncrease(Source src, int i, Node prev, int precedence) {
|
||||
if (precedence > 15) return ParseRes.failed();
|
||||
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "++")) return ParseRes.failed();
|
||||
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||
n += 2;
|
||||
|
||||
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n);
|
||||
}
|
||||
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
||||
if (precedence > 15) return ParseRes.failed();
|
||||
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "--")) return ParseRes.failed();
|
||||
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||
n += 2;
|
||||
|
||||
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n);
|
||||
}
|
||||
}
|
@ -16,15 +16,15 @@ public class TypeofNode extends Node {
|
||||
|
||||
@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());
|
||||
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true));
|
||||
if (pollute) target.add(Instruction.typeof());
|
||||
else target.add(Instruction.discard());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
value.compile(target, pollute);
|
||||
target.add(Instruction.typeof());
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
if (pollute) target.add(Instruction.typeof());
|
||||
}
|
||||
|
||||
public TypeofNode(Location loc, Node value) {
|
||||
|
@ -22,7 +22,9 @@ public interface Compiler {
|
||||
return body;
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
throw EngineException.ofSyntax(e.loc + ": " + e.msg);
|
||||
var res = EngineException.ofSyntax(e.msg);
|
||||
res.add(env, e.loc.filename() + "", e.loc);
|
||||
throw res;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -41,10 +41,8 @@ public final class Engine implements EventLoop {
|
||||
try {
|
||||
((Task<Object>)task).notifier.complete(task.runnable.get());
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (e instanceof InterruptException) throw e;
|
||||
task.notifier.completeExceptionally(e);
|
||||
}
|
||||
catch (InterruptException e) { throw e; }
|
||||
catch (RuntimeException e) { task.notifier.completeExceptionally(e); }
|
||||
}
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : tasks) msg.notifier.cancel(false);
|
||||
|
@ -28,9 +28,9 @@ public interface EventLoop {
|
||||
}
|
||||
|
||||
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
|
||||
return pushMsg(() -> func.call(env, thisArg, args), micro);
|
||||
return pushMsg(() -> func.invoke(env, thisArg, args), micro);
|
||||
}
|
||||
public default Future<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
|
||||
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).call(env, thisArg, args), micro);
|
||||
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).invoke(env, thisArg, args), micro);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
@ -11,11 +9,9 @@ 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.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
|
||||
public final class Frame {
|
||||
@ -385,39 +381,39 @@ public final class Frame {
|
||||
* Gets an array proxy of the local locals
|
||||
*/
|
||||
public ObjectValue getValStackScope() {
|
||||
return new ObjectValue() {
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
var res = super.getOwnMember(env, key);
|
||||
if (res != null) return res;
|
||||
return new ArrayLikeValue() {
|
||||
@Override public Value get(int i) { return stack[i]; }
|
||||
@Override public void set(int i, Value val) { stack[i] = val; }
|
||||
@Override public boolean has(int i) { return i >= 0 && i < size(); }
|
||||
@Override public void remove(int i) { }
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
@Override public int size() { return stackPtr; }
|
||||
@Override public boolean setSize(int val) { return false; }
|
||||
|
||||
if (num != i || i < 0 || i >= stackPtr) return null;
|
||||
else return new FieldMember(false, true, true) {
|
||||
@Override public Value get(Environment env, Value self) { return stack[i]; }
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
stack[i] = val;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) {
|
||||
var res = new LinkedHashMap<String, Member>();
|
||||
// @Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
// var res = super.getOwnMember(env, key);
|
||||
// if (res != null) return res;
|
||||
|
||||
for (var i = 0; i < stackPtr; i++) {
|
||||
var _i = i;
|
||||
res.put(i + "", new FieldMember(false, true, true) {
|
||||
@Override public Value get(Environment env, Value self) { return stack[_i]; }
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
stack[_i] = val;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
// var num = key.toNumber(env);
|
||||
// var i = key.toInt(env);
|
||||
|
||||
return res;
|
||||
}
|
||||
// if (num != i || i < 0 || i >= stackPtr) return null;
|
||||
// else return new FieldMember(this, false, true, true) {
|
||||
// @Override public Value get(Environment env, Value self) { return stack[i]; }
|
||||
// @Override public boolean set(Environment env, Value val, Value self) {
|
||||
// stack[i] = val;
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// @Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||
// var res = new LinkedHashSet<String>();
|
||||
// res.addAll(super.getOwnMembers(env, onlyEnumerable));
|
||||
|
||||
// for (var i = 0; i < stackPtr; i++) res.add(i + "");
|
||||
|
||||
// return res;
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,7 +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.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
@ -52,45 +51,50 @@ public class InstructionRunner {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
frame.push(funcObj.callNew(env, instr.get(1), callArgs));
|
||||
frame.push(funcObj.construct(env, instr.get(1), callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
|
||||
var setterVal = frame.pop();
|
||||
var getterVal = frame.pop();
|
||||
var val = frame.pop();
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
FunctionValue getter, setter;
|
||||
FunctionValue accessor;
|
||||
|
||||
if (getterVal == Value.UNDEFINED) getter = null;
|
||||
else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal;
|
||||
if (val == Value.UNDEFINED) accessor = null;
|
||||
else if (val instanceof FunctionValue func) accessor = func;
|
||||
else throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
|
||||
if (setterVal == Value.UNDEFINED) setter = null;
|
||||
else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal;
|
||||
else throw EngineException.ofType("Setter must be a function or undefined.");
|
||||
if ((boolean)instr.get(0)) obj.defineOwnMember(env, key, new PropertyMember(obj, null, accessor, true, true));
|
||||
else obj.defineOwnMember(env, key, new PropertyMember(obj, accessor, null, true, true));
|
||||
|
||||
obj.defineOwnMember(env, key, new PropertyMember(getter, setter, true, true));
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execDefField(Environment env, Instruction instr, Frame frame) {
|
||||
var val = frame.pop();
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
obj.defineOwnMember(env, key, val);
|
||||
|
||||
frame.push(obj);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execKeys(Environment env, Instruction instr, Frame frame) {
|
||||
var val = frame.pop();
|
||||
|
||||
var members = new ArrayList<>(val.getMembers(env, false, true).keySet());
|
||||
var members = new ArrayList<>(val.getMembers(env, instr.get(0), instr.get(1)));
|
||||
Collections.reverse(members);
|
||||
|
||||
frame.push(null);
|
||||
|
||||
for (var el : members) {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el)));
|
||||
obj.defineOwnMember(env, "value", new StringValue(el));
|
||||
frame.push(obj);
|
||||
}
|
||||
|
||||
@ -116,9 +120,12 @@ public class InstructionRunner {
|
||||
|
||||
private static Value execDup(Environment env, Instruction instr, Frame frame) {
|
||||
int count = instr.get(0);
|
||||
int offset = instr.get(1);
|
||||
|
||||
var el = frame.stack[frame.stackPtr - offset - 1];
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
frame.push(frame.peek());
|
||||
frame.push(el);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
@ -231,7 +238,7 @@ public class InstructionRunner {
|
||||
}
|
||||
private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) {
|
||||
if (env.hasNotNull(Value.REGEX_CONSTR)) {
|
||||
frame.push(env.get(Value.REGEX_CONSTR).callNew(env, instr.get(0), instr.get(1)));
|
||||
frame.push(env.get(Value.REGEX_CONSTR).construct(env, instr.get(0), instr.get(1)));
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofSyntax("Regex is not supported.");
|
||||
@ -447,10 +454,15 @@ public class InstructionRunner {
|
||||
}
|
||||
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 ((boolean)instr.get(1)) {
|
||||
frame.push(Value.global(env).getMember(env, name));
|
||||
}
|
||||
else {
|
||||
var res = Value.global(env).getMemberOrNull(env, name);
|
||||
|
||||
if (res == null) throw EngineException.ofSyntax(name + " is not defined");
|
||||
else frame.push(res);
|
||||
if (res == null) throw EngineException.ofSyntax(name + " is not defined");
|
||||
else frame.push(res);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -574,6 +586,7 @@ public class InstructionRunner {
|
||||
|
||||
case KEYS: return execKeys(env, instr, frame);
|
||||
case DEF_PROP: return execDefProp(env, instr, frame);
|
||||
case DEF_FIELD: return execDefField(env, instr, frame);
|
||||
case TYPEOF: return execTypeof(env, instr, frame);
|
||||
case DELETE: return execDelete(env, instr, frame);
|
||||
|
||||
|
@ -8,7 +8,6 @@ import me.topchetoeu.jscript.common.json.JSONElement;
|
||||
import me.topchetoeu.jscript.common.json.JSONList;
|
||||
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
@ -27,7 +26,7 @@ public class JSONConverter {
|
||||
var res = new ObjectValue();
|
||||
|
||||
for (var el : val.map().entrySet()) {
|
||||
res.defineOwnMember(null, el.getKey(), FieldMember.of(toJs(el.getValue())));
|
||||
res.defineOwnMember(null, el.getKey(), toJs(el.getValue()));
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -70,10 +69,11 @@ public class JSONConverter {
|
||||
|
||||
var res = new JSONMap();
|
||||
|
||||
for (var el : val.getMembers(env, true, true).entrySet()) {
|
||||
var jsonEl = fromJs(env, el.getValue().get(env, val), prev);
|
||||
if (jsonEl == null) continue;
|
||||
res.put(el.getKey(), jsonEl);
|
||||
for (var key : val.getOwnMembers(env, true)) {
|
||||
var el = fromJs(env, val.getMember(env, key), prev);
|
||||
if (el == null) continue;
|
||||
|
||||
res.put(key, el);
|
||||
}
|
||||
|
||||
prev.remove(val);
|
||||
|
@ -41,7 +41,7 @@ public class SimpleRepl {
|
||||
try {
|
||||
try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); }
|
||||
}
|
||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
|
||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
|
||||
|
||||
for (var arg : args) {
|
||||
try {
|
||||
@ -58,7 +58,7 @@ public class SimpleRepl {
|
||||
}
|
||||
catch (ExecutionException e) { throw e.getCause(); }
|
||||
}
|
||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
|
||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
|
||||
}
|
||||
|
||||
for (var i = 0; ; i++) {
|
||||
@ -77,7 +77,7 @@ public class SimpleRepl {
|
||||
}
|
||||
catch (ExecutionException e) { throw e.getCause(); }
|
||||
}
|
||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
|
||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
@ -171,9 +171,7 @@ public class SimpleRepl {
|
||||
var configurable = args.get(4).toBoolean();
|
||||
var value = args.get(5);
|
||||
|
||||
obj.defineOwnMember(args.env, key, FieldMember.of(value, enumerable, configurable, writable));
|
||||
|
||||
return Value.UNDEFINED;
|
||||
return BoolValue.of(obj.defineOwnMember(args.env, key, FieldMember.of(obj, value, configurable, enumerable, writable)));
|
||||
}));
|
||||
res.defineOwnMember(env, "defineProperty", new NativeFunction(args -> {
|
||||
var obj = (ObjectValue)args.get(0);
|
||||
@ -183,9 +181,7 @@ public class SimpleRepl {
|
||||
var getter = args.get(4) instanceof VoidValue ? null : (FunctionValue)args.get(4);
|
||||
var setter = args.get(5) instanceof VoidValue ? null : (FunctionValue)args.get(5);
|
||||
|
||||
obj.defineOwnMember(args.env, key, new PropertyMember(getter, setter, configurable, enumerable));
|
||||
|
||||
return Value.UNDEFINED;
|
||||
return BoolValue.of(obj.defineOwnMember(args.env, key, new PropertyMember(obj, getter, setter, configurable, enumerable)));
|
||||
}));
|
||||
res.defineOwnMember(env, "getPrototype", new NativeFunction(args -> {
|
||||
return args.get(0).getPrototype(env);
|
||||
@ -195,6 +191,19 @@ public class SimpleRepl {
|
||||
args.get(0).setPrototype(env, proto);
|
||||
return args.get(0);
|
||||
}));
|
||||
res.defineOwnMember(env, "getOwnMembers", new NativeFunction(args -> {
|
||||
var val = new ArrayValue();
|
||||
|
||||
for (var key : args.get(0).getOwnMembers(env, args.get(1).toBoolean())) {
|
||||
val.set(val.size(), new StringValue(key));
|
||||
}
|
||||
|
||||
return val;
|
||||
}));
|
||||
res.defineOwnMember(env, "getOwnSymbolMembers", new NativeFunction(args -> {
|
||||
return ArrayValue.of(args.get(0).getOwnSymbolMembers(env, args.get(1).toBoolean()));
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -216,12 +225,21 @@ public class SimpleRepl {
|
||||
if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new");
|
||||
else return new StringValue("call");
|
||||
}));
|
||||
|
||||
res.defineOwnMember(env, "invoke", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var self = args.get(1);
|
||||
var funcArgs = (ArrayValue)args.get(2);
|
||||
var name = args.get(3).toString(env).value;
|
||||
|
||||
return func.call(env, self, funcArgs.toArray());
|
||||
return func.invoke(env, name, self, funcArgs.toArray());
|
||||
}));
|
||||
res.defineOwnMember(env, "construct", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var funcArgs = (ArrayValue)args.get(1);
|
||||
var name = args.get(2).toString(env).value;
|
||||
|
||||
return func.construct(env, name, funcArgs.toArray());
|
||||
}));
|
||||
|
||||
return res;
|
||||
@ -251,7 +269,7 @@ public class SimpleRepl {
|
||||
var self = args.get(1);
|
||||
var funcArgs = (ArrayValue)args.get(2);
|
||||
|
||||
return func.call(env, self, funcArgs.toArray());
|
||||
return func.invoke(env, self, funcArgs.toArray());
|
||||
}));
|
||||
|
||||
return res;
|
||||
@ -275,28 +293,55 @@ public class SimpleRepl {
|
||||
var obj = (ObjectValue)args.get(1);
|
||||
|
||||
switch (type) {
|
||||
case "string":
|
||||
args.env.add(Value.STRING_PROTO, obj);
|
||||
case "object":
|
||||
args.env.add(Value.OBJECT_PROTO, obj);
|
||||
break;
|
||||
case "number":
|
||||
args.env.add(Value.NUMBER_PROTO, obj);
|
||||
case "function":
|
||||
args.env.add(Value.FUNCTION_PROTO, obj);
|
||||
break;
|
||||
case "array":
|
||||
args.env.add(Value.ARRAY_PROTO, obj);
|
||||
break;
|
||||
case "boolean":
|
||||
args.env.add(Value.BOOL_PROTO, obj);
|
||||
break;
|
||||
case "number":
|
||||
args.env.add(Value.NUMBER_PROTO, obj);
|
||||
break;
|
||||
case "string":
|
||||
args.env.add(Value.STRING_PROTO, obj);
|
||||
break;
|
||||
case "symbol":
|
||||
args.env.add(Value.SYMBOL_PROTO, obj);
|
||||
break;
|
||||
case "object":
|
||||
args.env.add(Value.OBJECT_PROTO, obj);
|
||||
case "error":
|
||||
args.env.add(Value.ERROR_PROTO, obj);
|
||||
break;
|
||||
case "syntax":
|
||||
args.env.add(Value.SYNTAX_ERR_PROTO, obj);
|
||||
break;
|
||||
case "type":
|
||||
args.env.add(Value.TYPE_ERR_PROTO, obj);
|
||||
break;
|
||||
case "range":
|
||||
args.env.add(Value.RANGE_ERR_PROTO, obj);
|
||||
break;
|
||||
}
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnMember(env, "setIntrinsic", new NativeFunction(args -> {
|
||||
var name = args.get(0).toString(env).value;
|
||||
var val = args.get(1);
|
||||
|
||||
Value.intrinsics(environment).put(name, val);
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnMember(env, "compile", new NativeFunction(args -> {
|
||||
return Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env).value);
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||
@ -67,7 +66,7 @@ public class EngineException extends RuntimeException {
|
||||
public String toString(Environment env) {
|
||||
var ss = new StringBuilder();
|
||||
try {
|
||||
ss.append(value.toString(env)).append('\n');
|
||||
ss.append(value.toString(env).value).append('\n');
|
||||
}
|
||||
catch (EngineException e) {
|
||||
var name = value.getMember(env, "name");
|
||||
@ -98,8 +97,8 @@ public class EngineException extends RuntimeException {
|
||||
|
||||
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)));
|
||||
if (name != null) res.defineOwnMember(Environment.empty(), "name", new StringValue(name));
|
||||
res.defineOwnMember(Environment.empty(), "message", new StringValue(msg));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -7,51 +7,67 @@ import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
|
||||
|
||||
public interface Member {
|
||||
public static final class PropertyMember implements Member {
|
||||
public final FunctionValue getter;
|
||||
public final FunctionValue setter;
|
||||
public final boolean configurable;
|
||||
public final boolean enumerable;
|
||||
public final Value self;
|
||||
public FunctionValue getter;
|
||||
public FunctionValue setter;
|
||||
public boolean configurable;
|
||||
public boolean enumerable;
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
if (getter != null) return getter.call(env, self);
|
||||
if (getter != null) return getter.call(env, false, "", self);
|
||||
else return Value.UNDEFINED;
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
if (setter == null) return false;
|
||||
setter.call(env, self, val);
|
||||
setter.call(env, false, "", self, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean configurable() { return configurable; }
|
||||
@Override public boolean configurable() { return configurable && self.getState().configurable; }
|
||||
@Override public boolean enumerable() { return enumerable; }
|
||||
|
||||
@Override public boolean configure(Environment env, Member newMember, Value self) {
|
||||
if (!(newMember instanceof PropertyMember)) return false;
|
||||
var prop = (PropertyMember)newMember;
|
||||
@Override public boolean redefine(Environment env, Member newMember, Value self) {
|
||||
// If the given member isn't a property, we can't redefine
|
||||
if (!(newMember instanceof PropertyMember prop)) return false;
|
||||
|
||||
if (prop.configurable != configurable) return false;
|
||||
if (prop.enumerable != enumerable) return false;
|
||||
if (configurable()) {
|
||||
// We will overlay the getters and setters of the new member
|
||||
enumerable = prop.enumerable;
|
||||
configurable = prop.configurable;
|
||||
|
||||
if (prop.getter == getter) return true;
|
||||
if (prop.setter == setter) return true;
|
||||
return false;
|
||||
if (prop.getter != null) getter = prop.getter;
|
||||
if (prop.setter != null) setter = prop.setter;
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// We will pretend that a redefinition has occurred if the two members match exactly
|
||||
if (prop.configurable() != configurable()) return false;
|
||||
if (prop.enumerable != enumerable) return false;
|
||||
if (prop.getter != getter || prop.setter != setter) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public ObjectValue descriptor(Environment env, Value self) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
if (getter == null) res.defineOwnMember(env, "getter", FieldMember.of(Value.UNDEFINED));
|
||||
else res.defineOwnMember(env, "getter", FieldMember.of(getter));
|
||||
// Don't touch the ordering, as it's emulating V8
|
||||
|
||||
if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(Value.UNDEFINED));
|
||||
else res.defineOwnMember(env, "setter", FieldMember.of(setter));
|
||||
if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED);
|
||||
else res.defineOwnMember(env, "getter", getter);
|
||||
|
||||
res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable)));
|
||||
res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable)));
|
||||
if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED);
|
||||
else res.defineOwnMember(env, "setter", setter);
|
||||
|
||||
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
|
||||
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
|
||||
return res;
|
||||
}
|
||||
|
||||
public PropertyMember(FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
||||
public PropertyMember(Value self, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
||||
this.self = self;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
this.configurable = configurable;
|
||||
@ -69,60 +85,87 @@ public interface Member {
|
||||
value = val;
|
||||
return true;
|
||||
}
|
||||
public SimpleFieldMember(Value value, boolean configurable, boolean enumerable, boolean writable) {
|
||||
super(configurable, enumerable, writable);
|
||||
public SimpleFieldMember(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
|
||||
super(self, configurable, enumerable, writable);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public final Value self;
|
||||
public boolean configurable;
|
||||
public boolean enumerable;
|
||||
public boolean writable;
|
||||
|
||||
@Override public final boolean configurable() { return configurable; }
|
||||
@Override public final boolean configurable() { return configurable && self.getState().configurable; }
|
||||
@Override public final boolean enumerable() { return enumerable; }
|
||||
@Override public final boolean configure(Environment env, Member newMember, Value self) {
|
||||
if (!(newMember instanceof FieldMember)) return false;
|
||||
var field = (FieldMember)newMember;
|
||||
public final boolean writable() { return writable && self.getState().writable; }
|
||||
|
||||
if (field.configurable != configurable) return false;
|
||||
if (field.enumerable != enumerable) return false;
|
||||
if (!writable) return field.get(env, self).equals(get(env, self));
|
||||
@Override public final boolean redefine(Environment env, Member newMember, Value self) {
|
||||
// If the given member isn't a field, we can't redefine
|
||||
if (!(newMember instanceof FieldMember field)) return false;
|
||||
|
||||
set(env, field.get(env, self), self);
|
||||
writable = field.writable;
|
||||
return true;
|
||||
if (configurable()) {
|
||||
configurable = field.configurable;
|
||||
enumerable = field.enumerable;
|
||||
writable = field.enumerable;
|
||||
|
||||
// We will try to set a new value. However, the underlying field might be immutably readonly
|
||||
// In such case, we will silently fail, since this is not covered by the specification
|
||||
if (!set(env, field.get(env, self), self)) writable = false;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// New field settings must be an exact match
|
||||
if (configurable() != field.configurable()) return false;
|
||||
if (enumerable() != field.enumerable()) return false;
|
||||
|
||||
if (!writable()) {
|
||||
// If the field isn't writable, the redefinition should be an exact match
|
||||
if (field.writable()) return false;
|
||||
if (field.get(env, self).equals(this.get(env, self))) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Writable non-configurable fields may be made readonly or their values may be changed
|
||||
writable = field.writable;
|
||||
|
||||
if (!set(env, field.get(env, self), self)) writable = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public ObjectValue descriptor(Environment env, Value self) {
|
||||
var res = new ObjectValue();
|
||||
res.defineOwnMember(env, "value", FieldMember.of(get(env, self)));
|
||||
res.defineOwnMember(env, "writable", FieldMember.of(BoolValue.of(writable)));
|
||||
res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable)));
|
||||
res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable)));
|
||||
res.defineOwnMember(env, "value", get(env, self));
|
||||
res.defineOwnMember(env, "writable", BoolValue.of(writable));
|
||||
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
|
||||
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
|
||||
return res;
|
||||
}
|
||||
|
||||
public FieldMember(boolean configurable, boolean enumerable, boolean writable) {
|
||||
public FieldMember(Value self, boolean configurable, boolean enumerable, boolean writable) {
|
||||
this.self = self;
|
||||
this.configurable = configurable;
|
||||
this.enumerable = enumerable;
|
||||
this.writable = writable;
|
||||
}
|
||||
|
||||
public static FieldMember of(Value value) {
|
||||
return new SimpleFieldMember(value, true, true, true);
|
||||
public static FieldMember of(Value self, Value value) {
|
||||
return new SimpleFieldMember(self, value, true, true, true);
|
||||
}
|
||||
public static FieldMember of(Value value, boolean writable) {
|
||||
return new SimpleFieldMember(value, true, true, writable);
|
||||
public static FieldMember of(Value self, Value value, boolean writable) {
|
||||
return new SimpleFieldMember(self, value, true, true, writable);
|
||||
}
|
||||
public static FieldMember of(Value value, boolean configurable, boolean enumerable, boolean writable) {
|
||||
return new SimpleFieldMember(value, configurable, enumerable, writable);
|
||||
public static FieldMember of(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
|
||||
return new SimpleFieldMember(self, value, configurable, enumerable, writable);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean configurable();
|
||||
public boolean enumerable();
|
||||
public boolean configure(Environment env, Member newMember, Value self);
|
||||
public boolean redefine(Environment env, Member newMember, Value self);
|
||||
public ObjectValue descriptor(Environment env, Value self);
|
||||
|
||||
public Value get(Environment env, Value self);
|
||||
|
@ -7,8 +7,9 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
@ -19,6 +20,7 @@ import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
|
||||
@ -30,21 +32,21 @@ import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
||||
|
||||
public abstract class Value {
|
||||
public static enum CompareResult {
|
||||
NOT_EQUAL,
|
||||
EQUAL,
|
||||
LESS,
|
||||
GREATER;
|
||||
public static enum State {
|
||||
NORMAL(true, true, true),
|
||||
NON_EXTENDABLE(false, true, true),
|
||||
SEALED(false, false, true),
|
||||
FROZEN(false, false, false);
|
||||
|
||||
public boolean less() { return this == LESS; }
|
||||
public boolean greater() { return this == GREATER; }
|
||||
public boolean lessOrEqual() { return this == LESS || this == EQUAL; }
|
||||
public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; }
|
||||
|
||||
public static CompareResult from(int cmp) {
|
||||
if (cmp < 0) return LESS;
|
||||
if (cmp > 0) return GREATER;
|
||||
return EQUAL;
|
||||
public final boolean extendable;
|
||||
public final boolean configurable;
|
||||
public final boolean writable;
|
||||
|
||||
private State(boolean extendable, boolean configurable, boolean writable) {
|
||||
this.extendable = extendable;
|
||||
this.writable = writable;
|
||||
this.configurable = configurable;
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +84,11 @@ public abstract class Value {
|
||||
if (isNew) throw EngineException.ofType(name + " is not a constructor");
|
||||
else throw EngineException.ofType(name + " is not a function");
|
||||
}
|
||||
public final Value callNew(Environment env, String name, Value ...args) {
|
||||
|
||||
public final Value invoke(Environment env, String name, Value self, Value ...args) {
|
||||
return call(env, false, name, self, args);
|
||||
}
|
||||
public final Value construct(Environment env, String name, Value ...args) {
|
||||
var res = new ObjectValue();
|
||||
var proto = getMember(env, new StringValue("prototype"));
|
||||
|
||||
@ -95,11 +101,11 @@ public abstract class Value {
|
||||
return res;
|
||||
}
|
||||
|
||||
public final Value call(Environment env, Value self, Value ...args) {
|
||||
return call(env, false, "", self, args);
|
||||
public final Value invoke(Environment env, Value self, Value ...args) {
|
||||
return invoke(env, "", self, args);
|
||||
}
|
||||
public final Value callNew(Environment env, Value ...args) {
|
||||
return callNew(env, "", args);
|
||||
public final Value construct(Environment env, Value ...args) {
|
||||
return construct(env, "", args);
|
||||
}
|
||||
|
||||
public abstract Value toPrimitive(Environment env);
|
||||
@ -119,14 +125,20 @@ public abstract class Value {
|
||||
}
|
||||
|
||||
public abstract Member getOwnMember(Environment env, KeyCache key);
|
||||
public abstract Map<String, Member> getOwnMembers(Environment env);
|
||||
public abstract Map<SymbolValue, Member> getOwnSymbolMembers(Environment env);
|
||||
public abstract Set<String> getOwnMembers(Environment env, boolean onlyEnumerable);
|
||||
public abstract Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable);
|
||||
public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member);
|
||||
public abstract boolean deleteOwnMember(Environment env, KeyCache key);
|
||||
|
||||
public abstract ObjectValue getPrototype(Environment env);
|
||||
public abstract boolean setPrototype(Environment env, ObjectValue val);
|
||||
|
||||
public abstract State getState();
|
||||
|
||||
public abstract void preventExtensions();
|
||||
public abstract void seal();
|
||||
public abstract void freeze();
|
||||
|
||||
public final Member getOwnMember(Environment env, Value key) {
|
||||
return getOwnMember(env, new KeyCache(key));
|
||||
}
|
||||
@ -154,19 +166,19 @@ public abstract class Value {
|
||||
}
|
||||
|
||||
public final boolean defineOwnMember(Environment env, KeyCache key, Value val) {
|
||||
return defineOwnMember(env, key, FieldMember.of(val));
|
||||
return defineOwnMember(env, key, FieldMember.of(this, val));
|
||||
}
|
||||
public final boolean defineOwnMember(Environment env, Value key, Value val) {
|
||||
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
|
||||
return defineOwnMember(env, new KeyCache(key), val);
|
||||
}
|
||||
public final boolean defineOwnMember(Environment env, String key, Value val) {
|
||||
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
|
||||
return defineOwnMember(env, new KeyCache(key), val);
|
||||
}
|
||||
public final boolean defineOwnMember(Environment env, int key, Value val) {
|
||||
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
|
||||
return defineOwnMember(env, new KeyCache(key), val);
|
||||
}
|
||||
public final boolean defineOwnMember(Environment env, double key, Value val) {
|
||||
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
|
||||
return defineOwnMember(env, new KeyCache(key), val);
|
||||
}
|
||||
|
||||
public final boolean deleteOwnMember(Environment env, Value key) {
|
||||
@ -224,8 +236,8 @@ public abstract class Value {
|
||||
public final boolean setMember(Environment env, KeyCache key, Value val) {
|
||||
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
|
||||
var member = obj.getOwnMember(env, key);
|
||||
if (member != null) {
|
||||
if (member.set(env, val, obj)) {
|
||||
if (member instanceof PropertyMember prop) {
|
||||
if (prop.set(env, val, obj)) {
|
||||
if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
|
||||
return true;
|
||||
}
|
||||
@ -233,7 +245,7 @@ public abstract class Value {
|
||||
}
|
||||
}
|
||||
|
||||
if (defineOwnMember(env, key, FieldMember.of(val))) {
|
||||
if (defineOwnMember(env, key, val)) {
|
||||
if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
|
||||
return true;
|
||||
}
|
||||
@ -317,8 +329,8 @@ public abstract class Value {
|
||||
return deleteMember(env, new KeyCache(key));
|
||||
}
|
||||
|
||||
public final Map<String, Member> getMembers(Environment env, boolean own, boolean onlyEnumerable) {
|
||||
var res = new LinkedHashMap<String, Member>();
|
||||
public final Set<String> getMembers(Environment env, boolean own, boolean onlyEnumerable) {
|
||||
var res = new LinkedHashSet<String>();
|
||||
var protos = new ArrayList<Value>();
|
||||
|
||||
for (var proto = this; proto != null; proto = proto.getPrototype(env)) {
|
||||
@ -329,19 +341,13 @@ public abstract class Value {
|
||||
Collections.reverse(protos);
|
||||
|
||||
for (var proto : protos) {
|
||||
if (onlyEnumerable) {
|
||||
for (var el : proto.getOwnMembers(env).entrySet()) {
|
||||
if (!el.getValue().enumerable()) continue;
|
||||
res.put(el.getKey(), el.getValue());
|
||||
}
|
||||
}
|
||||
else res.putAll(proto.getOwnMembers(env));
|
||||
res.addAll(proto.getOwnMembers(env, onlyEnumerable));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
public final Map<SymbolValue, Member> getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) {
|
||||
var res = new LinkedHashMap<SymbolValue, Member>();
|
||||
public final Set<SymbolValue> getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) {
|
||||
var res = new LinkedHashSet<SymbolValue>();
|
||||
var protos = new ArrayList<Value>();
|
||||
|
||||
for (var proto = this; proto != null; proto = proto.getPrototype(env)) {
|
||||
@ -352,13 +358,7 @@ public abstract class Value {
|
||||
Collections.reverse(protos);
|
||||
|
||||
for (var proto : protos) {
|
||||
if (onlyEnumerable) {
|
||||
for (var el : proto.getOwnSymbolMembers(env).entrySet()) {
|
||||
if (!el.getValue().enumerable()) continue;
|
||||
res.put(el.getKey(), el.getValue());
|
||||
}
|
||||
}
|
||||
else res.putAll(proto.getOwnSymbolMembers(env));
|
||||
res.addAll(proto.getOwnSymbolMembers(env, onlyEnumerable));
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -389,7 +389,7 @@ public abstract class Value {
|
||||
private void loadNext() {
|
||||
if (supplier == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = supplier.call(env, Value.UNDEFINED);
|
||||
var curr = supplier.invoke(env, Value.UNDEFINED);
|
||||
|
||||
if (curr == null) { supplier = null; value = null; }
|
||||
if (curr.getMember(env, new StringValue("done")).toBoolean()) { supplier = null; value = null; }
|
||||
@ -417,12 +417,12 @@ public abstract class Value {
|
||||
|
||||
public void callWith(Environment env, Iterable<? extends Value> it) {
|
||||
for (var el : it) {
|
||||
this.call(env, Value.UNDEFINED, el);
|
||||
this.invoke(env, Value.UNDEFINED, el);
|
||||
}
|
||||
}
|
||||
public void callWithAsync(Environment env, Iterable<? extends Value> it, boolean async) {
|
||||
for (var el : it) {
|
||||
env.get(EventLoop.KEY).pushMsg(() -> this.call(env, Value.UNDEFINED, el), true);
|
||||
env.get(EventLoop.KEY).pushMsg(() -> this.invoke(env, Value.UNDEFINED, el), true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,7 +444,7 @@ public abstract class Value {
|
||||
if (
|
||||
func.prototype instanceof ObjectValue objProto &&
|
||||
objProto.getMember(env, "constructor") == func &&
|
||||
objProto.getOwnMembers(env).size() + objProto.getOwnSymbolMembers(env).size() == 1
|
||||
objProto.getOwnMembers(env, true).size() + objProto.getOwnSymbolMembers(env, true).size() == 1
|
||||
) { keys.remove("constructor"); }
|
||||
}
|
||||
else if (this instanceof ArrayValue) {
|
||||
@ -469,30 +469,42 @@ public abstract class Value {
|
||||
|
||||
passed.add(this);
|
||||
|
||||
if (keys.size() + obj.getOwnSymbolMembers(env).size() == 0) {
|
||||
if (!printed) res.append("{}\n");
|
||||
if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) {
|
||||
if (!printed) res.append("{}");
|
||||
}
|
||||
else if (!printed) {
|
||||
if (tab > 3) return "{...}";
|
||||
res.append("{\n");
|
||||
|
||||
for (var entry : obj.getOwnSymbolMembers(env).entrySet()) {
|
||||
for (var entry : obj.getOwnSymbolMembers(env, true)) {
|
||||
for (int i = 0; i < tab + 1; i++) res.append(" ");
|
||||
res.append("[" + entry.getKey().value + "]" + ": ");
|
||||
res.append("[" + entry.value + "]" + ": ");
|
||||
|
||||
var member = entry.getValue();
|
||||
if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1));
|
||||
else res.append("[property]");
|
||||
var member = obj.getOwnMember(env, entry);
|
||||
if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1));
|
||||
else if (member instanceof PropertyMember prop) {
|
||||
if (prop.getter == null && prop.setter == null) res.append("[No accessors]");
|
||||
else if (prop.getter == null) res.append("[Setter]");
|
||||
else if (prop.setter == null) res.append("[Getter]");
|
||||
else res.append("[Getter/Setter]");
|
||||
}
|
||||
else res.append("[???]");
|
||||
|
||||
res.append(",\n");
|
||||
}
|
||||
for (var entry : obj.getOwnMembers(env).entrySet()) {
|
||||
for (var entry : obj.getOwnMembers(env, true)) {
|
||||
for (int i = 0; i < tab + 1; i++) res.append(" ");
|
||||
res.append(entry.getKey() + ": ");
|
||||
res.append(entry + ": ");
|
||||
|
||||
var member = entry.getValue();
|
||||
if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1));
|
||||
else res.append("[property]");
|
||||
var member = obj.getOwnMember(env, entry);
|
||||
if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1));
|
||||
else if (member instanceof PropertyMember prop) {
|
||||
if (prop.getter == null && prop.setter == null) res.append("[No accessors]");
|
||||
else if (prop.getter == null) res.append("[Setter]");
|
||||
else if (prop.setter == null) res.append("[Getter]");
|
||||
else res.append("[Getter/Setter]");
|
||||
}
|
||||
else res.append("[???]");
|
||||
|
||||
res.append(",\n");
|
||||
}
|
||||
@ -527,8 +539,8 @@ public abstract class Value {
|
||||
return new NativeFunction("", args -> {
|
||||
var obj = new ObjectValue();
|
||||
|
||||
if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE));
|
||||
else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next()));
|
||||
if (!it.hasNext()) obj.defineOwnMember(args.env, "done", BoolValue.TRUE);
|
||||
else obj.defineOwnMember(args.env, "value", it.next());
|
||||
|
||||
return obj;
|
||||
});
|
||||
@ -658,22 +670,22 @@ public abstract class Value {
|
||||
return a.toString(env).equals(b.toString(env));
|
||||
}
|
||||
|
||||
// public static Value operation(Environment env, Operation op, Value ...args) {
|
||||
// }
|
||||
|
||||
public static final String errorToReadable(RuntimeException err, String prefix) {
|
||||
public static final String errorToReadable(Environment env, RuntimeException err, String prefix) {
|
||||
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
|
||||
if (err instanceof EngineException) {
|
||||
var ee = ((EngineException)err);
|
||||
if (err instanceof EngineException ee) {
|
||||
if (env == null) env = ee.env;
|
||||
|
||||
try {
|
||||
return prefix + " " + ee.toString(ee.env);
|
||||
return prefix + " " + ee.toString(env);
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
return prefix + " " + ee.value.toReadable(ee.env);
|
||||
return prefix + " " + ee.value.toReadable(env);
|
||||
}
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
return prefix + " SyntaxError " + ((SyntaxException)err).msg;
|
||||
else if (err instanceof SyntaxException syntax) {
|
||||
var newErr = EngineException.ofSyntax(syntax.msg);
|
||||
newErr.add(null, syntax.loc.filename() + "", syntax.loc);
|
||||
return errorToReadable(env, newErr, prefix);
|
||||
}
|
||||
else if (err.getCause() instanceof InterruptedException) return "";
|
||||
else {
|
||||
|
@ -10,6 +10,8 @@ import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||
|
||||
public abstract class FunctionValue extends ObjectValue {
|
||||
private static final StringValue typeString = new StringValue("function");
|
||||
|
||||
public String name = "";
|
||||
public int length;
|
||||
public Value prototype = new ObjectValue();
|
||||
@ -17,7 +19,7 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
public boolean enableCall = true;
|
||||
public boolean enableNew = true;
|
||||
|
||||
private final FieldMember nameField = new FieldMember(true, false, false) {
|
||||
private final FieldMember nameField = new FieldMember(this, true, false, false) {
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
if (name == null) return new StringValue("");
|
||||
return new StringValue(name);
|
||||
@ -27,7 +29,7 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private final FieldMember lengthField = new FieldMember(true, false, false) {
|
||||
private final FieldMember lengthField = new FieldMember(this, true, false, false) {
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return new NumberValue(length);
|
||||
}
|
||||
@ -35,7 +37,7 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
private final FieldMember prototypeField = new FieldMember(false, false, true) {
|
||||
private final FieldMember prototypeField = new FieldMember(this, false, false, true) {
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return prototype;
|
||||
}
|
||||
@ -77,6 +79,8 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public StringValue type() { return typeString; }
|
||||
|
||||
public void setName(String val) {
|
||||
if (this.name == null || this.name.equals("")) this.name = val;
|
||||
}
|
||||
@ -88,7 +92,7 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
this.length = length;
|
||||
this.name = name;
|
||||
|
||||
prototype.defineOwnMember(null, "constructor", FieldMember.of(this));
|
||||
prototype.defineOwnMember(null, "constructor", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,97 @@
|
||||
package me.topchetoeu.jscript.runtime.values.objects;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
|
||||
|
||||
public abstract class ArrayLikeValue extends ObjectValue {
|
||||
private static class IndexField extends FieldMember {
|
||||
private int i;
|
||||
private ArrayLikeValue arr;
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return arr.get(i);
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
arr.set(i, val);
|
||||
return true;
|
||||
}
|
||||
public IndexField(int i, ArrayLikeValue arr) {
|
||||
super(arr, true, true, true);
|
||||
this.arr = arr;
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
private final FieldMember lengthField = new FieldMember(this, false, false, true) {
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return new NumberValue(size());
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
return setSize(val.toInt(env));
|
||||
}
|
||||
};
|
||||
|
||||
public abstract int size();
|
||||
public abstract boolean setSize(int val);
|
||||
|
||||
public abstract Value get(int i);
|
||||
public abstract void set(int i, Value val);
|
||||
public abstract boolean has(int i);
|
||||
public abstract void remove(int i);
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
var res = super.getOwnMember(env, key);
|
||||
if (res != null) return res;
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0 && i < size() && has(i)) return new IndexField(i, this);
|
||||
else if (key.toString(env).equals("length")) return lengthField;
|
||||
else return null;
|
||||
}
|
||||
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
||||
if (!(member instanceof FieldMember) || super.getOwnMember(env, key) != null) return super.defineOwnMember(env, key, member);
|
||||
if (!getState().writable) return false;
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0) {
|
||||
if (!getState().extendable && !has(i)) return false;
|
||||
set(i, ((FieldMember)member).get(env, this));
|
||||
return true;
|
||||
}
|
||||
else return super.defineOwnMember(env, key, member);
|
||||
}
|
||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||
if (!super.deleteOwnMember(env, key)) return false;
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0 && i < size()) return super.deleteOwnMember(env, key);
|
||||
else return true;
|
||||
}
|
||||
|
||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||
var res = new LinkedHashSet<String>();
|
||||
|
||||
res.addAll(super.getOwnMembers(env, onlyEnumerable));
|
||||
|
||||
for (var i = 0; i < size(); i++) {
|
||||
if (has(i)) res.add(i + "");
|
||||
}
|
||||
|
||||
if (!onlyEnumerable) res.add("length");
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
@ -4,50 +4,15 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
||||
|
||||
// TODO: Make methods generic
|
||||
public class ArrayValue extends ObjectValue implements Iterable<Value> {
|
||||
public class ArrayValue extends ArrayLikeValue implements Iterable<Value> {
|
||||
private Value[] values;
|
||||
private int size;
|
||||
|
||||
private final FieldMember lengthField = new FieldMember(false, false, true) {
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return new NumberValue(size);
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
size = val.toInt(env);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private class IndexField extends FieldMember {
|
||||
private int i;
|
||||
private ArrayValue arr;
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return arr.get(i);
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
arr.set(i, val);
|
||||
return true;
|
||||
}
|
||||
public IndexField(int i, ArrayValue arr) {
|
||||
super(true, true, true);
|
||||
this.arr = arr;
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
private Value[] alloc(int index) {
|
||||
index++;
|
||||
if (index < values.length) return values;
|
||||
@ -69,26 +34,27 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Value get(int i) {
|
||||
@Override public Value get(int i) {
|
||||
if (i < 0 || i >= size) return null;
|
||||
var res = values[i];
|
||||
|
||||
if (res == null) return Value.UNDEFINED;
|
||||
else return res;
|
||||
}
|
||||
public void set(int i, Value val) {
|
||||
@Override public void set(int i, Value val) {
|
||||
if (i < 0) return;
|
||||
|
||||
alloc(i)[i] = val;
|
||||
if (i >= size) size = i + 1;
|
||||
}
|
||||
public boolean has(int i) {
|
||||
@Override public boolean has(int i) {
|
||||
return i >= 0 && i < size && values[i] != null;
|
||||
}
|
||||
public void remove(int i) {
|
||||
@Override public void remove(int i) {
|
||||
if (i < 0 || i >= values.length) return;
|
||||
values[i] = null;
|
||||
}
|
||||
|
||||
public void shrink(int n) {
|
||||
if (n >= values.length) {
|
||||
values = new Value[16];
|
||||
@ -149,54 +115,6 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
|
||||
});
|
||||
}
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
var res = super.getOwnMember(env, key);
|
||||
if (res != null) return res;
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0 && i < size && has(i)) return new IndexField(i, this);
|
||||
else if (key.toString(env).equals("length")) return lengthField;
|
||||
else return null;
|
||||
}
|
||||
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
||||
if (!(member instanceof FieldMember) || hasMember(env, key, true)) return super.defineOwnMember(env, key, member);
|
||||
if (!extensible) return false;
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0) {
|
||||
set(i, ((FieldMember)member).get(env, this));
|
||||
return true;
|
||||
}
|
||||
else return super.defineOwnMember(env, key, member);
|
||||
}
|
||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||
if (!super.deleteOwnMember(env, key)) return false;
|
||||
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0 && i < size) return super.deleteOwnMember(env, key);
|
||||
else return true;
|
||||
}
|
||||
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) {
|
||||
var res = new LinkedHashMap<String, Member>();
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
var member = getOwnMember(env, i);
|
||||
if (member != null) res.put(i + "", member);
|
||||
}
|
||||
|
||||
res.put("length", lengthField);
|
||||
|
||||
res.putAll(super.getOwnMembers(env));
|
||||
|
||||
return res;
|
||||
}
|
||||
@Override public Iterator<Value> iterator() {
|
||||
return new Iterator<>() {
|
||||
private int i = 0;
|
||||
|
@ -1,8 +1,8 @@
|
||||
package me.topchetoeu.jscript.runtime.values.objects;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
@ -20,13 +20,6 @@ public class ObjectValue extends Value {
|
||||
public ObjectValue get(Environment env);
|
||||
}
|
||||
|
||||
public static enum State {
|
||||
NORMAL,
|
||||
NO_EXTENSIONS,
|
||||
SEALED,
|
||||
FROZEN,
|
||||
}
|
||||
|
||||
public static class Property {
|
||||
public final FunctionValue getter;
|
||||
public final FunctionValue setter;
|
||||
@ -41,8 +34,6 @@ public class ObjectValue extends Value {
|
||||
|
||||
protected PrototypeProvider prototype;
|
||||
|
||||
public boolean extensible = true;
|
||||
|
||||
public LinkedHashMap<String, Member> members = new LinkedHashMap<>();
|
||||
public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>();
|
||||
|
||||
@ -52,13 +43,13 @@ public class ObjectValue extends Value {
|
||||
var valueOf = getMember(env, new StringValue("valueOf"));
|
||||
|
||||
if (valueOf instanceof FunctionValue) {
|
||||
var res = valueOf.call(env, this);
|
||||
var res = valueOf.invoke(env, this);
|
||||
if (res.isPrimitive()) return res;
|
||||
}
|
||||
|
||||
var toString = getMember(env, new StringValue("toString"));
|
||||
if (toString instanceof FunctionValue) {
|
||||
var res = toString.call(env, this);
|
||||
var res = toString.invoke(env, this);
|
||||
if (res.isPrimitive()) return res;
|
||||
}
|
||||
}
|
||||
@ -70,9 +61,17 @@ public class ObjectValue extends Value {
|
||||
@Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
|
||||
@Override public StringValue type() { return typeString; }
|
||||
|
||||
private State state = State.NORMAL;
|
||||
|
||||
@Override public State getState() { return state; }
|
||||
|
||||
public final void preventExtensions() {
|
||||
extensible = false;
|
||||
if (state == State.NORMAL) state = State.NON_EXTENDABLE;
|
||||
}
|
||||
public final void seal() {
|
||||
if (state == State.NORMAL || state == State.NON_EXTENDABLE) state = State.SEALED;
|
||||
}
|
||||
@Override public final void freeze() { state = State.FROZEN; }
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
if (key.isSymbol()) return symbolMembers.get(key.toSymbol());
|
||||
@ -80,7 +79,7 @@ public class ObjectValue extends Value {
|
||||
}
|
||||
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
|
||||
var old = getOwnMember(env, key);
|
||||
if (old != null && old.configure(env, member, this)) return true;
|
||||
if (old != null && old.redefine(env, member, this)) return true;
|
||||
if (old != null && !old.configurable()) return false;
|
||||
|
||||
if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member);
|
||||
@ -89,22 +88,40 @@ public class ObjectValue extends Value {
|
||||
return true;
|
||||
}
|
||||
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||
if (!extensible) return false;
|
||||
if (!getState().extendable) return false;
|
||||
|
||||
var member = getOwnMember(env, key);
|
||||
if (member == null) return true;
|
||||
if (member.configurable()) return false;
|
||||
if (!member.configurable()) return false;
|
||||
|
||||
if (key.isSymbol()) symbolMembers.remove(key.toSymbol());
|
||||
else members.remove(key.toString(env));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) {
|
||||
return members;
|
||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||
if (onlyEnumerable) {
|
||||
var res = new LinkedHashSet<String>();
|
||||
|
||||
for (var el : members.entrySet()) {
|
||||
if (el.getValue().enumerable()) res.add(el.getKey());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
else return members.keySet();
|
||||
}
|
||||
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) {
|
||||
return Collections.unmodifiableMap(symbolMembers);
|
||||
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
|
||||
if (onlyEnumerable) {
|
||||
var res = new LinkedHashSet<SymbolValue>();
|
||||
|
||||
for (var el : symbolMembers.entrySet()) {
|
||||
if (el.getValue().enumerable()) res.add(el.getKey());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
else return symbolMembers.keySet();
|
||||
}
|
||||
|
||||
@Override public ObjectValue getPrototype(Environment env) {
|
||||
@ -116,12 +133,12 @@ public class ObjectValue extends Value {
|
||||
}
|
||||
|
||||
public final boolean setPrototype(PrototypeProvider val) {
|
||||
if (!extensible) return false;
|
||||
if (!getState().extendable) return false;
|
||||
prototype = val;
|
||||
return true;
|
||||
}
|
||||
public final boolean setPrototype(Key<ObjectValue> key) {
|
||||
if (!extensible) return false;
|
||||
if (!getState().extendable) return false;
|
||||
prototype = env -> env.get(key);
|
||||
return true;
|
||||
}
|
||||
|
@ -5,20 +5,20 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
|
||||
public final class ScopeValue extends ObjectValue {
|
||||
private class VariableField extends FieldMember {
|
||||
private static class VariableField extends FieldMember {
|
||||
private int i;
|
||||
|
||||
public VariableField(int i) {
|
||||
super(false, true, true);
|
||||
public VariableField(int i, ScopeValue self) {
|
||||
super(self, false, true, true);
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
return variables[i][0];
|
||||
return ((ScopeValue)self).variables[i][0];
|
||||
}
|
||||
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
variables[i][0] = val;
|
||||
((ScopeValue)self).variables[i][0] = val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@ public final class ScopeValue extends ObjectValue {
|
||||
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));
|
||||
defineOwnMember(Environment.empty(), i, new VariableField(i, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
@ -17,6 +17,12 @@ public abstract class PrimitiveValue extends Value {
|
||||
@Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; }
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) { return Map.of(); }
|
||||
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) { return Map.of(); }
|
||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return Set.of(); }
|
||||
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return Set.of(); }
|
||||
|
||||
@Override public State getState() { return State.FROZEN; }
|
||||
|
||||
@Override public void preventExtensions() {}
|
||||
@Override public void seal() {}
|
||||
@Override public void freeze() {}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
|
||||
public final class StringValue extends PrimitiveValue {
|
||||
@ -32,9 +35,29 @@ public final class StringValue extends PrimitiveValue {
|
||||
|
||||
@Override public ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); }
|
||||
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) {
|
||||
// TODO Auto-generated method stub
|
||||
return super.getOwnMembers(env);
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
var num = key.toNumber(env);
|
||||
var i = key.toInt(env);
|
||||
|
||||
if (i == num && i >= 0 && i < value.length()) {
|
||||
return FieldMember.of(this, new StringValue(value.charAt(i) + ""), false, true, false);
|
||||
}
|
||||
else if (key.toString(env).equals("length")) {
|
||||
return FieldMember.of(this, new NumberValue(value.length()), false, false, false);
|
||||
}
|
||||
else return super.getOwnMember(env, key);
|
||||
}
|
||||
|
||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||
var res = new LinkedHashSet<String>();
|
||||
|
||||
res.addAll(super.getOwnMembers(env, onlyEnumerable));
|
||||
|
||||
for (var i = 0; i < value.length(); i++) res.add(i + "");
|
||||
|
||||
if (!onlyEnumerable) res.add("length");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public StringValue(String value) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
@ -24,16 +22,6 @@ public final class VoidValue extends PrimitiveValue {
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env)));
|
||||
}
|
||||
@Override public Map<String, Member> getOwnMembers(Environment env) {
|
||||
throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name));
|
||||
}
|
||||
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) {
|
||||
throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name));
|
||||
}
|
||||
|
||||
// @Override public Value call(Environment env, Value self, Value... args) {
|
||||
// throw EngineException.ofType(String.format("Tried to call a value of %s", name));
|
||||
// }
|
||||
|
||||
public VoidValue(String name, StringValue type) {
|
||||
this.name = name;
|
||||
|
@ -1,16 +1,32 @@
|
||||
const target = arguments[0];
|
||||
const primordials = arguments[1];
|
||||
|
||||
const makeSymbol = primordials.symbol.makeSymbol;
|
||||
const getSymbol = primordials.symbol.getSymbol;
|
||||
const getSymbolKey = primordials.symbol.getSymbolKey;
|
||||
const getSymbolDescription = primordials.symbol.getSymbolDescription;
|
||||
const symbol = primordials.symbol || (() => {
|
||||
const repo = {};
|
||||
|
||||
const parseInt = primordials.number.parseInt;
|
||||
const parseFloat = primordials.number.parseFloat;
|
||||
const isNaN = primordials.number.isNaN;
|
||||
const NaN = primordials.number.NaN;
|
||||
const Infinity = primordials.number.Infinity;
|
||||
return {
|
||||
makeSymbol: (name) => { name },
|
||||
getSymbol(name) {
|
||||
if (name in repo) return repo[name];
|
||||
else return repo[name] = { name };
|
||||
},
|
||||
getSymbolKey(symbol) {
|
||||
if (symbol.name in repo && repo[symbol.name] === symbol) return symbol.name;
|
||||
else return undefined;
|
||||
},
|
||||
getSymbolDescription: ({ name }) => name,
|
||||
};
|
||||
});
|
||||
|
||||
const number = primordials.number || (() => {
|
||||
return {
|
||||
parseInt() { throw new Error("parseInt not supported"); },
|
||||
parseFloat() { throw new Error("parseFloat not supported"); },
|
||||
isNaN: (val) => val !== val,
|
||||
NaN: 0 / 0,
|
||||
Infinity: 1 / 0,
|
||||
};
|
||||
});
|
||||
|
||||
const fromCharCode = primordials.string.fromCharCode;
|
||||
const fromCodePoint = primordials.string.fromCodePoint;
|
||||
@ -29,13 +45,15 @@ const invokeType = primordials.function.invokeType;
|
||||
const setConstructable = primordials.function.setConstructable;
|
||||
const setCallable = primordials.function.setCallable;
|
||||
const invoke = primordials.function.invoke;
|
||||
|
||||
const setGlobalPrototype = primordials.setGlobalPrototype;
|
||||
const compile = primordials.compile;
|
||||
const construct = primordials.function.construct;
|
||||
|
||||
const json = primordials.json;
|
||||
|
||||
const valueKey = makeSymbol("Primitive.value");
|
||||
const setGlobalPrototype = primordials.setGlobalPrototype;
|
||||
const compile = primordials.compile;
|
||||
const setIntrinsic = primordials.setIntrinsic;
|
||||
|
||||
const valueKey = symbol.makeSymbol("Primitive.value");
|
||||
const undefined = ({}).definitelyDefined;
|
||||
|
||||
target.undefined = undefined;
|
||||
@ -52,13 +70,13 @@ const unwrapThis = (self, type, constr, name, arg, defaultVal) => {
|
||||
|
||||
const wrapIndex = (i, len) => {};
|
||||
|
||||
const Symbol = (name = "") => makeSymbol(name);
|
||||
const Symbol = (name = "") => symbol.makeSymbol(name);
|
||||
|
||||
defineField(Symbol, "for", true, false, true, function(name) {
|
||||
return getSymbol(name + "");
|
||||
return symbol.getSymbol(name + "");
|
||||
});
|
||||
defineField(Symbol, "keyFor", true, false, true, function(symbol) {
|
||||
return getSymbolKey(unwrapThis(symbol, "symbol", Symbol, "Symbol.keyFor"));
|
||||
defineField(Symbol, "keyFor", true, false, true, function(value) {
|
||||
return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor"));
|
||||
});
|
||||
|
||||
defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator"));
|
||||
@ -72,7 +90,7 @@ defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringT
|
||||
defineField(Symbol, "prototype", false, false, false, {});
|
||||
|
||||
defineProperty(Symbol.prototype, "description", false, true, function () {
|
||||
return getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description"));
|
||||
return symbol.getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description"));
|
||||
}, undefined);
|
||||
defineField(Symbol.prototype, "toString", true, false, true, function() {
|
||||
return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")";
|
||||
@ -95,7 +113,7 @@ const Number = function(value) {
|
||||
defineField(Number, "isFinite", true, false, true, function(value) {
|
||||
value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined);
|
||||
|
||||
if (value === undefined || isNaN(value)) return false;
|
||||
if (value === undefined || value !== value) return false;
|
||||
if (value === Infinity || value === -Infinity) return false;
|
||||
|
||||
return true;
|
||||
@ -103,34 +121,34 @@ defineField(Number, "isFinite", true, false, true, function(value) {
|
||||
defineField(Number, "isInteger", true, false, true, function(value) {
|
||||
value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined);
|
||||
if (value === undefined) return false;
|
||||
return parseInt(value) === value;
|
||||
return number.parseInt(value) === value;
|
||||
});
|
||||
defineField(Number, "isNaN", true, false, true, function(value) {
|
||||
return isNaN(value);
|
||||
return number.isNaN(value);
|
||||
});
|
||||
defineField(Number, "isSafeInteger", true, false, true, function(value) {
|
||||
value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined);
|
||||
if (value === undefined || parseInt(value) !== value) return false;
|
||||
if (value === undefined || number.parseInt(value) !== value) return false;
|
||||
return value >= -9007199254740991 && value <= 9007199254740991;
|
||||
});
|
||||
defineField(Number, "parseFloat", true, false, true, function(value) {
|
||||
value = 0 + value;
|
||||
return parseFloat(value);
|
||||
return number.parseFloat(value);
|
||||
});
|
||||
defineField(Number, "parseInt", true, false, true, function(value, radix) {
|
||||
value = 0 + value;
|
||||
radix = +radix;
|
||||
if (isNaN(radix)) radix = 10;
|
||||
if (number.isNaN(radix)) radix = 10;
|
||||
|
||||
return parseInt(value, radix);
|
||||
return number.parseInt(value, radix);
|
||||
});
|
||||
|
||||
defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16);
|
||||
defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991);
|
||||
defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991);
|
||||
defineField(Number, "POSITIVE_INFINITY", false, false, false, +Infinity);
|
||||
defineField(Number, "NEGATIVE_INFINITY", false, false, false, -Infinity);
|
||||
defineField(Number, "NaN", false, false, false, NaN);
|
||||
defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity);
|
||||
defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity);
|
||||
defineField(Number, "NaN", false, false, false, number.NaN);
|
||||
defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308);
|
||||
defineField(Number, "MIN_VALUE", false, false, false, 5e-324);
|
||||
defineField(Number, "prototype", false, false, false, {});
|
||||
@ -144,6 +162,10 @@ defineField(Number.prototype, "valueOf", true, false, true, function() {
|
||||
});
|
||||
|
||||
target.Number = Number;
|
||||
target.parseInt = Number.parseInt;
|
||||
target.parseFloat = Number.parseFloat;
|
||||
target.NaN = Number.NaN;
|
||||
target.Infinity = Number.POSITIVE_INFINITY;
|
||||
|
||||
const String = function(value) {
|
||||
if (invokeType(arguments) === "call") {
|
||||
@ -231,6 +253,37 @@ const Object = function(value) {
|
||||
|
||||
defineField(Object, "prototype", false, false, false, setPrototype({}, null));
|
||||
|
||||
defineField(Object, "defineProperty", true, false, true, (obj, key, desc) => {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
print(obj);
|
||||
print(typeof obj);
|
||||
throw new TypeError("Object.defineProperty called on non-object");
|
||||
}
|
||||
if (typeof desc !== "object" || desc === null) throw new TypeError("Property description must be an object: " + desc);
|
||||
|
||||
if ("get" in desc || "set" in desc) {
|
||||
let get = desc.get, set = desc.set;
|
||||
|
||||
print(typeof get);
|
||||
|
||||
if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get);
|
||||
if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set);
|
||||
|
||||
if ("value" in desc || "writable" in desc) {
|
||||
throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute");
|
||||
}
|
||||
|
||||
if (!defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) {
|
||||
throw new TypeError("Cannot redefine property: " + key);
|
||||
}
|
||||
}
|
||||
else if (!defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) {
|
||||
throw new TypeError("Cannot redefine property: " + key);
|
||||
}
|
||||
|
||||
return obj;
|
||||
});
|
||||
|
||||
defineField(Object.prototype, "toString", true, false, true, function() {
|
||||
if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]";
|
||||
else if (typeof this === "number" || this instanceof Number) return "[object Number]";
|
||||
@ -244,7 +297,7 @@ defineField(Object.prototype, "valueOf", true, false, true, function() {
|
||||
return this;
|
||||
});
|
||||
|
||||
target.Boolean = Boolean;
|
||||
target.Object = Object;
|
||||
|
||||
const Function = function() {
|
||||
const parts = ["return function annonymous("];
|
||||
@ -304,8 +357,61 @@ defineField(Function.prototype, "valueOf", true, false, true, function() {
|
||||
|
||||
target.Function = Function;
|
||||
|
||||
// setIntrinsic("spread_obj", target.spread_obj = (target, obj) => {
|
||||
// if (obj === null || obj === undefined) return;
|
||||
// const members = getOwnMembers(obj, true);
|
||||
// const symbols = getOwnSymbolMembers(obj, true);
|
||||
|
||||
// for (let i = 0; i < members.length; i++) {
|
||||
// const member = members[i];
|
||||
// target[member] = obj[member];
|
||||
// }
|
||||
|
||||
// for (let i = 0; i < symbols.length; i++) {
|
||||
// const member = symbols[i];
|
||||
// target[member] = obj[member];
|
||||
// }
|
||||
// });
|
||||
// setIntrinsic("apply", target.spread_call = (func, self, args) => {
|
||||
// return invoke(func, self, args);
|
||||
// });
|
||||
// setIntrinsic("apply", target.spread_new = (func, args) => {
|
||||
// return invoke(func, null, args);
|
||||
// });
|
||||
|
||||
const Error = function(msg = "") {
|
||||
if (invokeType(arguments) === "call") return new Error(msg);
|
||||
this.message = msg + "";
|
||||
};
|
||||
defineField(Error.prototype, "name", true, false, true, "Error");
|
||||
defineField(Error.prototype, "message", true, false, true, "");
|
||||
defineField(Error.prototype, "toString", true, false, true, function toString() {
|
||||
let res = this.name || "Error";
|
||||
|
||||
const msg = this.message;
|
||||
if (msg) res += ": " + msg;
|
||||
|
||||
return res;
|
||||
});
|
||||
|
||||
target.Error = Error;
|
||||
|
||||
const SyntaxError = function(msg = "") {
|
||||
if (invokeType(arguments) === "call") return new SyntaxError(msg);
|
||||
this.message = msg + "";
|
||||
};
|
||||
defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError");
|
||||
|
||||
setPrototype(SyntaxError, Error);
|
||||
setPrototype(SyntaxError.prototype, Error.prototype);
|
||||
|
||||
target.SyntaxError = SyntaxError;
|
||||
|
||||
setGlobalPrototype("string", String.prototype);
|
||||
setGlobalPrototype("number", Number.prototype);
|
||||
setGlobalPrototype("boolean", Boolean.prototype);
|
||||
setGlobalPrototype("symbol", Symbol.prototype);
|
||||
setGlobalPrototype("object", Object.prototype);
|
||||
setGlobalPrototype("function", Function.prototype);
|
||||
setGlobalPrototype("error", Error.prototype);
|
||||
setGlobalPrototype("syntax", SyntaxError.prototype);
|
||||
|
Loading…
Reference in New Issue
Block a user