package evaluator import ( "fmt" "monkey/internal/ast" "monkey/internal/builtins" "monkey/internal/lexer" "monkey/internal/object" "monkey/internal/parser" "monkey/internal/utils" "os" "strings" ) var ( NULL = &object.Null{} TRUE = &object.Boolean{Value: true} FALSE = &object.Boolean{Value: false} ) func isError(obj object.Object) bool { if obj != nil { return obj.Type() == object.ErrorType } return false } func Eval(node ast.Node, env *object.Environment) object.Object { switch node := node.(type) { // Statements case *ast.Program: return evalProgram(node, env) case *ast.ExpressionStatement: return Eval(node.Expression, env) case *ast.BlockStatement: return evalBlockStatements(node, env) case *ast.IfExpression: return evalIfExpression(node, env) case *ast.WhileExpression: return evalWhileExpression(node, env) case *ast.ImportExpression: return evalImportExpression(node, env) case *ast.ReturnStatement: val := Eval(node.ReturnValue, env) if isError(val) { return val } return &object.ReturnValue{Value: val} case *ast.BindExpression: value := Eval(node.Value, env) if isError(value) { return value } if ident, ok := node.Left.(*ast.Identifier); ok { if immutable, ok := value.(object.Immutable); ok { env.Set(ident.Value, immutable.Clone()) } else { env.Set(ident.Value, value) } return NULL } return newError("expected identifier on left got=%T", node.Left) case *ast.AssignmentExpression: left := Eval(node.Left, env) if isError(left) { return left } value := Eval(node.Value, env) if isError(value) { return value } if ident, ok := node.Left.(*ast.Identifier); ok { env.Set(ident.Value, value) } else if ie, ok := node.Left.(*ast.IndexExpression); ok { obj := Eval(ie.Left, env) if isError(obj) { return obj } if array, ok := obj.(*object.Array); ok { index := Eval(ie.Index, env) if isError(index) { return index } if idx, ok := index.(*object.Integer); ok { array.Elements[idx.Value] = value } else { return newError("cannot index array with %#v", index) } } else if hash, ok := obj.(*object.Hash); ok { key := Eval(ie.Index, env) if isError(key) { return key } if hashKey, ok := key.(object.Hashable); ok { hashed := hashKey.HashKey() hash.Pairs[hashed] = object.HashPair{Key: key, Value: value} } else { return newError("cannot index hash with %T", key) } } else { return newError("object type %T does not support item assignment", obj) } } else { return newError("expected identifier or index expression got=%T", left) } return NULL case *ast.Identifier: return evalIdentifier(node, env) case *ast.FunctionLiteral: params := node.Parameters body := node.Body return &object.Function{Parameters: params, Env: env, Body: body} // Expressions case *ast.IntegerLiteral: return &object.Integer{Value: node.Value} case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) case *ast.Null: return NULL case *ast.PrefixExpression: right := Eval(node.Right, env) if isError(right) { return right } return evalPrefixExpression(node.Operator, right) case *ast.InfixExpression: left := Eval(node.Left, env) if isError(left) { return left } right := Eval(node.Right, env) if isError(right) { return right } return evalInfixExpression(node.Operator, left, right) case *ast.CallExpression: function := Eval(node.Function, env) if isError(function) { return function } args := evalExpressions(node.Arguments, env) if len(args) == 1 && isError(args[0]) { return args[0] } return applyFunction(function, args) case *ast.StringLiteral: return &object.String{Value: node.Value} case *ast.ArrayLiteral: elements := evalExpressions(node.Elements, env) if len(elements) == 1 && isError(elements[0]) { return elements[0] } return &object.Array{Elements: elements} case *ast.IndexExpression: left := Eval(node.Left, env) if isError(left) { return left } index := Eval(node.Index, env) if isError(index) { return index } return evalIndexExpression(left, index) case *ast.HashLiteral: return evalHashLiteral(node, env) } return nil } func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) object.Object { name := Eval(ie.Name, env) if isError(name) { return name } if s, ok := name.(*object.String); ok { attrs := EvalModule(s.Value) if isError(attrs) { return attrs } return &object.Module{Name: s.Value, Attrs: attrs} } return newError("ImportError: invalid import path '%s'", name) } func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { var result object.Object for { condition := Eval(we.Condition, env) if isError(condition) { return condition } if isTruthy(condition) { result = Eval(we.Consequence, env) } else { break } } if result != nil { return result } return NULL } func evalProgram(program *ast.Program, env *object.Environment) object.Object { var result object.Object for _, statement := range program.Statements { result = Eval(statement, env) switch result := result.(type) { case *object.ReturnValue: return result.Value case *object.Error: return result } } return result } func evalBlockStatements(block *ast.BlockStatement, env *object.Environment) object.Object { var result object.Object for _, statement := range block.Statements { result = Eval(statement, env) if result != nil { rt := result.Type() if rt == object.ReturnType || rt == object.ErrorType { return result } } } return result } func nativeBoolToBooleanObject(input bool) object.Object { if input { return TRUE } return FALSE } func evalPrefixExpression(operator string, right object.Object) object.Object { switch operator { case "!": if right.Type() == object.BooleanType { return evalBooleanPrefixOperatorExpression(operator, right) } return evalIntegerPrefixOperatorExpression(operator, right) case "~", "-": return evalIntegerPrefixOperatorExpression(operator, right) default: return newError("unknown operator: %s%s", operator, right.Type()) } } func evalBooleanPrefixOperatorExpression(operator string, right object.Object) object.Object { if right.Type() != object.BooleanType { return newError("unknown operator: %s%s", operator, right.Type()) } switch right { case TRUE: return FALSE case FALSE: return TRUE case NULL: return TRUE default: return FALSE } } func evalIntegerPrefixOperatorExpression(operator string, right object.Object) object.Object { if right.Type() != object.IntegerType { return newError("unknown operator: -%s", right.Type()) } value := right.(*object.Integer).Value switch operator { case "!": return FALSE case "~": return &object.Integer{Value: ^value} case "-": return &object.Integer{Value: -value} default: return newError("unknown operator: %s", operator) } } func evalInfixExpression(operator string, left, right object.Object) object.Object { switch { // {"a": 1} + {"b": 2} case operator == "+" && left.Type() == object.HashType && right.Type() == object.HashType: leftVal := left.(*object.Hash).Pairs rightVal := right.(*object.Hash).Pairs pairs := make(map[object.HashKey]object.HashPair) for k, v := range leftVal { pairs[k] = v } for k, v := range rightVal { pairs[k] = v } return &object.Hash{Pairs: pairs} // [1] + [2] case operator == "+" && left.Type() == object.ArrayType && right.Type() == object.ArrayType: leftVal := left.(*object.Array).Elements rightVal := right.(*object.Array).Elements elements := make([]object.Object, len(leftVal)+len(rightVal)) elements = append(leftVal, rightVal...) return &object.Array{Elements: elements} // [1] * 3 case operator == "*" && left.Type() == object.ArrayType && right.Type() == object.IntegerType: leftVal := left.(*object.Array).Elements rightVal := int(right.(*object.Integer).Value) elements := leftVal for i := rightVal; i > 1; i-- { elements = append(elements, leftVal...) } return &object.Array{Elements: elements} // 3 * [1] case operator == "*" && left.Type() == object.IntegerType && right.Type() == object.ArrayType: leftVal := int(left.(*object.Integer).Value) rightVal := right.(*object.Array).Elements elements := rightVal for i := leftVal; i > 1; i-- { elements = append(elements, rightVal...) } return &object.Array{Elements: elements} // " " * 4 case operator == "*" && left.Type() == object.StringType && right.Type() == object.IntegerType: leftVal := left.(*object.String).Value rightVal := right.(*object.Integer).Value return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} // 4 * " " case operator == "*" && left.Type() == object.IntegerType && right.Type() == object.StringType: leftVal := left.(*object.Integer).Value rightVal := right.(*object.String).Value return &object.String{Value: strings.Repeat(rightVal, int(leftVal))} case operator == "==": return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0) case operator == "!=": return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0) case operator == "<=": return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) < 1) case operator == ">=": return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1) case operator == "<": return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == -1) case operator == ">": return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1) case left.Type() == object.BooleanType && right.Type() == object.BooleanType: return evalBooleanInfixExpression(operator, left, right) case left.Type() == object.IntegerType && right.Type() == object.IntegerType: return evalIntegerInfixExpression(operator, left, right) case left.Type() == object.StringType && right.Type() == object.StringType: return evalStringInfixExpression(operator, left, right) default: return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } func evalBooleanInfixExpression(operator string, left, right object.Object) object.Object { leftVal := left.(*object.Boolean).Value rightVal := right.(*object.Boolean).Value switch operator { case "&&": return nativeBoolToBooleanObject(leftVal && rightVal) case "||": return nativeBoolToBooleanObject(leftVal || rightVal) default: return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } func evalStringInfixExpression(operator string, left object.Object, right object.Object) object.Object { leftVal := left.(*object.String).Value rightVal := right.(*object.String).Value switch operator { case "+": return &object.String{Value: leftVal + rightVal} default: return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object { leftVal := left.(*object.Integer).Value rightVal := right.(*object.Integer).Value switch operator { case "+": return &object.Integer{Value: leftVal + rightVal} case "-": return &object.Integer{Value: leftVal - rightVal} case "*": return &object.Integer{Value: leftVal * rightVal} case "/": return &object.Integer{Value: leftVal / rightVal} case "%": return &object.Integer{Value: leftVal % rightVal} case "|": return &object.Integer{Value: leftVal | rightVal} case "^": return &object.Integer{Value: leftVal ^ rightVal} case "&": return &object.Integer{Value: leftVal & rightVal} case "<<": return &object.Integer{Value: leftVal << uint64(rightVal)} case ">>": return &object.Integer{Value: leftVal >> uint64(rightVal)} case "<": return nativeBoolToBooleanObject(leftVal < rightVal) case "<=": return nativeBoolToBooleanObject(leftVal <= rightVal) case ">": return nativeBoolToBooleanObject(leftVal > rightVal) case ">=": return nativeBoolToBooleanObject(leftVal >= rightVal) case "==": return nativeBoolToBooleanObject(leftVal == rightVal) case "!=": return nativeBoolToBooleanObject(leftVal != rightVal) default: return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { condition := Eval(ie.Condition, env) if isError(condition) { return condition } if isTruthy(condition) { return Eval(ie.Consequence, env) } else if ie.Alternative != nil { return Eval(ie.Alternative, env) } else { return NULL } } func isTruthy(obj object.Object) bool { switch obj { case NULL: return false case TRUE: return true case FALSE: return false default: return true } } func newError(format string, a ...interface{}) *object.Error { return &object.Error{Message: fmt.Sprintf(format, a...)} } // EvalModule evaluates the named module and returns a *object.Module objec func EvalModule(name string) object.Object { filename := utils.FindModule(name) b, err := os.ReadFile(filename) if err != nil { return newError("IOError: error reading module '%s': %s", name, err) } l := lexer.New(string(b)) p := parser.New(fmt.Sprintf("", name), l) module := p.ParseProgram() if len(p.Errors()) != 0 { return newError("ParseError: %s", p.Errors()) } env := object.NewEnvironment() Eval(module, env) return env.ExportedHash() } func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { if val, ok := env.Get(node.Value); ok { return val } if builtin, ok := builtins.Builtins[node.Value]; ok { return builtin } return newError("identifier not found: " + node.Value) } func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object for _, e := range exps { evaluated := Eval(e, env) if isError(evaluated) { return []object.Object{evaluated} } result = append(result, evaluated) } return result } func applyFunction(fn object.Object, args []object.Object) object.Object { switch fn := fn.(type) { case *object.Function: extendedEnv := extendFunctionEnv(fn, args) evaluated := Eval(fn.Body, extendedEnv) return unwrapReturnValue(evaluated) case *object.Builtin: if result := fn.Fn(args...); result != nil { return result } return NULL default: return newError("not a function: %s", fn.Type()) } } func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment { env := object.NewEnclosedEnvironment(fn.Env) for paramIdx, param := range fn.Parameters { env.Set(param.Value, args[paramIdx]) } return env } func unwrapReturnValue(obj object.Object) object.Object { if returnValue, ok := obj.(*object.ReturnValue); ok { return returnValue.Value } return obj } func evalIndexExpression(left, index object.Object) object.Object { switch { case left.Type() == object.StringType && index.Type() == object.IntegerType: return evalStringIndexExpression(left, index) case left.Type() == object.ArrayType && index.Type() == object.IntegerType: return evalArrayIndexExpression(left, index) case left.Type() == object.HashType: return evalHashIndexExpression(left, index) case left.Type() == object.ModuleType: return EvalModuleIndexExpression(left, index) default: return newError("index operator not supported: %s", left.Type()) } } func EvalModuleIndexExpression(module, index object.Object) object.Object { moduleObject := module.(*object.Module) return evalHashIndexExpression(moduleObject.Attrs, index) } func evalStringIndexExpression(str, index object.Object) object.Object { stringObject := str.(*object.String) idx := index.(*object.Integer).Value max := int64((len(stringObject.Value)) - 1) if idx < 0 || idx > max { return &object.String{Value: ""} } return &object.String{Value: string(stringObject.Value[idx])} } func evalHashIndexExpression(hash, index object.Object) object.Object { hashObject := hash.(*object.Hash) key, ok := index.(object.Hashable) if !ok { return newError("unusable as hash key: %s", index.Type()) } pair, ok := hashObject.Pairs[key.HashKey()] if !ok { return NULL } return pair.Value } func evalArrayIndexExpression(array, index object.Object) object.Object { arrayObject := array.(*object.Array) idx := index.(*object.Integer).Value maxInx := int64(len(arrayObject.Elements) - 1) if idx < 0 || idx > maxInx { return NULL } return arrayObject.Elements[idx] } func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Object { pairs := make(map[object.HashKey]object.HashPair) for keyNode, valueNode := range node.Pairs { key := Eval(keyNode, env) if isError(key) { return key } hashKey, ok := key.(object.Hashable) if !ok { return newError("unusable as hash key: %s", key.Type()) } value := Eval(valueNode, env) if isError(value) { return value } hashed := hashKey.HashKey() pairs[hashed] = object.HashPair{ Key: key, Value: value, } } return &object.Hash{Pairs: pairs} }