149 lines
2.8 KiB
Go
149 lines
2.8 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
type Interpreter struct{}
|
||
|
|
||
|
type RuntimeError struct {
|
||
|
token Token
|
||
|
msg string
|
||
|
}
|
||
|
|
||
|
func (re RuntimeError) Error() string {
|
||
|
return re.msg
|
||
|
}
|
||
|
|
||
|
func newInterpreter() *Interpreter {
|
||
|
return &Interpreter{}
|
||
|
}
|
||
|
|
||
|
func (i *Interpreter) evaluate(e Expr) any {
|
||
|
defer i.recover()
|
||
|
return e.accept(i)
|
||
|
}
|
||
|
|
||
|
func (i *Interpreter) recover() {
|
||
|
if err := recover(); err != nil {
|
||
|
pe, ok := err.(RuntimeError)
|
||
|
|
||
|
if !ok {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
reportRuntimeError(pe.token, pe.msg)
|
||
|
hadRuntimeError = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (i *Interpreter) visitBinary(b *Binary) any {
|
||
|
left := i.evaluate(b.left)
|
||
|
right := i.evaluate(b.right)
|
||
|
|
||
|
switch b.op.typ {
|
||
|
case MINUS:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) - right.(float64)
|
||
|
case SLASH:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) / right.(float64)
|
||
|
case STAR:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) * right.(float64)
|
||
|
case GREATER:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) > right.(float64)
|
||
|
case LESS:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) < right.(float64)
|
||
|
case GREATER_EQUAL:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) >= right.(float64)
|
||
|
case LESS_EQUAL:
|
||
|
checkIfFloats(b.op, left, right)
|
||
|
return left.(float64) <= right.(float64)
|
||
|
case BANG_EQUAL:
|
||
|
return !reflect.DeepEqual(left, right)
|
||
|
case EQUAL_EQUAL:
|
||
|
return reflect.DeepEqual(left, right)
|
||
|
case PLUS:
|
||
|
if isFloats(left, right) {
|
||
|
return left.(float64) + right.(float64)
|
||
|
}
|
||
|
|
||
|
if isStrings(left, right) {
|
||
|
return left.(string) + right.(string)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
panic(RuntimeError{b.op, fmt.Sprintf("Operands must be numbers or strings: %v %s %v", left, b.op.lexeme, right)})
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (i *Interpreter) visitLiteral(l *Literal) any {
|
||
|
return l.value
|
||
|
}
|
||
|
|
||
|
func (i *Interpreter) visitGrouping(g *Grouping) any {
|
||
|
return i.evaluate(g.expression)
|
||
|
}
|
||
|
|
||
|
func (i *Interpreter) visitUnary(u *Unary) any {
|
||
|
val := i.evaluate(u.right)
|
||
|
|
||
|
switch u.op.typ {
|
||
|
case MINUS:
|
||
|
checkIfFloat(u.op, val)
|
||
|
return -val.(float64)
|
||
|
case BANG:
|
||
|
return !isTruthy(val)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func checkIfFloat(op Token, val any) {
|
||
|
if _, ok := val.(float64); ok {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
panic(RuntimeError{op, "value must ne a number."})
|
||
|
}
|
||
|
|
||
|
func checkIfFloats(op Token, a any, b any) {
|
||
|
if isFloats(a, b) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
panic(RuntimeError{op, fmt.Sprintf("Operands must be numbers: %v %s %v", a, op.lexeme, b)})
|
||
|
}
|
||
|
|
||
|
func isFloats(a any, b any) bool {
|
||
|
ltype := reflect.TypeOf(a)
|
||
|
rtype := reflect.TypeOf(b)
|
||
|
|
||
|
return ltype.Kind() == rtype.Kind() && ltype.Kind() == reflect.Float64
|
||
|
}
|
||
|
|
||
|
func isStrings(a any, b any) bool {
|
||
|
ltype := reflect.TypeOf(a)
|
||
|
rtype := reflect.TypeOf(b)
|
||
|
|
||
|
return ltype.Kind() == rtype.Kind() && ltype.Kind() == reflect.String
|
||
|
}
|
||
|
|
||
|
func isTruthy(val any) bool {
|
||
|
if val == nil {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if boolean, ok := val.(bool); ok {
|
||
|
return boolean
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|