Skip to content

Commit

Permalink
Make 'super' static instead of dynamic
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Apr 8, 2024
1 parent 31b081a commit b55707e
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,45 +1,23 @@
package com.endoflineblog.truffle.part_13.nodes.exprs.objects;

import com.endoflineblog.truffle.part_13.exceptions.EasyScriptException;
import com.endoflineblog.truffle.part_13.nodes.exprs.EasyScriptExprNode;
import com.endoflineblog.truffle.part_13.runtime.ClassPrototypeChainObject;
import com.endoflineblog.truffle.part_13.runtime.ClassPrototypeObject;
import com.endoflineblog.truffle.part_13.runtime.JavaScriptObject;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;

/**
* The Node implementing the 'super' expression.
*/
public final class SuperExprNode extends EasyScriptExprNode {
static abstract class ReadParentPrototypeNode extends Node {
abstract Object executeReadParentPrototype(Object object);

@Specialization
protected Object parentPrototypeOfJavaScriptObject(JavaScriptObject javaScriptObject) {
ClassPrototypeObject classPrototypeObject = javaScriptObject.classPrototypeObject;
if (classPrototypeObject instanceof ClassPrototypeChainObject) {
return ((ClassPrototypeChainObject) classPrototypeObject).superClassPrototype;
} else {
throw new EasyScriptException("Class '" + classPrototypeObject.className +
"' does not have a superclass that can be accessed with 'super'");
}
}

@Specialization
protected void parentPrototypeOfNonJavaScriptObject(Object object) {
throw new EasyScriptException("Cannot access prototype with 'super' of: " + object);
}
}
private final ClassPrototypeChainObject classPrototype;

@SuppressWarnings("FieldMayBeFinal")
@Child
private ThisExprNode thisExprNode = new ThisExprNode();
private ThisExprNode thisExprNode;

@SuppressWarnings("FieldMayBeFinal")
@Child
private ReadParentPrototypeNode readParentPrototypeNode = SuperExprNodeFactory.ReadParentPrototypeNodeGen.create();
public SuperExprNode(ClassPrototypeChainObject classPrototype) {
this.classPrototype = classPrototype;
this.thisExprNode = new ThisExprNode();
}

@Override
public Object executeGeneric(VirtualFrame frame) {
Expand All @@ -52,6 +30,6 @@ public Object executeGeneric(VirtualFrame frame) {
public Object readParentPrototype(Object thisValue) {
// this method is called from the property access Nodes
// to find the parent prototype
return this.readParentPrototypeNode.executeReadParentPrototype(thisValue);
return this.classPrototype.superClassPrototype;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ private static final class ClassPrototype extends FrameMember {
*/
private int localVariablesCounter;

/**
* The prototype of the class that we are currently parsing.
* Needed to make {@code super} static, instead of dynamic,
* like {@code this} is.
*/
private ClassPrototypeObject currentClassPrototype;

private EasyScriptTruffleParser(Shape objectShape) {
this.objectShape = objectShape;
this.state = ParserState.TOP_LEVEL;
Expand All @@ -158,6 +165,7 @@ private EasyScriptTruffleParser(Shape objectShape) {
// we add a global scope, in which we store the class prototypes
this.localScopes.push(new HashMap<>());
this.localVariablesCounter = 0;
this.currentClassPrototype = null;
}

private List<EasyScriptStmtNode> parseStmtsList(List<EasyScriptParser.StmtContext> stmts) {
Expand Down Expand Up @@ -353,11 +361,15 @@ private EasyScriptStmtNode parseClassDeclStmt(EasyScriptParser.ClassDeclStmtCont
}
}
this.localScopes.get(0).put(className, new ClassPrototype(classPrototype));
this.currentClassPrototype = classPrototype;

List<FuncDeclStmtNode> classMethods = new ArrayList<>();
for (var classMember : classDeclStmt.class_member()) {
classMethods.add(this.parseSubroutineDecl(classMember.subroutine_decl(),
new DynamicObjectReferenceExprNode(classPrototype)));
}

this.currentClassPrototype = null;
return GlobalVarDeclStmtNodeGen.create(
GlobalScopeObjectExprNodeGen.create(),
new ClassDeclExprNode(classMethods, classPrototype),
Expand Down Expand Up @@ -533,7 +545,7 @@ private EasyScriptExprNode parseExpr6(EasyScriptParser.Expr6Context expr6) {
} else if (expr6 instanceof EasyScriptParser.ThisExpr6Context) {
return new ThisExprNode();
} else if (expr6 instanceof EasyScriptParser.SuperExpr6Context) {
return new SuperExprNode();
return this.parseSuperExpr((EasyScriptParser.SuperExpr6Context) expr6);
} else if (expr6 instanceof EasyScriptParser.ReferenceExpr6Context) {
return this.parseReference(((EasyScriptParser.ReferenceExpr6Context) expr6).ID().getText());
} else if (expr6 instanceof EasyScriptParser.ArrayLiteralExpr6Context) {
Expand Down Expand Up @@ -569,6 +581,17 @@ private EasyScriptExprNode parseLiteralExpr(EasyScriptParser.LiteralExpr6Context
return new UndefinedLiteralExprNode();
}

private EasyScriptExprNode parseSuperExpr(EasyScriptParser.SuperExpr6Context superExpr) {
if (this.currentClassPrototype == null) {
throw new EasyScriptException("'super' is only available in class declarations");
}
if (!(this.currentClassPrototype instanceof ClassPrototypeChainObject)) {
throw new EasyScriptException("Cannot use 'super' in class '" + this.currentClassPrototype.className +
"' without a superclass");
}
return new SuperExprNode((ClassPrototypeChainObject) this.currentClassPrototype);
}

private EasyScriptExprNode parseReference(String variableId) {
FrameMember frameMember = this.findFrameMember(variableId);
if (frameMember == null || frameMember instanceof ClassPrototype) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ void super_reads_property_of_parent_prototype() {
assertEquals("Base_Derived", result.asString());
}

@Test
void super_is_static_not_dynamic() {
Value result = this.context.eval("ezs", "" +
"class Base { " +
" m() { " +
" return 'Base'; " +
" } " +
"} " +
"class Middle extends Base { " +
" m() { " +
" return 'Middle'; " +
" } " +
" callSuperM() { " +
" return super.m(); " +
" } " +
"} " +
"class Derived extends Middle { " +
" m() {" +
" return 'Derived'; " +
" } " +
"} " +
"const obj = new Derived(); " +
"obj.callSuperM();"
);

assertEquals("Base", result.asString());
}

@Test
void extending_non_existent_class_is_an_error() {
try {
Expand Down

0 comments on commit b55707e

Please sign in to comment.