/*  ****************************************************************************
    *  File: astris_except.h
    *
    *  Exception macros
    *
    ****************************************************************************
    *
    *  Copyright (C) 2016-2017  Apple, Inc. All rights reserved.
    *
    *  This document is the property of Apple Inc.
    *  It is considered confidential and proprietary.
    *
    *  This document may not be reproduced or transmitted in any form,
    *  in whole or in part, without the express written permission of
    *  Apple Inc.
    ************************************************************************* */

#ifndef _ASTRIS_EXCEPT_H
#define _ASTRIS_EXCEPT_H

// c99 standard library
#include <stdio.h>
#include <stdint.h>

// astris library
#include "astris_except_classes.h"

// Description:
//      The astris API cannot use C++ exceptions because there is no way for
//      client C code to handle them.  An alternative approach is to use these
//      AXEXC_*() macros with comparison, goto's and logging code to implement
//      something similar to c++ exceptions.  It is highly specific for astris.
//
// Requirements:
//      1)  Works in both c and c++ source, using either clang c or c++
//          compiler.  Minimize use of c++ automatically calling destructor
//          so use in c code does not require explicit destructor call which
//          is less error prone.
//      2)  Works across function calls.  Generate complete exception stack
//          trace as exception propagates up function calls to root calling
//          function.  Includes filename, line number, class name, function
//          name, and constant string holding a human readable description
//          of where the exception occurred.
//      3)  Works with astris logging.  printf() like formatting at the end
//          of all macros for adding arbitrary messages to Astris log.
//      4)  Works with unit test framework like libdarwintest.  For injected
//          faults, should be easy to compare exception call stack and against
//          expected fault call stack.
//      5)  Works with console TCL layer.  Exception extends up into TCL
//          procs call stack.
//      6)  Works with Python calling into API.  Exception extends up into
//          Python exception handlers.
//      7)  Works with single or multiple threads, and code like gRPC
//          that uses thread pools.  Uses pthreads per thread data to
//          hold exception information from functions in the call stack
//          as the exception propogates up the call stack with function
//          returns.
//      8)  Minimal amount of code execution in case where exceptions do
//          not occur, similar to c++ exception handling.  Check for an
//          error and branch past the exc_check() or exc_throw() call is
//          the minimum extra code executed at every possible point in
//          code where an exception can occur.
//      9)  Minimal amount of code added.  Adds comparison and branch at all
//          possible exception branch locations in code and calls to
//          exc_throw() or exc_check().
//      10) The exception code added should be as visually unobtusive as
//          possible because it could be 20% of the source code.  A human
//          reader should be able to filter it out without thinking about it
//          allowing primary focus on the underlying code and project domain.
//      11) Option to trace code execution passing through all AXEXC_*()
//          macros. Can turn it on and off at runtime via a global variable.
//
// Interface:
//      The interface is based on the simplest possible exception handling.
//      At a minimum, there needs to be a way to throw an exception, and a
//      way to check for an exception previously thrown.  This is implemented
//      as two exception macros, one to throw, and one to check for a
//      previously thrown exception.  For convenience, there is an additional
//      throw conditional macro that throws if a condition is true.
//      Additionally there are trace macros that write to the log if first
//      parameter except_ != ASTRIS_ERR_OK.  Additionally there are debug
//      macros that write to the log if global variable g_axexc_debug is true.
//
//      All AXEXC_*() macros take as the first parameter, except_, the local
//      exception state.  This holds an integer containg a value with
//      ASTRIS_ERR_* values.  If not equal to ASTRIS_ERR_OK, all AXEXC_CHECK*(),
//      AXEXC_THROW*(), AXEXC_THROW_IF*() macros will branch to c/c++ label
//      exc_br_label passed in as a macro argument.  AXEXC_TRACE*() and
//      AXEXC_DEBUG*() are passed and record except_, but they never branch.
//
//      The except_t local variable has only one integer field containing the
//      ASTRIS_ERR_* value passed to the THROW macro.  It is used to keep
//      astris error state passed from called function to calling function,
//      usually passed back as an integer return value.
//
//      The except_root_t pthreads global variable contains the entire
//      state of the exception.  It is created on demand and deleted
//      when the exception is caught.  It is only accessed if there is
//      an exception being processed.  It is never accessed during
//      normal, non-exception paths through the code.  If an exception is
//      thrown, call information is appended to the except_rpot_t global
//      exception stack trace.
//
//
//      //                      AXEXC_TRACE()
//      //                      AXEXC_DEBUG()
//      // Description:
//      //      AXEXC_TRACE() - Check if except_ has an error.  If so,
//      //          log information.
//      //          case:  AXEXC_CONFIG == AXEXC_CONFIG_NONE
//      //              logs never
//      //          case:  AXEXC_CONFIG == AXEXC_CONFIG_LOG_EXCEPTIONS
//      //              logs if (except_ has an error)
//      //          case:  AXEXC_CONFIG == AXEXC_CONFIG_LOG_EXCEPTIONS_AND_TRACE
//      //              logs if (except_ has an error) or (g_axexc_debug is true)
//      //      AXEXC_DEBUG() - Check if g_axexc_debug is true.  If so,
//      //          log information.
//      //          case:  AXEXC_CONFIG == AXEXC_CONFIG_NONE
//      //              logs never
//      //          case:  AXEXC_CONFIG == AXEXC_CONFIG_LOG_EXCEPTIONS
//      //              logs if (except_ has an error) or (g_axexc_debug is true)
//      //          case:  AXEXC_CONFIG == AXEXC_CONFIG_LOG_EXCEPTIONS_AND_TRACE
//      //              logs if (except_ has an error) or (g_axexc_debug is true)
//      // Inputs:
//      //      except_ - Local exception state
//      //      log_domain
//      //      log_category
//      //      exc_id_str
//      //      ... - Optional printf() format string and variable
//      //          parameters for format.
//
//      AXEXC_TRACE(except_, log_domain, log_category, exc_id_str, ...)
//      AXEXC_DEBUG(except_, log_domain, log_category, exc_id_str, ...)
//
//      //                      AXEXC_CHECK()
//      //                      AXEXC_CHECK_CALL()
//      //                      AXEXC_CHECK_EXEC()
//      // Description:
//      //      Check if except_ has an error.  If so,
//      //      log information, and jump to exc_br_label.
//      // Inputs:
//      //      except_ - Local exception state
//      //      exc_br_label - Label to jump to if exception has been
//      //          thrown.
//      //      log_domain
//      //      log_category
//      //      exc_id_str
//      //      c_function, c_arg - (AXEXC_CHECK_CALL() only)
//      //          Call c_function(c_arg) immediately before jump to
//      //          label if exception has been thrown.
//      //      c_statement_block - (AXEXC_CHECK_EXEC() only)
//      //          Execute this statement immediately before jump to
//      //          label if exception has been thrown.
//      //      ... - Optional printf() format string and variable
//      //          parameters for format.
//
//      AXEXC_CHECK(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, ...)
//
//      AXEXC_CHECK_CALL(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, c_function, c_arg, ...)
//
//      AXEXC_CHECK_EXEC(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, c_statement_block, ...)
//
//      //                      AXEXC_THROW()
//      //                      AXEXC_THROW_CALL()
//      //                      AXEXC_THROW_EXEC()
//      // Description:
//      //      Unconditionally throw an exception.
//      // Inputs:
//      //      except_ - Local exception state
//      //      exc_br_label - Label to jump to after throwing exception.
//      //      log_domain
//      //      log_category
//      //      exc_id_str
//      //      err_val - Error value of the cause for this exception
//      //          to be thrown.  One of ASTRIS_ERR_<name> values.
//      //      c_function, c_arg - (AXEXC_CHECK_CALL() only)
//      //          Call c_function(c_arg) immediately before jump to
//      //          label.
//      //      c_statement_block - (AXEXC_THROW_EXEC() only)
//      //          Execute this statement immediately before jump to
//      //          label.
//      //      ... - Optional printf() format string and variable
//      //          parameters for format.
//      // Updates:
//      //      except_.err_val
//      //      pthreed.exc_root.exc_call_stack
//
//      AXEXC_THROW(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, err_val, format, ...)
//
//      //                      AXEXC_THROW_IF()
//      //                      AXEXC_THROW_CALL_IF()
//      //                      AXEXC_THROW_EXEC_IF()
//      // Description:
//      //      Throw an exception if cond_expr is true.  Do nothing if
//      //      false. Typically used to check for invalid parameters.
//      // Inputs:
//      //      except_ - Local exception state
//      //      exc_br_label - Label to jump to if cond_expr is true,
//      //          after throwing exception.
//      //      log_domain
//      //      log_category
//      //      exc_id_str
//      //      cond_expr - If true, exception is thrown. Evaluated once.
//      //      err_val - Error value of the cause for this exception
//      //          to be thrown.  One of ASTRIS_ERR_<name> values.
//      //      c_function, c_arg - (AXEXC_CHECK_CALL() only)
//      //          Call c_function(c_arg) immediately before jump to
//      //          label if exception occurred.  If c_function returns
//      //          value != ASTRIS_ERR_OK, also throws exception.
//      //      c_statement_block - (AXEXC_THROW_EXEC_IF() only)
//      //          Execute this statement immediately before jump to
//      //          label if exception occurred.
//      //      ... - Optional printf() format string and variable
//      //          parameters for format.
//
//      AXEXC_THROW_IF(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, cond_expr, err_val, ...)
//
//      AXEXC_THROW_CALL_IF(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, cond_expr, err_val, c_function, c_arg, ...)
//
//      AXEXC_THROW_EXEC_IF(except_, exc_br_label, log_domain, log_category,
//          exc_id_str, cond_expr, err_val, c_statement_block, ...)
//
//
// Notes:
//      There is a similar exception type macro in
//      api/cpus/astris_arm64cpu.cpp.  195 uses of require() macro in
//      4300 lines of code.
//      #define require(a, b) do {if (!(a)) {goto b; }} while (0)
//
//      Another similar set of exception macros are in
//      console/src/ac_coredump.c.  113 useds of CORE_REQUIRE_*() macros.
//      #define CORE_REQUIRE_CODE(cdump, code, label, fmt, ...)
//      #define CORE_REQUIRE_BOOL(cdump, check, label, fmt, ...)
//      #define CORE_REQUIRE_SET(cdump, result, code, label, fmt, ...)
//
// References:
//      Exception Handling for C++.  Andrew Koenig, Bjarne Stroustrup. 1989.
//          http://www.stroustrup.com/except89.pdf
//
//      Exception Handling in C without C++.  Implementation using c setjmp()
//          longjmp().  Source available from Dr. Dobbs Journal.
//          http://www.on-time.com/ddj0011.htm
//
//      Google C++ Guidelines, Exceptions.  Has list of pros and cons.
//          One of the pros is using them in testing frameworks.
//          Google does not recommend them for existing libraries, but
//          recommends instead error codes and assertions.
//          https://google.github.io/styleguide/cppguide.html#Exceptions
//
//      libdarwintest White Paper. Confluence, Darwin Test Engineering.
//          January 20, 2016.

