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.
6!!
7!! This includes support for:
8!! - All C-style arithmetic, bitwise, logical, relational, and conditional operators
9!! - Operator precedence and associativity
10!! - Macro identifier substitution and the special `defined(identifier)` operator
11!! - Integer literals in decimal, octal (`0...`), hexadecimal (`0x...`), and binary (`0b...`) bases
12!! - Parenthesized sub-expressions and proper handling of unary operators
13!!
14!! The implementation consists of two major phases:
15!!
16!! 1. Tokenization
17!! The input string is scanned and converted into a sequence of @link fpx_token::token token @endlink objects.
18!! The tokenizer recognizes multi-character operators ('&&', '||', '==', '!=', '<=', '>=', '<<', '>>', '**'),
19!! the `defined` operator (with or without parentheses), numbers in all supported bases,
20!! identifiers, and parentheses. Whitespace is ignored except as a token separator.
21!!
22!! 2. Parsing and evaluation via top-down recursive descent
23!! A classic predictive (LL(1)) recursive descent parser is used, where each non-terminal
24!! in the grammar is implemented as a separate parsing function with the exact precedence level.
25!! The grammar is directly derived from the C standard operator precedence table:
26!!
27!! parse_expression ? parse_conditional
28!! parse_conditional ? parse_or (parse_or '?' parse_expression ':' parse_conditional)
29!! parse_or ? parse_and ( '||' parse_and )*
30!! parse_and ? parse_bitwise_or ( '&&' parse_bitwise_or )*
31!! parse_bitwise_or ? parse_bitwise_xor ( '|' parse_bitwise_xor )*
32!! parse_bitwise_xor ? parse_bitwise_and ( '^' parse_bitwise_and )*
33!! parse_bitwise_and ? parse_equality ( '&' parse_equality )*
34!! parse_equality ? parse_relational ( ('==' | '!=') parse_relational )*
35!! parse_relational ? parse_shifting ( ('<' | '>' | '<=' | '>=') parse_shifting )*
36!! parse_shifting ? parse_additive ( ('<<' | '>>') parse_additive )*
37!! parse_additive ? parse_multiplicative ( ('+' | '-') parse_multiplicative )*
38!! parse_multiplicative ? parse_power ( ('*' | '/' | '%') parse_unary )*
39!! parse_unary ? ('!' | '-' | '+' | '~') parse_unary
40!! | parse_power
41!! parse_power ? parse_unary ( '**' parse_unary )* (right-associative)
42!! parse_atom ? number
43!! | identifier (macro expansion)
44!! | 'defined' ( identifier ) | 'defined' identifier
45!! | '(' parse_expression ')'
46!!
47!! Each parsing function consumes tokens from the global position `pos` and returns the
48!! integer value of the sub-expression it recognizes. Because the grammar is factored by
49!! precedence, left-associativity is achieved naturally via left-recursive loops,
50!! while right-associativity for the power operator (`**`) is handled by calling
51!! `parse_unary` on the right-hand side first.
52!!
53!! Macro expansion occurs lazily inside `parse_atom` when an identifier token is encountered:
54!! - If the identifier is defined, its replacement text is recursively evaluated.
55!! - The special `defined` operator yields 1 or 0 depending on whether the identifier exists.
56!!
57!! The parser is fully re-entrant and has no global state.
58!!
59!! Public interface:
60!! - @link fpx_operators::evaluate_expression evaluate_expression @endlink: high-level function that tokenizes and evaluates in
61!! one call
62!! - @link fpx_operators::parse_expression parse_expression @endlink: low-level entry point for already-tokenized input
63!!
64!! This design guarantees correct operator precedence without the need for an explicit
65!! abstract syntax tree or stack-based shunting-yard algorithm, while remaining easy to
66!! read, maintain, and extend.
67module fpx_operators
68 use fpx_global
69 use fpx_string
70 use fpx_constants
71 use fpx_macro
72 use fpx_logging
73 use fpx_token
74 use fpx_context
75
76 implicit none; private
77
78 public :: evaluate_expression, &
80
81 !> Evaluates a preprocessor-style expression with macro substitution.
82 !! Tokenizes the input expression, expands macros where appropriate,
83 !! parses it according to operator precedence, and computes the integer result.
84 !! Returns .true. if evaluation succeeded and the result is non-zero.
85 !!
86 !! <h3>evaluate(character(*), type(macros)(:), integer)</h3>
87 !! @verbatim logical function evaluate(expr, macros, val) @endverbatim
88 !!
89 !! @param[in] expr Expression string to evaluate
90 !! @param[inout] macros Array of defined macros for substitution and `defined()` checks
91 !! @param[out] val (optional) integer result of the evaluation
92 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
93 !!
94 !! <h3>evaluate(character(*), type(macros)(:), type(context), integer)</h3>
95 !! @verbatim logical function evaluate(expr, macros, ctx, val) @endverbatim
96 !!
97 !! @param[in] expr Expression string to evaluate
98 !! @param[inout] macros Array of defined macros for substitution and `defined()` checks
99 !! @param[in] ctx Current context
100 !! @param[out] val (optional) integer result of the evaluation
101 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
102 !!
103 !! @b Remarks
104 !! @ingroup group_operators
106 module procedure :: evaluate_expression_default
107 module procedure :: evaluate_expression_with_context
108 end interface
109
110contains
111
112 !> Evaluates a preprocessor-style expression with macro substitution.
113 !! Tokenizes the input expression, expands macros where appropriate,
114 !! parses it according to operator precedence, and computes the integer result.
115 !! Returns .true. if evaluation succeeded and the result is non-zero.
116 !!
117 !! @param[in] expr Expression string to evaluate
118 !! @param[inout] macros Array of defined macros for substitution and `defined()` checks
119 !! @param[out] val (optional) integer result of the evaluation
120 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
121 !!
122 !! @b Remarks
123 !! @ingroup group_operators
124 logical function evaluate_expression_default(expr, macros, val) result(res)
125 character(*), intent(in) :: expr
126 type(macro), allocatable, intent(inout) :: macros(:)
127 integer, intent(out), optional :: val
128 !private
129 type(context) :: ctx
130
131 ctx = context(expr, 1, '')
132 res = evaluate_expression(expr, macros, ctx, val)
133 end function
134
135 !> Evaluates a preprocessor-style expression with macro substitution.
136 !! Tokenizes the input expression, expands macros where appropriate,
137 !! parses it according to operator precedence, and computes the integer result.
138 !! Returns .true. if evaluation succeeded and the result is non-zero.
139 !!
140 !! @param[in] expr Expression string to evaluate
141 !! @param[inout] macros Array of defined macros for substitution and `defined()` checks
142 !! @param[in] ctx Context
143 !! @param[out] val (optional) integer result of the evaluation
144 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
145 !!
146 !! @b Remarks
147 !! @ingroup group_operators
148 logical function evaluate_expression_with_context(expr, macros, ctx, val) result(res)
149 character(*), intent(in) :: expr
150 type(macro), allocatable, intent(inout) :: macros(:)
151 type(context), intent(in) :: ctx
152 integer, intent(out), optional :: val
153 !private
154 type(token), allocatable :: tokens(:)
155 integer :: ntokens, pos, result
156
157 call tokenize(expr, tokens, ntokens)
158 if (ntokens == 0) then
159 call printf(render(diagnostic_report(level_error, &
160 message='Tokenization failed', &
161 label=label_type('No tokens found', 1, len_trim(expr)), &
162 source=trim(ctx%path)), &
163 expr, ctx%line))
164 res = .false.
165 return
166 end if
167
168 pos = 1
169 result = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
170 if (pos <= ntokens) then
171 call printf(render(diagnostic_report(level_error, &
172 message='Tokenization failed', &
173 label=label_type('Extra tokens found', tokens(pos)%start, len_trim(tokens(pos)%value)), &
174 source=trim(ctx%path)), &
175 expr, ctx%line))
176 res = .false.
177 return
178 end if
179 res = (result /= 0)
180 if (present(val)) val = result
181 end function
182
183 !> Parses a sequence of tokens starting at position `pos` as a full expression.
184 !! Entry point for the recursive descent parser. Delegates to parse_or().
185 !! @param[in] expr Expression to be processed
186 !! @param[in] tokens Array of tokens to parse
187 !! @param[in] ntokens Number of valid tokens in the array
188 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
189 !! @param[inout] macros Defined macros for expansion and `defined()` checks
190 !! @param[in] ctx Context
191 !! @return Integer value of the parsed expression
192 !!
193 !! @b Remarks
194 !! @ingroup group_operators
195 recursive integer function parse_expression(expr, tokens, ntokens, pos, macros, ctx) result(val)
196 character(*), intent(in) :: expr
197 type(token), intent(in) :: tokens(:)
198 integer, intent(in) :: ntokens
199 integer, intent(inout) :: pos
200 type(macro), allocatable, intent(inout) :: macros(:)
201 type(context), intent(in) :: ctx
202
203 val = parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
204 end function
205
206 !> Parses conditional expressions (?:). Right-associative.
207 !! @param[in] expr Expression to be processed
208 !! @param[in] tokens Array of tokens to parse
209 !! @param[in] ntokens Number of valid tokens in the array
210 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
211 !! @param[inout] macros Defined macros for expansion and `defined()` checks
212 !! @param[in] ctx Context
213 !! @return Integer value of the parsed expression
214 !!
215 !! @b Remarks
216 !! @ingroup group_operators
217 recursive integer function parse_conditional(expr, tokens, ntokens, pos, macros, ctx) result(val)
218 character(*), intent(in) :: expr
219 type(token), intent(in) :: tokens(:)
220 integer, intent(in) :: ntokens
221 integer, intent(inout) :: pos
222 type(macro), allocatable, intent(inout) :: macros(:)
223 type(context), intent(in) :: ctx
224 !private
225 integer :: condition, true_val, false_val
226
227 ! First parse condition at higher precedence
228 condition = parse_or(expr, tokens, ntokens, pos, macros, ctx)
229 if (pos > ntokens) then
230 val = condition
231 return
232 end if
233 ! Check for '?'
234 if (pos <= ntokens .and. tokens(pos)%value == '?') then
235 pos = pos + 1
236 ! Parse true expression (full expression allowed)
237 true_val = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
238
239 ! Expect ':'
240 if (pos > ntokens .or. tokens(pos)%value /= ':') then
241 call printf(render(diagnostic_report(level_error, &
242 message='Syntax error', &
243 label=label_type('Expected ":" in conditional expression', 1, len(expr)), &
244 source=trim(ctx%path)), &
245 expr, ctx%line))
246 val = 0
247 return
248 end if
249
250 pos = pos + 1
251
252 ! Parse false expression (right-associative)
253 false_val = parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
254
255 ! Evaluate condition
256 val = merge(true_val, false_val, condition /= 0)
257 else
258 val = condition
259 end if
260
261 end function
262
263 !> Parses logical OR expressions (`||`).
264 !! @param[in] expr Expression to be processed
265 !! @param[in] tokens Array of tokens to parse
266 !! @param[in] ntokens Number of valid tokens in the array
267 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
268 !! @param[inout] macros Defined macros for expansion and `defined()` checks
269 !! @param[in] ctx Context
270 !! @return Integer value of the parsed expression
271 !!
272 !! @b Remarks
273 !! @ingroup group_operators
274 recursive integer function parse_or(expr, tokens, ntokens, pos, macros, ctx) result(val)
275 character(*), intent(in) :: expr
276 type(token), intent(in) :: tokens(:)
277 integer, intent(in) :: ntokens
278 integer, intent(inout) :: pos
279 type(macro), allocatable, intent(inout) :: macros(:)
280 type(context), intent(in) :: ctx
281 !private
282 integer :: left
283
284 left = parse_and(expr, tokens, ntokens, pos, macros, ctx)
285 if (pos > ntokens) then
286 val = left
287 return
288 end if
289 do while (pos <= ntokens .and. tokens(pos)%value == '||')
290 pos = pos + 1
291 val = merge(1, 0, left /= 0 .or. parse_and(expr, tokens, ntokens, pos, macros, ctx) /= 0)
292 left = val
293 end do
294 val = left
295 end function
296
297 !> Parses logical AND expressions (`&&`).
298 !! @param[in] expr Expression to be processed
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[inout] macros Defined macros for expansion and `defined()` checks
303 !! @param[in] ctx Context
304 !! @return Integer value of the parsed expression
305 !!
306 !! @b Remarks
307 !! @ingroup group_operators
308 recursive integer function parse_and(expr, tokens, ntokens, pos, macros, ctx) result(val)
309 character(*), intent(in) :: expr
310 type(token), intent(in) :: tokens(:)
311 integer, intent(in) :: ntokens
312 integer, intent(inout) :: pos
313 type(macro), allocatable, intent(inout) :: macros(:)
314 type(context), intent(in) :: ctx
315 !private
316 integer :: left
317
318 left = parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx)
319 if (pos > ntokens) then
320 val = left
321 return
322 end if
323 do while (pos <= ntokens .and. tokens(pos)%value == '&&')
324 pos = pos + 1
325 val = merge(1, 0, left /= 0 .and. parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx) /= 0)
326 left = val
327 end do
328 val = left
329 end function
330
331 !> Parses bitwise OR expressions (`|`).
332 !! @param[in] expr Expression to be processed
333 !! @param[in] tokens Array of tokens to parse
334 !! @param[in] ntokens Number of valid tokens in the array
335 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
336 !! @param[inout] macros Defined macros for expansion and `defined()` checks
337 !! @param[in] ctx Context
338 !! @return Integer value of the parsed expression
339 !!
340 !! @b Remarks
341 !! @ingroup group_operators
342 recursive integer function parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx) result(val)
343 character(*), intent(in) :: expr
344 type(token), intent(in) :: tokens(:)
345 integer, intent(in) :: ntokens
346 integer, intent(inout) :: pos
347 type(macro), allocatable, intent(inout) :: macros(:)
348 type(context), intent(in) :: ctx
349 !private
350 integer :: left
351
352 left = parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
353 if (pos > ntokens) then
354 val = left
355 return
356 end if
357 do while (pos <= ntokens .and. tokens(pos)%value == '|')
358 pos = pos + 1
359 val = parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
360 left = ior(left, val)
361 end do
362 val = left
363 end function
364
365 !> Parses bitwise XOR expressions (`^`).
366 !! @param[in] expr Expression to be processed
367 !! @param[in] tokens Array of tokens to parse
368 !! @param[in] ntokens Number of valid tokens in the array
369 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
370 !! @param[inout] macros Defined macros for expansion and `defined()` checks
371 !! @param[in] ctx Context
372 !! @return Integer value of the parsed expression
373 !!
374 !! @b Remarks
375 !! @ingroup group_operators
376 recursive integer function parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx) result(val)
377 character(*), intent(in) :: expr
378 type(token), intent(in) :: tokens(:)
379 integer, intent(in) :: ntokens
380 integer, intent(inout) :: pos
381 type(macro), allocatable, intent(inout) :: macros(:)
382 type(context), intent(in) :: ctx
383 !private
384 integer :: left
385
386 left = parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
387 if (pos > ntokens) then
388 val = left
389 return
390 end if
391 do while (pos <= ntokens .and. tokens(pos)%value == '^')
392 pos = pos + 1
393 val = parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
394 left = ieor(left, val)
395 end do
396 val = left
397 end function
398
399 !> Parses bitwise AND expressions (`&`).
400 !! @param[in] expr Expression to be processed
401 !! @param[in] tokens Array of tokens to parse
402 !! @param[in] ntokens Number of valid tokens in the array
403 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
404 !! @param[inout] macros Defined macros for expansion and `defined()` checks
405 !! @param[in] ctx Context
406 !! @return Integer value of the parsed expression
407 !!
408 !! @b Remarks
409 !! @ingroup group_operators
410 recursive integer function parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx) result(val)
411 character(*), intent(in) :: expr
412 type(token), intent(in) :: tokens(:)
413 integer, intent(in) :: ntokens
414 integer, intent(inout) :: pos
415 type(macro), allocatable, intent(inout) :: macros(:)
416 type(context), intent(in) :: ctx
417 !private
418 integer :: left
419
420 left = parse_equality(expr, tokens, ntokens, pos, macros, ctx)
421 if (pos > ntokens) then
422 val = left
423 return
424 end if
425 do while (pos <= ntokens .and. tokens(pos)%value == '&')
426 pos = pos + 1
427 val = parse_equality(expr, tokens, ntokens, pos, macros, ctx)
428 left = iand(left, val)
429 end do
430 val = left
431 end function
432
433 !> Parses equality/inequality expressions (`==`, `!=`).
434 !! @param[in] expr Expression to be processed
435 !! @param[in] tokens Array of tokens to parse
436 !! @param[in] ntokens Number of valid tokens in the array
437 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
438 !! @param[inout] macros Defined macros for expansion and `defined()` checks
439 !! @param[in] ctx Context
440 !! @return Integer value of the parsed expression
441 !!
442 !! @b Remarks
443 !! @ingroup group_operators
444 recursive integer function parse_equality(expr, tokens, ntokens, pos, macros, ctx) result(val)
445 character(*), intent(in) :: expr
446 type(token), intent(in) :: tokens(:)
447 integer, intent(in) :: ntokens
448 integer, intent(inout) :: pos
449 type(macro), allocatable, intent(inout) :: macros(:)
450 type(context), intent(in) :: ctx
451 !private
452 integer :: left, right
453
454 left = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
455 if (pos > ntokens) then
456 val = left
457 return
458 end if
459 do while (pos <= ntokens .and. (tokens(pos)%value == '==' .or. tokens(pos)%value == '!='))
460 if (tokens(pos)%value == '==') then
461 pos = pos + 1
462 right = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
463 val = merge(1, 0, left == right)
464 else
465 pos = pos + 1
466 right = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
467 val = merge(1, 0, left /= right)
468 end if
469 left = val
470 end do
471 val = left
472 end function
473
474 !> Parses relational expressions (`<`, `>`, `<=`, `>=`).
475 !! @param[in] expr Expression to be processed
476 !! @param[in] tokens Array of tokens to parse
477 !! @param[in] ntokens Number of valid tokens in the array
478 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
479 !! @param[inout] macros Defined macros for expansion and `defined()` checks
480 !! @param[in] ctx Context
481 !! @return Integer value of the parsed expression
482 !!
483 !! @b Remarks
484 !! @ingroup group_operators
485 recursive integer function parse_relational(expr, tokens, ntokens, pos, macros, ctx) result(val)
486 character(*), intent(in) :: expr
487 type(token), intent(in) :: tokens(:)
488 integer, intent(in) :: ntokens
489 integer, intent(inout) :: pos
490 type(macro), allocatable, intent(inout) :: macros(:)
491 type(context), intent(in) :: ctx
492 !private
493 integer :: left, right
494
495 left = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
496 if (pos > ntokens) then
497 val = left
498 return
499 end if
500 do while (pos <= ntokens .and. (tokens(pos)%value == '<' .or. tokens(pos)%value == '>' .or. &
501 tokens(pos)%value == '<=' .or. tokens(pos)%value == '>='))
502 if (tokens(pos)%value == '<') then
503 pos = pos + 1
504 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
505 val = merge(1, 0, left < right)
506 else if (tokens(pos)%value == '>') then
507 pos = pos + 1
508 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
509 val = merge(1, 0, left > right)
510 else if (tokens(pos)%value == '<=') then
511 pos = pos + 1
512 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
513 val = merge(1, 0, left <= right)
514 else
515 pos = pos + 1
516 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
517 val = merge(1, 0, left >= right)
518 end if
519 left = val
520 end do
521 val = left
522 end function
523
524 !> Parses shift expressions (`<<`, `>>`).
525 !! @param[in] expr Expression to be processed
526 !! @param[in] tokens Array of tokens to parse
527 !! @param[in] ntokens Number of valid tokens in the array
528 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
529 !! @param[inout] macros Defined macros for expansion and `defined()` checks
530 !! @param[in] ctx Context
531 !! @return Integer value of the parsed expression
532 !!
533 !! @b Remarks
534 !! @ingroup group_operators
535 recursive integer function parse_shifting(expr, tokens, ntokens, pos, macros, ctx) result(val)
536 character(*), intent(in) :: expr
537 type(token), intent(in) :: tokens(:)
538 integer, intent(in) :: ntokens
539 integer, intent(inout) :: pos
540 type(macro), allocatable, intent(inout) :: macros(:)
541 type(context), intent(in) :: ctx
542 !private
543 integer :: left, right
544
545 left = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
546 if (pos > ntokens) then
547 val = left
548 return
549 end if
550 do while (pos <= ntokens .and. (tokens(pos)%value == '<<' .or. tokens(pos)%value == '>>'))
551 if (tokens(pos)%value == '<<') then
552 pos = pos + 1
553 right = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
554 val = lshift(left, right)
555 else
556 pos = pos + 1
557 right = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
558 val = rshift(left, right)
559 end if
560 left = val
561 end do
562 val = left
563 end function
564
565 !> Parses additive expressions (`+`, `-`).
566 !! @param[in] expr Expression to be processed
567 !! @param[in] tokens Array of tokens to parse
568 !! @param[in] ntokens Number of valid tokens in the array
569 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
570 !! @param[inout] macros Defined macros for expansion and `defined()` checks
571 !! @param[in] ctx Context
572 !! @return Integer value of the parsed expression
573 !!
574 !! @b Remarks
575 !! @ingroup group_operators
576 recursive integer function parse_additive(expr, tokens, ntokens, pos, macros, ctx) result(val)
577 character(*), intent(in) :: expr
578 type(token), intent(in) :: tokens(:)
579 integer, intent(in) :: ntokens
580 integer, intent(inout) :: pos
581 type(macro), allocatable, intent(inout) :: macros(:)
582 type(context), intent(in) :: ctx
583 !private
584 integer :: left, right
585
586 left = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
587 if (pos > ntokens) then
588 val = left
589 return
590 end if
591 do while (pos <= ntokens .and. (tokens(pos)%value == '+' .or. tokens(pos)%value == '-'))
592 if (tokens(pos)%value == '+') then
593 pos = pos + 1
594 right = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
595 val = left + right
596 else
597 pos = pos + 1
598 right = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
599 val = left - right
600 end if
601 left = val
602 end do
603 val = left
604 end function
605
606 !> Parses multiplicative expressions (`*`, `/`, `%`).
607 !! @param[in] expr Expression to be processed
608 !! @param[in] tokens Array of tokens to parse
609 !! @param[in] ntokens Number of valid tokens in the array
610 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
611 !! @param[inout] macros Defined macros for expansion and `defined()` checks
612 !! @param[in] ctx Context
613 !! @return Integer value of the parsed expression
614 !!
615 !! @b Remarks
616 !! @ingroup group_operators
617 recursive integer function parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx) result(val)
618 character(*), intent(in) :: expr
619 type(token), intent(in) :: tokens(:)
620 integer, intent(in) :: ntokens
621 integer, intent(inout) :: pos
622 type(macro), allocatable, intent(inout) :: macros(:)
623 type(context), intent(in) :: ctx
624 !private
625 integer :: left, right
626
627 left = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
628 if (pos > ntokens) then
629 val = left
630 return
631 end if
632 do while (pos <= ntokens .and. (tokens(pos)%value == '*' .or. tokens(pos)%value == '/' .or. tokens(pos)%value == '%'))
633 if (tokens(pos)%value == '*') then
634 pos = pos + 1
635 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
636 val = left * right
637 else if (tokens(pos)%value == '/') then
638 pos = pos + 1
639 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
640 val = left / right
641 else
642 pos = pos + 1
643 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
644 val = modulo(left, right)
645 end if
646 left = val
647 end do
648 val = left
649 end function
650
651 !> Parses exponentiation (`**`). Right-associative.
652 !! @param[in] expr Expression to be processed
653 !! @param[in] tokens Array of tokens to parse
654 !! @param[in] ntokens Number of valid tokens in the array
655 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
656 !! @param[inout] macros Defined macros for expansion and `defined()` checks
657 !! @param[in] ctx Context
658 !! @return Integer value of the parsed expression
659 !!
660 !! @b Remarks
661 !! @ingroup group_operators
662 recursive integer function parse_power(expr, tokens, ntokens, pos, macros, ctx) result(val)
663 character(*), intent(in) :: expr
664 type(token), intent(in) :: tokens(:)
665 integer, intent(in) :: ntokens
666 integer, intent(inout) :: pos
667 type(macro), allocatable, intent(inout) :: macros(:)
668 type(context), intent(in) :: ctx
669 !private
670 integer :: left, right
671
672 left = parse_atom(expr, tokens, ntokens, pos, macros, ctx)
673 if (pos > ntokens) then
674 val = left
675 return
676 end if
677 if (pos <= ntokens .and. tokens(pos)%value == '**') then
678 pos = pos + 1
679 ! recurse at same precedence level
680 right = parse_power(expr, tokens, ntokens, pos, macros, ctx)
681 val = left**right
682 else
683 val = left
684 end if
685 end function
686
687 !> Parses unary operators (`!`, `-`, `+`, `~`).
688 !! @param[in] expr Expression to be processed
689 !! @param[in] tokens Array of tokens to parse
690 !! @param[in] ntokens Number of valid tokens in the array
691 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
692 !! @param[inout] macros Defined macros for expansion and `defined()` checks
693 !! @param[in] ctx Context
694 !! @return Integer value of the parsed expression
695 !!
696 !! @b Remarks
697 !! @ingroup group_operators
698 recursive integer function parse_unary(expr, tokens, ntokens, pos, macros, ctx) result(val)
699 character(*), intent(in) :: expr
700 type(token), intent(in) :: tokens(:)
701 integer, intent(in) :: ntokens
702 integer, intent(inout) :: pos
703 type(macro), allocatable, intent(inout) :: macros(:)
704 type(context), intent(in) :: ctx
705
706 if (pos <= ntokens .and. tokens(pos)%value == '!') then
707 pos = pos + 1
708 val = merge(0, 1, parse_unary(expr, tokens, ntokens, pos, macros, ctx) /= 0)
709 else if (pos <= ntokens .and. tokens(pos)%value == '-') then
710 pos = pos + 1
711 val = -parse_unary(expr, tokens, ntokens, pos, macros, ctx)
712 else if (pos <= ntokens .and. tokens(pos)%value == '+') then
713 pos = pos + 1
714 val = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
715 else if (pos <= ntokens .and. tokens(pos)%value == '~') then
716 pos = pos + 1
717 val = not(parse_unary(expr, tokens, ntokens, pos, macros, ctx))
718 else
719 val = parse_power(expr, tokens, ntokens, pos, macros, ctx)
720 end if
721 end function
722
723 !> Parses primary expressions: numbers, identifiers, `defined(...)`, parentheses.
724 !! @param[in] expr Input expression
725 !! @param[in] tokens Array of tokens to parse
726 !! @param[in] ntokens Number of valid tokens in the array
727 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
728 !! @param[inout] macros Defined macros for expansion and `defined()` checks
729 !! @param[in] ctx Context
730 !! @return Integer value of the parsed expression
731 !!
732 !! @b Remarks
733 !! @ingroup group_operators
734 recursive integer function parse_atom(expr, tokens, ntokens, pos, macros, ctx) result(val)
735 character(*), intent(in) :: expr
736 type(token), intent(in) :: tokens(:)
737 integer, intent(in) :: ntokens
738 integer, intent(inout) :: pos
739 type(macro), allocatable, intent(inout) :: macros(:)
740 type(context), intent(in) :: ctx
741 !private
742 integer :: i
743 character(:), allocatable :: expanded
744 logical :: stitch
745
746 if (pos > ntokens) then
747 call printf(render(diagnostic_report(level_error, &
748 message='Syntax error', &
749 label=label_type('Unexpected end of expression', pos, 1), &
750 source=trim(ctx%path)), &
751 expr, ctx%line))
752 val = 0
753 return
754 end if
755
756 if (tokens(pos)%type == 0) then
757 val = strtol(tokens(pos)%value)
758 pos = pos + 1
759 else if (tokens(pos)%type == 2) then
760 if (is_defined(tokens(pos)%value, macros)) then
761 expanded = expand_macros(tokens(pos)%value, macros, stitch, global%implicit_continuation, &
762 global%support_dollar_insert, ctx)
763 if (.not. evaluate_expression(expanded, macros, ctx, val)) val = 0
764 else
765 val = 0
766 end if
767 pos = pos + 1
768 else if (tokens(pos)%value == '(') then
769 pos = pos + 1
770 val = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
771 if (pos > ntokens .or. tokens(pos)%value /= ')') then
772 call printf(render(diagnostic_report(level_error, &
773 message='Syntax error', &
774 label=label_type('Missing closing parenthesis in expression', len(expr), 1), &
775 source=trim(ctx%path)), &
776 expr, ctx%line))
777 val = 0
778 else
779 pos = pos + 1
780 end if
781 else if (tokens(pos)%type == 4) then
782 expanded = trim(tokens(pos)%value)
783 val = merge(1, 0, is_defined(expanded, macros))
784 pos = pos + 1
785 else
786 call printf(render(diagnostic_report(level_error, &
787 message='Invalid expression', &
788 label=label_type('Unknown token', 1, len_trim(tokens(pos)%value)), &
789 source=trim(ctx%path)), &
790 expr, ctx%line))
791 val = 0
792 pos = pos + 1
793 end if
794 end function
795end module
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
Definition global.f90:96
character(:) function, allocatable, public expand_macros(line, macros, stitch, implicit_conti, dollar_insert, ctx)
Core recursive macro expander (handles function-like, variadic, #, ##).
Definition macro.f90:344
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:710
recursive integer function, public parse_expression(expr, tokens, ntokens, pos, macros, ctx)
Parses a sequence of tokens starting at position pos as a full expression. Entry point for the recurs...
subroutine, public tokenize(expr, tokens, ntokens)
Tokenizes a preprocessor expression into an array of token structures. Handles whitespace,...
Definition token.f90:162
Interface to render diagnostic messages and labels.
Definition logging.f90:185
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
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
Converts a string to integer.
Definition token.f90:142
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
Represents a single token in a parsed expression. Holds the string value of the token and its classif...
Definition token.f90:106