Skip to content

Commit

Permalink
Merge pull request #43 from bonachea/assert_always
Browse files Browse the repository at this point in the history
Assert always
  • Loading branch information
rouson authored Nov 8, 2024
2 parents 2bd9f8f + b37b5d9 commit 8e33ee8
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 18 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ preprocessor ASSERTIONS to non-zero, eg:
```
fpm build --flag "-DASSERTIONS"
```
The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke the `assert` subroutine via the three provided macros.
Invoking `assert` this way insures that `assert` invocations will be completely removed whenever the `ASSERTIONS` macro is undefined (or defined to zero) during compilation.
The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke assertions via the three provided macros.
Invoking assertions this way ensures such calls will be completely removed whenever the `ASSERTIONS` macro is undefined (or defined to zero) during compilation.
Due to a limitation of `fpm`, this approach works best if the project using Assert is also a `fpm` project.
If instead `fpm install` is used, then either the user must copy `include/assert_macros.h` to the installation directory (default: `~/.local/include`) or
the user must invoke `assert` directly (via `call assert(...)`).
Expand Down
9 changes: 5 additions & 4 deletions example/invoke-via-macro.F90
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
program invoke_via_macro
!! Demonstrate how to invoke the 'assert' subroutine using a preprocessor macro that facilitates
!! the complete removal of the call in the absence of the compiler flag: -DASSERTIONS
use assert_m, only : assert, intrinsic_array_t, string
!! If an "only" clause is employed as above, it must include the "string" function that the
!! call_assert* macros reference when transforming the code below into "assert" subroutine calls.
use assert_m ! <--- this is the recommended use statement
!! If an "only" clause is employed above, the symbols required by the
!! macro expansion are subject to change without notice between versions.
!! You have been warned!
implicit none

#if !ASSERTIONS
Expand All @@ -15,7 +16,7 @@ program invoke_via_macro
print *
#endif

! The C preprocessor will convert each call_assert* macro below into calls to the "assert" subroutine
! The C preprocessor will convert each call_assert* macro below into calls that enforce the assertion
! whenever the ASSERTIONS macro is defined to non-zero (e.g. via the -DASSERTIONS compiler flag).
! Whenever the ASSERTIONS macro is undefined or defined to zero (e.g. via the -DASSERTIONS=0 compiler flag),
! these calls will be entirely removed by the preprocessor.
Expand Down
6 changes: 3 additions & 3 deletions include/assert_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
#endif

#if ASSERTIONS
# define call_assert(assertion) call assert(assertion, "call_assert(" // STRINGIFY(assertion) // ") in file " // __FILE__ // ", line " // string(__LINE__))
# define call_assert_describe(assertion, description) call assert(assertion, description // " in file " // __FILE__ // ", line " // string(__LINE__))
# define call_assert_diagnose(assertion, description, diagnostic_data) call assert(assertion, description // " in file " // __FILE__ // ", line " // string(__LINE__), diagnostic_data)
# define call_assert(assertion) call assert_always(assertion, "call_assert(" // STRINGIFY(assertion) // ") in file " // __FILE__ // ", line " // string(__LINE__))
# define call_assert_describe(assertion, description) call assert_always(assertion, description // " in file " // __FILE__ // ", line " // string(__LINE__))
# define call_assert_diagnose(assertion, description, diagnostic_data) call assert_always(assertion, description // " in file " // __FILE__ // ", line " // string(__LINE__), diagnostic_data)
#else
# define call_assert(assertion)
# define call_assert_describe(assertion, description)
Expand Down
13 changes: 11 additions & 2 deletions src/assert/assert_subroutine_m.F90
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module assert_subroutine_m
!!
implicit none
private
public :: assert
public :: assert, assert_always

#ifndef USE_ASSERTIONS
# if ASSERTIONS
Expand All @@ -47,7 +47,8 @@ module assert_subroutine_m
interface

pure module subroutine assert(assertion, description, diagnostic_data)
!! If assertion is .false., error-terminate with a character stop code that contains diagnostic_data if present
!! If assertion is .false. and enforcement is enabled (e.g. via -DASSERTIONS=1),
!! then error-terminate with a character stop code that contains diagnostic_data if present
implicit none
logical, intent(in) :: assertion
!! Most assertions will be expressions such as i>0
Expand All @@ -57,6 +58,14 @@ pure module subroutine assert(assertion, description, diagnostic_data)
!! Data to include in an error ouptput: may be of an intrinsic type or a type that extends characterizable_t
end subroutine

pure module subroutine assert_always(assertion, description, diagnostic_data)
!! Same as above but always enforces the assertion (regardless of ASSERTIONS)
implicit none
logical, intent(in) :: assertion
character(len=*), intent(in) :: description
class(*), intent(in), optional :: diagnostic_data
end subroutine

end interface

end module assert_subroutine_m
14 changes: 9 additions & 5 deletions src/assert/assert_subroutine_s.F90
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@
contains

module procedure assert
use characterizable_m, only : characterizable_t

character(len=:), allocatable :: header, trailer

toggle_assertions: &
if (enforce_assertions) then
call assert_always(assertion, description, diagnostic_data)
end if toggle_assertions

end procedure

module procedure assert_always
use characterizable_m, only : characterizable_t

character(len=:), allocatable :: header, trailer

check_assertion: &
if (.not. assertion) then
Expand Down Expand Up @@ -59,8 +65,6 @@

end if check_assertion

end if toggle_assertions

contains

pure function string(numeric) result(number_as_string)
Expand Down
29 changes: 27 additions & 2 deletions test/test-assert-macro.F90
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,32 @@ program test_assert_macros

#undef ASSERTIONS
#include "assert_macros.h"
call_assert_describe(.false., "")
print *," passes on being removed by the preprocessor when ASSERTIONS is undefined"
call_assert_diagnose(.false., "", "")
print *," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('')

!------------------------------------------

#undef ASSERTIONS
#define ASSERTIONS 1
#include "assert_macros.h"
print *,"The call_assert_* macros"
block
logical :: foo
foo = check_assert(.true.)
print *," pass on invocation from a pure function"
end block

contains

pure function check_assert(cond) result(ok)
logical, intent(in) :: cond
logical ok

call_assert(cond)
call_assert_describe(cond, "check_assert")
call_assert_diagnose(cond, "check_assert", "")

ok = .true.
end function

end program

0 comments on commit 8e33ee8

Please sign in to comment.