feat: implement classes (without inheritence)
This commit is contained in:
parent
0b3dca8b13
commit
0258cc0a90
158
src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java
Normal file
158
src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.members.FieldMemberNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.members.MethodMemberNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.members.PropertyMemberNode;
|
||||||
|
|
||||||
|
public abstract class ClassNode extends FunctionNode {
|
||||||
|
public static final class ClassBody {
|
||||||
|
public final List<Node> staticMembers;
|
||||||
|
public final List<FieldMemberNode> protoFields;
|
||||||
|
public final List<Node> protoMembers;
|
||||||
|
public final Parameters constructorParameters;
|
||||||
|
public final CompoundNode constructorBody;
|
||||||
|
|
||||||
|
public ClassBody(
|
||||||
|
List<Node> staticMembers, List<FieldMemberNode> protoFields, List<Node> protoMembers,
|
||||||
|
Parameters constructorParameters, CompoundNode constructorBody
|
||||||
|
) {
|
||||||
|
this.staticMembers = staticMembers;
|
||||||
|
this.protoFields = protoFields;
|
||||||
|
this.protoMembers = protoMembers;
|
||||||
|
this.constructorParameters = constructorParameters;
|
||||||
|
this.constructorBody = constructorBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ClassBody body;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Override public String name() { return name; }
|
||||||
|
|
||||||
|
public void compileStatic(CompileResult target) {
|
||||||
|
for (var member : body.staticMembers) member.compile(target, true);
|
||||||
|
}
|
||||||
|
public void compilePrototype(CompileResult target) {
|
||||||
|
if (body.protoMembers.size() > 0) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.loadMember("prototype"));
|
||||||
|
|
||||||
|
for (var i = 0; i < body.protoMembers.size() - 1; i++) {
|
||||||
|
body.protoMembers.get(i).compile(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.protoMembers.get(body.protoMembers.size() - 1).compile(target, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void compilePreBody(CompileResult target) {
|
||||||
|
for (var member : body.protoFields) {
|
||||||
|
target.add(Instruction.loadThis());
|
||||||
|
member.compile(target, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
var id = target.addChild(compileBody(target, name, null));
|
||||||
|
target.add(_i -> Instruction.loadFunc(id, false, true, false, name, captures(id, target)));
|
||||||
|
compileStatic(target);
|
||||||
|
compilePrototype(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassNode(Location loc, Location end, String name, ClassBody body) {
|
||||||
|
super(loc, end, body.constructorParameters, body.constructorBody);
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<Node> parseMember(Source src, int i) {
|
||||||
|
return ParseRes.first(src, i,
|
||||||
|
PropertyMemberNode::parse,
|
||||||
|
FieldMemberNode::parseClass,
|
||||||
|
MethodMemberNode::parse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ClassBody> parseBody(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 fields = new LinkedList<FieldMemberNode>();
|
||||||
|
var members = new LinkedList<Node>();
|
||||||
|
var statics = new LinkedList<Node>();
|
||||||
|
|
||||||
|
var params = new Parameters(new ArrayList<>());
|
||||||
|
var body = new CompoundNode(loc, false);
|
||||||
|
var hasConstr = false;
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
return ParseRes.res(new ClassBody(statics, fields, members, params, body), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ParseRes<Node> prop = parseMember(src, i + n);
|
||||||
|
|
||||||
|
if (prop.isSuccess()) {
|
||||||
|
n += prop.n;
|
||||||
|
|
||||||
|
if (prop.result instanceof FieldMemberNode field) fields.add(field);
|
||||||
|
else if (prop.result instanceof MethodMemberNode method && method.name().equals("constructor")) {
|
||||||
|
if (hasConstr) return ParseRes.error(loc, "A class may only have one constructor");
|
||||||
|
|
||||||
|
params = method.params;
|
||||||
|
body = method.body;
|
||||||
|
hasConstr = true;
|
||||||
|
}
|
||||||
|
else members.add(prop.result);
|
||||||
|
}
|
||||||
|
else if (Parsing.isIdentifier(src, i + n, "static")) {
|
||||||
|
n += 6;
|
||||||
|
|
||||||
|
var staticProp = parseMember(src, i + n);
|
||||||
|
if (!staticProp.isSuccess()) {
|
||||||
|
if (prop.isError()) return prop.chainError();
|
||||||
|
else return staticProp.chainError(src.loc(i + n), "Expected a member after 'static' keyword");
|
||||||
|
}
|
||||||
|
n += staticProp.n;
|
||||||
|
|
||||||
|
statics.add(staticProp.result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||||
|
if (end.isSuccess()) n += end.n;
|
||||||
|
else return ParseRes.error(src.loc(i + n), "Expected a member, end of statement or a closing colon");
|
||||||
|
}
|
||||||
|
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
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 ClassBody(statics, fields, members, params, body), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
|
||||||
|
// super(loc, end, params, body);
|
||||||
|
// this.name = name;
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
|
||||||
|
public class ClassStatementNode extends ClassNode {
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
super.compile(target, pollute, name, bp);
|
||||||
|
var i = target.scope.define(DeclarationType.LET, name(), loc());
|
||||||
|
target.add(_i -> i.index().toInit());
|
||||||
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassStatementNode(Location loc, Location end, String name, ClassBody body) {
|
||||||
|
super(loc, end, name, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ClassStatementNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
|
||||||
|
n += 5;
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a class name");
|
||||||
|
n += name.n;
|
||||||
|
|
||||||
|
var body = parseBody(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new ClassStatementNode(loc, src.loc(i + n), name.result, body.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -131,7 +131,7 @@ public final class CompileResult {
|
|||||||
|
|
||||||
for (var suppl : instructions) {
|
for (var suppl : instructions) {
|
||||||
instrRes[i] = suppl.apply(i);
|
instrRes[i] = suppl.apply(i);
|
||||||
System.out.println(instrRes[i]);
|
// System.out.println(instrRes[i]);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ public abstract class FunctionNode extends Node {
|
|||||||
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
|
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void compilePreBody(CompileResult target) { }
|
||||||
|
|
||||||
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) {
|
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) {
|
||||||
var name = this.name() != null ? this.name() : _name;
|
var name = this.name() != null ? this.name() : _name;
|
||||||
|
|
||||||
@ -30,6 +32,8 @@ public abstract class FunctionNode extends Node {
|
|||||||
.remove(LabelContext.CONTINUE_CTX);
|
.remove(LabelContext.CONTINUE_CTX);
|
||||||
|
|
||||||
return new CompileResult(env, scope, params.params.size(), target -> {
|
return new CompileResult(env, scope, params.params.size(), target -> {
|
||||||
|
compilePreBody(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, 0));
|
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0));
|
||||||
|
@ -28,6 +28,7 @@ import me.topchetoeu.jscript.compilation.control.WhileNode;
|
|||||||
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||||
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
||||||
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ClassValueNode;
|
||||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||||
import me.topchetoeu.jscript.compilation.values.RegexNode;
|
import me.topchetoeu.jscript.compilation.values.RegexNode;
|
||||||
import me.topchetoeu.jscript.compilation.values.ThisNode;
|
import me.topchetoeu.jscript.compilation.values.ThisNode;
|
||||||
@ -63,7 +64,7 @@ public final class JavaScript {
|
|||||||
"finally", "for", "do", "while", "switch", "case", "default", "new",
|
"finally", "for", "do", "while", "switch", "case", "default", "new",
|
||||||
"function", "var", "return", "throw", "typeof", "delete", "break",
|
"function", "var", "return", "throw", "typeof", "delete", "break",
|
||||||
"continue", "debugger", "implements", "interface", "package", "private",
|
"continue", "debugger", "implements", "interface", "package", "private",
|
||||||
"protected", "public", "static", "arguments"
|
"protected", "public", "static", "arguments", "class"
|
||||||
));
|
));
|
||||||
|
|
||||||
public static ParseRes<? extends Node> parseParens(Source src, int i) {
|
public static ParseRes<? extends Node> parseParens(Source src, int i) {
|
||||||
@ -88,6 +89,7 @@ public final class JavaScript {
|
|||||||
return ParseRes.first(src, i,
|
return ParseRes.first(src, i,
|
||||||
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
|
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
|
||||||
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
|
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
|
||||||
|
(s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j),
|
||||||
JavaScript::parseLiteral,
|
JavaScript::parseLiteral,
|
||||||
StringNode::parse,
|
StringNode::parse,
|
||||||
RegexNode::parse,
|
RegexNode::parse,
|
||||||
@ -96,7 +98,7 @@ public final class JavaScript {
|
|||||||
ChangeNode::parsePrefixIncrease,
|
ChangeNode::parsePrefixIncrease,
|
||||||
OperationNode::parsePrefix,
|
OperationNode::parsePrefix,
|
||||||
ArrayNode::parse,
|
ArrayNode::parse,
|
||||||
FunctionArrowNode::parse,
|
(s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j),
|
||||||
JavaScript::parseParens,
|
JavaScript::parseParens,
|
||||||
CallNode::parseNew,
|
CallNode::parseNew,
|
||||||
TypeofNode::parse,
|
TypeofNode::parse,
|
||||||
@ -188,6 +190,7 @@ public final class JavaScript {
|
|||||||
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
|
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
|
||||||
|
|
||||||
ParseRes<? extends Node> res = ParseRes.first(src, i + n,
|
ParseRes<? extends Node> res = ParseRes.first(src, i + n,
|
||||||
|
ClassStatementNode::parse,
|
||||||
VariableDeclareNode::parse,
|
VariableDeclareNode::parse,
|
||||||
ReturnNode::parse,
|
ReturnNode::parse,
|
||||||
ThrowNode::parse,
|
ThrowNode::parse,
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
|
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.ClassNode;
|
||||||
|
|
||||||
|
public class ClassValueNode extends ClassNode {
|
||||||
|
public ClassValueNode(Location loc, Location end, String name, ClassBody body) {
|
||||||
|
super(loc, end, name, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ClassValueNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
|
||||||
|
n += 5;
|
||||||
|
|
||||||
|
var name = Parsing.parseIdentifier(src, i + n);
|
||||||
|
n += name.n;
|
||||||
|
|
||||||
|
var body = parseBody(src, i + n);
|
||||||
|
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new ClassValueNode(loc, src.loc(i + n), name.result, body.result), n);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user