goruby/goruby

View on GitHub
object/exception.go

Summary

Maintainability
A
35 mins
Test Coverage
package object

import (
    "fmt"
    "reflect"
)

var (
    exceptionClass RubyClassObject = newClass(
        "Exception",
        objectClass,
        exceptionMethods,
        exceptionClassMethods,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &Exception{message: c.Name()}, nil
        },
    )
    standardErrorClass RubyClassObject = newClass(
        "StandardError",
        exceptionClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &StandardError{message: c.Name()}, nil
        },
    )
    runtimeErrorClass RubyClassObject = newClass(
        "RuntimeError",
        standardErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &RuntimeError{message: c.Name()}, nil
        },
    )
    zeroDivisionErrorClass RubyClassObject = newClass(
        "ZeroDivisionError",
        standardErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &ZeroDivisionError{message: c.Name()}, nil
        },
    )
    argumentErrorClass RubyClassObject = newClass(
        "ArgumentError",
        standardErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &ArgumentError{message: c.Name()}, nil
        },
    )
    nameErrorClass RubyClassObject = newClass(
        "NameError",
        standardErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &NameError{message: c.Name()}, nil
        },
    )
    noMethodErrorClass RubyClassObject = newClass(
        "NoMethodError",
        nameErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &NoMethodError{message: c.Name()}, nil
        },
    )
    typeErrorClass RubyClassObject = newClass(
        "TypeError",
        standardErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &TypeError{message: c.Name()}, nil
        },
    )
    scriptErrorClass RubyClassObject = newClass(
        "ScriptError",
        exceptionClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &ScriptError{message: c.Name()}, nil
        },
    )
    loadErrorClass RubyClassObject = newClass(
        "LoadError",
        scriptErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &LoadError{message: c.Name()}, nil
        },
    )
    syntaxErrorClass RubyClassObject = newClass(
        "SyntaxError",
        scriptErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &SyntaxError{message: c.Name()}, nil
        },
    )
    notImplementedErrorClass RubyClassObject = newClass(
        "NotImplementedError",
        scriptErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &NotImplementedError{message: c.Name()}, nil
        },
    )
    localJumpErrorClass RubyClassObject = newClass(
        "LocalJumpError",
        standardErrorClass,
        nil,
        nil,
        func(c RubyClassObject, args ...RubyObject) (RubyObject, error) {
            return &LocalJumpError{message: c.Name()}, nil
        },
    )
)

func init() {
    classes.Set("Exception", exceptionClass)
    classes.Set("StandardError", standardErrorClass)
    classes.Set("ZeroDivisionError", zeroDivisionErrorClass)
    classes.Set("ArgumentError", argumentErrorClass)
    classes.Set("NameError", nameErrorClass)
    classes.Set("NoMethodError", noMethodErrorClass)
    classes.Set("TypeError", typeErrorClass)
    classes.Set("ScriptError", scriptErrorClass)
    classes.Set("LoadError", loadErrorClass)
    classes.Set("SyntaxError", syntaxErrorClass)
    classes.Set("NotImplementedError", notImplementedErrorClass)
}

func formatException(exception RubyObject, message string) string {
    return fmt.Sprintf("%s: %s", reflect.TypeOf(exception).Elem().Name(), message)
}

type exception interface {
    setErrorMessage(string)
    error
}

// NewException creates a new exception with the given message template and
//uses fmt.Sprintf to interpolate the args into messageinto message.
func NewException(message string, args ...interface{}) *Exception {
    return &Exception{message: fmt.Sprintf(message, args...)}
}

// Exception represents a basic exception
type Exception struct {
    message string
}

// Type returns the type of the RubyObject
func (e *Exception) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *Exception) Inspect() string { return formatException(e, e.message) }
func (e *Exception) Error() string   { return e.message }

func (e *Exception) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns exceptionClass
func (e *Exception) Class() RubyClass { return exceptionClass }

var exceptionClassMethods = map[string]RubyMethod{
    "exception": publicMethod(exceptionClassException),
}

