Loading...
Searching...
No Matches
conditional.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_conditional Conditional
3!! Conditional support for the fpx preprocessor.
4!!
5!! This module implements the complete conditional compilation machinery used
6!! by fpx. It provides functionality equivalent to the traditional C
7!! preprocessor directives while also introducing a few convenience extensions.
8!!
9!! Supported directives include:
10!!
11!! - `#if` / `#elif`
12!! Evaluate arbitrary constant expressions using
13!! `evaluate_expression()`.
14!!
15!! - `#ifdef` / `#ifndef`
16!! Test whether a macro has been defined.
17!!
18!! - `#elifdef` / `#elifndef`
19!! fpx extensions combining `#elif` semantics with macro existence tests.
20!!
21!! - `#else`
22!! Select the fallback branch when no previous branch in the same
23!! conditional group has been activated.
24!!
25!! - `#endif`
26!! Terminate the current conditional block.
27!!
28!! Nested conditional blocks are supported up to
29!! `MAX_COND_DEPTH` levels.
30!!
31!! The implementation follows the standard "first-match" semantics:
32!! once a branch of a conditional group evaluates to true,
33!! all remaining `#elif`, `#elifdef`, `#elifndef`, and `#else`
34!! directives belonging to the same group are ignored.
35!!
36!! Internally, the module maintains a stack of conditional states
37!! (`cond_stack`) together with the current nesting depth
38!! (`cond_depth`). The helper function `is_active()` determines
39!! whether the current source line belongs to an active branch and
40!! therefore should be processed by the remainder of the preprocessor.
41!!
42!! @section conditional_design Design
43!!
44!! Each active conditional nesting level stores two pieces of state:
45!!
46!! - `active`
47!! Indicates whether the current branch should emit code.
48!!
49!! - `has_met`
50!! Indicates whether a previous branch within the same
51!! `#if`/`#elif`/`#else` group has already been selected.
52!!
53!! This design allows efficient evaluation of deeply nested
54!! conditionals while preserving correct first-match semantics.
55!!
56!! @section conditional_examples Examples
57!!
58!! -# Include guard pattern:
59!!
60!! @code{.f90}
61!! #ifndef MY_HEADER_H
62!! #define MY_HEADER_H
63!!
64!! ! Header contents
65!!
66!! #endif
67!! ...
68!! @endcode
69!!
70!! -# Feature selection using expression evaluation:
71!!
72!! @code{.f90}
73!! #if DEBUG >= 2
74!! print *, 'Verbose debugging'
75!! #elif DEBUG == 1
76!! print *, 'Standard debugging'
77!! #else
78!! ! Silent mode
79!! #endif
80!! ...
81!! @endcode
82!!
83!! -# Platform-dependent compilation:
84!!
85!! @code{.f90}
86!! #ifdef _OPENMP
87!! use omp_lib
88!! #else
89!! integer, parameter :: omp_get_thread_num = 0
90!! #endif
91!! ...
92!! @endcode
93!!
94!! -# Conditional compilation using macro existence:
95!!
96!! @code{.f90}
97!! #if defined(USE_MPI) && (MPI_VERSION >= 3)
98!! use mpi_f08
99!! #endif
100!! ...
101!! @endcode
102!!
103!! -# Using the fpx extension `#elifdef`:
104!!
105!! @code{.f90}
106!! #ifdef USE_CUDA
107!! call gpu_backend()
108!! #elifdef USE_OPENMP
109!! call omp_backend()
110!! #else
111!! call serial_backend()
112!! #endif
113!! ...
114!! @endcode
115!!
116!! -# Nested conditionals:
117!!
118!! @code{.f90}
119!! #ifdef DEBUG
120!! #if DEBUG > 1
121!! print *, 'Extra diagnostics'
122!! #endif
123!! #endif
124!! ...
125!! @endcode
126module fpx_conditional
127 use fpx_constants
128 use fpx_logging
129 use fpx_string
130 use fpx_macro, only: macro, is_defined
131 use fpx_operators, only: evaluate_expression
132 use fpx_context
133
134 implicit none; private
135
136 public :: handle_if, &
137 handle_ifdef, &
139 handle_elif, &
140 handle_else, &
141 handle_endif, &
145
146 !> State associated with a single conditional nesting level.
147 !!
148 !! Each `#if` directive pushes one instance of this type onto
149 !! `cond_stack`, and the corresponding `#endif` removes it.
150 !!
151 !! The combination of `active` and `has_met` implements the
152 !! standard first-match semantics of conditional preprocessing.
153 !!
154 !! @section cond_state_constructors Constructors
155 !!
156 !! @b Constructor
157 !! @code{.f90} type(cond_state) function cond_state(logical active, logical has_met) @endcode
158 !! @param[in] active
159 !! Whether the current branch is active and should emit code.
160 !! @param[in] has_met
161 !! Whether a previous branch in the same conditional group
162 !! has already evaluated to true.
163 !! @return A newly constructed conditional state object.
164 !! @ingroup group_conditional
165 type, public :: cond_state
166 logical, public :: active
167 logical, public :: has_met
168 end type
169
170 !> @brief Global stack of conditional states (depth-limited)
171 !! @ingroup group_conditional
173
174 !> @brief Current nesting depth of conditional directives (0 = outside any #if)
175 !! @ingroup group_conditional
176 integer, public :: cond_depth = 0
177
178contains
179
180 !> Determine whether the current source position is active.
181 !!
182 !! Traverses all enclosing conditional levels and returns `.true.`
183 !! only if every surrounding conditional branch is active.
184 !!
185 !! This routine is used throughout the preprocessing pipeline to
186 !! decide whether directives should be executed and whether
187 !! ordinary source lines should be emitted.
188 !!
189 !! @return
190 !! `.true.` if the current line belongs to an active branch;
191 !! `.false.` otherwise.
192 !!
193 !! @ingroup group_conditional
194 logical function is_active() result(res)
195 integer :: i
196 res = .true.
197 do i = 1, cond_depth + 1
198 if (.not. cond_stack(i)%active) then
199 res = .false.
200 exit
201 end if
202 end do
203 end function
204
205 !> Process a #if directive with constant expression evaluation
206 !! Evaluates the expression after #if using `evaluate_expression()` and pushes
207 !! a new state onto the conditional stack.
208 !! @param[in] ctx Context source line containing the directive
209 !! @param[inout] macros Current macro table
210 !! @param[in] token Usually 'if'
211 !!
212 !! @ingroup group_conditional
213 subroutine handle_if(ctx, macros, token)
214 type(context), intent(in) :: ctx
215 type(macro), allocatable, intent(inout) :: macros(:)
216 character(*), intent(in) :: token
217 !private
218 character(:), allocatable :: expr
219 logical :: result, parent_active
220 integer :: pos
221
222 if (cond_depth + 1 > max_cond_depth) then
223 call printf(render(diagnostic_report(level_error, &
224 message='Conditional nesting too deep', &
225 source=trim(ctx%path)), &
226 ctx%content, ctx%line))
227 return
228 end if
229
230 pos = index(lowercase(ctx%content), token) + len(token)
231 expr = trim(adjustl(ctx%content(pos:)))
232 result = evaluate_expression(expr, macros, ctx)
233 parent_active = is_active()
235 cond_stack(cond_depth + 1)%active = result .and. parent_active
236 cond_stack(cond_depth + 1)%has_met = result
237 end subroutine
238
239 !> Process #ifdef - test if a macro is defined
240 !! @param[in] ctx Context source line containing the directive
241 !! @param[in] macros Current macro table
242 !! @param[in] token Usually 'ifdef'
243 !!
244 !! @ingroup group_conditional
245 subroutine handle_ifdef(ctx, macros, token)
246 type(context), intent(in) :: ctx
247 type(macro), intent(in) :: macros(:)
248 character(*), intent(in) :: token
249 !private
250 character(:), allocatable :: name
251 logical :: defined, parent_active
252 integer :: pos
253
254 if (cond_depth + 1 > max_cond_depth) then
255 call printf(render(diagnostic_report(level_error, &
256 message='Conditional nesting too deep', &
257 source=trim(ctx%path)), &
258 ctx%content, ctx%line))
259 return
260 end if
261
262 pos = index(lowercase(ctx%content), token) + len(token)
263 name = trim(adjustl(ctx%content(pos:)))
264 defined = is_defined(name, macros)
265 parent_active = is_active()
267 cond_stack(cond_depth + 1)%active = defined .and. parent_active
268 cond_stack(cond_depth + 1)%has_met = defined
269 end subroutine
270
271 !> Process #ifndef - test if a macro is NOT defined
272 !! @param[in] ctx Context source line containing the directive
273 !! @param[in] macros Current macro table
274 !! @param[in] token Usually 'ifndef'
275 !!
276 !! @ingroup group_conditional
277 subroutine handle_ifndef(ctx, macros, token)
278 type(context), intent(in) :: ctx
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 + 1 > max_cond_depth) then
287 call printf(render(diagnostic_report(level_error, &
288 message='Conditional nesting too deep', &
289 source=trim(ctx%path)), &
290 ctx%content, ctx%line))
291 return
292 end if
293
294 pos = index(lowercase(ctx%content), token) + len(token)
295 name = trim(adjustl(ctx%content(pos:)))
296 defined = is_defined(name, macros)
297 parent_active = is_active()
299 cond_stack(cond_depth + 1)%active = (.not. defined) .and. parent_active
300 cond_stack(cond_depth + 1)%has_met = .not. defined
301 end subroutine
302
303 !> Process #elif - alternative branch after #if/#elif
304 !! Only activates if no previous branch in the group was taken.
305 !! @param[in] ctx Context source line containing the directive
306 !! @param[inout] macros Current macro table
307 !! @param[in] token Usually 'elif'
308 !!
309 !! @ingroup group_conditional
310 subroutine handle_elif(ctx, macros, token)
311 type(context), intent(in) :: ctx
312 type(macro), allocatable, intent(inout) :: macros(:)
313 character(*), intent(in) :: token
314 !private
315 character(:), allocatable :: expr
316 logical :: result, parent_active
317 integer :: pos
318
319 if (cond_depth == 0) then
320 call printf(render(diagnostic_report(level_error, &
321 message='Syntax error', &
322 label=label_type('#elif without matching #if', 1, len_trim(ctx%content)), &
323 source=trim(ctx%path)), &
324 ctx%content, ctx%line))
325 return
326 end if
327
328 pos = index(lowercase(ctx%content), token) + len(token)
329 expr = trim(adjustl(ctx%content(pos:)))
330 result = evaluate_expression(expr, macros, ctx)
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 = result .and. parent_active
334 if (result) 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 #elifdef - test if a macro is defined
341 !! @param[in] ctx Context source line containing the directive
342 !! @param[in] macros Current macro table
343 !! @param[in] token Usually 'elifdef'
344 !!
345 !! @ingroup group_conditional
346 subroutine handle_elifdef(ctx, macros, token)
347 type(context), intent(in) :: ctx
348 type(macro), intent(in) :: macros(:)
349 character(*), intent(in) :: token
350 !private
351 character(:), allocatable :: name
352 logical :: defined, parent_active
353 integer :: pos
354
355 if (cond_depth == 0) then
356 call printf(render(diagnostic_report(level_error, &
357 message='Syntax error', &
358 label=label_type('#elifdef without matching #if', 1, len_trim(ctx%content)), &
359 source=trim(ctx%path)), &
360 ctx%content, ctx%line))
361 return
362 end if
363
364 pos = index(lowercase(ctx%content), token) + len(token)
365 name = trim(adjustl(ctx%content(pos:)))
366 defined = is_defined(name, macros)
367 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
368 if (.not. cond_stack(cond_depth + 1)%has_met) then
369 cond_stack(cond_depth + 1)%active = defined .and. parent_active
370 if (defined) cond_stack(cond_depth + 1)%has_met = .true.
371 else
372 cond_stack(cond_depth + 1)%active = .false.
373 end if
374 end subroutine
375
376 !> Process #elifndef - test if a macro is not defined
377 !! @param[in] ctx Context source line containing the directive
378 !! @param[in] macros Current macro table
379 !! @param[in] token Usually 'elifndef'
380 !!
381 !! @ingroup group_conditional
382 subroutine handle_elifndef(ctx, macros, token)
383 type(context), intent(in) :: ctx
384 type(macro), intent(in) :: macros(:)
385 character(*), intent(in) :: token
386 !private
387 character(:), allocatable :: name
388 logical :: defined, parent_active
389 integer :: pos
390
391 if (cond_depth == 0) then
392 call printf(render(diagnostic_report(level_error, &
393 message='Syntax error', &
394 label=label_type('#elifndef without matching #if', 1, len_trim(ctx%content)), &
395 source=trim(ctx%path)), &
396 ctx%content, ctx%line))
397 return
398 end if
399
400 pos = index(lowercase(ctx%content), token) + len(token)
401 name = trim(adjustl(ctx%content(pos:)))
402 defined = is_defined(name, macros)
403 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
404 if (.not. cond_stack(cond_depth + 1)%has_met) then
405 cond_stack(cond_depth + 1)%active = (.not. defined) .and. parent_active
406 if (.not. defined) cond_stack(cond_depth + 1)%has_met = .true.
407 else
408 cond_stack(cond_depth + 1)%active = .false.
409 end if
410 end subroutine
411
412 !> Process #else - final fallback branch
413 !! Activates only if no previous #if/#elif branch was true.
414 !! @param[in] ctx Context (for error messages)
415 !!
416 !! @ingroup group_conditional
417 subroutine handle_else(ctx)
418 type(context), intent(in) :: ctx
419 !private
420 logical :: parent_active
421
422 if (cond_depth == 0) then
423 call printf(render(diagnostic_report(level_error, &
424 message='Syntax error', &
425 label=label_type('#else without matching #if', 1, len_trim(ctx%content)), &
426 source=trim(ctx%path)), &
427 ctx%content, ctx%line))
428 return
429 end if
430
431 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
432 if (.not. cond_stack(cond_depth + 1)%has_met) then
433 cond_stack(cond_depth + 1)%active = parent_active
434 cond_stack(cond_depth + 1)%has_met = .true.
435 else
436 cond_stack(cond_depth + 1)%active = .false.
437 end if
438 end subroutine
439
440 !> Process #endif - end of conditional block
441 !! Pops the top state from the stack. Reports error on unmatched #endif.
442 !! @param[in] ctx Context (for error messages)
443 !!
444 !! @ingroup group_conditional
445 subroutine handle_endif(ctx)
446 type(context), intent(in) :: ctx
447
448 if (cond_depth == 0) then
449 call printf(render(diagnostic_report(level_error, &
450 message='Syntax error', &
451 label=label_type('#endif without matching #if', 1, len_trim(ctx%content)), &
452 source=trim(ctx%path)), &
453 ctx%content, ctx%line))
454 return
455 end if
457 end subroutine
458
459end module
subroutine, public handle_ifndef(ctx, macros, token)
Process ifndef - test if a macro is NOT defined.
logical function, public is_active()
Determine whether the current source position is active.
subroutine, public handle_ifdef(ctx, macros, token)
Process ifdef - test if a macro is defined.
integer, public cond_depth
Current nesting depth of conditional directives (0 = outside any if).
subroutine, public handle_elif(ctx, macros, token)
Process elif - alternative branch after if/elif Only activates if no previous branch in the group was...
subroutine, public handle_elifndef(ctx, macros, token)
Process elifndef - test if a macro is not defined.
subroutine, public handle_else(ctx)
Process else - final fallback branch Activates only if no previous if/elif branch was true.
type(cond_state), dimension(max_cond_depth), public cond_stack
Global stack of conditional states (depth-limited).
subroutine, public handle_if(ctx, macros, token)
Process a if directive with constant expression evaluation Evaluates the expression after if using ev...
subroutine, public handle_elifdef(ctx, macros, token)
Process elifdef - test if a macro is defined.
subroutine, public handle_endif(ctx)
Process endif - end of conditional block Pops the top state from the stack. Reports error on unmatche...
integer, parameter, public max_cond_depth
Maximum nesting depth of conditional compilation directives.
Definition constants.f90:89
logical function, public is_defined(name, macros, idx)
Determine whether a macro is currently defined.
Definition macro.f90:825
pure character(len_trim(str)) function, public lowercase(str)
Convert string to lower case (respects contents of quotes).
Definition string.f90:852
Generic renderer for diagnostics and source excerpts.
Definition logging.f90:208
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
Locate the position of a substring.
Definition string.f90:395
Return the trimmed length of a string object.
Definition string.f90:201
Return the length of a string object.
Definition string.f90:164
Remove trailing blanks from a string object.
Definition string.f90:240
State associated with a single conditional nesting level.
Snapshot of a source location within the preprocessing stream.
Definition context.f90:114
Structured compiler diagnostic.
Definition logging.f90:337
Diagnostic label identifying a region of source text.
Definition logging.f90:304
Representation of a preprocessor macro.
Definition macro.f90:135