ES6 Object and assignment destructors + object stuff #28
@ -449,8 +449,8 @@ 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() {
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
|
||||||
|
|
||||||
public interface AssignableNode {
|
|
||||||
public void compileBeforeAssign(CompileResult target, boolean operator);
|
|
||||||
public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute);
|
|
||||||
public default String assignName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.destructing;
|
||||||
|
|
||||||
|
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.DeferredIntSupplier;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public class AssignDestructorNode extends Node implements Destructor {
|
||||||
|
public final String name;
|
||||||
|
public final Node value;
|
||||||
|
|
||||||
|
@Override public void destructDeclResolve(CompileResult target) {
|
||||||
|
target.scope.define(new Variable(name, false), loc());
|
||||||
|
}
|
||||||
|
@Override public void destructArg(CompileResult target) {
|
||||||
|
var v = target.scope.define(new Variable(name, false), loc());
|
||||||
|
destructAssign(target);
|
||||||
|
target.add(_i -> v.index().toSet(false));
|
||||||
|
}
|
||||||
|
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
||||||
|
if (decl != null && decl.strict) target.scope.define(new Variable(name, decl.readonly), loc());
|
||||||
|
var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
target.add(Instruction.jmpIfNot(end));
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
value.compile(target, true);
|
||||||
|
|
||||||
|
end.set(target.size());
|
||||||
|
|
||||||
|
target.add(VariableNode.toSet(target, loc(), name, false, decl != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssignDestructorNode(Location loc, String name, Node value) {
|
||||||
|
super(loc);
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<AssignDestructorNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i);
|
||||||
|
if (!JavaScript.checkVarName(null)) return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", name.result));
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i, "=")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var value = JavaScript.parseExpression(src, i, 2);
|
||||||
|
if (value.isError()) return ParseRes.error(src.loc(i + n), "Expected a value after '='");
|
||||||
|
n += value.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new AssignDestructorNode(loc, name.result, value.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.destructing;
|
||||||
|
|
||||||
|
public interface AssignTarget extends Destructor {
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.destructing;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
|
||||||
|
public interface ChangeTarget extends AssignTarget {
|
||||||
|
void beforeChange(CompileResult target);
|
||||||
|
void afterChange(CompileResult target, boolean pollute);
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.destructing;
|
||||||
|
|
||||||
|
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.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ObjectDestructorNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public interface Destructor {
|
||||||
|
void destructDeclResolve(CompileResult target);
|
||||||
|
|
||||||
|
default void destructArg(CompileResult target) {
|
||||||
|
beforeAssign(target, null);
|
||||||
|
afterAssign(target, null);
|
||||||
|
}
|
||||||
|
default void beforeAssign(CompileResult target, DeclarationType decl) {}
|
||||||
|
void afterAssign(CompileResult target, DeclarationType decl, boolean pollute);
|
||||||
|
|
||||||
|
public static ParseRes<Destructor> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
ParseRes<Destructor> first = ParseRes.first(src, i + n,
|
||||||
|
ObjectDestructorNode::parse,
|
||||||
|
AssignDestructorNode::parse,
|
||||||
|
VariableNode::parse
|
||||||
|
);
|
||||||
|
if (first.isSuccess()) return first.addN(n);
|
||||||
|
|
||||||
|
var exp = JavaScript.parseExpression(src, i, 2);
|
||||||
|
if (!(exp.result instanceof Destructor destructor)) return ParseRes.error(src.loc(i + n), "Expected a destructor expression");
|
||||||
|
n += exp.n;
|
||||||
|
|
||||||
|
return ParseRes.res(destructor, n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.destructing;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
public interface NamedDestructor extends Destructor {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -61,6 +61,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
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,199 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.destructing.Destructor;
|
||||||
|
|
||||||
|
|
||||||
|
public class ObjectDestructorNode extends Node implements Destructor {
|
||||||
|
public final LinkedHashMap<String, Destructor> destructors;
|
||||||
|
public final VariableNode restDestructor;
|
||||||
|
|
||||||
|
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 destructDeclResolve(CompileResult target) {
|
||||||
|
for (var el : destructors.values()) {
|
||||||
|
el.destructDeclResolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restDestructor != null) restDestructor.destructDeclResolve(target);
|
||||||
|
}
|
||||||
|
@Override public void destructArg(CompileResult target) {
|
||||||
|
if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2);
|
||||||
|
else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0));
|
||||||
|
|
||||||
|
for (var el : destructors.entrySet()) {
|
||||||
|
if (restDestructor != null) {
|
||||||
|
target.add(Instruction.pushValue(el.getKey()));
|
||||||
|
target.add(Instruction.delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember(el.getKey()));
|
||||||
|
el.getValue().destructArg(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restDestructor != null) restDestructor.destructArg(target);
|
||||||
|
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
||||||
|
if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2);
|
||||||
|
else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0));
|
||||||
|
|
||||||
|
for (var el : destructors.entrySet()) {
|
||||||
|
if (restDestructor != null) {
|
||||||
|
target.add(Instruction.pushValue(el.getKey()));
|
||||||
|
target.add(Instruction.delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember(el.getKey()));
|
||||||
|
el.getValue().afterAssign(target, decl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restDestructor != null) restDestructor.afterAssign(target, decl);
|
||||||
|
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
@Override public void destructAssign(CompileResult target, boolean pollute) {
|
||||||
|
if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2);
|
||||||
|
else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0));
|
||||||
|
|
||||||
|
for (var el : destructors.entrySet()) {
|
||||||
|
if (restDestructor != null) {
|
||||||
|
target.add(Instruction.pushValue(el.getKey()));
|
||||||
|
target.add(Instruction.delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
target.add(Instruction.loadMember(el.getKey()));
|
||||||
|
el.getValue().destructAssign(target, pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restDestructor != null) restDestructor.destructAssign(target, pollute);
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectDestructorNode(Location loc, Map<String, Destructor> map, VariableNode rest) {
|
||||||
|
super(loc);
|
||||||
|
this.destructors = new LinkedHashMap<>(map);
|
||||||
|
this.restDestructor = rest;
|
||||||
|
}
|
||||||
|
public ObjectDestructorNode(Location loc, Map<String, Destructor> map) {
|
||||||
|
super(loc);
|
||||||
|
this.destructors = new LinkedHashMap<>(map);
|
||||||
|
this.restDestructor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<String> parsePropName(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
var res = ParseRes.first(src, i + n,
|
||||||
|
Parsing::parseIdentifier,
|
||||||
|
Parsing::parseString,
|
||||||
|
(s, j) -> Parsing.parseNumber(s, j, false)
|
||||||
|
);
|
||||||
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(res.result.toString(), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ObjectDestructorNode> 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 destructors = new LinkedHashMap<String, Destructor>();
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
return ParseRes.res(new ObjectDestructorNode(loc, destructors), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
// if (src.is(i, null))
|
||||||
|
|
||||||
|
var name = parsePropName(src, i + n);
|
||||||
|
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a field name");
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var destructor = Destructor.parse(src, i + n);
|
||||||
|
if (!destructor.isSuccess()) return destructor.chainError(src.loc(i + n), "Expected a value in array list");
|
||||||
|
n += destructor.n;
|
||||||
|
|
||||||
|
destructors.put(name.result, destructor.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 ObjectDestructorNode(loc, destructors), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,9 +15,10 @@ import me.topchetoeu.jscript.compilation.FunctionNode;
|
|||||||
import me.topchetoeu.jscript.compilation.FunctionValueNode;
|
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.destructing.Destructor;
|
||||||
|
|
||||||
|
|
||||||
public class ObjectNode extends Node {
|
public class ObjectNode extends Node implements Destructor {
|
||||||
public static class ObjProp {
|
public static class ObjProp {
|
||||||
public final String name;
|
public final String name;
|
||||||
public final String access;
|
public final String access;
|
||||||
@ -148,7 +149,7 @@ public class ObjectNode extends Node {
|
|||||||
n++;
|
n++;
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list");
|
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after property key");
|
||||||
n += valRes.n;
|
n += valRes.n;
|
||||||
|
|
||||||
values.put(name.result, valRes.result);
|
values.put(name.result, valRes.result);
|
||||||
|
@ -12,9 +12,11 @@ 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.JavaScript.DeclarationType;
|
||||||
|
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 AssignTarget {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@Override public String assignName() { return name; }
|
@Override public String assignName() { return name; }
|
||||||
@ -28,6 +30,25 @@ public class VariableNode extends Node implements AssignableNode {
|
|||||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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, DeclarationType decl) {
|
||||||
|
if (decl.strict) {
|
||||||
|
var v = target.scope.defineStrict(new Variable(name, decl.readonly), loc());
|
||||||
|
target.add(_i -> v.index().toSet(false));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target.add(VariableNode.toSet(target, loc(), name, false, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void destructAssign(CompileResult target, boolean pollute) {
|
||||||
|
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
var i = target.scope.get(name, false);
|
var i = target.scope.get(name, false);
|
||||||
|
|
||||||
@ -81,10 +102,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);
|
||||||
|
@ -1,21 +1,68 @@
|
|||||||
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.Operation;
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.destructing.AssignTarget;
|
||||||
|
import me.topchetoeu.jscript.compilation.destructing.Destructor;
|
||||||
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class AssignNode extends Node {
|
public class AssignNode extends Node implements Destructor {
|
||||||
public final AssignableNode assignable;
|
public final AssignTarget assignable;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void destructDeclResolve(CompileResult target) {
|
||||||
assignable.compileBeforeAssign(target, false);
|
if (!(assignable instanceof VariableNode var)) {
|
||||||
value.compile(target, true);
|
throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable");
|
||||||
assignable.compileAfterAssign(target, false, pollute);
|
}
|
||||||
|
|
||||||
|
target.scope.define(new Variable(var.name, false), var.loc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssignNode(Location loc, AssignableNode assignable, Node value) {
|
@Override public void destructArg(CompileResult target) {
|
||||||
|
if (!(assignable instanceof VariableNode var)) {
|
||||||
|
throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
var v = target.scope.define(new Variable(var.name, false), var.loc());
|
||||||
|
afterAssign(target, null, false);
|
||||||
|
target.add(_i -> v.index().toSet(false));
|
||||||
|
}
|
||||||
|
@Override public void afterAssign(CompileResult target, DeclarationType decl, boolean pollute) {
|
||||||
|
if (decl != null && decl.strict) {
|
||||||
|
if (!(assignable instanceof VariableNode var)) {
|
||||||
|
throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable");
|
||||||
|
}
|
||||||
|
target.scope.define(new Variable(var.name, decl.strict), var.loc());
|
||||||
|
}
|
||||||
|
|
||||||
|
assignable.beforeAssign(target, decl);
|
||||||
|
|
||||||
|
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.jmp(target.size() - start));
|
||||||
|
|
||||||
|
assignable.afterAssign(target, decl, pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
assignable.beforeAssign(target, null);
|
||||||
|
value.compile(target, true);
|
||||||
|
assignable.afterAssign(target, null, pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssignNode(Location loc, AssignTarget assignable, Node value) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.assignable = assignable;
|
this.assignable = assignable;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
@ -6,25 +6,25 @@ 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.destructing.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 assignable;
|
public final ChangeTarget assignable;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final Operation op;
|
public final Operation op;
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
assignable.compileBeforeAssign(target, true);
|
assignable.beforeChange(target);
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
target.add(Instruction.operation(op));
|
target.add(Instruction.operation(op));
|
||||||
assignable.compileAfterAssign(target, true, pollute);
|
assignable.afterChange(target, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeNode(Location loc, AssignableNode assignable, Node value, Operation op) {
|
public ChangeNode(Location loc, ChangeTarget assignable, Node value, Operation op) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.assignable = assignable;
|
this.assignable = assignable;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@ -40,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, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n);
|
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
|
public static ParseRes<ChangeNode> parsePrefixDecrease(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
@ -53,8 +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, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n);
|
return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,39 +10,56 @@ 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.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.destructing.AssignTarget;
|
||||||
|
import me.topchetoeu.jscript.compilation.destructing.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;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
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 void compileBeforeAssign(CompileResult target, boolean op) {
|
@Override public void destructDeclResolve(CompileResult target) {
|
||||||
|
throw new SyntaxException(loc(), "Unexpected index in destructor");
|
||||||
|
}
|
||||||
|
@Override public void destructArg(CompileResult target) {
|
||||||
|
throw new SyntaxException(loc(), "Unexpected index in destructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void beforeAssign(CompileResult target, DeclarationType decl) {
|
||||||
|
if (decl != null) throw new SyntaxException(loc(), "Unexpected index in destructor");
|
||||||
|
object.compile(target, true);
|
||||||
|
|
||||||
|
if (index instanceof NumberNode num && (int)num.value == num.value) return;
|
||||||
|
if (index instanceof StringNode) return;
|
||||||
|
index.compile(target, true);
|
||||||
|
|
||||||
|
target.add(Instruction.dup(1, 1));
|
||||||
|
target.add(Instruction.dup(1, 1));
|
||||||
|
target.add(Instruction.loadMember());
|
||||||
|
}
|
||||||
|
@Override public void beforeChange(CompileResult target) {
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
|
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
if (op) {
|
target.add(Instruction.dup());
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.loadMember((int)num.value));
|
||||||
target.add(Instruction.loadMember((int)num.value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (index instanceof StringNode str) {
|
else if (index instanceof StringNode str) {
|
||||||
if (op) {
|
target.add(Instruction.dup());
|
||||||
target.add(Instruction.dup());
|
target.add(Instruction.loadMember(str.value));
|
||||||
target.add(Instruction.loadMember(str.value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
index.compile(target, true);
|
index.compile(target, true);
|
||||||
|
|
||||||
if (op) {
|
target.add(Instruction.dup(1, 1));
|
||||||
target.add(Instruction.dup(1, 1));
|
target.add(Instruction.dup(1, 1));
|
||||||
target.add(Instruction.dup(1, 1));
|
target.add(Instruction.loadMember());
|
||||||
target.add(Instruction.loadMember());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void compileAfterAssign(CompileResult target, boolean op, boolean pollute) {
|
@Override public void afterAssign(CompileResult target, boolean op, boolean pollute) {
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
target.add(Instruction.storeMember((int)num.value, pollute));
|
target.add(Instruction.storeMember((int)num.value, pollute));
|
||||||
}
|
}
|
||||||
@ -54,6 +71,16 @@ public class IndexNode extends Node implements AssignableNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
||||||
|
throw new SyntaxException(loc(), "Illegal index in declaration destruction context");
|
||||||
|
}
|
||||||
|
@Override public void destructAssign(CompileResult target, boolean pollute) {
|
||||||
|
object.compile(target, true);
|
||||||
|
target.add(Instruction.dup(1, 1));
|
||||||
|
compileAfterAssign(target, false, false);
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
// @Override public Node toAssign(Node val, Operation operation) {
|
// @Override public Node toAssign(Node val, Operation operation) {
|
||||||
// return new IndexAssignNode(loc(), object, index, val, operation);
|
// return new IndexAssignNode(loc(), object, index, val, operation);
|
||||||
// }
|
// }
|
||||||
|
@ -54,13 +54,13 @@ 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 (!(prev instanceof AssignTarget)) 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));
|
||||||
|
|
||||||
if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignableNode)prev), other.result), other.n);
|
if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignTarget)prev), other.result), other.n);
|
||||||
else return ParseRes.res(new ChangeNode(loc, ((AssignableNode)prev), other.result, operation), other.n);
|
else return ParseRes.res(new ChangeNode(loc, ((AssignTarget)prev), other.result, operation), other.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
||||||
|
@ -21,7 +21,7 @@ public class PostfixNode extends ChangeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PostfixNode(Location loc, AssignableNode value, double addAmount) {
|
public PostfixNode(Location loc, AssignTarget value, double addAmount) {
|
||||||
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,10 +32,10 @@ public class PostfixNode extends ChangeNode {
|
|||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "++")) return ParseRes.failed();
|
if (!src.is(i + n, "++")) return ParseRes.failed();
|
||||||
if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, 1), n);
|
return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, 1), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 15) return ParseRes.failed();
|
if (precedence > 15) return ParseRes.failed();
|
||||||
@ -44,9 +44,9 @@ public class PostfixNode extends ChangeNode {
|
|||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "--")) return ParseRes.failed();
|
if (!src.is(i + n, "--")) return ParseRes.failed();
|
||||||
if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, -1), n);
|
return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, -1), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user