Skip to content

Commit

Permalink
Init class support (#8)
Browse files Browse the repository at this point in the history
* Update parser_test.go

* can parse classes

* Add instances

* init tests

* update
  • Loading branch information
sevenreup authored Aug 23, 2024
1 parent 7196731 commit 306a3af
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 27 deletions.
8 changes: 8 additions & 0 deletions examples/classes.ny
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kalasi Munthu {
ndondomeko ndiNdani() {
console.lemba("Mariya Malizeni");
}
}

Munthu munthu = Munthu();
munthu.ndiNdani();
31 changes: 31 additions & 0 deletions src/ast/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ast

import (
"bytes"

"github.com/sevenreup/chewa/src/token"
)

type ClassStatement struct {
Expression
Token token.Token
Name *Identifier
Super *Identifier
Body *BlockStatement
}

func (class *ClassStatement) expressionNode() {}
func (class *ClassStatement) TokenLiteral() string { return class.Token.Literal }
func (class *ClassStatement) String() string {
var out bytes.Buffer
out.WriteString("ndondomeko ")
out.WriteString(class.Name.String())
if class.Super != nil {
out.WriteString(" ndi ")
out.WriteString(class.Super.String())
}
out.WriteString(" {\n")
out.WriteString(class.Body.String())
out.WriteString("\n}")
return out.String()
}
46 changes: 46 additions & 0 deletions src/chewa/chewa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package chewa

import (
"log"
"os"

"github.com/sevenreup/chewa/src/evaluator"
"github.com/sevenreup/chewa/src/object"
"github.com/sevenreup/chewa/src/utils"

"github.com/sevenreup/chewa/src/lexer"
"github.com/sevenreup/chewa/src/parser"
)

type Chewa struct {
file string
Environment *object.Environment
}

func New(file string) *Chewa {
chewa := &Chewa{
file: file,
Environment: object.NewEnvironment(),
}
chewa.registerEvaluator()
return chewa
}

func (c *Chewa) Run() {
file, err := os.ReadFile(c.file)
if err != nil {
log.Fatal(err)
}
l := lexer.New(file)
p := parser.New(l)
env := object.NewEnvironment()
program := p.ParseProgram()
if len(p.Errors()) != 0 {
utils.PrintParserErrors(os.Stdout, p.Errors())
}
evaluator.Eval(program, env)
}

func (c *Chewa) registerEvaluator() {
object.RegisterEvaluator(evaluator.Eval)
}
25 changes: 25 additions & 0 deletions src/evaluator/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package evaluator

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

func evaluateClass(node *ast.ClassStatement, env *object.Environment) object.Object {
classEnv := object.NewEnclosedEnvironment(env)

class := &object.Class{
Name: node.Name,
Env: classEnv,
}

result := Eval(node.Body, classEnv)

if isError(result) {
return result
}

env.Set(class.Name.Value, class)

return class
}
4 changes: 4 additions & 0 deletions src/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/sevenreup/chewa/src/values"
)

type Evaluator func(node ast.Node, env *object.Environment) object.Object

func Eval(node ast.Node, env *object.Environment) object.Object {
switch node := node.(type) {
case *ast.Program:
Expand Down Expand Up @@ -73,6 +75,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return &object.String{Value: node.Value}
case *ast.PostfixExpression:
return evaluatePostfix(node, env)
case *ast.ClassStatement:
return evaluateClass(node, env)
}
return nil
}
Expand Down
38 changes: 36 additions & 2 deletions src/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ func testEval(input string) object.Object {
p := parser.New(l)
program := p.ParseProgram()
env := object.NewEnvironment()

evaluatorInstance := Eval
object.RegisterEvaluator(evaluatorInstance)

return Eval(program, env)
}

Expand Down Expand Up @@ -619,8 +623,8 @@ func TestHashLiterals(t *testing.T) {
(&object.String{Value: "two"}).MapKey(): 2,
(&object.String{Value: "three"}).MapKey(): 3,
(&object.Integer{Value: decimal.NewFromInt(4)}).MapKey(): 4,
values.TRUE.MapKey(): 5,
values.FALSE.MapKey(): 6,
values.TRUE.MapKey(): 5,
values.FALSE.MapKey(): 6,
}
if len(result.Pairs) != len(expected) {
t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
Expand Down Expand Up @@ -678,3 +682,33 @@ func TestHashIndexExpressions(t *testing.T) {
}
}
}

