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.

299 lines
6.4 KiB
Go

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(nil)}
}
// 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
}
// 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
}
// 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))
}
// 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))
}
}