Loading...
Searching...
No Matches
conditional.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_conditional Conditional
3!! Full-featured conditional compilation (#if / #ifdef / #else / #endif) for the fpx preprocessor
4!! This module implements standard-conforming conditional compilation with support for:
5!! - `#if` with arbitrary constant expressions (using `evaluate_expression`)
6!! - `#ifdef` / `#ifndef` and #elifdef` / `#elifndef` for testing macro existence
7!! - `#elif` chains (multiple alternative branches)
8!! - `#else` as final fallback
9!! - Proper nesting up to `MAX_COND_DEPTH` levels
10!! - Correct "first-match" semantics — once a branch is taken, later `#elif`/`#else` are skipped
11!! - Integration with macro expansion via `is_defined()` and expression evaluator
12!! - Comprehensive diagnostics when `verbose = .true.`
13!!
14!! The state is maintained in a global stack (`cond_stack`) with `cond_depth` tracking nesting.
15!! The function `is_active()` is used throughout the preprocessor to decide whether a line
16!! should be emitted or skipped.
17!!
18!! <h2 class="groupheader">Examples</h2>
19!!
20!! 1. Classic include guard pattern:
21!! @code{.f90}
22!! #ifndef MY_HEADER_H
23!! #define MY_HEADER_H
24!!
25!! ! ... header content ...
26!!
27!! #endif
28!! ...
29!! @endcode
30!!
31!! 2. Feature selection with #if and #elif:
32!! @code{.f90}
33!! #if DEBUG >= 2
34!! print *, 'Extra verbose debugging enabled'
35!! #elif DEBUG == 1
36!! print *, 'Standard debugging'
37!! #else
38!! ! Silent mode
39!! #endif
40!! !..
41!! @endcode
42!!
43!! 3. Cross-platform code selection:
44!! @code{.f90}
45!! #ifdef _OPENMP
46!! use omp_lib
47!! #else
48!! integer, parameter :: omp_get_thread_num = 0
49!! #endif
50!! ...
51!! @endcode
52!!
53!! 4. Complex expression in #if (requires `evaluate_expression` support):
54!! @code{.f90}
55!! #if defined(USE_MPI) && (MPI_VERSION >= 3)
56!! use mpi_f08
57!! #endif
58!! ...
59!! @endcode
60module fpx_conditional
61 use fpx_constants
62 use fpx_logging
63 use fpx_string
64 use fpx_macro, only: macro, is_defined
65 use fpx_operators, only: evaluate_expression
66
67 implicit none; private
68
69 public :: handle_if, &
78
79 !> State of a single conditional block
80 !! <h2 class="groupheader">Constructors</h2>
81 !! Initializes a new instance of the @ref cond_state type
82 !! <h3>cond_state(logical, logical)</h3>
83 !! @verbatim type(cond_state) function cond_state(logical active, logical has_met) @endverbatim
84 !!
85 !! @param[in] active whether code in this block should be emitted
86 !! @param[in] has_met whether a true branch has already been taken at this nesting level
87 !!
88 !! @return The constructed @ref cond_state object.
89 !!
90 !! <h2 class="groupheader">Remarks</h2>
91 !! @ingroup group_conditional
92 type, public :: cond_state
93 logical, public :: active !< Indicate whether the condition is active
94 logical, public :: has_met !< Indicates whether the condition has been met
95 end type
96
97 !> @brief Global stack of conditional states (depth-limited)
98 !! @ingroup group_conditional
100
101 !> @brief Current nesting depth of conditional directives (0 = outside any #if)
102 !! @ingroup group_conditional
103 integer, public :: cond_depth = 0
104
105contains
106
107 !> Determine if current line is inside an active conditional block
108 !! @return .true. if all enclosing #if/#elif/#else branches are active
109 !!
110 !! @b Remarks
111 !! @ingroup group_conditional
112 logical function is_active() result(res)
113 integer :: i
114 res = .true.
115 do i = 1, cond_depth + 1
116 if (.not. cond_stack(i)%active) then
117 res = .false.
118 exit
119 end if
120 end do
121 end function
122
123 !> Process a #if directive with constant expression evaluation
124 !! Evaluates the expression after #if using `evaluate_expression()` and pushes
125 !! a new state onto the conditional stack.
126 !! @param[in] line Full source line containing the directive
127 !! @param[in] filename Current file (for error messages)
128 !! @param[in] line_num Line number (for error messages)
129 !! @param[in] macros Current macro table
130 !! @param[in] token Usually 'IF'
131 !!
132 !! @b Remarks
133 !! @ingroup group_conditional
134 subroutine handle_if(line, filename, line_num, macros, token)
135 character(*), intent(in) :: line, filename
136 integer, intent(in) :: line_num
137 type(macro), intent(in) :: macros(:)
138 character(*), intent(in) :: token
139 !private
140 character(:), allocatable :: expr
141 logical :: result, parent_active
142 integer :: pos
143
144 if (cond_depth + 1 > max_cond_depth) then
145 if (verbose) print *, "Error: Conditional nesting too deep at ", trim(filename), ":", line_num
146 return
147 end if
148
149 pos = index(uppercase(line), token) + len(token)
150 expr = trim(adjustl(line(pos:)))
151 if (verbose) print *, "Evaluating #if: '", trim(expr), "'"
152 result = evaluate_expression(expr, macros)
153 parent_active = is_active()
155 cond_stack(cond_depth + 1)%active = result .and. parent_active
156 cond_stack(cond_depth + 1)%has_met = result
157 if (verbose) print *, "#if result: ", result, ", cond_depth = ", cond_depth, ", active = ", cond_stack(cond_depth + 1)%&
158 active
159 end subroutine
160
161 !> Process #ifdef – test if a macro is defined
162 !! @param[in] line Full source line containing the directive
163 !! @param[in] filename Current file (for error messages)
164 !! @param[in] line_num Line number (for error messages)
165 !! @param[in] macros Current macro table
166 !! @param[in] token Usually 'IFDEF'
167 !!
168 !! @b Remarks
169 !! @ingroup group_conditional
170 subroutine handle_ifdef(line, filename, line_num, macros, token)
171 character(*), intent(in) :: line
172 character(*), intent(in) :: filename
173 integer, intent(in) :: line_num
174 type(macro), intent(in) :: macros(:)
175 character(*), intent(in) :: token
176 !private
177 character(:), allocatable :: name
178 logical :: defined, parent_active
179 integer :: pos
180
181 if (cond_depth + 1 > max_cond_depth) then
182 if (verbose) print *, "Error: Conditional nesting too deep at ", trim(filename), ":", line_num
183 return
184 end if
185
186 pos = index(uppercase(line), token) + len(token)
187 name = trim(adjustl(line(pos:)))
188 defined = is_defined(name, macros)
189 parent_active = is_active()
191 cond_stack(cond_depth + 1)%active = defined .and. parent_active
192 cond_stack(cond_depth + 1)%has_met = defined
193 end subroutine
194
195 !> Process #ifndef – test if a macro is NOT defined
196 !! @param[in] line Full source line containing the directive
197 !! @param[in] filename Current file (for error messages)
198 !! @param[in] line_num Line number (for error messages)
199 !! @param[in] macros Current macro table
200 !! @param[in] token Usually 'IFNDEF'
201 !!
202 !! @b Remarks
203 !! @ingroup group_conditional
204 subroutine handle_ifndef(line, filename, line_num, macros, token)
205 character(*), intent(in) :: line
206 character(*), intent(in) :: filename
207 integer, intent(in) :: line_num
208 type(macro), intent(in) :: macros(:)
209 character(*), intent(in) :: token
210 !private
211 character(:), allocatable :: name
212 logical :: defined, parent_active
213 integer :: pos
214
215 if (cond_depth + 1 > max_cond_depth) then
216 if (verbose) print *, "Error: Conditional nesting too deep at ", trim(filename), ":", line_num
217 return
218 end if
219
220 pos = index(uppercase(line), token) + len(token)
221 name = trim(adjustl(line(pos:)))
222 defined = is_defined(name, macros)
223 parent_active = is_active()
225 cond_stack(cond_depth + 1)%active = (.not. defined) .and. parent_active
226 cond_stack(cond_depth + 1)%has_met = .not. defined
227 end subroutine
228
229 !> Process #elif – alternative branch after #if/#elif
230 !! Only activates if no previous branch in the group was taken.
231 !! @param[in] line Full source line containing the directive
232 !! @param[in] filename Current file (for error messages)
233 !! @param[in] line_num Line number (for error messages)
234 !! @param[in] macros Current macro table
235 !! @param[in] token Usually 'ELIF'
236 !!
237 !! @b Remarks
238 !! @ingroup group_conditional
239 subroutine handle_elif(line, filename, line_num, macros, token)
240 character(*), intent(in) :: line, filename
241 integer, intent(in) :: line_num
242 type(macro), intent(in) :: macros(:)
243 character(*), intent(in) :: token
244 !private
245 character(:), allocatable :: expr
246 logical :: result, parent_active
247 integer :: pos
248
249 if (cond_depth == 0) then
250 if (verbose) print *, "Error: #elif without matching #if at ", trim(filename), ":", line_num
251 return
252 end if
253
254 pos = index(uppercase(line), token) + len(token)
255 expr = trim(adjustl(line(pos:)))
256 result = evaluate_expression(expr, macros)
257 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
258 if (.not. cond_stack(cond_depth + 1)%has_met) then
259 cond_stack(cond_depth + 1)%active = result .and. parent_active
260 if (result) cond_stack(cond_depth + 1)%has_met = .true.
261 else
262 cond_stack(cond_depth + 1)%active = .false.
263 end if
264 end subroutine
265
266 !> Process #elifdef – test if a macro is defined
267 !! @param[in] line Full source line containing the directive
268 !! @param[in] filename Current file (for error messages)
269 !! @param[in] line_num Line number (for error messages)
270 !! @param[in] macros Current macro table
271 !! @param[in] token Usually 'ELIFDEF'
272 !!
273 !! @b Remarks
274 !! @ingroup group_conditional
275 subroutine handle_elifdef(line, filename, line_num, macros, token)
276 character(*), intent(in) :: line
277 character(*), intent(in) :: filename
278 integer, intent(in) :: line_num
279 type(macro), intent(in) :: macros(:)
280 character(*), intent(in) :: token
281 !private
282 character(:), allocatable :: name
283 logical :: defined, parent_active
284 integer :: pos
285
286 if (cond_depth == 0) then
287 if (verbose) print *, "Error: #elifdef without matching #if at ", trim(filename), ":", line_num
288 return
289 end if
290
291 pos = index(uppercase(line), token) + len(token)
292 name = trim(adjustl(line(pos:)))
293 defined = is_defined(name, macros)
294 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
295 if (.not. cond_stack(cond_depth + 1)%has_met) then
296 cond_stack(cond_depth + 1)%active = defined .and. parent_active
297 if (defined) cond_stack(cond_depth + 1)%has_met = .true.
298 else
299 cond_stack(cond_depth + 1)%active = .false.
300 end if
301 end subroutine
302
303 !> Process #elifndef – test if a macro is not defined
304 !! @param[in] line Full source line containing the directive
305 !! @param[in] filename Current file (for error messages)
306 !! @param[in] line_num Line number (for error messages)
307 !! @param[in] macros Current macro table
308 !! @param[in] token Usually 'ELIFNDEF'
309 !!
310 !! @b Remarks
311 !! @ingroup group_conditional
312 subroutine handle_elifndef(line, filename, line_num, macros, token)
313 character(*), intent(in) :: line
314 character(*), intent(in) :: filename
315 integer, intent(in) :: line_num
316 type(macro), intent(in) :: macros(:)
317 character(*), intent(in) :: token
318 !private
319 character(:), allocatable :: name
320 logical :: defined, parent_active
321 integer :: pos
322
323 if (cond_depth == 0) then
324 if (verbose) print *, "Error: #elifndef without matching #if at ", trim(filename), ":", line_num
325 return
326 end if
327
328 pos = index(uppercase(line), token) + len(token)
329 name = trim(adjustl(line(pos:)))
330 defined = is_defined(name, macros)
331 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
332 if (.not. cond_stack(cond_depth + 1)%has_met) then
333 cond_stack(cond_depth + 1)%active = (.not. defined) .and. parent_active
334 if (.not. defined) cond_stack(cond_depth + 1)%has_met = .true.
335 else
336 cond_stack(cond_depth + 1)%active = .false.
337 end if
338 end subroutine
339
340 !> Process #else – final fallback branch
341 !! Activates only if no previous #if/#elif branch was true.
342 !! @param[in] filename Current file (for error messages)
343 !! @param[in] line_num Line number (for error messages)
344 !!
345 !! @b Remarks
346 !! @ingroup group_conditional
347 subroutine handle_else(filename, line_num)
348 character(*), intent(in) :: filename
349 integer, intent(in) :: line_num
350 logical :: parent_active
351
352 if (cond_depth == 0) then
353 if (verbose) print *, "Error: #else without matching #if at ", trim(filename), ":", line_num
354 return
355 end if
356
357 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
358 if (.not. cond_stack(cond_depth + 1)%has_met) then
359 cond_stack(cond_depth + 1)%active = parent_active
360 cond_stack(cond_depth + 1)%has_met = .true.
361 else
362 cond_stack(cond_depth + 1)%active = .false.
363 end if
364 if (verbose) print *, "#else at depth ", cond_depth, ", active = ", cond_stack(cond_depth + 1)%active
365 end subroutine
366
367 !> Process #endif – end of conditional block
368 !! Pops the top state from the stack. Reports error on unmatched #endif.
369 !! @param[in] filename Current file (for error messages)
370 !! @param[in] line_num Line number (for error messages)
371 !!
372 !! @b Remarks
373 !! @ingroup group_conditional
374 subroutine handle_endif(filename, line_num)
375 character(*), intent(in) :: filename
376 integer, intent(in) :: line_num
377
378 if (cond_depth == 0) then
379 if (verbose) print *, "Error: #endif without matching #if at ", trim(filename), ":", line_num
380 return
381 end if
382
383 if (verbose) print *, "#endif at depth ", cond_depth
385 end subroutine
386
387end module
subroutine, public handle_elifndef(line, filename, line_num, macros, token)
Process elifndef – test if a macro is not defined.
logical function, public is_active()
Determine if current line is inside an active conditional block.
subroutine, public handle_endif(filename, line_num)
Process endif – end of conditional block Pops the top state from the stack. Reports error on unmatche...
subroutine, public handle_else(filename, line_num)
Process else – final fallback branch Activates only if no previous if/elif branch was true.
integer, public cond_depth
Current nesting depth of conditional directives (0 = outside any if)
subroutine, public handle_ifndef(line, filename, line_num, macros, token)
Process ifndef – test if a macro is NOT defined.
subroutine, public handle_ifdef(line, filename, line_num, macros, token)
Process ifdef – test if a macro is defined.
subroutine, public handle_if(line, filename, line_num, macros, token)
Process a if directive with constant expression evaluation Evaluates the expression after if using ev...
subroutine, public handle_elif(line, filename, line_num, macros, token)
Process elif – alternative branch after if/elif Only activates if no previous branch in the group was...
subroutine, public handle_elifdef(line, filename, line_num, macros, token)
Process elifdef – test if a macro is defined.
type(cond_state), dimension(max_cond_depth), public cond_stack
Global stack of conditional states (depth-limited)
integer, parameter, public max_cond_depth
Maximum nesting depth for conditional statements, set to 50 to prevent overly complex logic.
Definition constants.f90:19
logical, public verbose
Master switch for verbose diagnostic output Default value is .false. (quiet mode)....
Definition logging.f90:56
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:677
logical function, public evaluate_expression(expr, macros, val)
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
Definition operators.f90:93
pure character(len_trim(str)) function, public uppercase(str)
Convert string to upper case (respects contents of quotes).
Definition string.f90:583
Return the length of a string.
Definition string.f90:130
Return the trimmed string.
Definition string.f90:146
State of a single conditional block.
Derived type representing a single preprocessor macro Extends string with macro-specific fields: rep...
Definition macro.f90:103