Skip to content

Commit

Permalink
Merge pull request #2 from hutcho66/classes
Browse files Browse the repository at this point in the history
Classes
  • Loading branch information
hutcho66 committed Nov 3, 2023
2 parents 9c5d649 + c7fa26f commit 34da7a1
Show file tree
Hide file tree
Showing 15 changed files with 756 additions and 63 deletions.
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,65 @@ Closures are fully supported in both named and lambda functions
11
```

### Classes

glox classes are defined using the `class` keyword. Classes can be constructed using an optional `init` method.
```
> class Foo {
init(value) {
this.bar = value
}
> var foo = Foo("baz")
> foo.bar()
baz
```

The `static` keyword can be used to define a class method.
```
> class Math {
static square(r) {
return r * r
}
}
> Math.square(5)
25
```

Single inheritance is supported
```
> class A {
foo() {
return "bar"
}
}
> class B > A {
foo() {
return "foo" + super.foo()
}
}
> B().foo()
"foobar"
```

Getters (keyword `get`) and setters (keyword `set`) are supported.
```
> class Foo {
set name(first) {
this.fullName = first + " Smith"
}
get formalGreeting {
return "Good morning, " + this.fullName
}
}
> var foo = Foo()
> foo.name = "James" // using a setter
> foo.formalGreeting // using a getter
"Good morning, James Smith"
```



## Usage

```bash
Expand Down
Binary file modified glox
Binary file not shown.
48 changes: 48 additions & 0 deletions src/pkg/ast/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ type ExpressionVisitor interface {
VisitMapExpression(*MapExpression) any
VisitIndexExpression(*IndexExpression) any
VisitIndexedAssignmentExpression(*IndexedAssignmentExpression) any
VisitGetExpression(*GetExpression) any
VisitSetExpression(*SetExpression) any
VisitThisExpression(*ThisExpression) any
VisitSuperGetExpression(*SuperGetExpression) any
VisitSuperSetExpression(*SuperSetExpression) any
}

type BinaryExpression struct {
Expand Down Expand Up @@ -159,3 +164,46 @@ type IndexExpression struct {
func (e *IndexExpression) Accept(v ExpressionVisitor) any {
return v.VisitIndexExpression(e)
}

type GetExpression struct {
Object Expression
Name *token.Token
}

func (e *GetExpression) Accept(v ExpressionVisitor) any {
return v.VisitGetExpression(e)
}

type SetExpression struct {
Object, Value Expression
Name *token.Token
}

func (e *SetExpression) Accept(v ExpressionVisitor) any {
return v.VisitSetExpression(e)
}

type ThisExpression struct {
Keyword *token.Token
}

func (e *ThisExpression) Accept(v ExpressionVisitor) any {
return v.VisitThisExpression(e)
}

type SuperGetExpression struct {
Keyword, Method *token.Token
}

func (e *SuperGetExpression) Accept(v ExpressionVisitor) any {
return v.VisitSuperGetExpression(e)
}

type SuperSetExpression struct {
Keyword, Method *token.Token
Value Expression
}

func (e *SuperSetExpression) Accept(v ExpressionVisitor) any {
return v.VisitSuperSetExpression(e)
}
22 changes: 22 additions & 0 deletions src/pkg/ast/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type StatementVisitor interface {
VisitReturnStatement(*ReturnStatement)
VisitBreakStatement(*BreakStatement)
VisitContinueStatement(*ContinueStatement)
VisitClassStatement(*ClassStatement)
}

type ExpressionStatement struct {
Expand Down Expand Up @@ -75,10 +76,21 @@ func (s *ForEachStatement) Accept(v StatementVisitor) {
v.VisitForEachStatement(s)
}

type MethodType int

const (
NOT_METHOD = iota
NORMAL_METHOD
STATIC_METHOD
GETTER_METHOD
SETTER_METHOD
)

type FunctionStatement struct {
Name *token.Token
Params []*token.Token
Body []Statement
Kind MethodType
}

func (s *FunctionStatement) Accept(v StatementVisitor) {
Expand Down Expand Up @@ -109,3 +121,13 @@ type ContinueStatement struct {
func (s *ContinueStatement) Accept(v StatementVisitor) {
v.VisitContinueStatement(s)
}

type ClassStatement struct {
Name *token.Token
Methods []*FunctionStatement
Superclass *VariableExpression
}

func (s *ClassStatement) Accept(v StatementVisitor) {
v.VisitClassStatement(s)
}
41 changes: 0 additions & 41 deletions src/pkg/interpreter/callable.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,6 @@
package interpreter

import (
"github.com/hutcho66/glox/src/pkg/ast"
)

type LoxCallable interface {
Arity() int
Call(interpreter *Interpreter, arguments []any) (any, error)
}

type LoxFunction struct {
declaration *ast.FunctionStatement
closure *Environment
}

func (f *LoxFunction) Call(interpreter *Interpreter, arguments []any) (returnValue any, err error) {
enclosingEnvironment := interpreter.environment
environment := NewEnclosingEnvironment(f.closure)

defer func() {
if val := recover(); val != nil {
rv, ok := val.(*LoxControl)
if !ok || rv.controlType != RETURN {
// repanic
panic(val)
}

returnValue = rv.value
interpreter.environment = enclosingEnvironment
return
}
}()

for i, param := range f.declaration.Params {
environment.define(param.Lexeme, arguments[i])
}

interpreter.executeBlock(f.declaration.Body, environment)

// if we've reached here, there was no return statement, so implicitly return nil
return nil, nil
}

func (f LoxFunction) Arity() int {
return len(f.declaration.Params)
}
61 changes: 61 additions & 0 deletions src/pkg/interpreter/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package interpreter

import (
"github.com/hutcho66/glox/src/pkg/ast"
"github.com/hutcho66/glox/src/pkg/lox_error"
"github.com/hutcho66/glox/src/pkg/token"
)

type LoxClass struct {
Name string
Methods map[string]*LoxFunction
Super *LoxClass
}

func (c LoxClass) Arity() int {
initializer := c.findMethod("init")
if initializer == nil {
return 0
}

return initializer.Arity()
}

func (c *LoxClass) Call(interpreter *Interpreter, arguments []any) (any, error) {
instance := NewLoxInstance(c)

initializer := c.findMethod("init")
if initializer != nil {
initializer.bind(instance).Call(interpreter, arguments)
}

return instance, nil
}

func (c *LoxClass) findMethod(name string) *LoxFunction {
if method, ok := c.Methods[name]; ok {
return method
}

if c.Super != nil {
return c.Super.findMethod(name)
}

return nil
}

func (c *LoxClass) get(name *token.Token) any {

method := c.findMethod(name.Lexeme)

if method == nil {
panic(lox_error.RuntimeError(name, "Undefined property '"+name.Lexeme+"'."))
}

if method.declaration.Kind != ast.STATIC_METHOD {
panic(lox_error.RuntimeError(name, "Cannot call non-static method '"+name.Lexeme+"' directly on class."))
}

return method.bind(c)

}
55 changes: 55 additions & 0 deletions src/pkg/interpreter/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package interpreter

import "github.com/hutcho66/glox/src/pkg/ast"

type LoxFunction struct {
declaration *ast.FunctionStatement
closure *Environment
isInitializer bool
}

func (f *LoxFunction) Call(interpreter *Interpreter, arguments []any) (returnValue any, err error) {
enclosingEnvironment := interpreter.environment
environment := NewEnclosingEnvironment(f.closure)

defer func() {
if val := recover(); val != nil {
rv, ok := val.(*LoxControl)
if !ok || rv.controlType != RETURN {
// repanic
panic(val)
}

if f.isInitializer {
returnValue = f.closure.getAt(0, "this")
} else {
returnValue = rv.value
}

interpreter.environment = enclosingEnvironment
return
}
}()

for i, param := range f.declaration.Params {
environment.define(param.Lexeme, arguments[i])
}

interpreter.executeBlock(f.declaration.Body, environment)

if f.isInitializer {
return f.closure.getAt(0, "this"), nil
}

return nil, nil
}

func (f LoxFunction) Arity() int {
return len(f.declaration.Params)
}

func (f *LoxFunction) bind(instance LoxObject) *LoxFunction {
environment := NewEnclosingEnvironment(f.closure)
environment.define("this", instance)
return &LoxFunction{f.declaration, environment, f.isInitializer}
}
35 changes: 35 additions & 0 deletions src/pkg/interpreter/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package interpreter

import (
"github.com/hutcho66/glox/src/pkg/lox_error"
"github.com/hutcho66/glox/src/pkg/token"
)

type LoxInstance struct {
Class *LoxClass
Fields map[string]any
}

func NewLoxInstance(class *LoxClass) *LoxInstance {
return &LoxInstance{
Class: class,
Fields: make(map[string]any),
}
}

func (i *LoxInstance) get(name *token.Token) any {
if field, ok := i.Fields[name.Lexeme]; ok {
return field
}

method := i.Class.findMethod(name.Lexeme)
if method != nil {
return method.bind(i)
}

panic(lox_error.RuntimeError(name, "Undefined property '"+name.Lexeme+"'."))
}

func (i *LoxInstance) set(name *token.Token, value any) {
i.Fields[name.Lexeme] = value
}
Loading

0 comments on commit 34da7a1

Please sign in to comment.