Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assert always #43

Merged
merged 4 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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