Skip to content

Commit 6f451da

Browse files
authored
Feature/class traits (#130)
* add trait token * add trait AST * parse traits * evaluate traits * add use token * working traits ✨ * evaluate properties from traits
1 parent 7f7d98c commit 6f451da

File tree

19 files changed

+283
-22
lines changed

19 files changed

+283
-22
lines changed

ast/trait.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
import "ghostlang.org/x/ghost/token"
4+
5+
type Trait struct {
6+
ExpressionNode
7+
Token token.Token
8+
Name *Identifier
9+
Body *Block
10+
}

ast/use.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ast
2+
3+
import "ghostlang.org/x/ghost/token"
4+
5+
type Use struct {
6+
ExpressionNode
7+
Token token.Token
8+
Traits []*Identifier
9+
}

evaluator/class.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ func evaluateClass(node *ast.Class, scope *object.Scope) object.Object {
3434
classEnvironment := object.NewEnclosedEnvironment(scope.Environment)
3535
classScope := &object.Scope{Environment: classEnvironment, Self: class}
3636

37-
Evaluate(node.Body, classScope)
37+
result := Evaluate(node.Body, classScope)
38+
39+
if isError(result) {
40+
return result
41+
}
3842

3943
scope.Environment.Set(node.Name.Value, class)
4044

evaluator/evaluator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ func Evaluate(node ast.Node, scope *object.Scope) object.Object {
7676
return evaluateSwitch(node, scope)
7777
case *ast.Ternary:
7878
return evaluateTernary(node, scope)
79+
case *ast.Trait:
80+
return evaluateTrait(node, scope)
7981
case *ast.This:
8082
return evaluateThis(node, scope)
83+
case *ast.Use:
84+
return evaluateUse(node, scope)
8185
case *ast.While:
8286
return evaluateWhile(node, scope)
8387
}

evaluator/method.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,36 @@ func evaluateInstanceMethod(node *ast.Method, receiver *object.Instance, name st
6060
class := receiver.Class
6161
method, ok := receiver.Class.Environment.Get(name)
6262

63+
// If we dont have a method, loop through the super classes and check them.
64+
// Then check the traits.
6365
if !ok {
6466
for class != nil {
6567
method, ok = class.Environment.Get(name)
6668

6769
if !ok {
6870
class = class.Super
69-
70-
if class == nil {
71-
return object.NewError("%d:%d:%s: runtime error: undefined method %s for class %s", node.Token.Line, node.Token.Column, node.Token.File, name, receiver.Class.Name.Value)
72-
}
7371
} else {
7472
class = nil
7573
}
7674
}
7775
}
7876

77+
// if we dont have a method, check for a trait
78+
if method == nil {
79+
for _, trait := range receiver.Class.Traits {
80+
method, ok = trait.Environment.Get(name)
81+
82+
if !ok {
83+
return object.NewError("%d:%d:%s: runtime error: undefined method %s for class %s", node.Token.Line, node.Token.Column, node.Token.File, name, receiver.Class.Name.Value)
84+
}
85+
}
86+
}
87+
88+
// if we still dont have a method, return an error
89+
if method == nil {
90+
return object.NewError("%d:%d:%s: runtime error: undefined method %s for class %s", node.Token.Line, node.Token.Column, node.Token.File, name, receiver.Class.Name.Value)
91+
}
92+
7993
switch method := method.(type) {
8094
case *object.Function:
8195
env := createFunctionEnvironment(method, arguments)

evaluator/property.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,7 @@ func evaluateProperty(node *ast.Property, scope *object.Scope) object.Object {
1515

1616
switch left.(type) {
1717
case *object.Instance:
18-
property := node.Property.(*ast.Identifier)
19-
instance := left.(*object.Instance)
20-
21-
if !instance.Environment.Has(property.Value) {
22-
instance.Environment.Set(property.Value, value.NULL)
23-
}
24-
25-
val, _ := instance.Environment.Get(property.Value)
26-
27-
return val
18+
return evaluateInstanceProperty(left, node)
2819
case *object.LibraryModule:
2920
property := node.Property.(*ast.Identifier)
3021
module := left.(*object.LibraryModule)
@@ -49,3 +40,36 @@ func evaluateProperty(node *ast.Property, scope *object.Scope) object.Object {
4940

5041
return nil
5142
}
43+
44+
func evaluateInstanceProperty(left object.Object, node *ast.Property) object.Object {
45+
var val object.Object
46+
47+
instance := left.(*object.Instance)
48+
property := node.Property.(*ast.Identifier)
49+
50+
if instance.Environment.Has(property.Value) {
51+
val, _ = instance.Environment.Get(property.Value)
52+
53+
return val
54+
}
55+
56+
if instance.Class.Environment.Has(property.Value) {
57+
val, _ = instance.Class.Environment.Get(property.Value)
58+
59+
return val
60+
}
61+
62+
for _, trait := range instance.Class.Traits {
63+
if trait.Environment.Has(property.Value) {
64+
val, _ = trait.Environment.Get(property.Value)
65+
66+
return val
67+
}
68+
}
69+
70+
instance.Environment.Set(property.Value, value.NULL)
71+
72+
val, _ = instance.Environment.Get(property.Value)
73+
74+
return val
75+
}

evaluator/trait.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package evaluator
2+
3+
import (
4+
"ghostlang.org/x/ghost/ast"
5+
"ghostlang.org/x/ghost/object"
6+
)
7+
8+
func evaluateTrait(node *ast.Trait, scope *object.Scope) object.Object {
9+
trait := &object.Trait{
10+
Name: node.Name,
11+
Scope: scope,
12+
Environment: object.NewEnvironment(),
13+
}
14+
15+
// Create a new scope for this trait
16+
trait.Environment = object.NewEnclosedEnvironment(scope.Environment)
17+
traitScope := &object.Scope{Environment: trait.Environment, Self: trait}
18+
19+
result := Evaluate(node.Body, traitScope)
20+
21+
if isError(result) {
22+
return result
23+
}
24+
25+
scope.Environment.Set(node.Name.Value, trait)
26+
27+
return trait
28+
}

evaluator/use.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package evaluator
2+
3+
import (
4+
"ghostlang.org/x/ghost/ast"
5+
"ghostlang.org/x/ghost/object"
6+
)
7+
8+
func evaluateUse(node *ast.Use, scope *object.Scope) object.Object {
9+
// check that the scope is a class
10+
class, ok := scope.Self.(*object.Class)
11+
12+
if !ok {
13+
return object.NewError("%d:%d:%s: runtime error: use statement can only be used in a class", node.Token.Line, node.Token.Column, node.Token.File)
14+
}
15+
16+
var traits []*object.Trait
17+
18+
for _, trait := range node.Traits {
19+
if !scope.Environment.Has(trait.Value) {
20+
return object.NewError("%d:%d:%s: runtime error: trait '%s' is not defined", trait.Token.Line, trait.Token.Column, trait.Token.File, trait.Value)
21+
}
22+
23+
identifier, _ := scope.Environment.Get(trait.Value)
24+
25+
t, ok := identifier.(*object.Trait)
26+
27+
if !ok {
28+
return object.NewError("%d:%d:%s: runtime error: referenced identifier in use not a trait, got=%T", trait.Token.Line, trait.Token.Column, trait.Token.File, trait)
29+
}
30+
31+
traits = append(traits, t)
32+
}
33+
34+
class.Traits = traits
35+
36+
return nil
37+
}

examples/foo.ghost

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
trait Foo {
2+
message = 'Hello World!!!!'
3+
4+
function bar() {
5+
console.log(this.message)
6+
}
7+
}

examples/scratch.ghost

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
x = 0
2-
list = [0, 1, 2, 3]
1+
import Foo from 'foo'
32

4-
for (i = 0; i < list.length(); i++) {
5-
print(i)
6-
}
3+
class Lorem {
4+
use Foo
5+
6+
function hello() {
7+
console.log('hello')
8+
}
9+
}
10+
11+
lorem = Lorem.new()
12+
13+
lorem.hello()
14+
lorem.bar()
15+
16+
console.log('done.')

0 commit comments

Comments
 (0)