func TestClasses(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{
`
kalasi Munthu {
ndondomeko zaka() {
bweza 10;
}
}
Munthu maliko = Munthu();
maliko.zaka();
`,
5,
},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, decimal.NewFromInt(int64(integer)))
} else {
testNullObject(t, evaluated)
}
}
}
22 changes: 3 additions & 19 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,8 @@ package main
import (
"flag"
"log"
"os"

"github.com/sevenreup/chewa/src/evaluator"
"github.com/sevenreup/chewa/src/object"
"github.com/sevenreup/chewa/src/utils"

"github.com/sevenreup/chewa/src/lexer"
"github.com/sevenreup/chewa/src/parser"
"github.com/sevenreup/chewa/src/chewa"
)

var (
Expand All @@ -28,16 +22,6 @@ func main() {
log.Fatal("Please provide a file to run")
}

file, err := os.ReadFile(file)
if err != nil {
log.Fatal(err)
}
l := lexer.New(file)
p := parser.New(l)
env := object.NewEnvironment()
program := p.ParseProgram()
if len(p.Errors()) != 0 {
utils.PrintParserErrors(os.Stdout, p.Errors())
}
evaluator.Eval(program, env)
chewa := chewa.New(file)
chewa.Run()
}
35 changes: 35 additions & 0 deletions src/object/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package object

import "github.com/sevenreup/chewa/src/ast"

const CLASS_OBJ = "CLASS"

type Class struct {
Object
Name *ast.Identifier
Env *Environment
}

func (c *Class) Type() ObjectType { return CLASS_OBJ }

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)}

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

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

return instance, true
}
return nil, false
}
16 changes: 12 additions & 4 deletions src/object/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ 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
// }
e.store[name] = val
return val
}

func (e *Environment) Has(name string) bool {
_, ok := e.store[name]
if !ok && e.outer != nil {
e.outer.Set(name, val)
return val
return e.outer.Has(name)
}
e.store[name] = val
return val
return ok
}

func (e *Environment) Delete(name string) {
Expand Down
6 changes: 6 additions & 0 deletions src/object/error.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package object

import "fmt"

const ERROR_OBJ = "ERROR"

type Error struct {
Expand All @@ -15,3 +17,7 @@ func (i *Error) Method(method string, args []Object) (Object, bool) {
//TODO implement me
panic("implement me")
}

func NewError(format string, a ...interface{}) *Error {
return &Error{Message: fmt.Sprintf(format, a...)}
}
44 changes: 44 additions & 0 deletions src/object/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package object

const INSTANCE_OBJ = "INSTANCE"

type Instance struct {
Class *Class
Env *Environment
}

func (i *Instance) Type() ObjectType { return INSTANCE_OBJ }

func (i *Instance) Inspect() string {
return i.Class.Name.String()
}

func (i *Instance) Method(method string, args []Object) (Object, bool) {
return nil, true
}

func (i *Instance) Call(method string, args []Object) Object {
function, ok := i.Env.Get(method)
if !ok {
return NewError("undefined method %s for %s", method, i.Class.Name.String())
}
methodFunction, ok := function.(*Function)
if !ok {
return NewError("undefined method %s for %s", method, i.Class.Name.String())
}

methodEnv := createNewMethodInstanceEnvironment(methodFunction, args)
return evaluator(methodFunction.Body, methodEnv)
}

func createNewMethodInstanceEnvironment(method *Function, args []Object) *Environment {
env := NewEnclosedEnvironment(method.Env)

for i, param := range method.Parameters {
if len(args) > i {
env.Set(param.Value, args[i])
}
}

return env
}
11 changes: 10 additions & 1 deletion src/object/object.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package object

import "github.com/sevenreup/chewa/src/token"
import (
"github.com/sevenreup/chewa/src/ast"
"github.com/sevenreup/chewa/src/token"
)

var evaluator func(node ast.Node, env *Environment) Object

type ObjectType string

Expand All @@ -25,3 +30,7 @@ type HasMethods interface {

type GoFunction func(env *Environment, tok token.Token, args ...Object) Object
type GoProperty func(env *Environment, tok token.Token) Object

func RegisterEvaluator(e func(node ast.Node, env *Environment) Object) {
evaluator = e
}
24 changes: 24 additions & 0 deletions src/parser/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package parser

import (
"github.com/sevenreup/chewa/src/ast"
"github.com/sevenreup/chewa/src/token"
)

func (p *Parser) classStatement() ast.Expression {
class := &ast.ClassStatement{Token: p.curToken}

p.nextToken()

class.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}

// TODO: Implement inheritance

if !p.expectPeek(token.OPENING_BRACE) {
return nil
}

class.Body = p.parseBlockStatement()

return class
}
Loading

0 comments on commit 306a3af

Please sign in to comment.