The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

parcel Lucy;

/**
 * Exception.
 *
 * Most of the time when Lucy encounters an error, it tries to raise a
 * Lucy::Object::Err exception with an error message and context
 * information.
 *
 * At present, it is only safe to catch exceptions which are specifically
 * documented as catchable; most times when an Err is raised, Lucy leaks
 * memory.
 *
 * The Err module also provides access to a per-thread Err shared variable via
 * set_error() and get_error().  It may be used to store an Err object
 * temporarily, so that calling code may choose how to handle a particular
 * error condition.
 */
class Lucy::Object::Err inherits Lucy::Object::Obj {

    CharBuf *mess;

    inert incremented Err*
    new(decremented CharBuf *mess);

    inert Err*
    init(Err *self, decremented CharBuf *mess);

    public void
    Destroy(Err *self);

    public incremented CharBuf*
    To_String(Err *self);

    void*
    To_Host(Err *self);

    /** Concatenate the supplied argument onto the internal "mess".
     */
    public void
    Cat_Mess(Err *self, const CharBuf *mess);

    public CharBuf*
    Get_Mess(Err *self);

    /** Add information about the current stack frame onto <code>mess</code>.
     */
    void
    Add_Frame(Err *self, const char *file, int line, const char *func);

    public incremented Err*
    Make(Err *self);

    /** Set the value of "error", a per-thread Err shared variable.
     */
    public inert void
    set_error(decremented Err *error);

    /** Retrieve per-thread Err shared variable "error".
     */
    public inert nullable Err*
    get_error();

    /** Print an error message to stderr with some C contextual information.
     * Usually invoked via the WARN(pattern, ...) macro.
     */
    inert void
    warn_at(const char *file, int line, const char *func,
            const char *pattern, ...);

    /** Raise an exception. Usually invoked via the THROW macro.
     */
    inert void
    throw_at(VTable *vtable, const char *file, int line, const char *func,
               const char *pattern, ...);

    /** Throw an existing exception after tacking on additional context data.
     */
    inert void
    rethrow(Err *error, const char *file, int line, const char *func);

    /** Raise an exception.  Clean up the supplied message by decrementing its
     * refcount.
     *
     * @param vtable The vtable for the Err class to throw.
     * @param message Error message, to be output verbatim.
     */
    inert void
    throw_mess(VTable *vtable, decremented CharBuf *message);

    /** Invoke host exception handling.
     */
    inert void
    do_throw(decremented Err *self);

    /** Invoke host warning mechanism.  Clean up the supplied message by
     * decrementing its refcount.
     *
     * @param message Error message, to be output verbatim.
     */
    inert void
    warn_mess(decremented CharBuf *message);

    /** Create a formatted error message.  Ususally invoked via the MAKE_MESS
     * macro.
     */
    inert CharBuf*
    make_mess(const char *file, int line, const char *func,
              const char *pattern, ...);

    /** Verify that <code>obj</code> is either NULL or inherits from
     * the class represented by <code>vtable</code>.
     *
     * @return the object.
     */
    inert nullable Obj*
    downcast(Obj *obj, VTable *vtable, const char *file, int line,
                const char *func);

    /** Verify that <code>obj</code> is not NULL and inherits from the class
     * represented by <code>vtable</code>.
     *
     * @return the object.
     */
    inert Obj*
    certify(Obj *obj, VTable *vtable, const char *file, int line,
            const char *func);

    /** Verify that an object belongs to a subclass and not an abstract class.
     */
    inert inline void
    abstract_class_check(Obj *obj, VTable *vtable);

    /** On Windows, return a newly allocated buffer containing the string
     * description for the the last error in the thread.
     */
    inert char*
    win_error();
}

__C__
#ifdef CHY_HAS_FUNC_MACRO
 #define CFISH_ERR_FUNC_MACRO CHY_FUNC_MACRO