//******************************************************************************
//                          Constants, Configuration

// AXEXC_CONFIG
//      0 - No exception code inserted.  Branches on exceptions thrown or
//          previously taken.  Code executes correctly, just no exception
//          information written to log. Useful when doing static analysis to
//          eliminate all exception code.
//      1 - Log all exceptions.
//      2 - Log all exceptions and trace.  Identical to log all exceptions mode
//          except traces all AXEXC_CHECK() and AXEXC_THROW_IF() macros if
//          g_axexc_debug is true.  g_axexc_debug can turn tracing on and off
//          during runtime.

#define AXEXC_CONFIG_INVALID                  0
#define AXEXC_CONFIG_NONE                     1
#define AXEXC_CONFIG_LOG_EXCEPTIONS           2
#define AXEXC_CONFIG_LOG_EXCEPTIONS_AND_TRACE 3

#if !defined(AXEXC_CONFIG)
#define    AXEXC_CONFIG AXEXC_CONFIG_LOG_EXCEPTIONS
#endif // !defined(AXEXC_CONFIG)

// Specify if using in multi-threaded process, using pthreads.
#define AXEXC_MULTI_THREADED (1)

// Initial value for g_axexc_debug.
#define G_EXC_TRACE_INIT_VALUE false

//******************************************************************************
//                          Exception Macros

