Loading...
Searching...
No Matches
loop.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_for For
3!! Fortran Preprocessor (fpx) - compile-time loop expansion support
4!!
5!! This module implements the non-standard `#for` / `#endfor` directive pair
6!! used by fpx to generate repeated source code from a list of values.
7!!
8!! Features:
9!! - Simple iteration over explicit lists:
10!! `#for T in [integer, real, complex]`
11!! - Iteration over macro-expanded lists:
12!! `#define NUMERICS [integer, real, complex]`
13!! `#for T in NUMERICS`
14!! - Arbitrary nesting of `#for` blocks
15!! - Integration with the normal macro expansion engine
16!! - Deferred body collection until matching `#endfor`
17!! - Automatic cleanup of loop-local variables
18!!
19!! During parsing, loop bodies are stored internally and emitted only when
20!! the matching `#endfor` is encountered. Each iteration temporarily defines
21!! the loop variable as a macro whose value is substituted into the collected
22!! body before output.
23!!
24!! @section for_examples Examples
25!!
26!! 1. Basic iteration:
27!! @code{.f90}
28!! #for T in [integer, real, complex]
29!! type(T) :: value
30!! #endfor
31!!
32!! ! Expands to:
33!! type(integer) :: value
34!! type(real) :: value
35!! type(complex) :: value
36!! ...
37!! @endcode
38!!
39!! 2. Using a macro list:
40!! @code{.f90}
41!! #define NUMERICS [integer, real, complex]
42!!
43!! #for T in NUMERICS
44!! type(T) :: value
45!! #endfor
46!! ...
47!! @endcode
48!!
49!! 3. Nested loops:
50!! @code{.f90}
51!! #define CONCAT(a,b) a##b
52!! #for T in [integer, real]
53!! #for R in [32,64]
54!! type(CONCAT(T,R)) :: value
55!! #endfor
56!! #endfor
57!! ...
58!! @endcode
59!!
60!! 4. Generic procedure generation:
61!! @code{.f90}
62!! #define CONCAT(a,b) a##b
63!! #define NUMERICS [integer, real, complex]
64!!
65!! #for T in NUMERICS
66!! module procedure CONCAT(add_,T)
67!! #endfor
68!! ...
69!! @endcode
70!!
71!! 5. Cartesian product generation:
72!! @code{.f90}
73!! #for T in [real, complex]
74!! #for K in [32, 64]
75!! type(T(K)) :: value
76!! #endfor
77!! #endfor
78!!
79!! ! Generates:
80!! ! type(real(32)) :: value
81!! ! type(real(64)) :: value
82!! ! type(complex(32)) :: value
83!! ! type(complex(64)) :: value
84!! ...
85!! @endcode
86!!
87!! Loop variables behave exactly like temporary object-like macros and
88!! therefore participate in all normal macro expansion rules, including
89!! nested expansion and token pasting.
90!!
91!! @note
92!! When nested loops are active, generated lines are appended to the
93!! enclosing loop body rather than written immediately. This guarantees
94!! inside-out expansion semantics.
95module fpx_for
96 use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer
97 use fpx_constants
98 use fpx_logging
99 use fpx_macro
100 use fpx_string
101 use fpx_global
102 use fpx_context
103
104 implicit none; private
105
106 public :: handle_for, &
110
111 !> Internal storage for deferred loop bodies.
112 !!
113 !! Source lines belonging to a `#for` block are collected until the
114 !! corresponding `#endfor` directive is encountered.
115 !!
116 !! @ingroup group_for
117 type :: body
118 integer :: nlines = 0
119 type(string), allocatable :: lines(:)
120 end type
121
122 !! @cond
123 integer :: depth = 0
124 integer, parameter :: BODY_BUFFER = 50
125 type(body) :: bodies(MAX_FOR_DEPTH)
126 type(macro), allocatable :: fmacros(:)
127 !! @endcond
128
129contains
130
131 !> Process a `#for` directive and initialize a new loop context.
132 !!
133 !! The directive header is parsed immediately, but the loop body is
134 !! not expanded at this stage. Instead, subsequent source lines are
135 !! collected until the matching `#endfor` directive is encountered.
136 !!
137 !! The loop variable behaves as a temporary object-like macro whose
138 !! value changes for each iteration.
139 !!
140 !! Supported syntax:
141 !!
142 !! @code
143 !! #for identifier in [value1, value2, ...]
144 !! #for identifier in MACRO_NAME
145 !! @endcode
146 !!
147 !! where `MACRO_NAME` expands to a bracketed list.
148 !!
149 !! @note
150 !! `#for` and `#endfor` are fpx extensions and are not part of the
151 !! ISO C preprocessor specification.
152 !!
153 !! @param[in] ctx
154 !! Current parsing context
155 !! @param[inout] macros
156 !! Active macro table
157 !! @param[in] token
158 !! Directive keyword (`for`)
159 !!
160 !! @ingroup group_for
161 subroutine handle_for(ctx, macros, token)
162 type(context), intent(in) :: ctx
163 type(macro), allocatable, intent(inout) :: macros(:)
164 character(*), intent(in) :: token
165 !private
166 character(:), allocatable :: val, name, temp
167 integer :: pos, paren_start, paren_end, i, npar, imacro
168 logical :: stitch
169
170 depth = depth + 1
171 if (depth > max_for_depth) then
172 call printf(render(diagnostic_report(level_error, &
173 message='Loop nesting too deep', &
174 source=trim(ctx%path)), &
175 ctx%content, ctx%line))
176 return
177 end if
178
179 pos = index(lowercase(ctx%content), token) + len(token)
180 temp = trim(adjustl(ctx%content(pos + 1:)))
181
182 if (index(temp, ' in ') == 0) then
183 call printf(render(diagnostic_report(level_error, &
184 message='Syntax error', &
185 label=label_type('Missing " in " keyword', pos + 1, 4), &
186 source=ctx%path), &
187 trim(ctx%content), ctx%line))
188 return
189 else
190 name = trim(adjustl(temp(:index(temp, ' in '))))
191 if (global%undef .contains. name) return
192
193 if (name == 'defined') then
194 call printf(render(diagnostic_report(level_error, &
195 message='Reserved macro name', &
196 label=label_type('"defined" cannot be used as a macro name', paren_start + 1, len(name)), &
197 source=ctx%path), &
198 trim(ctx%content), ctx%line))
199 end if
200 end if
201
202 pos = index(temp, ' in ') + len(' in ')
203 temp = expand_macros(temp(pos:), macros, stitch, global%implicit_continuation, global%support_dollar_insert, ctx)
204
205 paren_start = index(temp, '[')
206 if (paren_start == 0) then
207 call printf(render(diagnostic_report(level_error, &
208 message='Syntax error', &
209 label=label_type('Missing opening square bracket in #for expression', 1, 1), &
210 source=ctx%path), &
211 trim(ctx%content), ctx%line))
212 return
213 end if
214
215 paren_end = index(temp, ']', back=.true.)
216 if (paren_end == 0) then
217 call printf(render(diagnostic_report(level_error, &
218 message='Syntax error', &
219 label=label_type('Missing closing square bracket in #for expression', len_trim(ctx%content) + 1, 1), &
220 source=ctx%path), &
221 trim(ctx%content), ctx%line))
222 return
223 end if
224 temp = temp(paren_start + 1:paren_end - 1)
225 npar = 0
226 pos = 1
227 do while (pos <= len_trim(temp))
228 if (temp(pos:pos) == ',') then
229 npar = npar + 1
230 end if
231 pos = pos + 1
232 end do
233 if (len_trim(temp) > 0) npar = npar + 1
234
235 if (.not. allocated(fmacros)) allocate(fmacros(0))
236 if (.not. is_defined(name, fmacros, imacro)) then
237 call add(fmacros, name, '')
238 imacro = size_of(fmacros)
239 else
240 fmacros(imacro) = macro(name, '')
241 end if
242
243 fmacros(imacro)%active = .false.
244 fmacros(imacro)%is_variadic = .false.
245 if (allocated(fmacros(imacro)%params)) deallocate(fmacros(imacro)%params)
246 allocate(fmacros(imacro)%params(npar))
247 pos = 1
248 i = 1
249 do while (pos <= len_trim(temp) .and. i <= npar)
250 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
251 pos = pos + 1
252 end do
253 if (pos > len_trim(temp)) exit
254 paren_start = pos
255 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',' .and. temp(pos:pos) /= ' ')
256 pos = pos + 1
257 if (pos > len_trim(temp)) exit
258 end do
259 fmacros(imacro)%params(i) = temp(paren_start:pos - 1)
260 i = i + 1
261 if (pos <= len_trim(temp)) then
262 if (temp(pos:pos) == ',') pos = pos + 1
263 end if
264 end do
265 end subroutine
266
267 !> Finalize a loop and emit all expanded iterations.
268 !!
269 !! The collected loop body is expanded once for every value contained in
270 !! the loop variable parameter list. Nested loops are handled recursively
271 !! by forwarding generated lines to the enclosing loop body when present.
272 !!
273 !! For each iteration value:
274 !! - the loop variable macro is activated,
275 !! - the stored body is macro-expanded,
276 !! - generated lines are reprocessed by the normal preprocessing engine,
277 !! - output is either emitted directly or forwarded to an enclosing loop.
278 !!
279 !! When the outermost loop terminates, all temporary loop state is
280 !! released automatically.
281 !!
282 !! @param[in] ctx Current parsing context
283 !! @param[in] ounit Output unit
284 !! @param[in] p preprocessor function pointer
285 !! @param[inout] macros Active macro table
286 !! @param[in] token Directive keyword (`endfor`)
287 !!
288 !! @ingroup group_for
289 subroutine handle_endfor(ctx, ounit, p, macros, token)
290 type(context), intent(inout) :: ctx
291 integer, intent(in) :: ounit
292 type(c_funptr), intent(in) :: p
293 type(macro), intent(in) :: macros(:)
294 character(*), intent(in) :: token
295 !private
296 integer :: i, j
297 character(:), allocatable :: rst, tmp
298 logical :: stitch
299 type(string), allocatable :: params(:)
300 type(macro), allocatable :: ms(:)
301 procedure(preprocess_line), pointer :: preprocess => null()
302
303 call c_f_procpointer(p, preprocess)
304
305 tmp = ''
306 depth = depth - 1
307
308 if (depth + 1 <= size_of(fmacros)) then
309 if (allocated(fmacros(depth + 1)%params)) params = fmacros(depth + 1)%params
310 if (allocated(fmacros(depth + 1)%params)) deallocate(fmacros(depth + 1)%params)
311
312 do i = 1, size_of(params)
313 fmacros(depth + 1)%value = params(i)
314 fmacros(depth + 1)%active = .true.
315 ms = [fmacros(depth + 1), macros]
316 !do j = 1, bodies(depth + 1)%nlines
317 do j = 1, bodies(depth + 1)%nlines !size_of(bodies(depth + 1)%lines)
318 if (head(bodies(depth + 1)%lines(j)%chars) == '#') then
319 if (len(bodies(depth + 1)%lines(j)%chars) == 1) then
320 return
321 else
322 rst = adjustl(expand_macros(bodies(depth + 1)%lines(j)%chars, ms, stitch, &
323 global%implicit_continuation, global%support_dollar_insert, ctx))
324 tmp = preprocess(rst, ounit, ctx%path, ctx%line, ms, stitch)
325 end if
326 else
327 rst = adjustl(expand_macros(bodies(depth + 1)%lines(j)%chars, ms, stitch, global%implicit_continuation, &
328 global%support_dollar_insert, ctx))
329 tmp = preprocess(rst, ounit, ctx%path, ctx%line, ms, stitch)
330 end if
331
332 if (depth > 0) then
333 if (len_trim(tmp) > 0) then
334 call addline(bodies(depth), string(tmp))
335 end if
336 else
337 do
338 if (tmp == rst) exit
339 tmp = preprocess(rst, ounit, ctx%path, ctx%line, ms, stitch)
340 rst = tmp
341 end do
342 write(ounit, '(A)') rst
343 end if
344 end do
345 if (depth > 0) then
346 call addline(bodies(depth), string(''))
347 else
348 write(ounit, '(A)') ''
349 end if
350 end do
351 bodies(depth + 1)%nlines = 0
352 if (allocated(bodies(depth + 1)%lines)) deallocate(bodies(depth + 1)%lines)
353 end if
354
355 if (allocated(params)) deallocate(params)
356 if (allocated(ms)) deallocate(ms)
357 nullify(preprocess)
358
359 if (depth < 0) then
360 call printf(render(diagnostic_report(level_warning, &
361 message='Unbalanced #for expression. Missing #for or #endfor directive.', &
362 source=ctx%path), &
363 trim(ctx%content)))
364 return
365 end if
366
367 if (depth == 0) then
368 if (allocated(fmacros)) deallocate(fmacros)
369 do i = 1, max_for_depth
370 if (allocated(bodies(i)%lines)) deallocate(bodies(i)%lines)
371 end do
372 end if
373 end subroutine
374
375 !> Append a source line to the innermost active loop body.
376 !!
377 !! Lines are stored verbatim without macro expansion. Expansion is
378 !! deferred until the corresponding `#endfor` directive is processed.
379 !!
380 !! @param[in] line Source line to store
381 !!
382 !! @ingroup group_for
383 subroutine add_to_loop(line)
384 character(*), intent(in) :: line
385
386 call addline(bodies(depth), string(line))
387 end subroutine
388
389 !> Query whether parsing is currently inside a `#for` block.
390 !! This routine is typically used by the main preprocessing engine to
391 !! determine whether incoming source lines should be emitted directly
392 !! or collected for later expansion.
393 !! @return `.true.` when one or more loop contexts are active,
394 !! `.false.` otherwise.
395 !!
396 !! @ingroup group_for
397 logical function is_in_forloop() result(res)
398 res = depth > 0
399 end function
400
401 subroutine addline(b, line)
402 type(body), intent(inout) :: b
403 type(string), intent(in) :: line
404 !private
405 type(string), allocatable :: tmp(:)
406 integer :: n
407
408 if (.not. allocated(b%lines)) then
409 allocate(b%lines(0))
410 b%nlines = 0
411 end if
412 b%nlines = b%nlines + 1
413 n = size(b%lines)
414 if (b%nlines <= n) then
415 b%lines(b%nlines) = line
416 else
417 allocate(tmp(n + body_buffer))
418 tmp(1:n) = b%lines(1:n)
419 tmp(n + 1) = line
420 call move_alloc(from=tmp, to=b%lines)
421 end if
422 end subroutine
423end module
subroutine, public handle_for(ctx, macros, token)
Process a #for directive and initialize a new loop context.
Definition loop.f90:162
subroutine, public add_to_loop(line)
Append a source line to the innermost active loop body.
Definition loop.f90:384
subroutine, public handle_endfor(ctx, ounit, p, macros, token)
Finalize a loop and emit all expanded iterations.
Definition loop.f90:290
logical function, public is_in_forloop()
Query whether parsing is currently inside a #for block. This routine is typically used by the main pr...
Definition loop.f90:398
type(global_settings), public global
Global preprocessor configuration instance.
Definition global.f90:192
character(:) function, allocatable, public expand_macros(line, macros, stitch, implicit_conti, dollar_insert, ctx)
Recursively expand user-defined macros.
Definition macro.f90:453
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
character function, public head(str)
Returns the first character of the trimmed string.
Definition string.f90:732
Generic renderer for diagnostics and source excerpts.
Definition logging.f90:208
Append macros to a macro table.
Definition macro.f90:175
Abstract interface for line preprocessing callbacks.
Definition macro.f90:258
Return the number of stored macro definitions.
Definition macro.f90:223
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
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
Represents text as a sequence of ASCII code units. The derived type wraps an allocatable character ar...
Definition string.f90:112