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!! Loop variables behave like temporary macros and participate in normal
72!! macro expansion rules.
73module fpx_for
74 use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer
75 use fpx_constants
76 use fpx_logging
77 use fpx_macro
78 use fpx_string
79 use fpx_global
80 use fpx_context
81
82 implicit none; private
83
84 public :: handle_for, &
88
89 type :: body
90 integer :: nlines = 0
91 type(string), allocatable :: lines(:)
92 end type
93
94 integer :: depth = 0
95 integer, parameter :: BODY_BUFFER = 50
96 type(body) :: bodies(MAX_FOR_DEPTH)
97 type(macro), allocatable :: fmacros(:)
98
99contains
100
101 !> Process a `#for` directive and initialize a new loop context.
102 !!
103 !! Parses directives of the form:
104 !! @code
105 !! #for variable in [item1, item2, ...]
106 !! ...
107 !! @endcode
108 !!
109 !! or
110 !!
111 !! @code
112 !! #for variable in MACRO_NAME
113 !! ...
114 !! @endcode
115 !!
116 !! where `MACRO_NAME` expands to a bracketed list.
117 !!
118 !! A temporary macro representing the loop variable is created and the
119 !! iteration values are stored internally until the matching `#endfor`
120 !! is reached.
121 !!
122 !! @param[in] ctx Current parsing context
123 !! @param[inout] macros Active macro table
124 !! @param[in] token Directive keyword (`for`)
125 !!
126 !! @b Remarks
127 !! @ingroup group_for
128 subroutine handle_for(ctx, macros, token)
129 type(context), intent(in) :: ctx
130 type(macro), allocatable, intent(inout) :: macros(:)
131 character(*), intent(in) :: token
132 !private
133 character(:), allocatable :: val, name, temp
134 integer :: pos, paren_start, paren_end, i, npar, imacro
135 logical :: stitch
136
137 depth = depth + 1
138 if (depth > max_for_depth) then
139 call printf(render(diagnostic_report(level_error, &
140 message='Loop nesting too deep', &
141 source=trim(ctx%path)), &
142 ctx%content, ctx%line))
143 return
144 end if
145
146 pos = index(lowercase(ctx%content), token) + len(token)
147 temp = trim(adjustl(ctx%content(pos + 1:)))
148
149 if (index(temp, ' in ') == 0) then
150 call printf(render(diagnostic_report(level_error, &
151 message='Syntax error', &
152 label=label_type('Missing " in " keyword', pos + 1, 4), &
153 source=ctx%path), &
154 trim(ctx%content), ctx%line))
155 return
156 else
157 name = trim(adjustl(temp(:index(temp, ' in '))))
158 if (global%undef .contains. name) return
159
160 if (name == 'defined') then
161 call printf(render(diagnostic_report(level_error, &
162 message='Reserved macro name', &
163 label=label_type('"defined" cannot be used as a macro name', paren_start + 1, len(name)), &
164 source=ctx%path), &
165 trim(ctx%content), ctx%line))
166 end if
167 end if
168
169 pos = index(temp, ' in ') + len(' in ')
170 temp = expand_macros(temp(pos:), macros, stitch, global%implicit_continuation, global%support_dollar_insert, ctx)
171
172 paren_start = index(temp, '[')
173 if (paren_start == 0) then
174 call printf(render(diagnostic_report(level_error, &
175 message='Syntax error', &
176 label=label_type('Missing opening square bracket in #for expression', 1, 1), &
177 source=ctx%path), &
178 trim(ctx%content), ctx%line))
179 return
180 end if
181
182 paren_end = index(temp, ']', back=.true.)
183 if (paren_end == 0) then
184 call printf(render(diagnostic_report(level_error, &
185 message='Syntax error', &
186 label=label_type('Missing closing square bracket in #for expression', len_trim(ctx%content) + 1, 1), &
187 source=ctx%path), &
188 trim(ctx%content), ctx%line))
189 return
190 end if
191 temp = temp(paren_start + 1:paren_end - 1)
192 npar = 0
193 pos = 1
194 do while (pos <= len_trim(temp))
195 if (temp(pos:pos) == ',') then
196 npar = npar + 1
197 end if
198 pos = pos + 1
199 end do
200 if (len_trim(temp) > 0) npar = npar + 1
201
202 if (.not. allocated(fmacros)) allocate(fmacros(0))
203 if (.not. is_defined(name, fmacros, imacro)) then
204 call add(fmacros, name, '')
205 imacro = size_of(fmacros)
206 else
207 fmacros(imacro) = macro(name, '')
208 end if
209
210 fmacros(imacro)%active = .false.
211 fmacros(imacro)%is_variadic = .false.
212 if (allocated(fmacros(imacro)%params)) deallocate(fmacros(imacro)%params)
213 allocate(fmacros(imacro)%params(npar))
214 pos = 1
215 i = 1
216 do while (pos <= len_trim(temp) .and. i <= npar)
217 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
218 pos = pos + 1
219 end do
220 if (pos > len_trim(temp)) exit
221 paren_start = pos
222 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',' .and. temp(pos:pos) /= ' ')
223 pos = pos + 1
224 if (pos > len_trim(temp)) exit
225 end do
226 fmacros(imacro)%params(i) = temp(paren_start:pos - 1)
227 i = i + 1
228 if (pos <= len_trim(temp)) then
229 if (temp(pos:pos) == ',') pos = pos + 1
230 end if
231 end do
232 end subroutine
233
234 !> Finalize a loop and emit all expanded iterations.
235 !!
236 !! The collected loop body is expanded once for every value contained in
237 !! the loop variable parameter list. Nested loops are handled recursively
238 !! by forwarding generated lines to the enclosing loop body when present.
239 !!
240 !! When the outermost loop terminates, all temporary loop state is
241 !! released automatically.
242 !!
243 !! @param[in] ctx Current parsing context
244 !! @param[in] ounit Output unit
245 !! @param[in] p preprocessor function pointer
246 !! @param[inout] macros Active macro table
247 !! @param[in] token Directive keyword (`endfor`)
248 !!
249 !! @b Remarks
250 !! @ingroup group_for
251 subroutine handle_endfor(ctx, ounit, p, macros, token)
252 type(context), intent(inout) :: ctx
253 integer, intent(in) :: ounit
254 type(c_funptr), intent(in) :: p
255 type(macro), intent(in) :: macros(:)
256 character(*), intent(in) :: token
257 !private
258 integer :: i, j
259 character(:), allocatable :: rst, tmp
260 logical :: stitch
261 type(string), allocatable :: params(:)
262 type(macro), allocatable :: ms(:)
263 procedure(preprocess_line), pointer :: preprocess => null()
264
265 call c_f_procpointer(p, preprocess)
266
267 tmp = ''
268 depth = depth - 1
269
270 if (depth + 1 <= size_of(fmacros)) then
271 if (allocated(fmacros(depth + 1)%params)) params = fmacros(depth + 1)%params
272 if (allocated(fmacros(depth + 1)%params)) deallocate(fmacros(depth + 1)%params)
273
274 do i = 1, size_of(params)
275 fmacros(depth + 1)%value = params(i)
276 fmacros(depth + 1)%active = .true.
277 ms = [fmacros(depth + 1), macros]
278 !do j = 1, bodies(depth + 1)%nlines
279 do j = 1, bodies(depth + 1)%nlines !size_of(bodies(depth + 1)%lines)
280 if (head(bodies(depth + 1)%lines(j)%chars) == '#') then
281 if (len(bodies(depth + 1)%lines(j)%chars) == 1) then
282 return
283 else
284 rst = adjustl(expand_macros(bodies(depth + 1)%lines(j)%chars, ms, stitch, &
285 global%implicit_continuation, global%support_dollar_insert, ctx))
286 tmp = preprocess(rst, ounit, ctx%path, ctx%line, ms, stitch)
287 end if
288 else
289 rst = adjustl(expand_macros(bodies(depth + 1)%lines(j)%chars, ms, stitch, global%implicit_continuation, &
290 global%support_dollar_insert, ctx))
291 tmp = preprocess(rst, ounit, ctx%path, ctx%line, ms, stitch)
292 end if
293
294 if (depth > 0) then
295 if (len_trim(tmp) > 0) then
296 call addline(bodies(depth), string(tmp))
297 end if
298 else
299 do
300 if (tmp == rst) exit
301 tmp = preprocess(rst, ounit, ctx%path, ctx%line, ms, stitch)
302 rst = tmp
303 end do
304 write(ounit, '(A)') rst
305 end if
306 end do
307 if (depth > 0) then
308 call addline(bodies(depth), string(''))
309 else
310 write(ounit, '(A)') ''
311 end if
312 end do
313 bodies(depth + 1)%nlines = 0
314 if (allocated(bodies(depth + 1)%lines)) deallocate(bodies(depth + 1)%lines)
315 end if
316
317 if (allocated(params)) deallocate(params)
318 if (allocated(ms)) deallocate(ms)
319 nullify(preprocess)
320
321 if (depth < 0) then
322 call printf(render(diagnostic_report(level_warning, &
323 message='Unbalanced #for expression. Missing #for or #endfor directive.', &
324 source=ctx%path), &
325 trim(ctx%content)))
326 return
327 end if
328
329 if (depth == 0) then
330 if (allocated(fmacros)) deallocate(fmacros)
331 do i = 1, max_for_depth
332 if (allocated(bodies(i)%lines)) deallocate(bodies(i)%lines)
333 end do
334 end if
335 end subroutine
336
337 !> Append a source line to the currently active loop body.
338 !!
339 !! Lines are stored verbatim and expanded only when the matching
340 !! `#endfor` directive is encountered.
341 !!
342 !! @param[in] line Source line to store
343 !!
344 !! @b Remarks
345 !! @ingroup group_for
346 subroutine add_to_loop(line)
347 character(*), intent(in) :: line
348
349 call addline(bodies(depth), string(line))
350 end subroutine
351
352 !> Query whether parsing is currently inside a `#for` block.
353 !!
354 !! @return `.true.` when one or more loop contexts are active,
355 !! `.false.` otherwise.
356 !!
357 !! @b Remarks
358 !! @ingroup group_for
359 logical function is_in_forloop() result(res)
360 res = depth > 0
361 end function
362
363 subroutine addline(b, line)
364 type(body), intent(inout) :: b
365 type(string), intent(in) :: line
366 !private
367 type(string), allocatable :: tmp(:)
368 integer :: n
369
370 if (.not. allocated(b%lines)) then
371 allocate(b%lines(0))
372 b%nlines = 0
373 end if
374 b%nlines = b%nlines + 1
375 n = size(b%lines)
376 if (b%nlines <= n) then
377 b%lines(b%nlines) = line
378 else
379 allocate(tmp(n + body_buffer))
380 tmp(1:n) = b%lines(1:n)
381 tmp(n + 1) = line
382 call move_alloc(from=tmp, to=b%lines)
383 end if
384 end subroutine
385end module
subroutine, public handle_for(ctx, macros, token)
Process a #for directive and initialize a new loop context.
Definition loop.f90:129
subroutine, public add_to_loop(line)
Append a source line to the currently active loop body.
Definition loop.f90:347
subroutine, public handle_endfor(ctx, ounit, p, macros, token)
Finalize a loop and emit all expanded iterations.
Definition loop.f90:252
logical function, public is_in_forloop()
Query whether parsing is currently inside a #for block.
Definition loop.f90:360
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
Definition global.f90:96
character(:) function, allocatable, public expand_macros(line, macros, stitch, implicit_conti, dollar_insert, ctx)
Core recursive macro expander (handles function-like, variadic, #, ##).
Definition macro.f90:344
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:710
pure character(len_trim(str)) function, public lowercase(str)
Convert string to lower case (respects contents of quotes).
Definition string.f90:642
character function, public head(str)
Returns the first non-blank character of a string.
Definition string.f90:518
Interface to render diagnostic messages and labels.
Definition logging.f90:185
Add one or more macros to a dynamic table.
Definition macro.f90:120
Return current number of stored macros.
Definition macro.f90:163
Index operator.
Definition string.f90:180
Return the trimmed length of a string.
Definition string.f90:143
Return the length of a string.
Definition string.f90:135
Return the trimmed string.
Definition string.f90:151
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:98
Represents text as a sequence of ASCII code units. The derived type wraps an allocatable character ar...
Definition string.f90:112