// AXEXC_CHECK(), AXEXC_THROW(), and AXEXC_THROW_IF() must be macros because
// they have goto's and c/c++ preprocessor references to file name, line number,
// class name, and function name.

//******************************************************************************
//                          AXEXC_CONFIG_NONE

#if AXEXC_CONFIG == AXEXC_CONFIG_NONE

// TRACE

// do nothing
#define AXEXC_TRACE(except_, log_domain, log_category, exc_id_str, ...)
#define AXEXC_TRACE_C(except_, log_domain, log_category, exc_id_str, ...)

// DEBUG

// do nothing
#define AXEXC_DEBUG(except_, log_domain, log_category, exc_id_str, ...)
#define AXEXC_DEBUG_C(except_, log_domain, log_category, exc_id_str, ...)

// CHECK

//  if (exception) {
//      goto exc_br_label
//  }

#define AXEXC_CHECK(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                                         \
        goto exc_br_label;                                                            \
    }
#define AXEXC_CHECK_C(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if (except_ != ASTRIS_ERR_OK) {                                                     \
        goto exc_br_label;                                                              \
    }

// CHECK_EXEC

//  if (exception) {
//      execute(c_statement_block);
//      goto exc_br_label
//  }

#define AXEXC_CHECK_EXEC(except_, exc_br_label, log_domain, log_category, exc_id_str, c_statement_block, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                                                                 \
        {c_statement_block;}                                                                                  \
        goto exc_br_label;                                                                                    \
    }
#define AXEXC_CHECK_EXEC_C(except_, exc_br_label, log_domain, log_category, exc_id_str, c_statement_block, ...) \
    if (except_ != ASTRIS_ERR_OK) {                                                                             \
        {c_statement_block;}                                                                                    \
        goto exc_br_label;                                                                                      \
    }

// THROW

//  goto exc_br_label;

#define AXEXC_THROW(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, ...) \
    {                                                                                          \
        except_ = err_val;                                                                     \
        goto exc_br_label;                                                                     \
    }
#define AXEXC_THROW_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, ...) \
    {                                                                                            \
        except_ = err_val;                                                                       \
        goto exc_br_label;                                                                       \
    }

// THROW_EXEC

//  execute(c_statement_block);
//  goto exc_br_label;

#define AXEXC_THROW_EXEC(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, c_statement_block, ...) \
    {                                                                                                                  \
        except_ = err_val;                                                                                             \
        {c_statement_block;}                                                                                           \
        goto exc_br_label;                                                                                             \
    }
#define AXEXC_THROW_EXEC_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, c_statement_block, ...) \
    {                                                                                                                    \
        except_ = err_val;                                                                                               \
        {c_statement_block;}                                                                                             \
        goto exc_br_label;                                                                                               \
    }

// THROW_IF

//  if (cond_expr) {
//      goto exc_br_label;
//  }

#define AXEXC_THROW_IF(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, ...) \
    if (cond_expr) {                                                                                         \
        except_ = err_val;                                                                                   \
        goto exc_br_label;                                                                                   \
    }
#define AXEXC_THROW_IF_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, ...) \
    if (cond_expr) {                                                                                           \
        except_ = err_val;                                                                                     \
        goto exc_br_label;                                                                                     \
    }

// THROW_EXEC_IF

//  if (cond_expr) {
//      execute(c_statement_block);
//      goto exc_br_label;
//  }

#define AXEXC_THROW_EXEC_IF(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, c_statement_block, ...) \
    if (cond_expr) {                                                                                                                 \
        except_ = err_val;                                                                                                           \
        {c_statement_block;}                                                                                                         \
        goto exc_br_label;                                                                                                           \
    }
