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!!
13!! The state is maintained in a global stack (`cond_stack`) with `cond_depth` tracking nesting.
14!! The function `is_active()` is used throughout the preprocessor to decide whether a line
15!! should be emitted or skipped.
16!!
17!! <h2 class="groupheader">Examples</h2>
18!!
19!! 1. Classic include guard pattern:
20!! @code{.f90}
21!! #ifndef MY_HEADER_H
22!! #define MY_HEADER_H
23!!
24!! ! ... header content ...
25!!
26!! #endif
27!! ...
28!! @endcode
29!!
30!! 2. Feature selection with #if and #elif:
31!! @code{.f90}
32!! #if DEBUG >= 2
33!! print *, 'Extra verbose debugging enabled'
34!! #elif DEBUG == 1
35!! print *, 'Standard debugging'
36!! #else
37!! ! Silent mode
38!! #endif
39!! !..
40!! @endcode
41!!
42!! 3. Cross-platform code selection:
43!! @code{.f90}
44!! #ifdef _OPENMP
45!! use omp_lib
46!! #else
47!! integer, parameter :: omp_get_thread_num = 0
48!! #endif
49!! ...
50!! @endcode
51!!
52!! 4. Complex expression in #if (requires `evaluate_expression` support):
53!! @code{.f90}
54!! #if defined(USE_MPI) && (MPI_VERSION >= 3)
55!! use mpi_f08
56!! #endif
57!! ...
58!! @endcode
59module fpx_conditional
60 use fpx_constants
61 use fpx_logging
62 use fpx_string
63 use fpx_macro, only: macro, is_defined
64 use fpx_operators, only: evaluate_expression
65 use fpx_context
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] ctx Context source line containing the directive
127 !! @param[in] macros Current macro table
128 !! @param[in] token Usually 'if'
129 !!
130 !! @b Remarks
131 !! @ingroup group_conditional
132 subroutine handle_if(ctx, macros, token)
133 type(context), intent(in) :: ctx
134 type(macro), intent(in) :: macros(:)
135 character(*), intent(in) :: token
136 !private
137 character(:), allocatable :: expr
138 logical :: result, parent_active
139 integer :: pos
140
141 if (cond_depth + 1 > max_cond_depth) then
142 call printf(render(diagnostic_report(level_error, &
143 message='Conditional nesting too deep', &
144 source=trim(ctx%path)), &
145 ctx%content, ctx%line))
146 return
147 end if
148
149 pos = index(lowercase(ctx%content), token) + len(token)
150 expr = trim(adjustl(ctx%content(pos:)))
151 result = evaluate_expression(expr, macros, ctx)
152 parent_active = is_active()
154 cond_stack(cond_depth + 1)%active = result .and. parent_active
155 cond_stack(cond_depth + 1)%has_met = result
156 end subroutine
157
158 !> Process #ifdef – test if a macro is defined
159 !! @param[in] ctx Context source line containing the directive
160 !! @param[in] macros Current macro table
161 !! @param[in] token Usually 'ifdef'
162 !!
163 !! @b Remarks
164 !! @ingroup group_conditional
165 subroutine handle_ifdef(ctx, macros, token)
166 type(context), intent(in) :: ctx
167 type(macro), intent(in) :: macros(:)
168 character(*), intent(in) :: token
169 !private
170 character(:), allocatable :: name
171 logical :: defined, parent_active
172 integer :: pos
173
174 if (cond_depth + 1 > max_cond_depth) then
175 call printf(render(diagnostic_report(level_error, &
176 message='Conditional nesting too deep', &
177 source=trim(ctx%path)), &
178 ctx%content, ctx%line))
179 return
180 end if
181
182 pos = index(lowercase(ctx%content), token) + len(token)
183 name = trim(adjustl(ctx%content(pos:)))
184 defined = is_defined(name, macros)
185 parent_active = is_active()
187 cond_stack(cond_depth + 1)%active = defined .and. parent_active
188 cond_stack(cond_depth + 1)%has_met = defined
189 end subroutine
190
191 !> Process #ifndef – test if a macro is NOT defined
192 !! @param[in] ctx Context source line containing the directive
193 !! @param[in] macros Current macro table
194 !! @param[in] token Usually 'ifndef'
195 !!
196 !! @b Remarks
197 !! @ingroup group_conditional
198 subroutine handle_ifndef(ctx, macros, token)
199 type(context), intent(in) :: ctx
200 type(macro), intent(in) :: macros(:)
201 character(*), intent(in) :: token
202 !private
203 character(:), allocatable :: name
204 logical :: defined, parent_active
205 integer :: pos
206
207 if (cond_depth + 1 > max_cond_depth) then
208 call printf(render(diagnostic_report(level_error, &
209 message='Conditional nesting too deep', &
210 source=trim(ctx%path)), &
211 ctx%content, ctx%line))
212 return
213 end if
214
215 pos = index(lowercase(ctx%content), token) + len(token)
216 name = trim(adjustl(ctx%content(pos:)))
217 defined = is_defined(name, macros)
218 parent_active = is_active()
220 cond_stack(cond_depth + 1)%active = (.not. defined) .and. parent_active
221 cond_stack(cond_depth + 1)%has_met = .not. defined
222 end subroutine
223
224 !> Process #elif – alternative branch after #if/#elif
225 !! Only activates if no previous branch in the group was taken.
226 !! @param[in] ctx Context source line containing the directive
227 !! @param[in] macros Current macro table
228 !! @param[in] token Usually 'elif'
229 !!
230 !! @b Remarks
231 !! @ingroup group_conditional
232 subroutine handle_elif(ctx, macros, token)
233 type(context), intent(in) :: ctx
234 type(macro), intent(in) :: macros(:)
235 character(*), intent(in) :: token
236 !private
237 character(:), allocatable :: expr
238 logical :: result, parent_active
239 integer :: pos
240
241 if (cond_depth == 0) then
242 call printf(render(diagnostic_report(level_error, &
243 message='Synthax error', &
244 label=label_type('#elif without matching #if', 1, len_trim(ctx%content)), &
245 source=trim(ctx%path)), &
246 ctx%content, ctx%line))
247 return
248 end if
249
250 pos = index(lowercase(ctx%content), token) + len(token)
251 expr = trim(adjustl(ctx%content(pos:)))
252 result = evaluate_expression(expr, macros, ctx)
253 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
254 if (.not. cond_stack(cond_depth + 1)%has_met) then
255 cond_stack(cond_depth + 1)%active = result .and. parent_active
256 if (result) cond_stack(cond_depth + 1)%has_met = .true.
257 else
258 cond_stack(cond_depth + 1)%active = .false.
259 end if
260 end subroutine
261
262 !> Process #elifdef – test if a macro is defined
263 !! @param[in] ctx Context source line containing the directive
264 !! @param[in] macros Current macro table
265 !! @param[in] token Usually 'elifdef'
266 !!
267 !! @b Remarks
268 !! @ingroup group_conditional
269 subroutine handle_elifdef(ctx, macros, token)
270 type(context), intent(in) :: ctx
271 type(macro), intent(in) :: macros(:)
272 character(*), intent(in) :: token
273 !private
274 character(:), allocatable :: name
275 logical :: defined, parent_active
276 integer :: pos
277
278 if (cond_depth == 0) then
279 call printf(render(diagnostic_report(level_error, &
280 message='Synthax error', &
281 label=label_type('#elifdef without matching #if', 1, len_trim(ctx%content)), &
282 source=trim(ctx%path)), &
283 ctx%content, ctx%line))
284 return
285 end if
286
287 pos = index(lowercase(ctx%content), token) + len(token)
288 name = trim(adjustl(ctx%content(pos:)))
289 defined = is_defined(name, macros)
290 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
291 if (.not. cond_stack(cond_depth + 1)%has_met) then
292 cond_stack(cond_depth + 1)%active = defined .and. parent_active
293 if (defined) cond_stack(cond_depth + 1)%has_met = .true.
294 else
295 cond_stack(cond_depth + 1)%active = .false.
296 end if
297 end subroutine
298
299 !> Process #elifndef – test if a macro is not defined
300 !! @param[in] ctx Context source line containing the directive
301 !! @param[in] macros Current macro table
302 !! @param[in] token Usually 'elifndef'
303 !!
304 !! @b Remarks
305 !! @ingroup group_conditional
306 subroutine handle_elifndef(ctx, macros, token)
307 type(context), intent(in) :: ctx
308 type(macro), intent(in) :: macros(:)
309 character(*), intent(in) :: token
310 !private
311 character(:), allocatable :: name
312 logical :: defined, parent_active
313 integer :: pos
314
315 if (cond_depth == 0) then
316 call printf(render(diagnostic_report(level_error, &
317 message='Synthax error', &
318 label=label_type('#elifndef without matching #if', 1, len_trim(ctx%content)), &
319 source=trim(ctx%path)), &
320 ctx%content, ctx%line))
321 return
322 end if
323
324 pos = index(lowercase(ctx%content), token) + len(token)
325 name = trim(adjustl(ctx%content(pos:)))
326 defined = is_defined(name, macros)
327 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
328 if (.not. cond_stack(cond_depth + 1)%has_met) then
329 cond_stack(cond_depth + 1)%active = (.not. defined) .and. parent_active
330 if (.not. defined) cond_stack(cond_depth + 1)%has_met = .true.
331 else
332 cond_stack(cond_depth + 1)%active = .false.
333 end if
334 end subroutine
335
336 !> Process #else – final fallback branch
337 !! Activates only if no previous #if/#elif branch was true.
338 !! @param[in] ctx Context (for error messages)
339 !!
340 !! @b Remarks
341 !! @ingroup group_conditional
342 subroutine handle_else(ctx)
343 type(context), intent(in) :: ctx
344 !private
345 logical :: parent_active
346
347 if (cond_depth == 0) then
348 call printf(render(diagnostic_report(level_error, &
349 message='Synthax error', &
350 label=label_type('#else without matching #if', 1, len_trim(ctx%content)), &
351 source=trim(ctx%path)), &
352 ctx%content, ctx%line))
353 return
354 end if
355
356 parent_active = cond_depth == 0 .or. cond_stack(cond_depth)%active
357 if (.not. cond_stack(cond_depth + 1)%has_met) then
358 cond_stack(cond_depth + 1)%active = parent_active
359 cond_stack(cond_depth + 1)%has_met = .true.
360 else
361 cond_stack(cond_depth + 1)%active = .false.
362 end if
363 end subroutine
364
365 !> Process #endif – end of conditional block
366 !! Pops the top state from the stack. Reports error on unmatched #endif.
367 !! @param[in] ctx Context (for error messages)
368 !!
369 !! @b Remarks
370 !! @ingroup group_conditional
371 subroutine handle_endif(ctx)
372 type(context), intent(in) :: ctx
373
374 if (cond_depth == 0) then
375 call printf(render(diagnostic_report(level_error, &
376 message='Synthax error', &
377 label=label_type('#endif without matching #if', 1, len_trim(ctx%content)), &
378 source=trim(ctx%path)), &
379 ctx%content, ctx%line))
380 return
381 end if
383 end subroutine
384
385end module
subroutine, public handle_ifndef(ctx, macros, token)
Process ifndef – 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_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 for conditional statements, set to 50 to prevent overly complex logic.
Definition constants.f90:19
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:625
pure character(len_trim(str)) function, public lowercase(str)
Convert string to lower case (respects contents of quotes).
Definition string.f90:640
Interface to render diagnostic messages and labels.
Definition logging.f90:185
Index operator.
Definition string.f90:178
Return the trimmed length of a string.
Definition string.f90:141
Return the length of a string.
Definition string.f90:133
Return the trimmed string.
Definition string.f90:149
State of a single conditional block.
Source location and content snapshot for precise diagnostics Instances of this type are created for e...
Definition context.f90:99
Definition of diagnostic message.
Definition logging.f90:269
Represents text as a sequence of ASCII code units. The derived type wraps an allocatable character ar...
Definition logging.f90:246
Derived type representing a single preprocessor macro Extends string with macro-specific fields: rep...
Definition macro.f90:94