#+TITLE: Error handling
#+SETUPFILE: ../tools/theme.setup
#+INCLUDE: ../tools/lib.org

* Headers                                                         :noexport:

   #+begin_src c :tangle (eval h_private_type)
#ifndef QMCKL_ERROR_HPT
#define QMCKL_ERROR_HPT
   #+end_src

   #+begin_src c :tangle (eval c)
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#endif

#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <errno.h>

#include "qmckl.h"
#include "qmckl_context_private_type.h"
   #+end_src

   #+begin_src c :tangle (eval c_test) :noweb yes
#include <string.h>
#include <stdio.h>
#include "qmckl.h"
#include "assert.h"
#include "qmckl_error_private_type.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
int main() {
  qmckl_context context;
  context = qmckl_context_create();

   #+end_src

   #+end_src

* -
:PROPERTIES:
:UNNUMBERED: t
:END:

   The library should never make the calling programs abort, nor
   perform any input/output operations. This decision has to be taken
   by the developer of the code calling the library.

   All the functions return with an exit code, defined as
   #+NAME: type-exit-code
   #+begin_src c :comments org :tangle (eval h_type)
typedef int32_t qmckl_exit_code;
   #+end_src


  #+begin_src f90 :comments org :tangle (eval fh_type) :exports none
      integer  , parameter :: qmckl_exit_code = c_int32_t
  #+end_src

   The exit code returns the completion status of the function to the
   calling program. When a function call completed successfully,
   ~QMCKL_SUCCESS~ is returned. If one of the functions of
   the library fails to complete the requested task, an appropriate
   error code is returned to the program.

   Here is the complete list of exit codes.

   #+NAME: table-exit-codes
   | Macro                       | Code | Description            |
   |-----------------------------+------+------------------------|
   | ~QMCKL_SUCCESS~             |    0 | 'Success'              |
   | ~QMCKL_INVALID_ARG_1~       |    1 | 'Invalid argument 1'   |
   | ~QMCKL_INVALID_ARG_2~       |    2 | 'Invalid argument 2'   |
   | ~QMCKL_INVALID_ARG_3~       |    3 | 'Invalid argument 3'   |
   | ~QMCKL_INVALID_ARG_4~       |    4 | 'Invalid argument 4'   |
   | ~QMCKL_INVALID_ARG_5~       |    5 | 'Invalid argument 5'   |
   | ~QMCKL_INVALID_ARG_6~       |    6 | 'Invalid argument 6'   |
   | ~QMCKL_INVALID_ARG_7~       |    7 | 'Invalid argument 7'   |
   | ~QMCKL_INVALID_ARG_8~       |    8 | 'Invalid argument 8'   |
   | ~QMCKL_INVALID_ARG_9~       |    9 | 'Invalid argument 9'   |
   | ~QMCKL_INVALID_ARG_10~      |   10 | 'Invalid argument 10'  |
   | ~QMCKL_INVALID_ARG_11~      |   11 | 'Invalid argument 11'  |
   | ~QMCKL_INVALID_ARG_12~      |   12 | 'Invalid argument 12'  |
   | ~QMCKL_INVALID_ARG_13~      |   13 | 'Invalid argument 13'  |
   | ~QMCKL_INVALID_ARG_14~      |   14 | 'Invalid argument 14'  |
   | ~QMCKL_INVALID_ARG_15~      |   15 | 'Invalid argument 15'  |
   | ~QMCKL_INVALID_ARG_16~      |   16 | 'Invalid argument 16'  |
   | ~QMCKL_INVALID_ARG_17~      |   17 | 'Invalid argument 17'  |
   | ~QMCKL_INVALID_ARG_18~      |   18 | 'Invalid argument 18'  |
   | ~QMCKL_INVALID_ARG_19~      |   19 | 'Invalid argument 19'  |
   | ~QMCKL_INVALID_ARG_20~      |   20 | 'Invalid argument 20'  |
   | ~QMCKL_FAILURE~             |  101 | 'Failure'              |
   | ~QMCKL_ERRNO~               |  102 | strerror(errno)        |
   | ~QMCKL_INVALID_CONTEXT~     |  103 | 'Invalid context'      |
   | ~QMCKL_ALLOCATION_FAILED~   |  104 | 'Allocation failed'    |
   | ~QMCKL_DEALLOCATION_FAILED~ |  105 | 'De-allocation failed' |
   | ~QMCKL_NOT_PROVIDED~        |  106 | 'Not provided'         |
   | ~QMCKL_OUT_OF_BOUNDS~       |  107 | 'Index out of bounds'  |
   | ~QMCKL_INVALID_EXIT_CODE~   |  108 | 'Invalid exit code'    |

   # We need to force Emacs not to indent the Python code:
   # -*- org-src-preserve-indentation: t

   #+begin_src python :var table=table-exit-codes :results drawer :exports none
""" This script generates the C and Fortran constants for the error
    codes from the org-mode table.
"""

result = [ "#+begin_src c :comments org :tangle (eval h_type) :exports none" ]
for (text, code,_) in table:
    text=text.replace("~","")
    result += [ f"#define  {text:30s} ((qmckl_exit_code) {code:d})" ]
result += [ "#+end_src" ]

result += [ "" ]

result += [ "#+begin_src f90 :comments org :tangle (eval fh_type) :exports none" ]
for (text, code,_) in table:
    text=text.replace("~","")
    result += [ f"   integer(qmckl_exit_code), parameter :: {text:30s} = {code:d}" ]
result += [ "#+end_src" ]

return '\n'.join(result)

   #+end_src

   #+RESULTS:
   :results:
   #+begin_src c :comments org :tangle (eval h_type) :exports none
   #define  QMCKL_SUCCESS                  ((qmckl_exit_code) 0)
   #define  QMCKL_INVALID_ARG_1            ((qmckl_exit_code) 1)
   #define  QMCKL_INVALID_ARG_2            ((qmckl_exit_code) 2)
   #define  QMCKL_INVALID_ARG_3            ((qmckl_exit_code) 3)
   #define  QMCKL_INVALID_ARG_4            ((qmckl_exit_code) 4)
   #define  QMCKL_INVALID_ARG_5            ((qmckl_exit_code) 5)
   #define  QMCKL_INVALID_ARG_6            ((qmckl_exit_code) 6)
   #define  QMCKL_INVALID_ARG_7            ((qmckl_exit_code) 7)
   #define  QMCKL_INVALID_ARG_8            ((qmckl_exit_code) 8)
   #define  QMCKL_INVALID_ARG_9            ((qmckl_exit_code) 9)
   #define  QMCKL_INVALID_ARG_10           ((qmckl_exit_code) 10)
   #define  QMCKL_INVALID_ARG_11           ((qmckl_exit_code) 11)
   #define  QMCKL_INVALID_ARG_12           ((qmckl_exit_code) 12)
   #define  QMCKL_INVALID_ARG_13           ((qmckl_exit_code) 13)
   #define  QMCKL_INVALID_ARG_14           ((qmckl_exit_code) 14)
   #define  QMCKL_INVALID_ARG_15           ((qmckl_exit_code) 15)
   #define  QMCKL_INVALID_ARG_16           ((qmckl_exit_code) 16)
   #define  QMCKL_INVALID_ARG_17           ((qmckl_exit_code) 17)
   #define  QMCKL_INVALID_ARG_18           ((qmckl_exit_code) 18)
   #define  QMCKL_INVALID_ARG_19           ((qmckl_exit_code) 19)
   #define  QMCKL_INVALID_ARG_20           ((qmckl_exit_code) 20)
   #define  QMCKL_FAILURE                  ((qmckl_exit_code) 101)
   #define  QMCKL_ERRNO                    ((qmckl_exit_code) 102)
   #define  QMCKL_INVALID_CONTEXT          ((qmckl_exit_code) 103)
   #define  QMCKL_ALLOCATION_FAILED        ((qmckl_exit_code) 104)
   #define  QMCKL_DEALLOCATION_FAILED      ((qmckl_exit_code) 105)
   #define  QMCKL_NOT_PROVIDED             ((qmckl_exit_code) 106)
   #define  QMCKL_OUT_OF_BOUNDS            ((qmckl_exit_code) 107)
   #define  QMCKL_INVALID_EXIT_CODE        ((qmckl_exit_code) 108)
   #+end_src

   #+begin_src f90 :comments org :tangle (eval fh_type) :exports none
      integer(qmckl_exit_code), parameter :: QMCKL_SUCCESS                  = 0
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_1            = 1
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_2            = 2
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_3            = 3
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_4            = 4
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_5            = 5
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_6            = 6
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_7            = 7
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_8            = 8
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_9            = 9
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_10           = 10
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_11           = 11
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_12           = 12
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_13           = 13
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_14           = 14
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_15           = 15
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_16           = 16
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_17           = 17
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_18           = 18
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_19           = 19
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_ARG_20           = 20
      integer(qmckl_exit_code), parameter :: QMCKL_FAILURE                  = 101
      integer(qmckl_exit_code), parameter :: QMCKL_ERRNO                    = 102
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_CONTEXT          = 103
      integer(qmckl_exit_code), parameter :: QMCKL_ALLOCATION_FAILED        = 104
      integer(qmckl_exit_code), parameter :: QMCKL_DEALLOCATION_FAILED      = 105
      integer(qmckl_exit_code), parameter :: QMCKL_NOT_PROVIDED             = 106
      integer(qmckl_exit_code), parameter :: QMCKL_OUT_OF_BOUNDS            = 107
      integer(qmckl_exit_code), parameter :: QMCKL_INVALID_EXIT_CODE        = 108
   #+end_src
   :end:

   The ~qmckl_string_of_error~ converts an exit code into a string. The
   string is assumed to be large enough to contain the error message
   (typically 128 characters).

* hidden                                                           :noexport:

   #+NAME: MAX_STRING_LENGTH
   : 128

* Decoding errors

   To decode the error messages, ~qmckl_string_of_error~ converts an
   error code into a string.

   #+begin_src c :comments org :tangle (eval h_func) :noweb yes
const char*
qmckl_string_of_error (const qmckl_exit_code error);
   #+end_src

   #+begin_src c :comments org :tangle (eval h_private_func) :exports none :noweb yes
void
qmckl_string_of_error_f(const qmckl_exit_code error,
                        char result[<<MAX_STRING_LENGTH()>>]);
   #+end_src

   The text strings are extracted from the previous table.

   #+NAME:cases
   #+begin_src python :var table=table-exit-codes :exports none :noweb yes
""" This script extracts the text associated with the error codes
    from the table.
"""

result = []
for (text, code, message) in table:
    text = text.replace("~","")
    message = message.replace("'",'"')
    result += [ f"""case {text}:
      return {message};
    break;""" ]
return '\n'.join(result)

   #+end_src

   #+RESULTS: cases
   #+begin_example
   case QMCKL_SUCCESS:
		 return "Success";
	       break;
   case QMCKL_INVALID_ARG_1:
		 return "Invalid argument 1";
	       break;
   case QMCKL_INVALID_ARG_2:
		 return "Invalid argument 2";
	       break;
   case QMCKL_INVALID_ARG_3:
		 return "Invalid argument 3";
	       break;
   case QMCKL_INVALID_ARG_4:
		 return "Invalid argument 4";
	       break;
   case QMCKL_INVALID_ARG_5:
		 return "Invalid argument 5";
	       break;
   case QMCKL_INVALID_ARG_6:
		 return "Invalid argument 6";
	       break;
   case QMCKL_INVALID_ARG_7:
		 return "Invalid argument 7";
	       break;
   case QMCKL_INVALID_ARG_8:
		 return "Invalid argument 8";
	       break;
   case QMCKL_INVALID_ARG_9:
		 return "Invalid argument 9";
	       break;
   case QMCKL_INVALID_ARG_10:
		 return "Invalid argument 10";
	       break;
   case QMCKL_INVALID_ARG_11:
		 return "Invalid argument 11";
	       break;
   case QMCKL_INVALID_ARG_12:
		 return "Invalid argument 12";
	       break;
   case QMCKL_INVALID_ARG_13:
		 return "Invalid argument 13";
	       break;
   case QMCKL_INVALID_ARG_14:
		 return "Invalid argument 14";
	       break;
   case QMCKL_INVALID_ARG_15:
		 return "Invalid argument 15";
	       break;
   case QMCKL_INVALID_ARG_16:
		 return "Invalid argument 16";
	       break;
   case QMCKL_INVALID_ARG_17:
		 return "Invalid argument 17";
	       break;
   case QMCKL_INVALID_ARG_18:
		 return "Invalid argument 18";
	       break;
   case QMCKL_INVALID_ARG_19:
		 return "Invalid argument 19";
	       break;
   case QMCKL_INVALID_ARG_20:
		 return "Invalid argument 20";
	       break;
   case QMCKL_FAILURE:
		 return "Failure";
	       break;
   case QMCKL_ERRNO:
		 return strerror(errno);
	       break;
   case QMCKL_INVALID_CONTEXT:
		 return "Invalid context";
	       break;
   case QMCKL_ALLOCATION_FAILED:
		 return "Allocation failed";
	       break;
   case QMCKL_DEALLOCATION_FAILED:
		 return "De-allocation failed";
	       break;
   case QMCKL_NOT_PROVIDED:
		 return "Not provided";
	       break;
   case QMCKL_INVALID_EXIT_CODE:
		 return "Invalid exit code";
	       break;
   #+end_example

   # Source
   #+begin_src c :comments org :tangle (eval c) :noweb yes :exports none
const char* qmckl_string_of_error(const qmckl_exit_code error) {
  switch (error) {
  <<cases()>>
  }
  return "Unknown error";
}

void qmckl_string_of_error_f(const qmckl_exit_code error, char result[<<MAX_STRING_LENGTH()>>]) {
  strncpy(result, qmckl_string_of_error(error), <<MAX_STRING_LENGTH()>>-1);
}
   #+end_src

  # Fortran interface
  #+begin_src f90 :tangle (eval fh_func) :exports none :noweb yes
  interface
     subroutine qmckl_string_of_error (error, string) bind(C, name='qmckl_string_of_error_f')
       use, intrinsic :: iso_c_binding
       import
       integer (qmckl_exit_code), intent(in), value :: error
       character, intent(out) :: string(<<MAX_STRING_LENGTH()>>)
     end subroutine qmckl_string_of_error
  end interface
  #+end_src

* Data structure in context

  The strings are declared internally with a maximum fixed size to avoid
  dynamic memory allocation.

   #+begin_src c :comments org :tangle (eval h_private_type)
#define  QMCKL_MAX_FUN_LEN   256
#define  QMCKL_MAX_MSG_LEN  1024

typedef struct qmckl_error_struct {

  qmckl_exit_code exit_code;
  char function[QMCKL_MAX_FUN_LEN];
  char message [QMCKL_MAX_MSG_LEN];

} qmckl_error_struct;
   #+end_src

* Updating errors in the context

   The error is updated in the context using ~qmckl_set_error~.
   When the error is set in the context, it is mandatory to specify
   from which function the error is triggered, and a message
   explaining the error. The exit code can't be ~QMCKL_SUCCESS~.

   # Header
   #+begin_src c :comments org :tangle (eval h_func)
qmckl_exit_code
qmckl_set_error(qmckl_context context,
                const qmckl_exit_code exit_code,
                const char* function_name,
                const char* message);
   #+end_src

  # Source
  #+begin_src c :tangle (eval c) :exports none
qmckl_exit_code
qmckl_set_error(qmckl_context context,
                const qmckl_exit_code exit_code,
                const char* function_name,
                const char* message)
{
  /* Passing a function name and a message is mandatory. */
  assert (function_name != NULL);
  assert (message != NULL);

  /* Exit codes are assumed valid. */
  assert (exit_code >= 0);
  assert (exit_code != QMCKL_SUCCESS);
  assert (exit_code < QMCKL_INVALID_EXIT_CODE);

  /* The context is assumed to exist. */
  assert (qmckl_context_check(context) != QMCKL_NULL_CONTEXT);

  qmckl_lock(context);
  {
    qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
    assert (ctx != NULL); /* Impossible because the context is valid. */

    ctx->error.exit_code = exit_code;
    strncpy(ctx->error.function, function_name, QMCKL_MAX_FUN_LEN-1);
    strncpy(ctx->error.message, message, QMCKL_MAX_MSG_LEN-1);
  }
  qmckl_unlock(context);

  return QMCKL_SUCCESS;
}
    #+end_src

* Get the error

  Upon error, the error type and message can be obtained from the
  context using ~qmckl_get_error~. The message and function name
  is returned in the variables provided. Therefore, passing a 
  function name and message is mandatory.

  # Header
   #+begin_src c :comments org :tangle (eval h_func)
qmckl_exit_code
qmckl_get_error(qmckl_context context,
                qmckl_exit_code *exit_code,
                char* function_name,
                char* message);
   #+end_src

  # Source
  #+begin_src c :tangle (eval c) :exports none
qmckl_exit_code
qmckl_get_error(qmckl_context context,
                qmckl_exit_code *exit_code,
                char* function_name,
                char* message)
{
  /* Passing a function name and a message is mandatory. */
  assert (function_name != NULL);
  assert (message != NULL);

  /* The context is assumed to exist. */
  assert (qmckl_context_check(context) != QMCKL_NULL_CONTEXT);

  qmckl_lock(context);
  {
    qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
    assert (ctx != NULL); /* Impossible because the context is valid. */

/* Turn off annoying GCC warning */
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif

    strncpy(function_name, ctx->error.function, QMCKL_MAX_FUN_LEN-1);
    strncpy(message      , ctx->error.message , QMCKL_MAX_MSG_LEN-1);

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

    (*exit_code) = ctx->error.exit_code;
  }
  qmckl_unlock(context);

  return QMCKL_SUCCESS;
}
    #+end_src

* Failing
  
   To make a function fail, the ~qmckl_failwith~ function should be
   called, such that information about the failure is stored in
   the context. The desired exit code is given as an argument, as
   well as the name of the function and an error message. If the
   message is ~NULL~, then the default message obtained by
   ~qmckl_string_of_error~ is used. The return code of the function is
   the desired return code.
   Upon failure, a ~QMCKL_NULL_CONTEXT~ is returned.

   #+begin_src c :comments org :tangle (eval h_func)
qmckl_exit_code
qmckl_failwith(qmckl_context context,
               const qmckl_exit_code exit_code,
               const char* function,
               const char* message) ;
   #+end_src
   
   #+begin_src c :comments org :tangle (eval c) :exports none
qmckl_exit_code
qmckl_failwith(qmckl_context context,
               const qmckl_exit_code exit_code,
               const char* function,
               const char* message)
{
  assert (exit_code > 0);
  assert (exit_code < QMCKL_INVALID_EXIT_CODE);
  assert (function != NULL);
  assert (strlen(function) < QMCKL_MAX_FUN_LEN);
  if (message != NULL) {
    assert (strlen(message)  < QMCKL_MAX_MSG_LEN);
  }

  if (qmckl_context_check(context) == QMCKL_NULL_CONTEXT)
    return QMCKL_INVALID_CONTEXT;

  if (message == NULL) {
    qmckl_exit_code rc =
      qmckl_set_error(context, exit_code, function, qmckl_string_of_error(exit_code));
    assert (rc == QMCKL_SUCCESS);
  } else {
    qmckl_exit_code rc =
      qmckl_set_error(context, exit_code, function, message);
    assert (rc == QMCKL_SUCCESS);
  }

  return exit_code;
}

   #+end_src

   For example, this function can be used as
   #+begin_src c :tangle no
if (x < 0) {
  return qmckl_failwith(context,
                        QMCKL_INVALID_ARG_2,
                        "qmckl_function",
                        "Expected x >= 0");
 }
   #+end_src


* End of files                                                    :noexport:

  #+begin_src c :comments link :tangle (eval h_private_type)
#endif
  #+end_src

   #+begin_src c :comments link :tangle (eval c_test)
  /* Initialize the variables */
  char function_name[QMCKL_MAX_FUN_LEN]="";
  char message[QMCKL_MAX_MSG_LEN]="";

  /* Set the error code to be different from Success */
  qmckl_exit_code exit_code;
  exit_code = 1;

  assert (qmckl_set_error(context, exit_code, "qmckl_transpose", "Success") == QMCKL_SUCCESS);

  assert (qmckl_get_error(context, &exit_code, function_name, message) == QMCKL_SUCCESS);
  assert (exit_code == 1);
  assert (strcmp(function_name,"qmckl_transpose") == 0);
  assert (strcmp(message,"Success") == 0);

  return 0;
}
   #+end_src

   # -*- mode: org -*-
   # vim: syntax=c


