package evaluator import ( "fmt" "monkey/internal/ast" "monkey/internal/builtins" "monkey/internal/context" "monkey/internal/lexer" "monkey/internal/object" "monkey/internal/ops" "monkey/internal/parser" "monkey/internal/utils" "os" ) func isError(obj object.Object) bool { if obj != nil { return obj.Type() == object.ErrorType } return false } func Eval(ctx context.Context, node ast.Node, env *object.Environment) object.Object { switch node := node.(type) { // Statements case *ast.Program: return evalProgram(ctx, node, env) case *ast.ExpressionStatement: return Eval(ctx, node.Expression, env) case *ast.BlockStatement: return evalBlockStatements(ctx, node, env) case *ast.IfExpression: return evalIfExpression(ctx, node, env) case *ast.WhileExpression: return evalWhileExpression(ctx, node, env) case *ast.ImportExpression: return evalImportExpression(ctx, node, env) case *ast.ReturnStatement: val := Eval(ctx, node.ReturnValue, env) if isError(val) { return val } return object.ReturnValue{Value: val} case *ast.BindExpression: value := Eval(ctx, node.Value, env) if isError(value) { return value } if ident, ok := node.Left.(*ast.Identifier); ok { if obj, ok := value.(object.Copyable); ok { env.Set(ident.Value, obj.Copy()) } else { env.Set(ident.Value, value) } return object.NULL } return newError("expected identifier on left got=%T", node.Left) case *ast.AssignmentExpression: left := Eval(ctx, node.Left, env) if isError(left) { return left } value := Eval(ctx, 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(ctx, ie.Left, env) if isError(obj) { return obj } if array, ok := obj.(*object.Array); ok { index := Eval(ctx, 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(ctx, ie.Index, env) if isError(key) { return key } if hashKey, ok := key.(object.Hasher); ok { hashed := hashKey.Hash() 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 object.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.FloatLiteral: return object.Float{Value: node.Value} case *ast.Boolean: return object.FromNativeBoolean(node.Value) case *ast.Null: return object.NULL case *ast.PrefixExpression: right := Eval(ctx, node.Right, env) if isError(right) { return right } return evalPrefixExpression(node.Operator, right) case *ast.InfixExpression: left := Eval(ctx, node.Left, env) if isError(left) { return left } right := Eval(ctx, node.Right, env) if isError(right) { return right } return evalInfixExpression(node.Operator, left, right) case *ast.CallExpression: function := Eval(ctx, node.Function, env) if isError(function) { return function } args := evalExpressions(ctx, node.Arguments, env) if len(args) == 1 && isError(args[0]) { return args[0] } return applyFunction(ctx, function, args) case *ast.StringLiteral: return object.String{Value: node.Value} case *ast.ArrayLiteral: elements := evalExpressions(ctx, node.Elements, env) if len(elements) == 1 && isError(elements[0]) { return elements[0] } return &object.Array{Elements: elements} case *ast.IndexExpression: left := Eval(ctx, node.Left, env) if isError(left) { return left } index := Eval(ctx, node.Index, env) if isError(index) { return index } return evalIndexExpression(left, index) case *ast.HashLiteral: return evalHashLiteral(ctx, node, env) } return nil } func evalImportExpression(ctx context.Context, ie *ast.ImportExpression, env *object.Environment) object.Object { name := Eval(ctx, ie.Name, env) if isError(name) { return name } if s, ok := name.(object.String); ok { attrs := Module(ctx, 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(ctx context.Context, we *ast.WhileExpression, env *object.Environment) object.Object { var result object.Object for { condition := Eval(ctx, we.Condition, env) if isError(condition) { return condition } if object.IsTruthy(condition) { result = Eval(ctx, we.Consequence, env) } else { break } } if result != nil { return result } return object.NULL } func evalProgram(ctx context.Context, program *ast.Program, env *object.Environment) object.Object { var result object.Object for _, statement := range program.Statements { result = Eval(ctx, statement, env) switch result := result.(type) { case object.ReturnValue: return result.Value case object.Error: return result } } return result } func evalBlockStatements(ctx context.Context, block *ast.BlockStatement, env *object.Environment) object.Object { var result object.Object for _, statement := range block.Statements { result = Eval(ctx, statement, env) if result != nil { rt := result.Type() if rt == object.ReturnType || rt == object.ErrorType { return result } } } return result } 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("unsupported operator: %s%s", operator, right.Type()) } } func evalBooleanPrefixOperatorExpression(operator string, right object.Object) object.Object { if right.Type() != object.BooleanType { return newError("unsupported operator: %s%s", operator, right.Type()) } switch right { case object.TRUE: return object.FALSE case object.FALSE: return object.TRUE case object.NULL: return object.TRUE default: return object.FALSE } } func evalIntegerPrefixOperatorExpression(operator string, right object.Object) object.Object { if right.Type() != object.IntegerType { return newError("unsupported operator: -%s", right.Type()) } value := right.(object.Integer).Value switch operator { case "!": return object.FALSE case "~": return object.Integer{Value: ^value} case "-": return object.Integer{Value: -value} default: return newError("unsupported operator: %s", operator) } } func evalInfixExpression(operator string, left, right object.Object) object.Object { switch { case operator == "==": return object.FromNativeBoolean(left.Compare(right) == 0) case operator == "!=": return object.FromNativeBoolean(left.Compare(right) != 0) case operator == "<=": return object.FromNativeBoolean(left.Compare(right) < 1) case operator == ">=": return object.FromNativeBoolean(left.Compare(right) > -1) case operator == "<": return object.FromNativeBoolean(left.Compare(right) == -1) case operator == ">": return object.FromNativeBoolean(left.Compare(right) == 1) case left.Type() == right.Type() && left.Type() == object.BooleanType: return evalBooleanInfixExpression(operator, left, right) case operator == "+": if val, err := ops.Add(left, right); err != nil { return newError(err.Error()) } else { return val } case operator == "-": if val, err := ops.Sub(left, right); err != nil { return newError(err.Error()) } else { return val } case operator == "*": if val, err := ops.Mul(left, right); err != nil { return newError(err.Error()) } else { return val } case operator == "/": if val, err := ops.Div(left, right); err != nil { return newError(err.Error()) } else { return val } case operator == "%": if val, err := ops.Mod(left, right); err != nil { return newError(err.Error()) } else { return val } default: return newError("unsupported 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 object.FromNativeBoolean(leftVal && rightVal) case "||": return object.FromNativeBoolean(leftVal || rightVal) default: return newError("unsupported operator: %s %s %s", left.Type(), operator, right.Type()) } } func evalIfExpression(ctx context.Context, ie *ast.IfExpression, env *object.Environment) object.Object { condition := Eval(ctx, ie.Condition, env) if isError(condition) { return condition } if object.IsTruthy(condition) { return Eval(ctx, ie.Consequence, env) } else if ie.Alternative != nil { return Eval(ctx, ie.Alternative, env) } else { return object.NULL } } func newError(format string, a ...interface{}) object.Error { return object.Error{Message: fmt.Sprintf(format, a...)} } // Module evaluates the named module and returns a object.Module object func Module(ctx context.Context, 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(ctx, 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(ctx context.Context, exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object for _, e := range exps { evaluated := Eval(ctx, e, env) if isError(evaluated) { return []object.Object{evaluated} } result = append(result, evaluated) } return result } func applyFunction(ctx context.Context, fn object.Object, args []object.Object) object.Object { switch fn := fn.(type) { case object.Function: extendedEnv := extendFunctionEnv(fn, args) evaluated := Eval(ctx, fn.Body, extendedEnv) return unwrapReturnValue(evaluated) case object.Builtin: if result := fn.Fn(ctx, args...); result != nil { return result } return object.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.Hasher) if !ok { return newError("unusable as hash key: %s", index.Type()) } pair, ok := hashObject.Pairs[key.Hash()] if !ok { return object.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 object.NULL } return arrayObject.Elements[idx] } func evalHashLiteral(ctx context.Context, node *ast.HashLiteral, env *object.Environment) object.Object { pairs := make(map[object.HashKey]object.HashPair) for keyNode, valueNode := range node.Pairs { key := Eval(ctx, keyNode, env) if isError(key) { return key } hashKey, ok := key.(object.Hasher) if !ok { return newError("unusable as hash key: %s", key.Type()) } value := Eval(ctx, valueNode, env) if isError(value) { return value } hashed := hashKey.Hash() pairs[hashed] = object.HashPair{ Key: key, Value: value, } } return &object.Hash{Pairs: pairs} }