wip on classes

This commit is contained in:
Greg 2025-02-03 15:37:14 +02:00
parent 0d98ae8ab1
commit 4ea80598b3
10 changed files with 206 additions and 46 deletions

View file

@ -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 {

View file

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

View file

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

23
expr.go
View file

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

41
function.go Normal file
View file

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

View file

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

View file

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

13
parser_test.go Normal file
View file

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

View file

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

32
tests/class.lox Normal file
View file

@ -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();