Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class support #11

Merged
merged 2 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/ast/property.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ast

import "github.com/sevenreup/duwa/src/token"

type PropertyExpression struct {
Token token.Token
Left Expression
Property Expression
}

func (pe *PropertyExpression) expressionNode() {}

func (pe *PropertyExpression) TokenLiteral() string { return pe.Token.Literal }

func (pe *PropertyExpression) String() string {
return pe.Left.String() + "." + pe.Property.String()
}
2 changes: 2 additions & 0 deletions src/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return function
case *ast.CallExpression:
return evaluateFunctionCall(node, env)
case *ast.PropertyExpression:
return evaluateProperty(node, env)
case *ast.ArrayLiteral:
elements := evalExpressions(node.Elements, env)
if len(elements) == 1 && isError(elements[0]) {
Expand Down
16 changes: 15 additions & 1 deletion src/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,21 @@ func TestClasses(t *testing.T) {
Munthu maliko = Munthu();
maliko.zaka();
`,
5,
10,
},
{
`
kalasi Munthu {
numbala zaka = 2;
ndondomeko yikaZaka() {
zaka = 10;
}
}
Munthu maliko = Munthu();
maliko.yikaZaka();
maliko.zaka;
`,
10,
},
}

Expand Down
5 changes: 5 additions & 0 deletions src/evaluator/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ func applyFunction(tok token.Token, fn object.Object, args []object.Object, env
extendedEnv := extendFunctionEnv(fn, args)
evaluated := Eval(fn.Body, extendedEnv)
return unwrapReturnValue(evaluated)
case *object.Class:
if tok.Literal != fn.Name.TokenLiteral() {
return newError("class name mismatch: expected %s, got %s", fn.Name.TokenLiteral(), tok.Literal)
}
return fn.CreateInstance(tok.Literal, args)
default:
return newError("not a function: %s", fn.Type())
}
Expand Down
41 changes: 38 additions & 3 deletions src/evaluator/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package evaluator
import (
"github.com/sevenreup/duwa/src/ast"
"github.com/sevenreup/duwa/src/object"
"github.com/sevenreup/duwa/src/values"
)

func evaluateMethod(node *ast.MethodExpression, env *object.Environment) object.Object {
Expand All @@ -24,15 +25,49 @@ func evaluateMethod(node *ast.MethodExpression, env *object.Environment) object.
return result
}

switch left.(type) {
switch receiver := left.(type) {
case *object.LibraryModule:
method := node.Method.(*ast.Identifier)
module := left.(*object.LibraryModule)

if function, ok := module.Methods[method.Value]; ok {
if function, ok := receiver.Methods[method.Value]; ok {
return applyFunction(node.Token, function, arguments, env)
}
case *object.Instance:
method := node.Method.(*ast.Identifier)
evaluated := evaluateInstanceMethod(node, receiver, method.Value, arguments)

if isError(evaluated) {
return evaluated
}

return unwrapReturn(evaluated)
}

return result
}

func evaluateInstanceMethod(node *ast.MethodExpression, receiverInstance *object.Instance, name string, arguments []object.Object) object.Object {
method, ok := receiverInstance.Class.Env.Get(name)

if !ok {
return newError("%d:%d:%s: runtime error: undefined method %s for class %s", node.Token.Pos.Line, node.Token.Pos.Column, node.Token.File, name, receiverInstance.Class.Name.Value)
}

if method, ok := method.(*object.Function); ok {
extendedEnv := extendFunctionEnv(method, arguments)
return Eval(method.Body, extendedEnv)
} else {
return newError("not a method: %s", name)
}
}

func unwrapReturn(obj object.Object) object.Object {
switch value := obj.(type) {
case *object.Error:
return obj
case *object.ReturnValue:
return value.Value
}

return values.NULL
}
40 changes: 40 additions & 0 deletions src/evaluator/property.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package evaluator

import (
"github.com/sevenreup/duwa/src/ast"
"github.com/sevenreup/duwa/src/object"
"github.com/sevenreup/duwa/src/values"
)

func evaluateProperty(node *ast.PropertyExpression, env *object.Environment) object.Object {
left := Eval(node.Left, env)

if isError(left) {
return left
}

switch receiver := left.(type) {
case *object.Instance:
return evaluateInstanceProperty(node, receiver)
}

return nil
}

func evaluateInstanceProperty(node *ast.PropertyExpression, instance *object.Instance) object.Object {
property := node.Property.(*ast.Identifier)

if instance.Env.Has(property.Value) {
val, _ := instance.Env.Get(property.Value)
return val
}

if instance.Class.Env.Has(property.Value) {
val, _ := instance.Class.Env.Get(property.Value)
return val
}

// If the property is not found in the instance or the class, return NULL
instance.Env.Set(property.Value, values.NULL)
return values.NULL
}
22 changes: 11 additions & 11 deletions src/object/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ func (c *Class) Inspect() string {
return "class " + c.Name.String()
}

func (i *Class) Method(method string, args []Object) (Object, bool) {
switch method {
case "new":
instance := &Instance{Class: i, Env: NewEnclosedEnvironment(i.Env)}
func (c *Class) CreateInstance(method string, args []Object) Object {
instance := &Instance{Class: c, Env: c.Env}

if ok := i.Env.Has("constructor"); ok {
result := instance.Call("constructor", args)
if ok := c.Env.Has("constructor"); ok {
result := instance.Call("constructor", args)

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

return instance, true
}

return instance
}

func (i *Class) Method(method string, args []Object) (Object, bool) {
return nil, false
}
10 changes: 5 additions & 5 deletions src/object/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ func (e *Environment) Get(name string) (Object, bool) {
}
func (e *Environment) Set(name string, val Object) Object {
// TODO: Make sure we dont accidentally mutate data that is not in the current scope
// _, ok := e.store[name]
// if !ok && e.outer != nil {
// e.outer.Set(name, val)
// return val
// }
_, ok := e.store[name]
if !ok && e.outer != nil {
e.outer.Set(name, val)
return val
}
e.store[name] = val
return val
}
Expand Down
2 changes: 1 addition & 1 deletion src/parser/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
exp := &ast.CallExpression{Token: p.curToken, Function: function}
exp := &ast.CallExpression{Token: p.previousToken, Function: function}
exp.Arguments = p.parseCallArguments()
return exp
}
Expand Down
5 changes: 2 additions & 3 deletions src/parser/dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ func (parser *Parser) dotExpression(left ast.Expression) ast.Expression {
return expression
}

// TODO: Add logic for handling properties "class.me"

return nil
// Property
return &ast.PropertyExpression{Token: parser.curToken, Left: left, Property: parser.parseExpression(currentPrecedence)}
}
30 changes: 30 additions & 0 deletions src/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1428,3 +1428,33 @@ func TestInstanceCreation(t *testing.T) {
}
}
}

func TestClassPropertyAccess(t *testing.T) {
input := `Munthu maria = Munthu(); maria.zaka;`
l := lexer.New([]byte(input))
p := New(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 2 {
t.Fatalf("program.Statements does not contain 2 statements. got=%d", len(program.Statements))
}
stmt := program.Statements[1]

expression, ok := stmt.(*ast.ExpressionStatement)
if !ok {
t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", stmt)
}
propertyExp, ok := expression.Expression.(*ast.PropertyExpression)

if !ok {
t.Fatalf("stmt.Expression is not ast.PropertyExpression. got=%T", expression.Expression)
}

if !testIdentifier(t, propertyExp.Left, "maria") {
return
}

if !testIdentifier(t, propertyExp.Property, "zaka") {
return
}
}
Loading