package interpreter import ( "fmt" "golox/ast" "golox/errors" "golox/token" "strings" ) // Interpreter interprets the AST. type Interpreter struct { errLogger errors.Logger env *environment } // New creates a new Interpreter. func New(el errors.Logger) *Interpreter { return &Interpreter{el, newEnvironment()} } // 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 } // VisitPrintStmt visits a print statement. func (i *Interpreter) VisitPrintStmt(ps *ast.PrintStmt) any { value := i.evaluate(ps.Expression) fmt.Println(stringify(value)) 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 } // 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) } // VisitLiteralExpr visits a LiteralExpr. func (i *Interpreter) VisitLiteralExpr(l *ast.LiteralExpr) any { return l.Value } // 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)) } // 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) } // 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)) } }