var exceptionMethods = map[string]RubyMethod{
    "initialize": privateMethod(exceptionInitialize),
    "exception":  publicMethod(exceptionException),
    "to_s":       withArity(0, publicMethod(exceptionToS)),
}

func exceptionInitialize(context CallContext, args ...RubyObject) (RubyObject, error) {
    receiver := context.Receiver()
    if self, ok := receiver.(*Self); ok {
        receiver = self.RubyObject
    }
    var message string
    message = receiver.Class().Name()
    if len(args) == 1 {
        msg, err := stringify(args[0])
        if err != nil {
            return nil, err
        }
        message = msg
    }
    if exception, ok := receiver.(exception); ok {
        exception.setErrorMessage(message)
    }
    return receiver, nil
}

func exceptionClassException(context CallContext, args ...RubyObject) (RubyObject, error) {
    receiver := context.Receiver()
    var message string
    class, ok := receiver.(RubyClass)
    if ok {
        receiver, _ = class.New()
    }
    if class == nil {
        class = receiver.Class()
    }
    message = class.Name()
    if len(args) == 1 {
        msg, err := stringify(args[0])
        if err != nil {
            return nil, err
        }
        message = msg
    }
    if exception, ok := receiver.(exception); ok {
        msg := exception.Error()
        if msg != message {
            exception.setErrorMessage(message)
        }
    }
    return receiver, nil
}

func exceptionException(context CallContext, args ...RubyObject) (RubyObject, error) {
    receiver := context.Receiver()
    if len(args) == 0 {
        return receiver, nil
    }
    var oldMessage string
    if err, ok := receiver.(error); ok {
        oldMessage = err.Error()
    }
    message, err := stringify(args[0])
    if err != nil {
        return nil, err
    }

    if oldMessage != message {
        class := receiver.Class()
        exc, err := class.New()
        if err != nil {
            return nil, err
        }
        if exception, ok := exc.(exception); ok {
            exception.setErrorMessage(message)
        }
        return exc, nil
    }
    return receiver, nil
}

func exceptionToS(context CallContext, args ...RubyObject) (RubyObject, error) {
    receiver := context.Receiver()
    if err, ok := receiver.(exception); ok {
        return &String{Value: err.Error()}, nil
    }
    return nil, nil
}

// NewStandardError returns a StandardError with the given message
func NewStandardError(message string) *StandardError {
    return &StandardError{message: message}
}

// StandardError is the default class for rescue blocks
type StandardError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *StandardError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *StandardError) Inspect() string { return formatException(e, e.message) }
func (e *StandardError) Error() string   { return e.message }

func (e *StandardError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns standardErrorClass
func (e *StandardError) Class() RubyClass { return standardErrorClass }

// NewRuntimeError returns a new RuntimeError with the formatted message
func NewRuntimeError(format string, args ...interface{}) *RuntimeError {
    return &RuntimeError{
        message: fmt.Sprintf(format, args...),
    }
}

// RuntimeError is a generic error class raised when an invalid operation is attempted
type RuntimeError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *RuntimeError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *RuntimeError) Inspect() string { return formatException(e, e.message) }
func (e *RuntimeError) Error() string   { return e.message }

func (e *RuntimeError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns runtimeErrorClass
func (e *RuntimeError) Class() RubyClass { return runtimeErrorClass }

// NewZeroDivisionError returns a new ZeroDivisionError with the default message
func NewZeroDivisionError() *ZeroDivisionError {
    return &ZeroDivisionError{
        message: "divided by 0",
    }
}

// ZeroDivisionError represents an arithmethic error when dividing through 0
type ZeroDivisionError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *ZeroDivisionError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *ZeroDivisionError) Inspect() string { return formatException(e, e.message) }
func (e *ZeroDivisionError) Error() string   { return e.message }