#define AXEXC_THROW_EXEC_IF_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, c_statement_block, ...) \
    if (cond_expr) {                                                                                                                   \
        except_ = err_val;                                                                                                             \
        {c_statement_block;}                                                                                                           \
        goto exc_br_label;                                                                                                             \
    }

//******************************************************************************
//                          AXEXC_CONFIG_LOG_EXCEPTIONS

#elif AXEXC_CONFIG == AXEXC_CONFIG_LOG_EXCEPTIONS

// TRACE

//  if (exception) {
//      ax_os_log(msg, location);
//  }

#define AXEXC_TRACE(except_, log_domain, log_category, exc_id_str, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                           \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__, \
                          __func__, exc_id_str, ## __VA_ARGS__);        \
    }
#define AXEXC_TRACE_C(except_, log_domain, log_category, exc_id_str, ...)       \
    if (except_ != ASTRIS_ERR_OK) {                                             \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__, \
                         __func__, exc_id_str, ## __VA_ARGS__);                 \
    }
// Add internal message to the breadcrumbs
#define AXEXC_INFO(except_, log_domain, log_category, ...) \
    {                                                                   \
        except_.info_msg(log_domain, log_category, __FILE__, __LINE__, \
                          __func__,  ## __VA_ARGS__);        \
    }
// Add user message, this should be user-readable
#define AXEXC_USER_MSG(except_, log_domain, log_category, ...) \
    {                                                                   \
        except_.user_msg(log_domain, log_category, __FILE__, __LINE__, \
                          __func__, ## __VA_ARGS__);        \
    }

// DEBUG

//  if (exception) {
//      ax_os_log(msg, location);
//  }

#define AXEXC_DEBUG(except_, log_domain, log_category, exc_id_str, ...) \
    if ((except_.m_err_val != ASTRIS_ERR_OK) || g_axexc_debug) {        \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__, \
                          __func__, exc_id_str, ## __VA_ARGS__);        \
    }
#define AXEXC_DEBUG_C(except_, log_domain, log_category, exc_id_str, ...)       \
    if ((except_ != ASTRIS_ERR_OK) || g_axexc_debug) {                          \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__, \
                         __func__, exc_id_str, ## __VA_ARGS__);                 \
    }

// CHECK

//  if (exception) {
//      ax_os_log(msg, location);
//      goto exc_br_label;
//  }

#define AXEXC_CHECK(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                                         \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__,               \
                          __func__, exc_id_str, ## __VA_ARGS__);                      \
        goto exc_br_label;                                                            \
    }
#define AXEXC_CHECK_C(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if (except_ != ASTRIS_ERR_OK) {                                                     \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__,         \
                         __func__, exc_id_str, ## __VA_ARGS__);                         \
        goto exc_br_label;                                                              \
    }

// Same as above, but the format string in the end will be sent to the user log
// so make sure its formatted for them.
#define AXEXC_CHECK_USER_FMT(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                                         \
        except_.set_fmt_user();                                                       \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__,               \
                          __func__, exc_id_str, ## __VA_ARGS__);                      \
        except_.clear_fmt_user();                                                     \
        goto exc_br_label;                                                            \
    }

// Same as AXEXC_CHECK_USER_FMT , but instead sends the exc_id_str to the user log.
#define AXEXC_CHECK_USER_IDSTR(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                                         \
        except_.set_idstr_user();                                                     \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__,               \
                          __func__, exc_id_str, ## __VA_ARGS__);                      \
        except_.clear_idstr_user();                                                   \
        goto exc_br_label;                                                            \
    }

// CHECK_EXEC

//  if (exception) {
//      ax_os_log(msg, location);
//      execute(c_statement_block);
//      goto exc_br_label;
//  }

#define AXEXC_CHECK_EXEC(except_, exc_br_label, log_domain, log_category, exc_id_str, c_statement_block, ...) \
    if (except_.m_err_val != ASTRIS_ERR_OK) {                                                                 \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__,                                       \
                          __func__, exc_id_str, ## __VA_ARGS__);                                              \
        {c_statement_block;}                                                                                  \
        goto exc_br_label;                                                                                    \
    }
#define AXEXC_CHECK_EXEC_C(except_, exc_br_label, log_domain, log_category, exc_id_str, c_statement_block, ...) \
    if (except_ != ASTRIS_ERR_OK) {                                                                             \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__,                                 \
                         __func__, exc_id_str, ## __VA_ARGS__);                                                 \
        {c_statement_block;}                                                                                    \
        goto exc_br_label;                                                                                      \
    }

// THROW

//  generate exception;
//  ax_os_log(msg, location);
//  goto exc_br_label;

#define AXEXC_THROW(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, ...) \
    {                                                                                          \
        except_.exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,               \
                          __func__, exc_id_str, ## __VA_ARGS__);                               \
        goto exc_br_label;                                                                     \
    }
#define AXEXC_THROW_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, ...) \
    {                                                                                            \
        except_ = astris_exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,        \
                                   __func__, exc_id_str, ## __VA_ARGS__);                        \
        goto exc_br_label;                                                                       \
    }

// THROW_EXEC

//  generate exception;
//  ax_os_log(msg, location);
//  execute(c_statement_block);
//  goto exc_br_label;

