Loading...
Searching...
No Matches
operators.f90
Go to the documentation of this file.
1!> @file
2!! @defgroup group_operators Operators
3!! Module implementing a full C-preprocessor-style constant expression evaluator using a top-down recursive descent parser.
4!! The module provides the ability to evaluate integer constant expressions of the kind used in
5!! classical preprocessor. This includes support for:
6!! - All C-style arithmetic, bitwise, logical, relational, and conditional operators
7!! - Operator precedence and associativity
8!! - Macro identifier substitution and the special `defined(identifier)` operator
9!! - Integer literals in decimal, octal (`0...`), hexadecimal (`0x...`), and binary (`0b...`) bases
10!! - Parenthesized sub-expressions and proper handling of unary operators
11!!
12!! The implementation consists of two major phases:
13!!
14!! 1. Tokenization
15!! The input string is scanned and converted into a sequence of @link fpx_token::token token @endlink objects.
16!! The tokenizer recognizes multi-character operators ('&&', '||', '==', '!=', '<=', '>=', '<<', '>>', '**'),
17!! the `defined` operator (with or without parentheses), numbers in all supported bases,
18!! identifiers, and parentheses. Whitespace is ignored except as a token separator.
19!!
20!! 2. Parsing and evaluation via top-down recursive descent
21!! A classic predictive (LL(1)) recursive descent parser is used, where each non-terminal
22!! in the grammar is implemented as a separate parsing function with the exact precedence level.
23!! The grammar is directly derived from the C standard operator precedence table:
24!!
25!! parse_expression ? parse_conditional
26!! parse_conditional ? parse_or (parse_or '?' parse_expression ':' parse_conditional)
27!! parse_or ? parse_and ( '||' parse_and )*
28!! parse_and ? parse_bitwise_or ( '&&' parse_bitwise_or )*
29!! parse_bitwise_or ? parse_bitwise_xor ( '|' parse_bitwise_xor )*
30!! parse_bitwise_xor ? parse_bitwise_and ( '^' parse_bitwise_and )*
31!! parse_bitwise_and ? parse_equality ( '&' parse_equality )*
32!! parse_equality ? parse_relational ( ('==' | '!=') parse_relational )*
33!! parse_relational ? parse_shifting ( ('<' | '>' | '<=' | '>=') parse_shifting )*
34!! parse_shifting ? parse_additive ( ('<<' | '>>') parse_additive )*
35!! parse_additive ? parse_multiplicative ( ('+' | '-') parse_multiplicative )*
36!! parse_multiplicative ? parse_power ( ('*' | '/' | '%') parse_power )*
37!! parse_power ? parse_unary ( '**' parse_unary )* (right-associative)
38!! parse_unary ? ('!' | '-' | '+' | '~') parse_unary
39!! | parse_atom
40!! parse_atom ? number
41!! | identifier (macro expansion)
42!! | 'defined' ( identifier ) | 'defined' identifier
43!! | '(' parse_expression ')'
44!!
45!! Each parsing function consumes tokens from the global position `pos` and returns the
46!! integer value of the sub-expression it recognizes. Because the grammar is factored by
47!! precedence, left-associativity is achieved naturally via left-recursive loops,
48!! while right-associativity for the power operator (`**`) is handled by calling
49!! `parse_unary` on the right-hand side first.
50!!
51!! Macro expansion occurs lazily inside `parse_atom` when an identifier token is encountered:
52!! - If the identifier is defined, its replacement text is recursively evaluated.
53!! - The special `defined` operator yields 1 or 0 depending on whether the identifier exists.
54!!
55!! The parser is fully re-entrant and has no global state except the `verbose` flag from
56!! the logging module (used only for debugging).
57!!
58!! Public interface:
59!! - @link fpx_operators::evaluate_expression evaluate_expression @endlink: high-level function that tokenizes and evaluates in one
60!! call
61!! - @link fpx_operators::parse_expression parse_expression @endlink: low-level entry point for already-tokenized input
62!!
63!! This design guarantees correct operator precedence without the need for an explicit
64!! abstract syntax tree or stack-based shunting-yard algorithm, while remaining easy to
65!! read, maintain, and extend.
66module fpx_operators
67 use fpx_string
68 use fpx_constants
69 use fpx_macro
70 use fpx_logging
71 use fpx_token
72
73 implicit none; private
74
75 public :: evaluate_expression, &
77
78contains
79
80 !> Evaluates a preprocessor-style expression with macro substitution.
81 !! Tokenizes the input expression, expands macros where appropriate,
82 !! parses it according to operator precedence, and computes the integer result.
83 !! Returns .true. if evaluation succeeded and the result is non-zero.
84 !!
85 !! @param[in] expr Expression string to evaluate
86 !! @param[in] macros Array of defined macros for substitution and `defined()` checks
87 !! @param[out] val Optional integer result of the evaluation
88 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
89 !!
90 !! @b Remarks
91 !! @ingroup group_operators
92 logical function evaluate_expression(expr, macros, val) result(res)
93 character(*), intent(in) :: expr
94 type(macro), intent(in) :: macros(:)
95 integer, intent(out), optional :: val
96 !private
97 type(token), allocatable :: tokens(:)
98 integer :: ntokens, pos, result
99
100 call tokenize(expr, tokens, ntokens)
101 if (ntokens == 0) then
102 if (verbose) print *, "No tokens found for expression"
103 res = .false.
104 return
105 end if
106
107 pos = 1
108 result = parse_expression(tokens, ntokens, pos, macros)
109 if (verbose) print *, "Parsed '", trim(expr), "': pos = ", pos, ", ntokens = ", ntokens, ", result = ", result
110 if (pos <= ntokens) then
111 if (verbose) print *, "Error: Extra tokens in expression: ", trim(tokens(pos)%value)
112 res = .false.
113 return
114 end if
115 res = (result /= 0)
116 if (present(val)) val = result
117 end function
118
119 !> Parses a sequence of tokens starting at position `pos` as a full expression.
120 !! Entry point for the recursive descent parser. Delegates to parse_or().
121 !! @param[in] tokens Array of tokens to parse
122 !! @param[in] ntokens Number of valid tokens in the array
123 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
124 !! @param[in] macros Defined macros for expansion and `defined()` checks
125 !! @return Integer value of the parsed expression
126 !!
127 !! @b Remarks
128 !! @ingroup group_operators
129 recursive integer function parse_expression(tokens, ntokens, pos, macros) result(val)
130 type(token), intent(in) :: tokens(:)
131 integer, intent(in) :: ntokens
132 integer, intent(inout) :: pos
133 type(macro), intent(in) :: macros(:)
134
135 val = parse_conditional(tokens, ntokens, pos, macros)
136 end function
137
138 !> Parses conditional expressions (?:). Right-associative.
139 !! @param[in] tokens Array of tokens to parse
140 !! @param[in] ntokens Number of valid tokens in the array
141 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
142 !! @param[in] macros Defined macros for expansion and `defined()` checks
143 !! @return Integer value of the parsed expression
144 !!
145 !! @b Remarks
146 !! @ingroup group_operators
147 recursive integer function parse_conditional(tokens, ntokens, pos, macros) result(val)
148 type(token), intent(in) :: tokens(:)
149 integer, intent(in) :: ntokens
150 integer, intent(inout) :: pos
151 type(macro), intent(in) :: macros(:)
152
153 integer :: condition, true_val, false_val
154
155 ! First parse condition at higher precedence
156 condition = parse_or(tokens, ntokens, pos, macros)
157
158 ! Check for '?'
159 if (pos <= ntokens .and. tokens(pos)%value == '?') then
160
161 if (verbose) print *, "Parsing ? at pos ", pos
162
163 pos = pos + 1
164
165 ! Parse true expression (full expression allowed)
166 true_val = parse_expression(tokens, ntokens, pos, macros)
167
168 ! Expect ':'
169 if (pos > ntokens .or. tokens(pos)%value /= ':') then
170 if (verbose) print *, "Error: expected ':' in conditional expression"
171 val = 0
172 return
173 end if
174
175 pos = pos + 1
176
177 ! Parse false expression (right-associative)
178 false_val = parse_conditional(tokens, ntokens, pos, macros)
179
180 ! Evaluate condition
181 val = merge(true_val, false_val, condition /= 0)
182
183 else
184
185 val = condition
186
187 end if
188
189 end function
190
191 !> Parses logical OR expressions (`||`).
192 !! @param[in] tokens Array of tokens to parse
193 !! @param[in] ntokens Number of valid tokens in the array
194 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
195 !! @param[in] macros Defined macros for expansion and `defined()` checks
196 !! @return Integer value of the parsed expression
197 !!
198 !! @b Remarks
199 !! @ingroup group_operators
200 recursive integer function parse_or(tokens, ntokens, pos, macros) result(val)
201 type(token), intent(in) :: tokens(:)
202 integer, intent(in) :: ntokens
203 integer, intent(inout) :: pos
204 type(macro), intent(in) :: macros(:)
205 integer :: left
206
207 left = parse_and(tokens, ntokens, pos, macros)
208 do while (pos <= ntokens .and. tokens(pos)%value == '||')
209 if (verbose) print *, "Parsing || at pos ", pos
210 pos = pos + 1
211 val = merge(1, 0, left /= 0 .or. parse_and(tokens, ntokens, pos, macros) /= 0)
212 left = val
213 end do
214 val = left
215 end function
216
217 !> Parses logical AND expressions (`&&`).
218 !! @param[in] tokens Array of tokens to parse
219 !! @param[in] ntokens Number of valid tokens in the array
220 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
221 !! @param[in] macros Defined macros for expansion and `defined()` checks
222 !! @return Integer value of the parsed expression
223 !!
224 !! @b Remarks
225 !! @ingroup group_operators
226 recursive integer function parse_and(tokens, ntokens, pos, macros) result(val)
227 type(token), intent(in) :: tokens(:)
228 integer, intent(in) :: ntokens
229 integer, intent(inout) :: pos
230 type(macro), intent(in) :: macros(:)
231 !private
232 integer :: left
233
234 left = parse_bitwise_or(tokens, ntokens, pos, macros)
235 do while (pos <= ntokens .and. tokens(pos)%value == '&&')
236 if (verbose) print *, "Parsing && at pos ", pos
237 pos = pos + 1
238 val = merge(1, 0, left /= 0 .and. parse_bitwise_or(tokens, ntokens, pos, macros) /= 0)
239 left = val
240 end do
241 val = left
242 end function
243
244 !> Parses bitwise OR expressions (`|`).
245 !! @param[in] tokens Array of tokens to parse
246 !! @param[in] ntokens Number of valid tokens in the array
247 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
248 !! @param[in] macros Defined macros for expansion and `defined()` checks
249 !! @return Integer value of the parsed expression
250 !!
251 !! @b Remarks
252 !! @ingroup group_operators
253 recursive integer function parse_bitwise_or(tokens, ntokens, pos, macros) result(val)
254 type(token), intent(in) :: tokens(:)
255 integer, intent(in) :: ntokens
256 integer, intent(inout) :: pos
257 type(macro), intent(in) :: macros(:)
258 !private
259 integer :: left
260
261 left = parse_bitwise_xor(tokens, ntokens, pos, macros)
262 do while (pos <= ntokens .and. tokens(pos)%value == '|')
263 if (verbose) print *, "Parsing | at pos ", pos
264 pos = pos + 1
265 val = parse_bitwise_xor(tokens, ntokens, pos, macros)
266 left = ior(left, val)
267 end do
268 val = left
269 end function
270
271 !> Parses bitwise XOR expressions (`^`).
272 !! @param[in] tokens Array of tokens to parse
273 !! @param[in] ntokens Number of valid tokens in the array
274 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
275 !! @param[in] macros Defined macros for expansion and `defined()` checks
276 !! @return Integer value of the parsed expression
277 !!
278 !! @b Remarks
279 !! @ingroup group_operators
280 recursive integer function parse_bitwise_xor(tokens, ntokens, pos, macros) result(val)
281 type(token), intent(in) :: tokens(:)
282 integer, intent(in) :: ntokens
283 integer, intent(inout) :: pos
284 type(macro), intent(in) :: macros(:)
285 !private
286 integer :: left
287
288 left = parse_bitwise_and(tokens, ntokens, pos, macros)
289 do while (pos <= ntokens .and. tokens(pos)%value == '^')
290 if (verbose) print *, "Parsing ^ at pos ", pos
291 pos = pos + 1
292 val = parse_bitwise_and(tokens, ntokens, pos, macros)
293 left = ieor(left, val)
294 end do
295 val = left
296 end function
297
298 !> Parses bitwise AND expressions (`&`).
299 !! @param[in] tokens Array of tokens to parse
300 !! @param[in] ntokens Number of valid tokens in the array
301 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
302 !! @param[in] macros Defined macros for expansion and `defined()` checks
303 !! @return Integer value of the parsed expression
304 !!
305 !! @b Remarks
306 !! @ingroup group_operators
307 recursive integer function parse_bitwise_and(tokens, ntokens, pos, macros) result(val)
308 type(token), intent(in) :: tokens(:)
309 integer, intent(in) :: ntokens
310 integer, intent(inout) :: pos
311 type(macro), intent(in) :: macros(:)
312 !private
313 integer :: left
314
315 left = parse_equality(tokens, ntokens, pos, macros)
316 do while (pos <= ntokens .and. tokens(pos)%value == '&')
317 if (verbose) print *, "Parsing && at pos ", pos
318 pos = pos + 1
319 val = parse_equality(tokens, ntokens, pos, macros)
320 left = iand(left, val)
321 end do
322 val = left
323 end function
324
325 !> Parses equality/inequality expressions (`==`, `!=`).
326 !! @param[in] tokens Array of tokens to parse
327 !! @param[in] ntokens Number of valid tokens in the array
328 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
329 !! @param[in] macros Defined macros for expansion and `defined()` checks
330 !! @return Integer value of the parsed expression
331 !!
332 !! @b Remarks
333 !! @ingroup group_operators
334 recursive integer function parse_equality(tokens, ntokens, pos, macros) result(val)
335 type(token), intent(in) :: tokens(:)
336 integer, intent(in) :: ntokens
337 integer, intent(inout) :: pos
338 type(macro), intent(in) :: macros(:)
339 !private
340 integer :: left, right
341
342 left = parse_relational(tokens, ntokens, pos, macros)
343 do while (pos <= ntokens .and. (tokens(pos)%value == '==' .or. tokens(pos)%value == '!='))
344 if (verbose) print *, "Parsing ", trim(tokens(pos)%value), " at pos ", pos
345 if (tokens(pos)%value == '==') then
346 pos = pos + 1
347 right = parse_relational(tokens, ntokens, pos, macros)
348 val = merge(1, 0, left == right)
349 else
350 pos = pos + 1
351 right = parse_relational(tokens, ntokens, pos, macros)
352 val = merge(1, 0, left /= right)
353 end if
354 left = val
355 end do
356 val = left
357 end function
358
359 !> Parses relational expressions (`<`, `>`, `<=`, `>=`).
360 !! @param[in] tokens Array of tokens to parse
361 !! @param[in] ntokens Number of valid tokens in the array
362 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
363 !! @param[in] macros Defined macros for expansion and `defined()` checks
364 !! @return Integer value of the parsed expression
365 !!
366 !! @b Remarks
367 !! @ingroup group_operators
368 recursive integer function parse_relational(tokens, ntokens, pos, macros) result(val)
369 type(token), intent(in) :: tokens(:)
370 integer, intent(in) :: ntokens
371 integer, intent(inout) :: pos
372 type(macro), intent(in) :: macros(:)
373 !private
374 integer :: left, right
375
376 left = parse_shifting(tokens, ntokens, pos, macros)
377 do while (pos <= ntokens .and. (tokens(pos)%value == '<' .or. tokens(pos)%value == '>' .or. &
378 tokens(pos)%value == '<=' .or. tokens(pos)%value == '>='))
379 if (verbose) print *, "Parsing ", trim(tokens(pos)%value), " at pos ", pos
380 if (tokens(pos)%value == '<') then
381 pos = pos + 1
382 right = parse_shifting(tokens, ntokens, pos, macros)
383 val = merge(1, 0, left < right)
384 else if (tokens(pos)%value == '>') then
385 pos = pos + 1
386 right = parse_shifting(tokens, ntokens, pos, macros)
387 val = merge(1, 0, left > right)
388 else if (tokens(pos)%value == '<=') then
389 pos = pos + 1
390 right = parse_shifting(tokens, ntokens, pos, macros)
391 val = merge(1, 0, left <= right)
392 else
393 pos = pos + 1
394 right = parse_shifting(tokens, ntokens, pos, macros)
395 val = merge(1, 0, left >= right)
396 end if
397 left = val
398 end do
399 val = left
400 end function
401
402 !> Parses shift expressions (`<<`, `>>`).
403 !! @param[in] tokens Array of tokens to parse
404 !! @param[in] ntokens Number of valid tokens in the array
405 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
406 !! @param[in] macros Defined macros for expansion and `defined()` checks
407 !! @return Integer value of the parsed expression
408 !!
409 !! @b Remarks
410 !! @ingroup group_operators
411 recursive integer function parse_shifting(tokens, ntokens, pos, macros) result(val)
412 type(token), intent(in) :: tokens(:)
413 integer, intent(in) :: ntokens
414 integer, intent(inout) :: pos
415 type(macro), intent(in) :: macros(:)
416 !private
417 integer :: left, right
418
419 left = parse_additive(tokens, ntokens, pos, macros)
420 do while (pos <= ntokens .and. (tokens(pos)%value == '<<' .or. tokens(pos)%value == '>>'))
421 if (verbose) print *, "Parsing ", trim(tokens(pos)%value), " at pos ", pos
422 if (tokens(pos)%value == '<<') then
423 pos = pos + 1
424 right = parse_additive(tokens, ntokens, pos, macros)
425 val = lshift(left, right)
426 else
427 pos = pos + 1
428 right = parse_additive(tokens, ntokens, pos, macros)
429 val = rshift(left, right)
430 end if
431 left = val
432 end do
433 val = left
434 end function
435
436 !> Parses additive expressions (`+`, `-`).
437 !! @param[in] tokens Array of tokens to parse
438 !! @param[in] ntokens Number of valid tokens in the array
439 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
440 !! @param[in] macros Defined macros for expansion and `defined()` checks
441 !! @return Integer value of the parsed expression
442 !!
443 !! @b Remarks
444 !! @ingroup group_operators
445 recursive integer function parse_additive(tokens, ntokens, pos, macros) result(val)
446 type(token), intent(in) :: tokens(:)
447 integer, intent(in) :: ntokens
448 integer, intent(inout) :: pos
449 type(macro), intent(in) :: macros(:)
450 !private
451 integer :: left, right
452
453 left = parse_multiplicative(tokens, ntokens, pos, macros)
454 do while (pos <= ntokens .and. (tokens(pos)%value == '+' .or. tokens(pos)%value == '-'))
455 if (verbose) print *, "Parsing ", trim(tokens(pos)%value), " at pos ", pos
456 if (tokens(pos)%value == '+') then
457 pos = pos + 1
458 right = parse_multiplicative(tokens, ntokens, pos, macros)
459 val = left + right
460 else
461 pos = pos + 1
462 right = parse_multiplicative(tokens, ntokens, pos, macros)
463 val = left - right
464 end if
465 left = val
466 end do
467 val = left
468 end function
469
470 !> Parses multiplicative expressions (`*`, `/`, `%`).
471 !! @param[in] tokens Array of tokens to parse
472 !! @param[in] ntokens Number of valid tokens in the array
473 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
474 !! @param[in] macros Defined macros for expansion and `defined()` checks
475 !! @return Integer value of the parsed expression
476 !!
477 !! @b Remarks
478 !! @ingroup group_operators
479 recursive integer function parse_multiplicative(tokens, ntokens, pos, macros) result(val)
480 type(token), intent(in) :: tokens(:)
481 integer, intent(in) :: ntokens
482 integer, intent(inout) :: pos
483 type(macro), intent(in) :: macros(:)
484 !private
485 integer :: left, right
486
487 left = parse_power(tokens, ntokens, pos, macros)
488 do while (pos <= ntokens .and. (tokens(pos)%value == '*' .or. tokens(pos)%value == '/' .or. tokens(pos)%value == '%'))
489 if (verbose) print *, "Parsing ", trim(tokens(pos)%value), " at pos ", pos
490 if (tokens(pos)%value == '*') then
491 pos = pos + 1
492 right = parse_power(tokens, ntokens, pos, macros)
493 val = left * right
494 else if (tokens(pos)%value == '/') then
495 pos = pos + 1
496 right = parse_power(tokens, ntokens, pos, macros)
497 val = left / right
498 else
499 pos = pos + 1
500 right = parse_power(tokens, ntokens, pos, macros)
501 val = modulo(left, right)
502 end if
503 left = val
504 end do
505 val = left
506 end function
507
508 !> Parses exponentiation (`**`). Right-associative.
509 !! @param[in] tokens Array of tokens to parse
510 !! @param[in] ntokens Number of valid tokens in the array
511 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
512 !! @param[in] macros Defined macros for expansion and `defined()` checks
513 !! @return Integer value of the parsed expression
514 !!
515 !! @b Remarks
516 !! @ingroup group_operators
517 recursive integer function parse_power(tokens, ntokens, pos, macros) result(val)
518 type(token), intent(in) :: tokens(:)
519 integer, intent(in) :: ntokens
520 integer, intent(inout) :: pos
521 type(macro), intent(in) :: macros(:)
522 !private
523 integer :: left, right
524
525 left = parse_unary(tokens, ntokens, pos, macros)
526 do while (pos <= ntokens .and. (tokens(pos)%value == '**'))
527 if (verbose) print *, "Parsing ", trim(tokens(pos)%value), " at pos ", pos
528 pos = pos + 1
529 right = parse_unary(tokens, ntokens, pos, macros)
530 val = left**right
531 left = val
532 end do
533 val = left
534 end function
535
536 !> Parses unary operators (`!`, `-`, `+`, `~`).
537 !! @param[in] tokens Array of tokens to parse
538 !! @param[in] ntokens Number of valid tokens in the array
539 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
540 !! @param[in] macros Defined macros for expansion and `defined()` checks
541 !! @return Integer value of the parsed expression
542 !!
543 !! @b Remarks
544 !! @ingroup group_operators
545 recursive integer function parse_unary(tokens, ntokens, pos, macros) result(val)
546 type(token), intent(in) :: tokens(:)
547 integer, intent(in) :: ntokens
548 integer, intent(inout) :: pos
549 type(macro), intent(in) :: macros(:)
550
551 if (pos <= ntokens .and. tokens(pos)%value == '!') then
552 if (verbose) print *, "Parsing ! at pos ", pos
553 pos = pos + 1
554 val = merge(0, 1, parse_unary(tokens, ntokens, pos, macros) /= 0)
555 else if (pos <= ntokens .and. tokens(pos)%value == '-') then
556 if (verbose) print *, "Parsing - at pos ", pos
557 pos = pos + 1
558 val = -parse_unary(tokens, ntokens, pos, macros)
559 else if (pos <= ntokens .and. tokens(pos)%value == '+') then
560 if (verbose) print *, "Parsing + at pos ", pos
561 pos = pos + 1
562 val = parse_unary(tokens, ntokens, pos, macros)
563 else if (pos <= ntokens .and. tokens(pos)%value == '~') then
564 if (verbose) print *, "Parsing + at pos ", pos
565 pos = pos + 1
566 val = not(parse_unary(tokens, ntokens, pos, macros))
567 else
568 val = parse_atom(tokens, ntokens, pos, macros)
569 end if
570 end function
571
572 !> Parses primary expressions: numbers, identifiers, `defined(...)`, parentheses.
573 !! @param[in] tokens Array of tokens to parse
574 !! @param[in] ntokens Number of valid tokens in the array
575 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
576 !! @param[in] macros Defined macros for expansion and `defined()` checks
577 !! @return Integer value of the parsed expression
578 !!
579 !! @b Remarks
580 !! @ingroup group_operators
581 recursive integer function parse_atom(tokens, ntokens, pos, macros) result(val)
582 type(token), intent(in) :: tokens(:)
583 integer, intent(in) :: ntokens
584 integer, intent(inout) :: pos
585 type(macro), intent(in) :: macros(:)
586 !private
587 integer :: i
588 character(:), allocatable :: expanded
589 logical :: stitch
590
591 if (pos > ntokens) then
592 if (verbose) print *, "Error: Unexpected end of expression at pos ", pos
593 val = 0
594 return
595 end if
596
597 if (verbose) print *, "Parsing primary: ", trim(tokens(pos)%value), " at pos ", pos
598 if (tokens(pos)%type == 0) then
599 val = strtol(tokens(pos)%value)
600 pos = pos + 1
601 else if (tokens(pos)%type == 2) then
602 if (is_defined(tokens(pos)%value, macros)) then
603 expanded = expand_macros(tokens(pos)%value, macros, stitch)
604 if (.not. evaluate_expression(expanded, macros, val)) val = 0
605 if (verbose) print *, "Expanded ", trim(tokens(pos)%value), " to ", trim(expanded), ", val = ", val
606 else
607 val = 0
608 end if
609 pos = pos + 1
610 else if (tokens(pos)%value == '(') then
611 pos = pos + 1
612 val = parse_expression(tokens, ntokens, pos, macros)
613 if (pos > ntokens .or. tokens(pos)%value /= ')') then
614 if (verbose) print *, "Error: Missing closing parenthesis at pos ", pos
615 val = 0
616 else
617 if (verbose) print *, "Parsed ) at pos ", pos
618 pos = pos + 1
619 end if
620 else if (tokens(pos)%type == 4) then
621 expanded = trim(tokens(pos)%value)
622 val = merge(1, 0, is_defined(expanded, macros))
623 if (verbose) print *, "defined(", trim(expanded), ") = ", val
624 pos = pos + 1
625 else
626 if (verbose) print *, "Error: Invalid token in expression: ", trim(tokens(pos)%value)
627 val = 0
628 pos = pos + 1
629 end if
630 end function
631end module
logical, public verbose
Master switch for verbose diagnostic output Default value is .false. (quiet mode)....
Definition logging.f90:56
character(:) function, allocatable, public expand_macros(line, macros, stitch)
Core recursive macro expander (handles function-like, variadic, #, ##)
Definition macro.f90:309
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:677
recursive integer function parse_equality(tokens, ntokens, pos, macros)
Parses equality/inequality expressions (==, @param[in] tokens Array of tokens to parse @param[in] nt...
recursive integer function parse_relational(tokens, ntokens, pos, macros)
Parses relational expressions (<, >, <=, >=).
recursive integer function parse_unary(tokens, ntokens, pos, macros)
Parses unary operators ( @param[in] tokens Array of tokens to parse @param[in] ntokens Number of vali...
recursive integer function parse_shifting(tokens, ntokens, pos, macros)
Parses shift expressions (<<, >>).
recursive integer function, public parse_expression(tokens, ntokens, pos, macros)
Parses a sequence of tokens starting at position pos as a full expression. Entry point for the recurs...
recursive integer function parse_bitwise_xor(tokens, ntokens, pos, macros)
Parses bitwise XOR expressions (^).
recursive integer function parse_atom(tokens, ntokens, pos, macros)
Parses primary expressions: numbers, identifiers, defined(...), parentheses.
recursive integer function parse_multiplicative(tokens, ntokens, pos, macros)
Parses multiplicative expressions (*, /, %).
recursive integer function parse_or(tokens, ntokens, pos, macros)
Parses logical OR expressions (||).
recursive integer function parse_conditional(tokens, ntokens, pos, macros)
Parses conditional expressions (?:). Right-associative.
recursive integer function parse_additive(tokens, ntokens, pos, macros)
Parses additive expressions (+, -).
logical function, public evaluate_expression(expr, macros, val)
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
Definition operators.f90:93
recursive integer function parse_and(tokens, ntokens, pos, macros)
Parses logical AND expressions (&&).
recursive integer function parse_bitwise_and(tokens, ntokens, pos, macros)
Parses bitwise AND expressions (&).
recursive integer function parse_bitwise_or(tokens, ntokens, pos, macros)
Parses bitwise OR expressions (|).
recursive integer function parse_power(tokens, ntokens, pos, macros)
Parses exponentiation (**). Right-associative.
subroutine, public tokenize(expr, tokens, ntokens)
Tokenizes a preprocessor expression into an array of token structures. Handles whitespace,...
Definition token.f90:161
Return the trimmed string.
Definition string.f90:146
Converts a string to integer.
Definition token.f90:141
Derived type representing a single preprocessor macro Extends string with macro-specific fields: rep...
Definition macro.f90:103
Represents a single token in a parsed expression. Holds the string value of the token and its classif...
Definition token.f90:106