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.
242 lines
5.3 KiB
Go
242 lines
5.3 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()}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
}
|