Toggle main menu visibility
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>
107
module
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
, &
118
handle_undef
119
120
contains
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
323
end module
fpx_define::handle_undef
subroutine, public handle_undef(ctx, macros, token)
Process a #undef directive.
Definition
define.f90:298
fpx_define::handle_define
subroutine, public handle_define(ctx, macros, token)
Process a #define directive.
Definition
define.f90:151
fpx_global::global
type(global_settings), public global
Global preprocessor configuration instance.
Definition
global.f90:193
fpx_macro::is_defined
logical function, public is_defined(name, macros, idx)
Determine whether a macro is currently defined.
Definition
macro.f90:831
fpx_string::lowercase
pure character(len_trim(str)) function, public lowercase(str)
Convert string to lower case (respects contents of quotes).
Definition
string.f90:852
fpx_logging::render
Generic renderer for diagnostics and source excerpts.
Definition
logging.f90:208
fpx_macro::add
Append macros to a macro table.
Definition
macro.f90:176
fpx_macro::remove
Remove a macro definition from a table.
Definition
macro.f90:214
fpx_macro::size_of
Return the number of stored macro definitions.
Definition
macro.f90:224
fpx_string::index
Locate the position of a substring.
Definition
string.f90:395
fpx_string::len_trim
Return the trimmed length of a string object.
Definition
string.f90:201
fpx_string::len
Return the length of a string object.
Definition
string.f90:164
fpx_string::trim
Remove trailing blanks from a string object.
Definition
string.f90:240
fpx_context::context
Snapshot of a source location within the preprocessing stream.
Definition
context.f90:114
fpx_logging::diagnostic_report
Structured compiler diagnostic.
Definition
logging.f90:337
fpx_logging::label_type
Diagnostic label identifying a region of source text.
Definition
logging.f90:304
fpx_macro::macro
Representation of a preprocessor macro.
Definition
macro.f90:136
© 2026 davidpfister. All Rights Reserved.