functions

This commit is contained in:
Greg 2024-10-09 19:57:52 +03:00
parent e8aeb6d5c5
commit a6e0673c3b
9 changed files with 136 additions and 5 deletions

View file

@ -1,5 +1,6 @@
{ {
"cSpell.words": [ "cSpell.words": [
"arity",
"glox", "glox",
"PAREN", "PAREN",
"stmts" "stmts"

View file

@ -71,6 +71,17 @@ func (as *AstStringer) visitLogical(l *Logical) any {
return nil return nil
} }
func (as *AstStringer) visitCall(c *Call) any {
as.str.WriteString("(call ")
c.callee.accept(as)
for _, arg := range c.arguments {
arg.accept(as)
}
as.str.WriteString(")")
return nil
}
func (as *AstStringer) visitPrintStmt(p *PrintStmt) { func (as *AstStringer) visitPrintStmt(p *PrintStmt) {
as.str.WriteString("(print ") as.str.WriteString("(print ")
p.val.accept(as) p.val.accept(as)

6
callable.go Normal file
View file

@ -0,0 +1,6 @@
package main
type Callable struct {
arity int
call func(*Interpreter, ...any) any
}

12
expr.go
View file

@ -1,6 +1,7 @@
package main package main
type ExprVisitor interface { type ExprVisitor interface {
visitCall(c *Call) any
visitUnary(u *Unary) any visitUnary(u *Unary) any
visitBinary(b *Binary) any visitBinary(b *Binary) any
visitLiteral(l *Literal) any visitLiteral(l *Literal) any
@ -49,6 +50,13 @@ type Logical struct {
right Expr right Expr
} }
type Call struct {
callee Expr
paren Token
arguments []Expr
}
func (c *Call) expr() {}
func (u *Unary) expr() {} func (u *Unary) expr() {}
func (a *Assign) expr() {} func (a *Assign) expr() {}
func (b *Binary) expr() {} func (b *Binary) expr() {}
@ -84,3 +92,7 @@ func (a *Assign) accept(v ExprVisitor) any {
func (l *Logical) accept(v ExprVisitor) any { func (l *Logical) accept(v ExprVisitor) any {
return v.visitLogical(l) return v.visitLogical(l)
} }
func (c *Call) accept(v ExprVisitor) any {
return v.visitCall(c)
}

View file

@ -61,3 +61,12 @@ func (as *ExprToRPN) visitLogical(lo *Logical) any {
as.str.WriteString(" or") as.str.WriteString(" or")
return nil return nil
} }
func (as *ExprToRPN) visitCall(c *Call) any {
for _, arg := range c.arguments {
arg.accept(as)
}
c.callee.accept(as)
as.str.WriteString(" call")
return nil
}

13
globals.go Normal file
View file

@ -0,0 +1,13 @@
package main
import "time"
func defineGlobals(env *Environment) {
env.set("clock", &Callable{
arity: 0,
call: func(i *Interpreter, arg ...any) any {
return time.Now().Unix()
},
})
}

View file

@ -9,6 +9,7 @@ import (
type Interpreter struct { type Interpreter struct {
env *Environment env *Environment
globals *Environment
errors []error errors []error
brk bool brk bool
} }
@ -23,7 +24,17 @@ func (re *RuntimeError) Error() string {
} }
func newInterpreter() *Interpreter { func newInterpreter() *Interpreter {
return &Interpreter{env: newEnvironment(nil), errors: []error{}, brk: false}
globals := newEnvironment(nil)
defineGlobals(globals)
return &Interpreter{
env: globals,
globals: globals,
errors: []error{},
brk: false,
}
} }
func (i *Interpreter) interpret(stmts []Stmt) []error { func (i *Interpreter) interpret(stmts []Stmt) []error {
@ -156,6 +167,36 @@ func (i *Interpreter) visitLogical(lo *Logical) any {
return i.evaluate(lo.right) return i.evaluate(lo.right)
} }
func (i *Interpreter) visitCall(c *Call) any {
callee := i.evaluate(c.callee)
args := []any{}
for _, arg := range c.arguments {
args = append(args, i.evaluate(arg))
}
callable, ok := callee.(*Callable)
if !ok {
i.panic(&RuntimeError{c.paren, "Can only call function and classes."})
}
if callable.arity != len(args) {
i.panic(&RuntimeError{
c.paren,
fmt.Sprintf(
"Expected %d arguments but got %d",
callable.arity,
len(args),
),
})
}
return callable.call(i, args...)
}
func (i *Interpreter) visitPrintStmt(p *PrintStmt) { func (i *Interpreter) visitPrintStmt(p *PrintStmt) {
fmt.Printf("%v\n", i.evaluate(p.val)) fmt.Printf("%v\n", i.evaluate(p.val))
} }

View file

@ -346,7 +346,41 @@ func (p *Parser) unary() Expr {
return &Unary{op, right} return &Unary{op, right}
} }
return p.primary() return p.call()
}
// call -> primary ( "(" arguments? ")" )*
func (p *Parser) call() Expr {
expr := p.primary()
for {
if p.match(LEFT_PAREN) {
expr = p.arguments(expr)
} else {
break
}
}
return expr
}
// arguments -> expression ( "," expression )*
func (p *Parser) arguments(callee Expr) Expr {
arguments := []Expr{}
if !p.check(RIGHT_PAREN) {
for {
arguments = append(arguments, p.expression())
if !p.match(COMMA) {
break
}
}
}
paren := p.consume(RIGHT_PAREN, "Expect ')' after arguments.")
return &Call{callee, paren, arguments}
} }
// primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER // primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER

4
tests/functions.lox Normal file
View file

@ -0,0 +1,4 @@
print "functions test";
print clock();