diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java b/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java new file mode 100644 index 0000000..9b2ba98 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java @@ -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 staticMembers; + public final List protoFields; + public final List protoMembers; + public final Parameters constructorParameters; + public final CompoundNode constructorBody; + + public ClassBody( + List staticMembers, List protoFields, List 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 parseMember(Source src, int i) { + return ParseRes.first(src, i, + PropertyMemberNode::parse, + FieldMemberNode::parseClass, + MethodMemberNode::parse + ); + } + + public static ParseRes 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(); + var members = new LinkedList(); + var statics = new LinkedList(); + + 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 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; + // } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java new file mode 100644 index 0000000..eaeb32c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java @@ -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 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); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index a346738..04711d5 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -131,7 +131,7 @@ public final class CompileResult { for (var suppl : instructions) { instrRes[i] = suppl.apply(i); - System.out.println(instrRes[i]); + // System.out.println(instrRes[i]); i++; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index f5a54e2..270ea0d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -22,6 +22,8 @@ public abstract class FunctionNode extends Node { 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) { var name = this.name() != null ? this.name() : _name; @@ -30,6 +32,8 @@ public abstract class FunctionNode extends Node { .remove(LabelContext.CONTINUE_CTX); return new CompileResult(env, scope, params.params.size(), target -> { + compilePreBody(target); + if (params.params.size() > 0) { target.add(Instruction.loadArgs(true)); if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0)); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index f11542b..cc0441d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -28,6 +28,7 @@ import me.topchetoeu.jscript.compilation.control.WhileNode; import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.values.ArgumentsNode; 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.RegexNode; import me.topchetoeu.jscript.compilation.values.ThisNode; @@ -63,7 +64,7 @@ public final class JavaScript { "finally", "for", "do", "while", "switch", "case", "default", "new", "function", "var", "return", "throw", "typeof", "delete", "break", "continue", "debugger", "implements", "interface", "package", "private", - "protected", "public", "static", "arguments" + "protected", "public", "static", "arguments", "class" )); public static ParseRes parseParens(Source src, int i) { @@ -88,6 +89,7 @@ public final class JavaScript { return ParseRes.first(src, i, (s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j), (s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false), + (s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j), JavaScript::parseLiteral, StringNode::parse, RegexNode::parse, @@ -96,7 +98,7 @@ public final class JavaScript { ChangeNode::parsePrefixIncrease, OperationNode::parsePrefix, ArrayNode::parse, - FunctionArrowNode::parse, + (s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j), JavaScript::parseParens, CallNode::parseNew, 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."); ParseRes res = ParseRes.first(src, i + n, + ClassStatementNode::parse, VariableDeclareNode::parse, ReturnNode::parse, ThrowNode::parse, diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java new file mode 100644 index 0000000..cf4434e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java @@ -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 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); + } +}