wip on classes
This commit is contained in:
parent
0d98ae8ab1
commit
4ea80598b3
10 changed files with 206 additions and 46 deletions
|
@ -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 {
|
||||
|
|
40
callable.go
40
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}
|
||||
}
|
||||
|
|
28
class.go
28
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
|
||||
}
|
||||
|
|
23
expr.go
23
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)
|
||||
}
|
||||
|
|
41
function.go
Normal file
41
function.go
Normal 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}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
16
parser.go
16
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
|
||||
}
|
||||
|
|
13
parser_test.go
Normal file
13
parser_test.go
Normal 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")
|
||||
}
|
||||
}
|
11
resolver.go
11
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
|
||||
}
|
||||
|
|
32
tests/class.lox
Normal file
32
tests/class.lox
Normal 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();
|
Loading…
Add table
Reference in a new issue