module m_err2 implicit none private public :: t_err2 character(*), parameter :: my_name = "my_prog_name" character(*), parameter :: str_warn = achar(27)//"[1;31m Warning "//achar(27)//"[0m" character(*), parameter :: str_err = achar(27)//"[1;31m Error "//achar(27)//"[0m" character(*), parameter :: str_message = ":>> Message : " character(*), parameter :: str_source = ":>> Source : " character(*), parameter :: str_caller = ":>> Caller : " character(*), parameter :: str_lastcaller = ":>> Last caller :" type :: t_err2 !! Independent error-storing and reporting type, !! using the `__FILE__` and `__LINE__` pre-processor macros. !! !! To use `t_err2`, first initialise it: !!```f90 !! type( t_err2 ), pointer :: bugs=>null() !! !! ! initialise !! bugs => t_err2() !!``` !! !! When an error is captured somewhere, set a message and location as: !!```f90 !! ! here some error occurs, which gives a nonzero error code !! ierr = some_error() !! if( ierr /= 0 ) then !! ! set the error message and location to t_err2: !! call bugs% err_set( ierr, __FILE__, __LINE__, msg="error msg", msg_arr=[words] ) !! return !! end if !!``` !! !! When you wish to report the stored error message: !!```f90 !! if( ierr /= 0 ) then !! ! error code has been propagated down to some routine (i.e. main), now output it !! call bugs% err_write( __FILE__, __LINE__ ) !! end if !!``` !! !! If you wish to add intermediate caller routines to the message: !!```f90 !! ! an intermediate routine captured the ierr code, and passed it to caller !! ierr = something_that_passed_error() !! if( ierr /= 0 ) then !! ! set current location into list of callers !! call bugs% err_caller( __FILE__, __LINE__ ) !! return !! end if !!``` integer, private :: last_ierr !! integer value of last error character(:), allocatable, private :: errsrc !! first location where error occured character(:), allocatable, private :: errmsg !! error message character(:), allocatable, private :: callers !! list of all callers following the error contains !> set an error message at the location where error occured procedure :: err_set => t_err2_err_set !> add a caller to the list of callers after an error procedure :: err_caller => t_err2_err_caller !> output the error message of currntly recevied error procedure :: err_write => t_err2_err_write !> check if an error is present or not procedure :: err_present => t_err2_err_present !> reset the variables of this class procedure :: err_reset => t_err2_err_reset final :: error_destructor end type t_err2 interface t_err2 module procedure :: error_constructor end interface t_err2 contains function error_constructor()result( this ) type( t_err2 ), pointer :: this allocate( t_err2 :: this ) this% last_ierr = 0 end function error_constructor subroutine error_destructor( self ) type( t_err2 ), intent( inout ) :: self ! output any remaining error from here if( self% last_ierr /= 0 ) call self% err_write(__FILE__,__LINE__) ! deallocate self% last_ierr = 0 if(allocated(self%errsrc))deallocate(self%errsrc) if(allocated(self%errmsg))deallocate(self%errmsg) if(allocated(self%callers))deallocate(self%callers) end subroutine error_destructor subroutine t_err2_err_set( self, ierr, file, linenr, msg, msg_arr ) !! set the information about an error. !! This should be called when an error is first encountered. implicit none class( t_err2 ), intent(inout) :: self integer, intent(in) :: ierr character(*), intent(in) :: file integer, intent(in) :: linenr character(*), intent(in), optional :: msg !! single-string error message character(*), intent(in), optional :: msg_arr(:) !! array of words to be added at end of error message character(len=516) :: loc integer :: i ! output any previous error if( self%last_ierr /= 0 ) call self%err_write(__FILE__,__LINE__) loc = "" write(loc, "(a,1x,a,1x,i0)") file,"line:",linenr ! delete previous error message if( allocated( self% errsrc))deallocate(self%errsrc) self%errsrc = trim(loc) ! if there is an error message, copy it if( present(msg))then if( allocated(self%errmsg))deallocate(self%errmsg) self%errmsg="" self%errmsg = msg end if if(present(msg_arr)) then if(.not.present(msg)) self%errmsg="" do i = 1, size(msg_arr(:)) self%errmsg = trim(self%errmsg)//" "//trim(msg_arr(i)) end do end if ! set list of callers to none if( allocated(self%callers))deallocate(self%callers) self%callers="" ! copy ierr value self%last_ierr = ierr end subroutine t_err2_err_set subroutine t_err2_err_caller( self, file, linenr ) !! add a caller to list of callers implicit none class( t_err2 ), intent(inout) :: self character(*), intent(in) :: file integer, intent(in) :: linenr character(len=516) :: loc loc = "" write(loc, "(a,1x,a,1x,i0)") file,"line:",linenr if( len_trim(self%callers) < 1)then ! if first caller, write self%callers = str_caller//trim(loc) else ! \n + append self%callers = self%callers//new_line("a")//str_caller//trim(loc) end if end subroutine t_err2_err_caller subroutine t_err2_err_write( self, file, linenr, fileunit, kill ) !! output the error info to screen. !! If fileunit is provided, the unit should be open for writing. !! If fileunit is not provided, output to screen. use, intrinsic :: iso_fortran_env, only: stdout => output_unit implicit none class( t_err2 ), intent(inout) :: self character(*), intent(in) :: file integer, intent(in) :: linenr integer, intent(in), optional :: fileunit !! output fileunit logical, intent(in), optional :: kill !! if `.true.`, kill the program via simple `stop` integer :: u0 character(len=512) :: loc, msg integer :: nlen u0 = stdout if(present(fileunit)) then u0 = fileunit ! test if file is actually open... end if write(u0,"(a)") repeat("=", 60) write(u0,"(a,1x,i0)") ":>>"//str_warn//"from "//my_name//", ierr value:", self% last_ierr write(u0,"(a)") ":>> Output from err_write():" ! saved error message if( allocated(self%errmsg)) then nlen = min(len(msg), len(self%errmsg)) msg="" msg(1:nlen) = self%errmsg(1:nlen) else msg = "Message unknown" end if write(u0,"(a,a)") str_message, trim(msg) ! saved error location if( allocated(self%errsrc))then nlen = min(len(loc),len(self%errsrc)) loc="" loc(1:nlen) = self%errsrc(1:nlen) else loc = "Source location unknown" end if write(u0, "(a,a)") str_source, trim(loc) ! write list of callers if( len_trim(self%callers) > 1) write(u0, "(a)") trim(self%callers) ! write last caller (called this routine) write(u0,"(3(a,1x),i0)") str_lastcaller,trim(file), "line:", linenr if( present(kill)) then if( kill ) then write(u0,*) "Stopping due to keyword `kill`" ! #ifdef USEMPI ! ! properly kill an mpi program with abort world_comm ! #endif write(u0,"(a)") repeat("=", 60) stop end if end if write(u0,"(a)") repeat("=", 60) ! reset the error state call self% err_reset() end subroutine t_err2_err_write subroutine t_err2_err_present( self, ierr ) !! Check if an error is present or not. !! On output, `ierr` has value of error present in the module, and 0 otherwise class(t_err2), intent(inout) :: self integer, intent(out) :: ierr ierr = self% last_ierr end subroutine t_err2_err_present subroutine t_err2_err_reset( self ) class(t_err2), intent(inout) :: self self% last_ierr = 0 ! self% errsrc="" ! self% errmsg="" ! self% callers="" if(allocated(self%errsrc))deallocate(self%errsrc) if(allocated(self%errmsg))deallocate(self%errmsg) if(allocated(self%callers))deallocate(self%callers) end subroutine t_err2_err_reset end module m_err2 subroutine err2_init( cptr ) use, intrinsic :: iso_c_binding, only: c_ptr, c_loc use m_err2, only: t_err2 type( c_ptr ) :: cptr type( t_err2 ), pointer :: fptr fptr => t_err2() cptr = c_loc( fptr ) nullify(fptr) end subroutine err2_init subroutine err2_set( cptr, ierr, file, linenr, msg ) use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer, c_associated use m_err2, only: t_err2 type( c_ptr ), intent(in) :: cptr integer, intent(in) :: ierr character(*), intent(in) :: file integer, intent(in) :: linenr character(*), intent(in) :: msg type( t_err2 ), pointer :: fptr if( .not. c_associated(cptr)) then write(*,*) repeat('-',40) write(*,*) " >>:: err2 is not initialised, cannot set error." write(*,*) repeat('-',40) return end if call c_f_pointer( cptr, fptr ) call fptr% err_set( ierr, file, linenr, msg=msg ) nullify(fptr) end subroutine err2_set subroutine err2_caller( cptr, file, linenr ) use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer, c_associated use m_err2, only: t_err2 type( c_ptr ), intent(in) :: cptr character(*), intent(in) :: file integer, intent(in) :: linenr type( t_err2 ), pointer :: fptr if( .not. c_associated(cptr)) then write(*,*) repeat('-',40) write(*,*) " >>:: err2 is not initialised." write(*,*) repeat('-',40) return end if call c_f_pointer( cptr, fptr ) call fptr% err_caller( file, linenr ) nullify(fptr) end subroutine err2_caller subroutine err2_write( cptr, file, linenr ) use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer, c_associated use m_err2, only: t_err2 type( c_ptr ), intent(in) :: cptr character(*), intent(in) :: file integer, intent(in) :: linenr type( t_err2 ), pointer :: fptr if( .not. c_associated(cptr)) then write(*,*) repeat('-',40) write(*,*) " >>:: err2 is not initialised." write(*,*) repeat('-',40) return end if call c_f_pointer( cptr, fptr ) call fptr% err_write( file, linenr ) nullify(fptr) end subroutine err2_write subroutine err2_present( cptr, ierr ) use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer, c_associated use m_err2, only: t_err2 type( c_ptr ), intent(in) :: cptr integer, intent(out) :: ierr type( t_err2 ), pointer :: fptr if( .not. c_associated(cptr)) then write(*,*) repeat('-',40) write(*,*) " >>:: err2 is not initialised." write(*,*) repeat('-',40) return end if call c_f_pointer( cptr, fptr ) call fptr% err_present( ierr ) nullify(fptr) end subroutine err2_present subroutine err2_reset( cptr ) use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer, c_associated use m_err2, only: t_err2 type( c_ptr ), intent(in) :: cptr type( t_err2 ), pointer :: fptr if( .not. c_associated(cptr)) then write(*,*) repeat('-',40) write(*,*) " >>:: err2 is not initialised." write(*,*) repeat('-',40) return end if call c_f_pointer( cptr, fptr ) call fptr% err_reset() nullify(fptr) end subroutine err2_reset subroutine err2_close( cptr ) use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer, c_associated use m_err2, only: t_err2 type( c_ptr ), intent(in) :: cptr type( t_err2 ), pointer :: fptr if( .not. c_associated(cptr)) then write(*,*) repeat('-',40) write(*,*) " >>:: err2 is not initialised." write(*,*) repeat('-',40) return end if call c_f_pointer( cptr, fptr ) deallocate(fptr) end subroutine err2_close