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.

186 lines
3.9 KiB
Go

package interpreter
import (
"fmt"
"golox/ast"
"golox/errors"
"golox/token"
"strings"
)
// Interpreter interprets the AST.
type Interpreter struct {
errLogger errors.Logger
}
// New creates a new Interpreter.
func New() *Interpreter {
return &Interpreter{}
}
// 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))
}
// 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)
}
// 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))
}
}