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.
339 lines
7.4 KiB
Go
339 lines
7.4 KiB
Go
package interpreter
|
|
|
|
import (
|
|
"fmt"
|
|
"golox/ast"
|
|
"golox/errors"
|
|
"golox/token"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
}
|