Loading...
Searching...
No Matches
define.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_define Define
3!! Macro definition and removal directives for the fpx preprocessor.
4!!
5!! This module implements the `#define` and `#undef` directives used to create,
6!! update, and remove preprocessor macros during source preprocessing.
7!!
8!! Supported macro forms include:
9!!
10!! - Object-like macros:
11!! `#define NAME value`
12!!
13!! - Function-like macros:
14!! `#define NAME(arg1,arg2,...) replacement`
15!!
16!! - Variadic macros:
17!! `#define LOG(level, ...) ...`
18!!
19!! - Empty definitions:
20!! `#define FEATURE`
21!!
22!! - Macro redefinition:
23!! Existing definitions are replaced by the most recent one.
24!!
25!! The parser correctly identifies matching parentheses in function-like macro
26!! signatures, allowing nested parentheses inside parameter lists. Whitespace
27!! surrounding parameters is ignored, and variadic arguments are detected
28!! automatically through the `...` notation.
29!!
30!! The module also implements `#undef`, allowing previously defined symbols
31!! to be removed from the active macro table. Symbols listed in
32!! `global%undef` are protected from redefinition and silently ignored.
33!!
34!! All syntax errors are reported through the diagnostic framework, providing
35!! source locations and explanatory messages.
36!!
37!! @note
38!! Macro definitions are local to the current preprocessing context unless
39!! explicitly propagated by the caller.
40!!
41!! @section define_examples Examples
42!!
43!! 1. Object-like macros:
44!! @code{.f90}
45!! #define PI 3.141592653589793
46!! #define DEBUG 1
47!! #define VERSION "1.2.0"
48!! ...
49!! @endcode
50!!
51!! 2. Empty definitions:
52!! @code{.f90}
53!! #define USE_MPI
54!!
55!! #ifdef USE_MPI
56!! !...
57!! #endif
58!! ...
59!! @endcode
60!!
61!! 3. Function-like macros:
62!! @code{.f90}
63!! #define SQR(x) ((x)*(x))
64!! #define MIN(a,b) ((a)<(b)?(a):(b))
65!! #define CONCAT(a,b) a ## b
66!! ...
67!! @endcode
68!!
69!! 4. Variadic macros:
70!! @code{.f90}
71!! #define LOG(level, ...) &
72!! print *, "[", level, "]", __VA_ARGS__
73!! @endcode
74!!
75!! 5. Removing a definition:
76!! @code{.f90}
77!! #undef DEBUG
78!!
79!! #ifdef DEBUG
80!! ! This block is skipped
81!! #endif
82!! ...
83!! @endcode
84!!
85!! 6. Redefinition:
86!! @code{.f90}
87!! #define SIZE 128
88!! #define SIZE 256
89!!
90!! integer :: buf(SIZE) ! expands to 256
91!! ...
92!! @endcode
93!!
94!! 7. Reserved names:
95!! @code{.f90}
96!! #define defined(x) 1
97!! ...
98!! @endcode
99!!
100!! produces a diagnostic because `defined` is reserved for conditional
101!! expressions.
102!!
103!! @see
104!! <a href="./group__group__macro.html">macro</a> @n
105!! <a href="./group__group__global.html">global</a> @n
106!! <a href="./group__group__context.html">context</a>
107module fpx_define
108 use fpx_constants
109 use fpx_logging
110 use fpx_macro
111 use fpx_string
112 use fpx_global
113 use fpx_context
114
115 implicit none; private
116
117 public :: handle_define, &
119
120contains
121
122 !> Process a `#define` directive.
123 !!
124 !! Parses the directive contained in the supplied context and updates the
125 !! active macro table accordingly.
126 !!
127 !! The routine automatically distinguishes between:
128 !!
129 !! - object-like macros,
130 !! - function-like macros,
131 !! - variadic macros using `...`,
132 !! - empty definitions.
133 !!
134 !! Function-like signatures are parsed using matching-parenthesis tracking,
135 !! ensuring that the closing parenthesis corresponding to the opening `(`
136 !! is located correctly even in the presence of nested parentheses.
137 !!
138 !! Existing definitions are overwritten. Symbols listed in
139 !! `global%undef` are ignored. Attempts to define the reserved identifier
140 !! `defined` generate an error diagnostic.
141 !!
142 !! @param[in] ctx
143 !! Source context containing the complete `#define` directive.
144 !! @param[inout] macros
145 !! Active macro table updated in place.
146 !! @param[in] token
147 !! Directive keyword, typically `"define"`.
148 !!
149 !! @ingroup group_define
150 subroutine handle_define(ctx, macros, token)
151 type(context), intent(in) :: ctx
152 type(macro), allocatable, intent(inout) :: macros(:)
153 character(*), intent(in) :: token
154 !private
155 character(:), allocatable :: val, name, temp
156 integer :: pos, paren_start, paren_end, i, npar, imacro, level
157
158 pos = index(lowercase(ctx%content), token) + len(token)
159 temp = trim(adjustl(ctx%content(pos + 1:)))
160
161 paren_start = index(temp, '(')
162 pos = index(temp, ' ')
163 if (pos > 0 .and. pos < paren_start) paren_start = 0
164
165 if (paren_start > 0) then
166 name = trim(temp(:paren_start - 1))
167
168 if (global%undef .contains. name) return
169 paren_end = 0; level = 0
170 do i = paren_start, len_trim(temp)
171 select case (temp(i:i))
172 case ('(')
173 level = level + 1
174 case (')')
175 level = level - 1
176 if (level == 0) then
177 paren_end = i
178 exit
179 end if
180 end select
181 end do
182 if (paren_end == 0) then
183 call printf(render(diagnostic_report(level_error, &
184 message='Syntax error', &
185 label=label_type('Missing closing parenthesis in macro definition', len_trim(ctx%content) + 1, 1), &
186 source=ctx%path), &
187 trim(ctx%content), ctx%line))
188 return
189 end if
190 val = trim(adjustl(temp(paren_end + 1:)))
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(macros)) allocate(macros(0))
203
204 if (name == 'defined') then
205 call printf(render(diagnostic_report(level_error, &
206 message='Reserved macro name', &
207 label=label_type('"defined" cannot be used as a macro name', paren_start + 1, len(name)), &
208 source=ctx%path), &
209 trim(ctx%content), ctx%line))
210 end if
211
212 if (.not. is_defined(name, macros, imacro)) then
213 call add(macros, name, val)
214 imacro = size_of(macros)
215 else
216 macros(imacro) = macro(name, val)
217 end if
218
219 if (index(temp, '...') > 0) then
220 macros(imacro)%is_variadic = .true.
221 npar = npar - 1
222 if (allocated(macros(imacro)%params)) deallocate(macros(imacro)%params)
223 allocate(macros(imacro)%params(npar))
224 pos = 1
225 i = 1
226 do while (pos <= len_trim(temp) .and. i <= npar)
227 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
228 pos = pos + 1
229 end do
230 if (pos > len_trim(temp)) exit
231 paren_start = pos
232 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',')
233 pos = pos + 1
234 end do
235 macros(imacro)%params(i) = temp(paren_start:pos - 1)
236 i = i + 1
237 pos = pos + 1
238 end do
239 else
240 macros(imacro)%is_variadic = .false.
241 if (allocated(macros(imacro)%params)) deallocate(macros(imacro)%params)
242 allocate(macros(imacro)%params(npar))
243 pos = 1
244 i = 1
245 do while (pos <= len_trim(temp) .and. i <= npar)
246 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
247 pos = pos + 1
248 end do
249 if (pos > len_trim(temp)) exit
250 paren_start = pos
251 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',' .and. temp(pos:pos) /= ' ')
252 pos = pos + 1
253 if (pos > len_trim(temp)) exit
254 end do
255 macros(imacro)%params(i) = temp(paren_start:pos - 1)
256 i = i + 1
257 if (pos <= len_trim(temp)) then
258 if (temp(pos:pos) == ',') pos = pos + 1
259 end if
260 end do
261 end if
262 else
263 pos = index(temp, ' ')
264 if (pos > 0) then
265 name = trim(temp(:pos - 1))
266 val = trim(adjustl(temp(pos + 1:)))
267 else
268 name = trim(temp)
269 val = ''
270 end if
271
272 if (global%undef .contains. name) return
273 if (.not. allocated(macros)) allocate(macros(0))
274 if (.not. is_defined(name, macros, imacro)) then
275 call add(macros, name, val)
276 imacro = size_of(macros)
277 else
278 macros(imacro) = macro(name, val)
279 end if
280 end if
281 end subroutine
282
283 !> Process a `#undef` directive.
284 !!
285 !! Removes the specified macro from the active macro table.
286 !! If the requested symbol is not currently defined, a warning
287 !! diagnostic is emitted.
288 !!
289 !! @param[in] ctx
290 !! Source context containing the complete `#undef` directive.
291 !! @param[inout] macros
292 !! Active macro table updated in place.
293 !! @param[in] token
294 !! Directive keyword, typically `"undef"`.
295 !!
296 !! @ingroup group_define
297 subroutine handle_undef(ctx, macros, token)
298 type(context), intent(in) :: ctx
299 type(macro), allocatable, intent(inout) :: macros(:)
300 character(*), intent(in) :: token
301 !private
302 character(:), allocatable :: name
303 integer :: i, n, pos
304
305 n = size_of(macros)
306 pos = index(lowercase(ctx%content), token) + len(token)
307 name = trim(adjustl(ctx%content(pos:)))
308 do i = 1, n
309 if (macros(i) == name) then
310 call remove(macros, i)
311 exit
312 end if
313 end do
314
315 if (i > n) then
316 call printf(render(diagnostic_report(level_warning, &
317 message='Unknown macro', &
318 label=label_type(name // ' not found', pos, len(name)), &
319 source=ctx%path), &
320 trim(ctx%content)))
321 end if
322 end subroutine
323end module
subroutine, public handle_undef(ctx, macros, token)
Process a #undef directive.
Definition define.f90:298
subroutine, public handle_define(ctx, macros, token)
Process a #define directive.
Definition define.f90:151
type(global_settings), public global
Global preprocessor configuration instance.
Definition global.f90:192
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
Append macros to a macro table.
Definition macro.f90:175
Remove a macro definition from a table.
Definition macro.f90:213
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