Loading...
Searching...
No Matches
token.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_token Token
3!! @brief Token classification and representation for expression parsing in fpx
4!!
5!! This module provides the lightweight but robust token infrastructure used by the
6!! fpx preprocessor when evaluating constant expressions in `#if` / `#elif` directives.
7!!
8!! It defines:
9!! - A clean enumeration of token kinds (`tokens_enum`)
10!! - A simple `token` derived type that carries both the lexical value and its semantic category
11!!
12!! These types are used internally by `evaluate_expression()` (from `fpx_token`) to parse
13!! and compute `#if DEBUG > 1 && defined(USE_MPI)`-style conditions.
14!!
15!! @par Key design goals
16!! - Minimal memory footprint
17!! - Clear separation between lexical scanning and semantic interpretation
18!! - Easy extensibility for future operators or functions
19!!
20!! @section token_examples Examples
21!!
22!! 1. Manual token creation (mostly for testing/debugging):
23!! @code{.f90}
24!! use fpx_token
25!!
26!! type(token) :: t1, t2, t3
27!!
28!! t1 = token('42', number) ! numeric literal
29!! t2 = token('DEBUG', identifier) ! macro name
30!! t3 = token('>', operator) ! comparison operator
31!!
32!! print *, 'Token: ', t1%value, ' type=', t1%type ! -> 42 type=0
33!! @endcode
34!!
35!! 2. Typical internal usage during `#if` evaluation:
36!! @code{.f90}
37!! ! (inside evaluate_expression)
38!! tokens = tokenize('defined(USE_MPI) && MPI_VERSION >= 3')
39!! ! tokens(1) -> value=vdefined' type=identifier
40!! ! tokens(2) -> value='(' type=parenthesis
41!! ! tokens(3) -> value='USE_MPI' type=identifier
42!! ! ...
43!! @endcode
44!!
45!! @par Token kinds overview
46!! | Enumerator | Value | Meaning |
47!! |--------------|-------|----------------------------------------------|
48!! | `unknown` | -1 | Invalid / unrecognized token |
49!! | `number` | 0 | Integer or floating-point literal |
50!! | `operator` | 1 | ?:, +, -, *, /, ==, !=, &&, ||, !, >, <, etc.|
51!! | `identifier` | 2 | Macro name or function name (e.g. `defined`) |
52!! | `parenthesis`| 3 | `(` or `)` |
53!! | `defined` | 4 | Special keyword `defined` (treated specially)|
54!!
55module fpx_token
56 use fpx_constants, only: max_tokens
57 use fpx_string
58 use fpx_logging
59
60 implicit none; private
61
62 public :: tokenize, &
63 strtol, &
65 unknown, &
66 number, &
67 operation, &
68 identifier, &
69 parenthesis, &
70 defined
71
72 !> @brief Token kinds used in expression parsing.
73 !! Enumeration defining the possible types of tokens recognized by the tokenizer.
74 !! @ingroup group_token
75 enum, bind(c)
76 enumerator :: unknown = -1
77 enumerator :: number = 0
78 enumerator :: operation = 1
79 enumerator :: identifier = 2
80 enumerator :: parenthesis = 3
81 enumerator :: defined = 4
82 end enum
83
84 !> @brief Kind parameter for token type enumeration. Values are (`unknown`, `number`, `operation`, `identifier`, `parenthesis`,
85 !! `defined`)
86 !! @ingroup group_token
87 integer, parameter :: tokens_enum = kind(unknown)
88
89 !> Represents a single token in a parsed expression.
90 !! Holds the string value of the token and its classified type.
91 !! <h2 class="groupheader">Constructors</h2>
92 !! Initializes a new instance of the @link fpx_token::token token @endlink class
93 !! <h3>token(character(:), integer)</h3>
94 !! @verbatim type(token) function token(character(:) value, integer type) @endverbatim
95 !!
96 !! @param[in] value
97 !! @param[in] type
98 !!
99 !! @b Examples
100 !! @code{.f90}
101 !! a = token('9', number)
102 !! @endcode
103 !!
104 !! @ingroup group_token
105 type, public :: token
106 character(:), allocatable :: value !< Token value
107 integer(tokens_enum) :: type !< Token type, from the enum @ref tokens_enum.
108 integer :: start
109 end type
110
111 !> Converts a string to integer.
112 !! <h2 class="groupheader">Methods</h2>
113 !!
114 !! @code{.f90}strtol(character(*) str, (optional) logical success)@endcode
115 !!
116 !! @param[in] str String to convert
117 !! @param[out] success Optional flag indicating successful conversion
118 !! @return Converted integer value
119 !!
120 !! @code{.f90}strtol(character(*) str, integer base, (optional) logical success)@endcode
121 !!
122 !! Converts a string to integer with explicit base handling.
123 !! Supports base 2, 8, 10, 16 and prefixes `0x`, `0b`.
124 !! @param[in] str String to convert
125 !! @param[inout] base 0 = auto-detect, otherwise forces given base
126 !! @param[out] success Optional flag indicating successful conversion
127 !! @return Converted integer value
128 !!
129 !! <h2 class="groupheader"> Examples </h2>
130 !! The following demonstrate a call to the `strtol` interface.
131 !! @code{.f90}
132 !! integer :: i
133 !! logical :: success
134 !!
135 !! i = strtol(' 123', 0, success = res)
136 !! ! i = 123
137 !! @endcode
138 !!
139 !! @ingroup group_operators
140 interface strtol
141 !! @cond
142 module procedure :: strtol_default
143 module procedure :: strtol_with_base
144 !! @endcond
145 end interface
146
147contains
148
149 !> Tokenizes a preprocessor expression into an array of token structures.
150 !! Handles whitespace, multi-character operators (`&&`, `||`, `==`, etc.),
151 !! the `defined` operator (with or without parentheses), numbers in various bases,
152 !! identifiers, and parentheses.
153 !! @param[in] expr Expression string to tokenize
154 !! @param[out] tokens Allocated array receiving the tokens
155 !! @param[out] ntokens Number of tokens produced
156 !!
157 !! @ingroup group_token
158 subroutine tokenize(expr, tokens, ntokens)
159 character(*), intent(in) :: expr
160 type(token), allocatable, intent(out) :: tokens(:)
161 integer, intent(out) :: ntokens
162 !private
163 character(:), allocatable :: temp
164 integer :: i, pos, len_expr
165 logical :: in_word
166 logical, save :: in_comment
167
168 if (allocated(tokens)) deallocate(tokens)
169 allocate(tokens(max_tokens))
170 ntokens = 0
171 temp = trim(adjustl(expr)) // ' '
172 len_expr = len_trim(temp)
173 i = 1
174 in_word = .false.
175
176 do while (i <= len_expr)
177 if (temp(i:i) == ' ') then
178 i = i + 1
179 in_word = .false.
180 cycle
181 end if
182
183 if (.not. in_word) then
184 ntokens = ntokens + 1
185 if (ntokens > max_tokens) then
186 call printf(render(diagnostic_report(level_error, &
187 message='The maximum number of tokens has been reached', &
188 label=label_type('Too many tokens in expression.', 1, 1)), &
189 expr))
190 return
191 end if
192 in_word = .true.
193 end if
194
195 if (temp(i:i) == '(' .or. temp(i:i) == ')') then
196 tokens(ntokens)%value = temp(i:i)
197 tokens(ntokens)%type = parenthesis
198 tokens(ntokens)%start = i
199 i = i + 1
200 in_word = .false.
201 else if (temp(i:i + 1) == '&&' .or. temp(i:i + 1) == '||' .or. temp(i:i + 1) == '==' .or. &
202 temp(i:i + 1) == '!=' .or. temp(i:i + 1) == '<=' .or. temp(i:i + 1) == '>=') then
203 tokens(ntokens)%value = temp(i:i + 1)
204 tokens(ntokens)%type = operation
205 tokens(ntokens)%start = i
206 i = i + 2
207 in_word = .false.
208 else if (temp(i:i) == '!') then
209 tokens(ntokens)%value = temp(i:i)
210 tokens(ntokens)%type = operation
211 tokens(ntokens)%start = i
212 i = i + 1
213 in_word = .false.
214 else if (temp(i:i + 1) == '**') then
215 tokens(ntokens)%value = temp(i:i + 1)
216 tokens(ntokens)%type = operation
217 tokens(ntokens)%start = i
218 i = i + 2
219 in_word = .false.
220 else if (temp(i:i + 1) == '<<' .or. temp(i:i + 1) == '>>') then
221 tokens(ntokens)%value = temp(i:i + 1)
222 tokens(ntokens)%type = operation
223 tokens(ntokens)%start = i
224 i = i + 2
225 in_word = .false.
226 else if (temp(i:i) == '<' .or. temp(i:i) == '>' .or. temp(i:i) == '=' .or. &
227 temp(i:i) == '+' .or. temp(i:i) == '-' .or. temp(i:i) == '*' .or. &
228 temp(i:i) == '/' .or. temp(i:i) == '%' .or. &
229 temp(i:i) == '?' .or. temp(i:i) == ':') then
230 tokens(ntokens)%value = temp(i:i)
231 tokens(ntokens)%type = operation
232 tokens(ntokens)%start = i
233 i = i + 1
234 in_word = .false.
235 else if (temp(i:i) == '&' .or. temp(i:i) == '|' .or. temp(i:i) == '^' .or. &
236 temp(i:i) == '~') then
237 tokens(ntokens)%value = temp(i:i)
238 tokens(ntokens)%type = operation
239 tokens(ntokens)%start = i
240 i = i + 1
241 in_word = .false.
242 else if (starts_with(temp(i:), 'defined')) then
243 i = i + 7
244 do while (i <= len_expr .and. temp(i:i) == ' ')
245 i = i + 1
246 end do
247 if (i <= len_expr .and. temp(i:i) == '(') then
248 i = i + 1
249 pos = i
250 do while (pos <= len_expr .and. temp(pos:pos) /= ')')
251 pos = pos + 1
252 end do
253 tokens(ntokens)%value = trim(adjustl(temp(i:pos - 1)))
254 tokens(ntokens)%type = defined
255 tokens(ntokens)%start = i
256 i = pos + 1
257 else
258 pos = i
259 do while (pos <= len_expr .and. temp(pos:pos) /= ' ')
260 pos = pos + 1
261 end do
262 tokens(ntokens)%value = trim(adjustl(temp(i:pos - 1)))
263 tokens(ntokens)%type = defined
264 tokens(ntokens)%start = i
265 i = pos
266 end if
267 in_word = .false.
268 else if (is_typeless(temp(i:), pos)) then
269 pos = i + pos
270 tokens(ntokens)%value = trim(adjustl(temp(i:pos - 1)))
271 tokens(ntokens)%type = number
272 tokens(ntokens)%start = i
273 i = pos
274 in_word = .false.
275 else if (is_digit(temp(i:i))) then
276 pos = i
277 do while (pos <= len_expr .and. is_digit(temp(pos:pos)))
278 pos = pos + 1
279 end do
280 tokens(ntokens)%value = trim(adjustl(temp(i:pos - 1)))
281 tokens(ntokens)%type = number
282 tokens(ntokens)%start = i
283 i = pos
284 in_word = .false.
285 else
286 pos = i
287 do while (pos <= len_expr .and. temp(pos:pos) /= ' ' .and. &
288 temp(pos:pos) /= '(' .and. temp(pos:pos) /= ')')
289 pos = pos + 1
290 end do
291 tokens(ntokens)%value = trim(temp(i:pos - 1))
292 tokens(ntokens)%type = identifier
293 tokens(ntokens)%start = i
294 i = pos
295 in_word = .false.
296 end if
297 end do
298 end subroutine
299
300 !> Tests whether a single character is a decimal digit ('0'-'9').
301 !! @param[in] ch Character to test
302 !! @return .true. if ch is a digit
303 !!
304 !! @ingroup group_token
305 logical elemental function is_digit(ch) result(res)
306 character(*), intent(in) :: ch
307
308 res = verify(ch, '0123456789') == 0
309 end function
310
311 !> Detects whether a string starts a typeless constant (hex, octal, binary).
312 !! Used to avoid treating them as identifiers during tokenization.
313 !! @param[in] str Input string starting at current position
314 !! @param[out] pos Length of the typeless constant (0 if not typeless)
315 !! @return .true. if the prefix is a valid typeless constant in non-base-10
316 !!
317 !! @ingroup group_token
318 logical function is_typeless(str, pos) result(res)
319 character(*), intent(in) :: str
320 integer, intent(out) :: pos
321 !private
322 integer :: i, base, n
323
324 pos = 0; base = 0; n = len(str)
325 do i = 1, n
326 if (verify(str(i:i), '0123456789xXaAbBcCdDeEfF') /= 0) then
327 pos = i
328 exit
329 end if
330 end do
331 if (pos > 0) i = strtol(str(:pos - 1), base, success=res)
332 if (base == 10) res = .false.
333 end function
334
335 !> Implementation of strtol function
336 integer function strtol_default(str, success) result(val)
337 character(*), intent(in) :: str
338 logical, intent(out), optional :: success
339 !private
340 integer :: base
341
342 base = 0
343 val = strtol_with_base(str, base, success)
344 end function
345
346 !> Implementation of strtol function with a base argument.
347 integer function strtol_with_base(str, base, success) result(val)
348 character(*), intent(in) :: str
349 integer, intent(inout) :: base
350 logical, intent(out), optional :: success
351 !private
352 integer :: i, len, digit
353 character :: c
354 logical :: is_valid, isdigit, is_lower_hex, is_upper_hex
355 character(len=len_trim(str)) :: work_str
356
357 val = 0; is_valid = .true.
358 work_str = adjustl(str) ! Remove leading spaces
359 len = len_trim(work_str)
360
361 ! Handle base 0 (auto-detect)
362 if (base == 0) then
363 if (len >= 2) then
364 if (work_str(1:2) == '0x' .or. work_str(1:2) == '0X') then
365 base = 16
366 work_str = work_str(3:len)
367 len = len - 2
368 else if (work_str(1:2) == '0b' .or. work_str(1:2) == '0B') then
369 base = 2
370 work_str = work_str(3:len)
371 len = len - 2
372 else
373 if (len > 1) then
374 if (work_str(1:1) == '0') then
375 base = 8
376 else
377 base = 10
378 end if
379 else
380 base = 10
381 end if
382 end if
383 else
384 base = 10
385 end if
386 end if
387
388 ! Validate base
389 if (base /= 2 .and. base /= 8 .and. base /= 10 .and. base /= 16) then
390 is_valid = .false.
391 if (present(success)) success = .false.
392 return
393 end if
394
395 ! Process each character
396 do i = 1, len
397 c = work_str(i:i)
398 digit = -1 ! Invalid digit marker
399
400 ! Convert character to digit
401 isdigit = c >= '0' .and. c <= '9'
402 if (isdigit) digit = ichar(c) - ichar('0')
403
404 is_lower_hex = base == 16 .and. c >= 'a' .and. c <= 'f'
405 if (is_lower_hex) digit = ichar(c) - ichar('a') + 10
406
407 is_upper_hex = base == 16 .and. c >= 'A' .and. c <= 'F'
408 if (is_upper_hex) digit = ichar(c) - ichar('A') + 10
409
410 ! Check if digit is valid
411 if (digit == -1) then
412 is_valid = .false.
413 exit
414 end if
415 if (digit >= base) then
416 is_valid = .false.
417 exit
418 end if
419
420 ! Check for potential overflow (approximate for 32-bit integer)
421 if (val > (huge(val) - digit) / base) then
422 is_valid = .false.
423 exit
424 end if
425
426 ! Accumulate value
427 val = val * base + digit
428 end do
429
430 ! Set success flag if provided
431 if (present(success)) success = is_valid
432 end function
433end module
integer, parameter, public max_tokens
Maximum number of tokens generated during tokenization.
logical function, public starts_with(str, arg1, idx)
Checks if a string starts with a given prefix Returns .true. if the string str (after trimming leadin...
Definition string.f90:715
integer, parameter, public tokens_enum
Kind parameter for token type enumeration. Values are (unknown, number, operation,...
Definition token.f90:87
subroutine, public tokenize(expr, tokens, ntokens)
Tokenizes a preprocessor expression into an array of token structures. Handles whitespace,...
Definition token.f90:159
Generic renderer for diagnostics and source excerpts.
Definition logging.f90:208
Return the trimmed length of a string object.
Definition string.f90:201
Remove trailing blanks from a string object.
Definition string.f90:240
Converts a string to integer.
Definition token.f90:140
Structured compiler diagnostic.
Definition logging.f90:337
Diagnostic label identifying a region of source text.
Definition logging.f90:304
Represents a single token in a parsed expression. Holds the string value of the token and its classif...
Definition token.f90:105