Merge pull request #28 from TopchetoEU:TopchetoEU/destructing

TopchetoEU/destructing
This commit is contained in:
TopchetoEU 2024-09-14 15:38:02 +03:00 committed by GitHub
commit b5b7781136
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1762 additions and 812 deletions

View File

@ -56,9 +56,10 @@ public class Instruction {
STORE_MEMBER_STR(0x4B), STORE_MEMBER_STR(0x4B),
DEF_PROP(0x50), DEF_PROP(0x50),
KEYS(0x51), DEF_FIELD(0x51),
TYPEOF(0x52), KEYS(0x52),
OPERATION(0x53), TYPEOF(0x53),
OPERATION(0x54),
GLOB_GET(0x60), GLOB_GET(0x60),
GLOB_SET(0x61), GLOB_SET(0x61),
@ -341,8 +342,8 @@ public class Instruction {
return new Instruction(Type.GLOB_DEF, name); return new Instruction(Type.GLOB_DEF, name);
} }
public static Instruction globGet(String name) { public static Instruction globGet(String name, boolean force) {
return new Instruction(Type.GLOB_GET, name); return new Instruction(Type.GLOB_GET, name, force);
} }
public static Instruction globSet(String name, boolean keep, boolean define) { public static Instruction globSet(String name, boolean keep, boolean define) {
return new Instruction(Type.GLOB_SET, name, keep, define); return new Instruction(Type.GLOB_SET, name, keep, define);
@ -404,10 +405,10 @@ public class Instruction {
return new Instruction(Type.LOAD_ARR, count); return new Instruction(Type.LOAD_ARR, count);
} }
public static Instruction dup() { public static Instruction dup() {
return new Instruction(Type.DUP, 1); return new Instruction(Type.DUP, 1, 0);
} }
public static Instruction dup(int count) { public static Instruction dup(int count, int offset) {
return new Instruction(Type.DUP, count); return new Instruction(Type.DUP, count, offset);
} }
public static Instruction storeVar(int i) { public static Instruction storeVar(int i) {
@ -435,7 +436,7 @@ public class Instruction {
return new Instruction(Type.STORE_MEMBER_INT, key, false); return new Instruction(Type.STORE_MEMBER_INT, key, false);
} }
public static Instruction storeMember(int key, boolean keep) { public static Instruction storeMember(int key, boolean keep) {
return new Instruction(Type.STORE_MEMBER_STR, key, keep); return new Instruction(Type.STORE_MEMBER_INT, key, keep);
} }
public static Instruction discard() { public static Instruction discard() {
@ -449,12 +450,15 @@ public class Instruction {
return new Instruction(Type.TYPEOF, varName); return new Instruction(Type.TYPEOF, varName);
} }
public static Instruction keys(boolean forInFormat) { public static Instruction keys(boolean own, boolean onlyEnumerable) {
return new Instruction(Type.KEYS, forInFormat); return new Instruction(Type.KEYS, own, onlyEnumerable);
} }
public static Instruction defProp() { public static Instruction defProp(boolean setter) {
return new Instruction(Type.DEF_PROP); return new Instruction(Type.DEF_PROP, setter);
}
public static Instruction defField() {
return new Instruction(Type.DEF_FIELD);
} }
public static Instruction operation(Operation op) { public static Instruction operation(Operation op) {

View File

@ -51,6 +51,7 @@ public class ParseRes<T> {
return new ParseRes<T>(State.FAILED, null, null, null, 0); return new ParseRes<T>(State.FAILED, null, null, null, 0);
} }
public static <T> ParseRes<T> error(Location loc, String error) { public static <T> ParseRes<T> error(Location loc, String error) {
// TODO: differentiate definitive and probable errors
return new ParseRes<>(State.ERROR, loc, error, null, 0); return new ParseRes<>(State.ERROR, loc, error, null, 0);
} }
public static <T> ParseRes<T> res(T val, int i) { public static <T> ParseRes<T> res(T val, int i) {

View File

@ -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);
}

View File

@ -13,7 +13,7 @@ import me.topchetoeu.jscript.common.parsing.Source;
public class CompoundNode extends Node { public class CompoundNode extends Node {
public final Node[] statements; public final Node[] statements;
public final boolean hasScope; public boolean hasScope;
public Location end; public Location end;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {

View File

@ -9,6 +9,7 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.control.ReturnNode; import me.topchetoeu.jscript.compilation.control.ReturnNode;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
public class FunctionArrowNode extends FunctionNode { public class FunctionArrowNode extends FunctionNode {
@Override public String name() { return null; } @Override public String name() { return null; }
@ -34,7 +35,7 @@ public class FunctionArrowNode extends FunctionNode {
Parameters params; Parameters params;
if (src.is(i + n, "(")) { 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(); if (!paramsRes.isSuccess()) return paramsRes.chainError();
n += paramsRes.n; n += paramsRes.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
@ -42,14 +43,12 @@ public class FunctionArrowNode extends FunctionNode {
params = paramsRes.result; params = paramsRes.result;
} }
else { else {
var singleParam = Parsing.parseIdentifier(src, i + n); var singleParam = Pattern.parse(src, i + n, true);
if (!singleParam.isSuccess()) return ParseRes.failed(); if (!singleParam.isSuccess()) return ParseRes.failed();
var paramLoc = src.loc(i + n);
n += singleParam.n; n += singleParam.n;
n += Parsing.skipEmpty(src, i + 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(); if (!src.is(i + n, "=>")) return ParseRes.failed();

View File

@ -1,16 +1,15 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; 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.FunctionScope;
import me.topchetoeu.jscript.compilation.scope.Variable; import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public abstract class FunctionNode extends Node { public abstract class FunctionNode extends Node {
public final CompoundNode body; public final CompoundNode body;
@ -33,40 +32,41 @@ public abstract class FunctionNode extends Node {
return new CompileResult(env, scope, params.params.size(), target -> { return new CompileResult(env, scope, params.params.size(), target -> {
if (params.params.size() > 0) { if (params.params.size() > 0) {
target.add(Instruction.loadArgs(true)); 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; var i = 0;
for (var param : params.params) { 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++)); 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) { // if (param.node != null) {
var end = new DeferredIntSupplier(); // var end = new DeferredIntSupplier();
target.add(Instruction.dup()); // target.add(Instruction.dup());
target.add(Instruction.pushUndefined()); // target.add(Instruction.pushUndefined());
target.add(Instruction.operation(Operation.EQUALS)); // target.add(Instruction.operation(Operation.EQUALS));
target.add(Instruction.jmpIfNot(end)); // target.add(Instruction.jmpIfNot(end));
target.add(Instruction.discard()); // target.add(Instruction.discard());
param.node.compile(target, true); // 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 (params.rest != null) {
if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
var restVar = scope.define(new Variable(params.restName, false), params.restLocation);
target.add(Instruction.loadRestArgs(params.params.size())); target.add(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)) { if (selfName != null && !scope.has(name, false)) {
@ -107,6 +107,7 @@ public abstract class FunctionNode extends Node {
this.end = end; this.end = end;
this.params = params; this.params = params;
this.body = body; this.body = body;
this.body.hasScope = false;
} }
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { 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 += name.n;
n += Parsing.skipEmpty(src, i + 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"); if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
n += params.n; n += params.n;

View File

@ -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.DiscardNode;
import me.topchetoeu.jscript.compilation.values.operations.IndexNode; import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
import me.topchetoeu.jscript.compilation.values.operations.OperationNode; 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.compilation.values.operations.TypeofNode;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
@ -140,8 +141,8 @@ public final class JavaScript {
ParseRes<Node> res = ParseRes.first(src, i + n, ParseRes<Node> res = ParseRes.first(src, i + n,
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence), (s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence), (s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
(s, j) -> ChangeNode.parsePostfixIncrease(s, j, _prev, precedence), (s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
(s, j) -> ChangeNode.parsePostfixDecrease(s, j, _prev, precedence), (s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence), (s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence), (s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence), (s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
@ -221,71 +222,6 @@ public final class JavaScript {
return ParseRes.failed(); 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) { public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
var res = Parsing.parseIdentifier(src, i); var res = Parsing.parseIdentifier(src, i);
if (!res.isSuccess()) return res.chainError(); if (!res.isSuccess()) return res.chainError();

View File

@ -1,29 +1,84 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List; 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 class Parameters {
public final int length; public final int length;
public final List<Parameter> params; public final List<Pattern> params;
public final String restName; public final Pattern rest;
public final Location restLocation;
public Parameters(List<Parameter> params, String restName, Location restLocation) { public Parameters(List<Pattern> params, Pattern rest) {
var len = params.size(); var len = params.size();
for (var i = params.size() - 1; i >= 0; i--) { for (var i = params.size() - 1; i >= 0; i--) {
if (params.get(i).node == null) break; if (!(params.get(i) instanceof AssignNode)) break;
len--; len--;
} }
this.params = params; this.params = params;
this.length = len; this.length = len;
this.restName = restName; this.rest = rest;
this.restLocation = restLocation;
} }
public Parameters(List<Parameter> params) { public Parameters(List<Pattern> params) {
this(params, null, null); 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);
} }
} }

View File

@ -4,23 +4,21 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.common.Instruction; 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.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.scope.Variable; import me.topchetoeu.jscript.compilation.patterns.Pattern;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class VariableDeclareNode extends Node { public class VariableDeclareNode extends Node {
public static class Pair { public static class Pair {
public final String name; public final Pattern destructor;
public final Node value; public final Node value;
public final Location location; public final Location location;
public Pair(String name, Node value, Location location) { public Pair(Pattern destr, Node value, Location location) {
this.name = name; this.destructor = destr;
this.value = value; this.value = value;
this.location = location; this.location = location;
} }
@ -32,25 +30,22 @@ public class VariableDeclareNode extends Node {
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
if (!declType.strict) { if (!declType.strict) {
for (var entry : values) { 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) { @Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) { for (var entry : values) {
if (entry.name == null) continue; if (entry.value == null) {
if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location); if (declType == DeclarationType.VAR) entry.destructor.declare(target, null);
else entry.destructor.declare(target, declType);
if (entry.value != null) {
FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
target.add(VariableNode.toSet(target, entry.location, entry.name, false, true));
} }
else target.add(_i -> { else {
var i = target.scope.get(entry.name, false); entry.value.compile(target, true);
if (i == null) return Instruction.globDef(entry.name); if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true);
else return Instruction.nop(); else entry.destructor.destruct(target, declType, true);
}); }
} }
if (pollute) target.add(Instruction.pushUndefined()); if (pollute) target.add(Instruction.pushUndefined());
@ -80,13 +75,10 @@ public class VariableDeclareNode extends Node {
while (true) { while (true) {
var nameLoc = src.loc(i + n); 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)) { var name = Pattern.parse(src, i + n, false);
return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor");
} n += name.n;
Node val = null; Node val = null;
var endN = n; var endN = n;

View File

@ -1,7 +1,6 @@
package me.topchetoeu.jscript.compilation.control; package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; 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); if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation);
object.compile(target, true, BreakpointType.STEP_OVER); object.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(true)); target.add(Instruction.keys(false, true));
int start = target.size(); int start = target.size();
target.add(Instruction.dup()); target.add(Instruction.dup());
target.add(Instruction.pushUndefined());
target.add(Instruction.operation(Operation.EQUALS));
int mid = target.temp(); int mid = target.temp();
target.add(Instruction.loadMember("value")).setLocation(varLocation); 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.jmp(start - endI));
target.add(Instruction.discard()); 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()); if (pollute) target.add(Instruction.pushUndefined());
} }

View File

@ -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);
}
}

View File

@ -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; }
}

View File

@ -0,0 +1,8 @@
package me.topchetoeu.jscript.compilation.patterns;
/**
* Represents all nodes that can be assign targets
*/
public interface AssignTargetLike {
AssignTarget toAssignTarget();
}

View File

@ -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);
}

View File

@ -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);
// }
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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; }
}

View File

@ -0,0 +1,5 @@
package me.topchetoeu.jscript.compilation.patterns;
public interface PatternLike {
Pattern toPattern();
}

View File

@ -19,6 +19,13 @@ public class FunctionScope extends Scope {
public final boolean passtrough; 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) { @Override public Variable define(Variable var, Location loc) {
checkNotEnded(); checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
@ -32,13 +39,6 @@ public class FunctionScope extends Scope {
return variables.add(var); 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) { public Variable defineSpecial(Variable var, Location loc) {
checkNotEnded(); checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);

View File

@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class Scope { public class Scope {
@ -41,15 +42,6 @@ public class Scope {
return var; 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) { protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", 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"); 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 * Defines an ES5-style variable
* *
@ -76,6 +78,11 @@ public class Scope {
return null; 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 * Defines an ES2015-style variable
* @param readonly True if const, false if let * @param readonly True if const, false if let
@ -86,6 +93,7 @@ public class Scope {
public Variable defineStrict(Variable var, Location loc) { public Variable defineStrict(Variable var, Location loc) {
checkNotEnded(); checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name);
strictVarMap.put(var.name, var); strictVarMap.put(var.name, var);
return variables.add(var); return variables.add(var);
@ -125,9 +133,6 @@ public class Scope {
} }
return res; return res;
// if (parent != null) return parent.variableOffset() + variables.size();
// else return variables.size();
} }
public final int capturablesOffset() { public final int capturablesOffset() {
var res = 0; var res = 0;
@ -138,8 +143,11 @@ public class Scope {
} }
return res; 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() { public int localsCount() {

View File

@ -18,11 +18,10 @@ public class ArrayNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadArr(statements.length)); target.add(Instruction.loadArr(statements.length));
if (statements.length > 0) target.add(Instruction.dup(statements.length));
for (var i = 0; i < statements.length; i++) { for (var i = 0; i < statements.length; i++) {
var el = statements[i]; var el = statements[i];
if (el != null) { if (el != null) {
target.add(Instruction.dup());
el.compile(target, true); el.compile(target, true);
target.add(Instruction.storeMember(i)); target.add(Instruction.storeMember(i));
} }

View File

@ -1,10 +1,10 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList; import java.util.LinkedList;
import java.util.LinkedHashMap; import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.common.Instruction; 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.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; 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.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode; import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.FunctionNode; import me.topchetoeu.jscript.compilation.FunctionNode;
import me.topchetoeu.jscript.compilation.FunctionValueNode;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; 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 { @Override public String name() {
public static class ObjProp { if (key instanceof StringNode str) {
public final String name; if (isGetter()) return "get " + str.value;
public final String access; else return "set " + str.value;
public final FunctionValueNode func; }
else return null;
}
public ObjProp(String name, String access, FunctionValueNode func) { public boolean isGetter() { return argument == null; }
this.name = name; public boolean isSetter() { return argument != null; }
this.access = access;
this.func = func; @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 List<Node> members;
public final Map<String, FunctionNode> getters;
public final Map<String, FunctionNode> setters; // 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) { @Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadObj()); target.add(Instruction.loadObj());
for (var el : map.entrySet()) { for (var el : members) {
target.add(Instruction.dup()); target.add(Instruction.dup());
var val = el.getValue(); el.compile(target, false);
FunctionNode.compileWithName(val, target, true, el.getKey().toString());
target.add(Instruction.storeMember(el.getKey()));
} }
var keys = new ArrayList<Object>();
keys.addAll(getters.keySet());
keys.addAll(setters.keySet());
for (var key : keys) {
target.add(Instruction.pushValue((String)key));
if (getters.containsKey(key)) getters.get(key).compile(target, true);
else target.add(Instruction.pushUndefined());
if (setters.containsKey(key)) setters.get(key).compile(target, true);
else target.add(Instruction.pushUndefined());
target.add(Instruction.defProp());
}
if (!pollute) target.add(Instruction.discard());
} }
public ObjectNode(Location loc, Map<String, Node> map, Map<String, FunctionNode> getters, Map<String, FunctionNode> setters) { @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); super(loc);
this.map = map; this.members = map;
this.getters = getters;
this.setters = setters;
} }
private static ParseRes<String> parsePropName(Source src, int i) { private static ParseRes<Node> parseComputePropName(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "[")) return ParseRes.failed();
n++;
var res = ParseRes.first(src, i + n, var val = JavaScript.parseExpression(src, i, 0);
Parsing::parseIdentifier, if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property");
Parsing::parseString, n += val.n;
(s, j) -> Parsing.parseNumber(s, j, false) 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) { public static ParseRes<ObjectNode> parse(Source src, int i) {
@ -120,39 +358,25 @@ public class ObjectNode extends Node {
n++; n++;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
var values = new LinkedHashMap<String, Node>(); var members = new LinkedList<Node>();
var getters = new LinkedHashMap<String, FunctionNode>();
var setters = new LinkedHashMap<String, FunctionNode>();
if (src.is(i + n, "}")) { if (src.is(i + n, "}")) {
n++; n++;
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); return ParseRes.res(new ObjectNode(loc, members), n);
} }
while (true) { 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()) { members.add(prop.result);
n += prop.n;
if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func);
else getters.put(prop.result.name, prop.result.func);
}
else {
var name = parsePropName(src, i + n);
if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name");
n += name.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
n++;
var valRes = JavaScript.parseExpression(src, i + n, 2);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list");
n += valRes.n;
values.put(name.result, valRes.result);
}
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (src.is(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."); 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);
} }
} }

View File

@ -1,26 +1,72 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import java.util.function.Supplier;
import me.topchetoeu.jscript.common.Instruction; 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.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableNode;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; 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; 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; public final String name;
@Override public Node toAssign(Node val, Operation operation) { public String assignName() { return name; }
return new VariableAssignNode(loc(), name, val, operation);
// @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) { @Override public void compile(CompileResult target, boolean pollute) {
@ -29,7 +75,7 @@ public class VariableNode extends Node implements AssignableNode {
if (i == null) { if (i == null) {
target.add(_i -> { target.add(_i -> {
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc(), String.format("Cannot access '%s' before initialization", name)); 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()); 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()); 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); var i = target.scope.get(name, false);
if (i == null) return _i -> { if (i == null) return _i -> {
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name)); 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(); else return _i -> i.index().toGet();
} }
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name) { 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) { 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; n += literal.n;
if (!JavaScript.checkVarName(literal.result)) { if (!JavaScript.checkVarName(literal.result)) {
if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported."); if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported");
if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported."); return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result));
if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported.");
return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result));
} }
return ParseRes.res(new VariableNode(loc, literal.result), n); return ParseRes.res(new VariableNode(loc, literal.result), n);

View File

@ -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;
}
}

View File

@ -38,7 +38,7 @@ public class CallNode extends Node {
shouldParen = true; 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 res = "{(intermediate value)}";
} }
else if (func instanceof StringNode) { else if (func instanceof StringNode) {

View File

@ -6,31 +6,29 @@ import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableNode;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; 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.NumberNode;
public class ChangeNode extends Node { public class ChangeNode extends Node {
public final AssignableNode value; public final ChangeTarget changable;
public final double addAmount; public final Node value;
public final boolean postfix; public final Operation op;
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compile(CompileResult target, boolean pollute) {
value.toAssign(new NumberNode(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); changable.beforeChange(target);
if (!pollute) target.add(Instruction.discard()); value.compile(target, true);
else if (postfix) { target.add(Instruction.operation(op));
target.add(Instruction.pushValue(addAmount)); changable.afterAssign(target, pollute);
target.add(Instruction.operation(Operation.SUBTRACT));
}
} }
public ChangeNode(Location loc, AssignableNode value, double addAmount, boolean postfix) { public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) {
super(loc); super(loc);
this.changable = changable;
this.value = value; this.value = value;
this.addAmount = addAmount; this.op = op;
this.postfix = postfix;
} }
public static ParseRes<ChangeNode> parsePrefixIncrease(Source src, int i) { 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); var res = JavaScript.parseExpression(src, i + n, 15);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
else if (!(res.result instanceof 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) { public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
@ -55,33 +53,8 @@ public class ChangeNode extends Node {
var res = JavaScript.parseExpression(src, i + n, 15); var res = JavaScript.parseExpression(src, i + n, 15);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
else if (!(res.result instanceof 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> 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);
} }
} }

View File

@ -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;
}
}

View File

@ -1,26 +1,61 @@
package me.topchetoeu.jscript.compilation.values.operations; package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableNode;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; 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.NumberNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode; 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 object;
public final Node index; public final Node index;
@Override public Node toAssign(Node val, Operation operation) { @Override public void beforeAssign(CompileResult target) {
return new IndexAssignNode(loc(), object, index, val, operation); 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) { public void compile(CompileResult target, boolean dupObj, boolean pollute) {
object.compile(target, true); object.compile(target, true);
if (dupObj) target.add(Instruction.dup()); if (dupObj) target.add(Instruction.dup());
@ -84,4 +119,35 @@ public class IndexNode extends Node implements AssignableNode {
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n); return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
} }
public static void indexStorePushKey(CompileResult target, Node index) {
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());
}
} }

View File

@ -11,10 +11,11 @@ import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableNode;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; 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 { public class OperationNode extends Node {
private static interface OperatorFactory { private static interface OperatorFactory {
@ -54,11 +55,22 @@ public class OperationNode extends Node {
@Override public ParseRes<Node> construct(Source src, int i, Node prev) { @Override public ParseRes<Node> construct(Source src, int i, Node prev) {
var loc = src.loc(i); var loc = src.loc(i);
if (!(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); var other = JavaScript.parseExpression(src, i, precedence);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
return ParseRes.res(((AssignableNode)prev).toAssign(other.result, operation), other.n);
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) { public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
@ -207,7 +219,7 @@ public class OperationNode extends Node {
var factory = factories.get(token); var factory = factories.get(token);
if (!src.is(i + n, token)) continue; if (!src.is(i + n, token)) continue;
if (factory.precedence() < precedence) ParseRes.failed(); if (factory.precedence() < precedence) return ParseRes.failed();
n += token.length(); n += token.length();
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);

View File

@ -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);
}
}

View File

@ -16,15 +16,15 @@ public class TypeofNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compile(CompileResult target, boolean pollute) {
if (value instanceof VariableNode varNode) { if (value instanceof VariableNode varNode) {
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name))); target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true));
if (!pollute) target.add(Instruction.discard()); if (pollute) target.add(Instruction.typeof());
else target.add(Instruction.discard());
return; return;
} }
value.compile(target, pollute); value.compile(target, pollute);
target.add(Instruction.typeof()); if (pollute) target.add(Instruction.typeof());
if (!pollute) target.add(Instruction.discard());
} }
public TypeofNode(Location loc, Node value) { public TypeofNode(Location loc, Node value) {

View File

@ -22,7 +22,9 @@ public interface Compiler {
return body; return body;
} }
catch (SyntaxException e) { 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;
} }
}; };

View File

@ -41,10 +41,8 @@ public final class Engine implements EventLoop {
try { try {
((Task<Object>)task).notifier.complete(task.runnable.get()); ((Task<Object>)task).notifier.complete(task.runnable.get());
} }
catch (RuntimeException e) { catch (InterruptException e) { throw e; }
if (e instanceof InterruptException) throw e; catch (RuntimeException e) { task.notifier.completeExceptionally(e); }
task.notifier.completeExceptionally(e);
}
} }
catch (InterruptedException | InterruptException e) { catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.cancel(false); for (var msg : tasks) msg.notifier.cancel(false);

View File

@ -28,9 +28,9 @@ public interface EventLoop {
} }
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
return pushMsg(() -> func.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) { 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);
} }
} }

View File

@ -1,8 +1,6 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.runtime;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.common.Instruction; 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.debug.DebugContext;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException; 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.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class Frame { public final class Frame {
@ -385,39 +381,39 @@ public final class Frame {
* Gets an array proxy of the local locals * Gets an array proxy of the local locals
*/ */
public ObjectValue getValStackScope() { public ObjectValue getValStackScope() {
return new ObjectValue() { return new ArrayLikeValue() {
@Override public Member getOwnMember(Environment env, KeyCache key) { @Override public Value get(int i) { return stack[i]; }
var res = super.getOwnMember(env, key); @Override public void set(int i, Value val) { stack[i] = val; }
if (res != null) return res; @Override public boolean has(int i) { return i >= 0 && i < size(); }
@Override public void remove(int i) { }
var num = key.toNumber(env); @Override public int size() { return stackPtr; }
var i = key.toInt(env); @Override public boolean setSize(int val) { return false; }
if (num != i || i < 0 || i >= stackPtr) return null; // @Override public Member getOwnMember(Environment env, KeyCache key) {
else return new FieldMember(false, true, true) { // var res = super.getOwnMember(env, key);
@Override public Value get(Environment env, Value self) { return stack[i]; } // if (res != null) return res;
@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>();
for (var i = 0; i < stackPtr; i++) { // var num = key.toNumber(env);
var _i = i; // var i = key.toInt(env);
res.put(i + "", new FieldMember(false, true, true) {
@Override public Value get(Environment env, Value self) { return stack[_i]; }
@Override public boolean set(Environment env, Value val, Value self) {
stack[_i] = val;
return true;
}
});
}
return res; // 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;
// }
}; };
} }

View File

@ -7,7 +7,6 @@ import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; 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.Member.PropertyMember;
import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
@ -52,45 +51,50 @@ public class InstructionRunner {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop(); var funcObj = frame.pop();
frame.push(funcObj.callNew(env, instr.get(1), callArgs)); frame.push(funcObj.construct(env, instr.get(1), callArgs));
frame.codePtr++; frame.codePtr++;
return null; return null;
} }
private static Value execDefProp(Environment env, Instruction instr, Frame frame) { private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
var setterVal = frame.pop(); var val = frame.pop();
var getterVal = frame.pop();
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
FunctionValue getter, setter; FunctionValue accessor;
if (getterVal == Value.UNDEFINED) getter = null; if (val == Value.UNDEFINED) accessor = null;
else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal; else if (val instanceof FunctionValue func) accessor = func;
else throw EngineException.ofType("Getter must be a function or undefined."); else throw EngineException.ofType("Getter must be a function or undefined.");
if (setterVal == Value.UNDEFINED) setter = null; if ((boolean)instr.get(0)) obj.defineOwnMember(env, key, new PropertyMember(obj, null, accessor, true, true));
else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal; else obj.defineOwnMember(env, key, new PropertyMember(obj, accessor, null, true, true));
else throw EngineException.ofType("Setter must be a function or undefined.");
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++; frame.codePtr++;
return null; return null;
} }
private static Value execKeys(Environment env, Instruction instr, Frame frame) { private static Value execKeys(Environment env, Instruction instr, Frame frame) {
var val = frame.pop(); 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); Collections.reverse(members);
frame.push(null); frame.push(null);
for (var el : members) { for (var el : members) {
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el))); obj.defineOwnMember(env, "value", new StringValue(el));
frame.push(obj); frame.push(obj);
} }
@ -116,9 +120,12 @@ public class InstructionRunner {
private static Value execDup(Environment env, Instruction instr, Frame frame) { private static Value execDup(Environment env, Instruction instr, Frame frame) {
int count = instr.get(0); 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++) { for (var i = 0; i < count; i++) {
frame.push(frame.peek()); frame.push(el);
} }
frame.codePtr++; frame.codePtr++;
@ -231,7 +238,7 @@ public class InstructionRunner {
} }
private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) { private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) {
if (env.hasNotNull(Value.REGEX_CONSTR)) { 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 { else {
throw EngineException.ofSyntax("Regex is not supported."); throw EngineException.ofSyntax("Regex is not supported.");
@ -447,10 +454,15 @@ public class InstructionRunner {
} }
private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) { private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0); 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"); if (res == null) throw EngineException.ofSyntax(name + " is not defined");
else frame.push(res); else frame.push(res);
}
frame.codePtr++; frame.codePtr++;
return null; return null;
@ -574,6 +586,7 @@ public class InstructionRunner {
case KEYS: return execKeys(env, instr, frame); case KEYS: return execKeys(env, instr, frame);
case DEF_PROP: return execDefProp(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 TYPEOF: return execTypeof(env, instr, frame);
case DELETE: return execDelete(env, instr, frame); case DELETE: return execDelete(env, instr, frame);

View File

@ -8,7 +8,6 @@ import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.json.JSONList; import me.topchetoeu.jscript.common.json.JSONList;
import me.topchetoeu.jscript.common.json.JSONMap; import me.topchetoeu.jscript.common.json.JSONMap;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; 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.Value;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
@ -27,7 +26,7 @@ public class JSONConverter {
var res = new ObjectValue(); var res = new ObjectValue();
for (var el : val.map().entrySet()) { for (var el : val.map().entrySet()) {
res.defineOwnMember(null, el.getKey(), FieldMember.of(toJs(el.getValue()))); res.defineOwnMember(null, el.getKey(), toJs(el.getValue()));
} }
return res; return res;
@ -70,10 +69,11 @@ public class JSONConverter {
var res = new JSONMap(); var res = new JSONMap();
for (var el : val.getMembers(env, true, true).entrySet()) { for (var key : val.getOwnMembers(env, true)) {
var jsonEl = fromJs(env, el.getValue().get(env, val), prev); var el = fromJs(env, val.getMember(env, key), prev);
if (jsonEl == null) continue; if (el == null) continue;
res.put(el.getKey(), jsonEl);
res.put(key, el);
} }
prev.remove(val); prev.remove(val);

View File

@ -41,7 +41,7 @@ public class SimpleRepl {
try { try {
try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); } 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) { for (var arg : args) {
try { try {
@ -58,7 +58,7 @@ public class SimpleRepl {
} }
catch (ExecutionException e) { throw e.getCause(); } 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++) { for (var i = 0; ; i++) {
@ -77,7 +77,7 @@ public class SimpleRepl {
} }
catch (ExecutionException e) { throw e.getCause(); } 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) { catch (IOException e) {
@ -171,9 +171,7 @@ public class SimpleRepl {
var configurable = args.get(4).toBoolean(); var configurable = args.get(4).toBoolean();
var value = args.get(5); var value = args.get(5);
obj.defineOwnMember(args.env, key, FieldMember.of(value, enumerable, configurable, writable)); return BoolValue.of(obj.defineOwnMember(args.env, key, FieldMember.of(obj, value, configurable, enumerable, writable)));
return Value.UNDEFINED;
})); }));
res.defineOwnMember(env, "defineProperty", new NativeFunction(args -> { res.defineOwnMember(env, "defineProperty", new NativeFunction(args -> {
var obj = (ObjectValue)args.get(0); 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 getter = args.get(4) instanceof VoidValue ? null : (FunctionValue)args.get(4);
var setter = args.get(5) instanceof VoidValue ? null : (FunctionValue)args.get(5); var setter = args.get(5) instanceof VoidValue ? null : (FunctionValue)args.get(5);
obj.defineOwnMember(args.env, key, new PropertyMember(getter, setter, configurable, enumerable)); return BoolValue.of(obj.defineOwnMember(args.env, key, new PropertyMember(obj, getter, setter, configurable, enumerable)));
return Value.UNDEFINED;
})); }));
res.defineOwnMember(env, "getPrototype", new NativeFunction(args -> { res.defineOwnMember(env, "getPrototype", new NativeFunction(args -> {
return args.get(0).getPrototype(env); return args.get(0).getPrototype(env);
@ -195,6 +191,19 @@ public class SimpleRepl {
args.get(0).setPrototype(env, proto); args.get(0).setPrototype(env, proto);
return args.get(0); 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; return res;
} }
@ -216,12 +225,21 @@ public class SimpleRepl {
if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new"); if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new");
else return new StringValue("call"); else return new StringValue("call");
})); }));
res.defineOwnMember(env, "invoke", new NativeFunction(args -> { res.defineOwnMember(env, "invoke", new NativeFunction(args -> {
var func = (FunctionValue)args.get(0); var func = (FunctionValue)args.get(0);
var self = args.get(1); var self = args.get(1);
var funcArgs = (ArrayValue)args.get(2); 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; return res;
@ -251,7 +269,7 @@ public class SimpleRepl {
var self = args.get(1); var self = args.get(1);
var funcArgs = (ArrayValue)args.get(2); var funcArgs = (ArrayValue)args.get(2);
return func.call(env, self, funcArgs.toArray()); return func.invoke(env, self, funcArgs.toArray());
})); }));
return res; return res;
@ -275,28 +293,55 @@ public class SimpleRepl {
var obj = (ObjectValue)args.get(1); var obj = (ObjectValue)args.get(1);
switch (type) { switch (type) {
case "string": case "object":
args.env.add(Value.STRING_PROTO, obj); args.env.add(Value.OBJECT_PROTO, obj);
break; break;
case "number": case "function":
args.env.add(Value.NUMBER_PROTO, obj); args.env.add(Value.FUNCTION_PROTO, obj);
break;
case "array":
args.env.add(Value.ARRAY_PROTO, obj);
break; break;
case "boolean": case "boolean":
args.env.add(Value.BOOL_PROTO, obj); args.env.add(Value.BOOL_PROTO, obj);
break; break;
case "number":
args.env.add(Value.NUMBER_PROTO, obj);
break;
case "string":
args.env.add(Value.STRING_PROTO, obj);
break;
case "symbol": case "symbol":
args.env.add(Value.SYMBOL_PROTO, obj); args.env.add(Value.SYMBOL_PROTO, obj);
break; break;
case "object": case "error":
args.env.add(Value.OBJECT_PROTO, obj); 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; break;
} }
return Value.UNDEFINED; 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 -> { 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 Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env).value);
})); }));
return res; return res;
} }

View File

@ -6,7 +6,6 @@ import java.util.List;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.runtime.values.Value; 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;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
@ -67,7 +66,7 @@ public class EngineException extends RuntimeException {
public String toString(Environment env) { public String toString(Environment env) {
var ss = new StringBuilder(); var ss = new StringBuilder();
try { try {
ss.append(value.toString(env)).append('\n'); ss.append(value.toString(env).value).append('\n');
} }
catch (EngineException e) { catch (EngineException e) {
var name = value.getMember(env, "name"); var name = value.getMember(env, "name");
@ -98,8 +97,8 @@ public class EngineException extends RuntimeException {
if (msg == null) msg = ""; if (msg == null) msg = "";
if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name))); if (name != null) res.defineOwnMember(Environment.empty(), "name", new StringValue(name));
res.defineOwnMember(Environment.empty(), "message", FieldMember.of(new StringValue(msg))); res.defineOwnMember(Environment.empty(), "message", new StringValue(msg));
return res; return res;
} }

View File

@ -7,51 +7,67 @@ import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
public interface Member { public interface Member {
public static final class PropertyMember implements Member { public static final class PropertyMember implements Member {
public final FunctionValue getter; public final Value self;
public final FunctionValue setter; public FunctionValue getter;
public final boolean configurable; public FunctionValue setter;
public final boolean enumerable; public boolean configurable;
public boolean enumerable;
@Override public Value get(Environment env, Value self) { @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; else return Value.UNDEFINED;
} }
@Override public boolean set(Environment env, Value val, Value self) { @Override public boolean set(Environment env, Value val, Value self) {
if (setter == null) return false; if (setter == null) return false;
setter.call(env, self, val); setter.call(env, false, "", self, val);
return true; 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 enumerable() { return enumerable; }
@Override public boolean configure(Environment env, Member newMember, Value self) { @Override public boolean redefine(Environment env, Member newMember, Value self) {
if (!(newMember instanceof PropertyMember)) return false; // If the given member isn't a property, we can't redefine
var prop = (PropertyMember)newMember; if (!(newMember instanceof PropertyMember prop)) return false;
if (prop.configurable != configurable) return false; if (configurable()) {
if (prop.enumerable != enumerable) return false; // 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.getter != null) getter = prop.getter;
if (prop.setter == setter) return true; if (prop.setter != null) setter = prop.setter;
return false;
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) { @Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue(); var res = new ObjectValue();
if (getter == null) res.defineOwnMember(env, "getter", FieldMember.of(Value.UNDEFINED)); // Don't touch the ordering, as it's emulating V8
else res.defineOwnMember(env, "getter", FieldMember.of(getter));
if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(Value.UNDEFINED)); if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED);
else res.defineOwnMember(env, "setter", FieldMember.of(setter)); else res.defineOwnMember(env, "getter", getter);
res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED);
res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); else res.defineOwnMember(env, "setter", setter);
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
return res; 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.getter = getter;
this.setter = setter; this.setter = setter;
this.configurable = configurable; this.configurable = configurable;
@ -69,60 +85,87 @@ public interface Member {
value = val; value = val;
return true; return true;
} }
public SimpleFieldMember(Value value, boolean configurable, boolean enumerable, boolean writable) { public SimpleFieldMember(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
super(configurable, enumerable, writable); super(self, configurable, enumerable, writable);
this.value = value; this.value = value;
} }
} }
public final Value self;
public boolean configurable; public boolean configurable;
public boolean enumerable; public boolean enumerable;
public boolean writable; public boolean writable;
@Override public final boolean configurable() { return configurable; } @Override public final boolean configurable() { return configurable && self.getState().configurable; }
@Override public final boolean enumerable() { return enumerable; } @Override public final boolean enumerable() { return enumerable; }
@Override public final boolean configure(Environment env, Member newMember, Value self) { public final boolean writable() { return writable && self.getState().writable; }
if (!(newMember instanceof FieldMember)) return false;
var field = (FieldMember)newMember;
if (field.configurable != configurable) return false; @Override public final boolean redefine(Environment env, Member newMember, Value self) {
if (field.enumerable != enumerable) return false; // If the given member isn't a field, we can't redefine
if (!writable) return field.get(env, self).equals(get(env, self)); if (!(newMember instanceof FieldMember field)) return false;
set(env, field.get(env, self), self); if (configurable()) {
writable = field.writable; configurable = field.configurable;
return true; 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) { @Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue(); var res = new ObjectValue();
res.defineOwnMember(env, "value", FieldMember.of(get(env, self))); res.defineOwnMember(env, "value", get(env, self));
res.defineOwnMember(env, "writable", FieldMember.of(BoolValue.of(writable))); res.defineOwnMember(env, "writable", BoolValue.of(writable));
res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable));
res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); res.defineOwnMember(env, "configurable", BoolValue.of(configurable));
return res; 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.configurable = configurable;
this.enumerable = enumerable; this.enumerable = enumerable;
this.writable = writable; this.writable = writable;
} }
public static FieldMember of(Value value) { public static FieldMember of(Value self, Value value) {
return new SimpleFieldMember(value, true, true, true); return new SimpleFieldMember(self, value, true, true, true);
} }
public static FieldMember of(Value value, boolean writable) { public static FieldMember of(Value self, Value value, boolean writable) {
return new SimpleFieldMember(value, true, true, writable); return new SimpleFieldMember(self, value, true, true, writable);
} }
public static FieldMember of(Value value, boolean configurable, boolean enumerable, boolean writable) { public static FieldMember of(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) {
return new SimpleFieldMember(value, configurable, enumerable, writable); return new SimpleFieldMember(self, value, configurable, enumerable, writable);
} }
} }
public boolean configurable(); public boolean configurable();
public boolean enumerable(); public boolean enumerable();
public boolean configure(Environment env, Member newMember, Value self); public boolean redefine(Environment env, Member newMember, Value self);
public ObjectValue descriptor(Environment env, Value self); public ObjectValue descriptor(Environment env, Value self);
public Value get(Environment env, Value self); public Value get(Environment env, Value self);

View File

@ -7,8 +7,9 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.environment.Key; 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.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember; 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.FunctionValue;
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; 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; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public abstract class Value { public abstract class Value {
public static enum CompareResult { public static enum State {
NOT_EQUAL, NORMAL(true, true, true),
EQUAL, NON_EXTENDABLE(false, true, true),
LESS, SEALED(false, false, true),
GREATER; 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) { public final boolean extendable;
if (cmp < 0) return LESS; public final boolean configurable;
if (cmp > 0) return GREATER; public final boolean writable;
return EQUAL;
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"); if (isNew) throw EngineException.ofType(name + " is not a constructor");
else throw EngineException.ofType(name + " is not a function"); 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 res = new ObjectValue();
var proto = getMember(env, new StringValue("prototype")); var proto = getMember(env, new StringValue("prototype"));
@ -95,11 +101,11 @@ public abstract class Value {
return res; return res;
} }
public final Value call(Environment env, Value self, Value ...args) { public final Value invoke(Environment env, Value self, Value ...args) {
return call(env, false, "", self, args); return invoke(env, "", self, args);
} }
public final Value callNew(Environment env, Value ...args) { public final Value construct(Environment env, Value ...args) {
return callNew(env, "", args); return construct(env, "", args);
} }
public abstract Value toPrimitive(Environment env); public abstract Value toPrimitive(Environment env);
@ -119,14 +125,20 @@ public abstract class Value {
} }
public abstract Member getOwnMember(Environment env, KeyCache key); public abstract Member getOwnMember(Environment env, KeyCache key);
public abstract Map<String, Member> getOwnMembers(Environment env); public abstract Set<String> getOwnMembers(Environment env, boolean onlyEnumerable);
public abstract Map<SymbolValue, Member> getOwnSymbolMembers(Environment env); public abstract Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable);
public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member); public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member);
public abstract boolean deleteOwnMember(Environment env, KeyCache key); public abstract boolean deleteOwnMember(Environment env, KeyCache key);
public abstract ObjectValue getPrototype(Environment env); public abstract ObjectValue getPrototype(Environment env);
public abstract boolean setPrototype(Environment env, ObjectValue val); 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) { public final Member getOwnMember(Environment env, Value key) {
return getOwnMember(env, new KeyCache(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) { 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) { 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) { 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) { 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) { 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) { 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) { public final boolean setMember(Environment env, KeyCache key, Value val) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
var member = obj.getOwnMember(env, key); var member = obj.getOwnMember(env, key);
if (member != null) { if (member instanceof PropertyMember prop) {
if (member.set(env, val, obj)) { if (prop.set(env, val, obj)) {
if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
return true; 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)); if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
return true; return true;
} }
@ -317,8 +329,8 @@ public abstract class Value {
return deleteMember(env, new KeyCache(key)); return deleteMember(env, new KeyCache(key));
} }
public final Map<String, Member> getMembers(Environment env, boolean own, boolean onlyEnumerable) { public final Set<String> getMembers(Environment env, boolean own, boolean onlyEnumerable) {
var res = new LinkedHashMap<String, Member>(); var res = new LinkedHashSet<String>();
var protos = new ArrayList<Value>(); var protos = new ArrayList<Value>();
for (var proto = this; proto != null; proto = proto.getPrototype(env)) { for (var proto = this; proto != null; proto = proto.getPrototype(env)) {
@ -329,19 +341,13 @@ public abstract class Value {
Collections.reverse(protos); Collections.reverse(protos);
for (var proto : protos) { for (var proto : protos) {
if (onlyEnumerable) { res.addAll(proto.getOwnMembers(env, onlyEnumerable));
for (var el : proto.getOwnMembers(env).entrySet()) {
if (!el.getValue().enumerable()) continue;
res.put(el.getKey(), el.getValue());
}
}
else res.putAll(proto.getOwnMembers(env));
} }
return res; return res;
} }
public final Map<SymbolValue, Member> getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) { public final Set<SymbolValue> getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) {
var res = new LinkedHashMap<SymbolValue, Member>(); var res = new LinkedHashSet<SymbolValue>();
var protos = new ArrayList<Value>(); var protos = new ArrayList<Value>();
for (var proto = this; proto != null; proto = proto.getPrototype(env)) { for (var proto = this; proto != null; proto = proto.getPrototype(env)) {
@ -352,13 +358,7 @@ public abstract class Value {
Collections.reverse(protos); Collections.reverse(protos);
for (var proto : protos) { for (var proto : protos) {
if (onlyEnumerable) { res.addAll(proto.getOwnSymbolMembers(env, onlyEnumerable));
for (var el : proto.getOwnSymbolMembers(env).entrySet()) {
if (!el.getValue().enumerable()) continue;
res.put(el.getKey(), el.getValue());
}
}
else res.putAll(proto.getOwnSymbolMembers(env));
} }
return res; return res;
@ -389,7 +389,7 @@ public abstract class Value {
private void loadNext() { private void loadNext() {
if (supplier == null) value = null; if (supplier == null) value = null;
else if (consumed) { 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 == null) { supplier = null; value = null; }
if (curr.getMember(env, new StringValue("done")).toBoolean()) { 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) { public void callWith(Environment env, Iterable<? extends Value> it) {
for (var el : 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) { public void callWithAsync(Environment env, Iterable<? extends Value> it, boolean async) {
for (var el : it) { 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 ( if (
func.prototype instanceof ObjectValue objProto && func.prototype instanceof ObjectValue objProto &&
objProto.getMember(env, "constructor") == func && 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"); } ) { keys.remove("constructor"); }
} }
else if (this instanceof ArrayValue) { else if (this instanceof ArrayValue) {
@ -469,30 +469,42 @@ public abstract class Value {
passed.add(this); passed.add(this);
if (keys.size() + obj.getOwnSymbolMembers(env).size() == 0) { if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) {
if (!printed) res.append("{}\n"); if (!printed) res.append("{}");
} }
else if (!printed) { else if (!printed) {
if (tab > 3) return "{...}"; if (tab > 3) return "{...}";
res.append("{\n"); 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(" "); for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append("[" + entry.getKey().value + "]" + ": "); res.append("[" + entry.value + "]" + ": ");
var member = entry.getValue(); var member = obj.getOwnMember(env, entry);
if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1));
else res.append("[property]"); 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"); 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(" "); for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(entry.getKey() + ": "); res.append(entry + ": ");
var member = entry.getValue(); var member = obj.getOwnMember(env, entry);
if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1));
else res.append("[property]"); 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"); res.append(",\n");
} }
@ -527,8 +539,8 @@ public abstract class Value {
return new NativeFunction("", args -> { return new NativeFunction("", args -> {
var obj = new ObjectValue(); var obj = new ObjectValue();
if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE)); if (!it.hasNext()) obj.defineOwnMember(args.env, "done", BoolValue.TRUE);
else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next())); else obj.defineOwnMember(args.env, "value", it.next());
return obj; return obj;
}); });
@ -658,22 +670,22 @@ public abstract class Value {
return a.toString(env).equals(b.toString(env)); return a.toString(env).equals(b.toString(env));
} }
// public static Value operation(Environment env, Operation op, Value ...args) { public static final String errorToReadable(Environment env, RuntimeException err, String prefix) {
// }
public static final String errorToReadable(RuntimeException err, String prefix) {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
if (err instanceof EngineException) { if (err instanceof EngineException ee) {
var ee = ((EngineException)err); if (env == null) env = ee.env;
try { try {
return prefix + " " + ee.toString(ee.env); return prefix + " " + ee.toString(env);
} }
catch (EngineException ex) { catch (EngineException ex) {
return prefix + " " + ee.value.toReadable(ee.env); return prefix + " " + ee.value.toReadable(env);
} }
} }
else if (err instanceof SyntaxException) { else if (err instanceof SyntaxException syntax) {
return prefix + " SyntaxError " + ((SyntaxException)err).msg; 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 if (err.getCause() instanceof InterruptedException) return "";
else { else {

View File

@ -10,6 +10,8 @@ import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
public abstract class FunctionValue extends ObjectValue { public abstract class FunctionValue extends ObjectValue {
private static final StringValue typeString = new StringValue("function");
public String name = ""; public String name = "";
public int length; public int length;
public Value prototype = new ObjectValue(); public Value prototype = new ObjectValue();
@ -17,7 +19,7 @@ public abstract class FunctionValue extends ObjectValue {
public boolean enableCall = true; public boolean enableCall = true;
public boolean enableNew = 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) { @Override public Value get(Environment env, Value self) {
if (name == null) return new StringValue(""); if (name == null) return new StringValue("");
return new StringValue(name); return new StringValue(name);
@ -27,7 +29,7 @@ public abstract class FunctionValue extends ObjectValue {
return true; 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) { @Override public Value get(Environment env, Value self) {
return new NumberValue(length); return new NumberValue(length);
} }
@ -35,7 +37,7 @@ public abstract class FunctionValue extends ObjectValue {
return false; 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) { @Override public Value get(Environment env, Value self) {
return prototype; return prototype;
} }
@ -77,6 +79,8 @@ public abstract class FunctionValue extends ObjectValue {
} }
} }
@Override public StringValue type() { return typeString; }
public void setName(String val) { public void setName(String val) {
if (this.name == null || this.name.equals("")) this.name = val; if (this.name == null || this.name.equals("")) this.name = val;
} }
@ -88,7 +92,7 @@ public abstract class FunctionValue extends ObjectValue {
this.length = length; this.length = length;
this.name = name; this.name = name;
prototype.defineOwnMember(null, "constructor", FieldMember.of(this)); prototype.defineOwnMember(null, "constructor", this);
} }
} }

View File

@ -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;
}
}

View File

@ -4,50 +4,15 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; 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.Value;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
// TODO: Make methods generic // TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Value> { public class ArrayValue extends ArrayLikeValue implements Iterable<Value> {
private Value[] values; private Value[] values;
private int size; 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) { private Value[] alloc(int index) {
index++; index++;
if (index < values.length) return values; if (index < values.length) return values;
@ -69,26 +34,27 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
return true; return true;
} }
public Value get(int i) { @Override public Value get(int i) {
if (i < 0 || i >= size) return null; if (i < 0 || i >= size) return null;
var res = values[i]; var res = values[i];
if (res == null) return Value.UNDEFINED; if (res == null) return Value.UNDEFINED;
else return res; else return res;
} }
public void set(int i, Value val) { @Override public void set(int i, Value val) {
if (i < 0) return; if (i < 0) return;
alloc(i)[i] = val; alloc(i)[i] = val;
if (i >= size) size = i + 1; 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; 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; if (i < 0 || i >= values.length) return;
values[i] = null; values[i] = null;
} }
public void shrink(int n) { public void shrink(int n) {
if (n >= values.length) { if (n >= values.length) {
values = new Value[16]; values = new Value[16];
@ -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() { @Override public Iterator<Value> iterator() {
return new Iterator<>() { return new Iterator<>() {
private int i = 0; private int i = 0;

View File

@ -1,8 +1,8 @@
package me.topchetoeu.jscript.runtime.values.objects; package me.topchetoeu.jscript.runtime.values.objects;
import java.util.Collections;
import java.util.LinkedHashMap; 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.Environment;
import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.environment.Key;
@ -20,13 +20,6 @@ public class ObjectValue extends Value {
public ObjectValue get(Environment env); public ObjectValue get(Environment env);
} }
public static enum State {
NORMAL,
NO_EXTENSIONS,
SEALED,
FROZEN,
}
public static class Property { public static class Property {
public final FunctionValue getter; public final FunctionValue getter;
public final FunctionValue setter; public final FunctionValue setter;
@ -41,8 +34,6 @@ public class ObjectValue extends Value {
protected PrototypeProvider prototype; protected PrototypeProvider prototype;
public boolean extensible = true;
public LinkedHashMap<String, Member> members = new LinkedHashMap<>(); public LinkedHashMap<String, Member> members = new LinkedHashMap<>();
public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>(); public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>();
@ -52,13 +43,13 @@ public class ObjectValue extends Value {
var valueOf = getMember(env, new StringValue("valueOf")); var valueOf = getMember(env, new StringValue("valueOf"));
if (valueOf instanceof FunctionValue) { if (valueOf instanceof FunctionValue) {
var res = valueOf.call(env, this); var res = valueOf.invoke(env, this);
if (res.isPrimitive()) return res; if (res.isPrimitive()) return res;
} }
var toString = getMember(env, new StringValue("toString")); var toString = getMember(env, new StringValue("toString"));
if (toString instanceof FunctionValue) { if (toString instanceof FunctionValue) {
var res = toString.call(env, this); var res = toString.invoke(env, this);
if (res.isPrimitive()) return res; 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 NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
@Override public StringValue type() { return typeString; } @Override public StringValue type() { return typeString; }
private State state = State.NORMAL;
@Override public State getState() { return state; }
public final void preventExtensions() { 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) { @Override public Member getOwnMember(Environment env, KeyCache key) {
if (key.isSymbol()) return symbolMembers.get(key.toSymbol()); 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) { @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
var old = getOwnMember(env, key); 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 (old != null && !old.configurable()) return false;
if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member);
@ -89,22 +88,40 @@ public class ObjectValue extends Value {
return true; return true;
} }
@Override public boolean deleteOwnMember(Environment env, KeyCache key) { @Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!extensible) return false; if (!getState().extendable) return false;
var member = getOwnMember(env, key); var member = getOwnMember(env, key);
if (member == null) return true; if (member == null) return true;
if (member.configurable()) return false; if (!member.configurable()) return false;
if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); if (key.isSymbol()) symbolMembers.remove(key.toSymbol());
else members.remove(key.toString(env)); else members.remove(key.toString(env));
return true; return true;
} }
@Override public Map<String, Member> getOwnMembers(Environment env) { @Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
return members; 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) { @Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
return Collections.unmodifiableMap(symbolMembers); 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) { @Override public ObjectValue getPrototype(Environment env) {
@ -116,12 +133,12 @@ public class ObjectValue extends Value {
} }
public final boolean setPrototype(PrototypeProvider val) { public final boolean setPrototype(PrototypeProvider val) {
if (!extensible) return false; if (!getState().extendable) return false;
prototype = val; prototype = val;
return true; return true;
} }
public final boolean setPrototype(Key<ObjectValue> key) { public final boolean setPrototype(Key<ObjectValue> key) {
if (!extensible) return false; if (!getState().extendable) return false;
prototype = env -> env.get(key); prototype = env -> env.get(key);
return true; return true;
} }

View File

@ -5,20 +5,20 @@ import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
public final class ScopeValue extends ObjectValue { public final class ScopeValue extends ObjectValue {
private class VariableField extends FieldMember { private static class VariableField extends FieldMember {
private int i; private int i;
public VariableField(int i) { public VariableField(int i, ScopeValue self) {
super(false, true, true); super(self, false, true, true);
this.i = i; this.i = i;
} }
@Override public Value get(Environment env, Value self) { @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) { @Override public boolean set(Environment env, Value val, Value self) {
variables[i][0] = val; ((ScopeValue)self).variables[i][0] = val;
return true; return true;
} }
} }
@ -28,7 +28,7 @@ public final class ScopeValue extends ObjectValue {
public ScopeValue(Value[][] variables, String[] names) { public ScopeValue(Value[][] variables, String[] names) {
this.variables = variables; this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) { 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));
} }
} }
} }

View File

@ -1,6 +1,6 @@
package me.topchetoeu.jscript.runtime.values.primitives; 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.common.environment.Environment;
import me.topchetoeu.jscript.runtime.values.KeyCache; 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 final boolean setPrototype(Environment env, ObjectValue val) { return false; }
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; } @Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
@Override public Map<String, Member> getOwnMembers(Environment env) { return Map.of(); } @Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return Set.of(); }
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) { return Map.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() {}
} }

View File

@ -1,11 +1,14 @@
package me.topchetoeu.jscript.runtime.values.primitives; 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.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; 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;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class StringValue extends PrimitiveValue { 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 ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); }
@Override public Map<String, Member> getOwnMembers(Environment env) { @Override public Member getOwnMember(Environment env, KeyCache key) {
// TODO Auto-generated method stub var num = key.toNumber(env);
return super.getOwnMembers(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) { public StringValue(String value) {

View File

@ -1,7 +1,5 @@
package me.topchetoeu.jscript.runtime.values.primitives; package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.Map;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.KeyCache; 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) { @Override public Member getOwnMember(Environment env, KeyCache key) {
throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); 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) { public VoidValue(String name, StringValue type) {
this.name = name; this.name = name;

View File

@ -1,16 +1,32 @@
const target = arguments[0]; const target = arguments[0];
const primordials = arguments[1]; const primordials = arguments[1];
const makeSymbol = primordials.symbol.makeSymbol; const symbol = primordials.symbol || (() => {
const getSymbol = primordials.symbol.getSymbol; const repo = {};
const getSymbolKey = primordials.symbol.getSymbolKey;
const getSymbolDescription = primordials.symbol.getSymbolDescription;
const parseInt = primordials.number.parseInt; return {
const parseFloat = primordials.number.parseFloat; makeSymbol: (name) => { name },
const isNaN = primordials.number.isNaN; getSymbol(name) {
const NaN = primordials.number.NaN; if (name in repo) return repo[name];
const Infinity = primordials.number.Infinity; 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 fromCharCode = primordials.string.fromCharCode;
const fromCodePoint = primordials.string.fromCodePoint; const fromCodePoint = primordials.string.fromCodePoint;
@ -29,13 +45,15 @@ const invokeType = primordials.function.invokeType;
const setConstructable = primordials.function.setConstructable; const setConstructable = primordials.function.setConstructable;
const setCallable = primordials.function.setCallable; const setCallable = primordials.function.setCallable;
const invoke = primordials.function.invoke; const invoke = primordials.function.invoke;
const construct = primordials.function.construct;
const setGlobalPrototype = primordials.setGlobalPrototype;
const compile = primordials.compile;
const json = primordials.json; 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; const undefined = ({}).definitelyDefined;
target.undefined = undefined; target.undefined = undefined;
@ -52,13 +70,13 @@ const unwrapThis = (self, type, constr, name, arg, defaultVal) => {
const wrapIndex = (i, len) => {}; const wrapIndex = (i, len) => {};
const Symbol = (name = "") => makeSymbol(name); const Symbol = (name = "") => symbol.makeSymbol(name);
defineField(Symbol, "for", true, false, true, function(name) { defineField(Symbol, "for", true, false, true, function(name) {
return getSymbol(name + ""); return symbol.getSymbol(name + "");
}); });
defineField(Symbol, "keyFor", true, false, true, function(symbol) { defineField(Symbol, "keyFor", true, false, true, function(value) {
return getSymbolKey(unwrapThis(symbol, "symbol", Symbol, "Symbol.keyFor")); return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor"));
}); });
defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); 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, {}); defineField(Symbol, "prototype", false, false, false, {});
defineProperty(Symbol.prototype, "description", false, true, function () { 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); }, undefined);
defineField(Symbol.prototype, "toString", true, false, true, function() { defineField(Symbol.prototype, "toString", true, false, true, function() {
return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; 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) { defineField(Number, "isFinite", true, false, true, function(value) {
value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); 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; if (value === Infinity || value === -Infinity) return false;
return true; return true;
@ -103,34 +121,34 @@ defineField(Number, "isFinite", true, false, true, function(value) {
defineField(Number, "isInteger", true, false, true, function(value) { defineField(Number, "isInteger", true, false, true, function(value) {
value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined);
if (value === undefined) return false; if (value === undefined) return false;
return parseInt(value) === value; return number.parseInt(value) === value;
}); });
defineField(Number, "isNaN", true, false, true, function(value) { defineField(Number, "isNaN", true, false, true, function(value) {
return isNaN(value); return number.isNaN(value);
}); });
defineField(Number, "isSafeInteger", true, false, true, function(value) { defineField(Number, "isSafeInteger", true, false, true, function(value) {
value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); 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; return value >= -9007199254740991 && value <= 9007199254740991;
}); });
defineField(Number, "parseFloat", true, false, true, function(value) { defineField(Number, "parseFloat", true, false, true, function(value) {
value = 0 + value; value = 0 + value;
return parseFloat(value); return number.parseFloat(value);
}); });
defineField(Number, "parseInt", true, false, true, function(value, radix) { defineField(Number, "parseInt", true, false, true, function(value, radix) {
value = 0 + value; value = 0 + value;
radix = +radix; 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, "EPSILON", false, false, false, 2.220446049250313e-16);
defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991);
defineField(Number, "MAX_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, "POSITIVE_INFINITY", false, false, false, +number.Infinity);
defineField(Number, "NEGATIVE_INFINITY", false, false, false, -Infinity); defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity);
defineField(Number, "NaN", false, false, false, NaN); defineField(Number, "NaN", false, false, false, number.NaN);
defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308);
defineField(Number, "MIN_VALUE", false, false, false, 5e-324); defineField(Number, "MIN_VALUE", false, false, false, 5e-324);
defineField(Number, "prototype", false, false, false, {}); defineField(Number, "prototype", false, false, false, {});
@ -144,6 +162,10 @@ defineField(Number.prototype, "valueOf", true, false, true, function() {
}); });
target.Number = Number; target.Number = Number;
target.parseInt = Number.parseInt;
target.parseFloat = Number.parseFloat;
target.NaN = Number.NaN;
target.Infinity = Number.POSITIVE_INFINITY;
const String = function(value) { const String = function(value) {
if (invokeType(arguments) === "call") { if (invokeType(arguments) === "call") {
@ -231,6 +253,37 @@ const Object = function(value) {
defineField(Object, "prototype", false, false, false, setPrototype({}, null)); 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() { defineField(Object.prototype, "toString", true, false, true, function() {
if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]";
else if (typeof this === "number" || this instanceof Number) return "[object Number]"; else if (typeof this === "number" || this instanceof Number) return "[object Number]";
@ -244,7 +297,7 @@ defineField(Object.prototype, "valueOf", true, false, true, function() {
return this; return this;
}); });
target.Boolean = Boolean; target.Object = Object;
const Function = function() { const Function = function() {
const parts = ["return function annonymous("]; const parts = ["return function annonymous("];
@ -304,8 +357,61 @@ defineField(Function.prototype, "valueOf", true, false, true, function() {
target.Function = 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("string", String.prototype);
setGlobalPrototype("number", Number.prototype); setGlobalPrototype("number", Number.prototype);
setGlobalPrototype("boolean", Boolean.prototype); setGlobalPrototype("boolean", Boolean.prototype);
setGlobalPrototype("symbol", Symbol.prototype); setGlobalPrototype("symbol", Symbol.prototype);
setGlobalPrototype("object", Object.prototype); setGlobalPrototype("object", Object.prototype);
setGlobalPrototype("function", Function.prototype);
setGlobalPrototype("error", Error.prototype);
setGlobalPrototype("syntax", SyntaxError.prototype);