#define AXEXC_THROW_EXEC(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, c_statement_block, ...) \
    {                                                                                                                  \
        except_.exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                                       \
                          __func__, exc_id_str, ## __VA_ARGS__);                                                       \
        {c_statement_block;}                                                                                           \
        goto exc_br_label;                                                                                             \
    }
#define AXEXC_THROW_EXEC_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, c_statement_block, ...) \
    {                                                                                                                    \
        except_ = astris_exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                                \
                                   __func__, exc_id_str, ## __VA_ARGS__);                                                \
        {c_statement_block;}                                                                                             \
        goto exc_br_label;                                                                                               \
    }

// THROW_IF

//  if (cond_expr) {
//      generate exception;
//      ax_os_log(msg, location);
//      goto exc_br_label;
//  }

#define AXEXC_THROW_IF(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, ...) \
    if (cond_expr) {                                                                                         \
        except_.exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                             \
                          __func__, exc_id_str, ## __VA_ARGS__);                                             \
        goto exc_br_label;                                                                                   \
    }
#define AXEXC_THROW_IF_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, ...) \
    if (cond_expr) {                                                                                           \
        except_ = astris_exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                      \
                                   __func__, exc_id_str, ## __VA_ARGS__);                                      \
        goto exc_br_label;                                                                                     \
    }

// THROW_EXEC_IF

//  if (cond_expr) {
//      generate exception;
//      ax_os_log(msg, location);
//      execute(c_statement_block);
//      goto exc_br_label;
//  }

#define AXEXC_THROW_EXEC_IF(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, c_statement_block, ...) \
    if (cond_expr) {                                                                                                                 \
        except_.exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                                                     \
                          __func__, exc_id_str, ## __VA_ARGS__);                                                                     \
        {c_statement_block;}                                                                                                         \
        goto exc_br_label;                                                                                                           \
    }
#define AXEXC_THROW_EXEC_IF_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, c_statement_block, ...) \
    if (cond_expr) {                                                                                                                   \
        except_ = astris_exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                                              \
                                   __func__, exc_id_str, ## __VA_ARGS__);                                                              \
        {c_statement_block;}                                                                                                           \
        goto exc_br_label;                                                                                                             \
    }

//******************************************************************************
//                          AXEXC_CONFIG_LOG_EXCEPTIONS_AND_TRACE
//  Notes:
//      THROW() and THROW_IF() should execute logically equivalent to non-trace
//      version.  exc_throw() is only called if an exception is being thrown.
//      There is no dependence on global variable g_axexc_debug.
//
//      CHECK() calls exc_check() under the additional condition of
//      global variable g_axexc_debug set to true.  But the branch behavior is
//      logically equivalent to the non-trace version, no branch taken iff
//      exception has not been thrown.

#elif AXEXC_CONFIG == AXEXC_CONFIG_LOG_EXCEPTIONS_AND_TRACE

// TRACE

//  if (exception or g_axexc_debug) {
//      ax_os_log(msg, location);
//  }
#define AXEXC_TRACE(except_, log_domain, log_category, exc_id_str, ...) \
    if ((except_.m_err_val != ASTRIS_ERR_OK) || g_axexc_debug) {        \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__, \
                          __func__, exc_id_str, ## __VA_ARGS__);        \
    }
#define AXEXC_TRACE_C(except_, log_domain, log_category, exc_id_str, ...)       \
    if ((except_ != ASTRIS_ERR_OK) || g_axexc_debug) {                          \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__, \
                         __func__, exc_id_str, ## __VA_ARGS__);                 \
    }

// DEBUG

//  if (exception or g_axexc_debug) {
//      ax_os_log(msg, location);
//  }
#define AXEXC_DEBUG(except_, log_domain, log_category, exc_id_str, ...) \
    if ((except_.m_err_val != ASTRIS_ERR_OK) || g_axexc_debug) {        \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__, \
                          __func__, exc_id_str, ## __VA_ARGS__);        \
    }
#define AXEXC_DEBUG_C(except_, log_domain, log_category, exc_id_str, ...)       \
    if ((except_ != ASTRIS_ERR_OK) || g_axexc_debug) {                          \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__, \
                         __func__, exc_id_str, ## __VA_ARGS__);                 \
    }

// CHECK - If exception has occurred,  branch

//  if (exception or g_axexc_debug) {
//      ax_os_log(msg, location);
//      if (exception) {
//          goto exc_br_label;
//      }
//  }
#define AXEXC_CHECK(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if ((except_.m_err_val != ASTRIS_ERR_OK) || g_axexc_debug) {                      \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__,               \
                          __func__, exc_id_str, ## __VA_ARGS__);                      \
        if (except_.m_err_val != ASTRIS_ERR_OK) {                                     \
            goto exc_br_label;                                                        \
        }                                                                             \
    }
#define AXEXC_CHECK_C(except_, exc_br_label, log_domain, log_category, exc_id_str, ...) \
    if ((except_ != ASTRIS_ERR_OK) || g_axexc_debug) {                                  \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__,         \
                         __func__, exc_id_str, ## __VA_ARGS__);                         \
        if (except_ != ASTRIS_ERR_OK) {                                                 \
            goto exc_br_label;                                                          \
        }                                                                               \
    }

// CHECK_EXEC - If exception has occurred, execute c_statement_block and branch

