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_power )*
39!! parse_power ? parse_unary ( '**' parse_unary )* (right-associative)
40!! parse_unary ? ('!' | '-' | '+' | '~') parse_unary
41!! | parse_atom
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
62!! call
63!! - @link fpx_operators::parse_expression parse_expression @endlink: low-level entry point for already-tokenized input
64!!
65!! This design guarantees correct operator precedence without the need for an explicit
66!! abstract syntax tree or stack-based shunting-yard algorithm, while remaining easy to
67!! read, maintain, and extend.
68module fpx_operators
69 use fpx_global
70 use fpx_string
71 use fpx_constants
72 use fpx_macro
73 use fpx_logging
74 use fpx_token
75 use fpx_context
76
77 implicit none; private
78
79 public :: evaluate_expression
80
82 module procedure :: evaluate_expression_default
83 module procedure :: evaluate_expression_with_context
84 end interface
85
86contains
87
88 !> Evaluates a preprocessor-style expression with macro substitution.
89 !! Tokenizes the input expression, expands macros where appropriate,
90 !! parses it according to operator precedence, and computes the integer result.
91 !! Returns .true. if evaluation succeeded and the result is non-zero.
92 !!
93 !! @param[in] expr Expression string to evaluate
94 !! @param[in] macros Array of defined macros for substitution and `defined()` checks
95 !! @param[out] val (optional) integer result of the evaluation
96 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
97 !!
98 !! @b Remarks
99 !! @ingroup group_operators
100 logical function evaluate_expression_default(expr, macros, val) result(res)
101 character(*), intent(in) :: expr
102 type(macro), intent(in) :: macros(:)
103 integer, intent(out), optional :: val
104 !private
105 type(context) :: ctx
106
107 ctx = context(expr, 1, '')
108 res = evaluate_expression(expr, macros, ctx, val)
109 end function
110
111 !> Evaluates a preprocessor-style expression with macro substitution.
112 !! Tokenizes the input expression, expands macros where appropriate,
113 !! parses it according to operator precedence, and computes the integer result.
114 !! Returns .true. if evaluation succeeded and the result is non-zero.
115 !!
116 !! @param[in] expr Expression string to evaluate
117 !! @param[in] macros Array of defined macros for substitution and `defined()` checks
118 !! @param[in] ctx Context
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_with_context(expr, macros, ctx, val) result(res)
125 character(*), intent(in) :: expr
126 type(macro), intent(in) :: macros(:)
127 type(context), intent(in) :: ctx
128 integer, intent(out), optional :: val
129 !private
130 type(token), allocatable :: tokens(:)
131 integer :: ntokens, pos, result
132
133 call tokenize(expr, tokens, ntokens)
134 if (ntokens == 0) then
135 call printf(render(diagnostic_report(level_error, &
136 message='Tokenization failed', &
137 label=label_type('No tokens found', 1, len_trim(expr)), &
138 source=trim(ctx%path)), &
139 expr, ctx%line))
140 res = .false.
141 return
142 end if
143
144 pos = 1
145 result = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
146 if (pos <= ntokens) then
147 call printf(render(diagnostic_report(level_error, &
148 message='Tokenization failed', &
149 label=label_type('Extra tokens found', tokens(pos)%start, len_trim(tokens(pos)%value)), &
150 source=trim(ctx%path)), &
151 expr, ctx%line))
152 res = .false.
153 return
154 end if
155 res = (result /= 0)
156 if (present(val)) val = result
157 end function
158
159 !> Parses a sequence of tokens starting at position `pos` as a full expression.
160 !! Entry point for the recursive descent parser. Delegates to parse_or().
161 !! @param[in] expr Expression to be processed
162 !! @param[in] tokens Array of tokens to parse
163 !! @param[in] ntokens Number of valid tokens in the array
164 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
165 !! @param[in] macros Defined macros for expansion and `defined()` checks
166 !! @param[in] ctx Context
167 !! @return Integer value of the parsed expression
168 !!
169 !! @b Remarks
170 !! @ingroup group_operators
171 recursive integer function parse_expression(expr, tokens, ntokens, pos, macros, ctx) result(val)
172 character(*), intent(in) :: expr
173 type(token), intent(in) :: tokens(:)
174 integer, intent(in) :: ntokens
175 integer, intent(inout) :: pos
176 type(macro), intent(in) :: macros(:)
177 type(context), intent(in) :: ctx
178
179 val = parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
180 end function
181
182 !> Parses conditional expressions (?:). Right-associative.
183 !! @param[in] expr Expression to be processed
184 !! @param[in] tokens Array of tokens to parse
185 !! @param[in] ntokens Number of valid tokens in the array
186 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
187 !! @param[in] macros Defined macros for expansion and `defined()` checks
188 !! @param[in] ctx Context
189 !! @return Integer value of the parsed expression
190 !!
191 !! @b Remarks
192 !! @ingroup group_operators
193 recursive integer function parse_conditional(expr, tokens, ntokens, pos, macros, ctx) result(val)
194 character(*), intent(in) :: expr
195 type(token), intent(in) :: tokens(:)
196 integer, intent(in) :: ntokens
197 integer, intent(inout) :: pos
198 type(macro), intent(in) :: macros(:)
199 type(context), intent(in) :: ctx
200 !private
201 integer :: condition, true_val, false_val
202
203 ! First parse condition at higher precedence
204 condition = parse_or(expr, tokens, ntokens, pos, macros, ctx)
205
206 ! Check for '?'
207 if (pos <= ntokens .and. tokens(pos)%value == '?') then
208 pos = pos + 1
209 ! Parse true expression (full expression allowed)
210 true_val = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
211
212 ! Expect ':'
213 if (pos > ntokens .or. tokens(pos)%value /= ':') then
214 call printf(render(diagnostic_report(level_error, &
215 message='Synthax error', &
216 label=label_type('Expected ":" in conditional expression', 1, len(expr)), &
217 source=trim(ctx%path)), &
218 expr, ctx%line))
219 val = 0
220 return
221 end if
222
223 pos = pos + 1
224
225 ! Parse false expression (right-associative)
226 false_val = parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
227
228 ! Evaluate condition
229 val = merge(true_val, false_val, condition /= 0)
230 else
231 val = condition
232 end if
233
234 end function
235
236 !> Parses logical OR expressions (`||`).
237 !! @param[in] expr Expression to be processed
238 !! @param[in] tokens Array of tokens to parse
239 !! @param[in] ntokens Number of valid tokens in the array
240 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
241 !! @param[in] macros Defined macros for expansion and `defined()` checks
242 !! @param[in] ctx Context
243 !! @return Integer value of the parsed expression
244 !!
245 !! @b Remarks
246 !! @ingroup group_operators
247 recursive integer function parse_or(expr, tokens, ntokens, pos, macros, ctx) result(val)
248 character(*), intent(in) :: expr
249 type(token), intent(in) :: tokens(:)
250 integer, intent(in) :: ntokens
251 integer, intent(inout) :: pos
252 type(macro), intent(in) :: macros(:)
253 type(context), intent(in) :: ctx
254 !private
255 integer :: left
256
257 left = parse_and(expr, tokens, ntokens, pos, macros, ctx)
258 do while (pos <= ntokens .and. tokens(pos)%value == '||')
259 pos = pos + 1
260 val = merge(1, 0, left /= 0 .or. parse_and(expr, tokens, ntokens, pos, macros, ctx) /= 0)
261 left = val
262 end do
263 val = left
264 end function
265
266 !> Parses logical AND expressions (`&&`).
267 !! @param[in] expr Expression to be processed
268 !! @param[in] tokens Array of tokens to parse
269 !! @param[in] ntokens Number of valid tokens in the array
270 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
271 !! @param[in] macros Defined macros for expansion and `defined()` checks
272 !! @param[in] ctx Context
273 !! @return Integer value of the parsed expression
274 !!
275 !! @b Remarks
276 !! @ingroup group_operators
277 recursive integer function parse_and(expr, tokens, ntokens, pos, macros, ctx) result(val)
278 character(*), intent(in) :: expr
279 type(token), intent(in) :: tokens(:)
280 integer, intent(in) :: ntokens
281 integer, intent(inout) :: pos
282 type(macro), intent(in) :: macros(:)
283 type(context), intent(in) :: ctx
284 !private
285 integer :: left
286
287 left = parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx)
288 do while (pos <= ntokens .and. tokens(pos)%value == '&&')
289 pos = pos + 1
290 val = merge(1, 0, left /= 0 .and. parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx) /= 0)
291 left = val
292 end do
293 val = left
294 end function
295
296 !> Parses bitwise OR expressions (`|`).
297 !! @param[in] expr Expression to be processed
298 !! @param[in] tokens Array of tokens to parse
299 !! @param[in] ntokens Number of valid tokens in the array
300 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
301 !! @param[in] macros Defined macros for expansion and `defined()` checks
302 !! @param[in] ctx Context
303 !! @return Integer value of the parsed expression
304 !!
305 !! @b Remarks
306 !! @ingroup group_operators
307 recursive integer function parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx) result(val)
308 character(*), intent(in) :: expr
309 type(token), intent(in) :: tokens(:)
310 integer, intent(in) :: ntokens
311 integer, intent(inout) :: pos
312 type(macro), intent(in) :: macros(:)
313 type(context), intent(in) :: ctx
314 !private
315 integer :: left
316
317 left = parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
318 do while (pos <= ntokens .and. tokens(pos)%value == '|')
319 pos = pos + 1
320 val = parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
321 left = ior(left, val)
322 end do
323 val = left
324 end function
325
326 !> Parses bitwise XOR expressions (`^`).
327 !! @param[in] expr Expression to be processed
328 !! @param[in] tokens Array of tokens to parse
329 !! @param[in] ntokens Number of valid tokens in the array
330 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
331 !! @param[in] macros Defined macros for expansion and `defined()` checks
332 !! @param[in] ctx Context
333 !! @return Integer value of the parsed expression
334 !!
335 !! @b Remarks
336 !! @ingroup group_operators
337 recursive integer function parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx) result(val)
338 character(*), intent(in) :: expr
339 type(token), intent(in) :: tokens(:)
340 integer, intent(in) :: ntokens
341 integer, intent(inout) :: pos
342 type(macro), intent(in) :: macros(:)
343 type(context), intent(in) :: ctx
344 !private
345 integer :: left
346
347 left = parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
348 do while (pos <= ntokens .and. tokens(pos)%value == '^')
349 pos = pos + 1
350 val = parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
351 left = ieor(left, val)
352 end do
353 val = left
354 end function
355
356 !> Parses bitwise AND expressions (`&`).
357 !! @param[in] expr Expression to be processed
358 !! @param[in] tokens Array of tokens to parse
359 !! @param[in] ntokens Number of valid tokens in the array
360 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
361 !! @param[in] macros Defined macros for expansion and `defined()` checks
362 !! @param[in] ctx Context
363 !! @return Integer value of the parsed expression
364 !!
365 !! @b Remarks
366 !! @ingroup group_operators
367 recursive integer function parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx) result(val)
368 character(*), intent(in) :: expr
369 type(token), intent(in) :: tokens(:)
370 integer, intent(in) :: ntokens
371 integer, intent(inout) :: pos
372 type(macro), intent(in) :: macros(:)
373 type(context), intent(in) :: ctx
374 !private
375 integer :: left
376
377 left = parse_equality(expr, tokens, ntokens, pos, macros, ctx)
378 do while (pos <= ntokens .and. tokens(pos)%value == '&')
379 pos = pos + 1
380 val = parse_equality(expr, tokens, ntokens, pos, macros, ctx)
381 left = iand(left, val)
382 end do
383 val = left
384 end function
385
386 !> Parses equality/inequality expressions (`==`, `!=`).
387 !! @param[in] expr Expression to be processed
388 !! @param[in] tokens Array of tokens to parse
389 !! @param[in] ntokens Number of valid tokens in the array
390 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
391 !! @param[in] macros Defined macros for expansion and `defined()` checks
392 !! @param[in] ctx Context
393 !! @return Integer value of the parsed expression
394 !!
395 !! @b Remarks
396 !! @ingroup group_operators
397 recursive integer function parse_equality(expr, tokens, ntokens, pos, macros, ctx) result(val)
398 character(*), intent(in) :: expr
399 type(token), intent(in) :: tokens(:)
400 integer, intent(in) :: ntokens
401 integer, intent(inout) :: pos
402 type(macro), intent(in) :: macros(:)
403 type(context), intent(in) :: ctx
404 !private
405 integer :: left, right
406
407 left = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
408 do while (pos <= ntokens .and. (tokens(pos)%value == '==' .or. tokens(pos)%value == '!='))
409 if (tokens(pos)%value == '==') then
410 pos = pos + 1
411 right = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
412 val = merge(1, 0, left == right)
413 else
414 pos = pos + 1
415 right = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
416 val = merge(1, 0, left /= right)
417 end if
418 left = val
419 end do
420 val = left
421 end function
422
423 !> Parses relational expressions (`<`, `>`, `<=`, `>=`).
424 !! @param[in] expr Expression to be processed
425 !! @param[in] tokens Array of tokens to parse
426 !! @param[in] ntokens Number of valid tokens in the array
427 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
428 !! @param[in] macros Defined macros for expansion and `defined()` checks
429 !! @param[in] ctx Context
430 !! @return Integer value of the parsed expression
431 !!
432 !! @b Remarks
433 !! @ingroup group_operators
434 recursive integer function parse_relational(expr, tokens, ntokens, pos, macros, ctx) result(val)
435 character(*), intent(in) :: expr
436 type(token), intent(in) :: tokens(:)
437 integer, intent(in) :: ntokens
438 integer, intent(inout) :: pos
439 type(macro), intent(in) :: macros(:)
440 type(context), intent(in) :: ctx
441 !private
442 integer :: left, right
443
444 left = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
445 do while (pos <= ntokens .and. (tokens(pos)%value == '<' .or. tokens(pos)%value == '>' .or. &
446 tokens(pos)%value == '<=' .or. tokens(pos)%value == '>='))
447 if (tokens(pos)%value == '<') then
448 pos = pos + 1
449 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
450 val = merge(1, 0, left < right)
451 else if (tokens(pos)%value == '>') then
452 pos = pos + 1
453 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
454 val = merge(1, 0, left > right)
455 else if (tokens(pos)%value == '<=') then
456 pos = pos + 1
457 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
458 val = merge(1, 0, left <= right)
459 else
460 pos = pos + 1
461 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
462 val = merge(1, 0, left >= right)
463 end if
464 left = val
465 end do
466 val = left
467 end function
468
469 !> Parses shift expressions (`<<`, `>>`).
470 !! @param[in] expr Expression to be processed
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 !! @param[in] ctx Context
476 !! @return Integer value of the parsed expression
477 !!
478 !! @b Remarks
479 !! @ingroup group_operators
480 recursive integer function parse_shifting(expr, tokens, ntokens, pos, macros, ctx) result(val)
481 character(*), intent(in) :: expr
482 type(token), intent(in) :: tokens(:)
483 integer, intent(in) :: ntokens
484 integer, intent(inout) :: pos
485 type(macro), intent(in) :: macros(:)
486 type(context), intent(in) :: ctx
487 !private
488 integer :: left, right
489
490 left = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
491 do while (pos <= ntokens .and. (tokens(pos)%value == '<<' .or. tokens(pos)%value == '>>'))
492 if (tokens(pos)%value == '<<') then
493 pos = pos + 1
494 right = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
495 val = lshift(left, right)
496 else
497 pos = pos + 1
498 right = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
499 val = rshift(left, right)
500 end if
501 left = val
502 end do
503 val = left
504 end function
505
506 !> Parses additive expressions (`+`, `-`).
507 !! @param[in] expr Expression to be processed
508 !! @param[in] tokens Array of tokens to parse
509 !! @param[in] ntokens Number of valid tokens in the array
510 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
511 !! @param[in] macros Defined macros for expansion and `defined()` checks
512 !! @param[in] ctx Context
513 !! @return Integer value of the parsed expression
514 !!
515 !! @b Remarks
516 !! @ingroup group_operators
517 recursive integer function parse_additive(expr, tokens, ntokens, pos, macros, ctx) result(val)
518 character(*), intent(in) :: expr
519 type(token), intent(in) :: tokens(:)
520 integer, intent(in) :: ntokens
521 integer, intent(inout) :: pos
522 type(macro), intent(in) :: macros(:)
523 type(context), intent(in) :: ctx
524 !private
525 integer :: left, right
526
527 left = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
528 do while (pos <= ntokens .and. (tokens(pos)%value == '+' .or. tokens(pos)%value == '-'))
529 if (tokens(pos)%value == '+') then
530 pos = pos + 1
531 right = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
532 val = left + right
533 else
534 pos = pos + 1
535 right = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
536 val = left - right
537 end if
538 left = val
539 end do
540 val = left
541 end function
542
543 !> Parses multiplicative expressions (`*`, `/`, `%`).
544 !! @param[in] expr Expression to be processed
545 !! @param[in] tokens Array of tokens to parse
546 !! @param[in] ntokens Number of valid tokens in the array
547 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
548 !! @param[in] macros Defined macros for expansion and `defined()` checks
549 !! @param[in] ctx Context
550 !! @return Integer value of the parsed expression
551 !!
552 !! @b Remarks
553 !! @ingroup group_operators
554 recursive integer function parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx) result(val)
555 character(*), intent(in) :: expr
556 type(token), intent(in) :: tokens(:)
557 integer, intent(in) :: ntokens
558 integer, intent(inout) :: pos
559 type(macro), intent(in) :: macros(:)
560 type(context), intent(in) :: ctx
561 !private
562 integer :: left, right
563
564 left = parse_power(expr, tokens, ntokens, pos, macros, ctx)
565 do while (pos <= ntokens .and. (tokens(pos)%value == '*' .or. tokens(pos)%value == '/' .or. tokens(pos)%value == '%'))
566 if (tokens(pos)%value == '*') then
567 pos = pos + 1
568 right = parse_power(expr, tokens, ntokens, pos, macros, ctx)
569 val = left * right
570 else if (tokens(pos)%value == '/') then
571 pos = pos + 1
572 right = parse_power(expr, tokens, ntokens, pos, macros, ctx)
573 val = left / right
574 else
575 pos = pos + 1
576 right = parse_power(expr, tokens, ntokens, pos, macros, ctx)
577 val = modulo(left, right)
578 end if
579 left = val
580 end do
581 val = left
582 end function
583
584 !> Parses exponentiation (`**`). Right-associative.
585 !! @param[in] expr Expression to be processed
586 !! @param[in] tokens Array of tokens to parse
587 !! @param[in] ntokens Number of valid tokens in the array
588 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
589 !! @param[in] macros Defined macros for expansion and `defined()` checks
590 !! @param[in] ctx Context
591 !! @return Integer value of the parsed expression
592 !!
593 !! @b Remarks
594 !! @ingroup group_operators
595 recursive integer function parse_power(expr, tokens, ntokens, pos, macros, ctx) result(val)
596 character(*), intent(in) :: expr
597 type(token), intent(in) :: tokens(:)
598 integer, intent(in) :: ntokens
599 integer, intent(inout) :: pos
600 type(macro), intent(in) :: macros(:)
601 type(context), intent(in) :: ctx
602 !private
603 integer :: left, right
604
605 left = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
606 do while (pos <= ntokens .and. (tokens(pos)%value == '**'))
607 pos = pos + 1
608 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
609 val = left**right
610 left = val
611 end do
612 val = left
613 end function
614
615 !> Parses unary operators (`!`, `-`, `+`, `~`).
616 !! @param[in] expr Expression to be processed
617 !! @param[in] tokens Array of tokens to parse
618 !! @param[in] ntokens Number of valid tokens in the array
619 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
620 !! @param[in] macros Defined macros for expansion and `defined()` checks
621 !! @param[in] ctx Context
622 !! @return Integer value of the parsed expression
623 !!
624 !! @b Remarks
625 !! @ingroup group_operators
626 recursive integer function parse_unary(expr, tokens, ntokens, pos, macros, ctx) result(val)
627 character(*), intent(in) :: expr
628 type(token), intent(in) :: tokens(:)
629 integer, intent(in) :: ntokens
630 integer, intent(inout) :: pos
631 type(macro), intent(in) :: macros(:)
632 type(context), intent(in) :: ctx
633
634 if (pos <= ntokens .and. tokens(pos)%value == '!') then
635 pos = pos + 1
636 val = merge(0, 1, parse_unary(expr, tokens, ntokens, pos, macros, ctx) /= 0)
637 else if (pos <= ntokens .and. tokens(pos)%value == '-') then
638 pos = pos + 1
639 val = -parse_unary(expr, tokens, ntokens, pos, macros, ctx)
640 else if (pos <= ntokens .and. tokens(pos)%value == '+') then
641 pos = pos + 1
642 val = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
643 else if (pos <= ntokens .and. tokens(pos)%value == '~') then
644 pos = pos + 1
645 val = not(parse_unary(expr, tokens, ntokens, pos, macros, ctx))
646 else
647 val = parse_atom(expr, tokens, ntokens, pos, macros, ctx)
648 end if
649 end function
650
651 !> Parses primary expressions: numbers, identifiers, `defined(...)`, parentheses.
652 !! @param[in] expr Input expression
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[in] 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_atom(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), intent(in) :: macros(:)
668 type(context), intent(in) :: ctx
669 !private
670 integer :: i
671 character(:), allocatable :: expanded
672 logical :: stitch
673
674 if (pos > ntokens) then
675 call printf(render(diagnostic_report(level_error, &
676 message='Synthax error', &
677 label=label_type('Unexpected end of expression', pos, 1), &
678 source=trim(ctx%path)), &
679 expr, ctx%line))
680 val = 0
681 return
682 end if
683
684 if (tokens(pos)%type == 0) then
685 val = strtol(tokens(pos)%value)
686 pos = pos + 1
687 else if (tokens(pos)%type == 2) then
688 if (is_defined(tokens(pos)%value, macros)) then
689 expanded = expand_macros(tokens(pos)%value, macros, stitch, global%implicit_continuation, ctx)
690 if (.not. evaluate_expression(expanded, macros, ctx, val)) val = 0
691 else
692 val = 0
693 end if
694 pos = pos + 1
695 else if (tokens(pos)%value == '(') then
696 pos = pos + 1
697 val = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
698 if (pos > ntokens .or. tokens(pos)%value /= ')') then
699 call printf(render(diagnostic_report(level_error, &
700 message='Synthax error', &
701 label=label_type('Missing closing parenthesis in expression', len(expr), 1), &
702 source=trim(ctx%path)), &
703 expr, ctx%line))
704 val = 0
705 else
706 pos = pos + 1
707 end if
708 else if (tokens(pos)%type == 4) then
709 expanded = trim(tokens(pos)%value)
710 val = merge(1, 0, is_defined(expanded, macros))
711 pos = pos + 1
712 else
713 call printf(render(diagnostic_report(level_error, &
714 message='Invalid expression', &
715 label=label_type('Unknown token', 1, len_trim(tokens(pos)%value)), &
716 source=trim(ctx%path)), &
717 expr, ctx%line))
718 val = 0
719 pos = pos + 1
720 end if
721 end function
722end module
type(global_settings), public global
The single global instance used throughout fpx Initialized automatically with sensible defaults value...
Definition global.f90:93
character(:) function, allocatable, public expand_macros(line, macros, stitch, implicit_conti, ctx)
Core recursive macro expander (handles function-like, variadic, #, ##).
Definition macro.f90:300
logical function, public is_defined(name, macros, idx)
Check if a macro with given name exists in table.
Definition macro.f90:625
recursive integer function parse_power(expr, tokens, ntokens, pos, macros, ctx)
Parses exponentiation (**). Right-associative.
recursive integer function parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx)
Parses bitwise OR expressions (|).
recursive integer function parse_additive(expr, tokens, ntokens, pos, macros, ctx)
Parses additive expressions (+, -).
recursive integer function parse_unary(expr, tokens, ntokens, pos, macros, ctx)
Parses unary operators ( @param[in] expr Expression to be processed @param[in] tokens Array of tokens...
recursive integer function parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
Parses bitwise XOR expressions (^).
recursive integer function parse_atom(expr, tokens, ntokens, pos, macros, ctx)
Parses primary expressions: numbers, identifiers, defined(...), parentheses.
recursive integer function 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...
recursive integer function parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
Parses bitwise AND expressions (&).
recursive integer function parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
Parses shift expressions (<<, >>).
logical function evaluate_expression_with_context(expr, macros, ctx, val)
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
recursive integer function parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
Parses conditional expressions (?:). Right-associative.
recursive integer function parse_relational(expr, tokens, ntokens, pos, macros, ctx)
Parses relational expressions (<, >, <=, >=).
recursive integer function parse_or(expr, tokens, ntokens, pos, macros, ctx)
Parses logical OR expressions (||).
recursive integer function parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
Parses multiplicative expressions (*, /, %).
logical function evaluate_expression_default(expr, macros, val)
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
recursive integer function parse_and(expr, tokens, ntokens, pos, macros, ctx)
Parses logical AND expressions (&&).
recursive integer function parse_equality(expr, tokens, ntokens, pos, macros, ctx)
Parses equality/inequality expressions (==, @param[in] expr Expression to be processed @param[in] to...
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
Return the trimmed length of a string.
Definition string.f90:141
Return the length of a string.
Definition string.f90:133
Return the trimmed string.
Definition string.f90:149
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:94
Represents a single token in a parsed expression. Holds the string value of the token and its classif...
Definition token.f90:106