diff --git a/ast_string.go b/ast_string.go index fdc1690..3287399 100644 --- a/ast_string.go +++ b/ast_string.go @@ -10,6 +10,18 @@ type AstStringer struct { stmts []Stmt } +func (as *AstStringer) visitGet(g *Get) any { + as.str.WriteString(fmt.Sprintf("(get %s)", g.name.lexeme)) + return nil +} + +func (as *AstStringer) visitSet(s *Set) any { + as.str.WriteString(fmt.Sprintf("(set %s ", s.name.lexeme)) + s.obj.accept(as) + as.str.WriteString(")") + return nil +} + func (as AstStringer) String() string { for _, stmt := range as.stmts { diff --git a/callable.go b/callable.go index 8db5da6..35cab8d 100644 --- a/callable.go +++ b/callable.go @@ -4,43 +4,3 @@ type Callable interface { arity() int call(i *Interpreter, args ...any) (ret any) } - -type Function struct { - name Token - args []Token - body []Stmt - closure *Environment -} - -func (f *Function) call(i *Interpreter, args ...any) (ret any) { - - defer func() { - if err := recover(); err != nil { - re, ok := err.(Return) - - if !ok { - panic(err) - } - - ret = re.val - } - }() - - env := newEnvironment(f.closure) - - for idx, arg := range f.args { - env.define(arg.lexeme, args[idx]) - } - - i.executeBlock(f.body, env) - - return nil -} - -func (f *Function) arity() int { - return len(f.args) -} - -func newFunction(name Token, args []Token, body []Stmt, env *Environment) Callable { - return &Function{name, args, body, env} -} diff --git a/class.go b/class.go index 1c66b77..add0d5d 100644 --- a/class.go +++ b/class.go @@ -1,9 +1,37 @@ package main +import "fmt" + type Class struct { name string } +type ClassInstance struct { + klass *Class + props map[string]any +} + +func (c *ClassInstance) String() string { + return fmt.Sprintf("instance of %s", c.klass.name) +} + +func (c *ClassInstance) get(name string) (any, bool) { + val, ok := c.props[name] + return val, ok +} + +func (c *ClassInstance) set(name string, val any) { + c.props[name] = val +} + +func (c *Class) arity() int { + return 0 +} + +func (c *Class) call(i *Interpreter, args ...any) (ret any) { + return &ClassInstance{c, map[string]any{}} +} + func (c *Class) String() string { return c.name } diff --git a/expr.go b/expr.go index 08b72ed..20c75b9 100644 --- a/expr.go +++ b/expr.go @@ -10,6 +10,8 @@ type ExprVisitor interface { visitVariable(v *Variable) any visitLogical(l *Logical) any visitAssignment(a *Assign) any + visitGet(g *Get) any + visitSet(s *Set) any } type Expr interface { @@ -63,6 +65,17 @@ type Lambda struct { body []Stmt } +type Get struct { + name Token + obj Expr +} + +type Set struct { + name Token + obj Expr + value Expr +} + func (c *Call) expr() {} func (u *Unary) expr() {} func (a *Assign) expr() {} @@ -72,6 +85,8 @@ func (l *Literal) expr() {} func (g *Grouping) expr() {} func (v *Variable) expr() {} func (l *Logical) expr() {} +func (g *Get) expr() {} +func (s *Set) expr() {} func (u *Unary) accept(v ExprVisitor) any { return v.visitUnary(u) @@ -108,3 +123,11 @@ func (c *Call) accept(v ExprVisitor) any { func (l *Lambda) accept(v ExprVisitor) any { return v.visitLambda(l) } + +func (g *Get) accept(v ExprVisitor) any { + return v.visitGet(g) +} + +func (s *Set) accept(v ExprVisitor) any { + return v.visitSet(s) +} diff --git a/function.go b/function.go new file mode 100644 index 0000000..08bcba5 --- /dev/null +++ b/function.go @@ -0,0 +1,41 @@ +package main + +type Function struct { + name Token + args []Token + body []Stmt + closure *Environment +} + +func (f *Function) call(i *Interpreter, args ...any) (ret any) { + + defer func() { + if err := recover(); err != nil { + re, ok := err.(Return) + + if !ok { + panic(err) + } + + ret = re.val + } + }() + + env := newEnvironment(f.closure) + + for idx, arg := range f.args { + env.define(arg.lexeme, args[idx]) + } + + i.executeBlock(f.body, env) + + return nil +} + +func (f *Function) arity() int { + return len(f.args) +} + +func newFunction(name Token, args []Token, body []Stmt, env *Environment) Callable { + return &Function{name, args, body, env} +} diff --git a/interpreter.go b/interpreter.go index 3370235..84f00eb 100644 --- a/interpreter.go +++ b/interpreter.go @@ -201,6 +201,42 @@ func (i *Interpreter) visitCall(c *Call) any { return callable.call(i, args...) } +func (i *Interpreter) visitGet(g *Get) any { + + object := i.evaluate(g.obj) + + instance, ok := object.(*ClassInstance) + + if !ok { + i.panic(&RuntimeError{g.name, "Only class instances can have properties"}) + } + + val, ok := instance.get(g.name.lexeme) + + if !ok { + i.panic(&RuntimeError{g.name, fmt.Sprintf("Undefined propery %s", g.name.lexeme)}) + } + + return val +} + +func (i *Interpreter) visitSet(s *Set) any { + + object := i.evaluate(s.obj) + + instance, ok := object.(*ClassInstance) + + if !ok { + i.panic(&RuntimeError{s.name, "Only class instances have fields."}) + } + + value := i.evaluate(s.value) + + instance.set(s.name.lexeme, value) + + return value +} + func (i *Interpreter) visitFunStmt(f *FunStmt) { i.env.define(f.name.lexeme, newFunction(f.name, f.args, f.body, i.env)) } diff --git a/parser.go b/parser.go index 8b6553d..a1c147a 100644 --- a/parser.go +++ b/parser.go @@ -76,7 +76,7 @@ func (p *Parser) varDecl() Stmt { initializer = p.expression() } - p.consume(SEMICOLON, "Expect ';' after expression.") + p.consume(SEMICOLON, "Expect ';' after expression in var declaration;") return &VarStmt{name, initializer} } @@ -174,7 +174,7 @@ func (p *Parser) statement() Stmt { // exprStmt -> expression ";" func (p *Parser) exprStmt() Stmt { expr := p.expression() - p.consume(SEMICOLON, "Expect ';' after expression.") + p.consume(SEMICOLON, "Expect ';' after statement.") if expr == nil { return nil @@ -191,7 +191,7 @@ func (p *Parser) printStmt() Stmt { p.panic(&ParseError{p.previous(), "Expect expression after 'print'"}) } - p.consume(SEMICOLON, "Expect ';' after expression.") + p.consume(SEMICOLON, "Expect ';' after print expression.") return &PrintStmt{expr} } @@ -316,7 +316,7 @@ func (p *Parser) expression() Expr { return p.assignment() } -// assignment -> IDENTIFIER "=" assignment | or +// assignment -> (call ".")? IDENTIFIER "=" assignment | or func (p *Parser) assignment() Expr { expr := p.or() @@ -326,11 +326,12 @@ func (p *Parser) assignment() Expr { if variable, ok := expr.(*Variable); ok { return &Assign{variable.name, val} + } else if get, ok := expr.(*Get); ok { + return &Set{get.name, get.obj, val} } p.panic(&ParseError{eq, "Invalid assignment target."}) } - return expr } @@ -424,13 +425,16 @@ func (p *Parser) unary() Expr { return p.call() } -// call -> primary ( "(" arguments? ")" )* +// call -> primary ( "(" arguments? ")" | "." IDENTIFIER )* func (p *Parser) call() Expr { expr := p.primary() for { if p.match(LEFT_PAREN) { expr = p.arguments(expr) + } else if p.match(DOT) { + name := p.consume(IDENTIFIER, "Expect property name after '.'") + expr = &Get{name, expr} } else { break } diff --git a/parser_test.go b/parser_test.go new file mode 100644 index 0000000..bb9e756 --- /dev/null +++ b/parser_test.go @@ -0,0 +1,13 @@ +package main + +import "testing" + +func TestSimpleParser(t *testing.T) { + s := newScanner([]byte("print 1;")) + tokens, _ := s.scan() + p, _ := newParser(tokens).parse() + + if p == nil { + t.Fatal("cant parse") + } +} diff --git a/resolver.go b/resolver.go index 922f4c5..3e59d95 100644 --- a/resolver.go +++ b/resolver.go @@ -191,3 +191,14 @@ func (r *Resolver) visitClassStmt(c *ClassStmt) { r.declare(c.name) r.define(c.name) } + +func (r *Resolver) visitGet(g *Get) any { + r.resolveExprs(g.obj) + return nil +} + +func (r *Resolver) visitSet(s *Set) any { + r.resolveExprs(s.value) + r.resolveExprs(s.obj) + return nil +} diff --git a/tests/class.lox b/tests/class.lox new file mode 100644 index 0000000..11eab7d --- /dev/null +++ b/tests/class.lox @@ -0,0 +1,32 @@ + +class Car { + +} + +print Car; + +var opel = Car(); + +env; + +print opel; + +opel.name = "Opel"; + +class Engine {} + +print Engine; + +var eng = Engine(); + +eng.power = "200hp"; + +opel.engine = eng; + +print opel.engine.power; + + +opel.run = fun () { +} + +opel.run();