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