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