From a6e0673c3b187071b02d40d1e9b898dcedd19b29 Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 9 Oct 2024 19:57:52 +0300 Subject: [PATCH] functions --- .vscode/settings.json | 1 + ast_string.go | 11 ++++++++++ callable.go | 6 ++++++ expr.go | 12 +++++++++++ expr_rpn.go | 9 ++++++++ globals.go | 13 ++++++++++++ interpreter.go | 49 +++++++++++++++++++++++++++++++++++++++---- parser.go | 36 ++++++++++++++++++++++++++++++- tests/functions.lox | 4 ++++ 9 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 callable.go create mode 100644 globals.go create mode 100644 tests/functions.lox diff --git a/.vscode/settings.json b/.vscode/settings.json index 6a8c7c1..14e80e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "arity", "glox", "PAREN", "stmts" diff --git a/ast_string.go b/ast_string.go index 9840c49..252c85e 100644 --- a/ast_string.go +++ b/ast_string.go @@ -71,6 +71,17 @@ func (as *AstStringer) visitLogical(l *Logical) any { 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) { as.str.WriteString("(print ") p.val.accept(as) diff --git a/callable.go b/callable.go new file mode 100644 index 0000000..14da667 --- /dev/null +++ b/callable.go @@ -0,0 +1,6 @@ +package main + +type Callable struct { + arity int + call func(*Interpreter, ...any) any +} diff --git a/expr.go b/expr.go index 4615b77..b8d0cff 100644 --- a/expr.go +++ b/expr.go @@ -1,6 +1,7 @@ package main type ExprVisitor interface { + visitCall(c *Call) any visitUnary(u *Unary) any visitBinary(b *Binary) any visitLiteral(l *Literal) any @@ -49,6 +50,13 @@ type Logical struct { right Expr } +type Call struct { + callee Expr + paren Token + arguments []Expr +} + +func (c *Call) expr() {} func (u *Unary) expr() {} func (a *Assign) expr() {} func (b *Binary) expr() {} @@ -84,3 +92,7 @@ func (a *Assign) accept(v ExprVisitor) any { func (l *Logical) accept(v ExprVisitor) any { return v.visitLogical(l) } + +func (c *Call) accept(v ExprVisitor) any { + return v.visitCall(c) +} diff --git a/expr_rpn.go b/expr_rpn.go index 1ef4679..e988011 100644 --- a/expr_rpn.go +++ b/expr_rpn.go @@ -61,3 +61,12 @@ func (as *ExprToRPN) visitLogical(lo *Logical) any { as.str.WriteString(" or") 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 +} diff --git a/globals.go b/globals.go new file mode 100644 index 0000000..f9412af --- /dev/null +++ b/globals.go @@ -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() + }, + }) +} diff --git a/interpreter.go b/interpreter.go index 397d48a..307f837 100644 --- a/interpreter.go +++ b/interpreter.go @@ -8,9 +8,10 @@ import ( ) type Interpreter struct { - env *Environment - errors []error - brk bool + env *Environment + globals *Environment + errors []error + brk bool } type RuntimeError struct { @@ -23,7 +24,17 @@ func (re *RuntimeError) Error() string { } 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 { @@ -156,6 +167,36 @@ func (i *Interpreter) visitLogical(lo *Logical) any { 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) { fmt.Printf("%v\n", i.evaluate(p.val)) } diff --git a/parser.go b/parser.go index 55bb54c..8b2a638 100644 --- a/parser.go +++ b/parser.go @@ -346,7 +346,41 @@ func (p *Parser) unary() Expr { 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 diff --git a/tests/functions.lox b/tests/functions.lox new file mode 100644 index 0000000..16d66bd --- /dev/null +++ b/tests/functions.lox @@ -0,0 +1,4 @@ + +print "functions test"; + +print clock(); \ No newline at end of file