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
57 implicit none; private
58
59 public :: handle_define, &
61
62contains
63
64 !> Process a #define directive and register or update a macro
65 !! Parses the line after `#define`, distinguishes between object-like and
66 !! function-like forms, handles variadic `...`, extracts parameters correctly,
67 !! and stores the macro in the active macro table. Existing macros are
68 !! overwritten. Respects `global%undef` list – macros listed there are ignored.
69 !!
70 !! @param[in] line Full source line containing the #define
71 !! @param[inout] macros Current macro table (updated in-place)
72 !! @param[in] token Usually 'DEFINE' – keyword matched in uppercase
73 !!
74 !! @b Remarks
75 !! @ingroup group_define
76 subroutine handle_define(line, macros, token)
77 character(*), intent(in) :: line
78 type(macro), allocatable, intent(inout) :: macros(:)
79 character(*), intent(in) :: token
80 !private
81 character(:), allocatable :: val, name, temp
82 integer :: pos, paren_start, paren_end, i, npar, imacro
83
84 pos = index(uppercase(line), token) + len(token)
85 temp = trim(adjustl(line(pos + 1:)))
86
87 paren_start = index(temp, '(')
88 pos = index(temp, ' ')
89 if (pos > 0 .and. pos < paren_start) paren_start = 0
90
91 if (paren_start > 0) then
92 name = trim(temp(:paren_start - 1))
93
94 if (global%undef .contains. name) return
95 paren_end = index(temp, ')')
96 if (paren_end == 0) then
97 if (verbose) print *, "Error: Unclosed parenthesis in macro definition: ", trim(line)
98 return
99 end if
100 val = trim(adjustl(temp(paren_end + 1:)))
101 if (verbose) print *, "Raw value before allocation: ", val, ", length = ", len(val)
102
103 temp = temp(paren_start + 1:paren_end - 1)
104 npar = 0
105 pos = 1
106 do while (pos <= len_trim(temp))
107 if (temp(pos:pos) == ',') then
108 npar = npar + 1
109 end if
110 pos = pos + 1
111 end do
112 if (len_trim(temp) > 0) npar = npar + 1
113
114 if (.not. allocated(macros)) allocate(macros(0))
115
116 if (name == 'defined') then
117 if (verbose) print *, '"defined" cannot be used a a macro name'
118 return
119 end if
120
121 if (.not. is_defined(name, macros, imacro)) then
122 call add(macros, name, val)
123 imacro = sizeof(macros)
124 else
125 macros(imacro) = macro(name, val)
126 end if
127
128 if (index(temp, '...') > 0) then
129 macros(imacro)%is_variadic = .true.
130 npar = npar - 1
131 if (allocated(macros(imacro)%params)) deallocate(macros(imacro)%params)
132 allocate(macros(imacro)%params(npar))
133 pos = 1
134 i = 1
135 do while (pos <= len_trim(temp) .and. i <= npar)
136 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
137 pos = pos + 1
138 end do
139 if (pos > len_trim(temp)) exit
140 paren_start = pos
141 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',')
142 pos = pos + 1
143 end do
144 macros(imacro)%params(i) = temp(paren_start:pos - 1)
145 if (verbose) print *, "Param ", i, ": '", macros(imacro)%params(i), &
146 "', length = ", len_trim(macros(imacro)%params(i))
147 i = i + 1
148 pos = pos + 1
149 end do
150 if (verbose) print *, "Defined variadic macro: ", trim(name), &
151 "(", (macros(imacro)%params(i) // ", ", i = 1, npar), "...) = ", trim(val)
152 else
153 macros(imacro)%is_variadic = .false.
154 if (allocated(macros(imacro)%params)) deallocate(macros(imacro)%params)
155 allocate(macros(imacro)%params(npar))
156 pos = 1
157 i = 1
158 do while (pos <= len_trim(temp) .and. i <= npar)
159 do while (pos <= len_trim(temp) .and. temp(pos:pos) == ' ')
160 pos = pos + 1
161 end do
162 if (pos > len_trim(temp)) exit
163 paren_start = pos
164 do while (pos <= len_trim(temp) .and. temp(pos:pos) /= ',' .and. temp(pos:pos) /= ' ')
165 pos = pos + 1
166 if (pos > len_trim(temp)) exit
167 end do
168 macros(imacro)%params(i) = temp(paren_start:pos - 1)
169 if (verbose) print *, "Param ", i, ": '", trim(macros(imacro)%params(i)), &
170 "', length = ", len_trim(macros(imacro)%params(i))
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 if (verbose) print *, "Defined macro: ", trim(name), "(", (macros(imacro)%params(i) // ", ", i = 1, npar - 1), &
177 macros(imacro)%params(npar), ") = ", trim(val)
178 end if
179 else
180 pos = index(temp, ' ')
181 if (pos > 0) then
182 name = trim(temp(:pos - 1))
183 val = trim(adjustl(temp(pos + 1:)))
184 else
185 name = trim(temp)
186 val = ''
187 end if
188
189 if (global%undef .contains. name) return
190 if (.not. allocated(macros)) allocate(macros(0))
191 if (.not. is_defined(name, macros, imacro)) then
192 call add(macros, name, val)
193 imacro = sizeof(macros)
194 else
195 macros(imacro) = macro(name, val)
196 end if
197
198 if (verbose) print *, "Defined macro: ", trim(name), " = ", trim(val)
199 end if
200 end subroutine
201
202 !> Process a #undef directive and remove a macro from the table
203 !! Finds the named macro in the current table and removes it.
204 !! Issues a warning if the macro was not previously defined.
205 !! @param[in] line Full source line containing the #undef
206 !! @param[inout] macros Current macro table (updated in-place)
207 !! @param[in] token Usually 'UNDEF' – keyword matched in uppercase
208 !!
209 !! @b Remarks
210 !! @ingroup group_define
211 subroutine handle_undef(line, macros, token)
212 character(*), intent(in) :: line
213 type(macro), allocatable, intent(inout) :: macros(:)
214 character(*), intent(in) :: token
215 !private
216 type(macro), allocatable :: temp_macros(:)
217 character(:), allocatable :: name
218 integer :: i, n, pos
219
220 n = sizeof(macros)
221 pos = index(uppercase(line), token) + len(token)
222 name = trim(adjustl(line(pos:)))
223 do i = 1, n
224 if (macros(i) == name) then
225 if (verbose) print *, "Undefining macro: ", name
226 call remove(macros, i)
227 exit
228 end if
229 end do
230
231 if (i > n) then
232 if (verbose) print *, "Warning: Macro ", name, " not found for #undef"
233 end if
234 end subroutine
235end module
subroutine, public handle_define(line, macros, token)
Process a define directive and register or update a macro Parses the line after #define,...
Definition define.f90:77
subroutine, public handle_undef(line, macros, token)
Process a undef directive and remove a macro from the table Finds the named macro in the current tabl...
Definition define.f90:212
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
Definition global.f90:92
logical, public verbose
Master switch for verbose diagnostic output Default value is .false. (quiet mode)....
Definition logging.f90:56
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:677
pure character(len_trim(str)) function, public uppercase(str)
Convert string to upper case (respects contents of quotes).
Definition string.f90:583
Add one or more macros to a dynamic table.
Definition macro.f90:113
Remove a macro at given index.
Definition macro.f90:148
Return current number of stored macros.
Definition macro.f90:156
Return the trimmed length of a string.
Definition string.f90:138
Return the length of a string.
Definition string.f90:130
Return the trimmed string.
Definition string.f90:146
Derived type representing a single preprocessor macro Extends string with macro-specific fields: rep...
Definition macro.f90:103