func (e *ZeroDivisionError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns zeroDivisionErrorClass
func (e *ZeroDivisionError) Class() RubyClass { return zeroDivisionErrorClass }

// NewWrongNumberOfArgumentsError returns an ArgumentError populated with the default message
func NewWrongNumberOfArgumentsError(expected, actual int) *ArgumentError {
    return &ArgumentError{
        message: fmt.Sprintf(
            "wrong number of arguments (given %d, expected %d)",
            actual,
            expected,
        ),
    }
}

// NewArgumentError creates an ArgumentError. It has the same API as fmt.Errorf
func NewArgumentError(format string, args ...interface{}) *ArgumentError {
    return &ArgumentError{
        message: fmt.Sprintf(format, args...),
    }
}

// ArgumentError represents an error in method call arguments
type ArgumentError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *ArgumentError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *ArgumentError) Inspect() string { return formatException(e, e.message) }
func (e *ArgumentError) Error() string   { return e.message }

func (e *ArgumentError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns argumentErrorClass
func (e *ArgumentError) Class() RubyClass { return argumentErrorClass }

// NewUninitializedConstantNameError returns a NameError with the default message for uninitialized constants
func NewUninitializedConstantNameError(name string) *NameError {
    return &NameError{
        message: fmt.Sprintf(
            "uninitialized constant %s",
            name,
        ),
    }
}

// NewUndefinedLocalVariableOrMethodNameError returns a NameError with the default message for undefined names
func NewUndefinedLocalVariableOrMethodNameError(context RubyObject, name string) *NameError {
    return &NameError{
        message: fmt.Sprintf(
            "undefined local variable or method `%s' for %s:%s",
            name,
            context.Inspect(),
            context.Class().(RubyObject).Inspect(),
        ),
    }
}

// A NameError represents an error accessing an identifier unknown to the environment
type NameError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *NameError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *NameError) Inspect() string { return formatException(e, e.message) }
func (e *NameError) Error() string   { return e.message }

func (e *NameError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns nameErrorClass
func (e *NameError) Class() RubyClass { return nameErrorClass }

// NewNoMethodError returns a NoMethodError with the default message for undefined methods
func NewNoMethodError(context RubyObject, method string) *NoMethodError {
    return &NoMethodError{
        message: fmt.Sprintf(
            "undefined method `%s' for %s:%s",
            method,
            context.Inspect(),
            context.Class().(RubyObject).Inspect(),
        ),
    }
}

// NewPrivateNoMethodError returns a NoMethodError with the default message for private methods
func NewPrivateNoMethodError(context RubyObject, method string) *NoMethodError {
    return &NoMethodError{
        message: fmt.Sprintf(
            "private method `%s' called for %s:%s",
            method,
            context.Inspect(),
            context.Class().(RubyObject).Inspect(),
        ),
    }
}

// NoMethodError represents an error finding a fitting method on an object
type NoMethodError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *NoMethodError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *NoMethodError) Inspect() string { return formatException(e, e.message) }
func (e *NoMethodError) Error() string   { return e.message }

func (e *NoMethodError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns noMethodErrorClass
func (e *NoMethodError) Class() RubyClass { return noMethodErrorClass }

// NewWrongArgumentTypeError returns a TypeError with the default message for wrong arugument type errors
func NewWrongArgumentTypeError(expected, actual RubyObject) *TypeError {
    return &TypeError{
        message: fmt.Sprintf(
            "wrong argument type %s (expected %s)",
            reflect.TypeOf(actual).Elem().Name(),
            reflect.TypeOf(expected).Elem().Name(),
        ),
    }
}

// NewCoercionTypeError returns a TypeError with the default message for coercing errors
func NewCoercionTypeError(expected, actual RubyObject) *TypeError {
    return &TypeError{
        message: fmt.Sprintf(
            "%s can't be coerced into %s",
            reflect.TypeOf(actual).Elem().Name(),
            reflect.TypeOf(expected).Elem().Name(),
        ),
    }
}

// NewImplicitConversionTypeError returns a TypeError with the default message for impossible implicit conversions
func NewImplicitConversionTypeError(expected, actual RubyObject) *TypeError {
    return &TypeError{
        message: fmt.Sprintf(
            "no implicit conversion of %s into %s",
            reflect.TypeOf(actual).Elem().Name(),
            reflect.TypeOf(expected).Elem().Name(),
        ),
    }
}

// NewTypeError returns a TypeError with the provided message
func NewTypeError(message string) *TypeError {
    return &TypeError{message: message}
}

// TypeError represents an error when the given type does not fit in the given context
type TypeError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *TypeError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *TypeError) Inspect() string { return formatException(e, e.message) }
func (e *TypeError) Error() string   { return e.message }

