BlackToppStudios / Mezz_Foundation

Foundational Data types that enforce no opinions on how to build games and provide many facilities that complex games need. This largely avoids graphics and physics, but provides tools like the SortedVector, CountedPtr and HashedString classes.
GNU General Public License v3.0
3 stars 0 forks source link

This is a rough draft using the new Jagati exceptions feature. #64

Closed Sqeaky closed 4 years ago

Sqeaky commented 4 years ago

This still needs some exception classes declared and updates to download the correct jagati version (once the Jagati Feature-Exceptions PR is merged). But I wanted others to see progress so far.

When merged this should resolve #42 and set perhaps resolve #46. I still need to find and replace all the throws, but that should be simple.

Here is a sample MezzException.h that this autogenerates (and therefore won't be in git):

// © Copyright 2010 - 2020 BlackTopp Studios Inc.
/* This file is part of The Mezzanine Engine.

    The Mezzanine Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    The Mezzanine Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with The Mezzanine Engine.  If not, see <http://www.gnu.org/licenses/>.
*/
/* The original authors have included a copy of the license specified above in the
   'Docs' folder. See 'gpl.txt'
*/
/* We welcome the use of the Mezzanine engine to anyone, including companies who wish to
   Build professional software and charge for their product.

   However there are some practical restrictions, so if your project involves
   any of the following you should contact us and we will try to work something
   out:
    - DRM or Copy Protection of any kind(except Copyrights)
    - Software Patents You Do Not Wish to Freely License
    - Any Kind of Linking to Non-GPL licensed Works
    - Are Currently In Violation of Another Copyright Holder's GPL License
    - If You want to change our code and not add a few hundred MB of stuff to
        your distribution

   These and other limitations could cause serious legal problems if you ignore
   them, so it is best to simply contact us or the Free Software Foundation, if
   you have any questions.

   Joseph Toppi - toppij@gmail.com
   John Blackwood - makoenergy02@gmail.com
*/

#ifndef Mezz_Exception_h
#define Mezz_Exception_h

#include "DataTypes.h"

namespace Mezzanine
{
namespace Exception
{

/// @brief A collection of number corresponding 1 to 1 with each exception class.
enum class ExceptionCode : Whole
{
    FirstCode = 0, ///< Used to indicate the numerical start of the range of exception codes.
    NotAnExceptionCode = 0, ///< Used to indicate no exception at all.
    FirstValidCode = 1, ///< Used to indicate the numerical start of the range of VALID exception codes.
    BaseCode = 1, ///< Corresponds to the base exception class @ref Exception::Base .
    InputOutPutCode = 2,
    LastCode = 2 ///< Used to indicate the numerical end of the range of exceptions.
};

/// @brief Convert an ExceptionCode to a string containing the class name.
/// @param Code The number you have that you want as a string.
/// @return A StringView with a class name like "Exception::InputOutPut"
StringView ExceptionClassNameFromCode(const Mezzanine::Exception::ExceptionCode Code);

/// @brief Get the ExceptionCode for the given string.
/// @param ClassName The string to convert.
/// @return A valid entry from the ExceptionCode enum or ExceptionCode::NotAnExceptionCode for invalid input.
Mezzanine::Exception::ExceptionCode ExceptionCodeFromClassname(String ClassName);

/// @brief A simple way to hash Exception names at compile time.
/// @details This is intended to be simple and fast to compile not rigorously checked for other hashing purposes
/// and this shouldn't be used elsewhere.
/// @param ToHash The string to hash.
/// @param Index The current character to bake into the hash, defaults to 0 and is used to seek the end.
/// @return An unsigned int at compile time that is a hash of the name passed.
/// @note Copied from StackOverflow with written permission under under cc by-sa 4.0 with attribution required
/// https://stackoverflow.com/questions/16388510/evaluate-a-string-with-a-switch-in-c .
constexpr unsigned int ExceptionNameHash(const char* ToHash, int Index = 0);

/// @brief Stream Exception code value by converting them to strings.
/// @param Stream Any valid std::ostream, like cout or any fstream to send this too.
/// @param Code Any ExceptionCode instance to emit.
/// @return The passed ostream is returned to allow for operator chaining.
std::ostream& operator<<(std::ostream& Stream, Mezzanine::Exception::ExceptionCode Code);

/// @brief This is intended to be the base class for all exceptions in the Mezzanine.
class MEZZ_LIB Base : public std::exception
{
private:
    /// @brief This stores the Error Message.
    const Mezzanine::String ErrorMessage;
    /// @brief This stores the function name where the exception originated.
    const Mezzanine::String Function;
    /// @brief This stores the file where the exception originated.
    const Mezzanine::String File;
    /// @brief This stores the line number where the exception originated.
    const Mezzanine::Whole Line;

public:
    /// @brief Fully initializing constructor.
    /// @param Message The error message.git
    /// @param SrcFunction The name of the throwing function.
    /// @param SrcFile The name of the throwing file.
    /// @param FileLine The number of the throwing line.
    Base(const Mezzanine::StringView& Message,
                  const Mezzanine::StringView& SrcFunction,
                  const Mezzanine::StringView& SrcFile,
                  const Mezzanine::Whole FileLine)
        : ErrorMessage(Message),
          Function(SrcFunction),
          File(SrcFile),
          Line(FileLine)
    {}

    /// @brief Default Copy Constructor
    Base(const Base&) = default;
    /// @brief Default Move Constructor
    Base(Base&&) = default;
    /// @brief Virtual Deconstructor.
    virtual ~Base() = default;

    /// @brief Get the Error Message associated with this exception.
    /// @return A StringView containing the error message.
    virtual StringView GetMessage() const noexcept
        { return ErrorMessage; }

    /// @brief Get the function this was thrown from.
    /// @return A StringView containing the function that threw this.
    virtual StringView GetOriginatingFunction() const noexcept
        { return Function; }

    /// @brief Get the file this was thrown from.
    /// @return A StringView containing the file with the code throwing this.
    virtual StringView GetOriginatingFile() const noexcept
        { return File; }

    /// @brief Get the line this was thrown from.
    /// @return A Whole containing the line number this was thrown from.
    virtual Whole GetOriginatingLine() const noexcept
        { return Line; }

    /// @brief Get the typename of this.
    /// @return A StringView containing a human readable name for this type, "Base".
    static StringView TypeName() noexcept
        { return "Base"; }

    /// @brief Get the error message in the std compatible way.
    /// @return A pointer to a C-String, containing the same messages as @ref GetMessage().
    virtual const char* what() const noexcept
        { return ErrorMessage.c_str(); }

}; // Base Exception class

/// @brief Template class that serves as the base for exception factories.
/// @details Additional exceptions and their factories have to specialize from this template changing the type value
/// to the new exception type.This allows our exception macro to find the appropriate factory at compile when
/// template are being resolved. So this system can be extended with additional exceptions wherever desired.
/// Attempting to create an unknown exception simply won't compile because the base exception class being abstract.
template <Mezzanine::Exception::ExceptionCode N>
struct ExceptionFactory
{
    /// @brief This allows parameterized uses of this type so exception can be throw without directly using the type.
    using Type = Base;
    //typedef BaseException Type;
};

/// @brief Base Exception for all IO
class MEZZ_LIB InputOutPut : public Base
{
public:
    /// @copydoc Mezzanine::Exception::Base::Base
    InputOutPut
        ( const Mezzanine::StringView& Message,
          const Mezzanine::StringView& SrcFunction,
          const Mezzanine::StringView& SrcFile,
          const Mezzanine::Whole FileLine)
      : Base(Message, SrcFunction, SrcFile, FileLine)
    {}

    /// @brief Default Copy Constructor
    InputOutPut(const InputOutPut&) = default;
    /// @brief Default Move Constructor
    InputOutPut(InputOutPut&&) = default;
    /// @brief Virtual Deconstructor.
    virtual ~InputOutPut() = default;
    /// @return A StringView containing a human readable name for this type, "InputOutPut".
    static StringView TypeName() noexcept
        { return "InputOutPut"; }
}; // InputOutPut

template<>
struct ExceptionFactory<ExceptionCode::InputOutPutCode>
{
    /// @copydoc ExceptionFactory::Type
    using Type = InputOutPut;
};

#ifndef MEZZ_EXCEPTION
/// @brief An easy way to throw exceptions with rich information.
/// @details An important part of troubleshooting errors from the users perspective is being able to tie a specific
/// 'fix' to a specific error message. An important part of that is catching the right exceptions at the right time.
/// It is also important to not allocate more memory or other resources while creating an exception.
/// @n @n
/// This macro makes doing all of these easy. Every exception thrown by this macro with provide the function name,
/// the file name and the line in the file from which it was thrown. That provides all the information the developer
/// needs to identify the issue. This uses some specific template machinery to generate specifically typed exceptions
/// static instances at compile to insure the behaviors a programmer needs. Since these are allocated (optimized out
/// really) when the program is first loaded so there will be no allocations when this is called, and the type is
/// controlled by the error number parameter.
/// @n @n
/// As long as the developer provides a unique string for each failure, then any messages logged or presented to the
/// user or log will uniquely identify that specific problem. This allows the user to perform very specific web
/// searches and potentially allows troubleshooters/technicians to skip lengthy diagnostics steps.
/// @param num A specific code from the @ref ExceptionBase::ExceptionCodes enum will control the type of exception
/// produced.
/// @param desc A message/description to be passed through to the exceptions constructor.
#define MEZZ_EXCEPTION(num, msg) throw Mezzanine::Exception::ExceptionFactory<Mezzanine::Exception::ExceptionCode::num>::Type(msg, __func__, __FILE__, __LINE__ );
#endif

StringView ExceptionClassNameFromCode(const Mezzanine::Exception::ExceptionCode Code)
{
    switch(Code)
    {
        case ExceptionCode::InputOutPutCode: return "InputOutPut";
        case ExceptionCode::BaseCode: return "Base";
        case ExceptionCode::NotAnExceptionCode: return "NotAnException";
    }
}

constexpr unsigned int ExceptionNameHash(const char* ToHash, int Index)
{
    return !ToHash[Index] ?
           5381 :
           (ExceptionNameHash(ToHash, Index+1) * 33) ^ static_cast<unsigned int>(ToHash[Index]);
}

Mezzanine::Exception::ExceptionCode ExceptionCodeFromClassname(String ClassName)
{
    switch(ExceptionNameHash(ClassName.c_str()))
    {
        case ExceptionNameHash("InputOutPut"): return ExceptionCode::InputOutPutCode;
        case ExceptionNameHash("Base"): return ExceptionCode::BaseCode;
        default: return ExceptionCode::NotAnExceptionCode;
    }
}

std::ostream& operator<<(std::ostream& Stream, Mezzanine::Exception::ExceptionCode Code)
{
    return Stream << "Exception::"
                  << Mezzanine::Exception::ExceptionClassNameFromCode(Code);
}

} // namespace Exception
} // namespace Mezzanine

#endif // Mezz_Exception_h
Sqeaky commented 4 years ago

This fails builds because it needs the Jagati version of this PR.

codecov[bot] commented 4 years ago

Codecov Report

:exclamation: No coverage uploaded for pull request base (master@011a04a). Click here to learn what that means. The diff coverage is 93.96%.

@@            Coverage Diff            @@
##             master      #64   +/-   ##
=========================================
  Coverage          ?   95.87%           
=========================================
  Files             ?       21           
  Lines             ?     1116           
  Branches          ?        0           
=========================================
  Hits              ?     1070           
  Misses            ?       46           
  Partials          ?        0
Sqeaky commented 4 years ago

I also added some comments to clarify the purpose of the of the exceptions tests.

MakoEnergy commented 4 years ago

Wait, new problem....where are the Jenkins results?