//  if (exception or g_axexc_debug) {
//      ax_os_log(msg, location);
//      if (exception) {
//          execute(c_statement_block);
//          goto exc_br_label
//      }
//  }
#define AXEXC_CHECK_EXEC(except_, exc_br_label, log_domain, log_category, exc_id_str, c_statement_block, ...) \
    if ((except_.m_err_val != ASTRIS_ERR_OK) || g_axexc_debug) {                                              \
        except_.exc_check(log_domain, log_category, __FILE__, __LINE__,                                       \
                          __func__, exc_id_str, ## __VA_ARGS__);                                              \
        if (except_.m_err_val != ASTRIS_ERR_OK) {                                                             \
            {c_statement_block;}                                                                              \
            goto exc_br_label;                                                                                \
        }                                                                                                     \
    }
#define AXEXC_CHECK_EXEC_C(except_, exc_br_label, log_domain, log_category, exc_id_str, c_statement_block, ...) \
    if ((except_ != ASTRIS_ERR_OK) || g_axexc_debug) {                                                          \
        astris_exc_check(except_, log_domain, log_category, __FILE__, __LINE__,                                 \
                         __func__, exc_id_str, ## __VA_ARGS__);                                                 \
        if (except_ != ASTRIS_ERR_OK) {                                                                         \
            {c_statement_block;}                                                                                \
            goto exc_br_label;                                                                                  \
        }                                                                                                       \
    }

// THROW - Throw exception and branch

//  generate exception;
//  ax_os_log(msg, location);
//  goto exc_br_label

#define AXEXC_THROW(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, ...) \
    {                                                                                          \
        except_.exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,               \
                          __func__, exc_id_str, ## __VA_ARGS__);                               \
        goto exc_br_label;                                                                     \
    }
#define AXEXC_THROW_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, ...) \
    {                                                                                            \
        except_ = astris_exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,        \
                                   __func__, exc_id_str, ## __VA_ARGS__);                        \
        goto exc_br_label;                                                                       \
    }

// THROW_EXEC - Throw exception and execute c_statement_block and branch

//  generate exception;
//  ax_os_log(msg, location);
//  execute(c_statement_block);
//  goto exc_br_label;

#define AXEXC_THROW_EXEC(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, c_statement_block, ...) \
    {                                                                                                                  \
        except_.exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                                       \
                          __func__, exc_id_str, ## __VA_ARGS__);                                                       \
        {c_statement_block;}                                                                                           \
        goto exc_br_label;                                                                                             \
    }
#define AXEXC_THROW_EXEC_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, c_statement_block, ...) \
    {                                                                                                                    \
        except_ = astris_exc_throw(err_val, log_domain, log_category, __FILE__, __LINE__,                                \
                                   __func__, exc_id_str, ## __VA_ARGS__);                                                \
        {c_statement_block;}                                                                                             \
        goto exc_br_label;                                                                                               \
    }

// THROW_IF - If condition, throw exception and branch

//  if (cond_expr) {
//      generate exception;
//  }
//  if (cond_expr or g_axexc_debug) {
//      ax_os_log(msg, location);
//  }
//  if (cond_expr) {
//      goto exc_br_label;
//  }

#define AXEXC_THROW_IF(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, ...) \
    {                                                                                                        \
        bool cond = (cond_expr);                                                                             \
        if (cond || g_axexc_debug) {                                                                         \
            except_.exc_throw((!cond && g_axexc_debug) ? 0 : err_val, log_domain, log_category,              \
                              __FILE__, __LINE__, __func__, exc_id_str, ## __VA_ARGS__);                     \
        }                                                                                                    \
        if (cond) {                                                                                          \
            goto exc_br_label;                                                                               \
        }                                                                                                    \
    }
#define AXEXC_THROW_IF_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, ...) \
    {                                                                                                          \
        int cond = (cond_expr);                                                                                \
        if (cond || g_axexc_debug) {                                                                           \
            except_ = astris_exc_throw((!cond && g_axexc_debug) ? 0 : err_val, log_domain, log_category,       \
                                       __FILE__, __LINE__, __func__, exc_id_str, ## __VA_ARGS__);              \
        }                                                                                                      \
        if (cond) {                                                                                            \
            goto exc_br_label;                                                                                 \
        }                                                                                                      \
    }

// THROW_EXEC_IF - If condition, throw exception and execute c_statement_block and branch

//  if (cond_expr) {
//      generate exception;
//  }
//  if (cond_expr or g_axexc_debug) {
//      log msg and location;
//  }
//  if (cond_expr) {
//      execute(c_statement_block);
//      goto exc_br_label;
//  }

#define AXEXC_THROW_EXEC_IF(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, c_statement_block, ...) \
    {                                                                                                                                \
        bool cond = (cond_expr);                                                                                                     \
        if (cond || g_axexc_debug) {                                                                                                 \
            except_.exc_throw((!cond && g_axexc_debug) ? 0 : err_val, log_domain, log_category,                                      \
                              __FILE__, __LINE__, __func__, exc_id_str, ## __VA_ARGS__);                                             \
        }                                                                                                                            \
        if (cond) {                                                                                                                  \
            {c_statement_block;}                                                                                                     \
            goto exc_br_label;                                                                                                       \
        }                                                                                                                            \
    }
#define AXEXC_THROW_EXEC_IF_C(except_, exc_br_label, log_domain, log_category, exc_id_str, err_val, cond_expr, c_statement_block, ...) \
    {                                                                                                                                  \
        int cond = (cond_expr);                                                                                                        \
        if (cond || g_axexc_debug) {                                                                                                   \
            except_ = astris_exc_throw((!cond && g_axexc_debug) ? 0 : err_val, log_domain, log_category,                               \
                                       __FILE__, __LINE__, __func__, exc_id_str, ## __VA_ARGS__);                                      \
        }                                                                                                                              \
        if (cond) {                                                                                                                    \
            {c_statement_block;}                                                                                                       \
            goto exc_br_label;                                                                                                         \
        }                                                                                                                              \
    }

//******************************************************************************
//                          AXEXC_CONFIG_INVALID

#else // if AXEXC_CONFIG == AXEXC_CONFIG_NONE
#error "Invalid AXEXC_CONFIG"

#endif // AXEXC_CONFIG

//******************************************************************************
//                          E_*() short macros for c++
//  Description:
//      Shortcut exception macros for class member functions only.
//      Do not need to pass macro parameters (exc_exit, log_domain, log_category).
//      Always use (exc_exit, _log_key, AXL_ERR).  _log_key should be a
//      static member so it works for both static and nonstatic member functions.

#define E_TRACE(except_, exc_id_str, ...) \
    AXEXC_TRACE(except_, _log_key, AXL_ERR, exc_id_str, ## __VA_ARGS__)

#define E_DEBUG(except_, exc_id_str, ...) \
    AXEXC_DEBUG(except_, _log_key, AXL_ERR, exc_id_str, ## __VA_ARGS__)

#define E_CHECK(except_, exc_id_str, ...)                         \
    AXEXC_CHECK(except_, exc_exit, _log_key, AXL_ERR, exc_id_str, \
                ## __VA_ARGS__)

#define E_CHECK_EXEC(except_, exc_id_str, c_statement_block, ...)                         \
    AXEXC_CHECK_EXEC(except_, exc_exit, _log_key, AXL_ERR, exc_id_str, c_statement_block, \
                     ## __VA_ARGS__)

#define E_THROW(except_, exc_id_str, astris_err_val, ...)         \
    AXEXC_THROW(except_, exc_exit, _log_key, AXL_ERR, exc_id_str, \
                astris_err_val, ## __VA_ARGS__)

#define E_THROW_EXEC(except_, exc_id_str, astris_err_val, c_statement_block, ...) \
    AXEXC_THROW_EXEC(except_, exc_exit, _log_key, AXL_ERR, exc_id_str,            \
                     astris_err_val, c_statement_block, ## __VA_ARGS__)

#define E_THROW_IF(except_, exc_id_str, astris_err_val, condition, ...) \
    AXEXC_THROW_IF(except_, exc_exit, _log_key, AXL_ERR, exc_id_str,    \
                   astris_err_val, condition, ## __VA_ARGS__)

#define E_THROW_EXEC_IF(except_, exc_id_str, astris_err_val, condition, c_statement_block, ...) \
    AXEXC_THROW_EXEC_IF(except_, exc_exit, _log_key, AXL_ERR, exc_id_str,                       \
                        astris_err_val, condition, c_statement_block, ## __VA_ARGS__)

//******************************************************************************
//                          E_*() short macros for c
//  Description:
//      Shortcut exception macros for c code.  _log_key should be a static
//      const variable per file like this:
//
//      static const char* _log_key = "<log_key_text>";
//
//      Or it can be a per function static local like this, which can
//      override the static const variable per file above.
//
//      static const char* _log_key = "<log_key_text>";

#define E_TRACE_C(except_, exc_id_str, ...) \
    AXEXC_TRACE_C(except_, _log_key, AXL_ERR, exc_id_str, ## __VA_ARGS__)

#define E_DEBUG_C(except_, exc_id_str, ...) \
    AXEXC_DEBUG_C(except_, _log_key, AXL_ERR, exc_id_str, ## __VA_ARGS__)

#define E_CHECK_C(except_, exc_id_str, ...)                         \
    AXEXC_CHECK_C(except_, exc_exit, _log_key, AXL_ERR, exc_id_str, \
                  ## __VA_ARGS__)

#define E_CHECK_EXEC_C(except_, exc_id_str, c_statement_block, ...)                         \
    AXEXC_CHECK_EXEC_C(except_, exc_exit, _log_key, AXL_ERR, exc_id_str, c_statement_block, \
                       ## __VA_ARGS__)

#define E_THROW_C(except_, exc_id_str, astris_err_val, ...)         \
    AXEXC_THROW_C(except_, exc_exit, _log_key, AXL_ERR, exc_id_str, \
                  astris_err_val, ## __VA_ARGS__)

#define E_THROW_EXEC_C(except_, exc_id_str, astris_err_val, c_statement_block, ...) \
    AXEXC_THROW_EXEC_C(except_, exc_exit, _log_key, AXL_ERR, exc_id_str,            \
                       astris_err_val, c_statement_block, ## __VA_ARGS__)

#define E_THROW_IF_C(except_, exc_id_str, astris_err_val, condition, ...) \
    AXEXC_THROW_IF_C(except_, exc_exit, _log_key, AXL_ERR, exc_id_str,    \
                     astris_err_val, condition, ## __VA_ARGS__)

#define E_THROW_EXEC_IF_C(except_, exc_id_str, astris_err_val, condition, c_statement_block, ...) \
    AXEXC_THROW_EXEC_IF_C(except_, exc_exit, _log_key, AXL_ERR, exc_id_str,                       \
                          astris_err_val, condition, c_statement_block, ## __VA_ARGS__)

//******************************************************************************

// Controls tracing exception checks if True.  Only has an effect for
// configuration AXEXC_CONFIG_LOG_EXCEPTIONS_AND_TRACE.
volatile extern int g_axexc_debug;

//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//                              Examples
//                              Examples
//                              Examples
//                              Examples
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

//                          Example 1.  c synatax
//

#if 0

// Dummy function declarations, so that example compiles under c/c++.
int astris_exc_throw(int err_val, const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format = NULL,
                     ...);
int astris_exc_check(int err_val, const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format = NULL,
                     ...);
void g(int foo, int *e, ...);
int  h(int *e, ...);

static const char *_log_key = "example_1";

int f_api()
{
    int e = ASTRIS_ERR_OK;
    E_TRACE_C(e, "fn.entry", NULL); // format = NULL required if no va_arg args.
    g(0, &e);
    E_CHECK_C(e, "fn.g.failed", "foo=%d", 1);
    // Exceptions work in composite funciton call in either g() or h().
    g(h(&e), &e);
    E_CHECK_C(e, "fn.g.h.failed", "bar=%d", 2);
exc_exit:
    E_TRACE_C(e, "fn.exit", NULL); // format = NULL required if no va_arg args.
    return e;
}

void g(int foo, int *e, ...)
{
    int  e_         = *e;
    bool exc_cond_1 = true;
    bool exc_cond_2 = true;
    if (exc_cond_1) {
        E_THROW_C(e_, "fn.exc_cond_1", ASTRIS_ERR_BADPARAM, "exc_cond_1=%d", exc_cond_1);
    }
    E_THROW_IF_C(e_, "fn.exc_cond_1", ASTRIS_ERR_BADPARAM, exc_cond_2,
                 "exc_cond_2=%d", exc_cond_2);
exc_exit:
    // Handle exception if needed.
    *e = e_;
}

// Function h updates exception value in *e.
int h(int *e, ...)
{
    int  return_val = 12345;
    int  e_         = *e;
    bool exc_cond_3 = true;
    E_THROW_IF_C(e_, "fn.exc_cond_3", ASTRIS_ERR_BADPARAM, exc_cond_3,
                 "exc_cond_3=%d", exc_cond_3);
exc_exit:
    E_TRACE_C(e_, "fn.exit");
    // Handle exception if needed.
    return return_val;
}

#endif // if 0

//                          Example 2.  c++ syntax

#if 0
#if defined(__cplusplus)

// Dummy class and function declarations, so that example compiles under c++.
class exc_t {
public:
    int m_err_val;
    int exc_throw(int err_val, const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format = NULL, ...);
    int exc_check(const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format              = NULL, ...);

    inline int exc_err_val() {return m_err_val;}

};
int         f_api();
int         g(int foo, exc_t &e, ...);
int         h(exc_t &e, ...);
const char *get_name();

const char *_log_key = "example_2";

int f_api()
{
    exc_t e;
    int   a = 1234;
    g(0, e);
    // AXEXC_CHECK(except_, exc_br_label, log_domain, log_category, exc_id_str, ...)
    E_CHECK(e, "fn.g.failed", "a=%d", a);
    // Exceptions work in composite funciton call in either g() or h().
    g(h(e), e);
    E_CHECK(e, "fn.g.h.failed", "a=%d", a);
exc_exit:
    return e.exc_err_val();
}

// Function g updates exception value in reference parameter e.
int g(int foo, exc_t &e, ...)
{
    int  bar        = 0;
    bool exc_cond_1 = true;
    bool exc_cond_2 = true;
    if (exc_cond_1) {
        E_THROW(e, "fn.exc_cond_1", ASTRIS_ERR_BADPARAM, "bar=%d", bar);
    }
    E_THROW_IF(e, "fn.exc_cond_2", ASTRIS_ERR_BADPARAM, exc_cond_2,
               "bar=%d", bar);

    // Example showing execution of a composite c_statement_block before the
    // jump, when an exception occurs.
    // The two c statements are:
    // 1) function call to ax_os_log() with its own function call
    // 2) c statement "foo++;"
    E_THROW_EXEC_IF(
        e,
    {
        ax_os_log(_log_key, AXL_STD, "c_statement_block foo=%d, name=%s",
                  foo++, get_name());
        foo++;
    },
        "fn.test.throw_exception.c_statement_block",
        ASTRIS_ERR_BADPARAM,
        true);
exc_exit:
    E_TRACE(e, "fn.test.throw_exception.c_statement_block", "foo=%d", foo);
    E_TRACE(e, "fn.exit", "foo=%d", foo);
    return e.exc_err_val();
}

// Function h updates exception value in reference parameter e.
int h(exc_t &e, ...)
{
    bool exc_cond_3 = true;
    int  return_val = 12345;
    E_THROW_IF(e, "fn.exc_cond_3", ASTRIS_ERR_BADPARAM, exc_cond_3,
               "return_val=%d", return_val);
exc_exit:
    // Handle exception if needed.
    return return_val;
}

#endif // defined(__cplusplus)

#endif // if 0

//******************************************************************************
#endif // _ASTRIS_EXCEPT_H
