You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
7.8 KiB
Go

package interpreter
import (
"fmt"
"golox/ast"
"golox/errors"
"golox/token"
"strings"
"time"
)
// ReturnValue is a struct that holds a return value when a return statement is encountered.
type ReturnValue struct {
Value any
}
// Interpreter interprets the AST.
type Interpreter struct {
errLogger errors.Logger
env *environment
globals *environment
}
// New creates a new Interpreter.
func New(el errors.Logger) *Interpreter {
globals := newEnvironment(nil)
clockCallable := newNativeCallable(
func() int { return 0 },
func(interpreter *Interpreter, args []any) any {
t := time.Now().UnixNano() / int64(time.Millisecond)
return float64(t)
})
globals.define("clock", clockCallable)
return &Interpreter{el, globals, globals}
}
// Interpret interprets the AST.
func (i *Interpreter) Interpret(stmts []ast.Stmt) {
defer i.afterPanic()
for _, stmt := range stmts {
i.execute(stmt)
}
}
// VisitErrorStmt visits an error statement.
func (i *Interpreter) VisitErrorStmt(es *ast.ErrorStmt) any {
panic(es.Value)
}
// VisitExpressionStmt visits an expression.
func (i *Interpreter) VisitExpressionStmt(es *ast.ExpressionStmt) any {
i.evaluate(es.Expression)
return nil
}
// VisitFunctionStmt visits a function statement.
func (i *Interpreter) VisitFunctionStmt(fs *ast.FunctionStmt) any {
function := newFunction(fs)
i.env.define(fs.Name.Lexeme, function)
return nil
}
// VisitIfStmt visits an if statement.
func (i *Interpreter) VisitIfStmt(is *ast.IfStmt) any {
if isTruthy(i.evaluate(is.Condition)) {
i.execute(is.ThenBranch)
} else if is.ElseBranch != nil {
i.execute(is.ElseBranch)
}
return nil
}
// VisitPrintStmt visits a print statement.
func (i *Interpreter) VisitPrintStmt(ps *ast.PrintStmt) any {
value := i.evaluate(ps.Expression)
fmt.Println(stringify(value))
return nil
}
// VisitReturnStmt visits a return statement.
func (i *Interpreter) VisitReturnStmt(rs *ast.ReturnStmt) any {
var value any
if rs.Value != nil {
value = i.evaluate(rs.Value)
}
panic(ReturnValue{Value: value})
}
// VisitWhileStmt visits a while statement.
func (i *Interpreter) VisitWhileStmt(ws *ast.WhileStmt) any {
for isTruthy(i.evaluate(ws.Condition)) {
i.execute(ws.Body)
}
return nil
}
// VisitVarStmt visits a var statement.
func (i *Interpreter) VisitVarStmt(vs *ast.VarStmt) any {
var value any
if vs.Initializer != nil {
value = i.evaluate(vs.Initializer)
}
i.env.define(vs.Name.Lexeme, value)
return nil
}
// VisitBlockStmt visits a block statement.
func (i *Interpreter) VisitBlockStmt(bs *ast.BlockStmt) any {
i.executeBlock(bs.Statements, newEnvironment(i.env))
return nil
}
// Interpret interprets the AST.
func (i *Interpreter) InterpretExpr(expr ast.Expr) string {
defer i.afterPanic()
value := i.evaluate(expr)
return stringify(value)
}
// VisitErrorExpr visits an ErrorExpr.
func (i *Interpreter) VisitErrorExpr(e *ast.ErrorExpr) any {
panic(e.Value)
}
// VisitAssignExpr visits an AssignExpr.
func (i *Interpreter) VisitAssignExpr(a *ast.AssignExpr) any {
value := i.evaluate(a.Value)
i.env.assign(a.Name.Lexeme, value)
return value
}
// VisitLiteralExpr visits a LiteralExpr.
func (i *Interpreter) VisitLiteralExpr(l *ast.LiteralExpr) any {
return l.Value
}
// VisitLogicalExpr visits a LogicalExpr.
func (i *Interpreter) VisitLogicalExpr(l *ast.LogicalExpr) any {
left := i.evaluate(l.Left)
if l.Operator.Type == token.OR {
if isTruthy(left) {
return left
}
} else {
if !isTruthy(left) {
return left
}
}
return i.evaluate(l.Right)
}
// VisitGroupingExpr visits a GroupingExpr.
func (i *Interpreter) VisitGroupingExpr(g *ast.GroupingExpr) any {
return i.evaluate(g.Expression)
}
// VisitUnaryExpr visits a UnaryExpr.
func (i *Interpreter) VisitUnaryExpr(u *ast.UnaryExpr) any {
right := i.evaluate(u.Right)
switch u.Operator.Type {
case token.MINUS:
checkNumberOperands(u.Operator, right)
return -right.(float64)
case token.BANG:
return !isTruthy(right)
}
return nil
}
// VisitBinaryExpr visits a BinaryExpr.
func (i *Interpreter) VisitBinaryExpr(b *ast.BinaryExpr) any {
left := i.evaluate(b.Left)
right := i.evaluate(b.Right)
switch b.Operator.Type {
case token.MINUS:
checkNumberOperands(b.Operator, left, right)
return left.(float64) - right.(float64)
case token.SLASH:
checkNumberOperands(b.Operator, left, right)
denominator := right.(float64)
if denominator == 0.0 {
panic(fmt.Sprintf("Division by zero [line %d]", b.Operator.Line))
}
return left.(float64) / denominator
case token.STAR:
checkNumberOperands(b.Operator, left, right)
return left.(float64) * right.(float64)
case token.PLUS:
if l, ok := left.(float64); ok {
if r, ok := right.(float64); ok {
return l + r
}
}
if l, ok := left.(string); ok {
if r, ok := right.(string); ok {
return l + r
}
}
panic(fmt.Sprintf("Operands must be two numbers or two strings [line %d]", b.Operator.Line))
case token.GREATER:
checkNumberOperands(b.Operator, left, right)
return left.(float64) > right.(float64)
case token.GREATER_EQUAL:
checkNumberOperands(b.Operator, left, right)
return left.(float64) >= right.(float64)
case token.LESS:
checkNumberOperands(b.Operator, left, right)
return left.(float64) < right.(float64)
case token.LESS_EQUAL:
checkNumberOperands(b.Operator, left, right)
return left.(float64) <= right.(float64)
case token.BANG_EQUAL:
return !isEqual(left, right)
case token.EQUAL_EQUAL:
return isEqual(left, right)
}
panic(fmt.Sprintf("Unknown binary operator '%s' [line %d]", b.Operator.Lexeme, b.Operator.Line))
}
// VisitCallExpr visits a CallExpr.
func (i *Interpreter) VisitCallExpr(c *ast.CallExpr) any {
callee := i.evaluate(c.Callee)
var arguments []any
for _, argument := range c.Arguments {
arguments = append(arguments, i.evaluate(argument))
}
if f, ok := callee.(callable); ok {
if len(arguments) != f.arity() {
panic(fmt.Sprintf("Expected %d arguments but got %d [line %d]", f.arity(), len(arguments), c.Paren.Line))
}
return f.call(i, arguments)
}
panic(fmt.Sprintf("Can only call functions and classes [line %d]", c.Paren.Line))
}
// VisitVariableExpr visits a VariableExpr.
func (i *Interpreter) VisitVariableExpr(v *ast.VariableExpr) any {
return i.env.get(v.Name.Lexeme)
}
// checkNumberOperands checks if the operands are numbers.
func checkNumberOperands(operator token.Token, operands ...any) {
for _, operand := range operands {
if _, ok := operand.(float64); !ok {
panic(fmt.Sprintf("Operands of operator '%s' must be numbers [line %d]", operator.Lexeme, operator.Line))
}
}
}
// isTruthy checks if a value is truthy.
func isTruthy(v any) bool {
if v == nil {
return false
}
if b, ok := v.(bool); ok {
return b
}
return true
}
// isEqual checks if two values are equal.
func isEqual(a, b any) bool {
if a == nil && b == nil {
return true
}
if a == nil {
return false
}
return a == b
}
// evaluate evaluates an expression.
func (i *Interpreter) evaluate(e ast.Expr) any {
return e.Accept(i)
}
// execute executes a statement.
func (i *Interpreter) execute(s ast.Stmt) {
s.Accept(i)
}
// executeBlock executes a block of statements.
func (i *Interpreter) executeBlock(stmts []ast.Stmt, env *environment) {
previous := i.env
defer func() {
i.env = previous
}()
i.env = env
for _, stmt := range stmts {
i.execute(stmt)
}
}
// stringify returns a string representation of a value.
func stringify(v any) string {
if v == nil {
return "nil"
}
if b, ok := v.(bool); ok {
if b {
return "true"
}
return "false"
}
s := fmt.Sprintf("%v", v)
if f, ok := v.(float64); ok {
if strings.HasSuffix(s, ".0") {
return fmt.Sprintf("%d", int(f))
}
return fmt.Sprintf("%g", f)
}
return s
}
// afterPanic handles a panic.
func (i *Interpreter) afterPanic() {
if r := recover(); r != nil {
i.errLogger.RuntimeError(r.(string))
}
}