Loading...
Searching...
No Matches
define.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_define Define
3!! Processing of #define and #undef preprocessor directives
4!! This module implements the core logic for handling macro definition and removal
5!! during preprocessing in the fpx Fortran preprocessor. It supports:
6!! - Object-like macros: `#define NAME value`
7!! - Function-like macros: `#define NAME(arg1, arg2, ...) replacement`
8!! - Variadic macros using `...` and automatic detection
9!! - Proper parameter parsing with whitespace handling
10!! - Macro redefinition (overwrites existing definition)
11!! - Safe `#undef` that removes a previously defined macro
12!! - Integration with global undef list (`global%undef`) to block redefinition
13!! - Comprehensive verbose logging of all definition actions
14!!
15!! The routines are designed to be robust against malformed input and provide
16!! clear diagnostics when `verbose = .true.`.
17!! <h2 class="groupheader">Examples</h2>
18!!
19!! 1. Define simple object-like macros:
20!! @code{.f90}
21!! #define PI 3.141592653589793
22!! #define DEBUG 1
23!! #define MAX_SIZE 1024
24!! ...
25!! @endcode
26!!
27!! 2. Define function-like and variadic macros:
28!! @code{.f90}
29!! #define SQR(x) ((x)*(x))
30!! #define LOG_MSG(level, ...) print *, '[LOG:', level, ']', __VA_ARGS__
31!! #define CONCAT(a,b) a ## _ ## b
32!! ...
33!! @endcode
34!!
35!! 3. Undefine a macro:
36!! @code{.f90}
37!! #undef DEBUG
38!! !> Subsequent #ifdef DEBUG will be false
39!! @endcode
40!!
41!! 4. Using from a driver program:
42!! @code{.f90}
43!! use fpx_global
44!! use fpx_logging, only: verbose
45!!
46!! verbose = .true.
47!! call preprocess('input.F90') ! Will show all macro definitions/undefs
48!! ...
49!! @endcode
50module fpx_define
51 use fpx_constants
52 use fpx_logging
53 use fpx_macro
54 use fpx_string
55 use fpx_global
56 use fpx_context
57
58 implicit none; private
59
60 public :: handle_define, &
62
63contains
64
65 !> Process a #define directive and register or update a macro
66 !! Parses the line after `#define`, distinguishes between object-like and
67 !! function-like forms, handles variadic `...`, extracts parameters correctly,
68 !! and stores the macro in the active macro table. Existing macros are
69 !! overwritten. Respects `global%undef` list – macros listed there are ignored.
70 !!
71 !! @param[in] ctx Context source line containing the #define
72 !! @param[inout] macros Current macro table (updated in-place)
73 !! @param[in] token Usually 'DEFINE' – keyword matched in lowercase
74 !!
75 !! @b Remarks
76 !! @ingroup group_define
77 subroutine handle_define(ctx, macros, token)
78 type(context), intent(in) :: ctx
79 type(macro), allocatable, intent(inout) :: macros(:)
80 character(*), intent(in) :: token
81 !private
82 character(:), allocatable :: val, name, temp
83 integer :: pos, paren_start, paren_end, i, npar, imacro
84
85 pos = index(lowercase(ctx%content), token) + len(token)
86 temp = trim(adjustl(ctx%content(pos + 1:)))
87
88 paren_start = index(temp, '(')
89 pos = index(temp, ' ')
90 if (pos > 0 .and. pos < paren_start) paren_start = 0
91
92 if (paren_start > 0) then
93 name = trim(temp(:paren_start - 1))
94
95 if (global%undef .contains. name) return
96 paren_end = index(temp, ')')
97 if (paren_end == 0) then
98 call printf(render(diagnostic_report(level_error, &
99 message='Synthax error', &
100 label=label_type('Missing closing parenthesis in macro definition', len_trim(ctx%content) + 1, 1), &
101 source=ctx%path), &
102 trim(ctx%content), ctx%line))
103 return
104 end if
105 val = trim(adjustl(temp(paren_end + 1:)))
106 temp = temp(paren_start + 1:paren_end - 1)
107 npar = 0
108 pos = 1
109 do while (pos <= len_trim(temp))
110 if (temp(pos:pos) == ',') then
111 npar = npar + 1
112 end if
113 pos = pos + 1
114 end do
115 if (len_trim(temp) > 0) npar = npar + 1
116
117 if (.not. allocated(macros)) allocate(macros(0))
118
119 if (name == 'defined') then
120 call printf(render(diagnostic_report(level_error, &
121 message='Reserved macro name', &
122 label=label_type('"defined" cannot be used as a macro name', paren_start + 1, len(name)), &
123 source=ctx%path), &
124 trim(ctx%content), ctx%line))
125 end if
126
127 if (.not. is_defined(name, macros, imacro)) then
128 call add(macros, name, val)
129 imacro = sizeof(macros)
130 else
131 macros(imacro) = macro(name, val)
132 end if
133
134 if (index(temp, '...') > 0) then
135 macros(imacro)%is_variadic = .true.
136 npar = npar - 1
137 if (allocated(macros(imacro)%params)) deallocate(macros(imacro)%params)
138 allocate(macros(imacro)%params(npar))
139 pos = 1
140 i = 1
141 do while (pos <= len_trim(temp) .and. i <= npar)
142 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
143 pos = pos + 1
144 end do
145 if (pos > len_trim(temp)) exit
146 paren_start = pos
147 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',')
148 pos = pos + 1
149 end do
150 macros(imacro)%params(i) = temp(paren_start:pos - 1)
151 i = i + 1
152 pos = pos + 1
153 end do
154 else
155 macros(imacro)%is_variadic = .false.
156 if (allocated(macros(imacro)%params)) deallocate(macros(imacro)%params)
157 allocate(macros(imacro)%params(npar))
158 pos = 1
159 i = 1
160 do while (pos <= len_trim(temp) .and. i <= npar)
161 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
162 pos = pos + 1
163 end do
164 if (pos > len_trim(temp)) exit
165 paren_start = pos
166 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',' .and. temp(pos:pos) /= ' ')
167 pos = pos + 1
168 if (pos > len_trim(temp)) exit
169 end do
170 macros(imacro)%params(i) = temp(paren_start:pos - 1)
171 i = i + 1
172 if (pos <= len_trim(temp)) then
173 if (temp(pos:pos) == ',') pos = pos + 1
174 end if
175 end do
176 end if
177 else
178 pos = index(temp, ' ')
179 if (pos > 0) then
180 name = trim(temp(:pos - 1))
181 val = trim(adjustl(temp(pos + 1:)))
182 else
183 name = trim(temp)
184 val = ''
185 end if
186
187 if (global%undef .contains. name) return
188 if (.not. allocated(macros)) allocate(macros(0))
189 if (.not. is_defined(name, macros, imacro)) then
190 call add(macros, name, val)
191 imacro = sizeof(macros)
192 else
193 macros(imacro) = macro(name, val)
194 end if
195 end if
196 end subroutine
197
198 !> Process a #undef directive and remove a macro from the table
199 !! Finds the named macro in the current table and removes it.
200 !! Issues a warning if the macro was not previously defined.
201 !! @param[in] ctx Context source line containing the #undef
202 !! @param[inout] macros Current macro table (updated in-place)
203 !! @param[in] token Usually 'UNDEF' – keyword matched in lowercase
204 !!
205 !! @b Remarks
206 !! @ingroup group_define
207 subroutine handle_undef(ctx, macros, token)
208 type(context), intent(in) :: ctx
209 type(macro), allocatable, intent(inout) :: macros(:)
210 character(*), intent(in) :: token
211 !private
212 type(macro), allocatable :: temp_macros(:)
213 character(:), allocatable :: name
214 integer :: i, n, pos
215
216 n = sizeof(macros)
217 pos = index(lowercase(ctx%content), token) + len(token)
218 name = trim(adjustl(ctx%content(pos:)))
219 do i = 1, n
220 if (macros(i) == name) then
221 call remove(macros, i)
222 exit
223 end if
224 end do
225
226 if (i > n) then
227 call printf(render(diagnostic_report(level_warning, &
228 message='Unknown macro', &
229 label=label_type(name // ' not found', pos, len(name)), &
230 source=ctx%path), &
231 trim(ctx%content)))
232 end if
233 end subroutine
234end module
subroutine, public handle_undef(ctx, macros, token)
Process a undef directive and remove a macro from the table Finds the named macro in the current tabl...
Definition define.f90:208
subroutine, public handle_define(ctx, macros, token)
Process a define directive and register or update a macro Parses the line after #define,...
Definition define.f90:78
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
Definition global.f90:93
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
Add one or more macros to a dynamic table.
Definition macro.f90:115
Remove a macro at given index.
Definition macro.f90:150
Return current number of stored macros.
Definition macro.f90:158
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
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