#else
 #define CFISH_ERR_FUNC_MACRO NULL
#endif

#define CFISH_ERR_ADD_FRAME(_error) \
    Lucy_Err_Add_Frame(_error, __FILE__, __LINE__, \
                       CFISH_ERR_FUNC_MACRO)

#define CFISH_RETHROW(_error) \
    lucy_Err_rethrow((lucy_Err*)_error, __FILE__, __LINE__, \
                     CFISH_ERR_FUNC_MACRO)

/** Macro version of lucy_Err_throw_at which inserts contextual information
 * automatically, provided that the compiler supports the necessary features.
 */
#ifdef CHY_HAS_VARIADIC_MACROS
 #ifdef CHY_HAS_ISO_VARIADIC_MACROS
  #define CFISH_THROW(_vtable, ...) \
    lucy_Err_throw_at(_vtable, __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO, \
                      __VA_ARGS__)
  #define CFISH_WARN(...) \
    lucy_Err_warn_at(__FILE__, __LINE__, CFISH_ERR_FUNC_MACRO, __VA_ARGS__)
  #define CFISH_MAKE_MESS(...) \
    lucy_Err_make_mess(__FILE__, __LINE__, CFISH_ERR_FUNC_MACRO, \
                       __VA_ARGS__)
 #elif defined(CHY_HAS_GNUC_VARIADIC_MACROS)
  #define CFISH_THROW(_vtable, args...) \
    lucy_Err_throw_at(_vtable, __FILE__, __LINE__, \
                      CFISH_ERR_FUNC_MACRO, ##args)
  #define CFISH_WARN(args...) \
    lucy_Err_warn_at(__FILE__, __LINE__, CFISH_ERR_FUNC_MACRO, ##args)
  #define CFISH_MAKE_MESS(args...) \
    lucy_Err_make_mess(__FILE__, __LINE__, CFISH_ERR_FUNC_MACRO, ##args)
 #endif
#else
  void
  CFISH_THROW(lucy_VTable *vtable, char* format, ...);
  void
  CFISH_WARN(char* format, ...);
  lucy_CharBuf*
  CFISH_MAKE_MESS(char* format, ...);
#endif

#define CFISH_DOWNCAST(_obj, _vtable) \
    lucy_Err_downcast((lucy_Obj*)(_obj), (_vtable), \
                      __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)


#define CFISH_CERTIFY(_obj, _vtable) \
    lucy_Err_certify((lucy_Obj*)(_obj), (_vtable), \
                     __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)

static CHY_INLINE void
lucy_Err_abstract_class_check(lucy_Obj *obj, lucy_VTable *vtable) {
    lucy_VTable *const my_vtable = *(lucy_VTable**)obj;
    if (my_vtable == vtable) {
        lucy_CharBuf *mess = CFISH_MAKE_MESS("%o is an abstract class",
                                             Lucy_Obj_Get_Class_Name(obj));
        Lucy_Obj_Dec_RefCount(obj);
        lucy_Err_throw_mess(LUCY_ERR, mess);
    }
}

#define CFISH_ABSTRACT_CLASS_CHECK(_obj, _vtable) \
    lucy_Err_abstract_class_check(((lucy_Obj*)_obj), _vtable)

#ifdef LUCY_USE_SHORT_NAMES
  #define THROW                 CFISH_THROW
  #define RETHROW               CFISH_RETHROW
  #define WARN                  CFISH_WARN
  #define MAKE_MESS             CFISH_MAKE_MESS
  #define ERR_ADD_FRAME         CFISH_ERR_ADD_FRAME
  #define ERR_FUNC_MACRO        CFISH_ERR_FUNC_MACRO
  #define DOWNCAST              CFISH_DOWNCAST
  #define CERTIFY               CFISH_CERTIFY
  #define ABSTRACT_CLASS_CHECK  CFISH_ABSTRACT_CLASS_CHECK
#endif
__END_C__