Skip to content

Commit

Permalink
Implement inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
chidiwilliams committed Mar 4, 2022
1 parent d9f99b5 commit 2f850fe
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 11 deletions.
10 changes: 10 additions & 0 deletions ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ func (b SetExpr) Accept(visitor ExprVisitor) interface{} {
return visitor.VisitSetExpr(b)
}

type SuperExpr struct {
Keyword Token
Method Token
}

func (b SuperExpr) Accept(visitor ExprVisitor) interface{} {
return visitor.VisitSuperExpr(b)
}

type ThisExpr struct {
Keyword Token
}
Expand Down Expand Up @@ -133,6 +142,7 @@ type ExprVisitor interface {
VisitLiteralExpr(expr LiteralExpr) interface{}
VisitLogicalExpr(expr LogicalExpr) interface{}
VisitSetExpr(expr SetExpr) interface{}
VisitSuperExpr(expr SuperExpr) interface{}
VisitThisExpr(expr ThisExpr) interface{}
VisitTernaryExpr(expr TernaryExpr) interface{}
VisitUnaryExpr(expr UnaryExpr) interface{}
Expand Down
5 changes: 3 additions & 2 deletions ast/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ func (b BlockStmt) Accept(visitor StmtVisitor) interface{} {
}

type ClassStmt struct {
Name Token
Methods []FunctionStmt
Name Token
Superclass *VariableExpr
Methods []FunctionStmt
}

func (b ClassStmt) Accept(visitor StmtVisitor) interface{} {
Expand Down
5 changes: 5 additions & 0 deletions ast_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import (

type astPrinter struct{}

func (a astPrinter) VisitSuperExpr(expr ast.SuperExpr) interface{} {
// TODO implement me
panic("implement me")
}

func (a astPrinter) VisitThisExpr(expr ast.ThisExpr) interface{} {
return expr.Keyword.Lexeme
}
Expand Down
17 changes: 10 additions & 7 deletions class.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
)

type Class struct {
name string
methods map[string]function
name string
methods map[string]function
superclass *Class
}

// arity returns the arity of the class's constructor
Expand Down Expand Up @@ -41,7 +42,13 @@ func (c Class) Get(in *Interpreter, name ast.Token) (interface{}, error) {

func (c Class) findMethod(name string) (function, bool) {
method, ok := c.methods[name]
return method, ok
if ok {
return method, true
}
if c.superclass != nil {
return c.superclass.findMethod(name)
}
return function{}, false
}

type Instance interface {
Expand All @@ -54,10 +61,6 @@ type instance struct {
fields map[string]interface{}
}

func (i *instance) String() string {
return i.class.name + " instance"
}

// Get returns value of the field or method with the given name. If
// the field is a getter, it runs the method body and returns the result.
func (i *instance) Get(in *Interpreter, name ast.Token) (interface{}, error) {
Expand Down
3 changes: 2 additions & 1 deletion cmd/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func main() {
"Literal : Value interface{}",
"Logical : Left Expr, Operator Token, Right Expr",
"Set : Object Expr, Name Token, Value Expr",
"Super : Keyword Token, Method Token",
"This : Keyword Token",
"Ternary : Cond Expr, Left Expr, Right Expr",
"Unary : Operator Token, Right Expr",
Expand All @@ -27,7 +28,7 @@ func main() {

writeAst("Stmt", []string{
"Block : Statements []Stmt",
"Class : Name Token, Methods []FunctionStmt",
"Class : Name Token, Superclass *VariableExpr, Methods []FunctionStmt",
"Expression : Expr Expr",
"Function : Name Token, Params []Token, Body []Stmt",
"If : Condition Expr, ThenBranch Stmt, ElseBranch Stmt",
Expand Down
32 changes: 31 additions & 1 deletion interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,22 @@ func (in *Interpreter) VisitBlockStmt(stmt ast.BlockStmt) interface{} {
}

func (in *Interpreter) VisitClassStmt(stmt ast.ClassStmt) interface{} {
var superclass *Class
if stmt.Superclass != nil {
superclassValue, ok := in.evaluate(stmt.Superclass).(Class)
if !ok {
panic(runtimeError{token: stmt.Superclass.Name, msg: "Superclass must be a class."})
}
superclass = &superclassValue
}

in.environment.define(stmt.Name.Lexeme, nil)

if superclass != nil {
in.environment = &environment{enclosing: in.environment}
in.environment.define("super", superclass)
}

methods := make(map[string]function)
for _, method := range stmt.Methods {
fn := function{
Expand All @@ -88,7 +102,12 @@ func (in *Interpreter) VisitClassStmt(stmt ast.ClassStmt) interface{} {
methods[method.Name.Lexeme] = fn
}

class := Class{name: stmt.Name.Lexeme, methods: methods}
class := Class{name: stmt.Name.Lexeme, methods: methods, superclass: superclass}

if superclass != nil {
in.environment = in.environment.enclosing
}

err := in.environment.assign(stmt.Name, class)
if err != nil {
panic(err)
Expand Down Expand Up @@ -362,6 +381,17 @@ func (in *Interpreter) VisitSetExpr(expr ast.SetExpr) interface{} {
return nil
}

func (in *Interpreter) VisitSuperExpr(expr ast.SuperExpr) interface{} {
distance := in.locals[expr]
superclass := in.environment.getAt(distance, "super").(*Class)
object := in.environment.getAt(distance-1, "this").(*instance)
method, ok := superclass.findMethod(expr.Method.Lexeme)
if !ok {
panic(runtimeError{token: expr.Method, msg: fmt.Sprintf("Undefined property '%s'.", expr.Method.Lexeme)})
}
return method.bind(object)
}

func (in *Interpreter) VisitThisExpr(expr ast.ThisExpr) interface{} {
val, err := in.lookupVariable(expr.Keyword, expr)
if err != nil {
Expand Down
29 changes: 29 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,35 @@ print Math.add(1, 2);`, "3\n", ""},
var circle = Circle(7);
print circle.area;`, "153.938039997\n", ""},
{"inheritance", `class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {}
BostonCream.cook();`, "Fry until golden brown.\n", ""},
{"calling super", `class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {
cook() {
super.cook();
print "Pipe full of custard and coat with chocolate.";
}
}
BostonCream().cook();`, "Fry until golden brown.\nPipe full of custard and coat with chocolate.\n", ""},
{"calling super outside class", `super.hello();`, "", "[line 0] Error at 'super': Can't use 'super' outside of a class.\n"},
{"calling super in class with no superclass", `class Doughnut {
cook() {
super.cook();
}
}`, "", "[line 2] Error at 'super': Can't use 'super' in a class with no superclass.\n"},
}

for _, tt := range tests {
Expand Down
28 changes: 28 additions & 0 deletions resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type classType int
const (
classTypeNone classType = iota
classTypeClass
classTypeSubClass
)

type scopeVar struct {
Expand Down Expand Up @@ -225,6 +226,17 @@ func (r *Resolver) VisitSetExpr(expr ast.SetExpr) interface{} {
return nil
}

func (r *Resolver) VisitSuperExpr(expr ast.SuperExpr) interface{} {
if r.currentClass == classTypeNone {
reportTokenErr(r.stdErr, expr.Keyword, "Can't use 'super' outside of a class.")
} else if r.currentClass != classTypeSubClass {
reportTokenErr(r.stdErr, expr.Keyword, "Can't use 'super' in a class with no superclass.")
}

r.resolveLocal(expr, expr.Keyword)
return nil
}

func (r *Resolver) VisitThisExpr(expr ast.ThisExpr) interface{} {
if r.currentClass == classTypeNone {
reportTokenErr(r.stdErr, expr.Keyword, "Can't use 'this' outside of a class.")
Expand Down Expand Up @@ -274,6 +286,22 @@ func (r *Resolver) VisitClassStmt(stmt ast.ClassStmt) interface{} {
r.declare(stmt.Name)
r.define(stmt.Name)

if stmt.Superclass != nil && stmt.Name.Lexeme == stmt.Superclass.Name.Lexeme {
reportTokenErr(r.stdErr, stmt.Superclass.Name, "A class can't inherit from itself.")
}

if stmt.Superclass != nil {
r.currentClass = classTypeSubClass
r.resolveExpr(stmt.Superclass)
}

if stmt.Superclass != nil {
r.beginScope()
defer func() { r.endScope() }()

r.scopes.peek().set("super")
}

r.beginScope()

r.scopes.peek().set("this")
Expand Down

0 comments on commit 2f850fe

Please sign in to comment.