Skip to content

Commit

Permalink
Properly evaluate instance properties (#122)
Browse files Browse the repository at this point in the history
* work on error reporting

* build in current directory

* improve error reporting in class instances

* properly evaluate instance properties
  • Loading branch information
kaidesu authored Oct 18, 2023
1 parent d514bcf commit ba16400
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 22 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ run:
build: build-mac build-linux build-windows

build-mac: clean
GOOS=darwin go build -trimpath -o ../dist/mac/ghost cmd/*.go
GOOS=darwin go build -trimpath -o ./dist/mac/ghost cmd/*.go

build-linux: clean
GOOS=linux go build -trimpath -o ../dist/linux/ghost cmd/*.go
GOOS=linux go build -trimpath -o ./dist/linux/ghost cmd/*.go

build-windows: clean
GOOS=windows go build -trimpath -o ../dist/windows/ghost.exe cmd/*.go
GOOS=windows go build -trimpath -o ./dist/windows/ghost.exe cmd/*.go

test:
go test -v -race -timeout 5s ./... | sed ''/PASS/s//$$(printf "\033[32mPASS\033[0m")/'' | sed ''/FAIL/s//$$(printf "\033[31mFAIL\033[0m")/''
Expand Down
35 changes: 32 additions & 3 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package evaluator
import (
"testing"

"ghostlang.org/x/ghost/library/modules"
"ghostlang.org/x/ghost/object"
"ghostlang.org/x/ghost/parser"
"ghostlang.org/x/ghost/scanner"
Expand Down Expand Up @@ -198,17 +199,45 @@ func TestWhileExpressions(t *testing.T) {
}
}

func TestClassProperties(t *testing.T) {
input := `
class Circle {
function constructor(area) {
this.area = area
}
function area() {
return math.pi * this.area * this.area
}
}
test = Circle.new(10)
return test.area()
`

result := evaluate(input)

isNumberObject(t, result, 314)
}

// =============================================================================
// Helper functions

func evaluate(input string) object.Object {
scanner := scanner.New(input, "test.ghost")
parser := parser.New(scanner)
program := parser.Parse()
scope := &object.Scope{
Environment: object.NewEnvironment(),
}

evaluatorInstance := Evaluate

object.RegisterEvaluator(evaluatorInstance)
modules.RegisterEvaluator(evaluatorInstance)

scanner := scanner.New(input, "test.ghost")
parser := parser.New(scanner)
program := parser.Parse()

result := Evaluate(program, scope)

return result
Expand Down
10 changes: 8 additions & 2 deletions evaluator/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ func evaluateMethod(node *ast.Method, scope *object.Scope) object.Object {
return arguments[0]
}

if result, ok := left.Method(node.Method.(*ast.Identifier).Value, arguments); ok {
result, _ := left.Method(node.Method.(*ast.Identifier).Value, arguments)

if isError(result) {
return result
}

Expand All @@ -27,6 +29,10 @@ func evaluateMethod(node *ast.Method, scope *object.Scope) object.Object {
method := node.Method.(*ast.Identifier)
evaluated := evaluateInstanceMethod(node, receiver, method.Value, arguments)

if isError(evaluated) {
return evaluated
}

return unwrapReturn(evaluated)
case *object.LibraryModule:
method := node.Method.(*ast.Identifier)
Expand All @@ -37,7 +43,7 @@ func evaluateMethod(node *ast.Method, scope *object.Scope) object.Object {
}
}

return newError("%d:%d:%s: runtime error: unknown method: %s.%s", node.Token.Line, node.Token.Column, node.Token.File, left.String(), node.Method.(*ast.Identifier).Value)
return result
}

func evaluateInstanceMethod(node *ast.Method, receiver *object.Instance, name string, arguments []object.Object) object.Object {
Expand Down
12 changes: 9 additions & 3 deletions evaluator/property.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ func evaluateProperty(node *ast.Property, scope *object.Scope) object.Object {
property := node.Property.(*ast.Identifier)
instance := left.(*object.Instance)

if value, ok := instance.Environment.Get(property.Value); ok {
return value
if !instance.Environment.Has(property.Value) {
instance.Environment.Set(property.Value, value.NULL)
}

val, _ := instance.Environment.Get(property.Value)

return val
case *object.LibraryModule:
property := node.Property.(*ast.Identifier)
module := left.(*object.LibraryModule)

if function, ok := module.Properties[property.Value]; ok {
return unwrapCall(node.Token, function, nil, scope)
}

return newError("%d:%d:%s: runtime error: unknown property: %s.%s", node.Token.Line, node.Token.Column, node.Token.File, module.Name, property.Value)
case *object.Map:
property := &object.String{Value: node.Property.(*ast.Identifier).Value}
mapObj := left.(*object.Map)
Expand All @@ -41,5 +47,5 @@ func evaluateProperty(node *ast.Property, scope *object.Scope) object.Object {
return pair.Value
}

return newError("%d:%d:%s: runtime error: unknown property: %s.%s", node.Token.Line, node.Token.Column, node.Token.File, left.String(), node.Property.(*ast.Number).Value)
return nil
}
10 changes: 9 additions & 1 deletion evaluator/this.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package evaluator

import (
"fmt"

"ghostlang.org/x/ghost/ast"
"ghostlang.org/x/ghost/object"
)
Expand All @@ -10,5 +12,11 @@ func evaluateThis(node *ast.This, scope *object.Scope) object.Object {
return scope.Self
}

return object.NewError("%d:%d:%s: runtime error: cannot call 'this' outside of scope", node.Token.Line, node.Token.Column, node.Token.File)
pairs := make(map[object.MapKey]object.MapPair)

fmt.Printf("self: %v\n", scope.Self)

return &object.Map{Pairs: pairs}

// return object.NewError("%d:%d:%s: runtime error: cannot call 'this' outside of scope", node.Token.Line, node.Token.Column, node.Token.File)
}
14 changes: 9 additions & 5 deletions ghost/ghost.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,14 @@ func (ghost *Ghost) Execute() object.Object {

if len(parser.Errors()) != 0 {
logParseErrors(parser.Errors())
return nil

return object.NewError(parser.Errors()[0])
}

result := evaluator.Evaluate(program, ghost.Scope)

if err, ok := result.(*object.Error); ok {
log.Error(err.Message)

return nil
if object.IsError(result) {
log.Error(result.(*object.Error).Message)
}

return result
Expand All @@ -92,6 +91,11 @@ func RegisterModule(name string, methods map[string]*object.LibraryFunction, pro
library.RegisterModule(name, methods, properties)
}

// Create a new function called "Call" that will call the passed function with the (optional) passed arguments.
func (ghost *Ghost) Call(function string, args []object.Object) object.Object {
return ghost.Scope.Environment.Call(function, args, nil)
}

func (ghost *Ghost) registerEvaluator() {
evaluatorInstance := evaluator.Evaluate

Expand Down
10 changes: 8 additions & 2 deletions object/class.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package object

import "ghostlang.org/x/ghost/ast"
import (
"ghostlang.org/x/ghost/ast"
)

const CLASS = "CLASS"

Expand Down Expand Up @@ -29,7 +31,11 @@ func (class *Class) Method(method string, args []Object) (Object, bool) {
instance := &Instance{Class: class, Environment: NewEnclosedEnvironment(class.Environment)}

if ok := instance.Environment.Has("constructor"); ok {
instance.Call("constructor", args, class.Name.Token)
result := instance.Call("constructor", args, class.Name.Token)

if result != nil && result.Type() == ERROR {
return result, false
}
}

return instance, true
Expand Down
11 changes: 11 additions & 0 deletions object/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,14 @@ func (environment *Environment) GetDirectory() string {

return directory
}

// create a new function "Call" that can be used to call a function within the environment.
func (environment *Environment) Call(function string, args []Object, writer io.Writer) Object {
if object, ok := environment.Get(function); ok {
if function, ok := object.(*Function); ok {
return function.Evaluate(args, writer)
}
}

return NewError("function not found: %s", function)
}
6 changes: 3 additions & 3 deletions object/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ func (instance *Instance) Method(method string, args []Object) (Object, bool) {
func (instance *Instance) Call(name string, arguments []Object, tok token.Token) Object {
if function, ok := instance.Environment.Get(name); ok {
if method, ok := function.(*Function); ok {
functionEnvironment := createMethodEnvironment(method, arguments)
functionScope := &Scope{Self: instance, Environment: functionEnvironment}
methodEnvironment := createMethodEnvironment(method, arguments)
methodScope := &Scope{Self: instance, Environment: methodEnvironment}

return evaluator(method.Body, functionScope)
return evaluator(method.Body, methodScope)
}
}

Expand Down

0 comments on commit ba16400

Please sign in to comment.