func (e *TypeError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns typeErrorClass
func (e *TypeError) Class() RubyClass { return typeErrorClass }

// NewScriptError returns a new script error with the provided message
func NewScriptError(format string, args ...interface{}) *ScriptError {
    return &ScriptError{message: fmt.Sprintf(format, args...)}
}

// ScriptError represetns an error in the loaded script
type ScriptError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *ScriptError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *ScriptError) Inspect() string { return formatException(e, e.message) }
func (e *ScriptError) Error() string   { return e.message }

func (e *ScriptError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns scriptErrorClass
func (e *ScriptError) Class() RubyClass { return scriptErrorClass }

// NewNoSuchFileLoadError returns a new LoadError with the default message
func NewNoSuchFileLoadError(filepath string) *LoadError {
    return &LoadError{
        message: fmt.Sprintf(
            "cannot load such file -- %s",
            filepath,
        ),
    }
}

// LoadError represents an error while loading another file
type LoadError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *LoadError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *LoadError) Inspect() string { return formatException(e, e.message) }
func (e *LoadError) Error() string   { return e.message }

func (e *LoadError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns loadErrorClass
func (e *LoadError) Class() RubyClass { return loadErrorClass }

// NewSyntaxError returns a new SyntaxError with the default message
func NewSyntaxError(syntaxError error) *SyntaxError {
    return &SyntaxError{
        message: fmt.Sprintf(
            "syntax error, %s",
            syntaxError.Error(),
        ),
        err: syntaxError,
    }
}

// SyntaxError represents a syntax error in the ruby scripts
type SyntaxError struct {
    err     error
    message string
}

// Type returns EXCEPTION_OBJ
func (e *SyntaxError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *SyntaxError) Inspect() string { return formatException(e, e.message) }
func (e *SyntaxError) Error() string   { return e.message }

func (e *SyntaxError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns syntaxErrorClass
func (e *SyntaxError) Class() RubyClass { return syntaxErrorClass }

// UnderlyingError returns the parser error wrapped by SyntaxError
func (e *SyntaxError) UnderlyingError() error { return e.err }

// NewNotImplementedError returns a NotImplementedError with the provided message
func NewNotImplementedError(format string, args ...interface{}) *NotImplementedError {
    return &NotImplementedError{message: fmt.Sprintf(format, args...)}
}

// NotImplementedError represents an error for a not implemented feature on a given platform
type NotImplementedError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *NotImplementedError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *NotImplementedError) Inspect() string { return formatException(e, e.message) }
func (e *NotImplementedError) Error() string   { return e.message }

func (e *NotImplementedError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns notImplementedErrorClass
func (e *NotImplementedError) Class() RubyClass { return notImplementedErrorClass }

// NewNoBlockGivenLocalJumpError returns a LocalJumpError with the default message for missing blocks
func NewNoBlockGivenLocalJumpError() *LocalJumpError {
    return &LocalJumpError{message: "no block given (yield)"}
}

// LocalJumpError represents an error for a not supported jump
type LocalJumpError struct {
    message string
}

// Type returns EXCEPTION_OBJ
func (e *LocalJumpError) Type() Type { return EXCEPTION_OBJ }

// Inspect returns a string starting with the exception class name, followed by the message
func (e *LocalJumpError) Inspect() string { return formatException(e, e.message) }
func (e *LocalJumpError) Error() string   { return e.message }

func (e *LocalJumpError) setErrorMessage(msg string) {
    e.message = msg
}

// Class returns notImplementedErrorClass
func (e *LocalJumpError) Class() RubyClass { return notImplementedErrorClass }